File Coverage

blib/lib/Config/Model/SimpleUI.pm
Criterion Covered Total %
statement 62 80 77.5
branch 9 22 40.9
condition 3 6 50.0
subroutine 14 15 93.3
pod 2 6 33.3
total 90 129 69.7


line stmt bran cond sub pod time code
1             #
2             # This file is part of Config-Model
3             #
4             # This software is Copyright (c) 2005-2022 by Dominique Dumont.
5             #
6             # This is free software, licensed under:
7             #
8             # The GNU Lesser General Public License, Version 2.1, February 1999
9             #
10             package Config::Model::SimpleUI 2.153; # TRIAL
11              
12 2     2   16614 use Carp;
  2         4  
  2         162  
13 2     2   31 use v5.020;
  2         7  
14 2     2   32 use strict;
  2         5  
  2         70  
15 2     2   12 use warnings;
  2         9  
  2         82  
16 2     2   499 use open qw(:std :utf8); # undeclared streams in UTF-8
  2         1298  
  2         11  
17 2     2   301 use Encode qw(decode_utf8);
  2         7  
  2         173  
18 2     2   1181 use Regexp::Common qw/delimited/;
  2         5623  
  2         11  
19              
20 2     2   201940 use feature qw/postderef signatures/;
  2         4  
  2         216  
21 2     2   13 no warnings qw/experimental::postderef experimental::signatures/;
  2         4  
  2         6309  
