;#
;# Copyright (c) 1995-1998
;# Ikuo Nakagawa. All rights reserved.
;#
;# Redistribution and use in source and binary forms, with or without
;# modification, are permitted provided that the following conditions
;# are met:
;#
;# 1. Redistributions of source code must retain the above copyright
;# notice unmodified, this list of conditions, and the following
;# disclaimer.
;# 2. Redistributions in binary form must reproduce the above copyright
;# notice, this list of conditions and the following disclaimer in the
;# documentation and/or other materials provided with the distribution.
;#
;# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
;# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
;# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
;# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS
;# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
;# OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
;# OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
;# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
;# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
;# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
;# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
;#
;# $Id: Param.pm,v 1.19 1998/09/19 03:58:35 ikuo Exp $
;#
;# Useful libraries to treat parameters.
;#
;# $param = Fan::Param->new(
;# param_name => 'INIT',
;# %param_values);
;#
;# $defaults = Fan::Param->new(
;# param_name => 'DEFAULT',
;# param_prefix => "/usr/local/etc",
;# param_file => "param.conf");
;#
;# $options = Fan::Param->new(
;# param_name => 'OPTION',
;# param_array => \@ARGV);
;#
;# Since any key begins with 'param_' has special meaning for
;# Param package, the first statement in above, %param_value
;# can have no assoc key whose name begins with 'param_'.
;#
;# You can also combine parameters, as:
;#
;# my $override = 1;
;# $param = Fan::Param->new(param_name => "TARGET");
;# $param->combine($init, $override);
;# $param->combine($defaults, $override);
;# $param->combine($options, $override);
;#
;# These statements are equevalent with:
;#
;# $param = Fan::Param->new(param_name => "TARGET");
;# $param->merge($init, $defaults, $options);
;#
;# You can access to any values in a Param object as follows:
;#
;# $param->getval('key-to-access');
;# $param->setval('key-to-access', 'value-to-set');
;#
;# You can restrict to set values for unexpected keys with
;# `param_keys' values for a Param object. For example,
;#
;# %param_default = (
;# "key_any" => '', # any value
;# "key_digit" => '/^\d+$/ || undef', # only digits
;# "key_ipaddr" => '/^\d+\.\d+\.\d+\.\d+$/ || undef',
;# # ip address
;# "key_range" => '$_ >= 100 && $_ < 200 || undef',
;# # number range
;# "key_path" => '-f $_ || undef' # real path
;# "key_abspath" => '/^\// || undef', # absolute path
;# "key_proc" => \&callproc # procedure
;# );
;# $param = Fan::Param->new(
;# param_name => 'PARAM WE WILL ACCESS',
;# param_keys => \%default_default);
;#
;# if you initialize $param as above,
;#
;# $param->setval('not_in_default', 'any_value'),
;#
;# will do nothing and simply returns undef. You can modify
;# $param{$key} only if $param_default{$key} exists and the
;# evaluation of
;#
;# $_ = $val;
;# eval $param_default{$key};
;#
;# returns non zero.
;#
package Fan::Param;
use strict;
use vars qw($VERSION $LOG $param_sequence %wants);
use Carp;
use AutoLoader 'AUTOLOAD';
$VERSION = '0.03';
$LOG = 5; # notice level...
;# Sequencial number for Param objects.
$param_sequence = 0;
;# prototypes.
sub DESTROY ($);
sub new ($%);
sub error ($;$);
sub try_check ($$;$);
sub getval ($$);
sub delete ($$);
sub setval ($$$);
sub addval ($$$);
sub dump ($);
sub combine ($@);
sub merge ($@);
;# internal routines.
sub want_ref;
sub want_code;
sub want_hash;
sub want_array;
sub want_boolean;
sub want_integer;
sub want_octal;
sub want_ipv4_addr;
sub want_path;
sub want_file;
sub want_directory;
sub want_timezone;
;# initialize want hash
%wants = (
'REF' => \&want_ref,
'CODE' => \&want_code,
'HASH' => \&want_hash,
'ARRAY' => \&want_array,
'BOOLEAN' => \&want_boolean,
'INTEGER' => \&want_integer,
'OCTAL' => \&want_octal,
'IPv4_ADDR' => \&want_ipv4_addr,
'PATH' => \&want_path,
'FILE' => \&want_file,
'DIRECTORY' => \&want_directory,
'TIMEZONE' => \&want_timezone,
);
;#
;# A special marker for AutoSplit.
1;
__END__
;# Destroy a Param object.
;#
sub DESTROY ($) {
my $self = shift;
# Log message for debugging purpose
carp("Param DESTROYING $self [$self->{param_name}]") if $LOG > 5;
}
;# Create a new Param object.
;#
sub new ($%) {
my $this = shift;
my $class = ref($this) || $this;
my %param = @_;
my $self = { param_error => 0 };
# Count up param objects.
$param_sequence++;
# Pick up some special parameters.
$self->{param_name} = $param{param_name} || "Param[$param_sequence]";
# Check keys param object.
if (ref($param{param_keys}) eq 'HASH') {
$self->{param_keys} = $param{param_keys}; # save ref
}
# Check keys for nesting parameters.
if (ref($param{param_nest}) eq 'HASH') {
$self->{param_nest} = $param{param_nest}; # save ref
}
# Create a new object.
bless $self, $class or return undef;
# Log message for debugging purpose
carp("Param CREATING $self [$self->{param_name}]") if $LOG > 5;
# Register (key, val) pairs in %param.
my $key;
my $val;
while (($key, $val) = each %param) {
$self->setval($key, $val) if $key !~ /^param_/;
}
# Return myself.
$self;
}
;#
sub error ($;$) {
my $self = shift;
if (@_) {
$self->{param_error} = shift;
}
$self->{param_error};
}
;#
;#
sub addkey ($$;$) {
my $p = shift;
my $key = shift;
my $val = @_ ? shift : '';
$p->{param_keys}->{$key} = $val;
}
;#
;#
sub try_check ($$;$) {
my $p = shift;
my $key = shift;
my $h = $p->{param_keys}; # hash for keys
local $_ = 1; # default return value.
# Validation of the given key.
if ($key =~ /^param_/ || (ref($h) eq 'HASH' && !exists($h->{$key}))) {
# carp("$p: key=$key invalid key") if $LOG > 4;
confess("$p: key=$key invalid key") if $LOG > 4;
$p->{param_error}++;
return undef;
}
# Validation of the given value, if exists.
if (@_ && ref($h) eq 'HASH' && exists($h->{$key})) {
my $val = $h->{$key};
my $x = shift; # backup
$_ = $x;
# copy from default wants tables.
if (!ref($val) && defined($wants{$val})) {
$val = $wants{$val};
}
# check value types
if ($_ eq '') {
; # null string is o.k.
} elsif ($val eq '') {
; # o.k.
} elsif (ref($val) eq 'CODE') {
$_ = &{$val}($_);
} elsif (defined(eval($val))) {
; # good.
} else {
carp $@ if $@ && $LOG > 3; # evaluation error...
undef $_;
}
if (!defined($_)) {
croak("Param ($key, $x) invalid value") if $LOG > 4;
$p->{param_error}++;
return undef;
}
}
# Result is the converted value.
$_;
}
;#
;#
sub getval ($$) {
my $p = shift;
my $key = shift;
$p->try_check($key) || return undef;
defined($p->{$key}) ? $p->{$key} : undef;
}
;#
;#
sub delete ($$) {
my $p = shift;
my $key = shift;
$p->try_check($key) || return undef;
exists($p->{$key}) ? CORE::delete($p->{$key}) : 0;
}
;#
;#
sub setval ($$$) {
my $p = shift;
my $key = shift;
my $val = shift;
my $x = $p->try_check($key, $val);
defined($x) ? ($p->{$key} = $x) : undef;
}
;#
;#
sub addval ($$$) {
my $p = shift;
my $key = shift;
my $val = shift;
$val = $p->{$key}."\n".$val;
my $x = $p->try_check($key, $val);
defined($x) ? ($p->{$key} = $x) : undef;
}
;# dump parameters
;#
sub dump ($) {
my $p = shift;
print "* $p name=$p->{param_name}\n";
my @keys = sort keys %{$p};
my $key;
for $key (grep(/^param_/, @keys), grep(!/^param_/, @keys)) {
my $val = $p->{$key};
if ($val =~ /\n/) {
my $s = $val =~ s/^\n// ? '+' : '';
print " $key $s=\n";
for $s (split(/\n/, $val)) {
print " $s\n";
}
} else {
print " $key = $val\n";
}
}
1;
}
;# Combine some parameters for Param objects.
;# $p->combine($a, $b, ..., $z, $flag) will combine as follows:
;# in order of $a, $b, ..., $z, copy parameter values to $p.
;# If $flag is non zero, override is permitted.
;#
sub combine ($@) {
my $p = shift; # output object
my @list = ();
my $count = 0;
my $n;
# check Param objects.
while (defined($n = shift) && ref($n) && $n->isa('Fan::Param')) {
push(@list, $n);
}
# now $n is the flag of override.
my $param;
for $param (@list) {
my $key;
my $val;
while (($key, $val) = each %{$param}) {
next if $key =~ /^param_/;
if (exists($p->{$key})) {
if ($val =~ /^\n/) { # append
$val = $p->{$key}.$val;
} elsif (!$n) { # not override
next;
}
}
if ($p->try_check($key)) {
$p->{$key} = $val; # copy
$count++;
} else {
; # simply ignored
}
}
}
# succeeded
1;
}
;# $p->merge($a, $b, ..., $z) is same as
;# $p->combine($a, $b, ..., $z, 1);
;#
sub merge ($@) {
my $p = shift;
$p->combine(@_, 1);
}
;# Subroutines for check operations
;#
sub want_ref {
my $x = shift;
if (@_) {
ref($x) eq shift || return undef;
} else {
ref($x) || return undef;
}
$x;
}
;#
sub want_code {
want_ref(shift, 'CODE');
}
;#
sub want_hash {
want_ref(shift, 'HASH');
}
;#
sub want_array {
want_ref(shift, 'ARRAY');
}
;# want boolean value,
;# converted to 1 or 0.
;#
sub want_boolean {
my $x = shift;
return $& ? 1 : 0 if $x =~ /^\d+$/;
return 1 if $x =~ /^(yes|t|true|do|will)$/i;
return 0 if $x =~ /^(no|nil|false|dont|wont)$/i;
undef;
}
;# want decimal value,
;# force to be converted to an integer.
;#
sub want_decimal {
my $x = shift;
return $& + 0 if $x =~ /^\d+$/;
undef;
}
;# want octal value,
;#
sub want_octal {
my $x = shift;
return $& if $x =~ /^[0-7]+$/;
undef;
}
;# want an integer value (with or without sign),
;# force to be an integer.
;#
sub want_integer {
my $x = shift;
my $flag = 1;
if ($x =~ s/^-//) {
$flag = -1;
} elsif ($x =~ s/^\+//) {
;
}
return $flag * $& if $x =~ /^\d+$/;
undef;
}
;# want IPv4 address.
;#
sub want_ipv4_addr {
my $x = shift;
return $& if $x =~ /^\d+\.\d+\.\d+\.\d+$/;
undef;
}
;# want_path($string, $eval)
;# convert a tilda notation (like ~ftp).
;#
sub want_path {
my $path = shift;
my $dir = '';
# warn("input is \"$path\"\n");
# Expand pathname first.
# For example, "~ikuo/src/hogehoge" will expanded to
# "/home/ikuo/src/hogehoge".
if ($path =~ s|^~([^/]*)||) {
if ($1 ne '') {
$dir = (getpwnam($1))[7];
} else {
$dir = $ENV{'HOME'} || (getpwuid($<))[7];
}
$path = $dir.$path;
}
# Result must not be null string.
return undef if $path eq '';
# Evaluation test.
if (@_) {
local $_ = $path;
if(!defined(eval shift)) {
carp $@ if $@ && $LOG > 3;
# warn("result is undef\n");
return undef;
}
$path = $_;
}
# warn("result is path\n");
# Result is $path.
$path;
}
;#
sub want_file {
want_path(shift, '-f $_ || undef');
}
;#
sub want_directory {
want_path(shift, '-d $_ || undef');
}
;# want timezone.
;# converted to ``sign . %02d . %02d ''.
;#
sub want_timezone {
my $tz = shift;
if ($tz =~ /^(\+|-)(\d\d?)(\d\d)$/) {
return sprintf("%s%02d%02d", $1, $2, $3);
} elsif ($tz eq 'GMT') {
return '+0000';
} elsif ($tz eq 'JST') {
return '+0900';
}
undef;
}
;# end of Fan::Param module
syntax highlighted by Code2HTML, v. 0.9.1