The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

App::Getconf - singleton-like config store for command-line applications

SYNOPSIS

  # main.pl

  use App::Getconf qw{ :schema };
  use YAML qw{ LoadFile };

  App::Getconf->option_schema(
    help    => opt { type => 'flag',
                     help => "this message" },
    version => opt { type => 'flag',
                     help => "print version information" },
    verbose => opt { type => 'bool',
                     help => "be verbose" },
    session => schema(
      timeout => opt { type => 'int',    value => 50 },
      path    => opt { type => 'string', value => '/' },
    ),
    # ...
  );

  App::Getconf->cmdline(\@ARGV);
  App::Getconf->options(LoadFile('/etc/myapp.yaml'));

  if (App::Getconf->getopt->help) {
    print App::Getconf->help_message();
    exit 0;
  }

  # real code...

  #-------------------------------------------------
  # My/Module.pm

  package My::Module;

  use App::Getconf;

  sub do_something {
    my ($self, %args) = @_;

    my $opts = App::Getconf->getopt;

    if ($opts->verbose) {
      print "Entering function do_something()\n";
    }

    # ...
  }

DESCRIPTION

This module is yet another command line options parser. But not only. Actually, it's an option container. It's a response to a question: after parsing options (from command line and from config file), how do I pass them down the function call stack?

