#!/usr/bin/perl -w
#
# generate-conf -- Generate configuration file from data and a template
#
# Written by Xueshan Feng <sfeng@stanford.edu>
# Copyright 2010, 2013
#     The Board of Trustees of the Leland Stanford Junior University

use 5.006;
use strict;
use warnings;

use Config::Simple;
use Getopt::Long;

#######
# Main
#######
{

    # Clean up the program name for error reporting.
    $0 =~ s%.*/%%;

    my ( $configfile, $template, $newfile, $dryrun ) = get_opts();
    my $config = new Config::Simple("$configfile") or die Config::Simple->error();

    my $tmpl = read_file($template);
    my $newcontent = filter_file( $tmpl, $config );
    $dryrun ? print $newcontent : write_file( $newcontent, $newfile );
}

exit 0;

#############
# Subroutines
#############

# Filter file based on template and key values.
sub filter_file {
    my ( $tmpl, $config ) = @_;

    # Get all variables into a hash
    my %variables = $config->vars();

    foreach my $k ( keys %variables ) {
        my $v = $variables{$k};

        # Variables in Config::Simple has 'default.' prefix. remove it
        $k =~ s/default\.//;
        $tmpl =~ s#%%$k%%#$v#g;
    }
    return $tmpl;
}

# Read template file into a string variable so we can do global variable
# substitution later
sub read_file {
    my ($file) = @_;
    my $data;
    local $/;
    open( FILE, $file ) or printerr("Cannot open $file:$!\n");
    $data = <FILE>;
    close FILE;
    return $data;
}

# Write out the generated file
sub write_file {
    my ( $filestring, $file ) = @_;

    open( DES, ">$file" ) or die "Cannot open $file:$!\n";
    print DES $filestring;
}

# Get options
sub get_opts {

    my %options;

    # Set desired options for getopt call
    Getopt::Long::config('bundling');

    # Read command line options
    GetOptions(
        \%options,

        # information options
        'help|h',

        # data file name containing simple key=value
        'config|c=s',

        # template file, with variables matching the ones in keyfile
        'template|t=s',

        # new file to be generated 
        'newfile|n=s',

        # dry run
        'dryrun|d',
    );

    # Give usage, if requested
    usage() if $options{'help'};
    my $config   = $options{'config'}   or printerr("Data configuration file required\n");
    my $template = $options{'template'} or printerr("Template file required\n");
    my $dryrun   = $options{'dryrun'};
    my $newfile = $options{'newfile'}
      or printerr("New file name required\n")
      unless $dryrun;

    printerr("$config file doesn't exist.") unless -f $config;
    printerr("$template file doesn't exist.") unless -f $template;
    return $config, $template, $newfile, $dryrun;
}

# Print error message and bail out
sub printerr {
    my ($msg) = @_;
    die "$0: $msg\n";
}

# Usage
sub usage {
    exec( 'perldoc', '-t', $0 ) or die "$0: cannot fork: $!\n";
}

__END__

# Documentation

=for stopwords
Feng INI Xueshan generate-conf --dryrun --newfile newfile

=head1 NAME

generate-conf - Generate configuration file from data and a template

=head1 SYNOPSIS

B<generate-conf> [B<--dryrun>] B<--template> I<file> B<--config> I<file>
    B<--newfile> I<newfile>

=head1 DESCRIPTION 

B<generate-conf> constructs a new file based on a data file containing
key=value pairs and a template. The new file has the same content as the
template but any variables matching a key in the data file are replaced
with the corresponding values. Variables in the template should be quoted
by '%%'.  See EXAMPLE below.

=head1 EXAMPLE

  Data configuration file '/tmp/config':

  MYSQLUSER = foobar
  MYSQLPASS = secrect

 Template file '/tmp/template':

  # This is a template file
  MYSQLUSER = %%MYSQLUSER%%
  MYSQLPASS = %%MYSQLPASS%%
  MYSQLHOST = mysql-app.stanford.edu
  .. other configurations ..

 Run:
  generate-conf --template /tmp/template --config /tmp/config
       --newfile /tmp/newconfig

 It will generate new file '/tmp/newconfig':

  # This is a template file
  MYSQLUSER = foobar
  MYSQLPASS = secret
  MYSQLHOST = mysql-app.stanford.edu
  .. other configurations ..

=head1 CAVEAT

The syntax for the data file containing key=value pairs should be in simple, 
key=value INI format. The script doesn't support block format yet.

=head1 AUTHOR

Xueshan Feng (sfeng@stanford.edu)

=cut
