NAME

Zydeco::Lite::App - use Zydeco::Lite to quickly develop command-line apps

SYNOPSIS

In consumer.pl:

#! perl

use strict;
use warnings;
use Zydeco::Lite::App;
use Types::Standard -types;

app 'MyApp' => sub {
  
  command 'Eat' => sub {
    
    constant documentation => 'Consume some food.';
    
    arg 'foods' => (
      type          => ArrayRef[Str],
      documentation => 'A list of foods.',
    );
    
    run {
      my ( $self, $foods ) = ( shift, @_ );
      $self->info( "Eating $_." ) for @$foods;
      return 0;
    };
  };
  
  command 'Drink' => sub {
    
    constant documentation => 'Consume some drinks.';
    
    arg 'drinks' => (
      type          => ArrayRef[Str],
      documentation => 'A list of drinks.',
    );
    
    run {
      my ( $self, $drinks ) = ( shift, @_ );
      $self->info( "Drinking $_." ) for @$drinks;
      return 0;
    };
  };
};

'MyApp'->execute( @ARGV );

At the command line:

$ ./consumer.pl help eat
usage: consumer.pl eat [<foods>...]

Consume some food.

Flags:
  --help  Show context-sensitive help.

Args:
  [<foods>]  A list of foods.

$ ./consumer.pl eat pizza chocolate
Eating pizza.
Eating chocolate.

DESCRIPTION

Zydeco::Lite::App extends Zydeco::Lite to redefine the app keyword to build command-line apps, and add command, arg, flag, and run keywords.

It assumes your command-line app will have a single level of subcommands, like many version control and package management tools often do. (You type git add filename.pl, not git filename.pl. The add part is the subcommand.)

It will handle @ARGV processing, loading config files, and IO for you.

app

The app keyword exported by Zydeco::Lite::App is a wrapper for the app keyword provided by Zydeco::Lite which performs additional processing for the command keyword to associate commands with applications, and adds the Zydeco::Lite::App::Trait::Application role (a.k.a. the App trait) to the package it defines.

# Named application:

app "Local::MyApp", sub {
  ...;   # definition of app
}

"Local::MyApp"->execute( @ARGV );

# Anonymous application:

my $app = app sub {
  ...;   # definition of app
};

$app->execute( @ARGV );

An anonymous application will actually have a package name, but it will be an automatically generated string of numbers, letters, and punctuation which you shouldn't rely on being the same from one run to another.

Within the coderef passed to app, you can define roles, classes, and commands.

The package defined by app will do the App trait.

The App Trait

commands

The commands method lists the app's subcommands. Subcommands will each be a package, typically with a package name that uses the app's package name as a prefix. So your "add" subcommand might have a package name "Local::MyApp::Add" and your "add-recursive" subcommand might be called "Local::MyApp::Add::Recursive".

The commands method will return these packages minus the prefix, so calling 'Local::MyApp'->commands would return a list of strings including "Add" and "Add::Recursive".

The App trait requires your app package to implement this method, but the app keyword will provide this method for you, so you don't typially need to worry about implementing it yourself.

execute

The execute method is the powerhouse of your app. It takes a list of command-line parameters, processes them, loads any config files, figures out which subcommand to run, dispatches to that, and exits.

The App trait implements this method for you and you should probably not override it.

execute_no_subcommand

In the case where execute cannot figure out what subcommand to dispatch to, execute_no_subcommand is called.

The App trait implements this method for you. The default behaviour is to call execute again, passing it "--help". You can override this behaviour though, if some other behaviour would be more useful.

stdio

Most of the methods in the App trait are okay to be called as either class methods or instance methods.

"Local::MyApp"->execute( @ARGV );
bless( {}, "Local::MyApp" )->execute( @ARGV );

stdio is for calling on an instance though, and will return an instance if you call it as a class method. The arguments set the filehandles used by the app for input, output, and error messages.

my $app = "Local::MyApp"->stdio( $in_fh, $out_fh, $err_fh );
$app->execite( @ARGV );
stdin, stdout, stderr

Accessors which return the handles set by stdio. If no filehandles have been given, or called as a class method, return STDIN, STDOUT, and STDERR.

readline

A method for reading input.

$app->readline() is a shortcut for $app->stdin->readline() but also calls chomp on the result.

print, debug, usage, info, warn, error, fatal, success

Methods for printing output.

All off them automatically append new lines.

print writes lines to $app->stdout.

debug writes lines to $app->stderr but only if $app->debug_mode returns true.

usage writes lines to $app->stderr and then exits with exit code 1.