There are two classic approaches. One utilizes global variables. This is not that convenient, because introduces some names treated in special way (not defined inside the current function). The other requires passing option container as an argument to each and every function (you can't always tell in advance that the function will never use the options on one hand, and API changes are tedious on the other).

App::Getconf tries a different way, which is not entirely new: the inspiration for this module was Log::Log4perl(3), which is Perl port of log4j Java library. The idea is simple: you need a value accessible similarly to a global variable, but declared locally.

ARCHITECTURE

App::Getconf consists of three different types of objects: option containers, option views and option schema nodes.

Option container (App::Getconf instance) stores all the options that were set, either from command line or from multi-level hash (e.g. loaded config file).

Option container needs to be initialized with option schema: list of allowed options, along with their types (int, float, string, flag and so on). Such schema is composed of nodes created with opt() function or derivatives.

Option view (App::Getconf::View(3) instance) is an interface to options list. When option is requested, view does a "lookup" to find appropriate option. For example, view $v for proto.client subsystem was created. When $v->get('timeout') was issued, the view will return value of the first existing option: proto.client.timeout, proto.timeout or timeout. Of course there's also a possibility to omit this lookup.

App::Getconf creates a default option container. This default container is used every time when semi-static method (see "Semi-Static Methods" section) is called as static one. This is how App::Getconf provides a way of accessing options globally. However, you are not limited to this default container. You may create your own containers with their own option schema. Of course you will need to pass them down the call stack.

Options Lifecycle

Option container needs a schema to tell which options are legal and which are not. Defining schema is basically the first thing to do. Schema can also contain initial values for some options.

Next go options defined in command line and in config file. Option container can parse command line on its own, it just needs an array of arguments.

Two above steps are only to be done once, at the application start, possibly as early as possible. Changing option values, however, is planned in the future to be supported after initialization process, at run-time.

From now on, getopt() method may be used in any part of application.

Schema Definition

Schema is simply a hashref that contains options. Each value is a node (actual option or alias) or a sub-schema.

Full name of an option from sub-schema is $schema.$option, where ${schema} is the key, under which sub-schema was stored. Command line option that sets such option is --$schema-$option.

Schemas stored under greater depth are analogous.

Example of schema:

  help    => opt { type => 'flag', ... },
  version => opt { type => 'flag', ... },
  verbose => opt { type => 'bool', ... },
  session => {
    timeout => opt { type => 'int',    ... },
    path    => opt { type => 'string', ... },
    ''      => opt { type => 'string', ... },
  },
  # ...

This schema defines options help, version, verbose, session.timeout, session.path and just plain session. The last one is example of how to define option of the same name as sub-schema.

End-user can set these options using command line options, accordingly: --help, --version, --verbose/--no-verbose, --session-timeout=###, --session-path=XXX and --session=XXX.

Basic way of creating node is using opt() function, but there are few shorthands, like opt_int(), opt_flag() and others. See "Functions Defining Schema" section for details.

Schema is also used, beside validating option correctness, for generating message printed typically after issuing --help option. Only options having help field are included in this message. Other options still may be set in command line, but are not exposed to the user. They are meant mainly to be specified with configuration file or with other means.

Order of options in autogenerated help message is lexicographic order. You may provide the order by changing Perl's built-in anonymous hashref {} to call to function schema(). Example:

  # ...
  session => schema(
    timeout => opt { type => 'int',    ... },
    path    => opt { type => 'string', ... },
    ''      => opt { type => 'string', ... },
  ),
  # ...

You may freely mix hashrefs and schema() calls, at the same or different nesting levels.

MODULE API

Following methods are available:

new(%opts)

Constructor.

No options are used at the moment.

NOTE: You don't need to use the constructor. You may (and typically would) want to use App::Getconf's default container.

Semi-Static Methods

Methods from this section can be called as instance methods, when you have created own instance of App::Getconf, or as static methods, when they operate on default instance of App::Getconf. Typically you would use the latter strategy, as passing option container down the function call stack is somewhat troublesome.

option_schema($schema_description)
option_schema(key => value, key => value, ...)

Set expected schema for the options. Schema may be either a hashref (Perl's ordinary or created using schema() function) or a list of key/value pairs. The latter form has the same result as passing the list to schema() first, i.e., the options order will be preserved.

help_message(%options)

Return message printed when --help (or similar) option was passed. Message will be \n-terminated.

Typical usage:

  if (App::Getconf->getopt->help) {
    print App::Getconf->help_message(
      screen   => 130,
      synopsis => "%0 [ options ] file ...",
    );
    exit 0;
  }

Supported options:

screen (default: 80)

Screen width, in columns.

arg0 (default: $0 with path stripped)

Name of the program. Usually $0 or a derivative.

synopsis (default: %0 [ options ... ])

Short call summary. Any occurrence of %0 will be replaced with content of arg0 option.

Synopsis may be also a multiline string or an array of single-line strings.

description

Three additional text fields: before synopsis, after synopsis but before options list, after options list.

Text will be re-wrapped to fit on a terminal of screen width. Empty lines will be treated as paragraph separators, but single newline characters will not be preserved.

Any occurrence of %0 will be replaced with content of arg0 option.

option_indent (default: 2)
description_indent (default: 6)

Indenting for option header ("--option" with parameter specification, if any) and for option description.

options($options)

Set options read from configuration file (hashref).

Example usage:

  App::Getconf->options(YAML::LoadFile("/etc/myapp.yaml"));
cmdline($arguments)

Set options based on command line arguments (arrayref). If $arguments was not specified, @ARGV is used.

Method returns list of messages (single line, no \n at end) for errors that were found, naturally empty if nothing was found.

Arguments that were not options can be retrieved using args() method.

Example usage:

  App::Getconf->cmdline(\@ARGV);
  # the same: App::Getconf->cmdline();
  for my $arg (App::Getconf->args()) {
    # ...
  }
set_verify($data)
set_verify($data, $path)

Set value(s) with verification against schema. If $path was specified, options start with this prefix. If values were verified successfully, they are saved in internal storage.

NOTE: This is a semi-internal API.

args()

Retrieve non-option arguments (e.g. everything after "--") passed from command line.

Values returned by this method are set by cmdline() method.

getopt($package)

Retrieve a view of options (App::Getconf::View(3)) appropriate for package or subsystem called $package.

If $package was not provided, caller's package name is used.

$package sets option search path. See new(), prefix option description in App::Getconf::View(3) for details.

Typical usage:

  sub foo {
    my (@args) = @_;

    my $opts = App::Getconf->getopt(__PACKAGE__);

    if ($opts->ssl) {
      # ...

Functions Defining Schema

schema(key => value, key => value, ...)

Create a hashref from key/value pairs. The resulting hash is tied to Tie::IxHash(3), so the order of keys is preserved.

Main use is for defining order of options in --help message, otherwise it acts just like anonymous hashref creation ({ key => value, ... }).

opt($data)

Generic option specification.

Possible data:

  opt {
    type    => 'flag' | 'bool' | 'int' | 'float' | 'string',
    check   => qr// | sub {} | ["enum", "value", ...],
    storage => undef | \$foo | [] | {},
    help    => "message displayed on --help",
    value   => "initial value",
    default => "default value",
  }

If type is not specified, the option is treated as a string.

Check is for verifying correctness of specified option. It may be a regexp, callback function (it gets the value to check as a first argument and in $_ variable) or list of possible string values.

Types of options:

flag

Simple option, like --help or --version. Flag's value tells how many times it was encountered.

bool

ON/OFF option. May be turned on (--verbose) or off (--no-verbose).

int

Option containing an integer.

float

Option containing a floating point number.

string

Option containing a string. This is the default.

Storage tells if the option is a single-value (default), multi-value accumulator (e.g. may be specified in command line multiple times, and the option arguments will be stored in an array) or multi-value hash accumulator (similar, but option argument is specified as key=value, and the value part is validated). Note that this specify only type of storage, not the actual container.

NOTE: Don't specify option with a hash storage and that has sub-options (see "Schema Definition"). Verification can't tell whether the value is meant for the hash under this option or for one of its sub-options.

Presence of help key indicates that this option should be exposed to end-users in --help message. Options lacking this key will be skipped (but stil honoured by App::Getconf).

Except for flags (--help) and bool (--no-verbose) options, the rest of types require an argument. It may be specified as --timeout=120 or as --timeout 120. This requirement may be loosened by providing default value. This way end-user may just provide --timeout option, and the argument to the option is taken from default. (Of course, only --timeout=120 form is supported if the argument needs to be provided.)

Initial value (value key) is the value set for the option just after defining schema. It may or may not be changed with command line options (which is different from default, for which the option still needs to be specified).

Initial and default values are both subject to check that was specified, if any.

Help message will not retain any formatting, all whitespaces are converted to single space (empty lines are squeezed to single empty line). On the other hand, the message will be pretty wrapped and indented, while you don't need to worry about formatting the string if it is longer and broken to separate lines in your source code, so I think it's a good trade-off.

opt_alias($option)

Create an alias for $option. Note that aliases are purely for command line. App::Getconf::View(3) and options() method don't honour them.

Aliases may only point to non-alias options.

opt_flag()

Flag option (like --help, --verbose or --debug).

opt_bool()

Boolean option (like --recursive). Such option gets its counterpart called --no-${option} (mentioned --recursive gets --no-recursive).

opt_int()

Integer option (--retries=3).

opt_float()

Option specifying a floating point number.

opt_string()

Option specifying a string.

opt_path()

Option specifying a path in local filesystem.

opt_hostname()

Option specifying a hostname.

NOTE: This doesn't check DNS for the hostname to exist. This only checks hostname's syntactic correctness (and only to some degree).

opt_re(qr/.../)

Option specifying a string, with check specified as regexp.

opt_sub(sub {...})
opt_sub {...}

Option specifying a string, with check specified as function (code ref).

Subroutine will have $_ set to value to check, and the value will be the only argument (@_) passed.

Subroutine should return TRUE when option value should be accepted, FALSE otherwise.

opt_enum ["first", ...]

Option specifying a string. The string must be one of the specified in the array.

AUTHOR

Stanislaw Klekot, <cpan at jarowit.net>

LICENSE AND COPYRIGHT

Copyright 2012 Stanislaw Klekot.

This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation; or the Artistic License.

See http://dev.perl.org/licenses/ for more information.

SEE ALSO

App::Getconf::View(3), Getopt::Long(3), Tie::IxHash(3).