#!/usr/bin/perl

# libconf standalone binary

# Copyright (C) 2003-2005 Damien Krotkine (dams@libconf.net)
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; only version 2, no any other version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

#we'll use this to understand command line options
use Getopt::Long;

my $VERSION = 0.01;
#options setup
my ($help_arg, $version_arg, $action_arg, $if_arg, $of_arg, $template_arg, $list_template_arg, $list_template_options_arg);
$result = GetOptions("action=s" => \$action_arg,
                     "if=s" => \$if_arg,
                     "of=s"  => \$of_arg,
                     "template=s" => \$template_arg,
                     "template-options=s" => \$template_options_arg,
                     "list-templates" => \$list_template_arg,
                     "list-templates-options" => \$list_template_options_arg,
                     "xml-format=i" => \$xml_format_arg,
                     "help" => \$help_arg,
                     "version" => \$version_arg,
                     "commands=s" => \$commands_arg,
                    );

#print "DEBUG : [$action_arg] [$if_arg] [$of_arg] [$help_arg] [$list_template_arg] [$template_options_arg]\n";

# help -> usage
$help_arg and usage();
$version_arg and version();
# list templates
if ($list_template_arg) {
    foreach (list_templates()) {
        my $a = $_; $a =~ s|/|::|g;
#        print "***********" . "require Libconf::Glueconf::$a; Libconf::Glueconf::$a" . "::description()" . "\n";
        print "$_ - " . eval (qq(require Libconf::Glueconf::$a; Libconf::Glueconf::$a) . qq(::description())) . "\n";
    }
    exit 0;
}
#if the options are not correct, we print usage and exit.
#my $err_mesg = check_options();
#$err_mesg and print ("\nERROR : $err_mesg \n\n-----------------\n"), usage();

if ($action_arg eq 'export_xml') {
    my $t = $template_arg;
    $t =~ s|/|::|g;
    my $template_class = "Libconf::Glueconf::$t";
    my $obj;
    my $xml;
    my $template_options = check_template_options($template_options_arg);
#    print "*************" . qq( require $template_class; import $template_class; \$obj = new $template_class({ filename => "$if_arg", $template_options }); ) . "\n";
        eval qq( require $template_class; import $template_class; \$obj=new $template_class({ filename => "$if_arg", $template_options }););
    eval {
        $of_arg eq '-' ? $obj->toXMLString() : $obj->toXMLFile($of_arg);
    };
    if ($@) {
        my $errmsg = $@;
        chomp $errmsg;
        print("an error occured, check the following message : \`\`$errmsg''\ntry libconf --help\n\n");
        exit 1;
    }
}

if ($action_arg eq 'import_xml') {
    my $t = $template_arg;
    $t =~ s|/|::|g;
    my $template_class = "Libconf::Glueconf::$t";
    my $obj;
    my $xml;
    my $template_options = check_template_options($template_options_arg);
#    print "*************" . qq( require $template_class; import $template_class; \$obj = new $template_class({ filename => "$if_arg", $template_options }); ) . "\n";
    eval qq( require $template_class; import $template_class; \$obj = new $template_class({ filename => "", $template_options }); );
    $if_arg eq '-' ? die 'not yet implemented, sorry :/': $obj->fromXMLFile($if_arg);
    $of_arg eq '-' ? die 'not yet implemented, sorry :/': $obj->write_conf($of_arg);
}

###############----------------

my %commands = (
                quit => [ 'exits this interactive console' ],
                help => [ 'gives help on general use or for a particular command',
                          'help [COMMAND] : displays help on the COMMAND. If COMMAND is not defined, displays a general help message',
                        ],
                version => [ 'displays the version of the software', ],
                status => [ 'displays the current status (loaded config files, and various details)' ],
                ls => [ 'same as status' ],
                load_from_file => [ 'loads a config file, so that you can work on it',
                                    'load_from_file FILENAME TEMPLATE_NAME [TEMPLATE_OPTIONS]
FILENAME is the configuration file to load
TEMPLATE_NAME is the template to use to parse it. You can get a list with list_templates (see help)
TEMPLATE_OPTIONS are the options to pass to the template. You can get a list of them with list_template_options (see help)'
                                  ],
                load_from_xml => [ 'loads an xml config file, so that you can work on it',
                                    'load_from_xml FILENAME TEMPLATE_NAME [TEMPLATE_OPTIONS]
FILENAME is the configuration file to load
TEMPLATE_NAME is the template to use to parse it. You can get a list with list_templates (see help)
TEMPLATE_OPTIONS are the options to pass to the template. You can get a list of them with list_template_options (see help)'
                                  ],
                save_to_file => ['saves the currently loaded config informations into a configuration file',
                                 'save_to_file [FILENAME]
FILENAME is the configuration file to save to. If not set, the default configuration file name (the one used when loading from a file for instance) is used'
                                 ],
                save_to_xml => ['saves the currently loaded config informations into a xml syntax file',
                                'save_to_xml [FILENAME]
FILENAME is the configuration file to save to. If not set, the default configuration file name (the one used when loading from a file for instance) is used'
                               ],
                list_templates => [ 'get a list of available templates',
                                    'displays a list of templates to use as TEMPLATE_NAME in load_from_file, load_from_xml, list_template_options'
                                  ],
                list_template_options => [ 'get the options of a given template',
                                           'list_template_options TEMPLATE_NAME : returns the options of TEMPLATE_NAME. You can get a list of templates with list_templates (see help)']
               );

