#!/usr/bin/perl -w package Template::Plugin::DataHash; use strict; use vars qw($VERSION @ISA $EXTENSION $DEFAULT_INCLUDE_PATH); $VERSION = '0.04'; use Template::Plugin; @ISA = qw(Template::Plugin); $EXTENSION = { perl => qr/\.pl$/i, split => qr/\.split$/i, storable => qr/\.sto$/i, xml => qr/\.xml$/i, yaml => qr/\.yaml$/i, }; ### $EXTENSION is a list of regular expression objects used to determine ### how to read in the given conf file ### if a INCLUDE_PATH isn't in the Template::Toolkit object, $DEFAULT_INCLUDE_PATH ### will be used as the INCLUDE_PATH $DEFAULT_INCLUDE_PATH = ['/tmp']; sub new { my $type = shift; my $template_object = shift || {}; my @PASSED_ARGS = (ref $_[0] eq 'HASH') ? %{$_[0]} : @_; my %DEFAULT_ARGS = ( extension => { %{$EXTENSION}, }, extension_order => [qw(yaml perl storable xml split)], ); my $self = bless \%DEFAULT_ARGS, $type; $self->merge_in_args(@_); unless($self->{INCLUDE_PATH}) { if($template_object->{CONFIG}{INCLUDE_PATH}) { $self->{INCLUDE_PATH} = [reverse @{$template_object->{CONFIG}{INCLUDE_PATH}}]; } else { $self->{INCLUDE_PATH} = $DEFAULT_INCLUDE_PATH; } } return $self; } sub hash { my $self = shift; my $filename = shift; my $dirs = shift || $self->{INCLUDE_PATH}; my $return = {}; foreach my $dir (to_array($dirs)) { my $full_path = "$dir/$filename"; next unless(-e $full_path); my $this_ref; foreach my $regex (@{$self->{extension_order}}) { if($full_path =~ $self->{extension}{$regex}) { my $method = "load_$regex"; my $ref = $self->can($method); if($ref) { $this_ref = $self->$ref($full_path); } elsif($self->{$method}) { my $method_ref = ref $self->{$method}; if($method_ref && $method_ref eq 'CODE') { $this_ref = &{$self->{$method}}($self, $full_path); } else { die "\$self->{$method_ref} needs to be a CODE ref"; } } else { die "couldn't find a $method method to load $full_path"; } } } die "conf file returned a non-hash ref" unless(ref $this_ref && ref $this_ref eq 'HASH'); foreach (keys %{$this_ref}) { $return->{$_} = $this_ref->{$_}; } } return $return; } sub to_array { my $values = shift; return () unless defined $values; if (ref $values eq "ARRAY") { return @$values; } return ($values); } sub merge_in_args { my $self = shift; my %PASSED_ARGS = (ref $_[0] eq 'HASH') ? %{$_[0]} : @_; foreach my $passed_arg (keys %PASSED_ARGS) { if(ref $PASSED_ARGS{$passed_arg} && ref $PASSED_ARGS{$passed_arg} eq 'HASH') { foreach my $key (keys %{$PASSED_ARGS{$passed_arg}}) { $self->{$passed_arg}{$key} = $PASSED_ARGS{$passed_arg}{$key}; } } else { $self->{$passed_arg} = $PASSED_ARGS{$passed_arg} } } } sub load_yaml { my $self = shift; my $full_path = shift; require YAML; return YAML::LoadFile($full_path); } sub load_perl { my $self = shift; my $full_path = shift; return do $full_path; } sub load_storable { my $self = shift; my $full_path = shift; require Storable; return Storable::retrieve($full_path); } sub load_split { my $self = shift; my $full_path = shift; my $this_ref = {}; open(FILE, $full_path) || die "couldn't open $full_path: $!"; while() { next if(/^\s*#/); next unless(/^(\S+)\s+(.+)\n?/); $this_ref->{$1} = $2; } close(FILE); return $this_ref; } sub load_xml { my $self = shift; my $full_path = shift; require XML::Simple; return XML::Simple::XMLin($full_path); } 1; __END__ =head1 NAME Template::Plugin::DataHash - use INCLUDE_PATH to get confs with key fallback =head1 OVERVIEW Template::Plugin::DataHash provides a simple way to turn conf files, gathered from your INCLUDE_PATH into a single hash ref (no support for non hashes). I walk the INCLUDE_PATH, tack on the filename onto the end of each directory and this gives me the full_path of the file I will check. If the file exists, I run all the regexes in $self->{extension} (going in the order specified in $self->{extension_order}), to see which $type of conf I have, and then look for either a load_$type method or $self->{load_$type} (a CODE ref) and run the appropriate method. Each load_$type method takes a refence to $self and the full_path of the file, and returns a hash ref. Two structures in the object help manage the process. =head1 $self->{extension} $self->{extension} contains a hash ref of regex objects that map a type (Storable for example) to a regex that gets run on the full_path of the file. =head1 $self->{extension_order} $self->{extension_order} contains a array ref specifying the order to check the extensions. =head1 EXAMPLE Let's say you have two conf files: /tmp/default/conf.yaml key1: default1 key2: default2 /tmp/override/conf.yaml key2: override2 In your template you could put [% USE dho = DataHash({INCLUDE_PATH => ['/tmp/override', '/tmp/default']}) %] [% hash = dho.hash('conf.yaml') %] hash would then look like { key1 => 'default1', key2 => 'override2', } By default, the INCLUDE_PATH comes from $template_object->{CONFIG}{INCLUDE_PATH}. =head1 SUPPORTED EXTENSIONS This is the set of default extension regexes extension => { perl => qr/\.pl$/i, split => qr/\.split$/i, storable => qr/\.sto$/i, xml => qr/\.xml$/i, yaml => qr/\.yaml$/i, }, The default extension order is extension_order => [qw(yaml perl storable xml split)], =head1 ADDING YOUR OWN EXTENSION TYPES If I have left out an extension that you feel others may be interested in, let me know and I can easily add new types. If you have a custom extension, you need to get the custom name into $self->{extension}, like $self->{extension}{custom} = qr/\.custom$/; then you can either do an overriding object, or specify the method in your object, like $self->{load_custom} = sub { my $self = shift; my $full_path = shift; ... convert file to $ref in custom fashion ... return $ref }; =head1 AUTHOR Copyright 2003, Earl J. Cahill. All rights reserved. This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. Address bug reports and comments to: cpan@spack.net. When sending bug reports, please provide the version of Template::Plugin::DataHash, the version of Perl, and the name and version of the operating system you are using.