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