info writes lines in blue text to $app->stderr.

warn writes lines in yellow text to $app->stderr.

error writes lines in red text to $app->stderr.

fatal writes lines in red text to $app->stderr and then exits with exit code 254.

success writes lines in green text to $app->stderr.

Any of these methods can be overridden in your app if you prefer different colours or different behaviour.

debug_mode

This method returns false by default.

You can override it to return true, or do something like this:

app "Local::MyApp" => sub {
  ...;
  
  method "debug_mode" => sub {
    return $ENV{MYAPP_DEBUG} || 0;
  };
};
config_file

Returns the empty list by default.

If you override it to return a list of filenames (not full path names, just simple filenames like "myapp.json"), your app will use these filenames to find configuration settings.

find_config

If config_file returns a non-empty list, this method will check the current working directory, a user-specific config directory (~/.config/ on Linux/Unix, another operating systems will vary), and a system-wide config directory (/etc/ on Linux/Unix), and return a list of config files found in those directories as Path::Tiny objects.

read_config

If given a list of Path::Tiny objects, will read each file as a config file and attempt to merge the results into a single hashref, which it will return.

If an empty list is given, will call find_config to get a list of Path::Tiny objects.

This allows your system-wide config in /etc/myapp.json to be overridden by user-specific ~/.config/myapp.json and a local ./myapp.json.

You should rarely need to call this manually. (The execute method will call it as needed and pass any relevant configuration to the subcommand that it dispatches to.) It may sometimes be useful to override it if you need to support a different way of merging configuration data from multiple files, or if you need to be able to read configuration data from a non-file source.

read_single_config

Helper method called by read_config.

Determines config file type by the last part of the filename. Understands JSON, INI, YAML, and TOML, and will assume TOML if the file type cannot be determined from its name.

Config::Tiny and YAML::XS or YAML::PP are required for reading those file types, but are not included in Zydeco::Lite::App's list of dependencies. TOML is the generally recommended file format for apps created with this module.

This method may be useful to override if you need to be able to handle other file types.

kingpin

Returns a Getopt::Kingpin object populated with everything necessary to perform command-line processing for this app.

You will rarely need to call this manually or override it.

exit

Passed an integer, exits with that exit code.

You may want to override this if you wish to perform some cleanup on exit.

command

The command keyword is used to define a subcommand for your app. An app should have one or more subcommands. It is a wrapper for the class keyword exported by Zydeco::Lite.

The command keyword adds the Zydeco::Lite::App::Trait::Command role (a.k.a. the Command trait) to the class it defines.

Commands may have zero or more args and flags. Args are (roughly speaking) positional parameters, passed to the command's execute method, while flags are named arguments passed the the command's constructor.

The Command Trait

command_name

The Command trait requires your class to implement the command_name method. However, the command keyword will provide a default implementation for you if you have not. The default implementation uses the class name of the command (minus its app prefix), lowercases it, and replaces "::" with "-".

So given the example:

app "MyApp::Local", sub {
  command "Add::Recursive", sub {
    run { ... };
  };
};

The package name of the command will be "MyApp::Local::Add::Recursive", and the command name will be "add-recursive".

documentation

This method is called to get a brief one-line description of the command.

app "MyApp::Local", sub {
  command "Add::Recursive", sub {
    
    method "documentation" => sub {
      return "Adds a directory recursively.";
    };
    
    run { ... };
  };
};

You may prefer to use constant to define this method in your command class.

app "MyApp::Local", sub {
  command "Add::Recursive", sub {
    
    constant "documentation" => "Adds a directory recursively.";
    
    run { ... };
  };
};

See Zydeco::Lite for more information on the method and constant keywords.

execute

Each subcommand is required to implement an execute method.

app "MyApp::Local", sub {
  command "Add::Recursive", sub {
    
    method "execute" => sub {
      ...;
    };
  };
};

The subcommand's execute method is called by the app's execute method. It is passed the subcommand object ($self) followed by any command-line arguments that were given, which may have been coerced. (See "arg".)

It should return the application's exit code; usually 0 for a successful execution, and an integer from 1 to 255 if unsuccessful.

The run keyword provides a helpful shortcut for defining the execute method. (See "run".)

app

Returns the app as an object or package name.

app "MyApp::Local", sub {
  command "Add::Recursive", sub {
    
    method "execute" => sub {
      my ( $self, @args ) = ( shift, @_ );
      ...;
      $self->app->success( "Done!" );
      $self->app->exit( 0 );
    };
  };
};

