File Coverage

blib/lib/Prophet/CLI.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1             package Prophet::CLI;
2 39     39   19107 use Any::Moose;
  39         905377  
  39         358  
3              
4 39     39   36870 use Prophet;
  39         84  
  39         1023  
5 39     39   18056 use Prophet::Replica;
  39         117  
  39         1645  
6 39     39   21128 use Prophet::CLI::Command;
  39         114  
  39         1456  
7 39     39   17342 use Prophet::CLI::Dispatcher;
  0            
  0            
8             use Prophet::CLIContext;
9              
10             use List::Util 'first';
11              
12             has app_class => (
13             is => 'rw',
14             isa => 'ClassName',
15             default => 'Prophet::App',
16             );
17              
18             has record_class => (
19             is => 'rw',
20             isa => 'ClassName',
21             lazy => 1,
22             default => 'Prophet::Record',
23             );
24              
25             has app_handle => (
26             is => 'rw',
27             isa => 'Prophet::App',
28             lazy => 1,
29             handles => [qw/handle config/],
30             default => sub {
31             return $_[0]->app_class->new;
32             },
33             );
34              
35              
36             has context => (
37             is => 'rw',
38             isa => 'Prophet::CLIContext',
39             lazy => 1,
40             default => sub {
41             return Prophet::CLIContext->new( app_handle => shift->app_handle);
42             }
43              
44             );
45              
46             has interactive_shell => (
47             is => 'rw',
48             isa => 'Bool',
49             default => 0,
50             );
51              
52              
53             =head2 _record_cmd
54              
55             handles the subcommand for a particular type
56              
57             =cut
58              
59             =head2 dispatcher_class -> Class
60              
61             Returns the dispatcher used to dispatch command lines. You'll want to override
62             this in your subclass.
63              
64             =cut
65              
66             sub dispatcher_class { "Prophet::CLI::Dispatcher" }
67              
68             =head2 run_one_command
69              
70             Runs a command specified by commandline arguments given in an
71             ARGV-like array of argumnents and key value pairs . To use in a
72             commandline front-end, create a L object and pass in
73             your main app class as app_class, then run this routine.
74              
75             Example:
76              
77             my $cli = Prophet::CLI->new({ app_class => 'App::SD' });
78             $cli->run_one_command(@ARGV);
79              
80             =cut
81              
82             sub run_one_command {
83             my $self = shift;
84             my @args = (@_);
85              
86             # find the first alias that matches, rerun the aliased cmd
87             # note: keys of aliases are treated as regex,
88             # we need to substitute $1, $2 ... in the value if there's any
89              
90             my $ori_cmd = join ' ', @args;
91              
92             if ($self->app_handle->local_replica_url) {
93             my $aliases = $self->app_handle->config->aliases;
94             for my $alias ( keys %$aliases ) {
95             my $command = $self->_command_matches_alias($ori_cmd, $alias, $aliases->{$alias}) || next;
96              
97             # we don't want to recursively call if people stupidly write
98             # alias pull --local = pull --local
99             next if ( $command eq $ori_cmd );
100             return $self->run_one_command( split /\s+/, $command );
101             }
102             }
103             # really, we shouldn't be doing this stuff from the command dispatcher
104             $self->context( Prophet::CLIContext->new( app_handle => $self->app_handle ) );
105             $self->context->setup_from_args(@args);
106             my $dispatcher = $self->dispatcher_class->new( cli => $self );
107              
108             # Path::Dispatcher is string-based, so we need to join the args
109             # hash with spaces before passing off (args with whitespace in
110             # them are quoted, double quotes are escaped)
111             my $dispatch_command_string = join(' ', map {
112             s/"/\\"/g; # escape double quotes
113             /\s/ ? qq{"$_"} : $_;
114             } @{ $self->context->primary_commands });
115             my $dispatch = $dispatcher->dispatch( $dispatch_command_string );
116             $self->start_pager();
117             $dispatch->run($dispatcher);
118             $self->end_pager();
119             }
120              
121             sub _command_matches_alias {
122             my $self = shift;
123             my $cmd = shift;
124             my $alias = shift;
125             my $dispatch_to = shift;;
126             if ( $cmd =~ /^\Q$alias\E\s*(.*)$/ ) {
127             no strict 'refs';
128              
129             my $rest = $1;
130             # we want to start at index 1
131             my @captures = (undef, $self->tokenize($rest));
132             $dispatch_to =~ s/\$$_\b/$captures[$_]/g for 1 .. 20;
133             return $dispatch_to;
134             }
135             return undef;
136             }
137              
138              
139             sub tokenize {
140             my $self = shift;
141             my $string = shift;
142             my @tokens = split(/\s+/,$string); # XXX TODO deal with quoted tokens
143             return @tokens;
144             }
145              
146             sub is_interactive {
147             return -t STDIN && -t STDOUT;
148             }
149              
150             sub get_pager {
151             my $self = shift;
152             return $ENV{'PAGER'} || `which less` || `which more`;
153             }
154              
155             our $ORIGINAL_STDOUT;
156              
157             sub start_pager {
158             my $self = shift;
159             my $content = shift;
160             if (is_interactive() && !$ORIGINAL_STDOUT) {
161             local $ENV{'LESS'} = '-FXe';
162             local $ENV{'MORE'};
163             $ENV{'MORE'} = '-FXe' unless $^O =~ /^MSWin/;
164              
165             my $pager = $self->get_pager();
166             return unless $pager;
167             open (my $cmd, "|-", $pager) || return;
168             $|++;
169             $ORIGINAL_STDOUT = *STDOUT;
170              
171             # $pager will be closed once we restore STDOUT to $ORIGINAL_STDOUT
172             *STDOUT = $cmd;
173             }
174             }
175              
176             sub in_pager {
177             return $ORIGINAL_STDOUT ? 1 :0;
178             }
179              
180             sub end_pager {
181             my $self = shift;
182             return unless ($self->in_pager);
183             *STDOUT = $ORIGINAL_STDOUT ;
184              
185             # closes the pager
186             $ORIGINAL_STDOUT = undef;
187             }
188              
189             =head2 get_script_name
190              
191             Return the name of the script that was run. This is the empty string
192             if we're in a shell, otherwise the script name concatenated with
193             a space character. This is so you can just use this for e.g.
194             printing usage messages or help docs that might be run from either
195             a shell or the command line.
196              
197             =cut
198              
199             sub get_script_name {
200             my $self = shift;
201             return '' if $self->interactive_shell;
202             require File::Spec;
203             my ($cmd) = ( File::Spec->splitpath($0) )[2];
204             return $cmd . ' ';
205             }
206              
207             END {
208             *STDOUT = $ORIGINAL_STDOUT if $ORIGINAL_STDOUT;
209             }
210              
211             __PACKAGE__->meta->make_immutable;
212             no Any::Moose;
213              
214             1;
215