summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTzafrir Cohen <tzafrir.cohen@xorcom.com>2009-03-02 20:43:25 +0000
committerTzafrir Cohen <tzafrir.cohen@xorcom.com>2009-03-02 20:43:25 +0000
commitbf8893ba35004a7f6649a90b5995b0eba457c66f (patch)
treef8fdd408ff830fdc38a0ebc15b3abb652def3108
parente594beaf47f16883bce725a686440d086683baff (diff)
dahdi_genconf: configuration handling cleanup.
* Parsing genconf_parameters is now in Dahdi::Config::Params All hard-coded defaults are there too (in the item() method). * Dahdi::Config::Genconf is gone (merged into Dahdi::Config::Gen) All semantic mapping is in the constructor. * dahdi_genconf is now lean and mean. * Add some implementation docs into these files. git-svn-id: http://svn.asterisk.org/svn/dahdi/tools/trunk@6075 a0bf4364-ded3-4de4-8d8a-66a801d63aff
-rwxr-xr-xxpp/dahdi_genconf226
-rw-r--r--xpp/perl_modules/Dahdi/Config/Gen.pm181
-rw-r--r--xpp/perl_modules/Dahdi/Config/Gen/Chandahdi.pm2
-rw-r--r--xpp/perl_modules/Dahdi/Config/Gen/System.pm2
-rw-r--r--xpp/perl_modules/Dahdi/Config/Gen/Unicall.pm2
-rw-r--r--xpp/perl_modules/Dahdi/Config/Gen/Users.pm2
-rw-r--r--xpp/perl_modules/Dahdi/Config/Params.pm148
7 files changed, 385 insertions, 178 deletions
diff --git a/xpp/dahdi_genconf b/xpp/dahdi_genconf
index cba1963..2988343 100755
--- a/xpp/dahdi_genconf
+++ b/xpp/dahdi_genconf
@@ -14,187 +14,52 @@ BEGIN { my $dir = dirname($0); unshift(@INC, "$dir", "$dir/perl_modules"); }
use Getopt::Std;
use Dahdi;
use Dahdi::Xpp;
-use Dahdi::Config::GenconfDefaults;
+use Dahdi::Config::Gen;
+use Dahdi::Config::Params;
my $version = '1'; # Functionality version (integer)
my $revision = '$Revision$';
my %opts;
-my $genconf_defaults;
-
-my %default_context = (
- FXO => 'from-pstn',
- FXS => 'from-internal',
- IN => 'astbank-input',
- OUT => 'astbank-output',
- BRI_TE => 'from-pstn',
- BRI_NT => 'from-internal',
- E1_TE => 'from-pstn',
- T1_TE => 'from-pstn',
- J1_TE => 'from-pstn',
- E1_NT => 'from-internal',
- T1_NT => 'from-internal',
- J1_NT => 'from-internal',
- );
-
-my %default_group = (
- FXO => 0,
- FXS => "5",
- IN => '',
- OUT => '',
- BRI_TE => 0,
- BRI_NT => 6,
- E1_TE => 0,
- T1_TE => 0,
- J1_TE => 0,
- E1_NT => 6,
- T1_NT => 6,
- J1_NT => 6,
- );
-
-my $fxs_default_start = 'ks';
-
-my %default_dahdi_signalling = (
- FXO => 'fxsks',
- FXS => "fxo{fxs_default_start}",
- IN => "fxo{fxs_default_start}",
- OUT => "fxo{fxs_default_start}",
- );
-
-my %default_chan_dahdi_signalling = (
- FXO => 'fxs_ks',
- FXS => "fxo_{fxs_default_start}",
- IN => "fxo_{fxs_default_start}",
- OUT => "fxo_{fxs_default_start}",
- );
-
-my $lc_country = 'us';
-my $pri_termtype = 'SPAN/* TE';
-my $echo_can = 'mg2';
-
-my %global_config = (
- 'genconf_file' => 'HARD-CODED-DEFAULT', # GenconfDefaults override
- 'base_exten' => 4000,
- 'freepbx' => 'no', # Better via -F command line
- 'fxs_immediate' => 'no',
- 'loadzone' => $lc_country,
- 'defaultzone' => $lc_country,
- 'context' => \%default_context,
- 'group' => \%default_group,
- 'bri_hardhdlc' => 'no',
- 'bri_sig_style' => 'bri_ptmp',
- 'r2_idle_bits' => '1101',
- 'brint_overlap' => 'no',
- 'pri_connection_type' => 'PRI', # PRI or R2
- 'dahdi_signalling' => \%default_dahdi_signalling,
- 'chan_dahdi_signalling' => \%default_chan_dahdi_signalling,
- );
-
-my %dahdi_default_vars = (
- GENCONF_FILE => \$global_config{'genconf_file'},
- base_exten => \$global_config{'base_exten'},
- freepbx => \$global_config{'freepbx'},
- fxs_immediate => \$global_config{'fxs_immediate'},
- fxs_default_start => \$fxs_default_start,
- lc_country => [
- \$global_config{'loadzone'},
- \$global_config{'defaultzone'},
- ],
- context_lines => \$default_context{FXO},
- context_phones => \$default_context{FXS},
- context_input => \$default_context{IN},
- context_output => \$default_context{OUT},
- group_phones => [
- \$default_group{FXS},
- \$default_group{IN},
- \$default_group{OUT},
- ],
- group_lines => \$default_group{FXO},
- bri_sig_style => \$global_config{'bri_sig_style'},
- brint_overlap => \$global_config{'brint_overlap'},
- pri_termtype => \$pri_termtype,
- pri_connection_type => \$global_config{'pri_connection_type'},
- r2_idle_bits => \$global_config{'r2_idle_bits'},
- echo_can => \$echo_can,
- bri_hardhdlc => \$global_config{'bri_hardhdlc'},
- );
-
-sub map_dahdi_defaults {
- my %defaults = @_;
- foreach my $name (keys %defaults) {
- my $val = $defaults{$name};
- my $ref = $dahdi_default_vars{$name};
- my $type = ref $ref;
- my @vars = ();
- # Some broken shells (msh) export even variables
- # That where not defined. Work around that.
- next unless defined $val && $val ne '';
- if($type eq 'SCALAR') {
- @vars = ($ref);
- } elsif($type eq 'ARRAY') {
- @vars = @$ref;
- } else {
- die "$0: Don't know how to map '$name' (type=$type)\n";
- }
- foreach my $v (@vars) {
- $$v = $val;
- #printf STDERR "%-20s %s\n", $v, $val;
- }
- }
-}
-
-my @spans = Dahdi::spans();
sub set_defaults {
- # Source default files
my $default_file = $ENV{GENCONF_PARAMETERS} || "/etc/dahdi/genconf_parameters";
- $genconf_defaults = Dahdi::Config::GenconfDefaults->new($default_file);
+ my $params = Dahdi::Config::Params->new($default_file);
+ #$params->dump;
if($opts{v}) {
- print "Default parameters from ", $genconf_defaults->{GENCONF_FILE}, "\n";
+ print "Default parameters from ", $params->{GENCONF_FILE}, "\n";
}
- map_dahdi_defaults(%$genconf_defaults);
+ my $gconfig = Dahdi::Config::Gen->new($params);
+ #$gconfig->dump;
+ return $gconfig;
+}
+
+sub spans_prep($@) {
+ my $gconfig = shift || die;
+ my @spans = @_;
foreach my $span (@spans) {
if($span->is_pri) {
- $span->pri_set_fromconfig($genconf_defaults);
+ $span->pri_set_fromconfig($gconfig);
}
}
- # Fixups
- foreach my $val (values %default_dahdi_signalling, values %default_chan_dahdi_signalling) {
- $val =~ s/{fxs_default_start}/$fxs_default_start/g;
- }
- #$genconf_defaults->dump;
}
-sub parse_args {
- my @default_generators;
+sub generator_list($) {
+ my $gconfig = shift || die;
+ my @genlist;
if (@ARGV) {
for my $gen (@ARGV) {
- push @default_generators, $gen;
+ push @genlist, $gen;
}
} else {
# No files given. Use the defaults.
- @default_generators = ('system', 'chandahdi');
- if($global_config{'pri_connection_type'} eq 'R2') {
- push @default_generators, 'unicall';
+ @genlist = ('system', 'chandahdi');
+ if($gconfig->{'pri_connection_type'} eq 'R2') {
+ push @genlist, 'unicall';
}
}
- return @default_generators;
-}
-
-sub run_generator($$) {
- my ($name, $genopts) = @_;
-
- if(defined $opts{'v'}) {
- $genopts->{'verbose'} = $opts{v};
- }
- my $module = "Dahdi::Config::Gen::$name";
- eval "use $module";
- if($@) {
- die "Failed to load configuration generator for '$name'\n";
- }
- my $cfg = new $module(\%global_config, $genopts);
- $cfg->generate(@spans);
+ return @genlist;
}
sub parse_genopts($) {
@@ -210,16 +75,21 @@ sub parse_genopts($) {
return %genopts;
}
-sub generate_files(@) {
- my @default_generators = @_;
+sub generate_files($@) {
+ my $gconfig = shift || die;
+ my @spans = @_;
+ my @generators = generator_list($gconfig);
- for my $gen (@default_generators) {
+ for my $gen (@generators) {
my ($name, $optstr) = split(/=/, $gen, 2);
die "Illegal name '$name'\n" unless $name =~ /^\w+$/;
$name =~ s/(.)(.*)/\u$1\L$2/;
my %genopts = parse_genopts($optstr);
$genopts{'freepbx'} = 'yes' if $opts{'F'};
- run_generator($name, \%genopts);
+ if(defined $opts{'v'}) {
+ $genopts{'verbose'} = $opts{v};
+ }
+ $gconfig->run_generator($name, \%genopts, @spans);
}
}
@@ -232,9 +102,10 @@ if($opts{'V'}) {
exit 0;
}
-my @default_generators = parse_args;
-set_defaults;
-generate_files @default_generators;
+my $gconfig = set_defaults;
+my @spans = Dahdi::spans();
+spans_prep($gconfig, @spans);
+generate_files($gconfig, @spans);
__END__
@@ -279,7 +150,7 @@ a comma separated list of options to the generator name. E.g:
dahdi_genconf system chandahdi=verbose unicall
-Global options:
+=head1 Global options:
=over 4
@@ -298,3 +169,28 @@ Currently, chandahdi is affected.
=back
+
+=head1 Implementation notes:
+
+=over 4
+
+=item *
+
+F<genconf_parameters> parsing is done via C<Dahdi::Config::Params>.
+An object representing the parsed data is instanciated by:
+C<Dahdi::Config::Params-E<gt>new()>.
+The C<item()> method of this object contains all the hard coded
+defaults of the configuration directives.
+
+=item *
+
+A configuration object is instanciated by C<Dahdi::Config::Gen-E<gt>new($params)>.
+The mapping of configuration directives into semantic configuration is
+done in the constructor.
+
+=item *
+
+A single generator is run via the the C<run_generator()> method of the
+configuration object.
+
+=back
diff --git a/xpp/perl_modules/Dahdi/Config/Gen.pm b/xpp/perl_modules/Dahdi/Config/Gen.pm
index 556e193..b68fa28 100644
--- a/xpp/perl_modules/Dahdi/Config/Gen.pm
+++ b/xpp/perl_modules/Dahdi/Config/Gen.pm
@@ -1,4 +1,46 @@
package Dahdi::Config::Gen;
+#
+# Written by Oron Peled <oron@actcom.co.il>
+# Copyright (C) 2009, Xorcom
+# This program is free software; you can redistribute and/or
+# modify it under the same terms as Perl itself.
+#
+# $Id$
+#
+
+=head1 NAME
+
+Dahdi::Config::Gen -- Wrapper class for configuration generators.
+
+=head1 SYNOPSIS
+
+ use Dahdi::Config::Gen qw(is_true);
+ my $params = Dahdi::Config::Params->new('the-config-file');
+ my $gconfig = Dahdi::Config::Gen->new($params);
+ my $num = $gconfig->{'base_exten'};
+ my $overlap = is_true($gconfig->{'brint_overlap'});
+ $gconfig->dump; # For debugging
+ $gconfig->run_generator('system', {}, @spans);
+
+=head1 DESCRIPTION
+
+The constructor must be given an C<Dahdi::Config::Params> object.
+The returned object contains all data required for generation in the
+form of a hash.
+
+The constructor maps the C<item()>s from the parameter object into semantic
+configuration keys. E.g: the C<lc_country> item is mapped to C<loadzone> and
+C<defaultzone> keys.
+
+The actual generation is done by delegation to one of the generators.
+This is done via the C<run_generator()> method which receive the
+generator name, a generator specific options hash and a list of
+span objects (from C<Dahdi::Span>) for which to generate configuration.
+
+This module contains few helper functions. E.g: C<is_true()>, C<bchan_range()>.
+
+=cut
+
require Exporter;
@ISA = qw(Exporter);
@@ -6,21 +48,15 @@ require Exporter;
use strict;
+# Parse values as true/false
sub is_true($) {
my $val = shift;
return undef unless defined $val;
return $val =~ /^(1|y|yes)$/i;
}
-sub show_gconfig($) {
- my $gconfig = shift || die;
-
- print "Global configuration:\n";
- foreach my $key (sort keys %{$gconfig}) {
- printf " %-20s %s\n", $key, $gconfig->{$key};
- }
-}
-
+# Generate channel range strings from span objects
+# E.g: "63-77,79-93"
sub bchan_range($) {
my $span = shift || die;
my $first_chan = ($span->chans())[0];
@@ -46,4 +82,131 @@ sub bchan_range($) {
return join(',', @range);
}
+sub new($) {
+ my $pack = shift || die "$0: Missing package argument";
+ my $p = shift || die "$0: Missing parameters argument";
+
+ # Set defaults
+ my $fxs_default_start = $p->item('fxs_default_start');
+
+ my %default_context = (
+ FXO => $p->item('context_lines'),
+ FXS => $p->item('context_phones'),
+ IN => $p->item('context_input'),
+ OUT => $p->item('context_output'),
+ BRI_TE => $p->item('context_lines'),
+ BRI_NT => $p->item('context_phones'),
+ E1_TE => $p->item('context_lines'),
+ T1_TE => $p->item('context_lines'),
+ J1_TE => $p->item('context_lines'),
+ E1_NT => $p->item('context_phones'),
+ T1_NT => $p->item('context_phones'),
+ J1_NT => $p->item('context_phones'),
+ );
+ my %default_group = (
+ FXO => $p->item('group_lines'),
+ FXS => $p->item('group_phones'),
+ IN => '',
+ OUT => '',
+ BRI_TE => 0,
+ BRI_NT => 6,
+ E1_TE => 0,
+ T1_TE => 0,
+ J1_TE => 0,
+ E1_NT => 6,
+ T1_NT => 6,
+ J1_NT => 6,
+ );
+ my %default_dahdi_signalling = (
+ FXO => 'fxsks',
+ FXS => "fxo$fxs_default_start",
+ IN => "fxo$fxs_default_start",
+ OUT => "fxo$fxs_default_start",
+ );
+ my %default_chan_dahdi_signalling = (
+ FXO => 'fxs_ks',
+ FXS => "fxo_$fxs_default_start",
+ IN => "fxo_$fxs_default_start",
+ OUT => "fxo_$fxs_default_start",
+ );
+
+ # First complex mapping
+ my $gconfig = {
+ PARAMETERS => $p,
+ 'loadzone' => $p->item('lc_country'),
+ 'defaultzone' => $p->item('lc_country'),
+ 'context' => \%default_context,
+ 'group' => \%default_group,
+ 'dahdi_signalling' => \%default_dahdi_signalling,
+ 'chan_dahdi_signalling' => \%default_chan_dahdi_signalling,
+ };
+ # Now add trivial mappings
+ my @trivial = qw(
+ base_exten
+ freepbx
+ fxs_immediate
+ bri_hardhdlc
+ bri_sig_style
+ r2_idle_bits
+ echo_can
+ brint_overlap
+ pri_termtype
+ pri_connection_type
+ );
+ foreach my $k (@trivial) {
+ $gconfig->{$k} = $p->item($k);
+ }
+ bless $gconfig,$pack;
+
+ return $gconfig;
+}
+
+sub run_generator($$@) {
+ my $gconfig = shift || die;
+ my $name = shift || die "$0: Missing generator name argument";
+ my $genopts = shift || die "$0: Missing genopts argument";
+ ref($genopts) eq 'HASH' or die "$0: Bad genopts argument";
+ my @spans = @_;
+
+ my $module = "Dahdi::Config::Gen::$name";
+ #print STDERR "DEBUG: $module\n";
+ eval "use $module";
+ if($@) {
+ die "Failed to load configuration generator for '$name'\n";
+ }
+ my $cfg = $module->new($gconfig, $genopts);
+ $cfg->generate(@spans);
+}
+
+sub dump($) {
+ my $self = shift || die;
+ printf STDERR "%s dump:\n", ref $self;
+ my $width = 30;
+ foreach my $k (sort keys %$self) {
+ my $val = $self->{$k};
+ my $ref = ref $val;
+ #print STDERR "DEBUG: '$k', '$ref', '$val'\n";
+ if($ref eq '') {
+ printf STDERR "%-${width}s %s\n", $k, $val;
+ } elsif($ref eq 'SCALAR') {
+ printf STDERR "%-${width}s %s\n", $k, ${$val};
+ } elsif($ref eq 'ARRAY') {
+ #printf STDERR "%s:\n", $k;
+ my $i = 0;
+ foreach my $v (@{$val}) {
+ printf STDERR "%-${width}s %s\n", "$k\->[$i]", $v;
+ $i++;
+ }
+ } elsif($ref eq 'HASH') {
+ #printf STDERR "%s:\n", $k;
+ foreach my $k1 (keys %{$val}) {
+ printf STDERR "%-${width}s %s\n", "$k\->\{$k1\}", ${$val}{$k1};
+ }
+ } else {
+ printf STDERR "%-${width}s (-> %s)\n", $k, $ref;
+ }
+ }
+}
+
+
1;
diff --git a/xpp/perl_modules/Dahdi/Config/Gen/Chandahdi.pm b/xpp/perl_modules/Dahdi/Config/Gen/Chandahdi.pm
index f48455d..1f39a7a 100644
--- a/xpp/perl_modules/Dahdi/Config/Gen/Chandahdi.pm
+++ b/xpp/perl_modules/Dahdi/Config/Gen/Chandahdi.pm
@@ -131,7 +131,7 @@ sub generate($) {
my $file = $self->{FILE};
my $gconfig = $self->{GCONFIG};
my $genopts = $self->{GENOPTS};
- #Dahdi::Config::Gen::show_gconfig($gconfig);
+ #$gconfig->dump;
my @spans = @_;
warn "Empty configuration -- no spans\n" unless @spans;
rename "$file", "$file.bak"
diff --git a/xpp/perl_modules/Dahdi/Config/Gen/System.pm b/xpp/perl_modules/Dahdi/Config/Gen/System.pm
index da60ec4..f805b65 100644
--- a/xpp/perl_modules/Dahdi/Config/Gen/System.pm
+++ b/xpp/perl_modules/Dahdi/Config/Gen/System.pm
@@ -107,7 +107,7 @@ sub generate($$$) {
rename "$file", "$file.bak"
or $! == 2 # ENOENT (No dependency on Errno.pm)
or die "Failed to backup old config: $!\n";
- #Dahdi::Config::Gen::show_gconfig($gconfig);
+ #$gconfig->dump;
print "Generating $file\n" if $genopts->{verbose};
open(F, ">$file") || die "$0: Failed to open $file: $!\n";
my $old = select F;
diff --git a/xpp/perl_modules/Dahdi/Config/Gen/Unicall.pm b/xpp/perl_modules/Dahdi/Config/Gen/Unicall.pm
index 526b62b..1a796f4 100644
--- a/xpp/perl_modules/Dahdi/Config/Gen/Unicall.pm
+++ b/xpp/perl_modules/Dahdi/Config/Gen/Unicall.pm
@@ -22,7 +22,7 @@ sub generate($) {
my $file = $self->{FILE};
my $gconfig = $self->{GCONFIG};
my $genopts = $self->{GENOPTS};
- #Dahdi::Config::Gen::show_gconfig($gconfig);
+ #$gconfig->dump;
my @spans = @_;
warn "Empty configuration -- no spans\n" unless @spans;
die "Only for R2" unless $gconfig->{'pri_connection_type'} eq 'R2';
diff --git a/xpp/perl_modules/Dahdi/Config/Gen/Users.pm b/xpp/perl_modules/Dahdi/Config/Gen/Users.pm
index 36c2e65..e9d8ab9 100644
--- a/xpp/perl_modules/Dahdi/Config/Gen/Users.pm
+++ b/xpp/perl_modules/Dahdi/Config/Gen/Users.pm
@@ -73,7 +73,7 @@ sub generate($) {
my $file = $self->{FILE};
my $gconfig = $self->{GCONFIG};
my $genopts = $self->{GENOPTS};
- #Dahdi::Config::Gen::show_gconfig($gconfig);
+ #$gconfig->dump;
my @spans = @_;
warn "Empty configuration -- no spans\n" unless @spans;
rename "$file", "$file.bak"
diff --git a/xpp/perl_modules/Dahdi/Config/Params.pm b/xpp/perl_modules/Dahdi/Config/Params.pm
new file mode 100644
index 0000000..b6d9cdc
--- /dev/null
+++ b/xpp/perl_modules/Dahdi/Config/Params.pm
@@ -0,0 +1,148 @@
+package Dahdi::Config::Params;
+#
+# Written by Oron Peled <oron@actcom.co.il>
+# Copyright (C) 2009, Xorcom
+# This program is free software; you can redistribute and/or
+# modify it under the same terms as Perl itself.
+#
+# $Id$
+#
+use strict;
+
+=head1 NAME
+
+Dahdi::Config::Params -- Object oriented representation of F<genconf_parameters> file.
+
+=head1 SYNOPSIS
+
+ use Dahdi::Config::Params;
+ my $params = Dahdi::Config::Params->new('the-config-file');
+ print $params->item{'some-key'};
+ $params->dump; # For debugging
+
+=head1 DESCRIPTION
+
+The constructor must be given a configuration file name:
+
+=over 4
+
+=item * Missing file is B<not> an error.
+
+=item * Other opening errors cause a C<die> to be thrown.
+
+=item * The file name is saved as the value of C<GENCONF_FILE> key.
+
+=back
+
+The access to config keys should only be done via the C<item()> method:
+
+=over 4
+
+=item * It contains all hard-coded defaults.
+
+=item * All these values are overriden by directives in the config file.
+
+=back
+
+=cut
+
+sub new($$) {
+ my $pack = shift || die;
+ my $cfg_file = shift || die;
+ my $self = {
+ GENCONF_FILE => $cfg_file,
+ };
+ bless $self, $pack;
+ if(!open(F, $cfg_file)) {
+ if(defined($!{ENOENT})) {
+ #print STDERR "No $cfg_file. Assume empty config\n";
+ return $self; # Empty configuration
+ }
+ die "$pack: Failed to open '$cfg_file': $!\n";
+ }
+ #print STDERR "$pack: $cfg_file\n";
+ my $array_key;
+ while(<F>) {
+ my ($key, $val);
+ chomp;
+ s/#.*$//;
+ s/\s+$//; # trim tail whitespace
+ next unless /\S/;
+ if(defined $array_key && /^\s+/) {
+ s/^\s+//; # trim beginning whitespace
+ push(@{$self->{$array_key}}, $_);
+ next;
+ }
+ undef $array_key;
+ ($key, $val) = split(/\s+/, $_, 2);
+ $key = lc($key);
+ if(! defined $val) {
+ $array_key = $key;
+ next;
+ }
+ die "$cfg_file:$.: Duplicate key '$key'\n", if exists $self->{$key};
+ $self->{$key} = $val;
+ }
+ close F;
+ return $self;
+}
+
+sub item($$) {
+ my $self = shift || die;
+ my $key = shift || die;
+ my %defaults = (
+ base_exten => '4000',
+ freepbx => 'no', # Better via -F command line
+ fxs_immediate => 'no',
+ fxs_default_start => 'ks',
+ lc_country => 'us',
+ context_lines => 'from-pstn',
+ context_phones => 'from-internal',
+ context_input => 'astbank-input',
+ context_output => 'astbank-output',
+ group_phones => '5',
+ group_lines => '0',
+ brint_overlap => 'no',
+ bri_sig_style => 'bri_ptmp',
+ echo_can => 'mg2',
+ bri_hardhdlc => 'no',
+ pri_connection_type => 'PRI',
+ r2_idle_bits => '1101',
+ 'pri_termtype' => [ 'SPAN/* TE' ],
+ );
+
+ return (exists($self->{$key})) ? $self->{$key} :$defaults{$key};
+}
+
+sub dump($) {
+ my $self = shift || die;
+ printf STDERR "%s dump:\n", ref $self;
+ my $width = 30;
+ foreach my $k (sort keys %$self) {
+ my $val = $self->{$k};
+ my $ref = ref $val;
+ #print STDERR "DEBUG: '$k', '$ref', '$val'\n";
+ if($ref eq '') {
+ printf STDERR "%-${width}s %s\n", $k, $val;
+ } elsif($ref eq 'SCALAR') {
+ printf STDERR "%-${width}s %s\n", $k, ${$val};
+ } elsif($ref eq 'ARRAY') {
+ #printf STDERR "%s:\n", $k;
+ my $i = 0;
+ foreach my $v (@{$val}) {
+ printf STDERR "%-${width}s %s\n", "$k\->[$i]", $v;
+ $i++;
+ }
+ } elsif($ref eq 'HASH') {
+ #printf STDERR "%s:\n", $k;
+ foreach my $k1 (keys %{$val}) {
+ printf STDERR "%-${width}s %s\n", "$k\->\{$k1\}", ${$val}{$k1};
+ }
+ } else {
+ printf STDERR "%-${width}s (-> %s)\n", $k, $ref;
+ }
+ }
+}
+
+1;
+