NAME

Acme::Beamerang::ArgParser - A partial commandlet oriented args parser

SYNOPSIS

use Acme::Beamerang::ArgParser;
use My::Sub::Dispatcher::Thing;

my $state = {};
my $opts  = {
  output => sub { defined $_[0] ? $state->{output} = $_[0] : die "--output expects a parameter" },
  quiet  => sub { defined $_[0] ? $state->{quiet}  = $_[0] : $state->{quiet}++ },
};
$opts->{o} = $opts->{output};
$opts->{q} = $opts->{quiet};

# Note: Only defines which pre-command "args" are "ours" for filtering.
# and ropes the rest off for children to parse themselves.
my $cfg = Acme::Beamerang::ArgParser->new( filter => [keys %opts] )->parse( @ARGV );
for my $argument ( @{$cfg->args} ) {
  my ( $name, $value ) = @{ $argument };
  $opts->{$name}->( $value );
}

my $worker = My::Sub::Dispatcher::Thing->new( %$state );
$worker->exec( @{ $cfg->extra_args } );

EXPERIMENTAL

This code is reasonably simple, but its really an experimental prototype for a bunch of things I'm working on, and is subject to change.

Any finalized results of said project may use other parts, or be implemented differently.

This module is subsequently prone to radical changes until a way forward ( and even a name ) is decided upon.

DESCRIPTION

Acme::Beamerang::ArgParser is an experimental argument parsing utility to simplify some of the essential flow control needed when building a multi-level -cmd style argument parser.

The general priniciple is to not actually implement anything specific to argument validation and dispatch, and to leave that up to the user, while focusing on determining the associations of arguments and the ownership of arguments, in order to allow composed command lines where the root node is blind to what their child nodes expect.

ANATOMY OF MULTI LEVEL COMMANDS

app [APP AND APP FACET ARGS] command [COMMAND ARGS]

app Reads its arguments up to the first non-argument, command.

Elements of [APP AND APP FACET ARGS] that it recognizes due to the filter rule it takes posession of, and hides them from any composed in facets.

Any elements of [APP AND APP FACET ARGS] that app does not recognize it assumes will be handled by child facets, or will otherwise be handled to be "excess" by the consuming code.

command and COMMAND ARGS are captured verbatim and not processed in any way.

APP AND FACET ARGS

All app level and facet level arguments need to be in one of the following forms in order to work as intended.

-u
--unary
-o=argument
--option=argument

Split style parameters are unsupported:

# NOT POSSIBLE as it would think the command to be "argument"
app --option argument command --commandarg

This is required to be able to parse out facet arguments without knowing what that facet expects those arguments to be.

For example, imagine an application as follows:

app --verbose --dispatcher legacy --size 10 command

Where size and verbose are arguments that only the dispatcher called legacy understood, and that dispatcher is loaded lazily.

As far as app is concerned, it only knows about the dispatcher argument.

Without the tight-binding of =, the logic would have to assume strange things, eventually culminating in --verbose having an argument of --dispatcher and the command being legacy (LOL!), or --size not supporting arguments, and the command being 10 (LOL!)

And this design choice is considered an important axiom of this approach.

METHODS

new

my $instance = Acme::Beamerang::ArgParser->new( filter => ['h','help','v','version' ]);

Creates a new instance of an ArgParser

new.filter

An ArrayRef of arguments to filter and deem as "ours"

  • Single character arguments map to single-dashed extractors

  • Multi-Character arguments map to double-dashed extractors.

parse

my $cfg = $instance->parse( @ARGV );

Processes input @ARGV and filters its content into categories based on filter, halting processing at the first token that doesn't start with a -.

Returns a ::Cfg instance describing the arguments.

Cfg.args

Returns the arguments deemed "ours" as an ArrayRef of decoded args.

for my $arg (@{ $cfg->args }) {
  my ( $argname, $argvalue ) = @{ $arg };

  do_help and last if $argname eq 'h' or $argname eq 'help';

  if ( $argname eq 'output' or $argname eq 'o' ) {
    die "output/o takes an argument" unless defined $argvalue and length $argvalue;
    set_output( $argvalue ) and next;
  }
}

Each arg is an ArrayRef as follows:

[ 'arg'          ] # --arg
[ 'arg', ''      ] # --arg=
[ 'arg', 'value' ] # --arg=value

Where arg can only be values passed in via filter

Cfg.command

my $command = $cfg->command

Contains the value of the first non-parameter argument, or undef if none was visited during parse.

Cfg.orig_args

Contains an ArrayRef of the original unmodified arguments that were passed in.

my (@orig_args) = @{ ::ArgParser->new( filter => [ qw/ hello / ])->parse( @ARGV )->orig_args }

This is mostly for debugging purposes.

Cfg.unknown_args

Contains an ArrayRef of all hyphen prefixed arguments that were seen prior to the command that were not defined in the filter list.

my (@unknown_args) = @{ $cfg->unknown_args };

The presence of any items in this list can be used to cause validation errors, or they can be assumed to be transparent arguments to pass down to the next level.

Cfg.unparsed_args

Contains an ArrayRef of all tokens that succeeded the command ( excluding the command itself ).

my (@unparsed_args) = @{ $cfg->unparsed_args };

These are to be considered "Property of the command"

Cfg.extra_args

Returns an ArrayRef equivalent to the input arguments to parse, minus the contents of .args.

my (@extra_args) = @{ $cfg->extra_args };

ENVIRONMENT

This module consumes Acme::Beamerang::Logger and subsequently will respond to all respective flags it supports.

BEAMERANG_ARGPARSER_$LEVEL
BEAMERANG_$LEVEL
BEAMERANG_ARGPARSER_UPTO
BEAMERANG_UPTO

AUTHOR

Kent Fredric <kentnl@cpan.org>

LICENSE

This software is copyright (c) 2016 by Kent Fredric.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.