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

NAME

Config::apiLayers - Auto-prototyping object properties in multiple configuration layers.

SYNOPSIS

    use Config::apiLayers;
    my $cfg = new Config::apiLayers({
        autoproto => 1,
        attributes => [qw(length width area)]
    });
    # Set the default values
    $cfg->config({
        data => {
            'length' => 6,
            'width' => 10,
            'area' => sub {
                my $cfg = shift;
                return ($cfg->apicall('length') * $cfg->apicall('width'))
            }
        }
    });

    $cfg->length( 3 );
    print ("This rectangle has length ".$cfg->length." by width ".$cfg->width.
           ", with an area of ".$cfg->area.".\n");
    
    $cfg->width( 4 );
    print ("This rectangle has length ".$cfg->length." by width ".$cfg->width.
           ", with an area of ".$cfg->area.".\n");

Resulting output:

    This rectangle has length 3 by width 10, with an area of 30.
    This rectangle has length 3 by width 4, with an area of 12.

DESCRIPTION

Used as a base module or on its own to manage configuration properties of an application. Its default behavior is to auto-prototype property attributes. Validators can be used to validate values to be set. Configuration can be used with Getopt::Long and Getopt::LongUsage for obtaining configuration.

Properties that are imported or directly configured can be stored in one or multiple layers, and do not immediately affect each other. When retrieved, the values of properties are obtained from the layers in a top-down fashion.

The values of properties can also be functions. When the value is retrieved, the function is executed which can be used to combine multiple property values together, or do smoething entirely different.

REQUIREMENTS

None. This module is Pure Perl. This module does not use AUTOLOAD.

METHODS

new

attributes - configure the allowed attributes. Three styles are available.
autoproto - 1 or 0 ; Default is 1; Set to 1 for the attributes to be functions of the object.
    # Style 1 - Only set the attributes, and each attribute stores a value. Order is retained.
    my $cfg = new Config::apiLayers({
        autoproto => 1,
        attributes => [qw(length width area)]
    });

    # Style 2 - Only set the attributes with a validator. Attributes with undef valdiator
    #           store a provided values without validation. Order is NOT retained.
    my $val_greater_than_zero = sub {
        my $cfg = shift;
        my $attribute_name = shift;
        my $value = shift;
        return undef unless $value > 0;
        return $value;
    };
    my $cfg = new Config::apiLayers({
        autoproto => 1,
        attributes => {
            'length'    => $val_greater_than_zero,
            'width'     => $val_greater_than_zero,
            'area'      => sub { return undef },  # do not allow storing any value
            'store_any' => undef
        }
    });
    # Style 3 - Same as Style 2, but retain the order of the attributes in configuration.
    my $cfg = new Config::apiLayers({
        autoproto => 1,
        attributes => [
            {'length'    => $val_greater_than_zero},
            {'width'     => $val_greater_than_zero},
            {'area'      => sub { return undef }},  # do not allow storing any value
            {'store_any' => undef}
        ]
    });
    # Style 3 gives the ability to provide additional attribute configuration.
    # Configure optional information for use with C<Getopt::Long> and
    # C<Getopt::LongUsage> modules, while retaining order of the attributes.
    my $cfg = new Config::apiLayers({
        autoproto => 1,
        attributes => [
            { name        => 'length',
              validator   => $val_greater_than_zero,
              getoptlong  => 'length|l:i',
              description => "The length of a rectangle"
            },
            { name        => 'width',
              validator   => $val_greater_than_zero,
              getoptlong  => 'width|w:i',
              description => "The width of a rectangle"
            },
            { name        => 'area',
              validator   => sub { return undef },  # do not allow storing any value
              getoptlong  => 'area|a',
              description => "The area of the rectangle, length times width"
            },
            { name        => 'store_any',
              validator   => undef,
              getoptlong  => 'store_any_value:s',
              description => "Store a value of your choosing, unvalidated"
            }
        ]
    });

    # Example use of configuration
    # Set the default values (the first layer is layer number 0)
    $cfg->config({
        data => {
            'length' => 6,
            'width' => 10,
            'area' => sub {
                my $cfg = shift;
                return ($cfg->apicall('length') * $cfg->apicall('width'))
            }
        }
    });
    # Perform the computation
    $cfg->length(5);
    $cfg->width(8);
    my $area = $cfg->area;  # $area == 40

config

Add configuration data to either the given index layer, or the highest layer if index is not provided. Data is NOT validated.

index - the index number of the top most configuration data layer to add.
data - the configuration data to add to the top most layer.
    # Configure the 2nd layer (layer numbering starts at 0) 
    $cfg->config({
        index => 1,
        data => {
            'length' => 6,
            'width' => 10,
            'area' => sub {
                my $cfg = shift;
                return ($cfg->apicall('length') * $cfg->apicall('width'))
            }
        }
    });

importdata

Import the data, performing validation as available. This method will import each attribute individually with apicall, which performs the validation if a validator was configured (see new for configuring a validator for an attribute).

data - the configuration data to import into the top most layer.
    $cfg->importdata({ data => { length => 4, width => 2 } });

exportdata

Export the data or specific configuration information.

