App-karr

 view release on metacpan or  search on metacpan

lib/App/karr/Dispatch.pm  view on Meta::CPAN

# MooX::Options ever look at argv (ticket #256).
#
# App::karr::Role::CliArgs/normalize_option_argv carries the whole diagnosis and
# the condition under which this call may go again. What belongs here is only
# why the CALL is here: the rewrite has to know which token is a flag and which
# is the value of the flag in front of it, that answer comes out of the option
# table of the command being run, and this is the last point at which the whole
# argv is still in one piece.
#
# Which command that is, is decided exactly the way MooX::Cmd decides it half a
# millisecond later: the first argv token that names a command
# (MooX::Cmd::Role::_initialize_from_cmd, `first_index` across the WHOLE argv --
# see the %COMMAND_ALIASES comment in App::karr for why the table is asked and
# argv is not rewritten). Everything before that token is the root's own argv
# and is normalized against App::karr, everything after it against the command
# class. The command name itself is passed through untouched, which is what
# keeps the dashed command spellings (`get-refs`) findable in the table.
sub _normalize_option_argv {
    my $commands = App::karr->_build_command_commands( {} );

    my ( $at, $class );
    for my $i ( 0 .. $#ARGV ) {
        next unless defined $commands->{ $ARGV[$i] };
        ( $at, $class ) = ( $i, $commands->{ $ARGV[$i] } );
        last;
    }

    my @root = defined $at ? @ARGV[ 0 .. $at - 1 ]      : @ARGV;
    my @name = defined $at ? ( $ARGV[$at] )             : ();
    my @rest = defined $at ? @ARGV[ $at + 1 .. $#ARGV ] : ();

    # Both calls are class-method calls: normalize_option_argv reads the option
    # table and nothing else, so it needs no instance -- and no instance exists
    # yet, which is the point of doing this here. Every App::karr::Cmd::* class
    # composes App::karr::Role::CliArgs (t/256 pins that, because a command that
    # forgot to would silently keep the defect for its own dashed options), but
    # the guard stays: MooX::Cmd's plugin scan decides what a command class is,
    # not this file.
    @root = App::karr->normalize_option_argv( \@root );
    if ( defined $class ) {
        use_module($class);
        @rest = $class->normalize_option_argv( \@rest )
            if $class->can('normalize_option_argv');
    }

    @ARGV = ( @root, @name, @rest );
    return;
}

sub dispatch {
    my (@argv) = @_;

    # dispatch operates on the global @ARGV, exactly as bin/karr did inline:
    # the two rewrites above and MooX::Cmd::new_with_cmd all read and write it.
    # Localising it lets an embedding host call dispatch repeatedly, and lets
    # bin/karr pass its own @ARGV in unchanged.
    local @ARGV = @argv;

    # The character/octet boundary (ticket #53). Everything the OS hands in is
    # bytes; everything a command body sees is Perl characters. @ARGV comes in
    # decoded, STDOUT and STDERR encode on the way out, and no command body
    # encodes anything itself. STDIN stays raw on purpose -- every reader of it
    # decodes its own payload (#246).
    enable_std_utf8();
    decode_argv();

    # The caller's own words, kept for the suggestion line an option-parse error
    # ends on (ticket k263). Recorded HERE because both rewrites below change
    # argv and neither leaves what anyone typed: _refuse_empty_argument's
    # diagnosis reads the raw line, and _normalize_option_argv respells
    # --claimed-by as --claimed_by and folds a flag-shaped value onto its option
    # with an `=`. App::karr::Role::ExitCodes reads it back through
    # App::karr::Error, and prints no suggestion at all where nothing was
    # recorded.
    set_original_argv(@ARGV);

    # Inside the eval on purpose: the "Usage error:" marker is what
    # App::karr::Error::is_usage_error keys on, so the handler below turns this
    # into exit 2 through the same path as every other usage error.
    my $ran = eval { _refuse_empty_argument(); _normalize_option_argv(); App::karr->new_with_cmd; 1 };
    if ( !$ran ) {
        my $err = $@;

        # An embedding host's exit-signal (see EMBEDDING) is not a command that
        # died: hand it back rather than classifying it. Nothing in karr's own
        # code answers this, so under bin/karr -- where exit() really exits and
        # never reaches this eval -- it never fires.
        die $err if blessed($err) && $err->can('__karr_dispatch_exit');

        # Exit-code contract (ADR 0002): 0 success / 1 runtime failure / 2 usage
        # error. This is the central handler the ADR calls for: it catches every
        # uncaught die from a command body and turns it into a deterministic 1
        # or 2, replacing the accidental 255 an uncaught die used to leak.
        #
        # Usage-error dies carry one of these stable leading markers:
        #   "Unknown command:"          the dispatch guard in App::karr
        #   "unexpected extra argument"  surplus positionals (Role::CliArgs)
        #   "Usage:"                     a missing required positional
        #   "Usage error:"               anything a command rejects as misuse
        #                                that is not one of the shapes above --
        #                                raised via App::karr::Role::ExitCodes'
        #                                usage_error(), the generic entry point
        #                                for e.g. an out-of-range option value
        # Any new usage-error die must start with one of these; prefer
        # usage_error() over inventing a fifth marker. Everything else -- task
        # not found, board missing, a Git/sync failure, a refused destructive
        # operation -- is a runtime failure (1).
        #
        # The markers themselves live in App::karr::Error::is_usage_error,
        # because this handler is no longer their only reader: the batch runner
        # in App::karr::Role::TaskMutation asks the same question to decide
        # whether a failure belongs to one id or to the whole invocation (#61).
        #
        # Option-parse errors (unknown option, unparseable option value) never
        # reach here: MooX::Options exits 2 directly via
        # App::karr::Role::ExitCodes (and the root's _print_help), so those
        # exits bypass this eval.
        my $is_usage = is_usage_error($err);

        print STDERR $err if defined $err && length $err;
        exit( $is_usage ? 2 : 1 );



( run in 0.999 second using v1.01-cache-2.11-cpan-364913b4093 )