#-*- perl -*-
#
# Copyright (C) 2004,2005,2006 Ken'ichi Fukamachi
# All rights reserved. This program is free software; you can
# redistribute it and/or modify it under the same terms as Perl itself.
#
# $FML: Menu.pm,v 1.7 2005/08/20 01:27:32 fukachan Exp $
#
package FML::Config::Menu;
use strict;
use vars qw(@ISA @EXPORT @EXPORT_OK $AUTOLOAD);
use Carp;
my $debug = 0;
=head1 NAME
FML::Config::Menu - menu utility.
=head1 SYNOPSIS
=head1 DESCRIPTION
=head1 METHODS
=head2 new()
constructor.
=cut
# Descriptions: constructor.
# Arguments: OBJ($self) OBJ($curproc)
# Side Effects: none
# Return Value: OBJ
sub new
{
my ($self, $curproc) = @_;
my ($type) = ref($self) || $self;
my $menu = {};
my $result = {};
my $me = {
_curproc => $curproc,
_menu => $menu,
_result => $result,
};
_init($me, $menu);
return bless $me, $type;
}
# Descriptions: read config file and return menu object.
# Arguments: OBJ($self) HASH_REF($menu)
# Side Effects: none
# Return Value: none
sub _init
{
my ($self, $menu) = @_;
my $curproc = $self->{ _curproc };
my $menu_path = $curproc->menu_get_cui_config_file_path();
my ($i, $buf, $class);
use FileHandle;
my $rh = new FileHandle $menu_path;
LINE:
while ($buf = <$rh>) {
last LINE if $buf =~ /^\.end\./o;
next LINE if $buf =~ /^\#/o;
chomp $buf;
if ($buf =~ /^(\/[\/\S_]+|\/)/o) {
$class = $1;
$i = 0;
next LINE;
}
if ($buf =~ /^\s+(.*)|^\s*$/) {
$buf = $1 || '';
$menu->{ $class }->[ $i ] = $buf;
print STDERR "menu{ $class }[$i] => $buf\n" if $debug;
$i++;
}
}
return $class;
}
# Descriptions: run interactive menu.
# Arguments: OBJ($self)
# Side Effects: none
# Return Value: none
sub run_cui
{
my ($self) = @_;
my $menu = $self->{ _menu };
my $result = $self->{ _result };
my $output = '';
use Term::ReadLine;
my $term = new Term::ReadLine 'menu';
my $prompt = "select> ";
my $wh = $term->OUT || \*STDOUT;
my $class = '/';
my $fallback = {};
my $r;
# show top menu.
$self->_menu_print($wh, $menu, $class, $fallback);
LOOP:
while (defined ($r = $term->readline($prompt))) {
if ($r eq 'q' || $r eq 'quit' || ($r eq '' && $class eq '/')) {
last LOOP;
}
if (defined $fallback->{ $r }->{ set }) {
$output .= $fallback->{ $r }->{ set } || '';
}
elsif (defined $fallback->{ $r }->{ next }) {
$class = $fallback->{ $r }->{ next };
}
# up if null input.
if ($r eq '') {
$class = $self->_get_parent_class_name($class);
}
$fallback = {};
$self->_menu_print($wh, $menu, $class, $fallback);
}
if ($output) {
$self->{ _output } = $output;
print STDERR "*** DIFF (debug) ***\n";
print STDERR $output, "\n";
print STDERR "*** DIFF END ***\n";
}
}
# Descriptions: print menu for the specified class.
# Arguments: OBJ($self)
# HANDLE($wh) HASH_REF($menu) STR($class) HASH_REF($fallback)
# Side Effects: none
# Return Value: none
sub _menu_print
{
my ($self, $wh, $menu, $class, $fallback) = @_;
# XXX-TODO: clear if unix, cls on ms.
system "clear";
print $wh "*** CURRENT CLASS (debug) = $class ***\n";
my $cur_mode = '';
my $i = 0;
my $item = 0;
my $ma = $menu->{ $class } || [];
my $k = $#$ma;
MENU:
for (my $j = 0; $j <= $k ; $j++) {
my $mbuf = $ma->[ $j ];
# EXAMPLE: "_item_ ARTICLE_POST_POLICY"
if ($mbuf =~ /^\s*_item_\s+(\S+)/o) {
my $next_layer = $1;
if ($next_layer =~ /^[A-Z0-9_]+$/) {
if ($next_layer eq 'END') {
$fallback->{ $i }->{ next } =
$self->_get_parent_class_name($class);
}
else {
$fallback->{ $i }->{ next } =
sprintf("%s/%s", $class, $next_layer);
}
$fallback->{ $i }->{ next } =~ s@//@/@g;
}
# convert _item_ to NUM.
$item = $i;
$mbuf =~ s/_item_/$item/;
$i++;
print $wh $mbuf, "\n";
}
# end of special mode.
if ($mbuf =~ /^\s*\}/o) {
$cur_mode = '';
}
# EXAMPLE: _set_ { ... }
if ($mbuf =~ /^\s*_set_\s*\{(.*)/o) {
$cur_mode = 'set';
$fallback->{ $item }->{ set } .= $1;
$fallback->{ $item }->{ set } .= "\n";
next MENU;
}
if ($cur_mode eq 'set') {
$fallback->{ $item }->{ set } .= $mbuf;
$fallback->{ $item }->{ set } .= "\n";
}
}
}
# Descriptions: get parent class name and return it.
# Arguments: OBJ($self) STR($class)
# Side Effects: none
# Return Value: none
sub _get_parent_class_name
{
my ($self, $class) = @_;
my $p_class = $class;
$p_class =~ s@/[^\/]+$@@;
$p_class =~ s@//@/@g;
return( $p_class || '/' );
}
# Descriptions:
# Arguments: OBJ($self)
# Side Effects: none
# Return Value: none
sub rewrite_config_cf
{
my ($self) = @_;
my $curproc = $self->{ _curproc };
my $config = $curproc->{ config };
my $cf_file = $curproc->config_cf_filepath();
# 1. save output into $tmp_file.
my $output = $self->{ _output } || '';
my $tmp_file = $curproc->tmp_file_path();
my $wh = new FileHandle ">> $tmp_file";
if (defined $wh) {
print $wh "\n";
print $wh $output;
print $wh "\n";
print $wh "=cut\n";
print $wh "\n";
$wh->close();
}
# 2. merge changes into the current configuration file.
my $diff_org = $self->_get_diff_as_hash_ref($cf_file);
my $diff_new = $self->_get_diff_as_hash_ref($cf_file, $tmp_file);
# 3.
print "\n// SUMMARY\n";
my $diff = $self->_get_diff_between_hash_ref($diff_org, $diff_new);
# 4. rewrite
$config->merge_into_file($cf_file, $diff);
for my $k (keys %$diff) {
print "# configured by CUI.\n";
print "$k = $diff->{ $k }\n";
print "\n";
}
}
# Descriptions: get difference between the current and default configuration.
# return the result as HASH_REF.
# Arguments: OBJ($self) VAR_ARGS(@files)
# Side Effects: none
# Return Value: HASH_REF
sub _get_diff_as_hash_ref
{
my ($self, @files) = @_;
my $config_tmp = new FML::Config;
$config_tmp->read($files[0]);
shift @files;
for my $f (@files) {
$config_tmp->overload($f);
}
return $config_tmp->dump_variables( { mode => 'get_diff_as_hash_ref' } );
}
# Descriptions: get difference between specified hashes.
# Arguments: OBJ($self) HASH_REF($hash) HASH_REF($hash_new)
# Side Effects: none
# Return Value: HASH_REF
sub _get_diff_between_hash_ref
{
my ($self, $hash, $hash_new) = @_;
my $diff = {};
KEY:
for my $k (sort keys %$hash) {
next KEY if $k =~ /\[/;
if ($hash_new->{ $k } ne $hash->{ $k }) {
$diff->{ $k } = $hash_new->{ $k };
}
}
KEY:
for my $k (sort keys %$hash_new) {
next KEY if $k =~ /\[/;
if ($hash_new->{ $k } ne $hash->{ $k }) {
$diff->{ $k } = $hash_new->{ $k };
}
}
return $diff;
}
=head1 CODING STYLE
See C on fml coding style guide.
=head1 AUTHOR
Ken'ichi Fukamachi
=head1 COPYRIGHT
Copyright (C) 2004,2005,2006 Ken'ichi Fukamachi
All rights reserved. This program is free software; you can
redistribute it and/or modify it under the same terms as Perl itself.
=head1 HISTORY
FML::Config::Menu appeared in fml8 mailing list driver package.
See C for more details.
=cut
1;