The print, debug, info, warn, error, fatal, usage, success, and readline methods are delegated to app, so $self->app->success(...) can just be written as $self->success(...).

config

Returns the config section as a hashref for this subcommand only.

So for example, if myapp.json had:

{
  "globals": { "foo": 1, "bar": 2 },
  "bumpf": { "bar": 3, "bat": 999 },
  "quuux": { "bar": 4, "baz": 5 }
}

Then the Quuux command would see the following config:

{
  "foo" => 1,
  "bar" => 4,
  "baz" => 5,
}

The globals section in a config is special and gets copied to all commands.

kingpin

Utility method used by the app's kingpin method to add a Getopt::Kingpin::Command object for processing this subcommand's arguments. You are unlikely to need to override this method or call it directly.

arg

Defines a command-line argument for a subcommand.

use Zydeco::Lite::App;
use Types::Path::Tiny -types;

app "Local::MyApp" => sub {
  command "Add" => sub {
    
    arg 'filename' => ( type => File, required => 1 );
    
    run {
      my ( $self, $file ) = ( shift, @_ );
      ...;
    };
  };
};

Arguments are ordered and are passed on the command line like follows:

$ ./myapp.pl add myfile.txt

The arg keyword acts a lot like Zydeco::Lite's has keyword.

It supports the following options for an argument:

type

The type constraint for the argument. The following types (from Types::Standard and Types::Path::Tiny) are supported: Int, Num, Str, File, Dir, Path, ArrayRef[Int], ArrayRef[Num], ArrayRef[Str], ArrayRef[File], ArrayRef[Dir], ArrayRef[Path], HashRef[Int], HashRef[Num], HashRef[Str], HashRef[File], HashRef[Dir], HashRef[Path], as well as any custom type constraint which can be coerced from strings.

HashRef types are passed on the command line like:

./myapp.pl somecommand key1=value1 key2=value2
kingpin_type

In cases where type is a custom type constraint and Zydeco::Lite::App cannot figure out what to do with it, you can set kingpin_type to be one of the above supported types to act as a hint about how to process it.

required

A boolean indicating whether the argument is required. (Optional otherwise.) Optional arguments may be better as a "flag".

documentation

A one-line description of the argument.

placeholder

A string to use as a placeholder value for the argument in help text.

default

A non-reference default value for the argument, or a coderef that when called will generate a default value (which may be a reference).

env

An environment variable which will override the default value if it is given.

Arguments don't need to be defined directly within a command. It is possible for a command to "inherit" arguments from a role or parent class, but this is usually undesirable as it may lead to their order being hard to predict.

flag

Flags are command-line options which are passed as --someopt on the command line.

use Zydeco::Lite::App;
use Types::Path::Tiny -types;

app "Local::MyApp" => sub {
  command "Add" => sub {
  
    arg 'filename' => ( type => File, required => 1 );
    
    flag 'logfile' => (
      init_arg => 'log',
      type     => File,
      handles  => { 'append_log' => 'append' },
      default  => sub { Path::Tiny::path('log.txt') },
    );
    
    run {
      my ( $self, $file ) = ( shift, @_ );
      $self->append_log( "Starting work...\n" );
      ...;
    };
  };
};

This would be called as:

./myapp.pl add --log=log2.txt filename.txt

The flag keyword is a wrapper around the has keyword, so supports all the options supported by has such as predicate, handles, etc. It also supports all the options described for "arg" such as env and placeholder. Additionally there is a short option, allowing for short, single-letter flag aliases:

flag 'logfile' => (
  init_arg => 'log',
  type     => File,
  short    => 'L',
);

Instead of being initialized using command-line arguments, flags can also be initialized in the application's config file. Flags given on the command line override flags in the config files; flags given in config files override those given by environment variables; environment variables override defaults.

Like args, flags can be defined in a parent class or a role. It can be helpful to define common flags in a role.

run

The run keyword just defines a method called "execute". The following are equivalent:

run { ... };
method 'execute' => sub { ... };

BUGS

Please report any bugs to http://rt.cpan.org/Dist/Display.html?Queue=Zydeco-Lite-App.

SEE ALSO

This module extends Zydeco::Lite to add support for rapid development of command-line apps.

Z::App is a shortcut for importing this module plus a collection of others that might be useful to you, including type constraint libraries, strict, warnings, etc.

Getopt::Kingpin is used for processing command-line arguments.

AUTHOR

Toby Inkster <tobyink@cpan.org>.

COPYRIGHT AND LICENCE

This software is copyright (c) 2020 by Toby Inkster.

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

DISCLAIMER OF WARRANTIES

THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.