/usr/share/perl5/Prophet/CLI/Command.pm is in libprophet-perl 0.750-1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 | package Prophet::CLI::Command;
use Any::Moose;
use Prophet::CLI;
use Params::Validate qw(validate);
has cli => (
is => 'rw',
isa => 'Prophet::CLI',
weak_ref => 1,
handles => [
qw/app_handle handle config/,
],
);
has context => (
is => 'rw',
isa => 'Prophet::CLIContext',
handles => [
qw/args set_arg arg has_arg delete_arg arg_names/,
qw/props set_prop prop has_prop delete_prop prop_names/,
'add_to_prop_set', 'prop_set',
],
);
has editor_var => (
is => 'rw',
isa => 'Str',
default => 'PROPHET_EDITOR',
);
sub ARG_TRANSLATIONS {
my $self = shift;
return ( 'v' => 'verbose',
'a' => 'all' );
}
=head2 Registering argument translations
This is the Prophet CLI's way of supporting short forms for arguments,
e.g. you want to let '-v' be able to used for the same purpose as
'--verbose' without dirtying your code checking both or manually
setting them if they exist. We want it to be as easy as possible
to have short commands.
To use, have your command subclass do:
sub ARG_TRANSLATIONS { shift->SUPER::ARG_TRANSLATIONS(), f => 'file' };
You can register as many translations at a time as you want.
The arguments will be translated when the command object is
instantiated. If an arg already exists in the arg translation
table, it is overwritten with the new value.
=cut
sub _translate_args {
my $self = shift;
my %translations = $self->ARG_TRANSLATIONS;
for my $arg (keys %translations) {
$self->set_arg($translations{$arg}, $self->arg($arg))
if $self->has_arg($arg);
}
}
# run arg translations on object instantiation
sub BUILD {
my $self = shift;
$self->_translate_args();
return $self;
}
sub fatal_error {
my $self = shift;
my $reason = shift;
# always skip this fatal_error function when generating a stack trace
local $Carp::CarpLevel = $Carp::CarpLevel + 1;
die $reason . "\n";
}
=head2 require_uuid
Checks to make sure the uuid attribute is set. Prints an error and dies
with the command's usage string if it is not set.
=cut
sub require_uuid {
my $self = shift;
if (!$self->has_uuid) {
my $type = $self->type;
my $name = (split /::/, $self->meta->name)[-1];
warn "No UUID or LUID given!\n";
$self->print_usage;
}
}
=head2 edit_text [text] -> text
Filters the given text through the user's C<$EDITOR> using
L<Proc::InvokeEditor>. If C<$ENV{$self-E<gt>editor_var}> is specified
(C<$self-E<gt>editor_var> defaults to PROPHET_EDITOR), it is favored
over C<$EDITOR>.
=cut
sub edit_text {
my $self = shift;
my $text = shift;
# don't invoke the editor in a script, the test will appear to hang
#die "Tried to invoke an editor in a test script!" if $ENV{IN_PROPHET_TEST_COMMAND};
require Proc::InvokeEditor;
my $pi = Proc::InvokeEditor->new;
my $editors = $pi->editors;
my $editor = $ENV{$self->editor_var};
unshift @$editors, $editor if defined $editor;
$pi->editors($editors);
return scalar $pi->edit($text);
}
=head2 edit_hash hash => hashref, ordering => arrayref
Filters the hash through the user's C<$EDITOR> using L<Proc::InvokeEditor>.
If C<$ENV{$self-E<gt>editor_var}> is specified (C<$self-E<gt>editor_var>
defaults to PROPHET_EDITOR), it is favored over C<$EDITOR>.
No validation is done on the input or output.
If the optional ordering argument is specified, hash keys will be presented
in that order (with unspecified elements following) for edit.
If the record class for the current type defines a C<immutable_props>
routine, those props will not be presented for editing.
False values are not returned unless a prop is removed from the output.
=cut
sub edit_hash {
my $self = shift;
validate( @_, { hash => 1, ordering => 0 } );
my %args = @_;
my $hash = $args{'hash'};
my @ordering = @{ $args{'ordering'} || [] };
my $record = $self->_get_record_object;
my @do_not_edit = $record->can('immutable_props') ? $record->immutable_props : ();
if (@ordering) {
# add any keys not in @ordering to the end of it
my %keys_in_ordering;
map { $keys_in_ordering{$_} = 1 if exists($hash->{$_}) } @ordering;
map { push @ordering, $_ if !exists($keys_in_ordering{$_}) } keys %$hash;
} else {
@ordering = sort keys %$hash;
}
# filter out props we don't want to present for editing
my %do_not_edit = map { $_ => 1 } @do_not_edit;
@ordering = grep { !$do_not_edit{$_} } @ordering;
my $input = join "\n", map { "$_: $hash->{$_}" } @ordering;
my $output = $self->edit_text($input);
die "Aborted.\n" if $input eq $output;
# parse the output
my $filtered = {};
for my $line (split "\n", $output) {
if ($line =~ m/^([^:]+):\s*(.*)$/) {
my $prop = $1;
my $val = $2;
# don't return empty values
$filtered->{$prop} = $val unless !($val);
}
}
no warnings 'uninitialized';
# if a key is deleted intentionally, set its value to ''
for my $prop (keys %$hash) {
if (!exists $filtered->{$prop} and ! exists $do_not_edit{$prop}) {
$filtered->{$prop} = '';
}
}
# filter out unchanged keys as they clutter changesets if they're set again
map { delete $filtered->{$_} if $hash->{$_} eq $filtered->{$_} } keys %$filtered;
return $filtered;
}
=head2 edit_props arg => str, defaults => hashref, ordering => arrayref
Returns a hashref of the command's props mixed in with any default props.
If the "arg" argument is specified, (default "edit", use C<undef> if you only
want default arguments), then L</edit_hash> is invoked on the property list.
If the C<ordering> argument is specified, properties will be presented in that
order (with unspecified props following) if filtered through L</edit_hash>.
=cut
sub edit_props {
my $self = shift;
my %args = @_;
my $arg = $args{'arg'} || 'edit';
my $defaults = $args{'defaults'};
my %props;
if ($defaults) {
%props = (%{ $defaults }, %{ $self->props });
} else {
%props = %{$self->props};
}
if ($self->has_arg($arg)) {
return $self->edit_hash(hash => \%props, ordering => $args{'ordering'});
}
return \%props;
}
=head2 prompt_choices question
Asks user the question and returns 0 if answer was the second choice,
1 otherwise. (First choice is the default.)
=cut
sub prompt_choices {
my $self = shift;
my ($choice1, $choice2, $question) = @_;
$choice1 = uc $choice1; # default is capsed
$choice2 = lc $choice2; # non-default is lowercased
Prophet::CLI->end_pager();
print "$question [$choice1/$choice2]: ";
chomp( my $answer = <STDIN> );
Prophet::CLI->start_pager();
return $answer !~ /^$choice2$/i;
}
=head2 prompt_Yn question
Asks user the question and returns true if answer was positive or false
otherwise. Default answer is 'Yes' (returns true).
=cut
sub prompt_Yn {
my $self = shift;
my $msg = shift;
return $self->prompt_choices( 'y', 'n', $msg );
}
# Create a new [replica] config file section for the given replica if
# it hasn't been seen before (config section doesn't already exist)
sub record_replica_in_config {
my $self = shift;
my $replica_url = shift;
my $replica_uuid = shift;
my $url_variable = shift || 'url';
my %previous_sources_by_uuid
= $self->app_handle->config->sources(
by_variable => 1,
variable => 'uuid',
);
my $found_prev_replica = $previous_sources_by_uuid{$replica_uuid};
if ( !$found_prev_replica ) {
# replica section doesn't exist at all; create a new one
my $url = $replica_url;
$self->app_handle->config->group_set(
$self->app_handle->config->replica_config_file,
[
{
key => "replica.$url.$url_variable",
value => $replica_url,
},
{
key => "replica.$url.uuid",
value => $replica_uuid,
},
],
);
}
elsif ( $found_prev_replica ne $replica_url ) {
# We're publishing to a different place than where it was published
# to previously--we don't want to end up with a multivalue in the
# config file, so just replace the old value.
my $name = $self->app_handle->display_name_for_replica($replica_uuid);
$self->app_handle->config->set(
filename => $self->app_handle->config->replica_config_file,
key => "replica.$name.$url_variable",
value => $replica_url,
);
}
}
=head2 print_usage
Print the command's usage message to STDERR and die. Commands should
implement C<usage_msg>, which returns the usage message.
If the usage message method needs arguments passed in, use a closure.
=cut
sub print_usage {
my $self = shift;
my %args = (
usage_method => sub { $self->usage_msg },
@_,
);
die $args{usage_method}();
}
=head2 get_cmd_and_subcmd_names [no_type => 1]
Gets the name of the script that was run and the primary commands that were
specified on the command-line. If a true boolean is passed in as C<no_type>,
won't add '<record-type>' to the subcmd if no type was passed in via the
primary commands.
=cut
sub get_cmd_and_subcmd_names {
my $self = shift;
my %args = @_;
my $cmd = $self->cli->get_script_name;
my @primary_commands = @{ $self->context->primary_commands };
# if primary commands was only length 1, the type was not specified
# and we should indicate that a type is expected
push @primary_commands, '<record-type>'
if @primary_commands <= 1 && !$args{no_type};
my $type_and_subcmd = join( q{ }, @primary_commands );
return ($cmd, $type_and_subcmd);
}
__PACKAGE__->meta->make_immutable;
no Any::Moose;
1;
|