File Coverage

blib/lib/CLI.pm
Criterion Covered Total %
statement 144 204 70.5
branch 32 76 42.1
condition 4 9 44.4
subroutine 30 33 90.9
pod 0 10 0.0
total 210 332 63.2


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