1package App::Sqitch::Command;
2
3use 5.010;
4use strict;
5use warnings;
6use utf8;
7use Try::Tiny;
8use Locale::TextDomain qw(App-Sqitch);
9use App::Sqitch::X qw(hurl);
10use Hash::Merge 'merge';
11use Moo;
12use App::Sqitch::Types qw(Sqitch Target);
13
14our $VERSION = '0.9994';
15
16use constant ENGINES => qw(
17    pg
18    sqlite
19    mysql
20    oracle
21    firebird
22    vertica
23);
24
25has sqitch => (
26    is       => 'ro',
27    isa      => Sqitch,
28    required => 1,
29    handles  => [qw(
30        run
31        shell
32        quote_shell
33        capture
34        probe
35        verbosity
36        trace
37        trace_literal
38        debug
39        debug_literal
40        info
41        info_literal
42        comment
43        comment_literal
44        emit
45        emit_literal
46        vent
47        vent_literal
48        warn
49        warn_literal
50        page
51        page_literal
52        prompt
53        ask_y_n
54    )],
55);
56
57has default_target => (
58    is      => 'ro',
59    isa     => Target,
60    lazy    => 1,
61    default => sub {
62        my $sqitch = shift->sqitch;
63        my @params = (sqitch => $sqitch);
64        unless (
65               $sqitch->options->{engine}
66            || $sqitch->config->get(key => 'core.engine')
67            || $sqitch->config->get(key => 'core.target')
68        ) {
69            # No specified engine, so specify an engineless URI.
70            require URI::db;
71            push @params, uri => URI::db->new('db:');
72        }
73        require App::Sqitch::Target;
74        return App::Sqitch::Target->new(@params);
75    },
76);
77
78sub command {
79    my $class = ref $_[0] || shift;
80    return '' if $class eq __PACKAGE__;
81    my $pkg = quotemeta __PACKAGE__;
82    $class =~ s/^$pkg\:://;
83    $class =~ s/_/-/g;
84    return $class;
85}
86
87sub load {
88    my ( $class, $p ) = @_;
89    my $sqitch = $p->{sqitch};
90
91    # We should have a command.
92    $class->usage unless $p->{command};
93    ( my $cmd = $p->{command} ) =~ s/-/_/g;
94
95    # Load the command class.
96    my $pkg = __PACKAGE__ . "::$cmd";
97    try {
98        eval "require $pkg" or die $@;
99    }
100    catch {
101        # Emit the original error for debugging.
102        $sqitch->debug($_);
103
104        # Suggest help if it's not a valid command.
105        hurl {
106            ident   => 'command',
107            exitval => 1,
108            message => __x(
109                '"{command}" is not a valid command',
110                command => $cmd,
111            ),
112        };
113    };
114
115    # Merge the command-line options and configuration parameters
116    my $params = $pkg->configure(
117        $p->{config},
118        $pkg->_parse_opts( $p->{args} )
119    );
120
121    # Instantiate and return the command.
122    $params->{sqitch} = $sqitch;
123    return $pkg->new($params);
124}
125
126sub configure {
127    my ( $class, $config, $options ) = @_;
128
129    return Hash::Merge->new->merge(
130        $options,
131        $config->get_section( section => $class->command ),
132    );
133}
134
135sub options {
136    return;
137}
138
139sub _parse_opts {
140    my ( $class, $args ) = @_;
141    return {} unless $args && @{$args};
142
143    my %opts;
144    Getopt::Long::Configure(qw(bundling no_pass_through));
145    Getopt::Long::GetOptionsFromArray( $args, \%opts, $class->options )
146        or $class->usage;
147
148    # Convert dashes to underscores.
149    for my $k (keys %opts) {
150        next unless ( my $nk = $k ) =~ s/-/_/g;
151        $opts{$nk} = delete $opts{$k};
152    }
153
154    return \%opts;
155}
156
157sub _bn {
158    require File::Basename;
159    File::Basename::basename($0);
160}
161
162sub _pod2usage {
163    my ( $self, %params ) = @_;
164    my $command = $self->command;
165    require Pod::Find;
166    require Pod::Usage;
167    my $bn = _bn;
168    my $find_pod = sub {
169        Pod::Find::pod_where({ '-inc' => 1, '-script' => 1 }, shift );
170    };
171    $params{'-input'} ||= $find_pod->("$bn-$command")
172                      ||  $find_pod->("sqitch-$command")
173                      ||  $find_pod->($bn)
174                      ||  $find_pod->('sqitch')
175                      ||  $find_pod->(ref $self || $self)
176                      ||  $find_pod->(__PACKAGE__);
177    Pod::Usage::pod2usage(
178        '-verbose'  => 99,
179        '-sections' => '(?i:(Usage|Synopsis|Options))',
180        '-exitval'  => 2,
181        %params
182    );
183}
184
185sub execute {
186    my $self = shift;
187    hurl(
188        'The execute() method must be called from a subclass of '
189        . __PACKAGE__
190    ) if ref $self eq __PACKAGE__;
191
192    hurl 'The execute() method has not been overridden in ' . ref $self;
193}
194
195sub usage {
196    my $self = shift;
197    require Pod::Find;
198    my $upod = _bn . '-' . $self->command . '-usage';
199    $self->_pod2usage(
200        '-input' => Pod::Find::pod_where( { '-inc' => 1 }, $upod ) || undef,
201        '-message' => join '', @_
202    );
203}
204
205
206sub parse_args {
207    my ($self, %p) = @_;
208    my $sqitch = $self->sqitch;
209    my $config = $sqitch->config;
210    require App::Sqitch::Target;
211    my $target = App::Sqitch::Target->new( sqitch => $sqitch, name => $p{target} );
212    my (%seen, %target_for);
213
214    my %rec = map { $_ => [] } qw(targets unknown);
215    $rec{changes} = [] unless $p{no_changes};
216    if ($p{target}) {
217        push @{ $rec{targets} } => $target;
218        $seen{$target->name}++;
219    }
220
221    my %engines = map { $_ => 1 } ENGINES;
222    for my $arg (@{ $p{args} }) {
223        if ( !$p{no_changes} && $target && $target->plan->contains($arg) ) {
224            # A change. Keep the target if it's the default.
225            push @{ $rec{targets} } => $target unless $seen{$target->name}++;
226            push @{ $rec{changes} } => $arg;
227        } elsif ($config->get( key => "target.$arg.uri") || URI->new($arg)->isa('URI::db')) {
228            # A target. Instantiate and keep for subsequente change searches.
229            $target = App::Sqitch::Target->new( sqitch => $sqitch, name => $arg );
230            push @{ $rec{targets} } => $target unless $seen{$target->name}++;
231        } elsif ($engines{$arg}) {
232            # An engine. Add its target.
233            my $name = $config->get(key => "engine.$arg.target") || "db:$arg:";
234            $target = App::Sqitch::Target->new( sqitch => $sqitch, name => $name );
235            push @{ $rec{targets} } => $target unless $seen{$target->name}++;
236        } elsif (-e $arg) {
237            # Maybe it's a plan file?
238            %target_for = map {
239                $_->plan_file => $_
240            } reverse App::Sqitch::Target->all_targets(sqitch => $sqitch) unless %target_for;
241            if ($target_for{$arg}) {
242                # It *is* a plan file.
243                $target = $target_for{$arg};
244                push @{ $rec{targets} } => $target unless $seen{$target->name}++;
245            } else {
246                # Nah, who knows.
247                push @{ $rec{unknown} } => $arg;
248            }
249        } else {
250            # Who knows?
251            push @{ $rec{unknown} } => $arg;
252        }
253    }
254
255    # Make sure we have the default target
256    push @{ $rec{targets} } => $target
257        if $target && !$p{no_default} && !@{ $rec{targets} };
258
259    # Replace missing names with unnknown values.
260    my @names = map { $_ || shift @{ $rec{unknown} } } @{ $p{names} || [] };
261
262    # Die on unknowns.
263    if (my @unknown = @{ $rec{unknown} } ) {
264        hurl $self->command => __nx(
265            'Unknown argument "{arg}"',
266            'Unknown arguments: {arg}',
267            scalar @unknown,
268            arg => join ', ', @unknown
269        );
270    }
271
272    # Figure out what targets to access. Use default unless --all.
273    my @targets = @{ $rec{targets} };
274    if ($p{all}) {
275        # Got --all.
276        hurl $self->command => __(
277            'Cannot specify both --all and engine, target, or plan arugments'
278        ) if @targets;
279        @targets = App::Sqitch::Target->all_targets( sqitch => $sqitch );
280    } elsif (!@targets) {
281        # Use all if tag.all is set, otherwise just the default.
282        my $key = $self->command . '.all';
283        @targets = $self->sqitch->config->get(key => $key, as => 'bool')
284            ? App::Sqitch::Target->all_targets( sqitch => $sqitch )
285            : ($self->default_target);
286    }
287
288    return (@names, \@targets, $rec{changes});
289}
290
2911;
292
293__END__
294
295=head1 Name
296
297App::Sqitch::Command - Sqitch Command support
298
299=head1 Synopsis
300
301  my $cmd = App::Sqitch::Command->load( deploy => \%params );
302  $cmd->run;
303
304=head1 Description
305
306App::Sqitch::Command is the base class for all Sqitch commands.
307
308=head1 Interface
309
310=head2 Constants
311
312=head3 C<ENGINES>
313
314Returns the list of supported engines, currently:
315
316=over
317
318=item * C<firebird>
319
320=item * C<mysql>
321
322=item * C<oracle>
323
324=item * C<pg>
325
326=item * C<sqlite>
327
328=item * C<vertica>
329
330=back
331
332=head2 Class Methods
333
334=head3 C<options>
335
336  my @spec = App::Sqitch::Command->options;
337
338Returns a list of L<Getopt::Long> options specifications. When C<load> loads
339the class, any options passed to the command will be parsed using these
340values. The keys in the resulting hash will be the first part of each option,
341with dashes converted to underscores. This hash will be passed to C<configure>
342along with a L<App::Sqitch::Config> object for munging into parameters to be
343passed to the constructor.
344
345Here's an example excerpted from the C<config> command:
346
347  sub options {
348      return qw(
349          get
350          unset
351          list
352          global
353          system
354          config-file=s
355      );
356  }
357
358This will result in hash keys with the same names as each option except for
359C<config-file=s>, which will be named C<config_file>.
360
361=head3 C<configure>
362
363  my $params = App::Sqitch::Command->configure($config, $options);
364
365Takes two arguments, an L<App::Sqitch::Config> object and the hash of
366command-line options as specified by C<options>. The returned hash should be
367the result of munging these two objects into a hash reference of parameters to
368be passed to the command subclass constructor.
369
370By default, this method converts dashes to underscores in command-line options
371keys, and then merges the configuration values with the options, with the
372command-line options taking priority. You may wish to override this method to
373do something different.
374
375=head2 Constructors
376
377=head3 C<load>
378
379  my $cmd = App::Sqitch::Command->load( \%params );
380
381A factory method for instantiating Sqitch commands. It loads the subclass for
382the specified command, uses the options returned by C<options> to parse
383command-line options, calls C<configure> to merge configuration with the
384options, and finally calls C<new> with the resulting hash. Supported parameters
385are:
386
387=over
388
389=item C<sqitch>
390
391The App::Sqitch object driving the whole thing.
392
393=item C<config>
394
395An L<App::Sqitch::Config> representing the current application configuration
396state.
397
398=item C<command>
399
400The name of the command to be executed.
401
402=item C<args>
403
404An array reference of command-line arguments passed to the command.
405
406=back
407
408=head3 C<new>
409
410  my $cmd = App::Sqitch::Command->new(%params);
411
412Instantiates and returns a App::Sqitch::Command object. This method is not
413designed to be overridden by subclasses; they should implement
414L<C<BUILDARGS>|Moo::Manual::Construction/BUILDARGS> or
415L<C<BUILD>|Moo::Manual::Construction/BUILD>, instead.
416
417=head2 Accessors
418
419=head3 C<sqitch>
420
421  my $sqitch = $cmd->sqitch;
422
423Returns the L<App::Sqitch> object that instantiated the command. Commands may
424access its properties in order to manage global state.
425
426=head2 Overridable Instance Methods
427
428These methods should be overridden by all subclasses.
429
430=head3 C<execute>
431
432  $cmd->execute;
433
434Executes the command. This is the method that does the work of the command.
435Must be overridden in all subclasses. Dies if the method is not overridden for
436the object on which it is called, or if it is called against a base
437App::Sqitch::Command object.
438
439=head3 C<command>
440
441  my $command = $cmd->command;
442
443The name of the command. Defaults to the last part of the package name, so as
444a rule you should not need to override it, since it is that string that Sqitch
445uses to find the command class.
446
447=head2 Utility Instance Methods
448
449These methods are mainly provided as utilities for the command subclasses to
450use.
451
452=head3 C<default_target>
453
454  my $target = $cmd->default_target;
455
456This method returns the default target. It should only be used by commands
457that don't use a C<parse_args()> to find and load a target.
458
459This method should always return a target option, never C<undef>. If the
460C<--engine> option or C<core.engine> configuration option has been set, then
461the target will support that engine. In the latter case, if
462C<engine.$engine.target> is set, that value will be used. Otherwise, the
463returned target will have a URI of C<db:> and no associated engine; the
464C<engine> method will throw an exception. This behavior should be fine for
465commands that don't need to load the engine.
466
467=head3 C<parse_args>
468
469  my ($name1, $name2, $targets, $changes) = $cmd->parse_args(
470    names  => \@names,
471    target => $target_name,
472    args   => \@args
473  );
474
475Examines each argument to determine whether it's a known change spec or
476identifies a target. Unrecognized arguments will replace false values in the
477C<names> array reference. Any remaining unknown arguments will trigger an
478error.
479
480Returns a list consisting all the desired names, followed by an array
481reference of target objects and an array reference of change specs.
482
483This method is useful for commands that take a number of arguments where the
484order may be mixed.
485
486The supported parameters are:
487
488=over
489
490=item C<args>
491
492An array reference of the command arguments.
493
494=item C<target>
495
496The name of a target, if any. Useful for commands that offer their own
497C<--target> option. This target will be the default target, and the first
498returned in the targets array.
499
500=item C<names>
501
502An array reference of names. If any is false, its place will be taken by an
503otherwise unrecognized argument. The number of values in this array reference
504determines the number of values returned as names in the return values. Such
505values may still be false or undefined; it's up to the caller to decide what
506to do about that.
507
508=item C<all>
509
510In the event that no targets are recognized (or changes that implicitly
511recognize the default target), if this parameter is true, then all known
512targets from the configuration will be returned.
513
514=item C<no_changes>
515
516If true, the parser will not check to see if any argument corresponds to a
517change. The last value returned will be C<undef> instead of the usual array
518reference. Any argument that might have been recognized as a change will
519instead be included in either the C<targets> array -- if it's recognized as a
520target -- or used to set names to return. Any remaining are considered
521unknown arguments and will result in an exception.
522
523=item C<no_default>
524
525If true, no default target will be returned, even if no other targets are
526found. See below for details.
527
528=back
529
530If a target parameter is passed, it will always be instantiated and returned
531as the first item in the "target" array, and arguments recognized as changes
532in the plan associated with that target will be returned as changes.
533
534If no target is passed or appears in the arguments, a default target will be
535instantiated based on the command-line options and configuration -- unless the
536C<no_default> parameter is true. Unlike the target returned by
537C<default_target>, this target B<must> have an associated engine specified by
538the C<--engine> option or configuration. This is on the assumption that it
539will be used by commands that require an engine to do their work. Of course,
540any changes must be recognized from the plan associated with this target.
541
542Changes are only recognized if they're found in the plan of the target that
543precedes them. If no target precedes them, the target specified by the
544C<target> parameter or the default target will be searched. Such changes can
545be specified in any way documented in L<sqitchchanges>.
546
547Targets may be recognized by any one of these types of arguments:
548
549=over
550
551=item * Target Name
552
553=item * Database URI
554
555=item * Engine Name
556
557=item * Plan File
558
559=back
560
561In the case of plan files, C<parse_args()> will return the first target it
562finds for that plan file, even if multiple targets use the same plan file. The
563order of precedence for this determination is the default project target,
564followed by named targets, then engine targets.
565
566=head3 C<run>
567
568  $cmd->run('echo hello');
569
570Runs a system command and waits for it to finish. Throws an exception on
571error.
572
573=head3 C<capture>
574
575  my @files = $cmd->capture(qw(ls -lah));
576
577Runs a system command and captures its output to C<STDOUT>. Returns the output
578lines in list context and the concatenation of the lines in scalar context.
579Throws an exception on error.
580
581=head3 C<probe>
582
583  my $git_version = $cmd->capture(qw(git --version));
584
585Like C<capture>, but returns just the C<chomp>ed first line of output.
586
587=head3 C<verbosity>
588
589  my $verbosity = $cmd->verbosity;
590
591Returns the verbosity level.
592
593=head3 C<trace>
594
595Send trace information to C<STDOUT> if the verbosity level is 3 or higher.
596Trace messages will have C<trace: > prefixed to every line. If it's lower than
5973, nothing will be output.
598
599=head3 C<debug>
600
601  $cmd->debug('Found snuggle in the crib.');
602
603Send debug information to C<STDOUT> if the verbosity level is 2 or higher.
604Debug messages will have C<debug: > prefixed to every line. If it's lower than
6052, nothing will be output.
606
607=head3 C<info>
608
609  $cmd->info('Nothing to deploy (up-to-date)');
610
611Send informational message to C<STDOUT> if the verbosity level is 1 or higher,
612which, by default, it is. Should be used for normal messages the user would
613normally want to see. If verbosity is lower than 1, nothing will be output.
614
615=head3 C<comment>
616
617  $cmd->comment('On database flipr_test');
618
619Send comments to C<STDOUT> if the verbosity level is 1 or higher, which, by
620default, it is. Comments have C<# > prefixed to every line. If verbosity is
621lower than 1, nothing will be output.
622
623=head3 C<emit>
624
625  $cmd->emit('core.editor=emacs');
626
627Send a message to C<STDOUT>, without regard to the verbosity. Should be used
628only if the user explicitly asks for output, such as for
629C<sqitch config --get core.editor>.
630
631=head3 C<vent>
632
633  $cmd->vent('That was a misage.');
634
635Send a message to C<STDERR>, without regard to the verbosity. Should be used
636only for error messages to be printed before exiting with an error, such as
637when reverting failed changes.
638
639=head3 C<page>
640
641  $sqitch->page('Search results:');
642
643Like C<emit()>, but sends the output to a pager handle rather than C<STDOUT>.
644Unless there is no TTY (such as when output is being piped elsewhere), in
645which case it I<is> sent to C<STDOUT>. Meant to be used to send a lot of data
646to the user at once, such as when display the results of searching the event
647log:
648
649  $iter = $sqitch->engine->search_events;
650  while ( my $change = $iter->() ) {
651      $cmd->page(join ' - ', @{ $change }{ qw(change_id event change) });
652  }
653
654=head3 C<warn>
655
656  $cmd->warn('Could not find nerble; using nobble instead.');
657
658Send a warning messages to C<STDERR>. Warnings will have C<warning: > prefixed
659to every line. Use if something unexpected happened but you can recover from
660it.
661
662=head3 C<usage>
663
664  $cmd->usage('Missing "value" argument');
665
666Sends the specified message to C<STDERR>, followed by the usage sections of
667the command's documentation. Those sections may be named "Name", "Synopsis",
668or "Options". Any or all of these will be shown. The doc used to display them
669will be the first found of:
670
671=over
672
673=item C<sqitch-$command-usage>
674
675=item C<sqitch-$command>
676
677=item C<sqitch>
678
679=item C<App::Sqitch::Command::$command>
680
681=item C<App::Sqitch::Command>
682
683=back
684
685For an ideal usage messages, C<sqitch-$command-usage.pod> should be created by
686all command subclasses.
687
688=head1 See Also
689
690=over
691
692=item L<sqitch>
693
694The Sqitch command-line client.
695
696=back
697
698=head1 Author
699
700David E. Wheeler <david@justatheory.com>
701
702=head1 License
703
704Copyright (c) 2012-2015 iovation Inc.
705
706Permission is hereby granted, free of charge, to any person obtaining a copy
707of this software and associated documentation files (the "Software"), to deal
708in the Software without restriction, including without limitation the rights
709to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
710copies of the Software, and to permit persons to whom the Software is
711furnished to do so, subject to the following conditions:
712
713The above copyright notice and this permission notice shall be included in all
714copies or substantial portions of the Software.
715
716THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
717IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
718FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
719AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
720LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
721OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
722SOFTWARE.
723
724=cut
725
726