Changeset 156

Show
Ignore:
Timestamp:
06/01/04 09:56:16 (4 years ago)
Author:
svn
Message:

Added tab-completion, automatic configuration saving DIE and SIGINT handling.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • manage/trunk/CLI/CLI.pm

    r154 r156  
    33use strict; 
    44use warnings; 
    5 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK); 
     5use vars qw($VERSION); 
    66use base qw(Exporter); 
    77 
     
    240240                                        # Tokens are still remaining 
    241241                                        if (@tokens) { 
    242                                                 return ('HELP', "Command is complete.\n") 
     242                                                return ('HELP', "\e[32m\e[1mOK Command is complete.\e[0m\n") 
    243243                                                        if "@tokens" eq '?'; 
    244244                                                return ('ERROR', 'Trailing tokens (possibly missing help?).'); 
  • manage/trunk/manage

    r155 r156  
    11#!/usr/bin/perl 
    2  
    3 package Manage; 
    42 
    53# Add cli-parser to our module include path 
     
    2119 
    2220  
    23 my $BREAK = 0; 
     21my $DEBUG = 0; 
     22my $BREAK = 0; $SIG{INT} = sub { $BREAK = 1; }; 
     23my $FATAL = 0; $SIG{__DIE__} = sub { $FATAL = 1; }; 
    2424my @ALTCOLOURS = ("\e[36m", "\e[36m\e[1m"); 
    25 $SIG{INT} = sub { $BREAK = 1; }; 
    2625# Global variables 
    2726my $PROMPT = 'manage> '; 
     
    2928my $PLUGINS = './plugins'; 
    3029our %CONF = ( 
    31         'prompt' => {  
     30        prompt => {  
    3231                value => \$PROMPT, 
    33                 pattern => '.*', 
    3432                help => 'CLI prompt.' 
    3533        }, 
    36         'banner' => { 
    37                 value => \$BANNER, 
    38                 pattern => '.*', 
    39                 help => 'Startup banner.' 
    40         }, 
    41         'plugins' => { 
    42                 value => \$PLUGINS, 
    43                 pattern => '.*', 
    44                 help => 'Driver base directory.' 
    45         }, 
    46  
     34        debug => { 
     35                value => \$DEBUG, 
     36                pattern => '\d+', 
     37                help => 'Set debug level (0-9).', 
     38        }, 
    4739); 
     40my %ORIGINAL_CONF = (); 
     41my $CLI; 
    4842 
    4943  
    5044# Helper functions 
     45sub logger { 
     46        #print(LOG "@_"); 
     47} 
     48 
     49sub debug($;$) { 
     50my ($msg, $level) = @_; 
     51 
     52        if (defined($level)) { 
     53                if ($DEBUG >= $msg) { 
     54                        print("\e[35m\e[1mDB$msg $level\e[0m\n"); 
     55                } 
     56        } elsif ($DEBUG > 0) { 
     57                print("\e[35m\e[1mDB1 $msg\e[0m\n"); 
     58        } 
     59} 
     60 
     61sub fatal { 
     62        my $str = "\e[31m\e[1mFTL @_\e[0m\n"; 
     63        print($str); 
     64        logger($str); 
     65        exit 1; 
     66} 
     67 
    5168sub error { 
    52         print("\e[31m\e[1mERR @_\e[0m\n"); 
    53 
    54  
    55 sub fatal { 
    56         error(@_); 
    57         exit 1; 
     69        my $str = "\e[31m\e[1mERR @_\e[0m\n"; 
     70 
     71        print($str); 
     72        logger($str); 
     73        return undef; 
     74}        
     75 
     76sub warning { 
     77        my $str = "\e[33m\e[1mWRN @_\e[0m\n"; 
     78 
     79        print($str); 
     80        logger($str); 
     81        return undef; 
     82
     83 
     84sub info { 
     85my $str; 
     86 
     87        if (@_ and $_[0] eq '-n') { 
     88                shift(@_); 
     89                $str = "\e[32m\e[1mOK\e[22m @_\e[0m"; 
     90        } else { 
     91                $str = "\e[32m\e[1mOK\e[22m @_\e[0m\n"; 
     92        } 
     93        print($str); 
    5894} 
    5995 
     
    66102my $text; 
    67103my $table = new Text::Table(@header); 
    68         for my $r (@body) { 
    69                push(@bc, shift(@$r)); 
    70        
     104 
     105        # Extract colour column 
     106        for my $r (@body) { push(@bc, shift(@$r));
    71107 
    72108        $table->load(@body); 
     
    79115                print($bc[$line] . $text . "\e[0m\n"); 
    80116        } 
    81 #my ($width, $height) = GetTerminalSize(*STDIN); 
    82 #my @width = (); 
    83 #my $total = 0; 
    84 
    85 #       sub sizeof($) { 
    86 #       my $s = shift; 
    87 
    88 #               $s =~ s/\e\[[^m]+m//g; 
    89 #       my @s = split(/\n/m, $s); 
    90 #       my $size = 0; 
    91 
    92 #               for my $line (@s) { 
    93 #                       $size = length($line) if length($line) > $size; 
    94 #               } 
    95 #               return $size; 
    96 #       } 
    97 
    98 
    99 #       sub render_row($\@\@) { 
    100 #       my $width = shift; 
    101 #       my @row = @{$_[0]}; 
    102 #       my @width = @{$_[1]}; 
    103 #       my @out = (); 
    104 #       my $lines = 0; 
    105 
    106 #               for (my $i = 1; $i < @row; ++$i) { 
    107 #                       $Text::Wrap::columns = $width[$i] + 1; 
    108 #               my @lines = split(/\n/m, wrap("", "", $row[$i])); 
    109 
    110 #                       $out[$i] = \@lines; 
    111 #                       $lines = @lines if @lines > $lines; 
    112 #               } 
    113 #        
    114 #       my $out = ""; 
    115 #               for (my $i = 0; $i < $lines; ++$i) { 
    116 #                       for (my $j = 1; $j < @row; ++$j) { 
    117 #                       my $text = $out[$j][$i]; 
    118 
    119 #                               $text = "" unless defined($text); 
    120 #                               $out .= sprintf("%-$width[$j].$width[$j]s", $text); 
    121 #                       } 
    122 #                       $out .= "\n"; 
    123 #               } 
    124 #               chomp($out); 
    125 #               $out; 
    126 #       } 
    127 
    128 #       # Find minimum column sizes 
    129 #       for (my $i = 1; $i < @header; ++$i) { 
    130 #               $width[$i] = sizeof($header[$i]) + 1; 
    131 
    132 #               for my $row (@body) { 
    133 #               my $sz = sizeof($row->[$i]) + 1; 
    134 
    135 #                       $width[$i] = $sz if $sz > $width[$i]; 
    136 #               } 
    137 
    138 #               $total += $width[$i]; 
    139 #               return if $BREAK; 
    140 #       } 
    141 
    142 #my $scale = $width / $total; 
    143 
    144 #       # Scale widths up 
    145 #       for (my $i = 1; $i < @width; ++$i) { 
    146 #       my $new = floor($width[$i] * $scale); 
    147 
    148 #               if ($width[$i] < 16 and $new < $width[$i]) { 
    149 #               } else { 
    150 #                       $width[$i] = $new; 
    151 #               } 
    152 #       } 
    153 
    154 #       # Actually display the table 
    155 #       print($header[0] . render_row($width, @header, @width) . "\e[0m\n"); 
    156 #       for my $row (@body) { 
    157 
    158 #               last if $BREAK; 
    159 #               print($row->[0] . render_row($width, @{$row}, @width) . "\e[0m\n"); 
    160 #       } 
     117
     118 
     119sub exec_line { 
     120my ($type, $action, $args) = $CLI->parse("@_"); 
     121 
     122        if ($type eq 'HELP') { 
     123                print($action); 
     124        } elsif ($type eq 'ACTION') { 
     125                $action->(@{$args}); 
     126        } elsif ($type eq 'ERROR') { 
     127                error($action); 
     128        } 
     129
     130 
     131sub load_config($) { 
     132my $file = shift; 
     133 
     134        if (open(CONF, "<$file")) { 
     135                while (<CONF>) { 
     136                        next if /^\s*$/ or /^#.*/; 
     137                        $_ =~ /^([.\w-]+)\s*=\s*(.*)/; 
     138                        if (defined($CONF{$1})) { 
     139                                ${$CONF{$1}->{value}} = $2; 
     140                        } 
     141                } 
     142                close(CONF); 
     143        } 
    161144} 
    162145 
     
    173156        'set' => { 
    174157                HELP => 'Query/modify settings.', 
    175                 '$\w+' => { 
     158                '$[\w.-]+' => { 
    176159                        '$.+' => { 
    177160                                ACTION => sub { 
     
    179162 
    180163                                        if ($CONF{$key}) { 
    181                                                 if ($value =~ /$CONF{$key}->{pattern}/) { 
     164                                        my $pattern = $CONF{$key}->{pattern}; 
     165 
     166                                                $pattern = '.+' unless defined($pattern); 
     167                                                if ($value =~ /$pattern/) { 
    182168                                                        ${$CONF{$key}->{value}} = $value; 
    183169                                                } else { 
     
    227213                }, 
    228214        }, 
     215        history => { 
     216                clear => { 
     217                        ACTION => sub { 
     218                                Term::ReadLine::Gnu->clear_history(); 
     219                        }, 
     220                        HELP => "Clear the command line history." 
     221                }, 
     222                ACTION => sub { 
     223                        print(join("\n", Term::ReadLine::Gnu->GetHistory) . "\n"); 
     224                }, 
     225                HELP => "Display command line history.", 
     226        }, 
    229227        'quit|exit' => { 
    230228                HELP => [ 'exit', 'Exit.' ], 
     
    232230        }, 
    233231}; 
    234  
    235 my $term = new Term::ReadLine('manage'); 
    236 $term->ornaments(0); 
     232$CLI = new CLI( 
     233        grammar => $GRAMMAR, 
     234        plugins => $PLUGINS, 
     235        ); 
     236 
     237 
     238# Initialise terminal and readline 
     239my $term = new Term::ReadLine('manage'); $term->ornaments(0); 
    237240my $termattribs = $term->Attribs; 
    238 my $CLI = new CLI( 
    239         grammar => $GRAMMAR, 
    240         plugins => ${$CONF{plugins}->{value}}, 
    241         ); 
    242  
    243 # Turn off filename completion 
    244 $termattribs->{attempted_completion_function} = sub { return (''); }; 
     241# Do command completion 
     242$termattribs->{completion_function} = sub { 
     243my ($text, $line, $start, $end) = @_; 
     244my ($type, $action, $args) = $CLI->parse(substr($line, 0, $start) . ' ?'); 
     245my @matches; 
     246 
     247        if ($type eq 'HELP') { 
     248        my @line = grep(/^  \e/, split(/\n/, $action)); 
     249 
     250                for my $line (@line) { 
     251                        $line =~ s/^  \e\[1m([^\s\e]+).*/$1/; 
     252                        if ($line =~ /^$text/) { 
     253                                push(@matches, $line); 
     254                        } 
     255                } 
     256        } 
     257        return (@matches); 
     258}; 
    245259# Map the ? key to auto-magic help 
    246260$term->add_defun('dns-help', sub { 
     
    252266}, ord('?')); 
    253267 
    254 sub exec_line { 
    255 my ($type, $action, $args) = $CLI->parse("@_"); 
    256  
    257         if ($type eq 'HELP') { 
    258                 print($action); 
    259         } elsif ($type eq 'ACTION') { 
    260                 $action->(@{$args}); 
    261         } elsif ($type eq 'ERROR') { 
    262                 error($action); 
    263         } 
    264 
     268load_config("/etc/manage.conf"); 
     269load_config("$ENV{HOME}/.managerc"); 
     270 
     271for my $key (keys %CONF) { 
     272        $ORIGINAL_CONF{$key} = ${$CONF{$key}->{value}}; 
     273
     274 
     275END { 
     276        Term::ReadLine::Gnu->WriteHistory("$ENV{HOME}/.managehistory"); 
     277        if (!$FATAL) { 
     278                # Compare original config to current config 
     279                for my $key (keys %CONF) { 
     280                        goto COMMIT if !defined($ORIGINAL_CONF{$key}) or 
     281                        ${$CONF{$key}->{value}} ne $ORIGINAL_CONF{$key}; 
     282                } 
     283                return; 
     284        COMMIT: info("Committing modified config."); 
     285                if (open(CONF, ">$ENV{HOME}/.managerc")) { 
     286                        foreach my $key (keys %CONF) { 
     287                                print(CONF "# $CONF{$key}->{help}\n"); 
     288                                print(CONF "$key=${$CONF{$key}->{value}}\n"); 
     289                        } 
     290                        close(CONF); 
     291                } 
     292        } 
     293
     294 
     295Term::ReadLine::Gnu->ReadHistory("$ENV{HOME}/.managehistory"); 
     296 
     297  
     298# The main part of the program 
    265299 
    266300# Command line? 
     
    270304} 
    271305 
    272 if ($BANNER) { 
     306if (@ARGV == 0 and $BANNER) { 
    273307print(<<EOF); 
    274308$BANNER 
     
    277311 
    278312# Do the input and parsing 
    279 while (defined(my $line = $term->readline($PROMPT))) { 
     313my $line; 
     314while (defined($line = $term->readline($PROMPT))) { 
    280315        next if $line =~ /^\s*$/; 
    281316        exec_line($line); 
    282317        $BREAK = 0; 
    283318} 
     319 
     320print("\n") unless defined($line); 
  • manage/trunk/plugins/dns.pm

    r155 r156  
    1 package DNS; 
    2  
    31use strict; 
    42use warnings; 
    53 
     4my ($KEY, $SECRET, $NS) = ("", "", ""); 
     5my $DETAIL = 'off'; 
     6my $TIMEOUT = 'none'; 
     7my $ZONE = ""; 
     8my $TTL = 3600; 
     9my @RECORDS = (); 
     10my $FQDNRX = '[-a-z0-9]+(\.[-a-z0-9]+)+(\.)?'; 
     11my $HOSTRX = '[-a-z0-9]+(\.[-a-z0-9]+)*(\.)?'; 
     12my $IPRX = '\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}'; 
     13my $STRINGRX = '\'(?:\\.|[^\'])*\'|"(?:\\.|[^"])*"'; 
     14my %UPDATE_ERRORS = ( 
     15        REFUSED => "Update refused.", 
     16        FORMERR => "Update formatting error?", 
     17        NXDOMAIN => "No such domain.", 
     18        NOTAUTH => "You are not authorised to modify this zone.", 
     19        NXRRSET => "Pre-requisite failed." 
     20); 
     21 
     22# Export some configuration variables 
     23%::CONF = ( 
     24        %::CONF, 
     25        'dns-detail' => { 
     26                value => \$DETAIL, 
     27                pattern => 'on|off', 
     28                help => 'Whether to display extra detail in zone dumps.', 
     29        }, 
     30        'dns-key-name' => { 
     31                value => \$KEY, 
     32                help => 'The TSIG key name.', 
     33        }, 
     34        'dns-key-secret' => { 
     35                value => \$SECRET, 
     36                help => 'The TSIG key secret.', 
     37        }, 
     38        'dns-timeout' => { 
     39                value => \$TIMEOUT, 
     40                help => 'Network timeout.', 
     41        }, 
     42); 
     43 
     44# Grammar 
    645{ 
    746        dns => { 
    847                add => sub { 
    9                         print(keys(%CONF) . "\n"); 
    1048                }, 
    1149                delete => { 
     
    1351                show => { 
    1452                }, 
     53                # Initialise DNS module 
     54                setup => { 
     55                }, 
    1556                HELP => "DNS administration functions.", 
    1657        },