| 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
|
|
|
|
|
|
|
|