use Term::ReadLine;
use Text::ParseWords;

# if called with --commands 
if ($commands_arg) {
    print "\n" . parse_command($commands_arg);
    exit 0;
}


my $term = new Term::ReadLine 'Libconf Interactive Console';
my $prompt = "libconf >";
my $OUT = $term->OUT || \*STDOUT;
print $OUT "\nEnter your commands or `help'\n";
while ( defined ($_ = $term->readline($prompt)) ) {
    my $func;
    print $OUT "\n" . parse_command($_);
    $term->addhistory($_) if /\S/;
}

sub parse_command {
    my ($command_line) = @_;
    my ($func, $res);

    foreach (parse_line(';', 1, $command_line)) {
        s/^\s*//;
        s/\s*$//;
        my ($command, @options) = parse_line('\s+', 1, $_);
        defined $command && defined $commands{$command} and $func = "command_$command";
        if (defined $func) {
            my $res2;
            eval { $res2 = &$func(@options) . "\n" };
            $@ and $res2 = "fatal error : $@\n";
            $res .= $res2;
        } else {
            /^\s*$/ or $res .= "syntax error : '$_' command not found\n";
        }
    }
    $res;
}

sub check_template_options {
    my ($template_options_arg) = @_;
    join (', ', map { my ($k, $v) = split(/:/, $_); qq($k => "$v"); } split(/,/, $template_options_arg));
}

sub check_options {
    member($action_arg, qw(export_xml import_xml)) or return "bad action or no action specified.";
    member($template_arg, list_templates()) or return "bad or no template specified. Try --list-templates to get a list of templates";
    $if_arg eq '-' || -r $if_arg or return "input file [$if_arg] is not readable. Specify a correct input file with --if= ";
    return 0;
}

########### ----------------

my $env = {};

sub usage {
print q(
Usage: libconf
       libconf --commands 'command 1; command 2; ...'

Interactive console to modify configuration files without loosing any information or destroying anything.

When called with --commands, the console is not interactive, and execute the commandes passed in arguments, then exits.

see http://www.libconf.net

);
exit(0);    
}

sub command_quit {
    exit(0);
}

sub command_help {
    my ($options) = @_;
    $options or return q(The command syntax is : COMMAND [OPTIONS] option1:value1,option2:value2
COMMAND is one of :

) . join ("\n", map { "$_ : " . $commands{$_}[0]} (sort keys(%commands))) . q(.

type help COMMAND for more detail

Examples :
write an xml representation of /etc/passwd to /tmp/pass.xml :
load_from_file /etc/passwd System/Passwd; save_to_xml /tmp/pass.xml

the reverse operation :
load_from_xml /tmp/pass.xml System/Passwd; save_to_file /etc/passwd
);
    exists $commands{$options} or return "unknown command $options";
    return $commands{$options}[0] . "\n" . $commands{$options}[1];
}

sub command_version {
    require Libconf;
    "Libconf $Libconf::VERSION\n" .
    "readline frontend version $VERSION";
}

sub command_ls { goto &command_status }

sub command_status {
    $env->{config_object} or return 'no configuration loaded';
    my $status = "Configuration loaded : \n";
    if ($env->{load_type} eq 'xml') {
        $status .=   
"  loaded from $env->{load_type} : " . $env->{xml_filename} . "
  High level template : ";
    }
    if ($env->{load_type} eq 'file') {
        $status .=   
"  loaded from $env->{load_type} : " . $env->{config_object}{libconf}{filename} . "
  High level template : " . $env->{high_template} . "
  Low level template : " . $env->{config_object}{libconf}{template_name} . "
  Specific template informations :\n        ";
        my %temp;
        $temp{$_} = $env->{config_object}{libconf}{$_} foreach (grep { !member ($_, qw(filename template_name atoms)) } keys %{$env->{config_object}{libconf}});
        use Data::Dumper;
        $Data::Dumper::Terse = 1;
        $Data::Dumper::Deepcopy = 1;
        $status .=  Dumper(\%temp);
    }
    $status;
}

