File Coverage

blib/lib/CLI.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             package CLI;
2              
3              
4             BEGIN {
5 1     1   2801 use Exporter();
  1         1  
  1         25  
6 1     1   3 use vars qw(@ISA @EXPORT);
  1         1  
  1         67  
7              
8             @ISA = qw(Exporter);
9             @EXPORT = qw(VAR HASH COMMAND ARRAY MIXED ARRAY INTEGER FLOAT STRING
10             SSTRING TIME DEGREE BOOLEAN
11             hashmatch parse_string typeStr string_value);
12 1     1   895 use Astro::Time qw(turn2str str2turn);
  0            
  0            
13             use Carp;
14             }
15              
16             use constant VAR => 1;
17             use constant HASH => 2;
18             use constant COMMAND => 3;
19             use constant ARRAY => 4;
20             use constant MIXEDARRAY => 5;
21              
22             use constant INTEGER => 1;
23             use constant FLOAT => 2;
24             use constant STRING => 3;
25             use constant SSTRING => 4;
26             use constant TIME => 5;
27             use constant DEGREE => 6;
28             use constant BOOLEAN => 7;
29              
30             sub parse_string ($$);
31             sub typeStr ($);
32             sub hashmatch ($%\@);
33             sub string_value($$);
34              
35             use CLI::Var;
36             use CLI::Hash;
37             use CLI::Command;
38             use CLI::Array;
39             use CLI::MixedArray;
40              
41             use strict;
42              
43             sub new {
44             my $proto = shift;
45             my $class = ref($proto) || $proto;
46              
47             my $self = {
48             ELEMENTS => {},
49             DEFAULT => undef
50             };
51              
52             bless ($self, $class);
53              
54             return $self;
55             }
56              
57             sub add {
58             my $self = shift;
59             my $type = shift;
60             my $name = shift;
61             my $elements = $self->{ELEMENTS};
62              
63             my $new_element;
64             if ($type==VAR) {
65             my $var = shift;
66             tie $$var, 'CLI::Var', $name, @_;
67             $new_element = tied $$var;
68             } elsif ($type==ARRAY) {
69             my $var = shift;
70             tie @{$var}, 'CLI::Array', $name, @_;
71             $new_element = tied @{$var};
72             } elsif ($type==MIXEDARRAY) {
73             my $var = shift;
74             tie @{$var}, 'CLI::MixedArray', $name, @_;
75             $new_element = tied @{$var};
76             } elsif ($type==HASH) {
77             $new_element = new CLI::Hash($name, @_);
78             } elsif ($type==COMMAND) {
79             $new_element = new CLI::Command($name, @_);
80             } else {
81             carp 'CLI::add Unknown element type';
82             }
83              
84             $elements->{$name} = $new_element;
85              
86             return $new_element;
87             }
88              
89             # Command to run if command is not known
90             sub default {
91             my $self = shift;
92             if (@_) {
93             $self->{DEFAULT} = shift; #TODO Should check type
94             }
95             return $self->{DEFAULT};
96             }
97              
98             sub parse {
99             my $self = shift;
100             my $line = shift;
101              
102             my $elements = $self->{ELEMENTS};
103              
104             my ($key, $value);
105             if ($line =~ /^\s*(\S+) # Key
106             (?:\s+ # Optionally followed by a space
107             (?:(.*\S))?\s*)? # and then some value
108             $/x) {
109             $key = $1;
110             $value = $2;
111             } else {
112             return; # Ignore blank lines
113             }
114             my @matches = ();
115             my $command = hashmatch($key, $elements, \@matches);
116              
117             if (defined $command) {
118             $command->parse($value);
119             } else {
120             if (@matches) {
121             print "\"$key\" matches:\n\n";
122             foreach my $match (@matches) {
123             print " $match\n";
124             }
125             print "\n";
126             } else {
127             if (defined $self->default) {
128             &{$self->default()}($line, $key, $value);
129             } else {
130             print "\n Unknown command $key\n\n";
131             }
132             }
133             }
134             }
135              
136             # Save a config file based on the current state of variables
137             # Currently does not save anything but variable types
138             sub save_config{
139             my $self = shift;
140              
141             my $fconfig = shift;
142              
143             if (!defined $fconfig) {
144             carp "CLI->save_config must supply config filename";
145             return;
146             }
147              
148             if (! open(CONFIG, '>', $fconfig)) {
149             carp "Could not open $fconfig: $!";
150             return;
151             }
152              
153             my $elements = $self->{ELEMENTS};
154              
155             foreach (keys(%$elements)) {
156             my $type = ref($elements->{$_});
157             if ($type eq 'CLI::Var') {
158             printf(CONFIG "%s %s\n", $_, $elements->{$_}->value);
159             }
160             }
161              
162             close(CONFIG);
163             }
164              
165             # Read back a previously saved config file.
166             # Currently config file can contain anything that could be
167             # type on the command line
168             sub restore_config{
169             my $self = shift;
170              
171             my $fconfig = shift;
172              
173             if (!defined $fconfig) {
174             carp "CLI->restore_config: must supply config filename";
175             return;
176             }
177              
178             if (! open(CONFIG, $fconfig)) {
179             carp "Could not open $fconfig for reading: $!";
180             return;
181             }
182              
183             my $elements = $self->{ELEMENTS};
184              
185             while () {
186             $self->parse($_);
187             }
188              
189             close(CONFIG);
190             }
191              
192              
193             # Some generally useful routines
194              
195             sub hashmatch ($%\@) {
196             #+
197             # Match a keyword in a hash, using case insensitive minimal matching, and
198             # returns the hash value
199             # (ie 'Fr' matches 'Fred' and 'Frank')
200             # Usage:
201             # my %hash = (
202             # Fred => 'Some value',
203             # William => 20,
204             # Mary => [0, 1, 2]
205             # );
206             # my $key = 'Fr';
207             # my @matches = ();
208             # my $match = hashmatch($key, %hash, @matches);
209             #
210             # Returns undef if no matches found, or multiple matches found
211             # The third parameter is set to a list reference containing all the matches
212             # Wild cards are allowed: * match any number of characters
213             # ? Match one character
214             #-
215             my($key, $hash, $matches) = @_;
216              
217             @$matches = ();
218              
219             # Cannot do exact matches with wild cards
220             my $exactmatch = 1;
221             $exactmatch = 0 if (($key =~ /\?/) || ($key =~ /\*/));
222              
223             my @matches = ();
224             # Clean up the key
225             $key =~ s/\./\\\./g; # Pass '.'s (. -> \.)
226             $key =~ s/\+/\\\+/g; # Pass '+'s (+ -> \+)
227             $key =~ s/\[/\\\[/g; # Pass '['s (+ -> \[)
228             $key =~ s/\]/\\\]/g; # Pass ']'s (+ -> \])
229             $key =~ s/\(/\\\(/g; # Pass '('s (+ -> \()
230             $key =~ s/\)/\\\)/g; # Pass ')'s (+ -> \))
231             $key =~ s/\*/\.\*/g; # Allow simple wild cards ( * -> .* )
232             $key =~ s/\?/\./g; # ? matches single character (? -> .)
233              
234             foreach (keys(%$hash)) {
235             #print "Trying $_ ";
236             if ($exactmatch && /^$key$/i) { # Return immediately for an exact match
237             #print " exact match to $key!\n";
238             @$matches = ($_);
239             return $$hash{$_};
240             } elsif (/^$key/i) {
241             push @$matches, $_;
242             #print " matches $key\n";
243             } else {
244             #print " no match\n";
245             }
246             }
247              
248             if (@$matches==1) {
249             return $hash->{$matches->[0]};
250             } else {
251             return undef;
252             }
253             };
254              
255             sub parse_string ($$) {
256             my ($type, $string) = @_;
257              
258             if (defined $type) {
259              
260             my $pattern = '';
261             if ($type == INTEGER) {
262             $pattern = '[+-]?\\d+';
263             } elsif ($type == FLOAT) {
264             #$pattern = '\\d+\.\\d+'; # Way to simplistic
265             $pattern = '\\S+'; # Way to simplistic
266             } elsif ($type == SSTRING) {
267             $pattern = '\\S+';
268             } elsif ($type == STRING) {
269             $pattern = '\\S.*';
270             } elsif ($type == TIME) {
271             $pattern = '\\S+';
272             } elsif ($type == DEGREE) {
273             $pattern = '\\S+';
274             } else {
275             $pattern = '.+';
276             }
277              
278             if ($string =~ /^\s*($pattern)(?:\s+(.*))?$/) {
279             $_[1] = $2; # Pass back the unused portion of the string
280              
281             # Return the matched portion, with possibly with some additional processing
282             if ($type == DEGREE) {
283             return str2turn($1,'D');
284             } elsif ($type == TIME) {
285             return str2turn($1,'H');
286             } else {
287             return $1;
288             }
289             } else {
290             return undef;
291             }
292             } else {
293             return undef;
294             }
295             }
296              
297             sub typeStr ($) {
298             my $type = shift;
299             if ($type == INTEGER) {
300             return 'Integer';
301             } elsif ($type == FLOAT) {
302             return 'Float';
303             } elsif ($type == STRING) {
304             return 'String';
305             } elsif ($type == SSTRING) {
306             return 'SString';
307             } elsif ($type == TIME) {
308             return 'Time';
309             } elsif ($type == DEGREE) {
310             return 'Degree';
311             } elsif ($type == BOOLEAN) {
312             return 'Boolean';
313             } else {
314             return 'Unknown';
315             }
316             }
317              
318             sub string_value ($$) {
319             my $value = shift;
320             my $type = shift;
321             if ($type == TIME) {
322             return turn2str($value, 'H', 2);
323             } elsif ($type == DEGREE) {
324             return turn2str($value, 'D', 2);
325             } else {
326             return $value;
327             }
328             }
329              
330              
331             1;