22              
23             my $syntax = '
24             cd <elt>, cd <elt:key>, cd - , cd !
25             -> jump into node
26             set elt=value, elt:key=value
27             -> set a value
28             clear elt
29             -> clear value or list or hash
30             delete elt:key
31             -> delete a value from a list or hash element
32             delete elt
33             -> like reset, delete a value (set to undef)
34             display elt elt:key
35             -> display a value
36             ls -> show content of object (args: path and/or filter pattern)
37             ls -> show elements of current node
38             ls foo* -> shows element matching foo.*
39             ls \'foo*\' -> shows elements of node stored in "foo*"
40             ls aHash -> shows keys of Hash
41             ls \'aHash:"*"\' -> shows elements of node stored in key "*" of "aHash" hash
42             ll [-nz] [-v] [ element | pattern ]
43             -> show elements of current node and their value
44             (options: -nz → hides empty value, -v → verbose)
45             (args: element name or filter pattern)
46             info -> show detailed information on object or current node
47             (args: optional path to object)
48             tree -> show configuration tree from an object or current node
49             (args: optional path to object)
50             help -> show available command
51             desc[ription] -> show class desc of current node
52             desc <element> -> show desc of element from current node
53             desc <value> -> show effect of value (for enum)
54             changes -> list unsaved changes
55             check [elt] -> run check current on current node or elt
56             fix [ ! | elt ]
57             -> fix warnings in current node or of specified element or on all tree (with ! arg)
58             save -> save current changes
59             exit -> exit shell
60             ';
61              
62             my $desc_sub = sub {
63             my $self = shift;
64             my $obj = $self->{current_node};
65             my $res = '';
66              
67             if (@_) {
68             my $item;
69             while ( $item = shift ) {
70             if ( $obj->get_type() eq 'node' ) {
71             my $type = $obj->element_type($item);
72             my $elt = $obj->fetch_element($item);
73             my $help = $obj->get_help_as_text($item);
74             $res .= "element $item (type $type)";
75             $res .= ": " if $help;
76             $res .= "\n" if $help =~ /\n/ or length($help) > 40 ;
77             $res .= $help . "\n" if $help;
78             if ( $type eq 'leaf' and $elt->value_type eq 'enum' ) {
79             $res .= " possible values: " . join( ', ', $elt->get_choice ) . "\n";
80             }
81             }
82             }
83             }
84             else {
85             $res = $obj->get_help_as_text();
86             }
87             return $res;
88             };
89              
90             my $ll_sub = sub {
91             my $self = shift;
92             my @raw_args = @_;
93              
94             my @desc_opt = qw/check no/;
95              
96             my %opt = map { /^-(\w+)/ ? ($1 => 1) : () } @raw_args;
97             push @desc_opt, hide_empty => 1 if $opt{nz} ;
98             push @desc_opt, verbose => 1 if $opt{v} ;
99              
100             my @args = grep {! /^-/ } @raw_args;
101             push @args, '*' unless @args; # default action is to list all elements
102              
103             my $obj = $self->{current_node};
104             for (@args) {s/\*/.*/g;} ;
105             my $pattern = join ('|',@args);
106              
107             return $obj->describe( pattern => qr/^$pattern$/, @desc_opt );
108             };
109              
110             my $cd_sub = sub {
111             my $self = shift;
112             my @cmds = @_;
113              
114             # convert usual cd_ism ( .. /foo) to grab syntax ( - ! foo)
115             #map { s(^/) (! );
116             # s(\.\.)(-)g;
117             # s(/) ( )g;
118             # } @cmds ;
119              
120             my $new_node = $self->{current_node}->grab("@cmds");
121             my $type = $new_node->get_type;
122             my $name = $new_node->element_name;
123              
124             if ( defined $new_node && $type eq 'node' ) {
125             $self->{current_node} = $new_node;
126             }
127             elsif ( defined $new_node && $type eq 'list' ) {
128             print "Can't cd in a $type, please add an index (e.g. $name:0)\n";
129             }
130             elsif ( defined $new_node && $type eq 'hash' ) {
131             print "Can't cd in a $type, please add an index (e.g. $name:foo)\n";
132             }
133             elsif ( defined $new_node && $type eq 'leaf' ) {
134             print "Can't cd in a $type\n";
135             }
136             else {
137             print "Cannot find @_\n";
138             }
139              
140             return "";
141             };
142              
143             my %run_dispatch = (
144             help => sub { return $syntax; },
145             set => sub {
146             my $self = shift;
147             if (@_) {
148             $self->{current_node}->load(join('',@_));
149             }
150             else {
151             say "No command given.";
152             }
153             return "";
154             },
155             display => sub ($self, @args) {
156             unless (@args) {
157             say "Nothing to display";
158             return;
159             }
160             return $self->{current_node}->grab_value(@args);
161             },
162             info => sub {
163             my $self = shift;
164             my $cnode = $self->{current_node};
165             my $target = @_ ? $cnode->grab(steps => [@_]) : $cnode;
166             return join("\n", $target->get_info );
167             },
168             ls => sub {
169             my $self = shift;
170             my $target = $self->{current_node};
171             my $pattern = '*';
172             for (@_) {
173             if (/\*/ and not /^["']/) {
174             $pattern = $_;
175             last;
176             }
177             $target = $target->grab(steps => $_);
178             }
179             $pattern =~ s/\*/.*/g;
180              
181             my $i = $self->{current_node}->instance;
182             my @res = $target->can('children') ? grep {/^$pattern$/} $target->children : ();
183             return join( ' ', @res );
184             },
185             tree => sub {
186             my $self = shift;
187             my $i = $self->{current_node}->instance;
188             my $cnode = $self->{current_node};
189             my $target = @_ ? $cnode->grab(steps => [@_]) : $cnode;
190             my @res = $target->dump_tree( mode => 'user' );
191             return join( ' ', @res );
192             },
193             delete => sub {
194             my $self = shift;
195             if ($_[0]) {
196             my ( $elt_name, $key ) = split /\s*:\s*/, $_[0];
197             my $elt = $self->{current_node}->fetch_element($elt_name);
198             if ( length($key) ) {
199             $elt->delete($key);
200             }
201             else {
202             $elt->store(undef);
203             }
204             }
205             else {
206             say "delete what ?";
207             }
208             return '';
209             },
210             clear => sub {
211             my ( $self, $elt_name ) = @_;
212             if ($elt_name) {
213             $self->{current_node}->fetch_element($elt_name)->clear();
214             }
215             else {
216             say "Expected element name for clear command. I.e. one of ",
217             join(' ',$self->{current_node}->get_element_name);
218             }
219             return '';
220             },
221             check => sub {
222             my ( $self, $elt_name ) = @_;
223             if ($elt_name) {
224             $self->{current_node}->fetch_element($elt_name)->check();
225             }
226             else {
227             $self->{current_node}->check;
228             }
229             return '';
230             },
231             fix => sub {
232             my ( $self, $elt_name ) = @_;
233             if ($elt_name eq '!') {
234             $self->{root}->instance->apply_fixes;
235             }
236             elsif ($elt_name) {
237             $self->{current_node}->fetch_element($elt_name)->apply_fixes;
238             }
239             else {
240             $self->{current_node}->apply_fixes;
241             }
242             return '';
243             },
244             save => sub {
245             my ($self) = @_;
246             $self->{root}->instance->write_back();
247             return "done";
248             },
249             changes => sub {
250             my ( $self ) = @_;
251             return $self->{root}->instance->list_changes;
252             },
253             ll => $ll_sub,
254             cd => $cd_sub,
255             description => $desc_sub,
256             desc => $desc_sub,
257             );
258              
259             $run_dispatch{reset} = $run_dispatch{clear};
260             $run_dispatch{dump} = $run_dispatch{tree};
261              
262             sub simple_ui_commands {
263 1     1 0 51 my @cmds = sort keys %run_dispatch;
264 1         7 return @cmds;
265             }
266              
267             sub new {
268 1     1 0 3 my $type = shift;
269 1         6 my %args = @_;
270              
271 1         3 my $self = {};
272              
273 1         4 foreach my $p (qw/root title prompt/) {
274 3 50       12 $self->{$p} = delete $args{$p}
275             or croak "SimpleUI->new: Missing $p parameter";
276             }
277              
278 1         5 $self->{current_node} = $self->{root};
279              
280 1         4 bless $self, $type;
281             }
282              
283             sub run_loop {
284 0     0 1 0 my $self = shift;
285              
286 0         0 my $user_cmd;
287 0         0 print $self->prompt;
288 0         0 while ( defined( $user_cmd = <STDIN> ) ) {
289 0         0 chomp $user_cmd;
290 0 0 0     0 last if $user_cmd eq 'exit' or $user_cmd eq 'quit';
291 0         0 my $res = $self->run($user_cmd);
292 0 0       0 print $res, "\n" if defined $res;
293 0         0 print $self->prompt;
294             }
295 0         0 print "\n";
296              
297 0         0 my $instance = $self->{root}->instance;
298 0 0       0 if ( $instance->c_count ) {
299 0 0       0 if ($instance->has_changes) {
300 0         0 $instance->say_changes;
301 0         0 print "write back data before exit ? (Y/n)";
302 0         0 $user_cmd = <STDIN>;
303 0 0       0 $instance->write_back unless $user_cmd =~ /n/i;
304 0         0 print "\n";
305             }
306             }
307              
308             }
309              
310             sub prompt {
311 11     11 1 7911 my $self = shift;
312 11         32 my $ret = $self->{prompt} . ':';
313 11         57 my $loc = $self->{current_node}->location_short;
314 11 100       31 $ret .= " $loc " if $loc;
315 11         50 return $ret . '$ ';
316             }
317              
318             sub run {
319 10     10 0 6047 my ( $self, $user_cmd ) = @_;
320              
321 10 50       59 return '' unless $user_cmd =~ /\w/;
322              
323 10         61 my $re = $RE{delimited}{-delim=>q{'"}};
324 10         559 my ( $action, @args ) = ( $user_cmd =~ /((?:[^\s"']|$re)+)/g );
325              
326 10 100       1522 if ( defined $run_dispatch{$action} ) {
327 9         16 my $res;
328 9         15 my $ok = eval {
329 9         39 $res = $run_dispatch{$action}->( $self, @args );
330 9         22 1;
331             };
332 9 50       22 say $@->message unless $ok;
333 9         45 return $res;
334             }
335             else {
336 1         10 return "Unexpected command '$action'";
337             }
338             }
339              
340             sub list_cd_path {
341 1     1 0 305 my $self = shift;
342 1         3 my $c_node = $self->{current_node};
343              
344 1         3 my @result;
345 1         5 foreach my $elt_name ( $c_node->get_element_name ) {
346 18         45 my $t = $c_node->element_type($elt_name);
347              
348 18 100 100     68 if ( $t eq 'list' or $t eq 'hash' ) {
349             push @result,
350 7         17 map { "$elt_name:$_" } $c_node->fetch_element($elt_name)->fetch_all_indexes;
  2         10  
351             }
352             else {
353 11         40 push @result, $elt_name;
354             }
355             }
356              
357 1         7 return \@result;
358             }
359             1;
360              
361             #ABSTRACT: Simple interface for Config::Model
362              
363             __END__
364              
365             =pod
366              
367             =encoding UTF-8
368              
369             =head1 NAME
370              
371             Config::Model::SimpleUI - Simple interface for Config::Model
372              
373             =head1 VERSION
374              
375             version 2.153
376              
377             =head1 SYNOPSIS
378              
379             use Config::Model;
380             use Config::Model::SimpleUI ;
381              
382             # define configuration tree object
383             my $model = Config::Model->new;
384             $model->create_config_class(
385             name => "Foo",
386             element => [
387             [qw/foo bar/] => {
388             type => 'leaf',
389             value_type => 'string'
390             },
391             ]
392             );
393             $model ->create_config_class (
394             name => "MyClass",
395              
396             element => [
397              
398             [qw/foo bar/] => {
399             type => 'leaf',
400             value_type => 'string'
401             },
402             hash_of_nodes => {
403             type => 'hash', # hash id
404             index_type => 'string',
405             cargo => {
406             type => 'node',
407             config_class_name => 'Foo'
408             },
409             },
410             ],
411             ) ;
412              
413             my $inst = $model->instance(root_class_name => 'MyClass' );
414              
415             my $root = $inst->config_root ;
416              
417             # put data
418             my $steps = 'foo=FOO hash_of_nodes:fr foo=bonjour -
419             hash_of_nodes:en foo=hello ';
420             $root->load( steps => $steps );
421              
422             my $ui = Config::Model::SimpleUI->new(
423             root => $root ,
424             title => 'My class ui',
425             prompt => 'class ui',
426             );
427              
428             # engage in user interaction
429             $ui -> run_loop ;
430              
431             print $root->dump_tree ;
432              
433             Once the synopsis above has been saved in C<my_test.pl>, you can do:
434              
435             $ perl my_test.pl
436             class ui:$ ls
437             foo bar hash_of_nodes
438             class ui:$ ll hash_of_nodes
439             name value type comment
440             hash_of_nodes <Foo> node hash keys: "en" "fr"
441              
442             class ui:$ cd hash_of_nodes:en
443              
444             class ui: hash_of_nodes:en $ ll
445             name value type comment
446             foo hello string
447             bar [undef] string
448              
449             class ui: hash_of_nodes:en $ set bar=bonjour
450              
451             class ui: hash_of_nodes:en $ ll
452             name value type comment
453             foo hello string
454             bar bonjour string
455              
456             class ui: hash_of_nodes:en $ ^D
457              
458             At the end, the test script dumps the configuration tree. The modified
459             C<bar> value can be found in there:
460              
461             foo=FOO
462             hash_of_nodes:en
463             foo=hello
464             bar=bonjour -
465             hash_of_nodes:fr
466             foo=bonjour - -
467              
468             =head1 DESCRIPTION
469              
470             This module provides a pure ASCII user interface using STDIN and
471             STDOUT.
472              
473             =head1 USER COMMAND SYNTAX
474              
475             =over
476              
477             =item cd ...
478              
479             Jump into node or value element. You can use C<< cd <element> >>,
480             C<< cd <elt:key> >> or C<cd -> to go up one node or C<cd !>
481             to go to configuration root.
482              
483             =item set elt=value
484              
485             Set a leaf value.
486              
487             =item set elt:key=value
488              
489             Set a leaf value locate in a hash or list element.
490              
491             =item clear elt
492              
493             Clear leaf value (set to C<undef>) or removed all elements of hash or list.
494              
495             =item delete elt
496              
497             Delete leaf value.
498              
499             =item delete elt:key
500              
501             Delete a list or hash element
502              
503             =item display node_name elt:key
504              
505             Display a value
506              
507             =item ls [path] [ pattern ]
508              
509             Show elements of current node or of a node pointed by path. Elements
510             can be filtered with a shell pattern. See inline help for more details.
511              
512             =item ll [-nz] [-v] [ pattern ... ]
513              
514             Describe elements of current node. Can be used with shell patterns or element names.
515             Skip empty element with C<-nz> option. Display more information with C<-v> option
516              
517             =item tree [path]
518              
519             Show configuration tree from current node or of a node pointed by path.
520              
521             =item info [path]
522              
523             Show debug information on current node or on the element pointed by
524             path. The debug information may show model parametersm default or computed
525             values.
526              
527             =item help
528              
529             Show available commands.
530              
531             =item desc[ription]
532              
533             Show class description of current node.
534              
535             =item desc(elt)
536              
537             Show description of element from current node.
538              
539             =item desc(value)
540              
541             Show effect of value (for enum)
542              
543             =item changes
544              
545             Show unsaved changes
546              
547             =item check
548              
549             Without parameter, show warnings starting from current node. With an
550             element name as parameter, do the same on the element.
551              
552             =item fix
553              
554             Try to fix warning starting from current node. With an element name as parameter,
555             do the same on the element. With "C<!>" as parameter, try to fix warnings starting
556             from root node by calling L<apply_fixes|Config::Model::Instance/apply_fixes> there.
557              
558             =item exit
559              
560             Exit shell
561              
562             =back
563              
564             =head1 CONSTRUCTOR
565              
566             =head2 parameters
567              
568             =over
569              
570             =item root
571              
572             Root node of the configuration tree
573              
574             =item title
575              
576             UI title
577              
578             =item prompt
579              
580             UI prompt. The prompt will be completed with the location of the
581             current node.
582              
583             =back
584              
585             =head1 Methods
586              
587             =head2 run_loop
588              
589             Engage in user interaction until user enters '^D' (CTRL-D).
590              
591             =head1 AUTHOR
592              
593             Dominique Dumont, (ddumont at cpan dot org)
594              
595             =head1 SEE ALSO
596              
597             L<Config::Model>,
598             L<Config::Model::Instance>,
599             L<Config::Model::Node>,
600              
601             =head1 AUTHOR
602              
603             Dominique Dumont
604              
605             =head1 COPYRIGHT AND LICENSE
606              
607             This software is Copyright (c) 2005-2022 by Dominique Dumont.
608              
609             This is free software, licensed under:
610              
611             The GNU Lesser General Public License, Version 2.1, February 1999
612              
613             =cut