The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.
package PkgForge::ConfigFile; # -*-perl-*-
use strict;
use warnings;

# $Id: ConfigFile.pm.in 16519 2011-03-25 15:50:07Z squinney@INF.ED.AC.UK $
# $Source:$
# $Revision: 16519 $
# $HeadURL: https://svn.lcfg.org/svn/source/tags/PkgForge/PkgForge_1_4_8/lib/PkgForge/ConfigFile.pm.in $
# $Date: 2011-03-25 15:50:07 +0000 (Fri, 25 Mar 2011) $

our $VERSION = '1.4.8';

use English qw(-no_match_vars);
use YAML::Syck ();

use Moose::Role;
use Moose::Util::TypeConstraints;

my $extra_role = Moose::Meta::Role->initialize('MooseX::ConfigFromFile');
__PACKAGE__->meta->add_role($extra_role);

subtype 'ConfigList' => as 'ArrayRef[Str]';
coerce  'ConfigList' => from 'Str' => via { [$_] };

has 'configfile'=> (
    traits      => [ 'Array','Getopt' ],
    is          => 'ro',
    isa         => 'ConfigList',
    default     => sub { [] },
    coerce      => 1,
    predicate   => 'has_configfile',
    cmd_aliases => 'c',
    handles     => {
        'configlist' => 'elements',
    },
    documentation => 'The configuration file for this application',
);

sub new_with_config {
    my ( $class, @args ) = @_;

    my %opts;
    if ( scalar @args == 1 && ref $args[0] eq 'HASH' ) {
        %opts = %{$args[0]};
    } elsif ( @args % 2 == 0 ) {
        %opts = @args;
    }

    my $configfile = delete $opts{configfile};

    if ( !defined $configfile ) {
        my $cfmeta = $class->meta->find_attribute_by_name('configfile');
        $configfile = $cfmeta->default if $cfmeta->has_default;
    }

    if ( defined $configfile ) {
        %opts = ( %{$class->get_config_from_file($configfile)}, %opts );
    }

    $class->new(%opts);
}

sub get_config_from_file {
    my ( $class, $conf ) = @_;

    if ( !defined $conf ) { # should never happen but just in case...
        $conf = q{};
    } elsif ( ref $conf eq 'ARRAY' && scalar @{$conf} == 1 ) {
        $conf = $conf->[0];
    }

    # If the config file is a string and is prefixed with a plus (+)
    # it is an 'extra' file which should be appended to the default
    # list. Otherwise it is an override and the default is not
    # consulted.

    my $extra;
    if ( ref $conf eq q{} && $conf =~ m/^\+(.+)$/ ) {
        $extra = $1;
        my $attr = $class->meta->find_attribute_by_name('configfile');
        if ( $attr->has_default ) {
            $conf = $attr->default;
        } else {
            $conf = q{};
        }
    }

    if ( ref $conf eq 'CODE' ) {
        $conf = $conf->($class);
    }

    my @files_list;
    if ( ref $conf eq 'ARRAY' ) {
        @files_list = @{$conf};
    } elsif ( length $conf > 0 ) {
        @files_list = ($conf);
    }

    if ( defined $extra ) {
        push @files_list, $extra;
    }

    my %config;
    for my $file ( @files_list ) {
        if ( !-f $file ) {
            next; # just ignore
        }

        my $data = eval {
            # Allow true/false, yes/no for booleans
            local $YAML::Syck::ImplicitTyping = 1;

            YAML::Syck::LoadFile($file);
        };
        if (!defined $data || $EVAL_ERROR) {
            die "An error occurred whilst loading '$file': $EVAL_ERROR\n";
        }

        %config = ( %config, %{$data} );
    }

    $config{configfile} = [@files_list];

    return \%config;
}

no Moose::Role;

1;
__END__

=head1 NAME

     PkgForge::ConfigFile - A configuration file class for the LCFG Package Forge

=head1 VERSION

     This documentation refers to PkgForge::ConfigFile version 1.4.8

=head1 SYNOPSIS

     This is a Moose role and cannot be instantiated directly. Use it
     as shown below:

     package PkgForge::Foo;
     use Moose;

     with 'PkgForge::ConfigFile';

     # optionally, default the configfile:
     has '+configfile' => ( default => '/etc/foo.yaml' );

     ########
     ## A script that uses the class with a configfile
     ########

     my $obj = PkgForge::Foo->new_with_config(configfile => '/etc/bar.yaml',
                                              other_opt  => 'foo');

=head1 DESCRIPTION

This is a Moose role which can be applied to a class to give it the
ability to set values of attributes for new instances from
configuration files. The configuration file format must be YAML,
multiple files can be supplied which will be loaded in sequence.

=head1 ATTRIBUTES

=over

=item configfile

This attribute is provided by the base role
MooseX::ConfigFromFile. You can provide a default configfile pathname
like so:

         has '+configfile' => ( default => '/etc/myapp.yaml' );

You can also provide a list of files (wrapped in a sub to satisfy
Moose) as a default. Given a list of files the values of attributes
will come from the latest (right-most) file in which the attribute is
specified. The configuration format is YAML, in most cases all that is
required are simple key-value pairs separated with a colon, one
per-line, for example C<bucket: lcfg>, see L<YAML::Syck> for more
information.

=back

=head1 SUBROUTINES/METHODS

=head2 Class Methods

=over

=item get_config_from_file

This is used internally by either C<new_with_config> or the
C<new_with_options> method provided in the L<MooseX::Getopt> role.
This uses L<YAML::Syck> to parse the configuration files.

=item new_with_config

This is provided by the base role L<MooseX::ConfigFromFile>.  This
acts just like a regular C<new()>, but it also accepts an argument
C<configfile> to specify the configuration file from which to load
other attributes.  Explicit arguments to this method will override
anything loaded from the configfile. You may also pass in a string
which is the filename for the configuration file but with a plus-sign
(+) prefix. This file name will then be appended to the list which
comes from the default for the attribute (if any).

=back

=head1 DEPENDENCIES

This module is powered by L<Moose> and also uses
L<MooseX::ConfigFromFile> and L<YAML::Syck>

=head1 SEE ALSO

L<PkgForge>

=head1 PLATFORMS

This is the list of platforms on which we have tested this
software. We expect this software to work on any Unix-like platform
which is supported by Perl.

ScientificLinux5, Fedora13

=head1 BUGS AND LIMITATIONS

Please report any bugs or problems (or praise!) to bugs@lcfg.org,
feedback and patches are also always very welcome.

=head1 AUTHOR

    Stephen Quinney <squinney@inf.ed.ac.uk>

=head1 LICENSE AND COPYRIGHT

    Copyright (C) 2010-2011 University of Edinburgh. All rights reserved.

This library is free software; you can redistribute it and/or modify
it under the terms of the GPL, version 2 or later.

=cut