cfg - the type of configuration to export. Provide this in two formats.
cfg => 'getoptlong' - return an array of GetoptLong config, as configured in new. This can be used with the Getopt::Long module. Or you can parse it for a custom purpose using Getopt::LongUsage::ParseGetoptLongConfig method.
cfg => 'descriptions' - return a hash of attribute and descriptions, as configured in new. This can be used with the Getopt::LongUsage module.
data - the configuration data to export. Provide this in three formats:
data => 'undef' - (this is the default action) to export each attribute among all layers
data => 'layerNumber' - to export attributes only from the given layer number
data => ['startingLayer','endingLayer'] - to export attributes only from the given layer range
data => /invalid or unrecognized value/ - preform the default action
    my $getoptlong_config = $cfg->exportdata({ cfg => 'getoptlong' });
    my %options;
    GetOptions( \%options, @$getoptlong_config );
    $cfg->importdata({ data => \%options });

    # Export Data from layers. Data for each attribute in top most layer is exported.
    my $exported_data = $cfg->exportdata({ data => undef });

add_layer

Add a new configuration layer. If index is provided, then configuration data layers are added until index number of layers exist. If data is provided, then the given configuration data is added to the highest configuration data layer. If both index and data are provided, the layers are added first, then the configuration data is added to the top most existing layer.

index - the index number of the top most configuration data layer to add.
data - the configuration data to add to the top most layer.
    # Add one more layer to the existing layers.
    $cfg->add_layer();
    # Add one more layer and set the data for that layer.
    $cfg->add_layer({
        data => {
            'length' => 6,
            'width' => 10,
            'area' => sub {
                my $cfg = shift;
                return ($cfg->apicall('length') * $cfg->apicall('width'))
            }
        }
    });
    # Add 3 layers (layer numbering starts at 0)
    $cfg->add_layer({ 'index' => 2 });

apican

Determine if the apiname is available to be called. Returns undef if false, or the referenced function if true.

apiname - the name of the property/api
    my $subref = $cfg->apican("apiname");

apicall

Call the apiname to get or set the attribute.

apiname - the name of the property/api
arguments - any arguments required by the apicall, i.e. like a value to set
    # Both of these calls do the same thing
    my $res = $cfg->apicall("apiname" [, arguments ]);
    my $res = $cfg->apiname([ arguments ]);

VALIDATORS

VALIDATOR FORMAT

    The format of the validator is to return the value to store if the value is validated successfully, or return undef is not validated successfully.

    Validators must be a function, or a string that can be evaluated as a regex. Validators that are hashes or arrays are invalid.

    An undefined validator will allow any value passed in to be stored.

VALIDATOR AS A FUNCTION

    This allows a function that is used as the validator to change the value, as the stored value is whatever the function returns.

    Validator Function Call Format:

        # The Validator is called internally as follows:
        $validator->( <Config::apiLayers Object>, <attribute_name>, <attribute_value> )

    Validator Function Example:

        # Accept any value that is not a reference
        [{ "FirstName" => sub{ return $_[2] if !ref $_[2] } }]

VALIDATOR AS A STRING

    If a string/scalar is provided as the validator, it is used as a regex to test the value. The value is considered to be validated successfully if the regex match test is true.

    Validator Format: # The Validator is called similar to: if ($value =~ /$validator_string/) { <store the value> }

    Validator Example:

        # Accept any value that matches a word
        [{ "FirstName" => "\w" }]
        [{ name => "FirstName", validator => "\w" }]
        [{ name => "FirstName", validator => "\w", description => "The first name" }]

EXAMPLES

Use Config::apiLayers with Getopt::Long and Getopt::LongUsage

    use Getopt::Long;
    use Getopt::LongUsage;
    use Config::apiLayers;

    # Note the missing getoptlong configuration and description for area attribute
    my $cfg = new Config::apiLayers({
        autoproto => 1,
        attributes => [
            { name        => 'length',
              validator   => sub { return $_[2] > 0 ? $_[2] : undef },
              getoptlong  => 'length|l:i',
              description => "The length of a rectangle"
            },
            { name        => 'width',
              validator   => sub { return $_[2] > 0 ? $_[2] : undef },
              getoptlong  => 'width|w:i',
              description => "The width of a rectangle"
            },
            { name        => 'area',
              validator   => sub { return undef }  # do not allow storing any value
            }
        ]
    });
    # Set the default values
    $cfg->config({
        data => {
            'length' => 6,
            'width' => 10,
            'area' => sub {
                my $cfg = shift;
                return ($cfg->apicall('length') * $cfg->apicall('width'))
            }
        }
    });

    my $getoptlong_config = $cfg->exportdata({ cfg => 'getoptlong' });
    my $attr_descriptions = $cfg->exportdata({ cfg => 'descriptions' });

    my %options;
    my @getoptconf = (  \%options,
                        @{$getoptlong_config},
                        'verbose|v',
                        'help|h'
                        );
    my $usage = sub {
        my @getopt_long_configuration = @_;
        GetLongUsage (
            'cli_use'       => ($0 ." [options]"),
            'descriptions'  =>
                [   @{$attr_descriptions},
                    'verbose'       => "verbose",
                    'help'          => "this help message"
                    ],
            'Getopt_Long'   => \@getopt_long_configuration,
        );
    };
    GetOptions( @getoptconf ) || die ($usage->( @getoptconf ),"\n");
    $cfg->add_layer();
    $cfg->importdata({ data => \%options }) || die ($usage->( @getoptconf ),"\n");
    print "Area is: ",$cfg->area,"\n";

Example outputs:

    linux$ ./test_it.pl --not-an-option
    Unknown option: not-an-option
    ./test_it.pl [options]
      --length       The length
      --width        The width
      -v, --verbose  verbose
      -h, --help     this help message

    linux$ ./test_it.pl --width=101
    Area is: 606

AUTHOR

Russell E Glaue, http://russ.glaue.org

COPYRIGHT AND LICENSE

Copyright (c) 2015-2016 Russell E Glaue, All rights reserved.

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