sub command_list_templates {
    join("\n", map {
        my $a = $_; $a =~ s|/|::|g;
        #        print "***********" . "require Libconf::Glueconf::$a; Libconf::Glueconf::$a" . "::description()" . "\n";
        my $desc = eval (qq(require Libconf::Glueconf::$a; Libconf::Glueconf::$a) . qq(::description()));
        $_ . ($desc ? " - $desc" : '');
    } list_templates());
}

sub list_templates {
    require Libconf::Glueconf;
    my $d = substr($INC{'Libconf/Glueconf.pm'}, 0, -3);
    map { (m|$d/(.*).pm|) } glob(substr($INC{'Libconf/Glueconf.pm'}, 0, -3) . "/*/*");
}

sub command_list_template_options {
    my ($template_name) = @_;
    $template_name or return "one arg is needed, try 'help list_template_options'";
    member($template_name, list_templates()) or return "$template_name is not a valid template. try 'list_templates'" ;
    $template_name =~ s|/|::|g;
    "please look at the documentation of Libconf::Glueconf::$template_name, launch 'man Libconf::Glueconf::$template_name'";
}

sub command_load_from_file { load_from('file', @_) }
sub command_load_from_xml { load_from('xml', @_) }

sub load_from {
    my ($type, $filename, $template_name, $options) = @_;
    member($template_name, list_templates()) or return "$template_name is not a valid template. try 'list_templates'" ;
    my $temp = $template_name;
    $template_name =~ s|/|::|g;
    my $template_class = "Libconf::Glueconf::$template_name";

#    my $template_options = check_template_options($template_options_arg);
#    print "*************" . qq( require $template_class; import $template_class; \$env->{config_object} = new $template_class({ filename => "$if_arg", $template_options }); ) . "\n";

    my $res;
    my $obj;
    eval {
        eval qq(require $template_class; import $template_class; \$obj = new $template_class({ filename => "", $template_options }); );
        $type eq 'file' and $obj->read_conf($filename);
        $type eq 'xml' and $obj->fromXMLFile($filename);
    };
    if ($@ || !defined($obj)) {
        my $errmsg = $@;
        chomp $errmsg;
        return $res . "an error occured, check the following message : \`\`$errmsg''\ntry libconf --help";
    }
    $env->{config_object} = $obj;
    $env->{load_type} = $type;
    $env->{high_template} = $temp;
    $res . "$filename loaded successfully";
}

sub command_save_to_file { save_to('file', @_) }
sub command_save_to_xml { save_to('xml', @_) }

sub save_to {
    my ($type, $filename) = @_;
    my $obj = $env->{config_object};
    $obj or return "no configuration information has been loaded. try 'load_from_file' or 'load_from_xml'";
    $filename ||= $obj->{libconf}{filename};
    $filename or return "no valid filename were found in the configuration informations. Retry by specifying a filename";
    my $res = "saving to '$filename'\n";
    eval {
        $type eq 'file' and $obj->write_conf($filename);
        $type eq 'xml' and $obj->toXMLFile($filename);
    };
    if ($@) {
        my $errmsg = $@;
        chomp $errmsg;
        $res .= "an error occured, check the following message : \`\`$errmsg''\ntry libconf --help";
    } else { $res .= 'done.' }
    $res;
}


sub foo { 
q(

  --action \t\t\t the action to do, can be :
\t\t\t\t export_xml : output an xml equivalent of the config file
\t\t\t\t import_xml : use the input file as xml, and generate the config fil accordingly

  --if=<FILENAME> \t\t the input config file to work on. If set to - , data are read from STDIN
  --of=<FILENAME> \t\t the output config file to produce. If set to - , data are outputed to STDOUT

  --template=<TEMPLATE> \t the template that should be used to parse the config file (see list-templates)
  --list-templates \t\t lists the templates available
  --template-options=<OPTIONS> \t the options that you may need to pass to the template (see list-templates-options)
\t\t\t\t the options are a list of key value pair, seperated by comas. For instance : option1:value1,option2:value2
  --list-templates-options \t lists the options of a particular template (which you need to specify with --template)

  --xml-format : \t\t specify the optional indenting format of the output, can be 0, 1, 2
  --help \t\t\t prints this message

Examples
write an xml representation of /etc/passwd to /tmp/pass.xml :
libconf -action=export_xml -template=System/Passwd -if=/etc/passwd -of=/tmp/pass.xml

the reverse operation :
libconf -action=import_xml -template=System/Passwd -of=/tmp/passwd -if=/tmp/pass.xml

WARNING : for import/export-xml to work, you need to install Data::DumpXML and
Data::DumpXML::Parser from cpan or through your distribution);
}

sub member { my $e = shift; foreach (@_) { $e eq $_ and return 1 } 0 }
