diff -Nru libconfig-inifiles-perl-2.88/Build.PL libconfig-inifiles-perl-2.94/Build.PL --- libconfig-inifiles-perl-2.88/Build.PL 2015-07-10 08:37:30.000000000 +0000 +++ libconfig-inifiles-perl-2.94/Build.PL 2016-11-29 17:30:06.000000000 +0000 @@ -1,63 +1,81 @@ +# This file was automatically generated by Dist::Zilla::Plugin::ModuleBuild v5.047. use strict; use warnings; -use File::Spec; -use lib File::Spec->catdir(File::Spec->curdir(), "inc"); +use Module::Build 0.28; -use Test::Run::Builder; -my $builder = Test::Run::Builder->new( - 'module_name' => "Config::IniFiles", - 'license' => "perl", - dist_author => 'Shlomi Fish ', - 'configure_requires' => - { - 'Module::Build' => '0.36', - }, - 'build_requires' => - { - 'base' => 0, - 'File::Spec' => 0, - 'List::Util' => 0, - 'Scalar::Util' => 0, - 'strict' => 0, - 'Test::More' => 0, - 'warnings' => 0, - }, - 'requires' => - { - 'strict' => 0, - 'perl' => '5.008', - 'Carp' => 0, - 'Symbol' => 0, - 'warnings' => 0, - 'Fcntl' => 0, - 'File::Basename' => 0, - 'File::Temp' => 0, - 'List::Util' => '1.33', - }, - create_makefile_pl => 'small', - meta_merge => - { - resources => - { - repository => "https://bitbucket.org/shlomif/perl-config-inifiles", - homepage => "https://sourceforge.net/projects/config-inifiles/", - }, - keywords => - [ - "ini", - "files", - "config", - "configuration", - "ini files", - "windows", - "tie", - "pure-perl", - "tested", - ], - }, +my %module_build_args = ( + "build_requires" => { + "Module::Build" => "0.28" + }, + "configure_requires" => { + "ExtUtils::MakeMaker" => 0, + "Module::Build" => "0.28" + }, + "dist_abstract" => "A module for reading .ini-style configuration files.", + "dist_author" => [ + "Shlomi Fish " + ], + "dist_name" => "Config-IniFiles", + "dist_version" => "2.94", + "license" => "perl", + "module_name" => "Config::IniFiles", + "recursive_test_files" => 1, + "requires" => { + "Carp" => 0, + "Fcntl" => 0, + "File::Basename" => 0, + "File::Temp" => 0, + "IO::Scalar" => 0, + "List::Util" => "1.33", + "Symbol" => 0, + "perl" => "5.008", + "strict" => 0, + "vars" => 0, + "warnings" => 0 + }, + "test_requires" => { + "English" => 0, + "Exporter" => 0, + "File::Spec" => 0, + "File::Temp" => 0, + "IO::File" => 0, + "IO::Handle" => 0, + "IPC::Open3" => 0, + "Scalar::Util" => 0, + "Test::More" => 0, + "base" => 0, + "blib" => "1.01", + "lib" => 0 + } ); -$builder->create_build_script; + +my %fallback_build_requires = ( + "English" => 0, + "Exporter" => 0, + "File::Spec" => 0, + "File::Temp" => 0, + "IO::File" => 0, + "IO::Handle" => 0, + "IPC::Open3" => 0, + "Module::Build" => "0.28", + "Scalar::Util" => 0, + "Test::More" => 0, + "base" => 0, + "blib" => "1.01", + "lib" => 0 +); + + +unless ( eval { Module::Build->VERSION(0.4004) } ) { + delete $module_build_args{test_requires}; + $module_build_args{build_requires} = \%fallback_build_requires; +} + +my $build = Module::Build->new(%module_build_args); + + +$build->create_build_script; diff -Nru libconfig-inifiles-perl-2.88/Changes libconfig-inifiles-perl-2.94/Changes --- libconfig-inifiles-perl-2.88/Changes 2015-07-10 08:37:30.000000000 +0000 +++ libconfig-inifiles-perl-2.94/Changes 2016-11-29 17:30:06.000000000 +0000 @@ -1,3 +1,34 @@ +2.94 2016-11-29 + * Move to GitHub. + - for better visibility, collaboration, and CI options. + * Apply a patch/pull-request from Peter from Debian with some corrections + for typos: + - https://github.com/shlomif/perl-Config-IniFiles/pull/1 + - https://rt.cpan.org/Ticket/Display.html?id=119001 + - Thanks! + +2.93 2016-07-24 + * Add [MetaJSON] and META.json to the distribution. + - Pull-request by Kent Fredric - thanks! + - https://bitbucket.org/shlomif/perl-config-inifiles/pull-requests/5/ship-metajson-with-metajson/diff + +2.92 2016-06-17 + * Hopefully fix tests with read/write test01.ini: + - http://www.cpantesters.org/cpan/report/121523be-341c-11e6-9fc5-14bfbd15da07 + * Some cleanups and modernisations to the tests. + +2.91 2016-06-03 + * Set minimal version of List::Util in the requires and in the code. + - Reported by some CPAN testers. + +2.90 2016-06-02 + * Convert to Dist-Zilla with some induced enhancements. + +2.89 2016-05-03 + * Abort if print returns false. + - See https://rt.cpan.org/Ticket/Display.html?id=114140 + - "WriteConfig nullifies the file in case of no space left on disk" + 2.88 2015-07-10 * Apply speedup patch from James Rouzier. - https://bitbucket.org/shlomif/perl-config-inifiles/pull-request/4/avoid-searching-through-all-the-sections/commits diff -Nru libconfig-inifiles-perl-2.88/debian/changelog libconfig-inifiles-perl-2.94/debian/changelog --- libconfig-inifiles-perl-2.88/debian/changelog 2015-08-04 12:30:35.000000000 +0000 +++ libconfig-inifiles-perl-2.94/debian/changelog 2021-08-18 22:29:12.000000000 +0000 @@ -1,3 +1,49 @@ +libconfig-inifiles-perl (2.94-1~16.04.sav0) xenial; urgency=medium + + * Backport to Xenial + + -- Rob Savoury Wed, 18 Aug 2021 15:29:12 -0700 + +libconfig-inifiles-perl (2.94-1) unstable; urgency=medium + + * New upstream release: + - drop the typos patch, integrated upstream + - update the upstream repository location in the metadata file - + moved to GitHub + * Add myself to the list of uploaders. + * Add a License line to the copyright file's header, too. + + -- Peter Pentchev Wed, 30 Nov 2016 11:04:04 +0200 + +libconfig-inifiles-perl (2.93-1) unstable; urgency=medium + + * Team upload. + + [ gregor herrmann ] + * Remove Jonathan Yu from Uploaders. Thanks for your work! + + [ Peter Pentchev ] + * Catch up with the renamed Lintian override for the debhelper version. + * Switch various upstream and Debian URLs to the HTTPS scheme. + * Use MetaCPAN as the upstream homepage, remove the references to + SourceForge. + * Add the typos patch to correct some errors. + * New upstream release: + - update the upstream copyright information + + -- Peter Pentchev Sun, 27 Nov 2016 19:39:43 +0200 + +libconfig-inifiles-perl (2.89-1) unstable; urgency=medium + + * Team upload + + * Add debian/upstream/metadata + * Import upstream version 2.89 (Closes: #787079) + * Declare compliance with Debian Policy 3.9.8 + * Mark package autopkgtest-able + + -- Florian Schlichting Tue, 03 May 2016 21:30:07 +0200 + libconfig-inifiles-perl (2.88-1) unstable; urgency=medium [ upstream ] diff -Nru libconfig-inifiles-perl-2.88/debian/control libconfig-inifiles-perl-2.94/debian/control --- libconfig-inifiles-perl-2.88/debian/control 2015-08-04 12:23:28.000000000 +0000 +++ libconfig-inifiles-perl-2.94/debian/control 2016-11-30 09:04:04.000000000 +0000 @@ -1,26 +1,28 @@ Source: libconfig-inifiles-perl +Maintainer: Debian Perl Group +Uploaders: Jonas Smedegaard , Peter Pentchev Section: perl +Testsuite: autopkgtest-pkg-perl Priority: extra Build-Depends: cdbs, - devscripts, - perl, - libmodule-build-perl, - debhelper, - dh-buildinfo, - perl (>= 5.19.5) | libscalar-list-utils-perl (>= 1:1.33), - libio-stringy-perl -Maintainer: Debian Perl Group -Uploaders: Jonas Smedegaard , Jonathan Yu -Standards-Version: 3.9.6 -Vcs-Git: git://anonscm.debian.org/pkg-perl/packages/libconfig-inifiles-perl + devscripts, + perl, + libmodule-build-perl, + debhelper, + dh-buildinfo, + perl (>= 5.19.5) | libscalar-list-utils-perl (>= 1:1.33), + libio-stringy-perl +Standards-Version: 3.9.8 Vcs-Browser: https://anonscm.debian.org/cgit/pkg-perl/packages/libconfig-inifiles-perl.git -Homepage: http://search.cpan.org/dist/Config-IniFiles/ +Vcs-Git: https://anonscm.debian.org/git/pkg-perl/packages/libconfig-inifiles-perl.git +Homepage: https://metacpan.org/release/Config-IniFiles/ Package: libconfig-inifiles-perl Architecture: all -Depends:, ${cdbs:Depends}, - ${misc:Depends}, - ${perl:Depends} +Depends: ${cdbs:Depends}, + ${misc:Depends}, + ${perl:Depends}, + perl (>= 5.19.5) | libscalar-list-utils-perl (>= 1:1.33) Description: Read .ini-style configuration files Config::IniFiles provides a way to have readable configuration files outside your Perl script. Configurations can be imported (inherited, diff -Nru libconfig-inifiles-perl-2.88/debian/control.in libconfig-inifiles-perl-2.94/debian/control.in --- libconfig-inifiles-perl-2.88/debian/control.in 2015-08-04 12:10:52.000000000 +0000 +++ libconfig-inifiles-perl-2.94/debian/control.in 2016-11-30 09:04:04.000000000 +0000 @@ -3,15 +3,16 @@ Priority: extra Build-Depends: @cdbs@ Maintainer: Debian Perl Group -Uploaders: Jonas Smedegaard , Jonathan Yu -Standards-Version: 3.9.6 -Vcs-Git: git://anonscm.debian.org/pkg-perl/packages/libconfig-inifiles-perl +Uploaders: Jonas Smedegaard , Peter Pentchev +Standards-Version: 3.9.8 +Vcs-Git: https://anonscm.debian.org/git/pkg-perl/packages/libconfig-inifiles-perl.git Vcs-Browser: https://anonscm.debian.org/cgit/pkg-perl/packages/libconfig-inifiles-perl.git -Homepage: http://search.cpan.org/dist/Config-IniFiles/ +Homepage: https://metacpan.org/release/Config-IniFiles/ +Testsuite: autopkgtest-pkg-perl Package: libconfig-inifiles-perl Architecture: all -Depends:, ${cdbs:Depends}, +Depends: ${cdbs:Depends}, ${misc:Depends}, ${perl:Depends} Description: Read .ini-style configuration files diff -Nru libconfig-inifiles-perl-2.88/debian/copyright libconfig-inifiles-perl-2.94/debian/copyright --- libconfig-inifiles-perl-2.88/debian/copyright 2015-08-04 12:23:13.000000000 +0000 +++ libconfig-inifiles-perl-2.94/debian/copyright 2016-11-30 09:04:04.000000000 +0000 @@ -1,9 +1,8 @@ -Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: Config::IniFiles Upstream-Contact: Shlomi Fish - http://sourceforge.net/projects/config-inifiles/ -Source: http://search.cpan.org/dist/Config-IniFiles/ - http://config-inifiles.svn.sourceforge.net/viewvc/config-inifiles/ +Source: https://metacpan.org/release/Config-IniFiles/ +License: Artistic or GPL-1+ Files: * Copyright: @@ -30,6 +29,11 @@ © 2011, Shlomi Fish License: Expat +Files: t/34trailing-comments-double-delimeter.t +Copyright: + © 2015, Shlomi Fish +License: Expat + Files: debian/* Copyright: © 2002-2004,2006,2008-2015, Jonas Smedegaard diff -Nru libconfig-inifiles-perl-2.88/debian/copyright_hints libconfig-inifiles-perl-2.94/debian/copyright_hints --- libconfig-inifiles-perl-2.88/debian/copyright_hints 2015-08-04 12:23:26.000000000 +0000 +++ libconfig-inifiles-perl-2.94/debian/copyright_hints 2016-11-30 09:04:04.000000000 +0000 @@ -1,4 +1,4 @@ -Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: FIXME Upstream-Contact: FIXME Source: FIXME diff -Nru libconfig-inifiles-perl-2.88/debian/README.source libconfig-inifiles-perl-2.94/debian/README.source --- libconfig-inifiles-perl-2.88/debian/README.source 2014-05-20 13:02:58.000000000 +0000 +++ libconfig-inifiles-perl-2.94/debian/README.source 2016-11-30 09:04:04.000000000 +0000 @@ -5,7 +5,7 @@ are encouraged to) make special use of these tools. In particular, the debian/control.in file can be completely ignored. -More info here: http://wiki.debian.org/CDBS+git-buildpackage +More info here: https://wiki.debian.org/CDBS+git-buildpackage -- Jonas Smedegaard Mon, 18 Feb 2013 12:55:37 +0100 diff -Nru libconfig-inifiles-perl-2.88/debian/source/lintian-overrides libconfig-inifiles-perl-2.94/debian/source/lintian-overrides --- libconfig-inifiles-perl-2.88/debian/source/lintian-overrides 2015-08-03 15:02:15.000000000 +0000 +++ libconfig-inifiles-perl-2.94/debian/source/lintian-overrides 2016-11-30 09:04:04.000000000 +0000 @@ -5,4 +5,4 @@ missing-license-paragraph-in-dep5-copyright # Debhelper 9 is satisfied even in oldstable -package-needs-versioned-debhelper-build-depends 9 +package-lacks-versioned-build-depends-on-debhelper 9 diff -Nru libconfig-inifiles-perl-2.88/debian/upstream/metadata libconfig-inifiles-perl-2.94/debian/upstream/metadata --- libconfig-inifiles-perl-2.88/debian/upstream/metadata 1970-01-01 00:00:00.000000000 +0000 +++ libconfig-inifiles-perl-2.94/debian/upstream/metadata 2016-11-30 09:04:04.000000000 +0000 @@ -0,0 +1,6 @@ +--- +Archive: CPAN +Contact: Shlomi Fish +Name: Config-IniFiles +Repository: https://github.com/shlomif/perl-Config-IniFiles.git +Repository-Browse: https://github.com/shlomif/perl-Config-IniFiles diff -Nru libconfig-inifiles-perl-2.88/debian/watch libconfig-inifiles-perl-2.94/debian/watch --- libconfig-inifiles-perl-2.88/debian/watch 2014-05-20 13:11:54.000000000 +0000 +++ libconfig-inifiles-perl-2.94/debian/watch 2016-11-30 09:04:04.000000000 +0000 @@ -1,4 +1,4 @@ # Run the "uscan" command to check for upstream updates and more. version=3 -http://metacpan.org/release/Config-IniFiles .*/Config-IniFiles-([\d.]+)\.tar\.gz +https://metacpan.org/release/Config-IniFiles .*/Config-IniFiles-([\d.]+)\.tar\.gz http://www.cpan.org/authors/id/S/SH/SHLOMIF/Config-IniFiles-([\d.]+)\.tar\.gz diff -Nru libconfig-inifiles-perl-2.88/dist.ini libconfig-inifiles-perl-2.94/dist.ini --- libconfig-inifiles-perl-2.88/dist.ini 1970-01-01 00:00:00.000000000 +0000 +++ libconfig-inifiles-perl-2.94/dist.ini 2016-11-29 17:30:06.000000000 +0000 @@ -0,0 +1,41 @@ +name = Config-IniFiles +author = Shlomi Fish +license = Perl_5 +copyright_holder = RBOW and others +copyright_year = 2000 + +[@Filter] +-bundle = @Basic +-remove = License +-remove = Readme +[AutoPrereqs] +[Keywords] +keyword = config +keyword = configuration +keyword = files +keyword = ini +keyword = ini files +keyword = pure-perl +keyword = tested +keyword = tie +keyword = windows +[MetaProvides::Package] +[MetaResources] +bugtracker.web = http://rt.cpan.org/NoAuth/Bugs.html?Dist=Config-IniFiles +bugtracker.mailto = bug-config-inifiles@rt.cpan.org +repository.url = ssh://git@github.com:shlomif/perl-Config-IniFiles.git +repository.web = https://github.com/shlomif/perl-Config-IniFiles +repository.type = git +[ModuleBuild] +[MetaJSON] +[PodSyntaxTests] +[PodCoverageTests] +[PodWeaver] +[PruneFiles] +match = ^rejects/ +[RewriteVersion] +[Test::Compile] +fake_home = 1 +skip = bump-ver|tag-release|run_agg_tests +[Test::CPAN::Changes] +[Test::Kwalitee::Extra] diff -Nru libconfig-inifiles-perl-2.88/lib/Config/IniFiles.pm libconfig-inifiles-perl-2.94/lib/Config/IniFiles.pm --- libconfig-inifiles-perl-2.88/lib/Config/IniFiles.pm 2015-07-10 08:37:30.000000000 +0000 +++ libconfig-inifiles-perl-2.94/lib/Config/IniFiles.pm 2016-11-29 17:30:06.000000000 +0000 @@ -1,17 +1,15 @@ package Config::IniFiles; -use vars qw($VERSION); - -$VERSION = '2.88'; - require 5.008; use strict; use warnings; + +our $VERSION = '2.94'; use Carp; use Symbol 'gensym','qualify_to_ref'; # For the 'any data type' hack use Fcntl qw( SEEK_SET SEEK_CUR ); -use List::Util qw(any none); +use List::Util 1.33 qw(any none); use File::Basename qw( dirname ); use File::Temp qw/ tempfile /; @@ -20,2973 +18,2939 @@ # $Header: /home/shlomi/progs/perl/cpan/Config/IniFiles/config-inifiles-cvsbackup/config-inifiles/IniFiles.pm,v 2.41 2003-12-08 10:50:56 domq Exp $ -=encoding utf8 -=head1 NAME +sub _nocase +{ + my $self = shift; -Config::IniFiles - A module for reading .ini-style configuration files. + if (@_) + { + $self->{nocase} = (shift(@_) ? 1 : 0); + } -=head1 SYNOPSIS + return $self->{nocase}; +} - use Config::IniFiles; - my $cfg = Config::IniFiles->new( -file => "/path/configfile.ini" ); - print "The value is " . $cfg->val( 'Section', 'Parameter' ) . "." - if $cfg->val( 'Section', 'Parameter' ); +sub _is_parm_in_sect +{ + my ($self, $sect, $parm) = @_; -=head1 DESCRIPTION + return any { $_ eq $parm } @{$self->{myparms}{$sect}}; +} -Config::IniFiles provides a way to have readable configuration files outside -your Perl script. Configurations can be imported (inherited, stacked,...), -sections can be grouped, and settings can be accessed from a tied hash. +sub new { + my $class = shift; + my %parms = @_; -=head1 FILE FORMAT + my $errs = 0; + my @groups = ( ); -INI files consist of a number of sections, each preceded with the -section name in square brackets, followed by parameter names and -their values. + my $self = bless { + default => '', + fallback =>undef, + fallback_used => 0, + imported =>undef, + v =>{}, + cf => undef, + firstload => 1, + nomultiline => 0, + handle_trailing_comment => 0, + }, $class; - [a section] - Parameter=Value + if( ref($parms{-import}) && ($parms{-import}->isa('Config::IniFiles')) ) { + $self->{imported}=$parms{-import}; # ReadConfig will load the data + $self->{negativedeltas}=1; + } elsif( defined $parms{-import} ) { + carp "Invalid -import value \"$parms{-import}\" was ignored."; + } # end if + delete $parms{-import}; - [section 2] - AnotherParameter=Some value - Setting=Something else - Parameter=Different scope than the one in the first section + # Copy the original parameters so we + # can use them when we build new sections + %{$self->{startup_settings}} = %parms; -The first non-blank character of the line indicating a section must -be a left bracket and the last non-blank character of a line indicating -a section must be a right bracket. The characters making up the section -name can be any symbols at all. However section names must be unique. + # Parse options + my($k, $v); + local $_; + $self->_nocase(0); -Parameters are specified in each section as Name=Value. Any spaces -around the equals sign will be ignored, and the value extends to the -end of the line (including any whitespace at the end of the line. -Parameter names are localized to the namespace of the section, but must -be unique within a section. + # Handle known parameters first in this order, + # because each() could return parameters in any order + if (defined ($v = delete $parms{'-file'})) { + # Should we be pedantic and check that the file exists? + # .. no, because now it could be a handle, IO:: object or something else + $self->{cf} = $v; + } + if (defined ($v = delete $parms{'-nocase'})) { + $self->_nocase($v); + } + if (defined ($v = delete $parms{'-default'})) { + $self->{default} = $self->_nocase ? lc($v) : $v; + } + if (defined ($v = delete $parms{'-fallback'})) { + $self->{fallback} = $self->_nocase ? lc($v) : $v; + } + if (defined ($v = delete $parms{'-reloadwarn'})) { + $self->{reloadwarn} = $v ? 1 : 0; + } + if (defined ($v = delete $parms{'-nomultiline'})) { + $self->{nomultiline} = $v ? 1 : 0; + } + if (defined ($v = delete $parms{'-allowcontinue'})) { + $self->{allowcontinue} = $v ? 1 : 0; + } + if (defined ($v = delete $parms{'-allowempty'})) { + $self->{allowempty} = $v ? 1 : 0; + } + if (defined ($v = delete $parms{'-negativedeltas'})) { + $self->{negativedeltas} = $v ? 1 : 0; + } + if (defined ($v = delete $parms{'-commentchar'})) { + if(!defined $v || length($v) != 1) { + carp "Comment character must be unique."; + $errs++; + } + elsif($v =~ /[\[\]=\w]/) { + # must not be square bracket, equal sign or alphanumeric + carp "Illegal comment character."; + $errs++; + } + else { + $self->{comment_char} = $v; + } + } + if (defined ($v = delete $parms{'-allowedcommentchars'})) { + # must not be square bracket, equal sign or alphanumeric + if(!defined $v || $v =~ /[\[\]=\w]/) { + carp "Illegal value for -allowedcommentchars."; + $errs++; + } + else { + $self->{allowed_comment_char} = $v; + } + } -Both the hash mark (#) and the semicolon (;) are comment characters. -by default (this can be changed by configuration). Lines that begin with -either of these characters will be ignored. Any amount of whitespace may -precede the comment character. + if (defined ($v = delete $parms{'-handle_trailing_comment'})) { + $self->{handle_trailing_comment} = $v ? 1 : 0; + } -Multi-line or multi-valued parameters may also be defined ala UNIX -"here document" syntax: + $self->{comment_char} = '#' unless exists $self->{comment_char}; + $self->{allowed_comment_char} = ';' unless exists $self->{allowed_comment_char}; + # make sure that comment character is always allowed + $self->{allowed_comment_char} .= $self->{comment_char}; - Parameter=<{_comments_at_end_of_file} = []; -You may use any string you want in place of "EOT". Note that whatever -follows the "<<" and what appears at the end of the text MUST match -exactly, including any trailing whitespace. + # Any other parameters are unknown + while (($k, $v) = each %parms) { + carp "Unknown named parameter $k=>$v"; + $errs++; + } -Alternately, as a configuration option (default is off), continuation -lines can be allowed: + return undef if $errs; - [Section] - Parameter=this parameter \ - spreads across \ - a few lines + if ($self->ReadConfig) { + return $self; + } else { + return undef; + } +} -=head1 USAGE -- Object Interface -Get a new Config::IniFiles object with the I method: +sub _caseify { + my ($self, @refs) = @_; - $cfg = Config::IniFiles->new( -file => "/path/config_file.ini" ); - $cfg = new Config::IniFiles -file => "/path/config_file.ini"; + if (not $self->_nocase) + { + return; + } -Optional named parameters may be specified after the configuration -file name. See the I in the B section, below. + foreach my $ref (@refs) { + ${$ref} = lc(${$ref}) + } -Values from the config file are fetched with the val method: + return; +} - $value = $cfg->val('Section', 'Parameter'); +sub val +{ + my ($self, $sect, $parm, $def) = @_; -If you want a multi-line/value field returned as an array, just -specify an array as the receiver: + # Always return undef on bad parameters + if (not (defined($sect) && defined($parm))) + { + return; + } - @values = $cfg->val('Section', 'Parameter'); + $self->_caseify(\$sect, \$parm); -=head1 METHODS + my $val_sect = + defined($self->{v}{$sect}{$parm}) + ? $sect + : $self->{default} + ; -=head2 new ( [-option=>value ...] ) + my $val = $self->{v}{$val_sect}{$parm}; -Returns a new configuration object (or "undef" if the configuration -file has an error, in which case check the global C<@Config::IniFiles::errors> -array for reasons why). One Config::IniFiles object is required per configuration -file. The following named parameters are available: + # If the value is undef, make it $def instead (which could just be undef) + if (!defined ($val)) + { + $val = $def; + } -=over 10 + # Return the value in the desired context + if (wantarray) + { + if (ref($val) eq "ARRAY") + { + return @$val; + } + elsif (defined($val)) + { + return $val; + } + else + { + return; + } + } + elsif (ref($val) eq "ARRAY") + { + return join( (defined($/) ? $/ : "\n"), @$val); + } + else + { + return $val; + } +} -=item I<-file> filename +sub exists { + my ($self, $sect, $parm) = @_; -Specifies a file to load the parameters from. This 'file' may actually be -any of the following things: + $self->_caseify(\$sect, \$parm); - 1) the pathname of a file + return (exists $self->{v}{$sect}{$parm}); +} - $cfg = Config::IniFiles->new( -file => "/path/to/config_file.ini" ); - 2) a simple filehandle - $cfg = Config::IniFiles->new( -file => STDIN ); - 3) a filehandle glob +sub push { + my ($self, $sect, $parm, @vals) = @_; - open( CONFIG, "/path/to/config_file.ini" ); - $cfg = Config::IniFiles->new( -file => *CONFIG ); + return undef if not defined $sect; + return undef if not defined $parm; - 4) a reference to a glob + $self->_caseify(\$sect, \$parm); - open( CONFIG, "/path/to/config_file.ini" ); - $cfg = Config::IniFiles->new( -file => \*CONFIG ); + return undef if (! defined($self->{v}{$sect}{$parm})); - 5) an IO::File object + return 1 if (! @vals); - $io = IO::File->new( "/path/to/config_file.ini" ); - $cfg = Config::IniFiles->new( -file => $io ); + $self->_touch_parameter($sect, $parm); - or + $self->{EOT}{$sect}{$parm} = 'EOT' if + (!defined $self->{EOT}{$sect}{$parm}); - open my $fh, '<', "/path/to/config_file.ini" or die $!; - $cfg = Config::IniFiles->new( -file => $fh ); + $self->{v}{$sect}{$parm} = [$self->{v}{$sect}{$parm}] unless + (ref($self->{v}{$sect}{$parm}) eq "ARRAY"); - 6) A reference to a scalar (requires newer versions of IO::Scalar) + CORE::push @{$self->{v}{$sect}{$parm}}, @vals; + return 1; +} - $ini_file_contents = <new( -file => \$ini_file_contents ); +sub setval { + my $self = shift; + my $sect = shift; + my $parm = shift; + my @val = @_; + return undef if not defined $sect; + return undef if not defined $parm; -If this option is not specified, (i.e. you are creating a config file from scratch) -you must specify a target file using L in order to save the parameters. + $self->_caseify(\$sect, \$parm); + if (defined($self->{v}{$sect}{$parm})) { + $self->_touch_parameter($sect, $parm); + if (@val > 1) { + $self->{v}{$sect}{$parm} = \@val; + $self->{EOT}{$sect}{$parm} = 'EOT'; + } else { + $self->{v}{$sect}{$parm} = shift @val; + } + return 1; + } else { + return undef; + } +} -=item I<-default> section -Specifies a section to be used for default values. For example, in the -following configuration file, if you look up the "permissions" parameter -in the "joe" section, there is none. +sub newval { + my $self = shift; + my $sect = shift; + my $parm = shift; + my @val = @_; - [all] - permissions=Nothing + return undef if not defined $sect; + return undef if not defined $parm; - [jane] - name=Jane - permissions=Open files + $self->_caseify(\$sect, \$parm); - [joe] - name=Joseph + $self->AddSection($sect); -If you create your Config::IniFiles object with a default section of "all" like this: + if (none { $_ eq $parm } @{$self->{parms}{$sect}}) + { + CORE::push(@{$self->{parms}{$sect}}, $parm) + } - $cfg = Config::IniFiles->new( -file => "file.ini", -default => "all" ); + $self->_touch_parameter($sect, $parm); + if (@val > 1) { + $self->{v}{$sect}{$parm} = \@val; + if (!defined $self->{EOT}{$sect}{$parm}) + { + $self->{EOT}{$sect}{$parm} = 'EOT'; + } + } else { + $self->{v}{$sect}{$parm} = shift @val; + } + return 1 +} -Then requsting a value for a "permissions" in the [joe] section will -check for a value from [all] before returning undef. - $permissions = $cfg->val( "joe", "permissions"); // returns "Nothing" +sub delval { + my $self = shift; + my $sect = shift; + my $parm = shift; + return undef if not defined $sect; + return undef if not defined $parm; -=item I<-fallback> section + $self->_caseify(\$sect, \$parm); -Specifies a section to be used for parameters outside a section. Default is none. -Without -fallback specified (which is the default), reading a configuration file -which has a parameter outside a section will fail. With this set to, say, -"GENERAL", this configuration: + $self->{parms}{$sect} = [grep {$_ ne $parm} @{$self->{parms}{$sect}}]; + $self->_touch_parameter($sect, $parm); + delete $self->{v}{$sect}{$parm}; - wrong=wronger + return 1; +} - [joe] - name=Joseph -will be assumed as: +# Auxiliary function to make deep (aliasing-free) copies of data +# structures. Ignores blessed objects in tree (could be taught not +# to, if needed) +sub _deepcopy { + my $ref = shift; - [GENERAL] - wrong=wronger + if (! ref($ref)) { + return $ref; + } - [joe] - name=Joseph + if (UNIVERSAL::isa($ref, "ARRAY")) { + return [map {_deepcopy($_)} @$ref]; + } -Note that Config::IniFiles will also omit the fallback section header when -outputing such configuration. + if (UNIVERSAL::isa($ref, "HASH")) { + my $return = {}; + foreach my $k (keys %$ref) { + $return->{$k} = _deepcopy($ref->{$k}); + } + return $return; + } -=item I<-nocase> 0|1 + carp "Unhandled data structure in $ref, cannot _deepcopy()"; +} -Set -nocase => 1 to handle the config file in a case-insensitive -manner (case in values is preserved, however). By default, config -files are case-sensitive (i.e., a section named 'Test' is not the same -as a section named 'test'). Note that there is an added overhead for -turning off case sensitivity. +# Internal method, gets the next line, taking proper care of line endings. +sub _nextline { + my ($self, $fh) = @_; + local $_; + if (!exists $self->{line_ends}) { + # no $self->{line_ends} is a hint set by caller that we are at + # the first line (kludge kludge). + { + local $/=\1; + my $nextchar; + do { + $nextchar=<$fh>; + return undef if (!defined $nextchar); + $_ .= $nextchar; + } until (m/((\015|\012|\025|\n)$)/s); + $self->{line_ends}=$1; + if ($nextchar eq "\x0d") { + # peek at the next char + $nextchar = <$fh>; + if ($nextchar eq "\x0a") { + $self->{line_ends} .= "\x0a"; + } else { + seek $fh, -1, SEEK_CUR(); + } + } + } + # If there's a UTF BOM (Byte-Order-Mark) in the first + # character of the first line then remove it before processing + # ( http://www.unicode.org/unicode/faq/utf_bom.html#22 ) + s/^//; -=item I<-import> object + return $_; + } else { + local $/=$self->{line_ends}; + return scalar <$fh>; + } +} -This allows you to import or inherit existing setting from another -Config::IniFiles object. When importing settings from another object, -sections with the same name will be merged and parameters that are -defined in both the imported object and the I<-file> will take the -value of given in the I<-file>. +# Internal method, closes or resets the file handle. To be called +# whenever ReadConfig() returns. +sub _rollback { + my ($self, $fh) = @_; + # Only close if this is a filename, if it's + # an open handle, then just roll back to the start + if( !ref($self->{cf}) ) { + close($fh); + } else { + # Attempt to rollback to beginning, no problem if this fails (e.g. STDIN) + seek( $fh, 0, SEEK_SET() ); + } # end if +} -If a I<-default> section is also given on this call, and it does not -coincide with the default of the imported object, the new default -section will be used instead. If no I<-default> section is given, -then the default of the imported object will be used. +sub _no_filename +{ + my $self = shift; + my $fn = $self->{cf}; -=item I<-allowcontinue> 0|1 + return (not (defined($fn) && length($fn))); +} -Set -allowcontinue => 1 to enable continuation lines in the config file. -i.e. if a line ends with a backslash C<\>, then the following line is -appended to the parameter value, dropping the backslash and the newline -character(s). +sub _read_line_num +{ + my $self = shift; -Default behavior is to keep a trailing backslash C<\> as a parameter -value. Note that continuation cannot be mixed with the "here" value -syntax. + if (@_) + { + $self->{_read_line_num} = shift; + } + return $self->{_read_line_num}; +} -=item I<-allowempty> 0|1 +# Reads the next line and removes the end of line from it. +sub _read_next_line +{ + my ($self, $fh) = @_; -If set to 1, then empty files are allowed at L -time. If set to 0 (the default), an empty configuration file is considered -an error. + my $line = $self->_nextline($fh); + if (! defined($line)) + { + return undef; + } -=item I<-negativedeltas> 0|1 + $self->_read_line_num( $self->_read_line_num() + 1); -If set to 1 (the default if importing this object from another one), -parses and honors lines of the following form in the configuration -file: + # Remove line ending char(s) + $line =~ s/(\015\012?|\012|\025|\n)\z//; - ; [somesection] is deleted + return $line; +} -or +sub _add_error +{ + my ($self, $msg) = @_; - [inthissection] - ; thisparameter is deleted - -If set to 0 (the default if not importing), these comments are treated -like ordinary ones. + CORE::push(@Config::IniFiles::errors, $msg); -The L1)> form will output such -comments to indicate deleted sections or parameters. This way, -reloading a delta file using the same imported object produces the -same results in memory again. See L for more -details. + return; +} -=item I<-commentchar> 'char' +# The current section - used for parsing. +sub _curr_sect +{ + my $self = shift; -The default comment character is C<#>. You may change this by specifying -this option to another character. This can be any character except -alphanumeric characters, square brackets or the "equal" sign. + if (@_) + { + $self->{_curr_sect} = shift; + } + return $self->{_curr_sect}; +} -=item I<-allowedcommentchars> 'chars' +# The current parameter - used for parsing. +sub _curr_parm +{ + my $self = shift; -Allowed default comment characters are C<#> and C<;>. By specifying this -option you may change the range of characters that are used to denote a -comment line to include any set of characters + if (@_) + { + $self->{_curr_parm} = shift; + } -Note: that the character specified by B<-commentchar> (see above) is -I part of the allowed comment characters. + return $self->{_curr_parm}; +} -Note 2: The given string is evaluated as a regular expression character -class, so '\' must be escaped if you wish to use it. +# Current location - section and parameter. +sub _curr_loc +{ + my $self = shift; + return ($self->_curr_sect, $self->_curr_parm); +} -=item I<-reloadwarn> 0|1 +# The current value - used in parsing. +sub _curr_val +{ + my $self = shift; -Set -reloadwarn => 1 to enable a warning message (output to STDERR) -whenever the config file is reloaded. The reload message is of the -form: + if (@_) + { + $self->{_curr_val} = shift; + } - PID reloading config file at YYYY.MM.DD HH:MM:SS + return $self->{_curr_val}; +} -Default behavior is to not warn (i.e. -reloadwarn => 0). +sub _curr_cmts +{ + my $self = shift; -This is generally only useful when using Config::IniFiles in a server -or daemon application. The application is still responsible for determining -when the object is to be reloaded. + if (@_) + { + $self->{_curr_cmts} = shift; + } + return $self->{_curr_cmts}; +} -=item I<-nomultiline> 0|1 +sub _curr_end_comment +{ + my $self = shift; -Set -nomultiline => 1 to output multi-valued parameter as: + if (@_) + { + $self->{_curr_end_comment} = shift; + } - param=value1 - param=value2 + return $self->{_curr_end_comment}; +} -instead of the default: +my $RET_CONTINUE = 1; +my $RET_BREAK; - param=<{negativedeltas} and + my ($to_delete) = $line =~ m/\A$self->{comment_char} (.*) is deleted\z/ + ) + { + if (my ($sect) = $to_delete =~ m/\A\[(.*)\]\z/) + { + $self->DeleteSection($sect); + } + else + { + $self->delval($self->_curr_sect, $to_delete); + } + } + else + { + CORE::push(@{$self->_curr_cmts}, $line); + } -=item I<-handle_trailing_comment> 0|1 + return $RET_CONTINUE; +} -Set -handle_trailing_comment => 1 to enable support of parameter trailing -comments. +sub _ReadConfig_new_section +{ + my ($self, $sect) = @_; -For example, if we have a parameter line like this: + $self->_caseify(\$sect); - param1=value1;comment1 + $self->_curr_sect($sect); + $self->AddSection($self->_curr_sect); + $self->SetSectionComment($self->_curr_sect, @{$self->_curr_cmts}); + $self->_curr_cmts([]); -by default, handle_trailing_comment will be set to B<0>, and we will get -I as the value of I. If we have --handle_trailing_comment set to B<1>, then we will get I -as the value for I, and I as the trailing comment of -I. + return $RET_CONTINUE; +} -Set and get methods for trailing comments are provided as -L and L. +sub _handle_fallback_sect +{ + my ($self) = @_; -=back + if ((!defined($self->_curr_sect)) and defined($self->{fallback})) + { + $self->_curr_sect($self->{fallback}); + $self->{fallback_used}++; + } -=cut + return; +} -sub _nocase +sub _ReadConfig_load_value { - my $self = shift; + my ($self, $val_aref) = @_; - if (@_) + # Now load value + if (exists $self->{v}{$self->_curr_sect}{$self->_curr_parm} && + exists $self->{myparms}{$self->_curr_sect} && + $self->_is_parm_in_sect($self->_curr_loc)) { - $self->{nocase} = (shift(@_) ? 1 : 0); + $self->push($self->_curr_loc, @$val_aref); + } + else + { + # Loaded parameters shadow imported ones, instead of appending + # to them + $self->newval($self->_curr_loc, @$val_aref); } - return $self->{nocase}; + return; } -sub _is_parm_in_sect +sub _test_for_fallback_or_no_sect { - my ($self, $sect, $parm) = @_; + my ($self, $fh) = @_; - return any { $_ eq $parm } @{$self->{myparms}{$sect}}; + $self->_handle_fallback_sect; + + if (!defined $self->_curr_sect) { + $self->_add_error( + sprintf('%d: %s', $self->_read_line_num(), + qq#parameter found outside a section# + ) + ); + $self->_rollback($fh); + return $RET_BREAK; + } + + return $RET_CONTINUE; } -sub new { - my $class = shift; - my %parms = @_; +sub _ReadConfig_handle_here_doc_param +{ + my ($self, $fh, $eotmark, $val_aref) = @_; - my $errs = 0; - my @groups = ( ); + my $foundeot = 0; + my $startline = $self->_read_line_num(); - my $self = bless { - default => '', - fallback =>undef, - fallback_used => 0, - imported =>undef, - v =>{}, - cf => undef, - firstload => 1, - nomultiline => 0, - handle_trailing_comment => 0, - }, $class; + HERE_DOC_LOOP: + while (defined( my $line = $self->_read_next_line($fh) )) + { + if ($line eq $eotmark) + { + $foundeot = 1; + last HERE_DOC_LOOP; + } + else + { + # Untaint + my ($contents) = $line =~ /(.*)/ms; + CORE::push(@$val_aref, $contents); + } + } - if( ref($parms{-import}) && ($parms{-import}->isa('Config::IniFiles')) ) { - $self->{imported}=$parms{-import}; # ReadConfig will load the data - $self->{negativedeltas}=1; - } elsif( defined $parms{-import} ) { - carp "Invalid -import value \"$parms{-import}\" was ignored."; - } # end if - delete $parms{-import}; + if (! $foundeot) + { + $self->_add_error(sprintf('%d: %s', $startline, + qq#no end marker ("$eotmark") found#)); + $self->_rollback(); + return $RET_BREAK; + } - # Copy the original parameters so we - # can use them when we build new sections - %{$self->{startup_settings}} = %parms; + return $RET_CONTINUE; +} - # Parse options - my($k, $v); - local $_; - $self->_nocase(0); +sub _ReadConfig_handle_non_here_doc_param +{ + my ($self, $fh, $val_aref) = @_; - # Handle known parameters first in this order, - # because each() could return parameters in any order - if (defined ($v = delete $parms{'-file'})) { - # Should we be pedantic and check that the file exists? - # .. no, because now it could be a handle, IO:: object or something else - $self->{cf} = $v; - } - if (defined ($v = delete $parms{'-nocase'})) { - $self->_nocase($v); - } - if (defined ($v = delete $parms{'-default'})) { - $self->{default} = $self->_nocase ? lc($v) : $v; - } - if (defined ($v = delete $parms{'-fallback'})) { - $self->{fallback} = $self->_nocase ? lc($v) : $v; - } - if (defined ($v = delete $parms{'-reloadwarn'})) { - $self->{reloadwarn} = $v ? 1 : 0; - } - if (defined ($v = delete $parms{'-nomultiline'})) { - $self->{nomultiline} = $v ? 1 : 0; - } - if (defined ($v = delete $parms{'-allowcontinue'})) { - $self->{allowcontinue} = $v ? 1 : 0; - } - if (defined ($v = delete $parms{'-allowempty'})) { - $self->{allowempty} = $v ? 1 : 0; - } - if (defined ($v = delete $parms{'-negativedeltas'})) { - $self->{negativedeltas} = $v ? 1 : 0; - } - if (defined ($v = delete $parms{'-commentchar'})) { - if(!defined $v || length($v) != 1) { - carp "Comment character must be unique."; - $errs++; - } - elsif($v =~ /[\[\]=\w]/) { - # must not be square bracket, equal sign or alphanumeric - carp "Illegal comment character."; - $errs++; - } - else { - $self->{comment_char} = $v; - } - } - if (defined ($v = delete $parms{'-allowedcommentchars'})) { - # must not be square bracket, equal sign or alphanumeric - if(!defined $v || $v =~ /[\[\]=\w]/) { - carp "Illegal value for -allowedcommentchars."; - $errs++; - } - else { - $self->{allowed_comment_char} = $v; - } - } - - if (defined ($v = delete $parms{'-handle_trailing_comment'})) { - $self->{handle_trailing_comment} = $v ? 1 : 0; - } - - $self->{comment_char} = '#' unless exists $self->{comment_char}; - $self->{allowed_comment_char} = ';' unless exists $self->{allowed_comment_char}; - # make sure that comment character is always allowed - $self->{allowed_comment_char} .= $self->{comment_char}; + my $allCmt = $self->{allowed_comment_char}; + my $end_commenthandle = $self->{handle_trailing_comment}; - $self->{_comments_at_end_of_file} = []; + # process continuation lines, if any + $self->_process_continue_val($fh); - # Any other parameters are unkown - while (($k, $v) = each %parms) { - carp "Unknown named parameter $k=>$v"; - $errs++; - } + # we should split value and comments if there is any comment + if ($end_commenthandle and + my ($value_to_assign, $end_comment_to_assign) = $self->_curr_val =~ /(.*?)\s*[$allCmt]\s*(.*)$/) + { + $self->_curr_val($value_to_assign); + $self->_curr_end_comment($end_comment_to_assign); + } + else + { + $self->_curr_end_comment(q{}); + } - return undef if $errs; + @{$val_aref} = ($self->_curr_val); - if ($self->ReadConfig) { - return $self; - } else { - return undef; - } + return; } -=head2 val ($section, $parameter [, $default] ) - -Returns the value of the specified parameter (C<$parameter>) in section -C<$section>, returns undef (or C<$default> if specified) if no section or -no parameter for the given section exists. - - -If you want a multi-line/value field returned as an array, just -specify an array as the receiver: - - @values = $cfg->val('Section', 'Parameter'); - -A multi-line/value field that is returned in a scalar context will be -joined using $/ (input record separator, default is \n) if defined, -otherwise the values will be joined using \n. - -=cut +sub _ReadConfig_populate_values +{ + my ($self, $val_aref, $eotmark) = @_; -sub _caseify { - my ($self, @refs) = @_; + $self->_ReadConfig_load_value($val_aref); - if (not $self->_nocase) + $self->SetParameterComment($self->_curr_loc, @{ $self->_curr_cmts }); + $self->_curr_cmts([]); + if (defined $eotmark) { - return; - } - - foreach my $ref (@refs) { - ${$ref} = lc(${$ref}) + $self->SetParameterEOT($self->_curr_loc, $eotmark); } + # if handle_trailing_comment is off, this line makes no sense, since all $end_comment="" + $self->SetParameterTrailingComment($self->_curr_loc, $self->_curr_end_comment); return; } -sub val +sub _ReadConfig_param_assignment { - my ($self, $sect, $parm, $def) = @_; + my ($self, $fh, $line, $parm, $value_to_assign) = @_; - # Always return undef on bad parameters - if (not (defined($sect) && defined($parm))) + $self->_curr_val($value_to_assign); + $self->_curr_end_comment(undef()); + + if (!defined( $self->_test_for_fallback_or_no_sect($fh) )) { - return; + return $RET_BREAK; } - $self->_caseify(\$sect, \$parm); - - my $val_sect = - defined($self->{v}{$sect}{$parm}) - ? $sect - : $self->{default} - ; - - my $val = $self->{v}{$val_sect}{$parm}; + $self->_caseify(\$parm); + $self->_curr_parm($parm); - # If the value is undef, make it $def instead (which could just be undef) - if (!defined ($val)) - { - $val = $def; - } + my @val = ( ); + my $eotmark; - # Return the value in the desired context - if (wantarray) + if (($eotmark) = $self->_curr_val =~ /\A<<(.*)$/) { - if (ref($val) eq "ARRAY") - { - return @$val; - } - elsif (defined($val)) - { - return $val; - } - else + if (! defined($self->_ReadConfig_handle_here_doc_param( + $fh, $eotmark, \@val + )) + ) { - return; + return $RET_BREAK; } } - elsif (ref($val) eq "ARRAY") - { - return join( (defined($/) ? $/ : "\n"), @$val); - } else { - return $val; + $self->_ReadConfig_handle_non_here_doc_param( $fh, \@val ); } -} -=head2 exists($section, $parameter) - -True if and only if there exists a section C<$section>, with -a parameter C<$parameter> inside, not counting default values. + $self->_ReadConfig_populate_values(\@val, $eotmark); -=cut + return $RET_CONTINUE; +} -sub exists { - my ($self, $sect, $parm) = @_; +# Return 1 to continue - undef to terminate the loop. +sub _ReadConfig_handle_line +{ + my ($self, $fh, $line) = @_; - $self->_caseify(\$sect, \$parm); + my $allCmt = $self->{allowed_comment_char}; - return (exists $self->{v}{$sect}{$parm}); -} + # ignore blank lines + if ($line =~ /\A\s*\z/) + { + return $RET_CONTINUE; + } + # collect comments + if ($line =~/\A\s*[$allCmt]/) + { + return $self->_ReadConfig_handle_comment($line); + } + # New Section + if (my ($sect) = $line =~ /\A\s*\[\s*(\S|\S.*\S)\s*\]\s*\z/) + { + return $self->_ReadConfig_new_section($sect); + } -=head2 push ($section, $parameter, $value, [ $value2, ...]) + # New parameter + if (my ($parm, $value_to_assign) = $line =~ /^\s*([^=]*?[^=\s])\s*=\s*(.*)$/) + { + return $self->_ReadConfig_param_assignment($fh, $line, $parm, $value_to_assign); + } -Pushes new values at the end of existing value(s) of parameter -C<$parameter> in section C<$section>. See below for methods to write -the new configuration back out to a file. + $self->_add_error( + sprintf("Line %d in file %s is malformed:\n\t\%s", + $self->_read_line_num(), $self->GetFileName(), $line + ) + ); -You may not set a parameter that didn't exist in the original -configuration file. B will return I if this is -attempted. See B below to do this. Otherwise, it returns 1. + return $RET_CONTINUE; +} -=cut +sub _ReadConfig_lines_loop +{ + my ($self, $fh) = @_; -sub push { - my ($self, $sect, $parm, @vals) = @_; + $self->_curr_sect(undef()); + $self->_curr_parm(undef()); + $self->_curr_val(undef()); + $self->_curr_cmts([]); - return undef if not defined $sect; - return undef if not defined $parm; + while ( defined(my $line = $self->_read_next_line($fh)) ) + { + if (!defined( + scalar( $self->_ReadConfig_handle_line($fh, $line) ) + ) + ) + { + return undef; + } + } - $self->_caseify(\$sect, \$parm); + return 1; +} - return undef if (! defined($self->{v}{$sect}{$parm})); - - return 1 if (! @vals); - - $self->_touch_parameter($sect, $parm); - - $self->{EOT}{$sect}{$parm} = 'EOT' if - (!defined $self->{EOT}{$sect}{$parm}); - - $self->{v}{$sect}{$parm} = [$self->{v}{$sect}{$parm}] unless - (ref($self->{v}{$sect}{$parm}) eq "ARRAY"); - - CORE::push @{$self->{v}{$sect}{$parm}}, @vals; - return 1; -} +sub ReadConfig +{ + my $self = shift; -=head2 setval ($section, $parameter, $value, [ $value2, ... ]) + @Config::IniFiles::errors = ( ); -Sets the value of parameter C<$parameter> in section C<$section> to -C<$value> (or to a set of values). See below for methods to write -the new configuration back out to a file. + # Initialize (and clear out) storage hashes + $self->{sects} = []; + $self->{parms} = {}; + $self->{group} = {}; + $self->{v} = {}; + $self->{sCMT} = {}; + $self->{pCMT} = {}; + $self->{EOT} = {}; + $self->{mysects} = []; # A pair of hashes to remember which params are loaded + $self->{myparms} = {}; # or set using the API vs. imported - useful for + $self->{peCMT} = {}; # this will store trailing comments at the end of single-line params + $self->{e} = {}; # If a section already exists + $self->{mye} = {}; # If a section already exists + # import shadowing, see below, and WriteConfig($fn, -delta=>1) -You may not set a parameter that didn't exist in the original -configuration file. B will return I if this is -attempted. See B below to do this. Otherwise, it returns 1. + if( defined $self->{imported} ) { + # Run up the import tree to the top, then reload coming + # back down, maintaining the imported file names and our + # file name. + # This is only needed on a reload though + $self->{imported}->ReadConfig() unless ($self->{firstload}); -=cut + foreach my $field (qw(sects parms group v sCMT pCMT EOT e)) { + $self->{$field} = _deepcopy($self->{imported}->{$field}); + } + } # end if -sub setval { - my $self = shift; - my $sect = shift; - my $parm = shift; - my @val = @_; + if ($self->_no_filename) + { + return 1; + } - return undef if not defined $sect; - return undef if not defined $parm; + # If this is a reload and we want warnings then send one to the STDERR log + unless( $self->{firstload} || !$self->{reloadwarn} ) { + my ($ss, $mm, $hh, $DD, $MM, $YY) = (localtime(time))[0..5]; + printf STDERR + "PID %d reloading config file %s at %d.%02d.%02d %02d:%02d:%02d\n", + $$, $self->{cf}, $YY+1900, $MM+1, $DD, $hh, $mm, $ss; + } - $self->_caseify(\$sect, \$parm); + # Turn off. Future loads are reloads + $self->{firstload} = 0; - if (defined($self->{v}{$sect}{$parm})) { - $self->_touch_parameter($sect, $parm); - if (@val > 1) { - $self->{v}{$sect}{$parm} = \@val; - $self->{EOT}{$sect}{$parm} = 'EOT'; - } else { - $self->{v}{$sect}{$parm} = shift @val; + # Get a filehandle, allowing almost any type of 'file' parameter + my $fh = $self->_make_filehandle( $self->{cf} ); + if (!$fh) { + carp "Failed to open $self->{cf}: $!"; + return undef; } - return 1; - } else { - return undef; - } -} -=head2 newval($section, $parameter, $value [, $value2, ...]) + # Get mod time of file so we can retain it (if not from STDIN) + # also check if it's a real file (could have been a filehandle made from a scalar). + if (ref($fh) ne "IO::Scalar" && -e $fh) + { + my @stats = stat $fh; + $self->{file_mode} = sprintf("%04o", $stats[2]) if defined $stats[2]; + } -Assignes a new value, C<$value> (or set of values) to the -parameter C<$parameter> in section C<$section> in the configuration -file. -=cut + # The first lines of the file must be blank, comments or start with [ + my $first = ''; -sub newval { - my $self = shift; - my $sect = shift; - my $parm = shift; - my @val = @_; + delete $self->{line_ends}; # Marks start of parsing for _nextline() - return undef if not defined $sect; - return undef if not defined $parm; + $self->_read_line_num(0); - $self->_caseify(\$sect, \$parm); + if (!defined($self->_ReadConfig_lines_loop($fh))) + { + return undef; + } - $self->AddSection($sect); + # Special case: return undef if file is empty. (suppress this line to + # restore the more intuitive behaviour of accepting empty files) + if (! keys %{$self->{v}} && ! $self->{allowempty}) { + $self->_add_error("Empty file treated as error"); + $self->_rollback($fh); + return undef; + } - if (none { $_ eq $parm } @{$self->{parms}{$sect}}) + if ( defined (my $defaultsect=$self->{startup_settings}->{-default}) ) { - CORE::push(@{$self->{parms}{$sect}}, $parm) + $self->AddSection($defaultsect); } - $self->_touch_parameter($sect, $parm); - if (@val > 1) { - $self->{v}{$sect}{$parm} = \@val; - if (!defined $self->{EOT}{$sect}{$parm}) - { - $self->{EOT}{$sect}{$parm} = 'EOT'; - } - } else { - $self->{v}{$sect}{$parm} = shift @val; - } - return 1 + $self->_SetEndComments(@{ $self->_curr_cmts }); + + $self->_rollback($fh); + return (@Config::IniFiles::errors ? undef : 1); } -=head2 delval($section, $parameter) -Deletes the specified parameter from the configuration file -=cut +sub Sections { + my $self = shift; -sub delval { + return @{_aref_or_empty($self->{sects})}; +} + + +sub SectionExists { my $self = shift; my $sect = shift; - my $parm = shift; return undef if not defined $sect; - return undef if not defined $parm; - $self->_caseify(\$sect, \$parm); - - $self->{parms}{$sect} = [grep {$_ ne $parm} @{$self->{parms}{$sect}}]; - $self->_touch_parameter($sect, $parm); - delete $self->{v}{$sect}{$parm}; + $self->_caseify(\$sect); - return 1; + return ((exists $self->{e}{$sect}) ? 1 : 0); } -=head2 ReadConfig - -Forces the configuration file to be re-read. Returns undef if the -file can not be opened, no filename was defined (with the C<-file> -option) when the object was constructed, or an error occurred while -reading. -If an error occurs while parsing the INI file the @Config::IniFiles::errors -array will contain messages that might help you figure out where the -problem is in the file. +sub _AddSection_Helper +{ + my ($self, $sect) = @_; + $self->{e}{$sect} = 1; + CORE::push @{$self->{sects}}, $sect; + $self->_touch_section($sect); -=cut + $self->SetGroupMember($sect); -# Auxillary function to make deep (aliasing-free) copies of data -# structures. Ignores blessed objects in tree (could be taught not -# to, if needed) -sub _deepcopy { - my $ref = shift; + # Set up the parameter names and values lists + $self->{parms}{$sect} ||= []; - if (! ref($ref)) { - return $ref; + if (!defined($self->{v}{$sect})) { + $self->{sCMT}{$sect} = []; + $self->{pCMT}{$sect} = {}; # Comments above parameters + $self->{parms}{$sect} = []; + $self->{v}{$sect} = {}; } - if (UNIVERSAL::isa($ref, "ARRAY")) { - return [map {_deepcopy($_)} @$ref]; - } + return; +} - if (UNIVERSAL::isa($ref, "HASH")) { - my $return = {}; - foreach my $k (keys %$ref) { - $return->{$k} = _deepcopy($ref->{$k}); - } - return $return; +sub AddSection { + my ($self, $sect) = @_; + + return undef if not defined $sect; + + $self->_caseify(\$sect); + + if ( $self->SectionExists($sect)) + { + return; } - carp "Unhandled data structure in $ref, cannot _deepcopy()"; + return $self->_AddSection_Helper($sect); } -# Internal method, gets the next line, taking proper care of line endings. -sub _nextline { - my ($self, $fh) = @_; - local $_; - if (!exists $self->{line_ends}) { - # no $self->{line_ends} is a hint set by caller that we are at - # the first line (kludge kludge). - { - local $/=\1; - my $nextchar; - do { - $nextchar=<$fh>; - return undef if (!defined $nextchar); - $_ .= $nextchar; - } until (m/((\015|\012|\025|\n)$)/s); - $self->{line_ends}=$1; - if ($nextchar eq "\x0d") { - # peek at the next char - $nextchar = <$fh>; - if ($nextchar eq "\x0a") { - $self->{line_ends} .= "\x0a"; - } else { - seek $fh, -1, SEEK_CUR(); - } - } - } +# Marks a section as modified by us (this includes deleted by us). +sub _touch_section { + my ($self, $sect) = @_; - # If there's a UTF BOM (Byte-Order-Mark) in the first - # character of the first line then remove it before processing - # ( http://www.unicode.org/unicode/faq/utf_bom.html#22 ) - s/^//; + $self->{mysects} ||= []; - return $_; - } else { - local $/=$self->{line_ends}; - return scalar <$fh>; + unless (exists $self->{mye}{$sect}) + { + CORE::push @{$self->{mysects}}, $sect; + $self->{mye}{$sect} = 1; } + + return; } -# Internal method, closes or resets the file handle. To be called -# whenever ReadConfig() returns. -sub _rollback { - my ($self, $fh) = @_; - # Only close if this is a filename, if it's - # an open handle, then just roll back to the start - if( !ref($self->{cf}) ) { - close($fh); - } else { - # Attempt to rollback to beginning, no problem if this fails (e.g. STDIN) - seek( $fh, 0, SEEK_SET() ); - } # end if -} +# Marks a parameter as modified by us (this includes deleted by us). +sub _touch_parameter { + my ($self, $sect, $parm) = @_; -sub _no_filename -{ - my $self = shift; + $self->_touch_section($sect); + return if (!exists $self->{v}{$sect}); + $self->{myparms}{$sect} ||= []; - my $fn = $self->{cf}; + if (! $self->_is_parm_in_sect($sect, $parm)) + { + CORE::push @{$self->{myparms}{$sect}}, $parm; + } - return (not (defined($fn) && length($fn))); + return; } -sub _read_line_num -{ - my $self = shift; - if (@_) - { - $self->{_read_line_num} = shift; - } - return $self->{_read_line_num}; -} +sub DeleteSection { + my $self = shift; + my $sect = shift; -# Reads the next line and removes the end of line from it. -sub _read_next_line -{ - my ($self, $fh) = @_; + return undef if not defined $sect; - my $line = $self->_nextline($fh); + $self->_caseify(\$sect); - if (! defined($line)) - { - return undef; - } + # This is done the fast way, change if data structure changes!! + delete $self->{v}{$sect}; + delete $self->{sCMT}{$sect}; + delete $self->{pCMT}{$sect}; + delete $self->{EOT}{$sect}; + delete $self->{parms}{$sect}; + delete $self->{myparms}{$sect}; + delete $self->{e}{$sect}; - $self->_read_line_num( $self->_read_line_num() + 1); + $self->{sects} = [grep {$_ ne $sect} @{$self->{sects}}]; + $self->_touch_section($sect); - # Remove line ending char(s) - $line =~ s/(\015\012?|\012|\025|\n)\z//; + $self->RemoveGroupMember($sect); - return $line; -} + return 1; +} # end DeleteSection -sub _add_error -{ - my ($self, $msg) = @_; - CORE::push(@Config::IniFiles::errors, $msg); +sub RenameSection { + my $self = shift; + my $old_sect = shift; + my $new_sect = shift; + my $include_groupmembers = shift; + return undef unless $self->CopySection($old_sect,$new_sect,$include_groupmembers); + return $self->DeleteSection($old_sect); - return; -} +} # end RenameSection -# The current section - used for parsing. -sub _curr_sect -{ + +sub CopySection { my $self = shift; + my $old_sect = shift; + my $new_sect = shift; + my $include_groupmembers = shift; - if (@_) - { - $self->{_curr_sect} = shift; + if (not defined $old_sect or + not defined $new_sect or + !$self->SectionExists($old_sect) or + $self->SectionExists($new_sect)) { + return undef; } - return $self->{_curr_sect}; -} + $self->_caseify(\$new_sect); + $self->_AddSection_Helper($new_sect); -# The current parameter - used for parsing. -sub _curr_parm -{ - my $self = shift; + # This is done the fast way, change if data structure changes!! + foreach my $key (qw(v sCMT pCMT EOT parms myparms e)) { + next unless exists $self->{$key}{$old_sect}; + $self->{$key}{$new_sect} = Config::IniFiles::_deepcopy($self->{$key}{$old_sect}); + } - if (@_) - { - $self->{_curr_parm} = shift; + if($include_groupmembers) { + foreach my $old_groupmember ($self->GroupMembers($old_sect)) { + my $new_groupmember = $old_groupmember; + $new_groupmember =~ s/\A\Q$old_sect\E/$new_sect/; + $self->CopySection($old_groupmember,$new_groupmember); + } } - return $self->{_curr_parm}; -} + return 1; +} # end CopySection -# Current location - section and parameter. -sub _curr_loc + +sub _aref_or_empty { - my $self = shift; + my ($aref) = @_; - return ($self->_curr_sect, $self->_curr_parm); + return ((defined($aref) and ref($aref) eq 'ARRAY') ? $aref : []); } -# The current value - used in parsing. -sub _curr_val -{ +sub Parameters { my $self = shift; + my $sect = shift; - if (@_) - { - $self->{_curr_val} = shift; - } + return undef if not defined $sect; - return $self->{_curr_val}; + $self->_caseify(\$sect); + + return @{_aref_or_empty($self->{parms}{$sect})}; } -sub _curr_cmts + +sub Groups { my $self = shift; - if (@_) + if (ref($self->{group}) eq 'HASH') { - $self->{_curr_cmts} = shift; + return keys %{$self->{group}}; } - - return $self->{_curr_cmts}; -} - -sub _curr_end_comment -{ - my $self = shift; - - if (@_) + else { - $self->{_curr_end_comment} = shift; + return (); } - - return $self->{_curr_end_comment}; } -my $RET_CONTINUE = 1; -my $RET_BREAK; -sub _ReadConfig_handle_comment +sub _group_member_handling_skeleton { - my ($self, $line) = @_; + my ($self, $sect, $method) = @_; - if ($self->{negativedeltas} and - my ($to_delete) = $line =~ m/\A$self->{comment_char} (.*) is deleted\z/ - ) + return undef if not defined $sect; + + if (! (my ($group) = ($sect =~ /\A(\S+)\s+\S/))) { - if (my ($sect) = $to_delete =~ m/\A\[(.*)\]\z/) - { - $self->DeleteSection($sect); - } - else - { - $self->delval($self->_curr_sect, $to_delete); - } + return 1; } else { - CORE::push(@{$self->_curr_cmts}, $line); + return $self->$method($sect, $group); } - - return $RET_CONTINUE; } -sub _ReadConfig_new_section +sub _SetGroupMember_helper { - my ($self, $sect) = @_; + my ($self, $sect, $group) = @_; - $self->_caseify(\$sect); + if (not exists($self->{group}{$group})) { + $self->{group}{$group} = []; + } - $self->_curr_sect($sect); - $self->AddSection($self->_curr_sect); - $self->SetSectionComment($self->_curr_sect, @{$self->_curr_cmts}); - $self->_curr_cmts([]); + if (none {$_ eq $sect} @{$self->{group}{$group}}) { + CORE::push @{$self->{group}{$group}}, $sect; + } - return $RET_CONTINUE; + return; } -sub _handle_fallback_sect -{ - my ($self) = @_; - - if ((!defined($self->_curr_sect)) and defined($self->{fallback})) - { - $self->_curr_sect($self->{fallback}); - $self->{fallback_used}++; - } +sub SetGroupMember { + my ($self, $sect) = @_; - return; + return $self->_group_member_handling_skeleton($sect, '_SetGroupMember_helper'); } -sub _ReadConfig_load_value + +sub _RemoveGroupMember_helper { - my ($self, $val_aref) = @_; + my ($self, $sect, $group) = @_; - # Now load value - if (exists $self->{v}{$self->_curr_sect}{$self->_curr_parm} && - exists $self->{myparms}{$self->_curr_sect} && - $self->_is_parm_in_sect($self->_curr_loc)) - { - $self->push($self->_curr_loc, @$val_aref); - } - else + if (!exists $self->{group}{$group}) { - # Loaded parameters shadow imported ones, instead of appending - # to them - $self->newval($self->_curr_loc, @$val_aref); + return; } + $self->{group}{$group} = + [grep { $_ ne $sect } @{$self->{group}{$group}}]; + return; } -sub _test_for_fallback_or_no_sect +sub RemoveGroupMember { - my ($self, $fh) = @_; + my ($self, $sect) = @_; - $self->_handle_fallback_sect; + return $self->_group_member_handling_skeleton($sect, '_RemoveGroupMember_helper'); +} - if (!defined $self->_curr_sect) { - $self->_add_error( - sprintf('%d: %s', $self->_read_line_num(), - qq#parameter found outside a section# - ) - ); - $self->_rollback($fh); - return $RET_BREAK; + +sub GroupMembers { + my ($self, $group) = @_; + + return undef if not defined $group; + + $self->_caseify(\$group); + + return @{_aref_or_empty($self->{group}{$group})}; +} + + +sub SetWriteMode +{ + my ($self, $mode) = @_; + + if (not (defined($mode) && ($mode =~ m/[0-7]{3}/))) + { + return undef; } - return $RET_CONTINUE; + return ($self->{file_mode} = $mode); } -sub _ReadConfig_handle_here_doc_param + +sub GetWriteMode { - my ($self, $fh, $eotmark, $val_aref) = @_; + my $self = shift; - my $foundeot = 0; - my $startline = $self->_read_line_num(); + return $self->{file_mode}; +} - HERE_DOC_LOOP: - while (defined( my $line = $self->_read_next_line($fh) )) - { - if ($line eq $eotmark) + +sub _write_config_to_filename +{ + my ($self, $filename, %parms) = @_; + + if (-e $filename) { + if (not (-w $filename)) { - $foundeot = 1; - last HERE_DOC_LOOP; + #carp "File $filename is not writable. Refusing to write config"; + return undef; } - else + my $mode = (stat $filename)[2]; + $self->{file_mode} = sprintf "%04o", ($mode & 0777); + #carp "Using mode $self->{file_mode} for file $file"; + } + + my ($fh, $new_file); + + # We need to trap the exception that tempfile() may throw and instead + # carp() and return undef() because that was the previous behaviour: + # + # See RT #77039 ( https://rt.cpan.org/Ticket/Display.html?id=77039 ) + eval { + ($fh, $new_file) = tempfile( + "temp.ini-XXXXXXXXXX", + DIR => dirname($filename) + ); + + # Convert the filehandle to a "text" filehandle suitable for use + # on Windows (and other platforms). + # + # This may break compatibility for ultra-old perls (ones before 5.6.0) + # so I say - Good Riddance! + if ($^O =~ m/\AMSWin/) { - # Untaint - my ($contents) = $line =~ /(.*)/ms; - CORE::push(@$val_aref, $contents); + binmode $fh, ':crlf'; } - } + }; - if (! $foundeot) + if ($@) { - $self->_add_error(sprintf('%d: %s', $startline, - qq#no end marker ("$eotmark") found#)); - $self->_rollback(); - return $RET_BREAK; + carp( "Unable to write temp config file: $!" ); + return undef; } - return $RET_CONTINUE; + $self->OutputConfigToFileHandle($fh, $parms{-delta}); + close($fh); + if (!rename( $new_file, $filename )) { + carp "Unable to rename temp config file ($new_file) to ${filename}: $!"; + return undef; + } + if (exists $self->{file_mode}) { + chmod oct($self->{file_mode}), $filename; + } + + return 1; } -sub _ReadConfig_handle_non_here_doc_param +sub _write_config_with_a_made_fh { - my ($self, $fh, $val_aref) = @_; - - my $allCmt = $self->{allowed_comment_char}; - my $end_commenthandle = $self->{handle_trailing_comment}; - - # process continuation lines, if any - $self->_process_continue_val($fh); + my ($self, $fh, %parms) = @_; - # we should split value and comments if there is any comment - if ($end_commenthandle and - my ($value_to_assign, $end_comment_to_assign) = $self->_curr_val =~ /(.*?)\s*[$allCmt]\s*(.*)$/) + # Only roll back if it's not STDIN (if it is, Carp) + if( $fh == \*STDIN ) { - $self->_curr_val($value_to_assign); - $self->_curr_end_comment($end_comment_to_assign); + carp "Cannot write configuration file to STDIN."; } else { - $self->_curr_end_comment(q{}); - } - - @{$val_aref} = ($self->_curr_val); + seek( $fh, 0, SEEK_SET() ); + # Make sure to keep the previous junk out. + # See: + # https://rt.cpan.org/Public/Bug/Display.html?id=103496 + truncate( $fh, 0 ); + $self->OutputConfigToFileHandle($fh, $parms{-delta}); + seek( $fh, 0, SEEK_SET() ); + } # end if - return; + return 1; } - -sub _ReadConfig_populate_values +sub _write_config_to_fh { - my ($self, $val_aref, $eotmark) = @_; + my ($self, $file, %parms) = @_; - $self->_ReadConfig_load_value($val_aref); + # Get a filehandle, allowing almost any type of 'file' parameter + ## NB: If this were a filename, this would fail because _make_file + ## opens a read-only handle, but we have already checked that case + ## so re-using the logic is ok [JW/WADG] + my $fh = $self->_make_filehandle( $file ); - $self->SetParameterComment($self->_curr_loc, @{ $self->_curr_cmts }); - $self->_curr_cmts([]); - if (defined $eotmark) - { - $self->SetParameterEOT($self->_curr_loc, $eotmark); + if (!$fh) { + carp "Could not find a filehandle for the input stream ($file): $!"; + return undef; } - # if handle_trailing_comment is off, this line makes no sense, since all $end_comment="" - $self->SetParameterTrailingComment($self->_curr_loc, $self->_curr_end_comment); - return; + return $self->_write_config_with_a_made_fh($fh, %parms); } -sub _ReadConfig_param_assignment -{ - my ($self, $fh, $line, $parm, $value_to_assign) = @_; +sub WriteConfig { + my ($self, $file, %parms) = @_; - $self->_curr_val($value_to_assign); - $self->_curr_end_comment(undef()); + return undef unless defined $file; - if (!defined( $self->_test_for_fallback_or_no_sect($fh) )) + # If we are using a filename, then do mode checks and write to a + # temporary file to avoid a race condition + if( !ref($file) ) { - return $RET_BREAK; + return $self->_write_config_to_filename($file, %parms); } + # Otherwise, reset to the start of the file and write, unless we are using + # STDIN + else + { + return $self->_write_config_to_fh($file, %parms); + } +} - $self->_caseify(\$parm); - $self->_curr_parm($parm); - my @val = ( ); - my $eotmark; +sub RewriteConfig { + my $self = shift; - if (($eotmark) = $self->_curr_val =~ /\A<<(.*)$/) - { - if (! defined($self->_ReadConfig_handle_here_doc_param( - $fh, $eotmark, \@val - )) - ) - { - return $RET_BREAK; - } - } - else + if ($self->_no_filename) { - $self->_ReadConfig_handle_non_here_doc_param( $fh, \@val ); + return 1; } - $self->_ReadConfig_populate_values(\@val, $eotmark); - - return $RET_CONTINUE; + return $self->WriteConfig($self->{cf}); } -# Return 1 to continue - undef to terminate the loop. -sub _ReadConfig_handle_line + +sub GetFileName { - my ($self, $fh, $line) = @_; + my $self = shift; - my $allCmt = $self->{allowed_comment_char}; + return $self->{cf}; +} - # ignore blank lines - if ($line =~ /\A\s*\z/) - { - return $RET_CONTINUE; - } - # collect comments - if ($line =~/\A\s*[$allCmt]/) - { - return $self->_ReadConfig_handle_comment($line); - } +sub SetFileName { + my ($self, $new_filename) = @_; - # New Section - if (my ($sect) = $line =~ /\A\s*\[\s*(\S|\S.*\S)\s*\]\s*\z/) - { - return $self->_ReadConfig_new_section($sect); + if ( length($new_filename) > 0 ) { + return ($self->{cf} = $new_filename); } - - # New parameter - if (my ($parm, $value_to_assign) = $line =~ /^\s*([^=]*?[^=\s])\s*=\s*(.*)$/) - { - return $self->_ReadConfig_param_assignment($fh, $line, $parm, $value_to_assign); + else { + return undef; } - - $self->_add_error( - sprintf("Line %d in file %s is mal-formed:\n\t\%s", - $self->_read_line_num(), $self->GetFileName(), $line - ) - ); - - return $RET_CONTINUE; } -sub _ReadConfig_lines_loop + +sub _calc_eot_mark { - my ($self, $fh) = @_; + my ($self, $sect, $parm, $val) = @_; - $self->_curr_sect(undef()); - $self->_curr_parm(undef()); - $self->_curr_val(undef()); - $self->_curr_cmts([]); + my $eotmark = $self->{EOT}{$sect}{$parm} || 'EOT'; - while ( defined(my $line = $self->_read_next_line($fh)) ) + # Make sure the $eotmark does not occur inside the string. + my @letters = ('A' .. 'Z'); + my $joined_val = join(q{ }, @$val); + while (index($joined_val, $eotmark) >= 0) { - if (!defined( - scalar( $self->_ReadConfig_handle_line($fh, $line) ) - ) - ) - { - return undef; - } + $eotmark .= $letters[rand(@letters)]; } - return 1; + return $eotmark; } -sub ReadConfig -{ - my $self = shift; - - @Config::IniFiles::errors = ( ); - - # Initialize (and clear out) storage hashes - $self->{sects} = []; - $self->{parms} = {}; - $self->{group} = {}; - $self->{v} = {}; - $self->{sCMT} = {}; - $self->{pCMT} = {}; - $self->{EOT} = {}; - $self->{mysects} = []; # A pair of hashes to remember which params are loaded - $self->{myparms} = {}; # or set using the API vs. imported - useful for - $self->{peCMT} = {}; # this will store trailing comments at the end of single-lined params - $self->{e} = {}; # If a section is already exists - $self->{mye} = {}; # If a section is already exists - # import shadowing, see below, and WriteConfig($fn, -delta=>1) +sub _OutputParam { + my ($self, $sect, $parm, $val, $end_comment, $output_cb) = @_; - if( defined $self->{imported} ) { - # Run up the import tree to the top, then reload coming - # back down, maintaining the imported file names and our - # file name. - # This is only needed on a re-load though - $self->{imported}->ReadConfig() unless ($self->{firstload}); + my $line_loop = sub { + my ($mapper) = @_; - foreach my $field (qw(sects parms group v sCMT pCMT EOT e)) { - $self->{$field} = _deepcopy($self->{imported}->{$field}); + foreach my $line (@{$val}[0 .. $#$val-1]) { + $output_cb->($mapper->($line)); } - } # end if + $output_cb->( + $mapper->($val->[-1]), + ($end_comment ? (" $self->{comment_char} $end_comment") : ()), + ); + return; + }; - if ($self->_no_filename) - { - return 1; + if (! @$val) { + # An empty variable - see: + # https://rt.cpan.org/Public/Bug/Display.html?id=68554 + $output_cb->("$parm="); + } + elsif ((@$val == 1) or $self->{nomultiline}) { + $line_loop->(sub { my ($line) = @_; return "$parm=$line"; }); } + else + { + my $eotmark = $self->_calc_eot_mark($sect, $parm, $val); - # If this is a reload and we want warnings then send one to the STDERR log - unless( $self->{firstload} || !$self->{reloadwarn} ) { - my ($ss, $mm, $hh, $DD, $MM, $YY) = (localtime(time))[0..5]; - printf STDERR - "PID %d reloading config file %s at %d.%02d.%02d %02d:%02d:%02d\n", - $$, $self->{cf}, $YY+1900, $MM+1, $DD, $hh, $mm, $ss; + $output_cb->("$parm= <<$eotmark"); + $line_loop->(sub { my ($line) = @_; return $line; }); + $output_cb->($eotmark); } - # Turn off. Future loads are reloads - $self->{firstload} = 0; + return; +} - # Get a filehandle, allowing almost any type of 'file' parameter - my $fh = $self->_make_filehandle( $self->{cf} ); - if (!$fh) { - carp "Failed to open $self->{cf}: $!"; - return undef; - } +sub OutputConfig { + my ($self, $delta) = @_; - # Get mod time of file so we can retain it (if not from STDIN) - # also check if it's a real file (could have been a filehandle made from a scalar). - if (ref($fh) ne "IO::Scalar" && -e $fh) - { - my @stats = stat $fh; - $self->{file_mode} = sprintf("%04o", $stats[2]) if defined $stats[2]; - } + return $self->OutputConfigToFileHandle(select(), $delta); +} +sub _output_comments +{ + my ($self, $print_line, $comments_aref) = @_; - # The first lines of the file must be blank, comments or start with [ - my $first = ''; + if (ref($comments_aref) eq 'ARRAY') { + foreach my $comment (@$comments_aref) { + $print_line->($comment); + } + } - delete $self->{line_ends}; # Marks start of parsing for _nextline() + return; +} - $self->_read_line_num(0); +sub _process_continue_val +{ + my ($self, $fh) = @_; - if (!defined($self->_ReadConfig_lines_loop($fh))) + if (not $self->{allowcontinue}) { - return undef; + return; } - # Special case: return undef if file is empty. (suppress this line to - # restore the more intuitive behaviour of accepting empty files) - if (! keys %{$self->{v}} && ! $self->{allowempty}) { - $self->_add_error("Empty file treated as error"); - $self->_rollback($fh); - return undef; - } + my $val = $self->_curr_val; - if ( defined (my $defaultsect=$self->{startup_settings}->{-default}) ) - { - $self->AddSection($defaultsect); + while($val =~ s/\\\z//) { + $val .= $self->_read_next_line($fh); } - $self->_SetEndComments(@{ $self->_curr_cmts }); + $self->_curr_val($val); - $self->_rollback($fh); - return (@Config::IniFiles::errors ? undef : 1); + return; } +sub _output_param_total +{ + my ($self, $sect, $parm, $print_line, $split_val, $delta) = @_; + if (!defined $self->{v}{$sect}{$parm}) { + if ($delta) { + $print_line->("$self->{comment_char} $parm is deleted"); + } + else { + warn "Weird unknown parameter $parm" if $^W; + } + return; + } -=head2 Sections + $self->_output_comments($print_line, $self->{pCMT}{$sect}{$parm}); -Returns an array containing section names in the configuration file. -If the I option was turned on when the config object was -created, the section names will be returned in lowercase. + my $val = $self->{v}{$sect}{$parm}; + my $end_comment = $self->{peCMT}{$sect}{$parm}; -=cut + return if ! defined ($val); # No parameter exists !! -sub Sections { - my $self = shift; + $self->_OutputParam( + $sect, + $parm, + $split_val->($val), + (defined($end_comment) ? $end_comment : ""), + $print_line, + ); - return @{_aref_or_empty($self->{sects})}; + return; } -=head2 SectionExists ( $sect_name ) - -Returns 1 if the specified section exists in the INI file, 0 otherwise (undefined if section_name is not defined). - -=cut +sub _output_section { + my ($self, $sect, $print_line, $split_val, $delta, $position) = @_; -sub SectionExists { - my $self = shift; - my $sect = shift; + if (!defined $self->{v}{$sect}) { + if ($delta) { + $print_line->("$self->{comment_char} [$sect] is deleted"); + } else { + warn "Weird unknown section $sect" if $^W; + } + return; + } + return if not defined $self->{v}{$sect}; + $print_line->() if ($position > 0); + $self->_output_comments($print_line, $self->{sCMT}{$sect}); - return undef if not defined $sect; + if (! + ($self->{fallback_used} and $sect eq $self->{fallback}) + ) + { + $print_line->("[$sect]"); + } + return if ref($self->{v}{$sect}) ne 'HASH'; - $self->_caseify(\$sect); + foreach my $parm (@{$self->{$delta ? "myparms" : "parms"}{$sect}}) { + $self->_output_param_total( + $sect, $parm, $print_line, $split_val, $delta + ); + } - return ((exists $self->{e}{$sect}) ? 1 : 0); + return; } -=head2 AddSection ( $sect_name ) - -Ensures that the named section exists in the INI file. If the section already -exists, nothing is done. In this case, the "new" section will possibly contain -data already. - -If you really need to have a new section with no parameters in it, check that -the name that you're adding isn't in the list of sections already. - -=cut +sub OutputConfigToFileHandle { + # We need no strict 'refs' to be able to print to $fh if it points + # to a glob filehandle. + no strict 'refs'; + my ($self, $fh, $delta) = @_; -sub _AddSection_Helper -{ - my ($self, $sect) = @_; - $self->{e}{$sect} = 1; - CORE::push @{$self->{sects}}, $sect; - $self->_touch_section($sect); + my $ors = $self->{line_ends} || $\ || "\n"; # $\ is normally unset, but use input by default + my $print_line = sub { + print {$fh} (@_, $ors) + or die "Config-IniFiles cannot print to filehandle (out-of-space?). Aborting!"; + return; + }; + my $split_val = sub { + my ($val) = @_; - $self->SetGroupMember($sect); + return ((ref($val) eq 'ARRAY') + ? $val + : [split /[$ors]/, $val, -1] + ); + }; - # Set up the parameter names and values lists - $self->{parms}{$sect} ||= []; + my $position = 0; - if (!defined($self->{v}{$sect})) { - $self->{sCMT}{$sect} = []; - $self->{pCMT}{$sect} = {}; # Comments above parameters - $self->{parms}{$sect} = []; - $self->{v}{$sect} = {}; + foreach my $sect (@{$self->{$delta ? "mysects" : "sects"}}) { + $self->_output_section( + $sect, $print_line, $split_val, $delta, $position++ + ); } - return; + $self->_output_comments($print_line, [ $self->_GetEndComments() ] ); + + return 1; } -sub AddSection { - my ($self, $sect) = @_; - return undef if not defined $sect; +sub SetSectionComment +{ + my ($self, $sect, @comment) = @_; + + if (not (defined($sect) && @comment)) + { + return undef; + } $self->_caseify(\$sect); - if ( $self->SectionExists($sect)) - { - return; - } + $self->_touch_section($sect); + # At this point it's possible to have a comment for a section that + # doesn't exist. This comment will not get written to the INI file. + $self->{sCMT}{$sect} = $self->_markup_comments(\@comment); - return $self->_AddSection_Helper($sect); + return scalar @comment; } -# Marks a section as modified by us (this includes deleted by us). -sub _touch_section { - my ($self, $sect) = @_; - $self->{mysects} ||= []; - unless (exists $self->{mye}{$sect}) - { - CORE::push @{$self->{mysects}}, $sect; - $self->{mye}{$sect} = 1; - } +# this helper makes sure that each line is preceded with the correct comment +# character +sub _markup_comments +{ + my ($self, $comment_aref) = @_; - return; + my $allCmt = $self->{allowed_comment_char}; + my $cmtChr = $self->{comment_char}; + + my $is_comment = qr/\A\s*[$allCmt]/; + + # TODO : Maybe create a qr// out of it. + return [map { ($_ =~ $is_comment) ? $_ : "$cmtChr $_" } @$comment_aref]; } -# Marks a parameter as modified by us (this includes deleted by us). -sub _touch_parameter { - my ($self, $sect, $parm) = @_; - $self->_touch_section($sect); - return if (!exists $self->{v}{$sect}); - $self->{myparms}{$sect} ||= []; - if (! $self->_is_parm_in_sect($sect, $parm)) - { - CORE::push @{$self->{myparms}{$sect}}, $parm; - } - return; +sub _return_comment +{ + my ($self, $comment_aref) = @_; + + my $delim = defined($/) ? $/ : "\n"; + + return wantarray() ? @$comment_aref : join($delim, @$comment_aref); } +sub GetSectionComment +{ + my ($self, $sect) = @_; -=head2 DeleteSection ( $sect_name ) + return undef if not defined $sect; -Completely removes the entire section from the configuration. + $self->_caseify(\$sect); -=cut + if (! exists $self->{sCMT}{$sect}) { + return undef; + } -sub DeleteSection { + return $self->_return_comment( $self->{sCMT}{$sect} ); +} + + +sub DeleteSectionComment +{ my $self = shift; my $sect = shift; return undef if not defined $sect; $self->_caseify(\$sect); + $self->_touch_section($sect); - # This is done the fast way, change if data structure changes!! - delete $self->{v}{$sect}; delete $self->{sCMT}{$sect}; - delete $self->{pCMT}{$sect}; - delete $self->{EOT}{$sect}; - delete $self->{parms}{$sect}; - delete $self->{myparms}{$sect}; - delete $self->{e}{$sect}; - $self->{sects} = [grep {$_ ne $sect} @{$self->{sects}}]; - $self->_touch_section($sect); + return; +} - $self->RemoveGroupMember($sect); - return 1; -} # end DeleteSection +sub SetParameterComment +{ + my ($self, $sect, $parm, @comment) = @_; -=head2 RenameSection ( $old_section_name, $new_section_name, $include_groupmembers) + if (not (defined($sect) && defined($parm) && @comment)) + { + return undef; + } -Renames a section if it does not already exists optionally including groupmembers + $self->_caseify(\$sect, \$parm); -=cut + $self->_touch_parameter($sect, $parm); -sub RenameSection { - my $self = shift; - my $old_sect = shift; - my $new_sect = shift; - my $include_groupmembers = shift; - return undef unless $self->CopySection($old_sect,$new_sect,$include_groupmembers); - return $self->DeleteSection($old_sect); + # Note that at this point, it's possible to have a comment for a parameter, + # without that parameter actually existing in the INI file. + $self->{pCMT}{$sect}{$parm} = $self->_markup_comments(\@comment); -} # end RenameSection + return scalar @comment; +} -=head2 CopySection ( $old_section_name, $new_section_name, $include_groupmembers) +sub _SetEndComments +{ + my $self = shift; + my @comments = @_; -Copies one section to another optionally including groupmembers + $self->{_comments_at_end_of_file} = \@comments; -=cut + return 1; +} -sub CopySection { +sub _GetEndComments { my $self = shift; - my $old_sect = shift; - my $new_sect = shift; - my $include_groupmembers = shift; - if (not defined $old_sect or - not defined $new_sect or - !$self->SectionExists($old_sect) or - $self->SectionExists($new_sect)) { - return undef; - } + return @{$self->{_comments_at_end_of_file}}; +} - $self->_caseify(\$new_sect); - $self->_AddSection_Helper($new_sect); - # This is done the fast way, change if data structure changes!! - foreach my $key (qw(v sCMT pCMT EOT parms myparms e)) { - next unless exists $self->{$key}{$old_sect}; - $self->{$key}{$new_sect} = Config::IniFiles::_deepcopy($self->{$key}{$old_sect}); - } +sub GetParameterComment +{ + my ($self, $sect, $parm) = @_; - if($include_groupmembers) { - foreach my $old_groupmember ($self->GroupMembers($old_sect)) { - my $new_groupmember = $old_groupmember; - $new_groupmember =~ s/\A\Q$old_sect\E/$new_sect/; - $self->CopySection($old_groupmember,$new_groupmember); - } + if (not (defined($sect) && defined($parm))) + { + return undef; } - return 1; -} # end CopySection + $self->_caseify(\$sect, \$parm); -=head2 Parameters ($sect_name) + if (not (exists( $self->{pCMT}{$sect} ) + && exists( $self->{pCMT}{$sect}{$parm} ))) + { + return undef; + } -Returns an array containing the parameters contained in the specified -section. + return $self->_return_comment( $self->{pCMT}{$sect}{$parm} ); +} -=cut -sub _aref_or_empty +sub DeleteParameterComment { - my ($aref) = @_; - - return ((defined($aref) and ref($aref) eq 'ARRAY') ? $aref : []); -} + my ($self, $sect, $parm) = @_; -sub Parameters { - my $self = shift; - my $sect = shift; + if (not (defined($sect) && defined($parm))) + { + return undef; + } - return undef if not defined $sect; + $self->_caseify(\$sect, \$parm); - $self->_caseify(\$sect); + # If the parameter doesn't exist, our goal has already been achieved + if ( exists( $self->{pCMT}{$sect} ) + && exists( $self->{pCMT}{$sect}{$parm} )) + { + $self->_touch_parameter($sect, $parm); + delete $self->{pCMT}{$sect}{$parm}; + } - return @{_aref_or_empty($self->{parms}{$sect})}; + return 1; } -=head2 Groups -Returns an array containing the names of available groups. +sub GetParameterEOT +{ + my ($self, $sect, $parm) = @_; -Groups are specified in the config file as new sections of the form + if (not (defined($sect) && defined($parm))) + { + return undef; + } - [GroupName MemberName] + $self->_caseify(\$sect, \$parm); -This is useful for building up lists. Note that parameters within a -"member" section are referenced normally (i.e., the section name is -still "Groupname Membername", including the space) - the concept of -Groups is to aid people building more complex configuration files. + return $self->{EOT}{$sect}{$parm}; +} -=cut -sub Groups +sub SetParameterEOT { - my $self = shift; + my ($self, $sect, $parm, $EOT) = @_; - if (ref($self->{group}) eq 'HASH') - { - return keys %{$self->{group}}; - } - else + if (not (defined($sect) && defined($parm) && defined($EOT))) { - return (); + return undef; } -} -=head2 SetGroupMember ( $sect ) + $self->_caseify(\$sect, \$parm); -Makes sure that the specified section is a member of the appropriate group. + $self->_touch_parameter($sect, $parm); -Only intended for use in newval. + $self->{EOT}{$sect}{$parm} = $EOT; -=cut + return; +} -sub _group_member_handling_skeleton -{ - my ($self, $sect, $method) = @_; - return undef if not defined $sect; +sub DeleteParameterEOT +{ + my ($self, $sect, $parm) = @_; - if (! (my ($group) = ($sect =~ /\A(\S+)\s+\S/))) - { - return 1; - } - else + if (not (defined($sect) && defined($parm))) { - return $self->$method($sect, $group); + return undef; } -} -sub _SetGroupMember_helper -{ - my ($self, $sect, $group) = @_; + $self->_caseify(\$sect, \$parm); - if (not exists($self->{group}{$group})) { - $self->{group}{$group} = []; - } - - if (none {$_ eq $sect} @{$self->{group}{$group}}) { - CORE::push @{$self->{group}{$group}}, $sect; - } + $self->_touch_parameter($sect, $parm); + delete $self->{EOT}{$sect}{$parm}; return; } -sub SetGroupMember { - my ($self, $sect) = @_; - return $self->_group_member_handling_skeleton($sect, '_SetGroupMember_helper'); -} +sub SetParameterTrailingComment +{ + my ($self, $sect, $parm, $cmt) = @_; -=head2 RemoveGroupMember ( $sect ) + if (not (defined($sect) && defined($parm) && defined($cmt))) + { + return undef; + } -Makes sure that the specified section is no longer a member of the -appropriate group. Only intended for use in DeleteSection. + $self->_caseify(\$sect, \$parm); -=cut + # confirm the parameter exist + return undef if not exists $self->{v}{$sect}{$parm}; -sub _RemoveGroupMember_helper + $self->_touch_parameter($sect, $parm); + $self->{peCMT}{$sect}{$parm} = $cmt; + + return 1; +} + + +sub GetParameterTrailingComment { - my ($self, $sect, $group) = @_; + my ($self, $sect, $parm) = @_; - if (!exists $self->{group}{$group}) + if (not (defined($sect) && defined($parm))) { - return; + return undef; } - $self->{group}{$group} = - [grep { $_ ne $sect } @{$self->{group}{$group}}]; + $self->_caseify(\$sect, \$parm); - return; + # confirm the parameter exist + return undef if not exists $self->{v}{$sect}{$parm}; + return $self->{peCMT}{$sect}{$parm}; } -sub RemoveGroupMember -{ - my ($self, $sect) = @_; - return $self->_group_member_handling_skeleton($sect, '_RemoveGroupMember_helper'); -} +sub Delete { + my $self = shift; -=head2 GroupMembers ($group) + foreach my $section ($self->Sections()) { + $self->DeleteSection($section); + } -Returns an array containing the members of specified $group. Each element -of the array is a section name. For example, given the sections + return 1; +} # end Delete - [Group Element 1] - ... - [Group Element 2] - ... -GroupMembers would return ("Group Element 1", "Group Element 2"). -=cut +############################################################ +# +# TIEHASH Methods +# +# Description: +# These methods allow you to tie a hash to the +# Config::IniFiles object. Note that, when tied, the +# user wants to look at thinks like $ini{sec}{parm}, but the +# TIEHASH only provides one level of hash interface, so the +# root object gets asked for a $ini{sec}, which this +# implements. To further tie the {parm} hash, the internal +# class Config::IniFiles::_section, is provided, below. +# +############################################################ +# ---------------------------------------------------------- +# Date Modification Author +# ---------------------------------------------------------- +# 2000May09 Created method JW +# ---------------------------------------------------------- +sub TIEHASH { + my $class = shift; + my %parms = @_; -sub GroupMembers { - my ($self, $group) = @_; + # Get a new object + my $self = $class->new( %parms ); - return undef if not defined $group; + return $self; +} # end TIEHASH - $self->_caseify(\$group); - return @{_aref_or_empty($self->{group}{$group})}; -} +# ---------------------------------------------------------- +# Date Modification Author +# ---------------------------------------------------------- +# 2000May09 Created method JW +# ---------------------------------------------------------- +sub FETCH { + my $self = shift; + my( $key ) = @_; -=head2 SetWriteMode ($mode) + $self->_caseify(\$key); + return if (! $self->{v}{$key}); -Sets the mode (permissions) to use when writing the INI file. + my %retval; + tie %retval, 'Config::IniFiles::_section', $self, $key; + return \%retval; -$mode must be a string representation of the octal mode. +} # end FETCH -=cut +# ---------------------------------------------------------- +# Date Modification Author +# ---------------------------------------------------------- +# 2000Jun14 Fixed bug where wrong ref was saved JW +# 2000Oct09 Fixed possible but in %parms with defaults JW +# 2001Apr04 Fixed -nocase problem in storing JW +# ---------------------------------------------------------- +sub STORE { + my $self = shift; + my( $key, $ref ) = @_; -sub SetWriteMode -{ - my ($self, $mode) = @_; + return undef unless ref($ref) eq 'HASH'; - if (not (defined($mode) && ($mode =~ m/[0-7]{3}/))) - { - return undef; - } + $self->_caseify(\$key); - return ($self->{file_mode} = $mode); -} + $self->AddSection($key); + $self->{v}{$key} = {%$ref}; + $self->{parms}{$key} = [keys %$ref]; + $self->{myparms}{$key} = [keys %$ref]; -=head2 GetWriteMode ($mode) + return 1; +} # end STORE -Gets the current mode (permissions) to use when writing the INI file. -$mode is a string representation of the octal mode. +# ---------------------------------------------------------- +# Date Modification Author +# ---------------------------------------------------------- +# 2000May09 Created method JW +# 2000Dec17 Now removes comments, groups and EOTs too JW +# 2001Arp04 Fixed -nocase problem JW +# ---------------------------------------------------------- +sub DELETE { + my $self = shift; + my( $key ) = @_; -=cut + my $retval=$self->FETCH($key); + $self->DeleteSection($key); + return $retval; +} # end DELETE -sub GetWriteMode -{ - my $self = shift; - return $self->{file_mode}; -} +# ---------------------------------------------------------- +# Date Modification Author +# ---------------------------------------------------------- +# 2000May09 Created method JW +# ---------------------------------------------------------- +sub CLEAR { + my $self = shift; -=head2 WriteConfig ($filename [, %options]) + return $self->Delete(); +} # end CLEAR -Writes out a new copy of the configuration file. A temporary file -is written out and then renamed to the specified filename. Also see -B below. +# ---------------------------------------------------------- +# Date Modification Author +# ---------------------------------------------------------- +# 2000May09 Created method JW +# ---------------------------------------------------------- +sub FIRSTKEY { + my $self = shift; -If C<-delta> is set to a true value in %options, and this object was -imported from another (see L), only the differences between this -object and the imported one will be recorded. Negative deltas will be -encoded into comments, so that a subsequent invocation of I -with the same imported object produces the same results (see the -I<-negativedeltas> option in L). + $self->{tied_enumerator}=0; + return $self->NEXTKEY(); +} # end FIRSTKEY -C<%options> is not required. -Returns true on success, C on failure. +# ---------------------------------------------------------- +# Date Modification Author +# ---------------------------------------------------------- +# 2000May09 Created method JW +# ---------------------------------------------------------- +sub NEXTKEY { + my $self = shift; + my( $last ) = @_; -=cut + my $i=$self->{tied_enumerator}++; + my $key=$self->{sects}[$i]; + return if (! defined $key); + return wantarray ? ($key, $self->FETCH($key)) : $key; +} # end NEXTKEY -sub _write_config_to_filename -{ - my ($self, $filename, %parms) = @_; - if (-e $filename) { - if (not (-w $filename)) - { - #carp "File $filename is not writable. Refusing to write config"; - return undef; - } - my $mode = (stat $filename)[2]; - $self->{file_mode} = sprintf "%04o", ($mode & 0777); - #carp "Using mode $self->{file_mode} for file $file"; - } +# ---------------------------------------------------------- +# Date Modification Author +# ---------------------------------------------------------- +# 2000May09 Created method JW +# 2001Apr04 Fixed -nocase bug and false true bug JW +# ---------------------------------------------------------- +sub EXISTS { + my $self = shift; + my( $key ) = @_; + return $self->SectionExists($key); +} # end EXISTS - my ($fh, $new_file); - # We need to trap the exception that tempfile() may throw and instead - # carp() and return undef() because that was the previous behaviour: - # - # See RT #77039 ( https://rt.cpan.org/Ticket/Display.html?id=77039 ) - eval { - ($fh, $new_file) = tempfile( - "temp.ini-XXXXXXXXXX", - DIR => dirname($filename) - ); +# ---------------------------------------------------------- +# DESTROY is used by TIEHASH and the Perl garbage collector, +# ---------------------------------------------------------- +# Date Modification Author +# ---------------------------------------------------------- +# 2000May09 Created method JW +# ---------------------------------------------------------- +sub DESTROY { + # my $self = shift; +} # end if - # Convert the filehandle to a "text" filehandle suitable for use - # on Windows (and other platforms). - # - # This may break compatibility for ultra-old perls (ones before 5.6.0) - # so I say - Good Riddance! - if ($^O =~ m/\AMSWin/) - { - binmode $fh, ':crlf'; - } - }; - - if ($@) - { - carp( "Unable to write temp config file: $!" ); - return undef; - } - - $self->OutputConfigToFileHandle($fh, $parms{-delta}); - close($fh); - if (!rename( $new_file, $filename )) { - carp "Unable to rename temp config file ($new_file) to ${filename}: $!"; - return undef; - } - if (exists $self->{file_mode}) { - chmod oct($self->{file_mode}), $filename; - } - - return 1; -} - -sub _write_config_with_a_made_fh -{ - my ($self, $fh, %parms) = @_; - - # Only roll back if it's not STDIN (if it is, Carp) - if( $fh == \*STDIN ) - { - carp "Cannot write configuration file to STDIN."; - } - else - { - seek( $fh, 0, SEEK_SET() ); - # Make sure to keep the previous junk out. - # See: - # https://rt.cpan.org/Public/Bug/Display.html?id=103496 - truncate( $fh, 0 ); - $self->OutputConfigToFileHandle($fh, $parms{-delta}); - seek( $fh, 0, SEEK_SET() ); - } # end if - - return 1; -} - -sub _write_config_to_fh -{ - my ($self, $file, %parms) = @_; - - # Get a filehandle, allowing almost any type of 'file' parameter - ## NB: If this were a filename, this would fail because _make_file - ## opens a read-only handle, but we have already checked that case - ## so re-using the logic is ok [JW/WADG] - my $fh = $self->_make_filehandle( $file ); - - if (!$fh) { - carp "Could not find a filehandle for the input stream ($file): $!"; - return undef; - } - return $self->_write_config_with_a_made_fh($fh, %parms); -} - -sub WriteConfig { - my ($self, $file, %parms) = @_; - - return undef unless defined $file; - - # If we are using a filename, then do mode checks and write to a - # temporary file to avoid a race condition - if( !ref($file) ) - { - return $self->_write_config_to_filename($file, %parms); - } - # Otherwise, reset to the start of the file and write, unless we are using - # STDIN - else - { - return $self->_write_config_to_fh($file, %parms); - } -} +# ---------------------------------------------------------- +# Sub: _make_filehandle +# +# Args: $thing +# $thing An input source +# +# Description: Takes an input source - a filehandle, +# filehandle glob, reference to a filehandle glob, IO::File +# object or scalar filename - and returns a file handle to +# read from it with. +# ---------------------------------------------------------- +# Date Modification Author +# ---------------------------------------------------------- +# 06Dec2001 Added to support input from any source JW +# ---------------------------------------------------------- +sub _make_filehandle { + my $self = shift; -=head2 RewriteConfig + # + # This code is 'borrowed' from Lincoln D. Stein's GD.pm module + # with modification for this module. Thanks Lincoln! + # -Same as WriteConfig, but specifies that the original configuration -file should be rewritten. + no strict 'refs'; + my $thing = shift; -=cut + if (ref($thing) eq "SCALAR") { + if (eval { require IO::Scalar; $IO::Scalar::VERSION >= 2.109; }) { + return IO::Scalar->new($thing); + } else { + warn "SCALAR reference as file descriptor requires IO::stringy ". + "v2.109 or later" if ($^W); + return; + } + } -sub RewriteConfig { - my $self = shift; + return $thing if defined(fileno $thing); - if ($self->_no_filename) - { - return 1; - } + # otherwise try qualifying it into caller's package + my $fh = qualify_to_ref($thing,caller(1)); + return $fh if defined(fileno $fh); - return $self->WriteConfig($self->{cf}); -} + # otherwise treat it as a file to open + $fh = gensym; + open($fh,$thing) || return; -=head2 GetFileName + return $fh; +} # end _make_filehandle -Returns the filename associated with this INI file. +############################################################ +# +# INTERNAL PACKAGE: Config::IniFiles::_section +# +# Description: +# This package is used to provide a single-level TIEHASH +# interface to the sections in the IniFile. When tied, the +# user wants to look at thinks like $ini{sec}{parm}, but the +# TIEHASH only provides one level of hash interface, so the +# root object gets asked for a $ini{sec} and must return a +# has reference that accurately covers the '{parm}' part. +# +# This package is only used when tied and is inter-woven +# between the sections and their parameters when the TIEHASH +# method is called by Perl. It's a very simple implementation +# of a tied hash object that simply maps onto the object API. +# +############################################################ +# Date Modification Author +# ---------------------------------------------------------- +# 2000.May.09 Created to excapsulate TIEHASH interface JW +############################################################ +package Config::IniFiles::_section; -If no filename has been specified, returns undef. +use strict; +use warnings; +use Carp; +use vars qw( $VERSION ); -=cut +$Config::IniFiles::_section::VERSION = 2.16; -sub GetFileName -{ - my $self = shift; +# ---------------------------------------------------------- +# Sub: Config::IniFiles::_section::TIEHASH +# +# Args: $class, $config, $section +# $class The class that this is being tied to. +# $config The parent Config::IniFiles object +# $section The section this tied object refers to +# +# Description: Builds the object that implements accesses to +# the tied hash. +# ---------------------------------------------------------- +# Date Modification Author +# ---------------------------------------------------------- +# ---------------------------------------------------------- +sub TIEHASH { + my $proto = shift; + my $class = ref($proto) || $proto; + my ($config, $section) = @_; - return $self->{cf}; -} + # Make a new object + return bless {config=>$config, section=>$section}, $class; +} # end TIEHASH -=head2 SetFileName ($filename) -If you created the Config::IniFiles object without initialising from -a file, or if you just want to change the name of the file to use for -ReadConfig/RewriteConfig from now on, use this method. +# ---------------------------------------------------------- +# Sub: Config::IniFiles::_section::FETCH +# +# Args: $key +# $key The name of the key whose value to get +# +# Description: Returns the value associated with $key. If +# the value is a list, returns a list reference. +# ---------------------------------------------------------- +# Date Modification Author +# ---------------------------------------------------------- +# 2000Jun15 Fixed bugs in -default handler JW +# 2000Dec07 Fixed another bug in -deault handler JW +# 2002Jul04 Returning scalar values (Bug:447532) AS +# ---------------------------------------------------------- +sub FETCH { + my ($self, $key) = @_; + my @retval=$self->{config}->val($self->{section}, $key); + return (@retval <= 1) ? $retval[0] : \@retval; +} # end FETCH -Returns $filename if that was a valid name, undef otherwise. -=cut +# ---------------------------------------------------------- +# Sub: Config::IniFiles::_section::STORE +# +# Args: $key, @val +# $key The key under which to store the value +# @val The value to store, either an array or a scalar +# +# Description: Sets the value for the specified $key +# ---------------------------------------------------------- +# Date Modification Author +# ---------------------------------------------------------- +# 2001Apr04 Fixed -nocase bug JW +# ---------------------------------------------------------- +sub STORE { + my ($self, $key, @val) = @_; + return $self->{config}->newval($self->{section}, $key, @val); +} # end STORE -sub SetFileName { - my ($self, $new_filename) = @_; - if ( length($new_filename) > 0 ) { - return ($self->{cf} = $new_filename); - } - else { - return undef; - } -} +# ---------------------------------------------------------- +# Sub: Config::IniFiles::_section::DELETE +# +# Args: $key +# $key The key to remove from the hash +# +# Description: Removes the specified key from the hash and +# returns its former value. +# ---------------------------------------------------------- +# Date Modification Author +# ---------------------------------------------------------- +# 2001Apr04 Fixed -nocase bug JW +# ---------------------------------------------------------- +sub DELETE { + my ($self, $key) = @_; + my $retval=$self->{config}->val($self->{section}, $key); + $self->{config}->delval($self->{section}, $key); + return $retval; +} # end DELETE -=head2 $ini->OutputConfigToFileHandle($fh, $delta) +# ---------------------------------------------------------- +# Sub: Config::IniFiles::_section::CLEAR +# +# Args: (None) +# +# Description: Empties the entire hash +# ---------------------------------------------------------- +# Date Modification Author +# ---------------------------------------------------------- +# ---------------------------------------------------------- +sub CLEAR { + my ($self) = @_; + return $self->{config}->DeleteSection($self->{section}); +} # end CLEAR -Writes OutputConfig to the $fh filehandle. $delta should be set to 1 -1 if writing only delta. This is a newer and safer version of -C and one is encouraged to use it instead. +# ---------------------------------------------------------- +# Sub: Config::IniFiles::_section::EXISTS +# +# Args: $key +# $key The key to look for +# +# Description: Returns whether the key exists +# ---------------------------------------------------------- +# Date Modification Author +# ---------------------------------------------------------- +# 2001Apr04 Fixed -nocase bug JW +# ---------------------------------------------------------- +sub EXISTS { + my ($self, $key) = @_; + return $self->{config}->exists($self->{section},$key); +} # end EXISTS -=head2 $ini->OutputConfig($delta) +# ---------------------------------------------------------- +# Sub: Config::IniFiles::_section::FIRSTKEY +# +# Args: (None) +# +# Description: Returns the first key in the hash +# ---------------------------------------------------------- +# Date Modification Author +# ---------------------------------------------------------- +# ---------------------------------------------------------- +sub FIRSTKEY { + my $self = shift; -Writes OutputConfig to STDOUT. Use select() to redirect STDOUT to -the output target before calling this function. Optional argument -should be set to 1 if writing only delta. Also see OutputConfigToFileHandle + $self->{tied_enumerator}=0; + return $self->NEXTKEY(); +} # end FIRSTKEY -=cut +# ---------------------------------------------------------- +# Sub: Config::IniFiles::_section::NEXTKEY +# +# Args: $last +# $last The last key accessed by the iterator +# +# Description: Returns the next key in line +# ---------------------------------------------------------- +# Date Modification Author +# ---------------------------------------------------------- +# ---------------------------------------------------------- +sub NEXTKEY { + my $self = shift; + my( $last ) = @_; -sub _calc_eot_mark -{ - my ($self, $sect, $parm, $val) = @_; + my $i=$self->{tied_enumerator}++; + my @keys = $self->{config}->Parameters($self->{section}); + my $key=$keys[$i]; + return if (! defined $key); + return wantarray ? ($key, $self->FETCH($key)) : $key; +} # end NEXTKEY - my $eotmark = $self->{EOT}{$sect}{$parm} || 'EOT'; - # Make sure the $eotmark does not occur inside the string. - my @letters = ('A' .. 'Z'); - my $joined_val = join(q{ }, @$val); - while (index($joined_val, $eotmark) >= 0) - { - $eotmark .= $letters[rand(@letters)]; - } +# ---------------------------------------------------------- +# Sub: Config::IniFiles::_section::DESTROY +# +# Args: (None) +# +# Description: Called on cleanup +# ---------------------------------------------------------- +# Date Modification Author +# ---------------------------------------------------------- +# ---------------------------------------------------------- +sub DESTROY { + # my $self = shift +} # end DESTROY - return $eotmark; -} +1; -sub _OutputParam { - my ($self, $sect, $parm, $val, $end_comment, $output_cb) = @_; - my $line_loop = sub { - my ($mapper) = @_; - foreach my $line (@{$val}[0 .. $#$val-1]) { - $output_cb->($mapper->($line)); - } - $output_cb->( - $mapper->($val->[-1]), - ($end_comment ? (" $self->{comment_char} $end_comment") : ()), - ); - return; - }; - if (! @$val) { - # An empty variable - see: - # https://rt.cpan.org/Public/Bug/Display.html?id=68554 - $output_cb->("$parm="); - } - elsif ((@$val == 1) or $self->{nomultiline}) { - $line_loop->(sub { my ($line) = @_; return "$parm=$line"; }); - } - else - { - my $eotmark = $self->_calc_eot_mark($sect, $parm, $val); +1; - $output_cb->("$parm= <<$eotmark"); - $line_loop->(sub { my ($line) = @_; return $line; }); - $output_cb->($eotmark); - } +# Please keep the following within the last four lines of the file +#[JW for editor]:mode=perl:tabSize=8:indentSize=2:noTabs=true:indentOnEnter=true: - return; -} +__END__ -sub OutputConfig { - my ($self, $delta) = @_; +=pod - return $self->OutputConfigToFileHandle(select(), $delta); -} +=encoding UTF-8 -sub _output_comments -{ - my ($self, $print_line, $comments_aref) = @_; +=head1 NAME - if (ref($comments_aref) eq 'ARRAY') { - foreach my $comment (@$comments_aref) { - $print_line->($comment); - } - } +Config::IniFiles - A module for reading .ini-style configuration files. - return; -} +=head1 VERSION -sub _process_continue_val -{ - my ($self, $fh) = @_; +version 2.94 - if (not $self->{allowcontinue}) - { - return; - } +=head1 SYNOPSIS - my $val = $self->_curr_val; + use Config::IniFiles; + my $cfg = Config::IniFiles->new( -file => "/path/configfile.ini" ); + print "The value is " . $cfg->val( 'Section', 'Parameter' ) . "." + if $cfg->val( 'Section', 'Parameter' ); - while($val =~ s/\\\z//) { - $val .= $self->_read_next_line($fh); - } +=head1 DESCRIPTION - $self->_curr_val($val); +Config::IniFiles provides a way to have readable configuration files outside +your Perl script. Configurations can be imported (inherited, stacked,...), +sections can be grouped, and settings can be accessed from a tied hash. - return; -} +=head1 FILE FORMAT -sub _output_param_total -{ - my ($self, $sect, $parm, $print_line, $split_val, $delta) = @_; - if (!defined $self->{v}{$sect}{$parm}) { - if ($delta) { - $print_line->("$self->{comment_char} $parm is deleted"); - } - else { - warn "Weird unknown parameter $parm" if $^W; - } - return; - } +INI files consist of a number of sections, each preceded with the +section name in square brackets, followed by parameter names and +their values. - $self->_output_comments($print_line, $self->{pCMT}{$sect}{$parm}); + [a section] + Parameter=Value - my $val = $self->{v}{$sect}{$parm}; - my $end_comment = $self->{peCMT}{$sect}{$parm}; + [section 2] + AnotherParameter=Some value + Setting=Something else + Parameter=Different scope than the one in the first section - return if ! defined ($val); # No parameter exists !! +The first non-blank character of the line indicating a section must +be a left bracket and the last non-blank character of a line indicating +a section must be a right bracket. The characters making up the section +name can be any symbols at all. However section names must be unique. - $self->_OutputParam( - $sect, - $parm, - $split_val->($val), - (defined($end_comment) ? $end_comment : ""), - $print_line, - ); +Parameters are specified in each section as Name=Value. Any spaces +around the equals sign will be ignored, and the value extends to the +end of the line (including any whitespace at the end of the line. +Parameter names are localized to the namespace of the section, but must +be unique within a section. - return; -} +Both the hash mark (#) and the semicolon (;) are comment characters. +by default (this can be changed by configuration). Lines that begin with +either of these characters will be ignored. Any amount of whitespace may +precede the comment character. -sub _output_section { - my ($self, $sect, $print_line, $split_val, $delta, $position) = @_; +Multi-line or multi-valued parameters may also be defined ala UNIX +"here document" syntax: - if (!defined $self->{v}{$sect}) { - if ($delta) { - $print_line->("$self->{comment_char} [$sect] is deleted"); - } else { - warn "Weird unknown section $sect" if $^W; - } - return; - } - return if not defined $self->{v}{$sect}; - $print_line->() if ($position > 0); - $self->_output_comments($print_line, $self->{sCMT}{$sect}); + Parameter=<{fallback_used} and $sect eq $self->{fallback}) - ) - { - $print_line->("[$sect]"); - } - return if ref($self->{v}{$sect}) ne 'HASH'; +You may use any string you want in place of "EOT". Note that whatever +follows the "<<" and what appears at the end of the text MUST match +exactly, including any trailing whitespace. - foreach my $parm (@{$self->{$delta ? "myparms" : "parms"}{$sect}}) { - $self->_output_param_total( - $sect, $parm, $print_line, $split_val, $delta - ); - } +Alternately, as a configuration option (default is off), continuation +lines can be allowed: - return; -} + [Section] + Parameter=this parameter \ + spreads across \ + a few lines -sub OutputConfigToFileHandle { - # We need no strict 'refs' to be able to print to $fh if it points - # to a glob filehandle. - no strict 'refs'; - my ($self, $fh, $delta) = @_; +=head1 USAGE -- Object Interface - my $ors = $self->{line_ends} || $\ || "\n"; # $\ is normally unset, but use input by default - my $print_line = sub { print {$fh} (@_, $ors); }; - my $split_val = sub { - my ($val) = @_; +Get a new Config::IniFiles object with the I method: - return ((ref($val) eq 'ARRAY') - ? $val - : [split /[$ors]/, $val, -1] - ); - }; + $cfg = Config::IniFiles->new( -file => "/path/config_file.ini" ); + $cfg = new Config::IniFiles -file => "/path/config_file.ini"; - my $position = 0; +Optional named parameters may be specified after the configuration +file name. See the I in the B section, below. - foreach my $sect (@{$self->{$delta ? "mysects" : "sects"}}) { - $self->_output_section( - $sect, $print_line, $split_val, $delta, $position++ - ); - } +Values from the config file are fetched with the val method: - $self->_output_comments($print_line, [ $self->_GetEndComments() ] ); + $value = $cfg->val('Section', 'Parameter'); - return 1; -} +If you want a multi-line/value field returned as an array, just +specify an array as the receiver: -=head2 SetSectionComment($section, @comment) + @values = $cfg->val('Section', 'Parameter'); -Sets the comment for section $section to the lines contained in @comment. +=head1 METHODS -Each comment line will be prepended with the comment character (default -is C<#>) if it doesn't already have a comment character (ie: if the -line does not start with whitespace followed by an allowed comment -character, default is C<#> and C<;>). +=head2 new ( [-option=>value ...] ) -To clear a section comment, use DeleteSectionComment ($section) +Returns a new configuration object (or "undef" if the configuration +file has an error, in which case check the global C<@Config::IniFiles::errors> +array for reasons why). One Config::IniFiles object is required per configuration +file. The following named parameters are available: -=cut +=over 10 -sub SetSectionComment -{ - my ($self, $sect, @comment) = @_; +=item I<-file> filename - if (not (defined($sect) && @comment)) - { - return undef; - } +Specifies a file to load the parameters from. This 'file' may actually be +any of the following things: - $self->_caseify(\$sect); + 1) the pathname of a file - $self->_touch_section($sect); - # At this point it's possible to have a comment for a section that - # doesn't exist. This comment will not get written to the INI file. - $self->{sCMT}{$sect} = $self->_markup_comments(\@comment); + $cfg = Config::IniFiles->new( -file => "/path/to/config_file.ini" ); - return scalar @comment; -} + 2) a simple filehandle + $cfg = Config::IniFiles->new( -file => STDIN ); + 3) a filehandle glob -# this helper makes sure that each line is preceded with the correct comment -# character -sub _markup_comments -{ - my ($self, $comment_aref) = @_; + open( CONFIG, "/path/to/config_file.ini" ); + $cfg = Config::IniFiles->new( -file => *CONFIG ); - my $allCmt = $self->{allowed_comment_char}; - my $cmtChr = $self->{comment_char}; + 4) a reference to a glob - my $is_comment = qr/\A\s*[$allCmt]/; + open( CONFIG, "/path/to/config_file.ini" ); + $cfg = Config::IniFiles->new( -file => \*CONFIG ); - # TODO : Maybe create a qr// out of it. - return [map { ($_ =~ $is_comment) ? $_ : "$cmtChr $_" } @$comment_aref]; -} + 5) an IO::File object + $io = IO::File->new( "/path/to/config_file.ini" ); + $cfg = Config::IniFiles->new( -file => $io ); + or -=head2 GetSectionComment ($section) + open my $fh, '<', "/path/to/config_file.ini" or die $!; + $cfg = Config::IniFiles->new( -file => $fh ); -Returns a list of lines, being the comment attached to section $section. In -scalar context, returns a string containing the lines of the comment separated -by newlines. + 6) A reference to a scalar (requires newer versions of IO::Scalar) -The lines are presented as-is, with whatever comment character was originally -used on that line. + $ini_file_contents = <new( -file => \$ini_file_contents ); -sub _return_comment -{ - my ($self, $comment_aref) = @_; +If this option is not specified, (i.e. you are creating a config file from scratch) +you must specify a target file using L in order to save the parameters. - my $delim = defined($/) ? $/ : "\n"; +=item I<-default> section - return wantarray() ? @$comment_aref : join($delim, @$comment_aref); -} +Specifies a section to be used for default values. For example, in the +following configuration file, if you look up the "permissions" parameter +in the "joe" section, there is none. -sub GetSectionComment -{ - my ($self, $sect) = @_; + [all] + permissions=Nothing - return undef if not defined $sect; + [jane] + name=Jane + permissions=Open files - $self->_caseify(\$sect); + [joe] + name=Joseph - if (! exists $self->{sCMT}{$sect}) { - return undef; - } +If you create your Config::IniFiles object with a default section of "all" like this: - return $self->_return_comment( $self->{sCMT}{$sect} ); -} + $cfg = Config::IniFiles->new( -file => "file.ini", -default => "all" ); -=head2 DeleteSectionComment ($section) +Then requesting a value for a "permissions" in the [joe] section will +check for a value from [all] before returning undef. -Removes the comment for the specified section. + $permissions = $cfg->val( "joe", "permissions"); // returns "Nothing" -=cut +=item I<-fallback> section -sub DeleteSectionComment -{ - my $self = shift; - my $sect = shift; +Specifies a section to be used for parameters outside a section. Default is none. +Without -fallback specified (which is the default), reading a configuration file +which has a parameter outside a section will fail. With this set to, say, +"GENERAL", this configuration: - return undef if not defined $sect; + wrong=wronger - $self->_caseify(\$sect); - $self->_touch_section($sect); + [joe] + name=Joseph - delete $self->{sCMT}{$sect}; +will be assumed as: - return; -} + [GENERAL] + wrong=wronger -=head2 SetParameterComment ($section, $parameter, @comment) + [joe] + name=Joseph -Sets the comment attached to a particular parameter. +Note that Config::IniFiles will also omit the fallback section header when +outputting such configuration. -Any line of @comment that does not have a comment character will be -prepended with one. See L above +=item I<-nocase> 0|1 -=cut +Set -nocase => 1 to handle the config file in a case-insensitive +manner (case in values is preserved, however). By default, config +files are case-sensitive (i.e., a section named 'Test' is not the same +as a section named 'test'). Note that there is an added overhead for +turning off case sensitivity. -sub SetParameterComment -{ - my ($self, $sect, $parm, @comment) = @_; +=item I<-import> object - if (not (defined($sect) && defined($parm) && @comment)) - { - return undef; - } +This allows you to import or inherit existing setting from another +Config::IniFiles object. When importing settings from another object, +sections with the same name will be merged and parameters that are +defined in both the imported object and the I<-file> will take the +value of given in the I<-file>. - $self->_caseify(\$sect, \$parm); +If a I<-default> section is also given on this call, and it does not +coincide with the default of the imported object, the new default +section will be used instead. If no I<-default> section is given, +then the default of the imported object will be used. - $self->_touch_parameter($sect, $parm); +=item I<-allowcontinue> 0|1 - # Note that at this point, it's possible to have a comment for a parameter, - # without that parameter actually existing in the INI file. - $self->{pCMT}{$sect}{$parm} = $self->_markup_comments(\@comment); +Set -allowcontinue => 1 to enable continuation lines in the config file. +i.e. if a line ends with a backslash C<\>, then the following line is +appended to the parameter value, dropping the backslash and the newline +character(s). - return scalar @comment; -} +Default behavior is to keep a trailing backslash C<\> as a parameter +value. Note that continuation cannot be mixed with the "here" value +syntax. -sub _SetEndComments -{ - my $self = shift; - my @comments = @_; +=item I<-allowempty> 0|1 - $self->{_comments_at_end_of_file} = \@comments; +If set to 1, then empty files are allowed at L +time. If set to 0 (the default), an empty configuration file is considered +an error. - return 1; -} +=item I<-negativedeltas> 0|1 -sub _GetEndComments { - my $self = shift; +If set to 1 (the default if importing this object from another one), +parses and honors lines of the following form in the configuration +file: - return @{$self->{_comments_at_end_of_file}}; -} + ; [somesection] is deleted -=head2 GetParameterComment ($section, $parameter) +or -Gets the comment attached to a parameter. In list context returns all -comments - in scalar context returns them joined by newlines. + [inthissection] + ; thisparameter is deleted -=cut +If set to 0 (the default if not importing), these comments are treated +like ordinary ones. -sub GetParameterComment -{ - my ($self, $sect, $parm) = @_; +The L1)> form will output such +comments to indicate deleted sections or parameters. This way, +reloading a delta file using the same imported object produces the +same results in memory again. See L for more +details. - if (not (defined($sect) && defined($parm))) - { - return undef; - } +=item I<-commentchar> 'char' - $self->_caseify(\$sect, \$parm); +The default comment character is C<#>. You may change this by specifying +this option to another character. This can be any character except +alphanumeric characters, square brackets or the "equal" sign. - if (not (exists( $self->{pCMT}{$sect} ) - && exists( $self->{pCMT}{$sect}{$parm} ))) - { - return undef; - } +=item I<-allowedcommentchars> 'chars' - return $self->_return_comment( $self->{pCMT}{$sect}{$parm} ); -} +Allowed default comment characters are C<#> and C<;>. By specifying this +option you may change the range of characters that are used to denote a +comment line to include any set of characters -=head2 DeleteParameterComment ($section, $parmeter) +Note: that the character specified by B<-commentchar> (see above) is +I part of the allowed comment characters. -Deletes the comment attached to a parameter. +Note 2: The given string is evaluated as a regular expression character +class, so '\' must be escaped if you wish to use it. -=cut +=item I<-reloadwarn> 0|1 -sub DeleteParameterComment -{ - my ($self, $sect, $parm) = @_; +Set -reloadwarn => 1 to enable a warning message (output to STDERR) +whenever the config file is reloaded. The reload message is of the +form: - if (not (defined($sect) && defined($parm))) - { - return undef; - } + PID reloading config file at YYYY.MM.DD HH:MM:SS - $self->_caseify(\$sect, \$parm); +Default behavior is to not warn (i.e. -reloadwarn => 0). - # If the parameter doesn't exist, our goal has already been achieved - if ( exists( $self->{pCMT}{$sect} ) - && exists( $self->{pCMT}{$sect}{$parm} )) - { - $self->_touch_parameter($sect, $parm); - delete $self->{pCMT}{$sect}{$parm}; - } +This is generally only useful when using Config::IniFiles in a server +or daemon application. The application is still responsible for determining +when the object is to be reloaded. - return 1; -} +=item I<-nomultiline> 0|1 -=head2 GetParameterEOT ($section, $parameter) +Set -nomultiline => 1 to output multi-valued parameter as: -Accessor method for the EOT text (in fact, style) of the specified parameter. If any text is used as an EOT mark, this will be returned. If the parameter was not recorded using HERE style multiple lines, GetParameterEOT returns undef. + param=value1 + param=value2 -=cut +instead of the default: -sub GetParameterEOT -{ - my ($self, $sect, $parm) = @_; + param=<_caseify(\$sect, \$parm); +=item I<-handle_trailing_comment> 0|1 - return $self->{EOT}{$sect}{$parm}; -} +Set -handle_trailing_comment => 1 to enable support of parameter trailing +comments. -=head2 $cfg->SetParameterEOT ($section, $parameter, $EOT) +For example, if we have a parameter line like this: -Accessor method for the EOT text for the specified parameter. Sets the HERE style marker text to the value $EOT. Once the EOT text is set, that parameter will be saved in HERE style. + param1=value1;comment1 -To un-set the EOT text, use DeleteParameterEOT ($section, $parameter). +by default, handle_trailing_comment will be set to B<0>, and we will get +I as the value of I. If we have +-handle_trailing_comment set to B<1>, then we will get I +as the value for I, and I as the trailing comment of +I. -=cut +Set and get methods for trailing comments are provided as +L and L. -sub SetParameterEOT -{ - my ($self, $sect, $parm, $EOT) = @_; +=back - if (not (defined($sect) && defined($parm) && defined($EOT))) - { - return undef; - } +=head2 val ($section, $parameter [, $default] ) - $self->_caseify(\$sect, \$parm); +Returns the value of the specified parameter (C<$parameter>) in section +C<$section>, returns undef (or C<$default> if specified) if no section or +no parameter for the given section exists. - $self->_touch_parameter($sect, $parm); +If you want a multi-line/value field returned as an array, just +specify an array as the receiver: - $self->{EOT}{$sect}{$parm} = $EOT; + @values = $cfg->val('Section', 'Parameter'); - return; -} +A multi-line/value field that is returned in a scalar context will be +joined using $/ (input record separator, default is \n) if defined, +otherwise the values will be joined using \n. -=head2 DeleteParameterEOT ($section, $parmeter) +=head2 exists($section, $parameter) -Removes the EOT marker for the given section and parameter. -When writing a configuration file, if no EOT marker is defined -then "EOT" is used. +True if and only if there exists a section C<$section>, with +a parameter C<$parameter> inside, not counting default values. -=cut +=head2 push ($section, $parameter, $value, [ $value2, ...]) -sub DeleteParameterEOT -{ - my ($self, $sect, $parm) = @_; +Pushes new values at the end of existing value(s) of parameter +C<$parameter> in section C<$section>. See below for methods to write +the new configuration back out to a file. - if (not (defined($sect) && defined($parm))) - { - return undef; - } +You may not set a parameter that didn't exist in the original +configuration file. B will return I if this is +attempted. See B below to do this. Otherwise, it returns 1. - $self->_caseify(\$sect, \$parm); +=head2 setval ($section, $parameter, $value, [ $value2, ... ]) - $self->_touch_parameter($sect, $parm); - delete $self->{EOT}{$sect}{$parm}; +Sets the value of parameter C<$parameter> in section C<$section> to +C<$value> (or to a set of values). See below for methods to write +the new configuration back out to a file. - return; -} +You may not set a parameter that didn't exist in the original +configuration file. B will return I if this is +attempted. See B below to do this. Otherwise, it returns 1. -=head2 SetParameterTrailingComment ($section, $parameter, $cmt) +=head2 newval($section, $parameter, $value [, $value2, ...]) -Set the end trailing comment for the given section and parameter. -If there is a old comment for the parameter, it will be -overwritten by the new one. +Assigns a new value, C<$value> (or set of values) to the +parameter C<$parameter> in section C<$section> in the configuration +file. -If there is a new parameter trailing comment to be added, the -value should be added first. +=head2 delval($section, $parameter) -=cut +Deletes the specified parameter from the configuration file -sub SetParameterTrailingComment -{ - my ($self, $sect, $parm, $cmt) = @_; +=head2 ReadConfig - if (not (defined($sect) && defined($parm) && defined($cmt))) - { - return undef; - } +Forces the configuration file to be re-read. Returns undef if the +file can not be opened, no filename was defined (with the C<-file> +option) when the object was constructed, or an error occurred while +reading. - $self->_caseify(\$sect, \$parm); +If an error occurs while parsing the INI file the @Config::IniFiles::errors +array will contain messages that might help you figure out where the +problem is in the file. - # confirm the parameter exist - return undef if not exists $self->{v}{$sect}{$parm}; +=head2 Sections - $self->_touch_parameter($sect, $parm); - $self->{peCMT}{$sect}{$parm} = $cmt; +Returns an array containing section names in the configuration file. +If the I option was turned on when the config object was +created, the section names will be returned in lowercase. - return 1; -} +=head2 SectionExists ( $sect_name ) -=head2 GetParameterTrailingComment ($section, $parameter) +Returns 1 if the specified section exists in the INI file, 0 otherwise (undefined if section_name is not defined). -An accessor method to read the trailing comment after the parameter. -The trailing comment will be returned if there is one. A null string -will be returned if the parameter exists but no comment for it. -otherwise, L will be returned. +=head2 AddSection ( $sect_name ) -=cut +Ensures that the named section exists in the INI file. If the section already +exists, nothing is done. In this case, the "new" section will possibly contain +data already. -sub GetParameterTrailingComment -{ - my ($self, $sect, $parm) = @_; +If you really need to have a new section with no parameters in it, check that +the name that you're adding isn't in the list of sections already. - if (not (defined($sect) && defined($parm))) - { - return undef; - } +=head2 DeleteSection ( $sect_name ) - $self->_caseify(\$sect, \$parm); +Completely removes the entire section from the configuration. - # confirm the parameter exist - return undef if not exists $self->{v}{$sect}{$parm}; - return $self->{peCMT}{$sect}{$parm}; -} +=head2 RenameSection ( $old_section_name, $new_section_name, $include_groupmembers) -=head2 Delete +Renames a section if it does not already exist, optionally including groupmembers -Deletes the entire configuration file in memory. +=head2 CopySection ( $old_section_name, $new_section_name, $include_groupmembers) -=cut +Copies one section to another optionally including groupmembers -sub Delete { - my $self = shift; +=head2 Parameters ($sect_name) - foreach my $section ($self->Sections()) { - $self->DeleteSection($section); - } +Returns an array containing the parameters contained in the specified +section. - return 1; -} # end Delete +=head2 Groups +Returns an array containing the names of available groups. +Groups are specified in the config file as new sections of the form -=head1 USAGE -- Tied Hash + [GroupName MemberName] -=head2 tie %ini, 'Config::IniFiles', (-file=>$filename, [-option=>value ...] ) +This is useful for building up lists. Note that parameters within a +"member" section are referenced normally (i.e., the section name is +still "Groupname Membername", including the space) - the concept of +Groups is to aid people building more complex configuration files. -Using C, you can tie a hash to a B object. This creates a new -object which you can access through your hash, so you use this instead of the -B method. This actually creates a hash of hashes to access the values in -the INI file. The options you provide through C are the same as given for -the B method, above. +=head2 SetGroupMember ( $sect ) -Here's an example: +Makes sure that the specified section is a member of the appropriate group. - use Config::IniFiles; +Only intended for use in newval. - my %ini; - tie %ini, 'Config::IniFiles', ( -file => "/path/configfile.ini" ); +=head2 RemoveGroupMember ( $sect ) - print "We have $ini{Section}{Parameter}." if $ini{Section}{Parameter}; +Makes sure that the specified section is no longer a member of the +appropriate group. Only intended for use in DeleteSection. -Accessing and using the hash works just like accessing a regular hash and -many of the object methods are made available through the hash interface. +=head2 GroupMembers ($group) -For those methods that do not coincide with the hash paradigm, you can use -the Perl C function to get at the underlying object tied to the hash -and call methods on that object. For example, to write the hash out to a new -ini file, you would do something like this: +Returns an array containing the members of specified $group. Each element +of the array is a section name. For example, given the sections - tied( %ini )->WriteConfig( "/newpath/newconfig.ini" ) || - die "Could not write settings to new file."; + [Group Element 1] + ... -=head2 $val = $ini{$section}{$parameter} + [Group Element 2] + ... -Returns the value of $parameter in $section. +GroupMembers would return ("Group Element 1", "Group Element 2"). -Multiline values accessed through a hash will be returned -as a list in list context and a concatenated value in scalar -context. +=head2 SetWriteMode ($mode) -=head2 $ini{$section}{$parameter} = $value; +Sets the mode (permissions) to use when writing the INI file. -Sets the value of C<$parameter> in C<$section> to C<$value>. +$mode must be a string representation of the octal mode. -To set a multiline or multiv-alue parameter just assign an -array reference to the hash entry, like this: +=head2 GetWriteMode ($mode) - $ini{$section}{$parameter} = [$value1, $value2, ...]; +Gets the current mode (permissions) to use when writing the INI file. -If the parameter did not exist in the original file, it will -be created. However, Perl does not seem to extend autovivification -to tied hashes. That means that if you try to say +$mode is a string representation of the octal mode. - $ini{new_section}{new_paramters} = $val; +=head2 WriteConfig ($filename [, %options]) -and the section 'new_section' does not exist, then Perl won't -properly create it. In order to work around this you will need -to create a hash reference in that section and then assign the -parameter value. Something like this should do nicely: +Writes out a new copy of the configuration file. A temporary file +is written out and then renamed to the specified filename. Also see +B below. - $ini{new_section} = {}; - $ini{new_section}{new_paramters} = $val; +If C<-delta> is set to a true value in %options, and this object was +imported from another (see L), only the differences between this +object and the imported one will be recorded. Negative deltas will be +encoded into comments, so that a subsequent invocation of I +with the same imported object produces the same results (see the +I<-negativedeltas> option in L). -=head2 %hash = %{$ini{$section}} +C<%options> is not required. -Using the tie interface, you can copy whole sections of the -ini file into another hash. Note that this makes a copy of -the entire section. The new hash in no longer tied to the -ini file, In particular, this means -default and -nocase -settings will not apply to C<%hash>. +Returns true on success, C on failure. +=head2 RewriteConfig -=head2 $ini{$section} = {}; %{$ini{$section}} = %parameters; +Same as WriteConfig, but specifies that the original configuration +file should be rewritten. -Through the hash interface, you have the ability to replace -the entire section with a new set of parameters. This call -will fail, however, if the argument passed in NOT a hash -reference. You must use both lines, as shown above so that -Perl recognizes the section as a hash reference context -before COPYing over the values from your C<%parameters> hash. +=head2 GetFileName -=head2 delete $ini{$section}{$parameter} +Returns the filename associated with this INI file. -When tied to a hash, you can use the Perl C function -to completely remove a parameter from a section. +If no filename has been specified, returns undef. -=head2 delete $ini{$section} +=head2 SetFileName ($filename) -The tied interface also allows you to delete an entire -section from the ini file using the Perl C function. +If you created the Config::IniFiles object without initialising from +a file, or if you just want to change the name of the file to use for +ReadConfig/RewriteConfig from now on, use this method. -=head2 %ini = (); +Returns $filename if that was a valid name, undef otherwise. -If you really want to delete B the items in the ini file, this -will do it. Of course, the changes won't be written to the actual -file unless you call B on the object tied to the hash. +=head2 $ini->OutputConfigToFileHandle($fh, $delta) -=head2 Parameter names +Writes OutputConfig to the $fh filehandle. $delta should be set to 1 +1 if writing only delta. This is a newer and safer version of +C and one is encouraged to use it instead. -=over 4 +=head2 $ini->OutputConfig($delta) -=item my @keys = keys %{$ini{$section}} +Writes OutputConfig to STDOUT. Use select() to redirect STDOUT to +the output target before calling this function. Optional argument +should be set to 1 if writing only a delta. Also see OutputConfigToFileHandle -=item while (($k, $v) = each %{$ini{$section}}) {...} +=head2 SetSectionComment($section, @comment) -=item if( exists %{$ini{$section}}, $parameter ) {...} +Sets the comment for section $section to the lines contained in @comment. -=back +Each comment line will be prepended with the comment character (default +is C<#>) if it doesn't already have a comment character (ie: if the +line does not start with whitespace followed by an allowed comment +character, default is C<#> and C<;>). -When tied to a hash, you use the Perl C and C -functions to iteratively list the parameters (C) or -parameters and their values (C) in a given section. +To clear a section comment, use DeleteSectionComment ($section) -You can also use the Perl C function to see if a -parameter is defined in a given section. +=head2 GetSectionComment ($section) -Note that none of these will return parameter names that -are part of the default section (if set), although accessing -an unknown parameter in the specified section will return a -value from the default section if there is one. +Returns a list of lines, being the comment attached to section $section. In +scalar context, returns a string containing the lines of the comment separated +by newlines. +The lines are presented as-is, with whatever comment character was originally +used on that line. -=head2 Section names +=head2 DeleteSectionComment ($section) -=over 4 +Removes the comment for the specified section. -=item foreach( keys %ini ) {...} +=head2 SetParameterComment ($section, $parameter, @comment) -=item while (($k, $v) = each %ini) {...} +Sets the comment attached to a particular parameter. -=item if( exists %ini, $section ) {...} +Any line of @comment that does not have a comment character will be +prepended with one. See L above -=back +=head2 GetParameterComment ($section, $parameter) -When tied to a hash, you use the Perl C and C -functions to iteratively list the sections in the ini file. +Gets the comment attached to a parameter. In list context returns all +comments - in scalar context returns them joined by newlines. -You can also use the Perl C function to see if a -section is defined in the file. +=head2 DeleteParameterComment ($section, $parmeter) -=cut +Deletes the comment attached to a parameter. -############################################################ -# -# TIEHASH Methods -# -# Description: -# These methods allow you to tie a hash to the -# Config::IniFiles object. Note that, when tied, the -# user wants to look at thinks like $ini{sec}{parm}, but the -# TIEHASH only provides one level of hash interace, so the -# root object gets asked for a $ini{sec}, which this -# implements. To further tie the {parm} hash, the internal -# class Config::IniFiles::_section, is provided, below. -# -############################################################ -# ---------------------------------------------------------- -# Date Modification Author -# ---------------------------------------------------------- -# 2000May09 Created method JW -# ---------------------------------------------------------- -sub TIEHASH { - my $class = shift; - my %parms = @_; +=head2 GetParameterEOT ($section, $parameter) - # Get a new object - my $self = $class->new( %parms ); +Accessor method for the EOT text (in fact, style) of the specified parameter. If any text is used as an EOT mark, this will be returned. If the parameter was not recorded using HERE style multiple lines, GetParameterEOT returns undef. - return $self; -} # end TIEHASH +=head2 $cfg->SetParameterEOT ($section, $parameter, $EOT) + +Accessor method for the EOT text for the specified parameter. Sets the HERE style marker text to the value $EOT. Once the EOT text is set, that parameter will be saved in HERE style. +To un-set the EOT text, use DeleteParameterEOT ($section, $parameter). -# ---------------------------------------------------------- -# Date Modification Author -# ---------------------------------------------------------- -# 2000May09 Created method JW -# ---------------------------------------------------------- -sub FETCH { - my $self = shift; - my( $key ) = @_; +=head2 DeleteParameterEOT ($section, $parmeter) - $self->_caseify(\$key); - return if (! $self->{v}{$key}); +Removes the EOT marker for the given section and parameter. +When writing a configuration file, if no EOT marker is defined +then "EOT" is used. - my %retval; - tie %retval, 'Config::IniFiles::_section', $self, $key; - return \%retval; +=head2 SetParameterTrailingComment ($section, $parameter, $cmt) -} # end FETCH +Set the end trailing comment for the given section and parameter. +If there is a old comment for the parameter, it will be +overwritten by the new one. -# ---------------------------------------------------------- -# Date Modification Author -# ---------------------------------------------------------- -# 2000Jun14 Fixed bug where wrong ref was saved JW -# 2000Oct09 Fixed possible but in %parms with defaults JW -# 2001Apr04 Fixed -nocase problem in storing JW -# ---------------------------------------------------------- -sub STORE { - my $self = shift; - my( $key, $ref ) = @_; +If there is a new parameter trailing comment to be added, the +value should be added first. - return undef unless ref($ref) eq 'HASH'; +=head2 GetParameterTrailingComment ($section, $parameter) - $self->_caseify(\$key); +An accessor method to read the trailing comment after the parameter. +The trailing comment will be returned if there is one. A null string +will be returned if the parameter exists but there is no comment for it. +otherwise, L will be returned. - $self->AddSection($key); - $self->{v}{$key} = {%$ref}; - $self->{parms}{$key} = [keys %$ref]; - $self->{myparms}{$key} = [keys %$ref]; +=head2 Delete - return 1; -} # end STORE +Deletes the entire configuration file in memory. +=head1 USAGE -- Tied Hash -# ---------------------------------------------------------- -# Date Modification Author -# ---------------------------------------------------------- -# 2000May09 Created method JW -# 2000Dec17 Now removes comments, groups and EOTs too JW -# 2001Arp04 Fixed -nocase problem JW -# ---------------------------------------------------------- -sub DELETE { - my $self = shift; - my( $key ) = @_; +=head2 tie %ini, 'Config::IniFiles', (-file=>$filename, [-option=>value ...] ) - my $retval=$self->FETCH($key); - $self->DeleteSection($key); - return $retval; -} # end DELETE +Using C, you can tie a hash to a B object. This creates a new +object which you can access through your hash, so you use this instead of the +B method. This actually creates a hash of hashes to access the values in +the INI file. The options you provide through C are the same as given for +the B method, above. +Here's an example: -# ---------------------------------------------------------- -# Date Modification Author -# ---------------------------------------------------------- -# 2000May09 Created method JW -# ---------------------------------------------------------- -sub CLEAR { - my $self = shift; + use Config::IniFiles; - return $self->Delete(); -} # end CLEAR + my %ini; + tie %ini, 'Config::IniFiles', ( -file => "/path/configfile.ini" ); -# ---------------------------------------------------------- -# Date Modification Author -# ---------------------------------------------------------- -# 2000May09 Created method JW -# ---------------------------------------------------------- -sub FIRSTKEY { - my $self = shift; + print "We have $ini{Section}{Parameter}." if $ini{Section}{Parameter}; - $self->{tied_enumerator}=0; - return $self->NEXTKEY(); -} # end FIRSTKEY +Accessing and using the hash works just like accessing a regular hash and +many of the object methods are made available through the hash interface. +For those methods that do not coincide with the hash paradigm, you can use +the Perl C function to get at the underlying object tied to the hash +and call methods on that object. For example, to write the hash out to a new +ini file, you would do something like this: -# ---------------------------------------------------------- -# Date Modification Author -# ---------------------------------------------------------- -# 2000May09 Created method JW -# ---------------------------------------------------------- -sub NEXTKEY { - my $self = shift; - my( $last ) = @_; + tied( %ini )->WriteConfig( "/newpath/newconfig.ini" ) || + die "Could not write settings to new file."; - my $i=$self->{tied_enumerator}++; - my $key=$self->{sects}[$i]; - return if (! defined $key); - return wantarray ? ($key, $self->FETCH($key)) : $key; -} # end NEXTKEY +=head2 $val = $ini{$section}{$parameter} +Returns the value of $parameter in $section. -# ---------------------------------------------------------- -# Date Modification Author -# ---------------------------------------------------------- -# 2000May09 Created method JW -# 2001Apr04 Fixed -nocase bug and false true bug JW -# ---------------------------------------------------------- -sub EXISTS { - my $self = shift; - my( $key ) = @_; - return $self->SectionExists($key); -} # end EXISTS +Multiline values accessed through a hash will be returned +as a list in list context and a concatenated value in scalar +context. +=head2 $ini{$section}{$parameter} = $value; -# ---------------------------------------------------------- -# DESTROY is used by TIEHASH and the Perl garbage collector, -# ---------------------------------------------------------- -# Date Modification Author -# ---------------------------------------------------------- -# 2000May09 Created method JW -# ---------------------------------------------------------- -sub DESTROY { - # my $self = shift; -} # end if +Sets the value of C<$parameter> in C<$section> to C<$value>. +To set a multiline or multi-value parameter just assign an +array reference to the hash entry, like this: -# ---------------------------------------------------------- -# Sub: _make_filehandle -# -# Args: $thing -# $thing An input source -# -# Description: Takes an input source of a filehandle, -# filehandle glob, reference to a filehandle glob, IO::File -# object or scalar filename and returns a file handle to -# read from it with. -# ---------------------------------------------------------- -# Date Modification Author -# ---------------------------------------------------------- -# 06Dec2001 Added to support input from any source JW -# ---------------------------------------------------------- -sub _make_filehandle { - my $self = shift; + $ini{$section}{$parameter} = [$value1, $value2, ...]; - # - # This code is 'borrowed' from Lincoln D. Stein's GD.pm module - # with modification for this module. Thanks Lincoln! - # +If the parameter did not exist in the original file, it will +be created. However, Perl does not seem to extend autovivification +to tied hashes. That means that if you try to say - no strict 'refs'; - my $thing = shift; + $ini{new_section}{new_paramters} = $val; - if (ref($thing) eq "SCALAR") { - if (eval { require IO::Scalar; $IO::Scalar::VERSION >= 2.109; }) { - return IO::Scalar->new($thing); - } else { - warn "SCALAR reference as file descriptor requires IO::stringy ". - "v2.109 or later" if ($^W); - return; - } - } +and the section 'new_section' does not exist, then Perl won't +properly create it. In order to work around this you will need +to create a hash reference in that section and then assign the +parameter value. Something like this should do nicely: - return $thing if defined(fileno $thing); + $ini{new_section} = {}; + $ini{new_section}{new_paramters} = $val; - # otherwise try qualifying it into caller's package - my $fh = qualify_to_ref($thing,caller(1)); - return $fh if defined(fileno $fh); +=head2 %hash = %{$ini{$section}} - # otherwise treat it as a file to open - $fh = gensym; - open($fh,$thing) || return; +Using the tie interface, you can copy whole sections of the +ini file into another hash. Note that this makes a copy of +the entire section. The new hash in no longer tied to the +ini file, In particular, this means -default and -nocase +settings will not apply to C<%hash>. - return $fh; -} # end _make_filehandle +=head2 $ini{$section} = {}; %{$ini{$section}} = %parameters; -############################################################ -# -# INTERNAL PACKAGE: Config::IniFiles::_section -# -# Description: -# This package is used to provide a single-level TIEHASH -# interface to the sections in the IniFile. When tied, the -# user wants to look at thinks like $ini{sec}{parm}, but the -# TIEHASH only provides one level of hash interace, so the -# root object gets asked for a $ini{sec} and must return a -# has reference that accurately covers the '{parm}' part. -# -# This package is only used when tied and is inter-woven -# between the sections and their parameters when the TIEHASH -# method is called by Perl. It's a very simple implementation -# of a tied hash object that simply maps onto the object API. -# -############################################################ -# Date Modification Author -# ---------------------------------------------------------- -# 2000.May.09 Created to excapsulate TIEHASH interface JW -############################################################ -package Config::IniFiles::_section; +Through the hash interface, you have the ability to replace +the entire section with a new set of parameters. This call +will fail, however, if the argument passed in NOT a hash +reference. You must use both lines, as shown above so that +Perl recognizes the section as a hash reference context +before COPYing over the values from your C<%parameters> hash. -use strict; -use warnings; -use Carp; -use vars qw( $VERSION ); +=head2 delete $ini{$section}{$parameter} -$Config::IniFiles::_section::VERSION = 2.16; +When tied to a hash, you can use the Perl C function +to completely remove a parameter from a section. -# ---------------------------------------------------------- -# Sub: Config::IniFiles::_section::TIEHASH -# -# Args: $class, $config, $section -# $class The class that this is being tied to. -# $config The parent Config::IniFiles object -# $section The section this tied object refers to -# -# Description: Builds the object that implements accesses to -# the tied hash. -# ---------------------------------------------------------- -# Date Modification Author -# ---------------------------------------------------------- -# ---------------------------------------------------------- -sub TIEHASH { - my $proto = shift; - my $class = ref($proto) || $proto; - my ($config, $section) = @_; +=head2 delete $ini{$section} - # Make a new object - return bless {config=>$config, section=>$section}, $class; -} # end TIEHASH +The tied interface also allows you to delete an entire +section from the ini file using the Perl C function. +=head2 %ini = (); -# ---------------------------------------------------------- -# Sub: Config::IniFiles::_section::FETCH -# -# Args: $key -# $key The name of the key whose value to get -# -# Description: Returns the value associated with $key. If -# the value is a list, returns a list reference. -# ---------------------------------------------------------- -# Date Modification Author -# ---------------------------------------------------------- -# 2000Jun15 Fixed bugs in -default handler JW -# 2000Dec07 Fixed another bug in -deault handler JW -# 2002Jul04 Returning scalar values (Bug:447532) AS -# ---------------------------------------------------------- -sub FETCH { - my ($self, $key) = @_; - my @retval=$self->{config}->val($self->{section}, $key); - return (@retval <= 1) ? $retval[0] : \@retval; -} # end FETCH +If you really want to delete B the items in the ini file, this +will do it. Of course, the changes won't be written to the actual +file unless you call B on the object tied to the hash. +=head2 Parameter names -# ---------------------------------------------------------- -# Sub: Config::IniFiles::_section::STORE -# -# Args: $key, @val -# $key The key under which to store the value -# @val The value to store, either an array or a scalar -# -# Description: Sets the value for the specified $key -# ---------------------------------------------------------- -# Date Modification Author -# ---------------------------------------------------------- -# 2001Apr04 Fixed -nocase bug JW -# ---------------------------------------------------------- -sub STORE { - my ($self, $key, @val) = @_; - return $self->{config}->newval($self->{section}, $key, @val); -} # end STORE +=over 4 +=item my @keys = keys %{$ini{$section}} -# ---------------------------------------------------------- -# Sub: Config::IniFiles::_section::DELETE -# -# Args: $key -# $key The key to remove from the hash -# -# Description: Removes the specified key from the hash and -# returns its former value. -# ---------------------------------------------------------- -# Date Modification Author -# ---------------------------------------------------------- -# 2001Apr04 Fixed -nocase bug JW -# ---------------------------------------------------------- -sub DELETE { - my ($self, $key) = @_; - my $retval=$self->{config}->val($self->{section}, $key); - $self->{config}->delval($self->{section}, $key); - return $retval; -} # end DELETE +=item while (($k, $v) = each %{$ini{$section}}) {...} -# ---------------------------------------------------------- -# Sub: Config::IniFiles::_section::CLEAR -# -# Args: (None) -# -# Description: Empties the entire hash -# ---------------------------------------------------------- -# Date Modification Author -# ---------------------------------------------------------- -# ---------------------------------------------------------- -sub CLEAR { - my ($self) = @_; - return $self->{config}->DeleteSection($self->{section}); -} # end CLEAR +=item if( exists %{$ini{$section}}, $parameter ) {...} -# ---------------------------------------------------------- -# Sub: Config::IniFiles::_section::EXISTS -# -# Args: $key -# $key The key to look for -# -# Description: Returns whether the key exists -# ---------------------------------------------------------- -# Date Modification Author -# ---------------------------------------------------------- -# 2001Apr04 Fixed -nocase bug JW -# ---------------------------------------------------------- -sub EXISTS { - my ($self, $key) = @_; - return $self->{config}->exists($self->{section},$key); -} # end EXISTS +=back -# ---------------------------------------------------------- -# Sub: Config::IniFiles::_section::FIRSTKEY -# -# Args: (None) -# -# Description: Returns the first key in the hash -# ---------------------------------------------------------- -# Date Modification Author -# ---------------------------------------------------------- -# ---------------------------------------------------------- -sub FIRSTKEY { - my $self = shift; +When tied to a hash, you use the Perl C and C +functions to iteratively list the parameters (C) or +parameters and their values (C) in a given section. - $self->{tied_enumerator}=0; - return $self->NEXTKEY(); -} # end FIRSTKEY +You can also use the Perl C function to see if a +parameter is defined in a given section. -# ---------------------------------------------------------- -# Sub: Config::IniFiles::_section::NEXTKEY -# -# Args: $last -# $last The last key accessed by the interator -# -# Description: Returns the next key in line -# ---------------------------------------------------------- -# Date Modification Author -# ---------------------------------------------------------- -# ---------------------------------------------------------- -sub NEXTKEY { - my $self = shift; - my( $last ) = @_; +Note that none of these will return parameter names that +are part of the default section (if set), although accessing +an unknown parameter in the specified section will return a +value from the default section if there is one. - my $i=$self->{tied_enumerator}++; - my @keys = $self->{config}->Parameters($self->{section}); - my $key=$keys[$i]; - return if (! defined $key); - return wantarray ? ($key, $self->FETCH($key)) : $key; -} # end NEXTKEY +=head2 Section names +=over 4 -# ---------------------------------------------------------- -# Sub: Config::IniFiles::_section::DESTROY -# -# Args: (None) -# -# Description: Called on cleanup -# ---------------------------------------------------------- -# Date Modification Author -# ---------------------------------------------------------- -# ---------------------------------------------------------- -sub DESTROY { - # my $self = shift -} # end DESTROY +=item foreach( keys %ini ) {...} -1; +=item while (($k, $v) = each %ini) {...} + +=item if( exists %ini, $section ) {...} + +=back + +When tied to a hash, you use the Perl C and C +functions to iteratively list the sections in the ini file. + +You can also use the Perl C function to see if a +section is defined in the file. =head1 IMPORT / DELTA FEATURES @@ -3088,9 +3052,6 @@ result, that is, [section2] and arg0 would cease to exist in the C<$overlay> object. -=cut - - =head1 DIAGNOSTICS =head2 @Config::IniFiles::errors @@ -3156,7 +3117,7 @@ Bernie Cosell, Alan Young, Alex Satrapa, Mike Blazer, Wilbert van de Pieterman, Steve Campbell, Robert Konigsberg, Scott Dellinger, R. Bernstein, Daniel Winkelmann, Pires Claudio, Adrian Phillips, -Marek Rouchal, Luc St Louis, Adam Fischler, Kay Röpke, Matt Wilson, +Marek Rouchal, Luc St Louis, Adam Fischler, Kay Röpke, Matt Wilson, Raviraj Murdeshwar and Slaven Rezic, Florian Pfaff Geez, that's a lot of people. And apologies to the folks who were missed. @@ -3184,10 +3145,140 @@ This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. -=cut +=head1 AUTHOR -1; +Shlomi Fish -# Please keep the following within the last four lines of the file -#[JW for editor]:mode=perl:tabSize=8:indentSize=2:noTabs=true:indentOnEnter=true: +=head1 COPYRIGHT AND LICENSE + +This software is copyright (c) 2000 by RBOW and others. + +This is free software; you can redistribute it and/or modify it under +the same terms as the Perl 5 programming language system itself. + +=head1 BUGS + +Please report any bugs or feature requests on the bugtracker website +http://rt.cpan.org/NoAuth/Bugs.html?Dist=Config-IniFiles or by email to +bug-config-inifiles@rt.cpan.org. + +When submitting a bug or request, please include a test-file or a +patch to an existing test-file that illustrates the bug or desired +feature. + +=for :stopwords cpan testmatrix url annocpan anno bugtracker rt cpants kwalitee diff irc mailto metadata placeholders metacpan + +=head1 SUPPORT + +=head2 Perldoc + +You can find documentation for this module with the perldoc command. + perldoc Config::IniFiles + +=head2 Websites + +The following websites have more information about this module, and may be of help to you. As always, +in addition to those websites please use your favorite search engine to discover more resources. + +=over 4 + +=item * + +MetaCPAN + +A modern, open-source CPAN search engine, useful to view POD in HTML format. + +L + +=item * + +Search CPAN + +The default CPAN search engine, useful to view POD in HTML format. + +L + +=item * + +RT: CPAN's Bug Tracker + +The RT ( Request Tracker ) website is the default bug/issue tracking system for CPAN. + +L + +=item * + +AnnoCPAN + +The AnnoCPAN is a website that allows community annotations of Perl module documentation. + +L + +=item * + +CPAN Ratings + +The CPAN Ratings is a website that allows community ratings and reviews of Perl modules. + +L + +=item * + +CPAN Forum + +The CPAN Forum is a web forum for discussing Perl modules. + +L + +=item * + +CPANTS + +The CPANTS is a website that analyzes the Kwalitee ( code metrics ) of a distribution. + +L + +=item * + +CPAN Testers + +The CPAN Testers is a network of smokers who run automated tests on uploaded CPAN distributions. + +L + +=item * + +CPAN Testers Matrix + +The CPAN Testers Matrix is a website that provides a visual overview of the test results for a distribution on various Perls/platforms. + +L + +=item * + +CPAN Testers Dependencies + +The CPAN Testers Dependencies is a website that shows a chart of the test results of all dependencies for a distribution. + +L + +=back + +=head2 Bugs / Feature Requests + +Please report any bugs or feature requests by email to C, or through +the web interface at L. You will be automatically notified of any +progress on the request by the system. + +=head2 Source Code + +The code is open to the world, and available for you to hack on. Please feel free to browse it and play +with it, or whatever. If you want to contribute patches, please send me a diff or prod me to pull +from your repository :) + +L + + git clone ssh://git@github.com:shlomif/perl-Config-IniFiles.git + +=cut diff -Nru libconfig-inifiles-perl-2.88/Makefile.PL libconfig-inifiles-perl-2.94/Makefile.PL --- libconfig-inifiles-perl-2.88/Makefile.PL 2015-07-10 08:37:30.000000000 +0000 +++ libconfig-inifiles-perl-2.94/Makefile.PL 2016-11-29 17:30:06.000000000 +0000 @@ -1,7 +1,91 @@ -# Note: this file was auto-generated by Module::Build::Compat version 0.4214 -require 5.008; - use Module::Build::Compat 0.02; - use lib 'inc'; - Module::Build::Compat->run_build_pl(args => \@ARGV); - require Test::Run::Builder; - Module::Build::Compat->write_makefile(build_class => 'Test::Run::Builder'); +# This file was automatically generated by Dist::Zilla::Plugin::MakeMaker v5.047. +use strict; +use warnings; + +use 5.008; + +use ExtUtils::MakeMaker; + +my %WriteMakefileArgs = ( + "ABSTRACT" => "A module for reading .ini-style configuration files.", + "AUTHOR" => "Shlomi Fish ", + "BUILD_REQUIRES" => { + "Module::Build" => "0.28" + }, + "CONFIGURE_REQUIRES" => { + "ExtUtils::MakeMaker" => 0, + "Module::Build" => "0.28" + }, + "DISTNAME" => "Config-IniFiles", + "LICENSE" => "perl", + "MIN_PERL_VERSION" => "5.008", + "NAME" => "Config::IniFiles", + "PREREQ_PM" => { + "Carp" => 0, + "Fcntl" => 0, + "File::Basename" => 0, + "File::Temp" => 0, + "IO::Scalar" => 0, + "List::Util" => "1.33", + "Symbol" => 0, + "strict" => 0, + "vars" => 0, + "warnings" => 0 + }, + "TEST_REQUIRES" => { + "English" => 0, + "Exporter" => 0, + "File::Spec" => 0, + "File::Temp" => 0, + "IO::File" => 0, + "IO::Handle" => 0, + "IPC::Open3" => 0, + "Scalar::Util" => 0, + "Test::More" => 0, + "base" => 0, + "blib" => "1.01", + "lib" => 0 + }, + "VERSION" => "2.94", + "test" => { + "TESTS" => "t/*.t" + } +); + + +my %FallbackPrereqs = ( + "Carp" => 0, + "English" => 0, + "Exporter" => 0, + "Fcntl" => 0, + "File::Basename" => 0, + "File::Spec" => 0, + "File::Temp" => 0, + "IO::File" => 0, + "IO::Handle" => 0, + "IO::Scalar" => 0, + "IPC::Open3" => 0, + "List::Util" => "1.33", + "Module::Build" => "0.28", + "Scalar::Util" => 0, + "Symbol" => 0, + "Test::More" => 0, + "base" => 0, + "blib" => "1.01", + "lib" => 0, + "strict" => 0, + "vars" => 0, + "warnings" => 0 +); + + +unless ( eval { ExtUtils::MakeMaker->VERSION(6.63_03) } ) { + delete $WriteMakefileArgs{TEST_REQUIRES}; + delete $WriteMakefileArgs{BUILD_REQUIRES}; + $WriteMakefileArgs{PREREQ_PM} = \%FallbackPrereqs; +} + +delete $WriteMakefileArgs{CONFIGURE_REQUIRES} + unless eval { ExtUtils::MakeMaker->VERSION(6.52) }; + +WriteMakefile(%WriteMakefileArgs); diff -Nru libconfig-inifiles-perl-2.88/MANIFEST libconfig-inifiles-perl-2.94/MANIFEST --- libconfig-inifiles-perl-2.88/MANIFEST 2015-07-10 08:37:30.000000000 +0000 +++ libconfig-inifiles-perl-2.94/MANIFEST 2016-11-29 17:30:06.000000000 +0000 @@ -1,15 +1,19 @@ +# This file was automatically generated by Dist::Zilla::Plugin::Manifest v5.047. Build.PL Changes LICENSE MANIFEST +MANIFEST.SKIP META.json META.yml Makefile.PL OLD-Changes.txt README +dist.ini inc/Test/Run/Builder.pm lib/Config/IniFiles.pm scripts/tag-release.pl +t/00-compile.t t/00load.t t/01basic.t t/02weird.t @@ -43,8 +47,11 @@ t/31comments_with_spaces.t t/32mswin-outputs-contain-crs.t t/33update-using-tied-fh-w-shorter-names.t +t/34trailing-comments-double-delimeter.t t/allowed-comment-chars.ini t/array.ini +t/author-pod-coverage.t +t/author-pod-syntax.t t/bad.ini t/blank.ini t/brackets-in-values.ini @@ -63,6 +70,9 @@ t/non-contiguous-groups.ini t/pod-coverage.t t/pod.t +t/release-cpan-changes.t +t/release-kwalitee.t t/style-trailing-space.t t/test.ini t/trailing-comments.ini +weaver.ini diff -Nru libconfig-inifiles-perl-2.88/MANIFEST.SKIP libconfig-inifiles-perl-2.94/MANIFEST.SKIP --- libconfig-inifiles-perl-2.88/MANIFEST.SKIP 1970-01-01 00:00:00.000000000 +0000 +++ libconfig-inifiles-perl-2.94/MANIFEST.SKIP 2016-11-29 17:30:06.000000000 +0000 @@ -0,0 +1 @@ +~$ diff -Nru libconfig-inifiles-perl-2.88/META.json libconfig-inifiles-perl-2.94/META.json --- libconfig-inifiles-perl-2.88/META.json 2015-07-10 08:37:30.000000000 +0000 +++ libconfig-inifiles-perl-2.94/META.json 2016-11-29 17:30:06.000000000 +0000 @@ -3,42 +3,46 @@ "author" : [ "Shlomi Fish " ], - "dynamic_config" : 1, - "generated_by" : "Module::Build version 0.4214", + "dynamic_config" : 0, + "generated_by" : "Dist::Zilla version 5.047, CPAN::Meta::Converter version 2.150005", "keywords" : [ - "ini", - "files", "config", "configuration", - "ini files", - "windows", - "tie", + "files", + "ini", + "ini", + "files", "pure-perl", - "tested" + "tested", + "tie", + "windows" ], "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", - "version" : "2" + "version" : 2 }, "name" : "Config-IniFiles", "prereqs" : { "build" : { "requires" : { - "File::Spec" : "0", - "List::Util" : "0", - "Scalar::Util" : "0", - "Test::More" : "0", - "base" : "0", - "strict" : "0", - "warnings" : "0" + "Module::Build" : "0.28" } }, "configure" : { "requires" : { - "Module::Build" : "0.36" + "ExtUtils::MakeMaker" : "0", + "Module::Build" : "0.28" + } + }, + "develop" : { + "requires" : { + "Pod::Coverage::TrustPod" : "0", + "Test::CPAN::Changes" : "0.19", + "Test::Pod" : "1.41", + "Test::Pod::Coverage" : "1.08" } }, "runtime" : { @@ -47,30 +51,50 @@ "Fcntl" : "0", "File::Basename" : "0", "File::Temp" : "0", + "IO::Scalar" : "0", "List::Util" : "1.33", "Symbol" : "0", "perl" : "5.008", "strict" : "0", + "vars" : "0", "warnings" : "0" } + }, + "test" : { + "requires" : { + "English" : "0", + "Exporter" : "0", + "File::Spec" : "0", + "File::Temp" : "0", + "IO::File" : "0", + "IO::Handle" : "0", + "IPC::Open3" : "0", + "Scalar::Util" : "0", + "Test::More" : "0", + "base" : "0", + "blib" : "1.01", + "lib" : "0" + } } }, "provides" : { "Config::IniFiles" : { "file" : "lib/Config/IniFiles.pm", - "version" : "2.88" + "version" : "2.94" } }, "release_status" : "stable", "resources" : { - "homepage" : "https://sourceforge.net/projects/config-inifiles/", - "license" : [ - "http://dev.perl.org/licenses/" - ], + "bugtracker" : { + "mailto" : "bug-config-inifiles@rt.cpan.org", + "web" : "http://rt.cpan.org/NoAuth/Bugs.html?Dist=Config-IniFiles" + }, "repository" : { - "url" : "https://bitbucket.org/shlomif/perl-config-inifiles" + "type" : "git", + "url" : "ssh://git@github.com:shlomif/perl-Config-IniFiles.git", + "web" : "https://github.com/shlomif/perl-Config-IniFiles" } }, - "version" : "2.88", - "x_serialization_backend" : "JSON::PP version 2.27300" + "version" : "2.94" } + diff -Nru libconfig-inifiles-perl-2.88/META.yml libconfig-inifiles-perl-2.94/META.yml --- libconfig-inifiles-perl-2.88/META.yml 2015-07-10 08:37:30.000000000 +0000 +++ libconfig-inifiles-perl-2.94/META.yml 2016-11-29 17:30:06.000000000 +0000 @@ -3,27 +3,35 @@ author: - 'Shlomi Fish ' build_requires: + English: '0' + Exporter: '0' File::Spec: '0' - List::Util: '0' + File::Temp: '0' + IO::File: '0' + IO::Handle: '0' + IPC::Open3: '0' + Module::Build: '0.28' Scalar::Util: '0' Test::More: '0' base: '0' - strict: '0' - warnings: '0' + blib: '1.01' + lib: '0' configure_requires: - Module::Build: '0.36' -dynamic_config: 1 -generated_by: 'Module::Build version 0.4214, CPAN::Meta::Converter version 2.150005' + ExtUtils::MakeMaker: '0' + Module::Build: '0.28' +dynamic_config: 0 +generated_by: 'Dist::Zilla version 5.047, CPAN::Meta::Converter version 2.150005' keywords: - - ini - - files - config - configuration - - 'ini files' - - windows - - tie + - files + - ini + - ini + - files - pure-perl - tested + - tie + - windows license: perl meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html @@ -32,20 +40,20 @@ provides: Config::IniFiles: file: lib/Config/IniFiles.pm - version: '2.88' + version: '2.94' requires: Carp: '0' Fcntl: '0' File::Basename: '0' File::Temp: '0' + IO::Scalar: '0' List::Util: '1.33' Symbol: '0' perl: '5.008' strict: '0' + vars: '0' warnings: '0' resources: - homepage: https://sourceforge.net/projects/config-inifiles/ - license: http://dev.perl.org/licenses/ - repository: https://bitbucket.org/shlomif/perl-config-inifiles -version: '2.88' -x_serialization_backend: 'CPAN::Meta::YAML version 0.016' + bugtracker: http://rt.cpan.org/NoAuth/Bugs.html?Dist=Config-IniFiles + repository: ssh://git@github.com:shlomif/perl-Config-IniFiles.git +version: '2.94' diff -Nru libconfig-inifiles-perl-2.88/OLD-Changes.txt libconfig-inifiles-perl-2.94/OLD-Changes.txt --- libconfig-inifiles-perl-2.88/OLD-Changes.txt 2015-07-10 08:37:30.000000000 +0000 +++ libconfig-inifiles-perl-2.94/OLD-Changes.txt 2016-11-29 17:30:06.000000000 +0000 @@ -1,453 +1,3 @@ -Version 2.84 ( Mon 13 Apr 21:24:07 IDT 2015 ) ------------------------------------------------- - -* Fix https://rt.cpan.org/Public/Bug/Display.html?id=103496 - - Improperly update while using _write_config_with_a_made_fh - - Thanks to JORISD for the report, a proposed patch and a test case. - -Version 2.83 ( Mon 27 Jan 10:51:14 IST 2014 ) ------------------------------------------------- - -* Add a LICENSE file and section. - - Kwalitee. - -* Minimum version is now perl-5.8.0. - - CPANTS Kwalitee. - -Version 2.82 ( Tue 21 May 18:32:23 IDT 2013 ) ------------------------------------------------- - -* Add t/style-trailing-space.t . - - Remove trailing space. - -Version 2.81 ( Thu 16 May 13:31:34 IDT 2013 ) ------------------------------------------------- - -* Add the CopySection method to copy a section. - - Thanks to James Rouzier. - -Version 2.80 ( Tue 14 May 22:22:55 IDT 2013 ) ------------------------------------------------- - -* Add the RenameSection method to rename a section. - - Thanks to James Rouzier. - -Version 2.79 ( Mon 6 May 10:02:47 IDT 2013 ) ------------------------------------------------- - -* Fix test failures with Pod-Simple-3.28: - - http://www.cpantesters.org/cpan/report/98f9d3a8-b557-11e2-9adc-3d5fc1508286 - -Version 2.78 ( Sun 21 Oct 13:14:39 IST 2012 ) ------------------------------------------------- - -* Fix https://rt.cpan.org/Public/Bug/Display.html?id=80259: - - Warnings on undefined value in length in perl-5.10.x. - -Version 2.77 ( Thu Jun 21 19:35:23 IDT 2012 ) ------------------------------------------------- - -* Bump the List::MoreUtils dependency to '0.33'. - - https://rt.cpan.org/Ticket/Display.html?id=77930 - - Thanks to TJENNESS for the report. - -Version 2.76 ( Fri Jun 15 17:44:31 IDT 2012 ) ------------------------------------------------- - -* Correct the documentation regarding the syntax of calling ->WriteConfig(), -which always accepts a mandatory filename and not just -"->WriteConfig(-delta => 1)". - - Thanks to the Mageia Linux QA team at: - - https://bugs.mageia.org/show_bug.cgi?id=6024#c17 - -Version 2.75 ( Fri May 25 15:25:29 IDT 2012 ) ------------------------------------------------- - -* Add "=encoding utf8" to the POD of lib/Config/IniFiles.pm - - The POD contained wide characters and it is now an error in - with recent Test::Pod and Pod::Simple. - - See for example: http://www.cpantesters.org/cpan/report/89000ebe-a5bc-11e1-a439-fa78ab6c2661 - -Version 2.74 ( Thu May 24 00:46:37 IDT 2012 ) ------------------------------------------------- - -* Made sure .ini files are output on Win32 with CR-LF pairs. - - Thanks to Ben Johnson for reporting the bug and for a preliminary - test script. - - Add t/32mswin-outputs-contain-crs.t - -Version 2.73 ( Mon May 14 10:46:38 IDT 2012 ) ------------------------------------------------- - -* Regression bug fix: temporary file creation error should generate a warning -and return undef() instead of throwing an exception. - - https://rt.cpan.org/Ticket/Display.html?id=77039 - - Thanks to Adam D. Barrat for noticing that, and Gregor Herrmann - from Debian for reporting this issue. - -Version 2.72 ( Sat May 5 19:19:09 IDT 2012 ) ------------------------------------------------- - -* More refactoring. - -* Now requiring Fcntl (which is a core module) for SEEK_SET() and SEEK_CUR(). - -* Update the contact information on the POD. - -* Maintainer’s special birthday (5-May) release. - -Version 2.71: ( Wed May 2 10:59:04 IDT 2012 ) ------------------------------------------------- - -* SECURITY BUG FIX: Config::IniFiles used to write to a temporary filename -with a predictable name ("${filename}-new") which opens the door for potential -exploits. - -* Now requiring List::MoreUtils (for any() and other functions), File::Temp -and File::Basename . - -* Add "use warnings;" to lib/Config/IniFiles.pm . - -* More refactoring. - -Version 2.70: ( Fri Apr 6 12:47:25 IDT 2012 ) ------------------------------------------------- - -* Fix the test dependency (in the new t/31comments_with_spaces.t test) -on IO::stringy (now writing it to a file). - - This caused some CPAN Testers failures such as: - - http://www.cpantesters.org/cpan/report/c16b9978-7f40-11e1-9d6f-f6dbfa7543f5 - -* Modernized some of the open() calls in the test files. - - three args open, lexcial file handles, etc. - -Version 2.69: ( Thu Apr 5 12:05:46 IDT 2012 ) ------------------------------------------------- - -* Fix https://sourceforge.net/tracker/?func=detail&aid=3388382&group_id=6926&atid=106926 - - "Trailing comments with whitespace are not recognised" - - Thanks to briconaut for the report and to rbowen for the patch and test. - -* Add the method ->OutputConfigToFileHandle which is a more explicit and -safer version of ->OutputConfig. - - ->OutputConfig now uses it. - - all existing tests pass, and a new test was added to - t/15store-and-retrieve-here-doc-terminator.t . - -* Started refactoring the code. - - Expand tabs to spaces. - - many other small changes. - -Version 2.68: ( Tue Jun 21 22:14:39 IDT 2011 ) ------------------------------------------------- - -* Typo correction: https://rt.cpan.org/Ticket/Display.html?id=69003 - -Version 2.67: ( Tue Jun 21 14:55:07 IDT 2011 ) ------------------------------------------------- - -* Made sure that parameters with empty values will be written to the file. - - Fixes https://rt.cpan.org/Public/Bug/Display.html?id=68554 . - -Version 2.66: ( Sat Jan 29 18:28:53 IST 2011 ) ------------------------------------------------- - -* Add support for the handle_trailing_comment option to support trailing -comments at the end of a param=value line. - - Fixes https://rt.cpan.org/Public/Bug/Display.html?id=43843 . - - Original patch by Peter Xu. - - Some comments and corrections of the patch by Shlomi Fish (the current - Config-IniFiles maintainer.) - -Version 2.65: ( Thu Nov 25 12:00:00 IST 2010 ) ------------------------------------------------- - -* Replaced calls to File::Temp's tempfile() with those to its tempdir() -followed by File::Spec->catfile() - hopefully fixes the CPAN smoking failures -on Win32. - -Version 2.64: ( Sat Nov 20 11:47:07 IST 2010 ) ------------------------------------------------- - -* Fixed https://rt.cpan.org/Ticket/Display.html?id=63177 - - tag-release.pl was installed into lib/Config by EU-MM. Converted the - Makefile.PL to a wrapper around Build.PL by using Module-Build's - create_makefile_pl='small' instead of create_makefile_pl='traditional' . - -Version 2.63: ( Fri Nov 19 16:50:58 IST 2010 ) ------------------------------------------------- - -* Fixed https://rt.cpan.org/Ticket/Display.html?id=63079 by -refactoring/bug-fixing the main module into the _OutputParam method, which -handles the output of multi-line and single-line parameters with all the edge -cases. - - The reported problem was that EOT was encountered again in certain cases - in the output string, and now we make sure the program always avoid it. The - reappearance of the bug was caused due to duplicate code, which was now - eliminated. - -Version 2.62: ( Fri Nov 19 15:31:36 IST 2010 ) ------------------------------------------------- - -* Applied a modified version of Steven Haryanto's commit -ac6ace8bf09fabba4e6ea3b2826ff13cd30507bc : - - When writing config with fallback section, also omit the fallback - section header, like the original file. Also, change - t/19param-found-outside-section.t a bit to not require IO::Scalar and use - File::Temp instead (we are testing WriteConfig anyway). - - Shlomi Fish fixed unless'es to if's and converted a do { ... } if call to - a block conditional, as well as made the open statements on t/19param - more secure. - -* Add -nomultiline option. - -Applied a modified version of Steven Haryanto's commit - 2181f438d40b54c290a2dda5627a244c6e467a7b - -Version 2.61: ( Sun Nov 14 10:52:39 IST 2010 ) ------------------------------------------------- - -* Correct the opening comment on t/26scalar-filehandle.t to point to the right -bug URL and to include the title of the bug - - https://rt.cpan.org/Ticket/Display.html?id=54997 - this is the right - URL. - -* Correct the check in t/26scalar-filehandle.t - it should check that Perl -is above 5.8.x - not that IO::Scalar is available, because we use -open my $fh, "<", \$buffer; - -Version 2.60: ( Sat Nov 13 09:21:23 IST 2010 ) ------------------------------------------------- - -* Made t/19param-found-outside-section.t pass in case IO::Scalar is not -installed. - - We got many failing test reports due to that, like: - - http://www.cpantesters.org/cpan/report/eb3df0f4-eeb7-11df-b934-9430e12b63f3 - -Version 2.59: ( Fri Nov 12 13:21:02 IST 2010 ) ------------------------------------------------- - -* Applied a patch from "sharyanto" about enabling the -fallback parameter -to new for items outside any section. - - https://rt.cpan.org/Public/Bug/Display.html?id=62944 - -* Add the tag-release.pl file to tag a release. - -Version 2.58: ( Mon May 17 10:43:06 IDT 2010 ) ------------------------------------------------- - -* Incorporated a fix for some broken POD. - - https://rt.cpan.org/Ticket/Display.html?id=51617 - - Thanks to Frank Wiegand for reporting it. - -* Got rid of \r/carriage-returns's in lib/Config/IniFiles.pm . - - It interfered with applying a patch. - -* Applied a patch to get rid of indirect object notation in the documentation -and code ("new Config::IniFiles") and replace it with Config::IniFiles->new(). - - https://rt.cpan.org/Ticket/Display.html?id=57536 - - Thanks to Tom Molesworth for the patch. - -Version 2.57: ( Mon Mar 1 15:49:54 IST 2010 ) ------------------------------------------------- - -* Fixed failure to read the ini file contents from a filehandle made out of a - scalar - - https://rt.cpan.org/Ticket/Display.html?id=45997 - - Applied a patch by Nicolas Mendoza - thanks! - -Version 2.56: ( Thu Dec 31 06:55:01 IST 2009 ) ------------------------------------------------- - -* Hopefully fixed the skip() in t/07misc.t - - https://rt.cpan.org/Public/Bug/Display.html?id=53238 - - It was skip($num, $label) instead of skip($label, $num) and it - lacked a "SKIP:" label. - -Version 2.55: ( Tue Dec 22 17:42:59 IST 2009 ) ------------------------------------------------- - -* Refactoring the tests: - - Converted t/05hash.t to Test::More and modernised it. - - Converted t/06oo.t to Test::More and modernised it. - - Testing: extracted t/lib/Config/IniFiles/TestPaths.pm. - - Converted t/10delta.t to Test::More and modernised it. - - Add the t_unlink method. - -Version 2.54: ( Wed Nov 18 12:00:44 IST 2009 ) ------------------------------------------------- - -* Adapted t/25line-endings.t to perl-5.6.x . - - http://www.nntp.perl.org/group/perl.cpan.testers/2009/11/msg6026936.html - - Failure there. - - did that by replacing open ">:raw" with "binmode()". - -Version 2.53: ( Fri Nov 13 11:52:40 IST 2009 ) ------------------------------------------------- - -* Converted t/01basic.t to Test::More and modernised it. -* Converted t/02weird.t to Test::More and modernised it. -* Converted t/04import.t to Test::More and modernised it. -* Applied a patch from Steffen Heinrich (SHE) for: - - https://rt.cpan.org/Ticket/Display.html?id=51445 - - included t/25line-endings.t - - modified a little. - -Version 2.52: ( Sun Jun 28 16:16:09 IDT 2009 ) ------------------------------------------------- - -* Added more keys to Build.PL (author, etc.) -* Added resources and keywords to META.yml. -* Converted t/00load.t to Test-More. -* No longer chdir()-ing into "t" in t/00load.t -* Got rid of the "new Config::IniFiles" syntax in the tests. -- replaced with Config::IniFiles->new(). -- see "Perl Best Practices", "Programming Perl", 3rd Edition - -Version 2.51: ( Mon Jun 8 12:38:50 IDT 2009 ) ------------------------------------------------- - -* Fixed "#46721: $config->exists() does not pay attention to -nocase => 1" -- https://rt.cpan.org/Ticket/Display.html?id=46721 - -Version 2.50: ( Sun May 31 14:54:14 IDT 2009 ) ------------------------------------------------- - -* Fixed the POD for SetParameterEOT: -- https://rt.cpan.org/Ticket/Display.html?id=28375 -* Added t/pod.t and t/pod-coverage.t and fixed the problems they pointed: -- Removed some trailing pod. - - http://rt.cpan.org/Ticket/Display.html?id=46549 -- Documented the ->OutputConfig() method. - -Version 2.49: ( Sat May 2 17:16:28 IDT 2009 ) ------------------------------------------------- - -* Fixed bug https://rt.cpan.org/Public/Bug/Display.html?id=45209 : -- Makefile.PL lacked the empty "PL_FILES" directive. - -* Fixed bug https://rt.cpan.org/Ticket/Display.html?id=45588 : -- Failure to read ini file contents from a scalar (via "IO::Scalar"). -- added t/23scalar-ref.t - -Version 2.48: ( Tue Apr 7 15:24:55 IDT 2009 ) ------------------------------------------------- - -* Fixed bug https://rt.cpan.org/Ticket/Display.html?id=30402 : -- Trailing comments at the end of the file are not written. -- added a test (t/22trailing-comment-lines.t ) and a test file. - -* Reverted the fix to bug #34067 ( "Multiline values returned as array -references in tied hash interface"), as it could potentially break a lot -of old code, and was not such a good idea, and was less preferable from -interface-sanity reasons. - -* Documented the old API in regards to bug #34067 - see above. - -Version 2.47: ( Wed Jan 21 11:32:24 IST 2009 ) ------------------------------------------------- - -* Fixed bug https://rt.cpan.org/Ticket/Display.html?id=34067 : -- Multiline values returned as array references in tied hash interface -- This was as opposed to the documentation. -- added t/21multiline-values-in-tied.t and fixed some older tests. - -* Moved the eval'ed debugging code (_assert_invariants) to its own module, -without the eval so the perl-5.6.2 will be happy. Now explicitly use'ing it -from t/lib where appropriate. See for example: -- http://www.nntp.perl.org/group/perl.cpan.testers/2009/01/msg3101788.html - -Version 2.46: ( Sat Jan 17 16:31:52 IST 2009 ) ------------------------------------------------- - -* Added regression tests for bug #1720915 -- https://sourceforge.net/tracker/index.php?func=detail&aid=1720915&group_id=6926&atid=106926 -- the bug seems to be fixed in trunk. - -* Added a regression test for RT #36584 -- https://rt.cpan.org/Ticket/Display.html?id=36584 -- the bug seems to be fixed in trunk. - -* Added a regression test for RT #36309 -- https://rt.cpan.org/Ticket/Display.html?id=36309 -- Testing for allowedcommentchars. -- the bug seems to be fixed in trunk. - -* Converted t/03comments.t to Test::More and did other cleanups in preparation -of solving a few bugs. - -* Now GetSectionComment does the documented behaviour in scalar context and -returns the string joined by newlines: -- https://rt.cpan.org/Ticket/Display.html?id=8612 - -* Now GetParameterComment returns the right thing in scalar context (joined -by newlines). It was documented and tested. -- https://rt.cpan.org/Ticket/Display.html?id=8612 - -Version 2.45: ( Sat Dec 27 17:21:45 IST 2008 ) ------------------------------------------------- - -* Fixed bug #1230339: -- https://sourceforge.net/tracker/index.php?func=detail&aid=1230339&group_id=6926&atid=106926 -- Can't store and retrive "\nEOT\n" -- Fixed by checking for the existence of the here-doc terminator in the -string, and if so appending more characters. - -* Fixed bug #1565180 -- https://sourceforge.net/tracker/index.php?func=detail&aid=1565180&group_id=6926&atid=106926 -- -nocase breaks -default. -- If a default-values section is specified, then -nocase did not look it up -if it's not lowercase. - -* Fixed bug #1831654 -- https://sourceforge.net/tracker/index.php?func=detail&aid=1831654&group_id=6926&atid=106926 -- multiline-values are tainted. -- Added a test as t/17untainted-multiline-values.t - -Version 2.44: ( Thu Dec 25 11:44:07 IST 2008 ) ------------------------------------------------- - -* Eliminated an assignment of $VERSION into itself to "get rid of warnings". -Only caused warnings at Build.PL, and is unnecessary now that we -"use vars". - -Version 2.43: ( Thu Dec 4 18:56:02 IST 2008 ) ------------------------------------------------- - -* Placed IniFiles.pm under lib/Config/IniFiles.pm. - -* Added a Build.PL file for the distribution. - -* Moved the ChangeLog to its own file - Changes. - -* Made the README contain the useful information and nothing else - previously -it was a copy of the module's entire POD. - -* Added a regression test for: - -https://sourceforge.net/tracker/index.php?func=detail&aid=778593&group_id=6926&atid=106926 - -When copying tied-hashes representing sections with multiline values as -keys, one used to get a reference instead of a copy. This was fixed in SVN -but not in CPAN. - -* Added a test for opening an empty file, inspired by: - -https://sourceforge.net/tracker/index.php?func=detail&aid=927111&group_id=6926&atid=106926 - -* Added a fix for this bug: - -https://sourceforge.net/tracker/index.php?func=detail&aid=767913&group_id=6926&atid=106926 - -With a test that I wrote myself: in list context, ->val() returned the list -containing (undef()) if there was no such key. Now returning the empty -list which evaluates to false. - -* Added a test for bug - - "[ 2030786 ] Brackets within values are treated as section headers" - -It seems to be fixed on SVN. - Revision 2.40 2003/12/08 10:33:13 domq Documentation tidyup diff -Nru libconfig-inifiles-perl-2.88/t/00-compile.t libconfig-inifiles-perl-2.94/t/00-compile.t --- libconfig-inifiles-perl-2.88/t/00-compile.t 1970-01-01 00:00:00.000000000 +0000 +++ libconfig-inifiles-perl-2.94/t/00-compile.t 2016-11-29 17:30:06.000000000 +0000 @@ -0,0 +1,57 @@ +use 5.006; +use strict; +use warnings; + +# this test was generated with Dist::Zilla::Plugin::Test::Compile 2.054 + +use Test::More; + +plan tests => 1 + ($ENV{AUTHOR_TESTING} ? 1 : 0); + +my @module_files = ( + 'Config/IniFiles.pm' +); + + + +# fake home for cpan-testers +use File::Temp; +local $ENV{HOME} = File::Temp::tempdir( CLEANUP => 1 ); + + +my $inc_switch = -d 'blib' ? '-Mblib' : '-Ilib'; + +use File::Spec; +use IPC::Open3; +use IO::Handle; + +open my $stdin, '<', File::Spec->devnull or die "can't open devnull: $!"; + +my @warnings; +for my $lib (@module_files) +{ + # see L + my $stderr = IO::Handle->new; + + my $pid = open3($stdin, '>&STDERR', $stderr, $^X, $inc_switch, '-e', "require q[$lib]"); + binmode $stderr, ':crlf' if $^O eq 'MSWin32'; + my @_warnings = <$stderr>; + waitpid($pid, 0); + is($?, 0, "$lib loaded ok"); + + shift @_warnings if @_warnings and $_warnings[0] =~ /^Using .*\bblib/ + and not eval { require blib; blib->VERSION('1.01') }; + + if (@_warnings) + { + warn @_warnings; + push @warnings, @_warnings; + } +} + + + +is(scalar(@warnings), 0, 'no warnings found') + or diag 'got warnings: ', ( Test::More->can('explain') ? Test::More::explain(\@warnings) : join("\n", '', @warnings) ) if $ENV{AUTHOR_TESTING}; + + diff -Nru libconfig-inifiles-perl-2.88/t/00load.t libconfig-inifiles-perl-2.94/t/00load.t --- libconfig-inifiles-perl-2.88/t/00load.t 2015-07-10 08:37:30.000000000 +0000 +++ libconfig-inifiles-perl-2.94/t/00load.t 2016-11-29 17:30:06.000000000 +0000 @@ -1,179 +1,181 @@ -#!/usr/bin/perl -use strict; -use warnings; - -# Should be 15 -use Test::More tests => 15; - -use lib "./t/lib"; - -use Config::IniFiles::TestPaths; - -use IO::File; - -BEGIN -{ - # TEST - use_ok ('Config::IniFiles'); -} - -my $ini; - -# a simple filehandle, such as STDIN -#** If anyone can come up with a test for STDIN that would be great -# The following could be run in a separate file with data piped -# in. e.g. ok( !system( "$^X stdin.pl < test.ini" ) ); -# But it's only good on platforms that support redirection. -# use strict; -# use Config::IniFiles; -# my $ini = Config::IniFiles->new(-file => STDIN); -# exit $ini ? 0; 1 - -local *CONFIG; -# TEST -# a filehandle glob, such as *CONFIG -if( open( CONFIG, "<", t_file("test.ini") ) ) { - $ini = Config::IniFiles->new(-file => *CONFIG); - ok ($ini, q{$ini was initialized}); - close CONFIG; -} else { - ok (0, "Could not open file"); -} - -# TEST -# a reference to a glob, such as \*CONFIG -if( open( CONFIG, "<", t_file("test.ini") ) ) { - $ini = Config::IniFiles->new(-file => \*CONFIG); - ok ($ini, q{$ini was initialized with a reference to a glob.}); - close CONFIG; -} else { - ok( 0, q{could not open test.ini}); -} - -# an IO::File object -# TEST -if( my $fh = IO::File->new( t_file("test.ini") )) { - $ini = Config::IniFiles->new(-file => $fh); - ok ($ini, q{$ini was initialized with an IO::File reference.}); - $fh->close; -} else { - ok ( 0, "Could not open file" ); -} # endif - - -# TEST -# Reread on an open handle -if( open( CONFIG, "<", t_file("test.ini") ) ) { - $ini = Config::IniFiles->new(-file => \*CONFIG); - ok (($ini && $ini->ReadConfig()), qq{ReadConfig() was successful}); - close CONFIG; -} else { - ok (0, "Could not open file" ); -} - - -# TEST -if( open( CONFIG, "<", t_file("test.ini") ) ) { - $ini = Config::IniFiles->new(-file => \*CONFIG); - $ini->SetFileName( t_file('test01.ini') ); - $ini->RewriteConfig(); - close CONFIG; - # Now test opening and re-write to the same handle - chmod(0644, t_file("test01.ini")); - if(! open( CONFIG, "+<", t_file("test01.ini" ) )) { - die "Could not open " . t_file("test01.ini") . " for read/write"; - } - $ini = Config::IniFiles->new(-file => \*CONFIG); - my $badname = scalar(\*CONFIG); - # Have to use open/close because -e seems to be always true! - ok( $ini && $ini->RewriteConfig() && !(open( I, $badname ) && close(I)) , - qq{Write to a new file name and write to it}, - ); - close CONFIG; - # In case it failed, remove the file - # (old behavior was to write to a file whose filename is the scalar value of the handle!) - unlink $badname; -} else { - ok (0, "Could not open file"); -} # end if - -# the pathname of a file -$ini = Config::IniFiles->new(-file => t_file("test.ini")); -# TEST -ok ($ini, q{Opening with -file works}); - -# A non-INI file should fail, but not throw warnings -local $@ = ''; -my $ERRORS = ''; -local $SIG{__WARN__} = sub { $ERRORS .= $_[0] }; -eval { $ini = Config::IniFiles->new(-file => t_file("00load.t")) }; -# TEST -ok( - !$@ && !$ERRORS && !defined($ini), - "A non-INI file should fail, but not throw errors" -); - -$@ = ''; -eval { $ini = Config::IniFiles->new(-file => \*DATA) }; -# TEST -ok (!$@ && defined($ini), - "Read in the DATA file without errors" -); - -# Try a file with utf-8 encoding (has a Byte-Order-Mark at the start) -# TEST -$ini = Config::IniFiles->new(-file => t_file("en.ini")); -ok ($ini, - "Try a file with utf-8 encoding (has a Byte-Order-Mark at the start)" -); - - -# Create a new INI file, and set the name using SetFileName -$ini = Config::IniFiles->new(); -my $filename = $ini->GetFileName; -# TEST -ok ((! defined($filename)), - "Not defined filename on fresh Config::IniFiles" -); - -# TEST -$ini->SetFileName(t_file("test9_name.ini")); -$filename = $ini->GetFileName; -is( - $filename, - t_file("test9_name.ini"), - "Check GetFileName method", -); - -$@ = ''; -eval { $ini = Config::IniFiles->new(-file => t_file('blank.ini')); }; -# TEST -ok ((!$@ && !defined($ini)), - "Make sure that no warnings are thrown for an empty file", -); - -$@ = ''; -eval { $ini = Config::IniFiles->new(-file => t_file('blank.ini'), -allowempty=>1); }; -# TEST -ok((!$@ && defined($ini)), - "Empty files should cause no rejection when appropriate switch set", -); - -$@ = ''; -eval { $ini = Config::IniFiles->new(-file => t_file('bad.ini')); }; -# TEST -ok((!$@ && !defined($ini) && @Config::IniFiles::errors), - "A malformed file should throw an error message", -); - -# Clean up when we're done -t_unlink("test01.ini"); - -__END__ -; File that has comments in the first line -; Comments are marked with ';'. -; This should not fail when checking if the file is valid -[section] -parameter=value - - +#!/usr/bin/perl +use strict; +use warnings; + +# Should be 15 +use Test::More tests => 15; + +use lib "./t/lib"; + +use Config::IniFiles::TestPaths; + +use IO::File; + +BEGIN +{ + # TEST + use_ok ('Config::IniFiles'); +} + +my $ini; + +# a simple filehandle, such as STDIN +#** If anyone can come up with a test for STDIN that would be great +# The following could be run in a separate file with data piped +# in. e.g. ok( !system( "$^X stdin.pl < test.ini" ) ); +# But it's only good on platforms that support redirection. +# use strict; +# use Config::IniFiles; +# my $ini = Config::IniFiles->new(-file => STDIN); +# exit $ini ? 0; 1 + +local *CONFIG; +# TEST +# a filehandle glob, such as *CONFIG +if( open( CONFIG, "<", t_file("test.ini") ) ) { + $ini = Config::IniFiles->new(-file => *CONFIG); + ok ($ini, q{$ini was initialized}); + close CONFIG; +} else { + ok (0, "Could not open file"); +} + +# TEST +# a reference to a glob, such as \*CONFIG +if( open( CONFIG, "<", t_file("test.ini") ) ) { + $ini = Config::IniFiles->new(-file => \*CONFIG); + ok ($ini, q{$ini was initialized with a reference to a glob.}); + close CONFIG; +} else { + ok( 0, q{could not open test.ini}); +} + +# an IO::File object +# TEST +if( my $fh = IO::File->new( t_file("test.ini") )) { + $ini = Config::IniFiles->new(-file => $fh); + ok ($ini, q{$ini was initialized with an IO::File reference.}); + $fh->close; +} else { + ok ( 0, "Could not open file" ); +} # endif + + +# TEST +# Reread on an open handle +if( open( CONFIG, "<", t_file("test.ini") ) ) { + $ini = Config::IniFiles->new(-file => \*CONFIG); + ok (($ini && $ini->ReadConfig()), qq{ReadConfig() was successful}); + close CONFIG; +} else { + ok (0, "Could not open file" ); +} + + +use File::Temp qw(tempdir); +use File::Spec (); + +my $dir_name = tempdir(CLEANUP => 1); +my $test01_fn = File::Spec->catfile($dir_name, 'test01.ini'); + +# TEST +if( open( CONFIG, "<", t_file("test.ini") ) ) { + $ini = Config::IniFiles->new(-file => \*CONFIG); + $ini->SetFileName( $test01_fn ); + $ini->RewriteConfig(); + close CONFIG; + # Now test opening and re-write to the same handle + if(! open( CONFIG, "+<", $test01_fn )) { + die "Could not open " . $test01_fn . " for read/write"; + } + $ini = Config::IniFiles->new(-file => \*CONFIG); + my $badname = scalar(\*CONFIG); + # Have to use open/close because -e seems to be always true! + ok( $ini && $ini->RewriteConfig() && !(open( I, $badname ) && close(I)) , + qq{Write to a new file name and write to it}, + ); + close CONFIG; + # In case it failed, remove the file + # (old behavior was to write to a file whose filename is the scalar value of the handle!) + unlink $badname; +} else { + ok (0, "Could not open file"); +} # end if + +# the pathname of a file +$ini = Config::IniFiles->new(-file => t_file("test.ini")); +# TEST +ok ($ini, q{Opening with -file works}); + +# A non-INI file should fail, but not throw warnings +local $@ = ''; +my $ERRORS = ''; +local $SIG{__WARN__} = sub { $ERRORS .= $_[0] }; +eval { $ini = Config::IniFiles->new(-file => t_file("00load.t")) }; +# TEST +ok( + !$@ && !$ERRORS && !defined($ini), + "A non-INI file should fail, but not throw errors" +); + +$@ = ''; +eval { $ini = Config::IniFiles->new(-file => \*DATA) }; +# TEST +ok (!$@ && defined($ini), + "Read in the DATA file without errors" +); + +# Try a file with utf-8 encoding (has a Byte-Order-Mark at the start) +# TEST +$ini = Config::IniFiles->new(-file => t_file("en.ini")); +ok ($ini, + "Try a file with utf-8 encoding (has a Byte-Order-Mark at the start)" +); + + +# Create a new INI file, and set the name using SetFileName +$ini = Config::IniFiles->new(); +my $filename = $ini->GetFileName; +# TEST +ok ((! defined($filename)), + "Not defined filename on fresh Config::IniFiles" +); + +# TEST +$ini->SetFileName(t_file("test9_name.ini")); +$filename = $ini->GetFileName; +is( + $filename, + t_file("test9_name.ini"), + "Check GetFileName method", +); + +$@ = ''; +eval { $ini = Config::IniFiles->new(-file => t_file('blank.ini')); }; +# TEST +ok ((!$@ && !defined($ini)), + "Make sure that no warnings are thrown for an empty file", +); + +$@ = ''; +eval { $ini = Config::IniFiles->new(-file => t_file('blank.ini'), -allowempty=>1); }; +# TEST +ok((!$@ && defined($ini)), + "Empty files should cause no rejection when appropriate switch set", +); + +$@ = ''; +eval { $ini = Config::IniFiles->new(-file => t_file('bad.ini')); }; +# TEST +ok((!$@ && !defined($ini) && @Config::IniFiles::errors), + "A malformed file should throw an error message", +); + +__END__ +; File that has comments in the first line +; Comments are marked with ';'. +; This should not fail when checking if the file is valid +[section] +parameter=value + + diff -Nru libconfig-inifiles-perl-2.88/t/05hash.t libconfig-inifiles-perl-2.94/t/05hash.t --- libconfig-inifiles-perl-2.88/t/05hash.t 2015-07-10 08:37:30.000000000 +0000 +++ libconfig-inifiles-perl-2.94/t/05hash.t 2016-11-29 17:30:06.000000000 +0000 @@ -25,7 +25,7 @@ (tie %ini, 'Config::IniFiles', ( -file => t_file("test.ini"), -default => 'test1', -nocase => 1 ) ), - "Tie to test.ini was succesful." + "Tie to test.ini was successful." ); tied(%ini)->SetFileName(t_file("test05.ini")); @@ -138,7 +138,7 @@ is ($value, 'value4', "Store new section in hash"); # Test 16 -# Writing 2 line multilvalue and returing it +# Writing 2 line multivalue and returning it $ini{newsect} = {}; $ini{test1}{multi_2} = ['line 1', 'line 2']; tied(%ini)->RewriteConfig; @@ -146,7 +146,7 @@ @value = @{$ini{test1}{multi_2}}; # TEST is_deeply ( \@value, ['line 1', 'line 2'], - "Writing 2 line multilvalue and returing it" + "Writing 2 line multivalue and returning it" ); # Test 17 diff -Nru libconfig-inifiles-perl-2.88/t/19param-found-outside-section.t libconfig-inifiles-perl-2.94/t/19param-found-outside-section.t --- libconfig-inifiles-perl-2.88/t/19param-found-outside-section.t 2015-07-10 08:37:30.000000000 +0000 +++ libconfig-inifiles-perl-2.94/t/19param-found-outside-section.t 2016-11-29 17:30:06.000000000 +0000 @@ -9,16 +9,12 @@ use strict; use warnings; -use Test::More; +use Test::More tests => 7; use Config::IniFiles; use File::Spec; -eval "use File::Temp qw(tempdir)"; - -plan skip_all => "File::Temp required for testing" if $@; - -plan tests => 7; +use File::Temp qw(tempdir); { my $dir_name = tempdir(CLEANUP => 1); diff -Nru libconfig-inifiles-perl-2.88/t/25line-endings.t libconfig-inifiles-perl-2.94/t/25line-endings.t --- libconfig-inifiles-perl-2.88/t/25line-endings.t 2015-07-10 08:37:30.000000000 +0000 +++ libconfig-inifiles-perl-2.94/t/25line-endings.t 2016-11-29 17:30:06.000000000 +0000 @@ -24,7 +24,7 @@ # being pedantic, we don't take line breaks from this or an external file for granted my $sample_ini = " - # this is a sample file for testing the proper detection of line endings in Config::Inifiles + # this is a sample file for testing the proper detection of line endings in Config::IniFiles [single values] firstval = first value @@ -32,7 +32,7 @@ # in v2.52 on linux multi values with crlf lines are failing [multi value] - Pathes=< + Paths=< path1 path2 EOT @@ -69,7 +69,7 @@ "Reading a single value from a '$lf_print'-separated file" ); - my @vals = $cfg->val("multi value", "Pathes"); + my @vals = $cfg->val("multi value", "Paths"); # TEST is_deeply( diff -Nru libconfig-inifiles-perl-2.88/t/26scalar-filehandle.t libconfig-inifiles-perl-2.94/t/26scalar-filehandle.t --- libconfig-inifiles-perl-2.88/t/26scalar-filehandle.t 2015-07-10 08:37:30.000000000 +0000 +++ libconfig-inifiles-perl-2.94/t/26scalar-filehandle.t 2016-11-29 17:30:06.000000000 +0000 @@ -18,7 +18,7 @@ use Config::IniFiles; -if ( ! eval "require 5.008;" ) +if ( ! eval { require 5.008; } ) { plan skip_all => "We need filehandles made from scalar which is a feature of Perl above 5.8.x"; } diff -Nru libconfig-inifiles-perl-2.88/t/28nomultiline.t libconfig-inifiles-perl-2.94/t/28nomultiline.t --- libconfig-inifiles-perl-2.88/t/28nomultiline.t 2015-07-10 08:37:30.000000000 +0000 +++ libconfig-inifiles-perl-2.94/t/28nomultiline.t 2016-11-29 17:30:06.000000000 +0000 @@ -1,6 +1,6 @@ #!/usr/bin/perl -use Test::More; +use Test::More tests => 2; use strict; use warnings; @@ -8,19 +8,17 @@ use Config::IniFiles; use File::Spec; -eval "use File::Temp qw(tempdir)"; - -plan skip_all => "File::Temp required for testing" if $@; - -plan tests => 2; +use File::Temp qw(tempdir); { my $dir_name = tempdir(CLEANUP => 1); my $filename = File::Spec->catfile($dir_name, "foo.ini"); my $data = join "", ; - open F, '>', $filename; - print F $data; - close F; + { + open my $out, '>', $filename; + print {$out} $data; + close( $out ); + } my $ini = Config::IniFiles->new(-file => $filename, -nomultiline => 1); diff -Nru libconfig-inifiles-perl-2.88/t/29end-of-line-comment.t libconfig-inifiles-perl-2.94/t/29end-of-line-comment.t --- libconfig-inifiles-perl-2.88/t/29end-of-line-comment.t 2015-07-10 08:37:30.000000000 +0000 +++ libconfig-inifiles-perl-2.94/t/29end-of-line-comment.t 2016-11-29 17:30:06.000000000 +0000 @@ -11,9 +11,7 @@ use lib "./t/lib"; use Config::IniFiles::TestPaths; -eval "use File::Temp qw(tempdir)"; -plan skip_all => "File::Temp required for testing" if $@; - +use File::Temp qw(tempdir); my $writefile = "end-trailing-comment-writeback.ini"; diff -Nru libconfig-inifiles-perl-2.88/t/33update-using-tied-fh-w-shorter-names.t libconfig-inifiles-perl-2.94/t/33update-using-tied-fh-w-shorter-names.t --- libconfig-inifiles-perl-2.88/t/33update-using-tied-fh-w-shorter-names.t 2015-07-10 08:37:30.000000000 +0000 +++ libconfig-inifiles-perl-2.94/t/33update-using-tied-fh-w-shorter-names.t 2016-11-29 17:30:06.000000000 +0000 @@ -18,8 +18,7 @@ use Config::IniFiles; use File::Spec; -eval "use File::Temp qw(tempdir)"; -plan skip_all => "File::Temp required for testing" if $@; +use File::Temp qw(tempdir); my $dirname = tempdir(CLEANUP => 1); my $filename = File::Spec->catfile($dirname, 'toto.ini'); diff -Nru libconfig-inifiles-perl-2.88/t/34trailing-comments-double-delimeter.t libconfig-inifiles-perl-2.94/t/34trailing-comments-double-delimeter.t --- libconfig-inifiles-perl-2.88/t/34trailing-comments-double-delimeter.t 1970-01-01 00:00:00.000000000 +0000 +++ libconfig-inifiles-perl-2.94/t/34trailing-comments-double-delimeter.t 2016-11-29 17:30:06.000000000 +0000 @@ -0,0 +1,63 @@ +#!/usr/bin/perl + +# See: https://rt.cpan.org/Ticket/Display.html?id=105255 +# RT #105255 + +use strict; +use warnings; + +use Test::More tests => 1; + +use Config::IniFiles; + +# Test of handle_trailing_comment enabled +{ + my $ini_contents = <<'EOF'; +[MySect] +key=val ; a comment ; further comment + +EOF + + my $cfg = Config::IniFiles->new( + -file => \$ini_contents, + -handle_trailing_comment => 1, + ); + + # TEST + is (scalar( $cfg->val("MySect", "key") ), + "val", + "Value with a double trailing comment." + ); +} + +__END__ + +=head1 COPYRIGHT & LICENSE + +Copyright 2015 by Shlomi Fish + +This program is distributed under the MIT (X11) License: +L + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +=cut diff -Nru libconfig-inifiles-perl-2.88/t/author-pod-coverage.t libconfig-inifiles-perl-2.94/t/author-pod-coverage.t --- libconfig-inifiles-perl-2.88/t/author-pod-coverage.t 1970-01-01 00:00:00.000000000 +0000 +++ libconfig-inifiles-perl-2.94/t/author-pod-coverage.t 2016-11-29 17:30:06.000000000 +0000 @@ -0,0 +1,15 @@ +#!perl + +BEGIN { + unless ($ENV{AUTHOR_TESTING}) { + require Test::More; + Test::More::plan(skip_all => 'these tests are for testing by the author'); + } +} + +# This file was automatically generated by Dist::Zilla::Plugin::PodCoverageTests. + +use Test::Pod::Coverage 1.08; +use Pod::Coverage::TrustPod; + +all_pod_coverage_ok({ coverage_class => 'Pod::Coverage::TrustPod' }); diff -Nru libconfig-inifiles-perl-2.88/t/author-pod-syntax.t libconfig-inifiles-perl-2.94/t/author-pod-syntax.t --- libconfig-inifiles-perl-2.88/t/author-pod-syntax.t 1970-01-01 00:00:00.000000000 +0000 +++ libconfig-inifiles-perl-2.94/t/author-pod-syntax.t 2016-11-29 17:30:06.000000000 +0000 @@ -0,0 +1,15 @@ +#!perl + +BEGIN { + unless ($ENV{AUTHOR_TESTING}) { + require Test::More; + Test::More::plan(skip_all => 'these tests are for testing by the author'); + } +} + +# This file was automatically generated by Dist::Zilla::Plugin::PodSyntaxTests. +use strict; use warnings; +use Test::More; +use Test::Pod 1.41; + +all_pod_files_ok(); diff -Nru libconfig-inifiles-perl-2.88/t/cmt.ini libconfig-inifiles-perl-2.94/t/cmt.ini --- libconfig-inifiles-perl-2.88/t/cmt.ini 2015-07-10 08:37:30.000000000 +0000 +++ libconfig-inifiles-perl-2.94/t/cmt.ini 2016-11-29 17:30:06.000000000 +0000 @@ -10,7 +10,7 @@ @ [Library] work = $WORKAREA/project_lib/debobj.lib++ -synopsys = $VHDL_PACKAGES_DEB/synopsys/$DEBOBJ_VERSION +synopsis = $VHDL_PACKAGES_DEB/synopsis/$DEBOBJ_VERSION ieee = $VHDL_PACKAGES_DEB/IEEE/$DEBOBJ_VERSION std_developerskit = $VHDL_PACKAGES_DEB/sdk/std_developerskit.lib/$DEBOBJ_VERSION gtech = $VHDL_PACKAGES_DEB/gtech/$DEBOBJ_VERSION diff -Nru libconfig-inifiles-perl-2.88/t/en.ini libconfig-inifiles-perl-2.94/t/en.ini --- libconfig-inifiles-perl-2.88/t/en.ini 2015-07-10 08:37:30.000000000 +0000 +++ libconfig-inifiles-perl-2.94/t/en.ini 2016-11-29 17:30:06.000000000 +0000 @@ -4,7 +4,7 @@ DataName = General Summary ReportType = GeneralSummary Description = <Note: Depending on the -report time frame, the first and last months may not represent a complete +The Monthly Report identifies activity for each month in the report +timeframe. Remember that each page hit can result in several server requests +as the images for each page are loaded.
Note: Depending on the +report time frame, the first and last months may not represent a complete month's worth of data, resulting in lower hits. END diff -Nru libconfig-inifiles-perl-2.88/t/es.ini libconfig-inifiles-perl-2.94/t/es.ini --- libconfig-inifiles-perl-2.88/t/es.ini 2015-07-10 08:37:30.000000000 +0000 +++ libconfig-inifiles-perl-2.94/t/es.ini 2016-11-29 17:30:06.000000000 +0000 @@ -17,7 +17,7 @@ múltiples peticiones al servidor, dado que se cargan las imágenes de cada página.
Nota: Dependiendo del periodo del informe, el primer y -último mes pueden no representar un mes completo, resultando en menos +último mes pueden no representar un mes completo, resultando en menos aciertos de los reales. FIN diff -Nru libconfig-inifiles-perl-2.88/t/release-cpan-changes.t libconfig-inifiles-perl-2.94/t/release-cpan-changes.t --- libconfig-inifiles-perl-2.88/t/release-cpan-changes.t 1970-01-01 00:00:00.000000000 +0000 +++ libconfig-inifiles-perl-2.94/t/release-cpan-changes.t 2016-11-29 17:30:06.000000000 +0000 @@ -0,0 +1,19 @@ +#!perl + +BEGIN { + unless ($ENV{RELEASE_TESTING}) { + require Test::More; + Test::More::plan(skip_all => 'these tests are for release candidate testing'); + } +} + + +use strict; +use warnings; + +use Test::More 0.96 tests => 2; +use_ok('Test::CPAN::Changes'); +subtest 'changes_ok' => sub { + changes_file_ok('Changes'); +}; +done_testing(); diff -Nru libconfig-inifiles-perl-2.88/t/release-kwalitee.t libconfig-inifiles-perl-2.94/t/release-kwalitee.t --- libconfig-inifiles-perl-2.88/t/release-kwalitee.t 1970-01-01 00:00:00.000000000 +0000 +++ libconfig-inifiles-perl-2.94/t/release-kwalitee.t 2016-11-29 17:30:06.000000000 +0000 @@ -0,0 +1,19 @@ +#!perl + +BEGIN { + unless ($ENV{RELEASE_TESTING}) { + require Test::More; + Test::More::plan(skip_all => 'these tests are for release candidate testing'); + } +} + + +# This test is generated by Dist::Zilla::Plugin::Test::Kwalitee::Extra +use strict; +use warnings; +use Test::More; # needed to provide plan. + +eval { require Test::Kwalitee::Extra }; +plan skip_all => "Test::Kwalitee::Extra required for testing kwalitee: $@" if $@; + +eval "use Test::Kwalitee::Extra"; diff -Nru libconfig-inifiles-perl-2.88/weaver.ini libconfig-inifiles-perl-2.94/weaver.ini --- libconfig-inifiles-perl-2.88/weaver.ini 1970-01-01 00:00:00.000000000 +0000 +++ libconfig-inifiles-perl-2.94/weaver.ini 2016-11-29 17:30:06.000000000 +0000 @@ -0,0 +1,39 @@ +[@CorePrep] + +[-SingleEncoding] + +[Generic / NAME] + +[Version] + +[Region / prelude] + + +[Generic / SYNOPSIS] +[Generic / DESCRIPTION] +[Generic / OVERVIEW] + +[Collect / ATTRIBUTES] +command = attr + +[Collect / METHODS] +command = method + +[Leftovers] + +[Region / postlude] + +[Authors] +[Legal] + +; [Generic / DESCRIPTION] +; required = 1 + +; [Generic / BUGS] + +; [Generic / Section::Bugs] +; [Generic / Section::License] +; +[Bugs] +[Support] +all_modules = 1