Config-General-2.67/0000755000175000017500000000000014737432447012725 5ustar scipscipConfig-General-2.67/example.cfg0000644000175000017500000000277614225255304015040 0ustar scipscip# -*-sh-*- (ignore, this is just for my operation system, emacs, # to function properly) # # This is an example of a config file supported by Config::General. # It shows almost all features of the format and its flexibility. # # To try it, install Config::General as usual and execute the # following perlscript: # # use Config::General; # use Data::Dumper; # my %conf = ParseConfig(-ConfigFile => "example.cfg", -InterPolateVars => 1); # print Dumper(\%C);' # # This will parse the config and print out a stringified version # of the hash it produces, which can be used in your program. # /* * c-style comment */ # variable assignment option1 = blah option2 blubber option3 = "something special" # this is a comment option4 = parameters can be written on \ multiple lines # duplicate options will be made into an array huc = 12 huc = 17 huc = 133 # options can be organized in blocks too user = hans server = mc200 db = maxis passwd = D3rf8d # nested blocks are no problem index int(100000) name char(100) prename char(100) status int(10) # named blocks can also be used # block names containing whitespaces must be quoted <"kyla cole"> # blocks maybe empty # here-docs are fully supported usage <1 to enable, add defines with -Define and add to your config, see pod for details. - added test case for the code. - fixed unindented half of the pod, which was largely no readable because of this. However, I wonder why this hasn't reported, seems nobody reads the docs :) - fixed tab/space issues here and there 2.61 - fix rt.cpan.org#113671: ignore utf BOM, if any and turn on UTF8 support if not yet enabled. 2.60 - fix rt.cpan.org#107929: added missing test config. 2.59 - fix rt.cpan.org#107108 by adding support for IncludeOptional. - clarified documentation on StoreDelimiter. 2.58 - bumbp version 2.57 - fix rt.cpan.org#104548, dont allow special chars like newline or < in keys, which leads to faile when saving. 2.56 - fix rt.cpan.org#95325 2.55 - fix rt.cpan.org#95314 2.54 - fixed rt.cpan.org#39814. changed the order of pre-processing in _read(): 1) remove comments, 2) check for continuation, 3) remove empty lines. 2.53 - applied patch rt.cpan.org#68153, which adds a find() method to Config::General::Extended. - fixed rt.cpan.org#79869 (in fact it has been fixed in 2.52 but I forgot to mention it here). - applied spelling fixes rt.cpan.org 87072+87080. - fixed rt.cpan.org#89379 2.52 - applied pod patch rt.cpan.org#79603 - fixed rt.cpan.org#80006, it tolerates now whitespaces after the block closing > - added -Plug parameter, which introduces plugin closures. idea from rt.cpan.org#79694. Currently available hooks are: pre_open, pre_read, post_read, pre_parse_value, post_parse_value - applied patch by Ville Skyttä, spelling fixes. - fixed rt.cpan.org#85080, more spelling fixes. - applied patch rt.cpan.org#85132, which fixes a deprecation warning in perl 5.18 and above. Fixes #85668 as well. - applied patch rt.cpan.org#85538, c-style comments are ignored inside here-docs. - fixed rt.cpan.org#82637, don't use indirect object syntax in pod and code. 2.51 - fixed rt.cpan.org#77667 which resulted in invalid configs written to file when using save_file() and a named block, whose 2nd part starts with a /. - fixed rt.cpan.org#64169 by applying patch by Dulaunoy Fabrice. adds -NoEscape switch which turns off escaping of anything. - implemented suggestion of rt.cpan.org#67564 by adding 3 new parameters: -NormalizeOption, -NormalizeBlock and -NormalizeValue, which take a subroutine reference and change the block, option or value accordingly. - fixed rt.cpan.org#65860+76953 undefined value error. 2.50 - fixed rt.cpan.org#63487 documentation error. - fixed rt.cpan.org#61302, now croak if the config file parameter is a directory and directory include is not turned on. - fixed rt.cpan.org#60429 META.yml typo - added new option -AllowSingleQuoteInterpolation, which turns on interpolation for variables inside single quotes. - added test case for the new option 2.49 - fixed rt.cpan.org#56532, '#' missed during fix for 56370 in 2.45. - added test case for this too 2.48 - arg, uploaded the wrong file to pause, so another version bump up. - fixed typos in pod section for -ForceArray. 2.47 - fixed rt.cpan.org#53759 by adding new option -ForceArray. when enabled a single config value enclosed in [] will become an array forcefully. - fixed typo in license: it is "artistic", not "artificial". 2.46 - fixed rt.cpan.org#56370: there was a sort() call in _store() left, which lead to sorted arrays even if -SaveSorted were turned off. 2.45 - fixed rt.cpan.org#50647 escaping bug. Now escaped $ or backslash characters are handled correctly (across save too) - fixed rt.cpan.org#52047, tied hash will remain tied when savong to a file. - fixed rt.cpan.org#54580, preserve single quotes during variable interpolation corrected. No more using rand() to mark single quotes but an incrementor instead. - fixed rt.cpan.org#42721+54583, empty config values will no more handed over to interpreting methods (as interpolate or autotrue and the like) but returned as undef untouched. 2.44 - fixed rt.cpan.org#49023 by rolling back change in 2.43 in line 158, regarding GLOB support. 2.43 - fixed rt.cpan.org#40925, $indichar replaced by internal configuration variable EOFseparator, which contains a 256 bit SHA checksum of the date I fixed the bug. This will prevent future conflicts hopefully. In addition it makes it possible to make it customizable, if necessary, in a future release. - fixed rt.cpan.org#42721, return undef for empty values - fixed rt.cpan.org#42331, return undef for empty objects - fixed rt.cpan.org#44600, comments after blockname causes parser failure. - fixed rt.cpan.org#42287, whitespace at beginning or end of a quoted value gets lost after save(). - fixed rt.cpan.org#46184, variables that were not previously defined are deleted when -InterPolateEnv is enabled. - fixed bug in config loader for FileHandle objects, it supports now any compatible object. Hint by Ingo Schmiegel. - applied spelling- and speedup patches by Ville Skyttä. - applied documentation patch by Jordan Macdonald. 2.42 - dist tarball for 2.41 missed t/Tie/LxHash.pm. Dammit. the File to the MANIFEST. 2.41 - fixed rt.cpan.org#38635. apache-like include now supports quoted strings. - fixed rt.cpan.org#41748. saving config with -tie enabled now keeps the tie as documented. - added unit test for -tie. For this to work, a copy of Tie::LxHash module is delivered with Config::General source, but will not installed, in fact, it is only used for 'make test' (number 50) - fixed rt.cpan.org#39159. documentation of functional interface now reflects that qw$method) is now required. - applied patch by AlexK fixing rt.cpan.org#41030: if files are included by means of a glob pattern having the -IncludeGlob option activated, paths specified by the -ConfigPath option are being neglected when trying to spot the files. This patch fixes this - applied patch by fbicknel, fixes rt.cpan.org#41570: An array of scalars (eg: option = [1,2,3]) cannot be used for interpolation (which element shall we use?!), so we ignore those types of lists and don't build a __stack for them. 2.40 - fixed SplitDelimiter parser regex, it does no more consider non-whitespaces (\S+?) as the option name but anything before the delimiter (.+?), this fixes bug rt.cpan.org#36607, the fix of 2.39 were not sufficient. Thanks to Jeffrey Ratcliffe for pointing it out. - added new parameter -SaveSorted. The default value is 0, that means configs will be saved unsorted (as always), however if you want to save it sorted, turn this parameter to 1. Thanks to Herbert Breunung for the hint. - added complexity test, which checks a combination of various complex features of the parser. 2.39 - fixed rt.cpan.org#35122. This one was one of the most intriguing bugs I've ever observed in my own code. The internal temporary __stack hashref were copied from one subhash to another to enable inheritance of variables. However, the hashes were copied by reference, so once a value changed later, that value were overwritten because the __stack in question were just a reference. I introduced a simple function _copy() which copies the contents of the __stack by value, which solved the bug. Conclusion: beware of perl hash refs! - fixed rt.cpan.org#36607, accept whitespaces in heredoc names if split delimiter is gues (equalsign or whitespace) - fixed rt.cpan.org#34080 (typo) - fixed rt.cpan.org#35766. Variables inside single quoted strings will no more interpolated (as the docu states). Also added test case for this. - fixed bug rt.cpan.org#33766. Checking for defined not true in ::Extended::AUTOLOAD(). - added -UTF8 flag, which opens files in utf8 mode (suggested by KAORU, rt.cpan.org#35583) I decided not to add a test case for this, since perls utf8 support is not stable with all versions. 2.38 - fixed rt.cpan.org#31529 variable inheritance failed with multiple named blocks. - fixed rt.cpan.org#33447, regex to catch variable names were too strict, now - . + or : are allowed too. - fixed rt.cpan.org#33385 and #32978 - using arrayrefs as param to -String didn't work anymore (sic) - fixed rt.cpan.org#33216 - variable stack were not properly re-constructed for pre-existing variables if -MergeDuplicateOptions is turned on. 2.37 - "fixed" rt.cpan.org#30199 - check for invalid and unsupported structures, especially mixing blocks and scalars with identical names. - added checks to 'make test' to test for the above checks. - revoked patch of rt.cpan.org#27225, it broke running code. - fixed rt.cpan.org#30063 (and #27225!) by reimplementing the whole interpolation code. The internal stack is no more a class variable of the module but stored directly within the generated config hash and cleaned before returning to the user. - added (modified) patch rt.cpan.org#30063 to check if interpolation works with supplied default config works. 2.36 - oh my goodness! For some unknown reason I deleted the Makefile.PL before packaging. Dammit. So, here it is again. 2.35 - 'make test' failed under perl 5.5 because some prequisite modules were not found. So now I added all requirements to Makefile.PL, even if those modules are part of recent perls (beginning with 5.6). I could have also added a 'use 5.6' to the code but this would users of perl5 exclude. This way they have the possibility to fix their installation. Hopefully. No code changes otherwise. 2.34 - fixed rt.cpan.org#27271 - removed output file from manifest. - fixed rt.cpan.org#27225 - clear vars off the stack if entering a new block, so old vars get not re-used. - fixed rt.cpan.org#27110 - re-implemented support for arrayref as -String parameter. - fixed rt.cpan.org#24155 - relative include bug fixed. - applied patch by GWYN, (see fixed rt.cpan.org#27622) which allows the same file included multiple times. there is no loop detection if turned on. new option introduced: -IncludeAgain => 1 (default turned off). - added support for -IncludeAgain to directory include code too. - the directory globbing code used slashes to join directory and file names. changed this to use catfile() instead. 2.33 - fixed rt.cpan.org#26333 - just return $con if env var is undefined. - applied part of a patch supplied by Vincent Rivellino which turns off explicit empty block support if in apache compatibility mode, see next. - added new option -ApacheCompatible, which makes the module behave really apache compatible by setting the required options. - a little bit re-organized the code, most of the stuff in new() is now outsourced into several extra subs to make maintenance of the code easier. The old new() sub in fact was a nightmare. - fixed a bug reported by Otto Hirr : the _store() sub used sort() to sort the keys, which conflicts with sorted hashes (eg. tied using Tie::IxHash). - fixed tie bug reported by King, Jason , loading of the tie module didn't work. 2.32 - fixed rt.cpan.org#24232 - import ENV vars only if defined - fixed rt.cpan.org#20742 - dont' overwrite a var if re-defined in current scope, interpolation failed for re-defined vars and used the value of the var defined in outer scope, not the current one. - fixed rt.cpan.org#17852 - a 0 as blockname were ignored. applied patch by SCOP to t/run.t to test for 0 in blocks. - applied most hints Perl::Critic had about Config::General: o the functions ParseConfig SaveConfig SaveConfigString must now imported implicitly. This might break existing code, but is easily to fix. o using IO::File instead of open(). o General.pm qualifies for "stern" level after all. - added much more tests to t/run.t for 'make test'. - using Test::More now. 2.31 - applied patches by Jason Rhinelander : o bugfix: multiple levels if include files didn't work properly. o new option -IncludeDirectories, which allows to include all files of a directory. The directory must be specified by -ConfigFile as usual. o new option -IncludeGlob, which allows to use globs (wildcards) to include multiple files. o -ConfigPath can be speciefied using a single scalar value instead of an array if there is only one path. o bugfix: quotes from quoted block names were not removed properly. o fixes and updates for tests (make test) for the above patches. Thanks a lot Jason. - fixed number of tests in run.t - applied suggestion by Eric Kisiel : ::Extended::keys() returns an empty hash if the referring object is not hash. - fixed bug #14770, "Use of uninitialized value.." during environment variable interpolation. 2.30 - applied patch by Branislav Zahradnik which adds -InterPolateEnv. This allows to use environment variables too. It implies -InterPolateVars. - added object list capability for the ::Extended::obj() method. If a certain key points to an array of hashrefs, then the whole arrayref is returned. Suggested by Alan Hodgkinson . 2.29 - applied patch by brian@kronos.com via rt.cpan.org #11211. - applied patch by plasmaball@pchome.com.tw via rt.cpan.org #5846 - added new files to MANIFEST file. - added example.cfg to show the config format. 2.28 - fixed bug in save(), now blocks containing whitespaces will be saved using quotes, in addition the parser observes the quoting feature, added portion about this to the pod doc. pointed out by Jeff Murphy . - added internal list of files opened so far to avoid reading in the same file multiple times. Suggested by Michael Graham. - added new method files() which returns the above list. - added workaround for foolish perl installation on debian systems (croak() doesn't work anymore as of 5.8.4, it's a shame!) - applied patch by Michael Graham which fixes IncludeRelative feature, now an included file is being included relative to the calling config file, not the first one. - added 'make test' targets for files() and include stuff. (by Michael too) 2.27 - bugfix in _store, which caused warning when saving a config containing empty hashes. Reported by herbert breunung . - removed applied patch (added in 2.20), there are no more calls to binmode(), this destroys portability, because perls determines itself wether it uses \n or \r\n as newline. Reported by herbert breunung too. - applied patch by Danial Pearce , scalars containing a backslash as the last character will be written out as here-doc when storing a config to disk. 2.26 - fixed invalid regexp in _open() which circumvented explicit empty block to work when the block statement included whitespaces. - more finetuning in Makefile.PL for cleaning emacs' ~ files. 2.25 - fixed bug with not working -IncludeRelative setting when including a config file. It were only included from the location relative to the underlying config if it were non-existent. reported by Dmitry Koteroff . - applied patch by Danial Pearce which adds the -BackslashEscape parameter to enable general escaping of special characters using the backslash. - fixed bug reported by Harold van Oostrom : according to the documentation one can call new() with a hash-ref as its single parameter which would then used as the config. This didn't work and were fixed. - added feature suggested by Eric Andreychek : now block statements like this are allowed: "" which is called an explicit empty block. This generates just an empty hash-ref and saves writing. In fact, internally it will be converted to: - fixed Makefile.PL: it cleans now files generated by 'make test' properly. reported by: Dagfinn Ilmari Mannsåker - updated MANIFEST (in fact I did this some years ago the last time!) also reported by: Dagfinn Ilmari Mannsåker 2.24 - fixed Bug #3869 (rt.cpan.org) reported by "Mike Depot" - applied patch by Roland Huss , which fixes a bug with the -Tie option, sub-hashes of named blocks were not properly created (in fact, not tied). - added documentation to Interpolated.pm that it does not interpolate variables in keys, see bug #3773 (rt.cpan.org). 2.23 - still versioning problem, stupid white man ;-) Extended.pm is now 2.00 which *is* higher than 1.10. 2.22 - incremented all version numbers because of cpan problem. no further changes. See Bug #3347 (rt.cpan.org). 2.21 - fixed bug in new() used $this instead of $self for empty hashref creation if no config file given. 2.20 - fixed bug reported by Stefano di Sandro : in OOP mode (extended access) the obj() method returned the whole config object if the given key does not exist. Now it returns a new empty object. - added patch by David Dick which sets $/ if it is unset. - added patch by David Dick which calls the binmode() function in case the modules is being used under win32 systems. Read perldoc -f binmode for more informations on this issue. - added feature suggested by Chase Phillips : the new() method has a new parameter -Tie which takes the name of a Tie class that each new hash should be based off of. This makes it possible to create a config hash with ordered contents across nested structures. 2.19 - forgot to import 'catfile' from File::Spec. Bug reported by various people. - applied patch by Peter Tandler which adds a search-path feature for include files. - applied patch by David Dick which adds an auto launder capability to the module which makes it possible to use variables read by Config::General in a tainted perlscript (executed with -T) for open(), backtick calls or something which the taintmode considers to be dangerous. 2.18 - fixed Bug #2325 (rt.cpan.org). The subs exported by File::Spec will now imported explicitly. - fixed warning about double my'ed variable $dummi, changed it to undef because it was unused anyway. 2.17 - added File::Spec support which makes the modules more portable (i.e. on win32 systems), as suggested by Peter Tandler . 2.16 - applied patch by Michael Gray which fixes a bug in the Interpolate.pm submodule. A second variable, when immediately following the first, did not get interpolated, i.e. ${var1}${var2}. 2.15 - fixed Bug in SaveConfig***, which didn't work. - applied patch by Robb Canfield , which fixes a bug in the variable interpolation scheme. It did not interpolate blocks nor blocknames. This patch fixes this. Patch slightly modified by me(interpolation on block and blocknames). - enhanced test for variable interpolation to reflect this. - added check if a named block occurs after the underlying block is already an array, which is not possible. perl cannot add a hashref to an array. i.e.: a = 1 b = 1 c = 1 As you can see, "" will be an array, and "blubber" cannot be stored in any way on this array. The module croaks now if such construct occurs. 2.14 - fixed bug reported by Francisco Olarte Sanz , which caused _parse to ignore blocks with the name "0": <0> .. , because it checked just if $block (the name between < and >) is true, and from the perl point of view "0" is not. Changed it to check for defined. Normally I avoid using 'defined' but in this case it will not be possible that $block contains the empty string, so defined is ok here. 2.13 - fixed bug reported by Steffen Schwigon . the parser was still active inside a here-doc, which cause weird results if the here-doc contained multiple < reported this mis-behavior. The problem was that the whole hash was feeded to ::Interpolated.pm, but as we all know, perl hashes doesn't preserve the order. So, in our case the module sometimes was unable to resolve variablenames, because they were stored in a different location as it occurred in the config. The change is, that Config::General now calls ::Interpolate.pm (new sub: _interpolate()) itself directly on a per-key/value pair basis. The internal varstack is now stored on $this globally. So, now a variable will be known when it occurs. period :-) 2.10 - added -StrictVars documentation section to the POD, which was missing. - fixed a formatting error in the POD documentation. 2.09 - added bugfix in '#' comment parsing. If current state was within a block, then /^ #/ was not ignored as comment but instead added as variable. Reported by Lupe Christoph - added -StrictObjects parameter support in the following ::Extended methods: hash() and value(). - added better parameter checks in the ::Extended::obj() method. Its now no more possible to create a new (sub-) object from an undefined key or a key which does not point to a hash reference. - simplified storing of ConfigFile and ConfigHash in new() removed my variable $configfile. - the original parameter list will now be saved, which is required for ::Extended to create new objects with the same config as their parents. 2.08 - added option -StrictVars, which causes Interpolate.pm to ignore undefined variables and replaces such occurrences with the emppty string. - applied patch by Stefan Moser , which fixes some weird bevavior if -MergeDuplicateOptions was turned on, the parser croaked regardless -MergeDuplicateBlocks was set or not. Now the two options behave almost independent from each other, which allows one to merge duplicate blocks but duplicate options not. - changed behavior of setting -MergeDuplicateOptions which implied in previous versions -AllowMultiOptions to be false. Now this will only be done if the user does not set -AllowMultiOptions by himself. This allows one to have duplicate blocks which will be turned into an array but duplicate options to be merged. - applied patch by Matthias Pitzl , which fixes a bug at parsing apache-like include directive (Include ...). It did not properly trim unnecessary whitespaces so that the filename to be included became invalid. This bug espessially occurred if one saved a hash containing a key/value pair like this: "Include" => "/etc/grs.cfg", which was then saved as "Include /etc/grs.cfg", the parser returned " /etc/grs.cfg" which, of course, does not exists. odd... 2.07 - fixed cpan bugid #1351, SaveConfig contained a deprecated function call which caused the module to croak. - added feature request, if in extended mode (OOP turned on with -ExtendedAccess => 1 access to non-existent keys caused a croak. While this is still the default behavior it is now possible to turn this off using -StrictObjects => 0. - added this to the related pod section in ::Extended. - fixed bug in new() which caused a couple of errors if the ConfigFile parameter is not set, or is set to undef. In this case it will now simply create an empty object. - fixed related bug in save_file() which will save "" to a file now if the config is uninitialized (i.e. the case mentioned below arrived). 2.06 - added -SplitPolicy, -SplitDelimiter and -StoreDelimiter - removed whitespace support in keys in the default parser SplitPolicy 'guess', which was introduced in 2.02. Now I (re-)use the old regex I used before. if you need whitespaces in keys, use 'equalsign' as SplitPolicy. - the write_scalar() method uses the StoreDelimiter for separating options from values. - added -CComments to make it possible to turn c-comment parsing off. - added support for FileHandle objects as parameter to the -ConfigFile parameter. This makes it possible to use locking. 2.05 - fixed bug in ::Extended. It exported for some weird reason I can't remember all of its methods. This included keys() exists() and delete(), which are perl internals. If one used keys() on a normal hash, then the ::Extended own keys() were used instead of perls own one. I removed the export line. 2.04 - added RFE from rt.cpan.org (ID: 1218). the ::Interpolate module populates now uses of uninitialized variables in config files itself instead of just letting perl die(). The other suggestion of the RFE was declined. 2.03 - fixed bug in the _parse() routine (better: design flaw). after the last patch for allowing whitespaces in option names, it had a problem with here-docs which contained equal signs. option/value splitting resulted in weird output. - as a side effect of the bug fix below it is now possible to use equal signs inside quoted values, which will then be ignored, thus not used for splitting the line into an option/value assignment. - added a new test, which tests for all possible notations of option/value lines. 2.02 - added patch by Jens Heunemann, which allows to use whitespaces in option names. - changed the save() calls in the test script (t/run.t) to save_file() - removed new() from ::Interpolated and ::Extended. This may break existing code (they will need to move to the flags of Config::General::new() ), but this decision must be made. The problem was that both the old way of directly using the subclasses and the enw way did not work together. So, now subclasses are only method holders and used by Config::General on request. Direct use of subclasses is prohibited. (you receive a warning if you do). 2.01 - added -ConfigFile (in replace for -file) and -ConfigHash (in replace for -hash) to get a consistent parameter naming scheme. The old names are still supported for backward compatibility, but no more documented. - the parameter -BaseHash has been dropped because -DefaultConfig already has the capabilities of defining a custom backing hash. The pod section for -DefaultConfig has been enhanced to reflect this. - README changed something. Removed the 'small' keyword, because the module isn't really small anymore :-) At least IMHO. 2.00 - fixed a bug in the ::Extended::keys() method, which caused a beloved "use of uninitialized ..." message. Reported by Danial Pearce . - Removed all deprecated methods (in fact, they are still there for shouting out a warn that its deprecated. But the pod sections are removed. These are NoMultiOptions() and save(). - added two new parameters to new(): -InterPolateVars and -ExtendedAccess, which allows one to use the functionalites of the supplied submodules without the need to decide for one of them. This makes it possible to use variable interpolation and oop access in the same time. Suggested by Jared Rhine . - added new parameter -BaseHash which makes it possible to supply your own hash which stores the parsed contents of the config. This can be a tied hash o the like. Suggested by Jared Rhine too. - switched to release 2.00 because the above is a major change. 1.36 - simplified new() parameter parsing, should be now a little bit better to understand. - added new parameter -DefaultConfig, which can hold a hashref or a string, which will be used to pre-define values of the resulting hash after parsing a config. Thanks to Mark Hampton for the suggestion. - added new parameter -MergeDuplicateOptions, which allows one to overwrite duplicate options, which is required, if you turn on -DefaultConfig, because otherwise a array would be created, which is probably not what you wanted. - added patch by Danial Pearce to Config::General::Extended::keys(), which allows to retrieve the keys of the object itself (which was not directly possible before) - added patch by Danial Pearce to Config::General::Extended::value(), which allows to set a value to a (perlish-) nontrue value. This was a bug. - added patch by Danial Pearce to Config::General::_parse_value, which fixes a bug in this method, which in prior versions caused values of "0" (zero digit) to be wiped out of the config. - added tests in t/run.t for the new default config feature. 1.35 - the here-doc identifier in saved configs will now created in a way which avoids the existence of this identifier inside the here-doc, which if it happens results in weird behavior in the resulting config. 1.34 - Danial Pearce reported a bug in _store(), which caused the module to create scalar entries even if the entry contained newlines. While Danial supplied a patch to fix this - thx(TM) - I did not apply it, because I "outsourced" this kind of stuff to the subroutine _write_scalar(), see next. - added internal methods _write_scalar() and _write_hash() to simplify _store(), which did the same thing more than once, which is a good time to create a sub which does the job. - fixed cut'n paste bug in General/Extended.pm reported by Danial Pearce , which caused Config::General::Extended::is_scalar() to return true even when the key you pass in is an array. - added new method Config::General::Extended::delete() suggested by Danial Pearce , which deletes the given key from the config. 1.33 - fixed bug in _parse_value() which caused perl to complain with "Use of uninitialized value in..." if a value was empty. 1.32 - *argl* ... I forgot Interpolated.pm, don't know how that could happen, in 1.29 it was "lost". However - I added it again now. - added patch by Peder Stray to the _store() method, which makes it possible to catch arrays of hashes to be stored correctly. - cleaned up the t/run.t testscript to reflect the changes (in fact I did not touch it since 1.18 or so). - added test number 16 to test variable interpolation using ::Interpolated in t/run.t. - fixed bug with new() parameter -AllowMultiOptions which generated a croak() if set to something other than "no". - changed Extended::save() to reflect the API change, it calls now save_file(). 1.31: - i'm such a moron ... I forgot to do a make clean in 1.30, pf. So this is 1.31, which is clean. 1.30: - fixed typo, which made 1.29 unusable (undefined var %config) - added code to check if unknown parameters to new() has been supplied. 1.29: - added 2 procedural functions ParseConf and SaveConf - added new parameters -AutoTrue and -FlagBits - added save_file() which replaces save(), which was weird implemented. If the user only supplied a hash as parameter to save(), then the first key was used as the filename and the rest was used as a config hash (which was then of an uneven size). - save_file() takes now instead of a hash a hash-ref and a filename. And the hashref is optional, since the object already contains a complete hash. - new method save_string() added, which returns the ready generated string instead of writing it to disk. The user can then save it himself. - POD updated. 1.28: - added contributed sub module Config::General::Interpolated by "Wei-Hon Chen" with help from "Autrijus Tang" which makes it possible to use variables inside config files. - _read() accepts now c-comments inside c-comments if they are on a single line. - _read() is now more tolerant to here-identifiers (the ends of here-docs), whitespaces right after such an identifier are allowed (i.e. "EOF "). - _read() does now behave somewhat different with C-comments, they will be the first thing being processed in a config, so the parser really ignores everything inside C-comments. Previously it did not do that, for example here-docs has not been ignored. 1.27: - "make test" complained about uninitialized value in :146, which is now fixed. 1.26: - added filehandle capability to -file. - added -String parameter to new(), which allows one to supply the whole config as a string. - added -MergeDuplicateBlocks option, which causes duplicate blocks to be merged. 1.25: - include statements are now case insensitive - include statements may now also being used with indentation(leading and following whitespaces are allowed) - changed the end here-doc regexp from .+? to \S+? so " < and Anton Luht :-) This allows to include files from the location of the configfile instead from the working directory. 1.24: - AllowMultiOptions printed out the value and not the option itself, if more than one of this particular option occurred. - added -UseApacheInclude feature, contributed by Thomas Klausner - fixed bug with multiple options stuff, which did not work with blocks or named blocks. Pointed out by Thomas Klausner , who meant it being feature request, but in fact it was a bug (IMHO). - Config::General does now contain also it's OO-sister Config::General::Extended, which is from now on no more available as an extra module, because it lived a shadowy existence. - finally(!) created a Changelog file (this one, yes). 1.23: - fixed bug, which removed trailing or leading " even no matching " was there. 1.22: - added a new option to new(): -LowerCaseNames, which lowercases all option-names (feature request) 1.21: - lines with just one "#" became an option array named "#" with empty entries, very weird, fixed 1.20: - added an if(exists... to new() for checking of the existence of -AllowMultiOptions. - use now "local $_" because it caused weird results if a user used $_ with the module. 1.19: - you can escape "#" characters using a backslash: "\#" which will now no more treated as a comment. - comments inside here-documents will now remain in the here-doc value. history logs 1.17+1.18 are lost in space :-( older history logs (stripped from CVS): revision 1.16 date: 2000/08/03 16:54:58; author: jens; state: Exp; lines: +4 -1 # Local Variables: *** # perl-master-file: ../../webmin/index.pl *** # End: *** rangehängt, damit ich mit C-c d das debugging von jedem File aus einschalten kann (siehe mein .emacs file) ---------------------------- revision 1.15 date: 2000/08/01 09:12:52; author: tom; state: Exp; lines: +57 -68 added comments to _open() and _parse() ---------------------------- revision 1.14 date: 2000/07/31 18:07:12; author: tom; state: Exp; lines: +44 -19 added <> capability ---------------------------- revision 1.13 date: 2000/07/16 18:35:33; author: tom; state: Exp; lines: +135 -10 added here-doc and multi-line feature, updated perlpod ---------------------------- revision 1.12 date: 2000/07/14 14:56:09; author: tom; state: Exp; lines: +2 -2 bug fixed, it did not ignore options inside c-comments with a # comment @ the end of line ---------------------------- revision 1.11 date: 2000/07/14 11:26:04; author: tom; state: Exp; lines: +42 -6 added C-Style comments and allow also comments after a statement. ---------------------------- revision 1.10 date: 2000/07/12 14:04:51; author: tom; state: Exp; lines: +2 -1 i woas ned ---------------------------- revision 1.9 date: 2000/07/12 10:59:53; author: jens; state: Exp; lines: +5 -3 hehe :) ---------------------------- revision 1.8 date: 2000/07/12 10:43:20; author: tom; state: Exp; lines: +5 -2 fixed bug in getall(), which doubled %config if called more than onse. ---------------------------- revision 1.7 date: 2000/07/12 09:09:33; author: tom; state: Exp; lines: +22 -24 100% Apache Config complete ;-) it supports now "named blocks"! ---------------------------- revision 1.6 date: 2000/07/11 23:43:03; author: tom; state: Exp; lines: +72 -19 added named block support () ---------------------------- revision 1.5 date: 2000/07/11 20:49:47; author: tom; state: Exp; lines: +2 -2 typo in pod corrected ---------------------------- revision 1.4 date: 2000/07/11 17:07:04; author: tom; state: Exp; lines: +61 -7 a config file can now contain an option more than once and will be returned as array ---------------------------- revision 1.3 date: 2000/07/07 11:27:38; author: cvs; state: Exp; lines: +2 -2 folgende Parameterform geht jetzt auch: parameter= blabla vorher musste man parameter = blabla schreiben ---------------------------- revision 1.2 date: 2000/07/04 13:21:12; author: tom; state: Exp; lines: +9 -4 added better failurehandling in case of missing block start/end statements ---------------------------- revision 1.1 date: 2000/07/04 12:52:09; author: tom; state: Exp; implemented module and method getall, works as expected. Config-General-2.67/Makefile.PL0000644000175000017500000000226314225256126014670 0ustar scipscip# # Makefile.PL - build file for Config::General # # Copyright (c) 2000-2022 Thomas Linden . # All Rights Reserved. Std. disclaimer applies. # Licensed under the Artistic License 2.0. # use ExtUtils::MakeMaker; WriteMakefile( 'NAME' => 'Config::General', 'VERSION_FROM' => 'General.pm', 'clean' => { FILES => 't/*.out t/test.cfg *~ */*~' }, 'PREREQ_PM' => { 'IO::File' => 0, 'FileHandle' => 0, 'File::Spec::Functions' => 0, 'File::Glob' => 0 }, 'META_MERGE' => { resources => { repository => 'https://github.com/TLINDEN/Config-General' }, }, ($ExtUtils::MakeMaker::VERSION ge '6.31'? ('LICENSE' => 'artistic_2', ) : ()), ); Config-General-2.67/README0000644000175000017500000000622214737423002013572 0ustar scipscipNAME Config::General - Generic Config Module SYNOPSIS use Config::General; $conf = new Config::General(-ConfigFile => "myconfig.rc"); my %config = $conf->getall; DESCRIPTION This module opens a config file and parses it's contents for you. After parsing the module returns a hash structure which contains the representation of the config file. The format of config files supported by Config::General is inspired by the well known apache config format, in fact, this module is 100% read-compatible to apache configs, but you can also just use simple name/value pairs in your config files. In addition to the capabilities of a apache config file it supports some enhancements such as here-documents, C- style comments or multiline options. It is also possible to save the config back to disk, which makes the module a perfect backend for configuration interfaces. It is possible to use variables in config files and there exists also support for object oriented access to the configuration. INSTALLATION to install, type: perl Makefile.PL make make test make install to read the complete documentation, type: perldoc Config::General perldoc Config::General::Extended perldoc Config::General::Interpolated see some example config files which can be parsed with Config::Genreal in the subdirectory t/cfg.* UPDATE If you are updating from version 1.xx, you might be interested, that some things in the API has changed, which might force you to change your application code. These changes were necessary to clean up the module interface. Now it has a consistent "look and feel" and behaves more naturally. Therefore historic remains were removed. Here is a short list: o it is no more possible to use Config::General::Extended and Config::General::Interpolated directly. Instead use Config::General and turn on -InterPolateVars and -ExtendedAccess respectively. o the method NoMultiOptions() is deprecated. Set the parameter -AllowMultiOptions to false when calling new() to create a new Config::General object. o the method save() is deprecated. Use save_file() or save_string() instead. o the parameter -file is deprecated. Use -ConfigFile instead. o the parameter -hash is deprecated. Use -ConfigHash instead. For a more detailed explanation of changes refer to the Changelog. COPYRIGHT Config::General Config::General::Extended Copyright (c) 2000-2022 by Thomas Linden Config::General::Interpolated Copyright (c) 2001 by Wei-Hon Chen Copyright (c) 2002-2022 by Thomas Linden . This library is free software; you can redistribute it and/or modify it under the terms of the Artistic 2.0 license. HOMEPAGE The homepage of Config::General is located at: http://www.daemon.de/config-general/ BUGS make test does currently not catch all possible scenarios. AUTHOR Thomas Linden VERSION 2.67 Config-General-2.67/General.pm0000644000175000017500000025056714737423002014642 0ustar scipscip# # Config::General.pm - Generic Config Module # # Purpose: Provide a convenient way for loading # config values from a given file and # return it as hash structure # # Copyright (c) 2000-2025 Thomas Linden . # All Rights Reserved. Std. disclaimer applies. # Licensed under the Artistic License 2.0. # # namespace package Config::General; use strict; use warnings; use English '-no_match_vars'; use IO::File; use FileHandle; use File::Spec::Functions qw(splitpath file_name_is_absolute catfile catpath); use File::Glob qw/:glob/; # on debian with perl > 5.8.4 croak() doesn't work anymore without this. # There is some require statement which dies 'cause it can't find Carp::Heavy, # I really don't understand, what the hell they made, but the debian perl # installation is definitely bullshit, damn! use Carp::Heavy; use Carp; use Exporter; $Config::General::VERSION = "2.67"; use base qw(Exporter); our @EXPORT_OK = qw(ParseConfig SaveConfig SaveConfigString); use constant _UTF8_BOM => "\x{ef}\x{bb}\x{bf}"; sub new { # # create new Config::General object # my($this, @param ) = @_; my $class = ref($this) || $this; # define default options my $self = { # sha256 of current date # hopefully this lowers the probability that # this matches any configuration key or value out there # bugfix for rt.40925 EOFseparator => 'ad7d7b87f5b81d2a0d5cb75294afeb91aa4801b1f8e8532dc1b633c0e1d47037', SlashIsDirectory => 0, AllowMultiOptions => 1, MergeDuplicateOptions => 0, MergeDuplicateBlocks => 0, LowerCaseNames => 0, ApacheCompatible => 0, UseApacheInclude => 0, IncludeRelative => 0, IncludeDirectories => 0, IncludeGlob => 0, IncludeAgain => 0, AutoLaunder => 0, AutoTrue => 0, AutoTrueFlags => { true => '^(on|yes|true|1)$', false => '^(off|no|false|0)$', }, DefaultConfig => {}, String => '', level => 1, InterPolateVars => 0, InterPolateEnv => 0, ExtendedAccess => 0, SplitPolicy => 'guess', # also possible: whitespace, equalsign and custom SplitDelimiter => 0, # must be set by the user if SplitPolicy is 'custom' StoreDelimiter => 0, # will be set by me unless user uses 'custom' policy CComments => 1, # by default turned on BackslashEscape => 0, # deprecated StrictObjects => 1, # be strict on non-existent keys in OOP mode StrictVars => 1, # be strict on undefined variables in Interpolate mode Tie => q(), # could be set to a perl module for tie'ing new hashes parsed => 0, # internal state stuff for variable interpolation files => {}, # which files we have read, if any UTF8 => 0, SaveSorted => 0, ForceArray => 0, # force single value array if value enclosed in [] AllowSingleQuoteInterpolation => 0, NoEscape => 0, NormalizeBlock => 0, NormalizeOption => 0, NormalizeValue => 0, Plug => {}, UseApacheIfDefine => 0, Define => {}, AlwaysQuoteOutput => 0 }; # create the class instance bless $self, $class; if ($#param >= 1) { # use of the new hash interface! $self->_prepare(@param); } elsif ($#param == 0) { # use of the old style $self->{ConfigFile} = $param[0]; if (ref($self->{ConfigFile}) eq 'HASH') { $self->{ConfigHash} = delete $self->{ConfigFile}; } } else { # this happens if $#param == -1,1 thus no param was given to new! $self->{config} = $self->_hashref(); $self->{parsed} = 1; } # find split policy to use for option/value separation $self->_splitpolicy(); # bless into variable interpolation module if necessary $self->_blessvars(); # process as usual if (!$self->{parsed}) { $self->_process(); } if ($self->{InterPolateVars}) { $self->{config} = $self->_clean_stack($self->{config}); } # bless into OOP namespace if required $self->_blessoop(); return $self; } sub _process { # # call _read() and _parse() on the given config my($self) = @_; if ($self->{DefaultConfig} && $self->{InterPolateVars}) { $self->{DefaultConfig} = $self->_interpolate_hash($self->{DefaultConfig}); # FIXME: _hashref() ? } if (exists $self->{StringContent}) { # consider the supplied string as config file $self->_read($self->{StringContent}, 'SCALAR'); $self->{config} = $self->_parse($self->{DefaultConfig}, $self->{content}); } elsif (exists $self->{ConfigHash}) { if (ref($self->{ConfigHash}) eq 'HASH') { # initialize with given hash $self->{config} = $self->{ConfigHash}; $self->{parsed} = 1; } else { croak "Config::General: Parameter -ConfigHash must be a hash reference!\n"; } } elsif (ref($self->{ConfigFile}) eq 'GLOB' || ref($self->{ConfigFile}) eq 'FileHandle') { # use the file the glob points to $self->_read($self->{ConfigFile}); $self->{config} = $self->_parse($self->{DefaultConfig}, $self->{content}); } else { if ($self->{ConfigFile}) { # open the file and read the contents in $self->{configfile} = $self->{ConfigFile}; if ( file_name_is_absolute($self->{ConfigFile}) ) { # look if this is an absolute path and save the basename if it is absolute my ($volume, $path, undef) = splitpath($self->{ConfigFile}); $path =~ s#/$##; # remove eventually existing trailing slash if (! $self->{ConfigPath}) { $self->{ConfigPath} = []; } unshift @{$self->{ConfigPath}}, catpath($volume, $path, q()); } $self->_open($self->{configfile}); # now, we parse immediately, getall simply returns the whole hash $self->{config} = $self->_hashref(); $self->{config} = $self->_parse($self->{DefaultConfig}, $self->{content}); } else { # hm, no valid config file given, so try it as an empty object $self->{config} = $self->_hashref(); $self->{parsed} = 1; } } } sub _blessoop { # # bless into ::Extended if necessary my($self) = @_; if ($self->{ExtendedAccess}) { # we are blessing here again, to get into the ::Extended namespace # for inheriting the methods available over there, which we doesn't have. bless $self, 'Config::General::Extended'; eval { require Config::General::Extended; }; if ($EVAL_ERROR) { croak "Config::General: " . $EVAL_ERROR; } } # return $self; } sub _blessvars { # # bless into ::Interpolated if necessary my($self) = @_; if ($self->{InterPolateVars} || $self->{InterPolateEnv}) { # InterPolateEnv implies InterPolateVars $self->{InterPolateVars} = 1; # we are blessing here again, to get into the ::InterPolated namespace # for inheriting the methods available overthere, which we doesn't have here. bless $self, 'Config::General::Interpolated'; eval { require Config::General::Interpolated; }; if ($EVAL_ERROR) { croak "Config::General: " . $EVAL_ERROR; } # pre-compile the variable regexp $self->{regex} = $self->_set_regex(); } } sub _splitpolicy { # # find out what split policy to use my($self) = @_; if ($self->{SplitPolicy} ne 'guess') { if ($self->{SplitPolicy} eq 'whitespace') { $self->{SplitDelimiter} = '\s+'; if (!$self->{StoreDelimiter}) { $self->{StoreDelimiter} = q( ); } } elsif ($self->{SplitPolicy} eq 'equalsign') { $self->{SplitDelimiter} = '\s*=\s*'; if (!$self->{StoreDelimiter}) { $self->{StoreDelimiter} = ' = '; } } elsif ($self->{SplitPolicy} eq 'custom') { if (! $self->{SplitDelimiter} ) { croak "Config::General: SplitPolicy set to 'custom' but no SplitDelimiter set.\n"; } } else { croak "Config::General: Unsupported SplitPolicy: $self->{SplitPolicy}.\n"; } } else { if (!$self->{StoreDelimiter}) { $self->{StoreDelimiter} = q( ); } } } sub _prepare { # # prepare the class parameters, mangle them, if there # are options to reset or to override, do it here. my ($self, %conf) = @_; # save the parameter list for ::Extended's new() calls $self->{Params} = \%conf; # be backwards compatible if (exists $conf{-file}) { $self->{ConfigFile} = delete $conf{-file}; } if (exists $conf{-hash}) { $self->{ConfigHash} = delete $conf{-hash}; } # store input, file, handle, or array if (exists $conf{-ConfigFile}) { $self->{ConfigFile} = delete $conf{-ConfigFile}; } if (exists $conf{-ConfigHash}) { $self->{ConfigHash} = delete $conf{-ConfigHash}; } # store search path for relative configs, if any if (exists $conf{-ConfigPath}) { my $configpath = delete $conf{-ConfigPath}; $self->{ConfigPath} = ref $configpath eq 'ARRAY' ? $configpath : [$configpath]; } # handle options which contains values we need (strings, hashrefs or the like) if (exists $conf{-String} ) { if (not ref $conf{-String}) { if ( $conf{-String}) { $self->{StringContent} = $conf{-String}; } delete $conf{-String}; } # re-implement arrayref support, removed after 2.22 as _read were # re-organized # fixed bug#33385 elsif(ref($conf{-String}) eq 'ARRAY') { $self->{StringContent} = join "\n", @{$conf{-String}}; } else { croak "Config::General: Parameter -String must be a SCALAR or ARRAYREF!\n"; } delete $conf{-String}; } if (exists $conf{-Tie}) { if ($conf{-Tie}) { $self->{Tie} = delete $conf{-Tie}; $self->{DefaultConfig} = $self->_hashref(); } } if (exists $conf{-FlagBits}) { if ($conf{-FlagBits} && ref($conf{-FlagBits}) eq 'HASH') { $self->{FlagBits} = 1; $self->{FlagBitsFlags} = $conf{-FlagBits}; } delete $conf{-FlagBits}; } if (exists $conf{-DefaultConfig}) { if ($conf{-DefaultConfig} && ref($conf{-DefaultConfig}) eq 'HASH') { # copy the hashref so that it is not being modified by subsequent calls, fixes bug#142095 $self->{DefaultConfig} = $self->_copy($conf{-DefaultConfig}); } elsif ($conf{-DefaultConfig} && ref($conf{-DefaultConfig}) eq q()) { $self->_read($conf{-DefaultConfig}, 'SCALAR'); $self->{DefaultConfig} = $self->_parse($self->_hashref(), $self->{content}); $self->{content} = (); } delete $conf{-DefaultConfig}; } # handle options which may either be true or false # allowing "human" logic about what is true and what is not foreach my $entry (keys %conf) { my $key = $entry; $key =~ s/^\-//; if (! exists $self->{$key}) { croak "Config::General: Unknown parameter: $entry => \"$conf{$entry}\" (key: <$key>)\n"; } if ($conf{$entry} =~ /$self->{AutoTrueFlags}->{true}/io) { $self->{$key} = 1; } elsif ($conf{$entry} =~ /$self->{AutoTrueFlags}->{false}/io) { $self->{$key} = 0; } else { # keep it untouched $self->{$key} = $conf{$entry}; } } if ($self->{MergeDuplicateOptions}) { # override if not set by user if (! exists $conf{-AllowMultiOptions}) { $self->{AllowMultiOptions} = 0; } } if ($self->{ApacheCompatible}) { # turn on all apache compatibility options which has # been incorporated during the years... $self->{UseApacheInclude} = 1; $self->{IncludeRelative} = 1; $self->{IncludeDirectories} = 1; $self->{IncludeGlob} = 1; $self->{SlashIsDirectory} = 1; $self->{SplitPolicy} = 'whitespace'; $self->{CComments} = 0; $self->{UseApacheIfDefine} = 1; } if ($self->{UseApacheIfDefine}) { if (exists $conf{-Define}) { my $ref = ref($conf{-Define}); if ($ref eq '') { $self->{Define} = {$conf{-Define} => 1}; } elsif ($ref eq 'SCALAR') { $self->{Define} = {${$conf{-Define}} = 1}; } elsif ($ref eq 'ARRAY') { my %h = map { $_ => 1 } @{$conf{-Define}}; $self->{Define} = \%h; } elsif ($ref eq 'HASH') { $self->{Define} = $conf{-Define}; } else { croak qq{Config::General: Unsupported ref '$ref' for 'Define'}; } delete $conf{-Define}; } } } sub getall { # # just return the whole config hash # my($this) = @_; return (exists $this->{config} ? %{$this->{config}} : () ); } sub files { # # return a list of files opened so far # my($this) = @_; return (exists $this->{files} ? keys %{$this->{files}} : () ); } sub _open { # # open the config file, or expand a directory or glob or include # my($this, $basefile, $basepath) = @_; my $cont; ($cont, $basefile, $basepath) = $this->_hook('pre_open', $basefile, $basepath); return if(!$cont); my($fh, $configfile); if($basepath) { # if this doesn't work we can still try later the global config path to use $configfile = catfile($basepath, $basefile); } else { $configfile = $basefile; } my $glob = qr/[*?\[\{\\]/; if ($^O =~ /win/i) { # fix for rt.cpan.org#116340: do only consider a backslash # as meta escape char, but not if it appears on it's own, # as it happens on windows platforms. $glob = qr/(\\[*?\[\{\\]|[*?\[\{])/; } if ($this->{IncludeGlob} and $configfile =~ /$glob/) { # Something like: *.conf (or maybe dir/*.conf) was included; expand it and # pass each expansion through this method again. local $_; my @include = grep { -f $_ } bsd_glob($configfile, GLOB_BRACE | GLOB_QUOTE); # applied patch by AlexK fixing rt.cpan.org#41030 if ( !@include && defined $this->{ConfigPath} ) { foreach my $dir (@{$this->{ConfigPath}}) { my ($volume, $path, undef) = splitpath($basefile); if ( -d catfile( $dir, $path ) ) { push @include, grep { -f $_ } bsd_glob(catfile($dir, $basefile), GLOB_BRACE | GLOB_QUOTE); last; } } } # Multiple results or no expansion results (which is fine, # include foo/* shouldn't fail if there isn't anything matching) # rt.cpan.org#79869: local $this->{IncludeGlob}; foreach my $file (@include) { $this->_open($file); } return; } if (!-e $configfile) { my $found; if (defined $this->{ConfigPath}) { # try to find the file within ConfigPath foreach my $dir (@{$this->{ConfigPath}}) { if( -e catfile($dir, $basefile) ) { $configfile = catfile($dir, $basefile); $found = 1; last; # found it } } } if (!$found) { my $path_message = defined $this->{ConfigPath} ? q( within ConfigPath: ) . join(q(.), @{$this->{ConfigPath}}) : q(); croak qq{Config::General The file "$basefile" does not exist$path_message!}; } } local ($RS) = $RS; if (! $RS) { carp(q(\$RS (INPUT_RECORD_SEPARATOR) is undefined. Guessing you want a line feed character)); $RS = "\n"; } if (-d $configfile and $this->{IncludeDirectories}) { # A directory was included; include all the files inside that directory in ASCII order local *INCLUDEDIR; opendir INCLUDEDIR, $configfile or croak "Config::General: Could not open directory $configfile!($!)\n"; #my @files = sort grep { -f catfile($configfile, $_) } catfile($configfile, $_), readdir INCLUDEDIR; # fixes rt.cpan.org#139261 my @files = sort grep { -f catfile($configfile, $_) } readdir INCLUDEDIR; closedir INCLUDEDIR; local $this->{CurrentConfigFilePath} = $configfile; for (@files) { my $file = catfile($configfile, $_); if (! exists $this->{files}->{$file} or $this->{IncludeAgain} ) { # support re-read if used urged us to do so, otherwise ignore the file $fh = $this->_openfile_for_read($file); $this->{files}->{"$file"} = 1; $this->_read($fh); } else { warn "File $file already loaded. Use -IncludeAgain to load it again.\n"; } } } elsif (-d $configfile) { croak "Config::General: config file argument is a directory, expecting a file!\n"; } elsif (-e _) { if (exists $this->{files}->{$configfile} and not $this->{IncludeAgain}) { # do not read the same file twice, just return warn "File $configfile already loaded. Use -IncludeAgain to load it again.\n"; return; } else { $fh = $this->_openfile_for_read($configfile); $this->{files}->{$configfile} = 1; my ($volume, $path, undef) = splitpath($configfile); local $this->{CurrentConfigFilePath} = catpath($volume, $path, q()); $this->_read($fh); } } return; } sub _openfile_for_read { # # actually open a file, turn on utf8 mode if requested by bom # my ($this, $file) = @_; my $fh = IO::File->new( $file, 'r') or croak "Config::General: Could not open $file!($!)\n"; # attempt to read an initial utf8 byte-order mark (BOM) my $n_read = sysread $fh, my $read_BOM, length(_UTF8_BOM); my $has_BOM = $n_read == length(_UTF8_BOM) && $read_BOM eq _UTF8_BOM; # set utf8 perlio layer if BOM was found or if option -UTF8 is turned on binmode $fh, ":utf8" if $this->{UTF8} || $has_BOM; # rewind to beginning of file if we read chars that were not the BOM sysseek $fh, 0, 0 if $n_read && !$has_BOM; return $fh; } sub _read { # # store the config contents in @content # and prepare it somewhat for easier parsing later # (comments, continuing lines, and stuff) # my($this, $fh, $flag) = @_; my(@stuff, @content, $c_comment, $longline, $hier, $hierend, @hierdoc); local $_ = q(); if ($flag && $flag eq 'SCALAR') { if (ref($fh) eq 'ARRAY') { @stuff = @{$fh}; } else { @stuff = split /\n/, $fh; } } else { @stuff = <$fh>; } my $cont; ($cont, $fh, @stuff) = $this->_hook('pre_read', $fh, @stuff); return if(!$cont); if ($this->{UseApacheIfDefine}) { $this->_process_apache_ifdefine(\@stuff); } foreach (@stuff) { if ($this->{AutoLaunder}) { if (m/^(.*)$/) { $_ = $1; } } chomp; if ($hier) { # inside here-doc, only look for $hierend marker if (/^(\s*)\Q$hierend\E\s*$/) { my $indent = $1; # preserve indentation $hier .= ' ' . $this->{EOFseparator}; # bugfix of rt.40925 # _parse will also preserver indentation if ($indent) { foreach (@hierdoc) { s/^$indent//; # i.e. the end was: " EOF" then we remove " " from every here-doc line $hier .= $_ . "\n"; # and store it in $hier } } else { $hier .= join "\n", @hierdoc; # there was no indentation of the end-string, so join it 1:1 } push @{$this->{content}}, $hier; # push it onto the content stack @hierdoc = (); undef $hier; undef $hierend; } else { # everything else onto the stack push @hierdoc, $_; } next; } if ($this->{CComments}) { # look for C-Style comments, if activated if (/(\s*\/\*.*\*\/\s*)/) { # single c-comment on one line s/\s*\/\*.*\*\/\s*//; } elsif (/^\s*\/\*/) { # the beginning of a C-comment ("/*"), from now on ignore everything. if (/\*\/\s*$/) { # C-comment end is already there, so just ignore this line! $c_comment = 0; } else { $c_comment = 1; } } elsif (/\*\//) { if (!$c_comment) { warn "invalid syntax: found end of C-comment without previous start!\n"; } $c_comment = 0; # the current C-comment ends here, go on s/^.*\*\///; # if there is still stuff, it will be read } next if($c_comment); # ignore EVERYTHING from now on, IF it IS a C-Comment } # Remove comments and empty lines s/(? .* bugfix rt.cpan.org#44600 next if /^\s*#/; # look for multiline option, indicated by a trailing backslash if (/(?{ApacheCompatible} && /\s*<([^\/]+?.*?)\/>\s*$/) { my $block = $1; if ($block !~ /\"/) { if ($block !~ /\s[^\s]/) { # fix of bug 7957, add quotation to pure slash at the # end of a block so that it will be considered as directory # unless the block is already quoted or contains whitespaces # and no quotes. if ($this->{SlashIsDirectory}) { push @{$this->{content}}, '<' . $block . '"/">'; next; } } } my $orig = $_; $orig =~ s/\/>$/>/; $block =~ s/\s\s*.*$//; push @{$this->{content}}, $orig, ""; next; } # look for here-doc identifier if ($this->{SplitPolicy} eq 'guess') { if (/^\s*([^=]+?)\s*=\s*<<\s*(.+?)\s*$/) { # try equal sign (fix bug rt#36607) $hier = $1; # the actual here-doc variable name $hierend = $2; # the here-doc identifier, i.e. "EOF" next; } elsif (/^\s*(\S+?)\s+<<\s*(.+?)\s*$/) { # try whitespace $hier = $1; # the actual here-doc variable name $hierend = $2; # the here-doc identifier, i.e. "EOF" next; } } else { # no guess, use one of the configured strict split policies if (/^\s*(.+?)($this->{SplitDelimiter})<<\s*(.+?)\s*$/) { $hier = $1; # the actual here-doc variable name $hierend = $3; # the here-doc identifier, i.e. "EOF" next; } } ### ### any "normal" config lines from now on ### if ($longline) { # previous stuff was a longline and this is the last line of the longline s/^\s*//; $longline .= $_; push @{$this->{content}}, $longline; # push it onto the content stack undef $longline; next; } else { # ignore empty lines next if /^\s*$/; # look for include statement(s) my $incl_file; my $path = ''; if ( $this->{IncludeRelative} and defined $this->{CurrentConfigFilePath}) { $path = $this->{CurrentConfigFilePath}; } elsif (defined $this->{ConfigPath}) { # fetch pathname of base config file, assuming the 1st one is the path of it $path = $this->{ConfigPath}->[0]; } # bugfix rt.cpan.org#38635: support quoted filenames if ($this->{UseApacheInclude}) { my $opt = ''; if (/^\s*(include|includeoptional)\s*(["'])(.*?)(?{IncludeGlob} && $opt =~ /opt/i && $incl_file !~ /[*?\[\{\\]/) { # fix rt#107108 # glob enabled && optional include && file is not already a glob: # turn it into a singular matching glob, like: # "file" => "[f][i][l][e]" and: # "dir/file" => "dir/[f][i][l][e]" # which IS a glob but only matches that particular file. if it # doesn't exist, it will be ignored by _open(), just what # we'd like to have when using IncludeOptional. my ($vol,$dirs,$file) = splitpath( $incl_file ); $incl_file = catpath($vol, $dirs, join '', map { "[$_]" } split //, $file); } } } else { if (/^\s*<>\\s*$/i) { $incl_file = $2; } elsif (/^\s*<>\s*$/i) { $incl_file = $1; } } if ($incl_file) { if ( $this->{IncludeRelative} && $path && !file_name_is_absolute($incl_file) ) { # include the file from within location of $this->{configfile} $this->_open( $incl_file, $path ); } else { # include the file from within pwd, or absolute $this->_open($incl_file); } } else { # standard entry, (option = value) push @{$this->{content}}, $_; } } } ($cont, $this->{content}) = $this->_hook('post_read', $this->{content}); return 1; } sub _process_apache_ifdefine { # # Loop trough config lines and exclude all those entries # for which there's no IFDEF but which reside inside an IFDEF. # # Called from _read(), if UseApacheIfDefine is enabled, returns # the modified array. my($this, $rawlines) = @_; my @filtered; my @includeFlag = (1); foreach (@{$rawlines}) { if (/^\s*<\s*IfDefine\s+([!]*)("[^"]+"|\S+)\s*>/i) { # new IFDEF block, mark following content to be included if # the DEF is known, otherwise skip it til end of IFDEF my ($negate, $define) = ($1 eq '!',$2); push(@includeFlag, $includeFlag[-1] & ((not $negate) & (exists $this->{Define}{$define})) ); } elsif (/^\s*<\s*\/IfDefine\s*>/i) { if (scalar(@includeFlag) <= 1) { croak qq(Config::General: without a !\n); } pop(@includeFlag); } elsif ($includeFlag[-1] && /^\s*Define\s+("[^"]+"|\S+)/i) { # inline Define, add it to our list $this->{Define}{$1} = 1; } elsif ($includeFlag[-1]) { push @filtered, $_; } } if (scalar(@includeFlag) > 1) { croak qq(Config::General: Block has no EndBlock statement!\n); } @$rawlines = @filtered; # replace caller array } sub _parse { # # parse the contents of the file # my($this, $config, $content) = @_; my(@newcontent, $block, $blockname, $chunk,$block_level); local $_; foreach (@{$content}) { # loop over content stack chomp; $chunk++; $_ =~ s/^\s+//; # strip spaces @ end and begin $_ =~ s/\s+$//; # # build option value assignment, split current input # using whitespace, equal sign or optionally here-doc # separator EOFseparator my ($option,$value); if (/$this->{EOFseparator}/) { ($option,$value) = split /\s*$this->{EOFseparator}\s*/, $_, 2; # separated by heredoc-finding in _open() } else { if ($this->{SplitPolicy} eq 'guess') { # again the old regex. use equalsign SplitPolicy to get the # 2.00 behavior. the new regexes were too odd. ($option,$value) = split /\s*=\s*|\s+/, $_, 2; } else { # no guess, use one of the configured strict split policies ($option,$value) = split /$this->{SplitDelimiter}/, $_, 2; } } if($this->{NormalizeOption}) { $option = $this->{NormalizeOption}($option); } if ($value && $value =~ /^"/ && $value =~ /"$/) { $value =~ s/^"//; # remove leading and trailing " $value =~ s/"$//; } if (! defined $block) { # not inside a block @ the moment if (/^<([^\/]+?.*?)>$/) { # look if it is a block $block = $1; # store block name if ($block =~ /^"([^"]+)"$/) { # quoted block, unquote it and do not split $block =~ s/"//g; } else { # If it is a named block store the name separately; allow the block and name to each be quoted if ($block =~ /^(?:"([^"]+)"|(\S+))(?:\s+(?:"([^"]+)"|(.*)))?$/) { $block = $1 || $2; $blockname = $3 || $4; } } if($this->{NormalizeBlock}) { $block = $this->{NormalizeBlock}($block); if (defined $blockname) { $blockname = $this->{NormalizeBlock}($blockname); if($blockname eq "") { # if, after normalization no blockname is left, remove it $blockname = undef; } } } if ($this->{InterPolateVars}) { # interpolate block(name), add "<" and ">" to the key, because # it is sure that such keys does not exist otherwise. $block = $this->_interpolate($config, "<$block>", $block); if (defined $blockname) { $blockname = $this->_interpolate($config, "<$blockname>", "$blockname"); } } if ($this->{LowerCaseNames}) { $block = lc $block; # only for blocks lc(), if configured via new() } $this->{level} += 1; undef @newcontent; next; } elsif (/^<\/(.+?)>$/) { # it is an end block, but we don't have a matching block! croak "Config::General: EndBlock \"<\/$1>\" has no StartBlock statement (level: $this->{level}, chunk $chunk)!\n"; } else { # insert key/value pair into actual node if ($this->{LowerCaseNames}) { $option = lc $option; } if (exists $config->{$option}) { if ($this->{MergeDuplicateOptions}) { $config->{$option} = $this->_parse_value($config, $option, $value); # bugfix rt.cpan.org#33216 if ($this->{InterPolateVars}) { # save pair on local stack $config->{__stack}->{$option} = $config->{$option}; } } else { if (! $this->{AllowMultiOptions} ) { # no, duplicates not allowed croak "Config::General: Option \"$option\" occurs more than once (level: $this->{level}, chunk $chunk)!\n"; } else { # yes, duplicates allowed if (ref($config->{$option}) ne 'ARRAY') { # convert scalar to array my $savevalue = $config->{$option}; delete $config->{$option}; push @{$config->{$option}}, $savevalue; } eval { # check if arrays are supported by the underlying hash my $i = scalar @{$config->{$option}}; }; if ($EVAL_ERROR) { $config->{$option} = $this->_parse_value($config, $option, $value); } else { # it's already an array, just push push @{$config->{$option}}, $this->_parse_value($config, $option, $value); } } } } else { if($this->{ForceArray} && defined $value && $value =~ /^\[\s*(.+?)\s*\]$/) { # force single value array entry push @{$config->{$option}}, $this->_parse_value($config, $option, $1); } else { # standard config option, insert key/value pair into node $config->{$option} = $this->_parse_value($config, $option, $value); if ($this->{InterPolateVars}) { # save pair on local stack $config->{__stack}->{$option} = $config->{$option}; } } } } } elsif (/^<([^\/]+?.*?)>$/) { # found a start block inside a block, don't forget it $block_level++; # $block_level indicates wether we are still inside a node push @newcontent, $_; # push onto new content stack for later recursive call of _parse() } elsif (/^<\/(.+?)>$/) { if ($block_level) { # this endblock is not the one we are searching for, decrement and push $block_level--; # if it is 0, then the endblock was the one we searched for, see below push @newcontent, $_; # push onto new content stack } else { # calling myself recursively, end of $block reached, $block_level is 0 if (defined $blockname) { # a named block, make it a hashref inside a hash within the current node if (! exists $config->{$block}) { # Make sure that the hash is not created implicitly $config->{$block} = $this->_hashref(); if ($this->{InterPolateVars}) { # inherit current __stack to new block $config->{$block}->{__stack} = $this->_copy($config->{__stack}); } } if (ref($config->{$block}) eq '') { croak "Config::General: Block <$block> already exists as scalar entry!\n"; } elsif (ref($config->{$block}) eq 'ARRAY') { croak "Config::General: Cannot append named block <$block $blockname> to array of scalars!\n" ."Block <$block> or scalar '$block' occurs more than once.\n" ."Turn on -MergeDuplicateBlocks or make sure <$block> occurs only once in the config.\n"; } elsif (exists $config->{$block}->{$blockname}) { # the named block already exists, make it an array if ($this->{MergeDuplicateBlocks}) { # just merge the new block with the same name as an existing one into # this one. $config->{$block}->{$blockname} = $this->_parse($config->{$block}->{$blockname}, \@newcontent); } else { if (! $this->{AllowMultiOptions}) { croak "Config::General: Named block \"<$block $blockname>\" occurs more than once (level: $this->{level}, chunk $chunk)!\n"; } else { # preserve existing data my $savevalue = $config->{$block}->{$blockname}; delete $config->{$block}->{$blockname}; my @ar; if (ref $savevalue eq 'ARRAY') { push @ar, @{$savevalue}; # preserve array if any } else { push @ar, $savevalue; } push @ar, $this->_parse( $this->_hashref(), \@newcontent); # append it $config->{$block}->{$blockname} = \@ar; } } } else { # the first occurrence of this particular named block my $tmphash = $this->_hashref(); if ($this->{InterPolateVars}) { # inherit current __stack to new block $tmphash->{__stack} = $this->_copy($config->{__stack}); } $config->{$block}->{$blockname} = $this->_parse($tmphash, \@newcontent); } } else { # standard block if (exists $config->{$block}) { if (ref($config->{$block}) eq '') { croak "Config::General: Cannot create hashref from <$block> because there is\n" ."already a scalar option '$block' with value '$config->{$block}'\n"; } # the block already exists, make it an array if ($this->{MergeDuplicateBlocks}) { # just merge the new block with the same name as an existing one into # this one. $config->{$block} = $this->_parse($config->{$block}, \@newcontent); } else { if (! $this->{AllowMultiOptions}) { croak "Config::General: Block \"<$block>\" occurs more than once (level: $this->{level}, chunk $chunk)!\n"; } else { my $savevalue = $config->{$block}; delete $config->{$block}; my @ar; if (ref $savevalue eq "ARRAY") { push @ar, @{$savevalue}; } else { push @ar, $savevalue; } # fixes rt#31529 my $tmphash = $this->_hashref(); if ($this->{InterPolateVars}) { # inherit current __stack to new block $tmphash->{__stack} = $this->_copy($config->{__stack}); } push @ar, $this->_parse( $tmphash, \@newcontent); $config->{$block} = \@ar; } } } else { # the first occurrence of this particular block my $tmphash = $this->_hashref(); if ($this->{InterPolateVars}) { # inherit current __stack to new block $tmphash->{__stack} = $this->_copy($config->{__stack}); } $config->{$block} = $this->_parse($tmphash, \@newcontent); } } undef $blockname; undef $block; $this->{level} -= 1; next; } } else { # inside $block, just push onto new content stack push @newcontent, $_; } } if ($block) { # $block is still defined, which means, that it had # no matching endblock! croak "Config::General: Block \"<$block>\" has no EndBlock statement (level: $this->{level}, chunk $chunk)!\n"; } return $config; } sub _copy { # # copy the contents of one hash into another # to circumvent invalid references # fixes rt.cpan.org bug #35122 my($this, $source) = @_; my %hash = (); while (my ($key, $value) = each %{$source}) { $hash{$key} = $value; } return \%hash; } sub _parse_value { # # parse the value if value parsing is turned on # by either -AutoTrue and/or -FlagBits # otherwise just return the given value unchanged # my($this, $config, $option, $value) =@_; my $cont; ($cont, $option, $value) = $this->_hook('pre_parse_value', $option, $value); return $value if(!$cont); # avoid "Use of uninitialized value" if (! defined $value) { # patch fix rt#54583 # Return an input undefined value without trying transformations return $value; } if($this->{NormalizeValue}) { $value = $this->{NormalizeValue}($value); } if ($this->{InterPolateVars}) { $value = $this->_interpolate($config, $option, $value); } # make true/false values to 1 or 0 (-AutoTrue) if ($this->{AutoTrue}) { if ($value =~ /$this->{AutoTrueFlags}->{true}/io) { $value = 1; } elsif ($value =~ /$this->{AutoTrueFlags}->{false}/io) { $value = 0; } } # assign predefined flags or undef for every flag | flag ... (-FlagBits) if ($this->{FlagBits}) { if (exists $this->{FlagBitsFlags}->{$option}) { my %__flags = map { $_ => 1 } split /\s*\|\s*/, $value; foreach my $flag (keys %{$this->{FlagBitsFlags}->{$option}}) { if (exists $__flags{$flag}) { $__flags{$flag} = $this->{FlagBitsFlags}->{$option}->{$flag}; } else { $__flags{$flag} = undef; } } $value = \%__flags; } } if (!$this->{NoEscape}) { # are there any escaped characters left? put them out as is $value =~ s/\\([\$\\\"#])/$1/g; } ($cont, $option, $value) = $this->_hook('post_parse_value', $option, $value); return $value; } sub _hook { my ($this, $hook, @arguments) = @_; if(exists $this->{Plug}->{$hook}) { my $sub = $this->{Plug}->{$hook}; my @hooked = &$sub(@arguments); return @hooked; } return (1, @arguments); } sub save { # # this is the old version of save() whose API interface # has been changed. I'm very sorry 'bout this. # # I'll try to figure out, if it has been called correctly # and if yes, feed the call to Save(), otherwise croak. # my($this, $one, @two) = @_; if ( (@two && $one) && ( (scalar @two) % 2 == 0) ) { # @two seems to be a hash my %h = @two; $this->save_file($one, \%h); } else { croak q(Config::General: The save() method is deprecated. Use the new save_file() method instead!); } return; } sub save_file { # # save the config back to disk # my($this, $file, $config) = @_; my $fh; my $config_string; if (!$file) { croak "Config::General: Filename is required!"; } else { if ($this->{UTF8}) { $fh = IO::File->new; open($fh, ">:utf8", $file) or croak "Config::General: Could not open $file in UTF8 mode!($!)\n"; } else { $fh = IO::File->new( "$file", 'w') or croak "Config::General: Could not open $file!($!)\n"; } if (!$config) { if (exists $this->{config}) { $config_string = $this->_store(0, $this->{config}); } else { croak "Config::General: No config hash supplied which could be saved to disk!\n"; } } else { $config_string = $this->_store(0, $config); } if ($config_string) { print {$fh} $config_string; } else { # empty config for whatever reason, I don't care print {$fh} q(); } close $fh; } return; } sub save_string { # # return the saved config as a string # my($this, $config) = @_; if (!$config || ref($config) ne 'HASH') { if (exists $this->{config}) { return $this->_store(0, $this->{config}); } else { croak "Config::General: No config hash supplied which could be saved to disk!\n"; } } else { return $this->_store(0, $config); } return; } sub _store { # # internal sub for saving a block # my($this, $level, $config) = @_; local $_; my $indent = q( ) x $level; my $config_string = q(); foreach my $entry ( $this->{SaveSorted} ? sort keys %$config : keys %$config ) { # fix rt#104548 if ($entry =~ /[<>\n\r]/) { croak "Config::General: current key contains invalid characters: $entry!\n"; } if (ref($config->{$entry}) eq 'ARRAY') { if( $this->{ForceArray} && scalar @{$config->{$entry}} == 1 && ! ref($config->{$entry}->[0]) ) { # a single value array forced to stay as array $config_string .= $this->_write_scalar($level, $entry, '[' . $config->{$entry}->[0] . ']'); } else { foreach my $line ( $this->{SaveSorted} ? sort @{$config->{$entry}} : @{$config->{$entry}} ) { if (ref($line) eq 'HASH') { $config_string .= $this->_write_hash($level, $entry, $line); } else { $config_string .= $this->_write_scalar($level, $entry, $line); } } } } elsif (ref($config->{$entry}) eq 'HASH') { $config_string .= $this->_write_hash($level, $entry, $config->{$entry}); } else { $config_string .= $this->_write_scalar($level, $entry, $config->{$entry}); } } return $config_string; } sub _write_scalar { # # internal sub, which writes a scalar # it returns it, in fact # my($this, $level, $entry, $line) = @_; my $indent = q( ) x $level; my $config_string; # patch fix rt#54583 if ( ! defined $line ) { $config_string .= $indent . $entry . "\n"; } elsif ($line =~ /\n/ || $line =~ /\\$/) { # it is a here doc my $delimiter; my $tmplimiter = 'EOF'; while (!$delimiter) { # create a unique here-doc identifier if ($line =~ /$tmplimiter/s) { $tmplimiter .= '%'; } else { $delimiter = $tmplimiter; } } my @lines = split /\n/, $line; $config_string .= $indent . $entry . $this->{StoreDelimiter} . "<<$delimiter\n"; foreach (@lines) { $config_string .= $indent . $_ . "\n"; } $config_string .= $indent . "$delimiter\n"; } else { # a simple stupid scalar entry if (!$this->{NoEscape}) { # re-escape contained $ or # or \ chars $line =~ s/([#\$\\\"])/\\$1/g; } if ($line =~ /^\s/ || $line =~ /\s$/ || $this->{AlwaysQuoteOutput}) { # quote lines containing whitespace $line = "\"$line\""; } $config_string .= $indent . $entry . $this->{StoreDelimiter} . $line . "\n"; } return $config_string; } sub _write_hash { # # internal sub, which writes a hash (block) # it returns it, in fact # my($this, $level, $entry, $line) = @_; my $indent = q( ) x $level; my $config_string; if ($entry =~ /\s/) { # quote the entry if it contains whitespaces $entry = q(") . $entry . q("); } # check if the next level key points to a hash and is the only one # in this case put out a named block # fixes rt.77667 my $num = scalar keys %{$line}; if($num == 1) { my $key = (keys %{$line})[0]; if(ref($line->{$key}) eq 'HASH') { $config_string .= $indent . qq(<$entry $key>\n); $config_string .= $this->_store($level + 1, $line->{$key}); $config_string .= $indent . qq(\n"; return $config_string; } } $config_string .= $indent . q(<) . $entry . ">\n"; $config_string .= $this->_store($level + 1, $line); $config_string .= $indent . q(\n"; return $config_string } sub _hashref { # # return a probably tied new empty hash ref # my($this) = @_; if ($this->{Tie}) { eval { eval qq{require $this->{Tie}}; }; if ($EVAL_ERROR) { croak q(Config::General: Could not create a tied hash of type: ) . $this->{Tie} . q(: ) . $EVAL_ERROR; } my %hash; tie %hash, $this->{Tie}; return \%hash; } else { return {}; } } # # Procedural interface # sub ParseConfig { # # @_ may contain everything which is allowed for new() # return (new Config::General(@_))->getall(); } sub SaveConfig { # # 2 parameters are required, filename and hash ref # my ($file, $hash) = @_; if (!$file || !$hash) { croak q{Config::General::SaveConfig(): filename and hash argument required.}; } else { if (ref($hash) ne 'HASH') { croak q(Config::General::SaveConfig() The second parameter must be a reference to a hash!); } else { (new Config::General(-ConfigHash => $hash))->save_file($file); } } return; } sub SaveConfigString { # # same as SaveConfig, but return the config, # instead of saving it # my ($hash) = @_; if (!$hash) { croak q{Config::General::SaveConfigString(): Hash argument required.}; } else { if (ref($hash) ne 'HASH') { croak q(Config::General::SaveConfigString() The parameter must be a reference to a hash!); } else { return (new Config::General(-ConfigHash => $hash))->save_string(); } } return; } # keep this one 1; __END__ =head1 NAME Config::General - Generic Config Module =head1 SYNOPSIS # # the OOP way use Config::General; $conf = Config::General->new("rcfile"); my %config = $conf->getall; # # the procedural way use Config::General qw(ParseConfig SaveConfig SaveConfigString); my %config = ParseConfig("rcfile"); =head1 DESCRIPTION This module opens a config file and parses its contents for you. The B method requires one parameter which needs to be a filename. The method B returns a hash which contains all options and its associated values of your config file. The format of config files supported by B is inspired by the well known Apache config format, in fact, this module is 100% compatible to Apache configs, but you can also just use simple name/value pairs in your config files. In addition to the capabilities of an Apache config file it supports some enhancements such as here-documents, C-style comments or multiline options. =head1 SUBROUTINES/METHODS =over =item new() Possible ways to call B: $conf = Config::General->new("rcfile"); $conf = Config::General->new(\%somehash); $conf = Config::General->new( %options ); # see below for description of possible options This method returns a B object (a hash blessed into "Config::General" namespace. All further methods must be used from that returned object. see below. You can use the new style with hash parameters or the old style which is of course still supported. Possible parameters to B are: * a filename of a configfile, which will be opened and parsed by the parser or * a hash reference, which will be used as the config. An alternative way to call B is supplying an option- hash with one or more of the following keys set: =over =item B<-ConfigFile> A filename or a filehandle, i.e.: -ConfigFile => "rcfile" or -ConfigFile => \$FileHandle =item B<-ConfigHash> A hash reference, which will be used as the config, i.e.: -ConfigHash => \%somehash =item B<-String> A string which contains a whole config, or an arrayref containing the whole config line by line. The parser will parse the contents of the string instead of a file. i.e: -String => $complete_config it is also possible to feed an array reference to -String: -String => \@config_lines =item B<-AllowMultiOptions> If the value is "no", then multiple identical options are disallowed. The default is "yes". i.e.: -AllowMultiOptions => "yes" see B for details. =item B<-LowerCaseNames> If set to a true value, then all options found in the config will be converted to lowercase. This allows you to provide case-in-sensitive configs. The values of the options will B lowercased. =item B<-UseApacheInclude> If set to a true value, the parser will consider "include ..." as valid include statement (just like the well known Apache include statement). It also supports apache's "IncludeOptional" statement with the same behavior, that is, if the include file doesn't exist no error will be thrown. =item B<-IncludeRelative> If set to a true value, included files with a relative path (i.e. "cfg/blah.conf") will be opened from within the location of the configfile instead from within the location of the script($0). This works only if the configfile has a absolute pathname (i.e. "/etc/main.conf"). If the variable B<-ConfigPath> has been set and if the file to be included could not be found in the location relative to the current config file, the module will search within B<-ConfigPath> for the file. See the description of B<-ConfigPath> for more details. =item B<-IncludeDirectories> If set to a true value, you may specify include a directory, in which case all files inside the directory will be loaded in ASCII order. Directory includes will not recurse into subdirectories. This is comparable to including a directory in Apache-style config files. =item B<-IncludeGlob> If set to a true value, you may specify a glob pattern for an include to include all matching files (e.g. <>). Also note that as with standard file patterns, * will not match dot-files, so <> is often more desirable than including a directory with B<-IncludeDirectories>. An include option will not cause a parser error if the glob didn't return anything. =item B<-IncludeAgain> If set to a true value, you will be able to include a sub-configfile multiple times. With the default, false, you will get a warning about duplicate includes and only the first include will succeed. Reincluding a configfile can be useful if it contains data that you want to be present in multiple places in the data tree. See the example under L. Note, however, that there is currently no check for include recursion. =item B<-ConfigPath> As mentioned above, you can use this variable to specify a search path for relative config files which have to be included. Config::General will search within this path for the file if it cannot find the file at the location relative to the current config file. To provide multiple search paths you can specify an array reference for the path. For example: @path = qw(/usr/lib/perl /nfs/apps/lib /home/lib); .. -ConfigPath => \@path =item B<-MergeDuplicateBlocks> If set to a true value, then duplicate blocks, that means blocks and named blocks, will be merged into a single one (see below for more details on this). The default behavior of Config::General is to create an array if some junk in a config appears more than once. =item B<-MergeDuplicateOptions> If set to a true value, then duplicate options will be merged. That means, if the same option occurs more than once, the last one will be used in the resulting config hash. Setting this option implies B<-AllowMultiOptions == false> unless you set B<-AllowMultiOptions> explicit to 'true'. In this case duplicate blocks are allowed and put into an array but duplicate options will be merged. =item B<-AutoLaunder> If set to a true value, then all values in your config file will be laundered to allow them to be used under a -T taint flag. This could be regarded as circumventing the purpose of the -T flag, however, if the bad guys can mess with your config file, you have problems that -T will not be able to stop. AutoLaunder will only handle a config file being read from -ConfigFile. =item B<-AutoTrue> If set to a true value, then options in your config file, whose values are set to true or false values, will be normalised to 1 or 0 respectively. The following values will be considered as B: yes, on, 1, true The following values will be considered as B: no, off, 0, false This effect is case-insensitive, i.e. both "Yes" or "No" will result in 1. =item B<-FlagBits> This option takes one required parameter, which must be a hash reference. The supplied hash reference needs to define variables for which you want to preset values. Each variable you have defined in this hash-ref and which occurs in your config file, will cause this variable being set to the preset values to which the value in the config file refers to. Multiple flags can be used, separated by the pipe character |. Well, an example will clarify things: my $conf = Config::General->new( -ConfigFile => "rcfile", -FlagBits => { Mode => { CLEAR => 1, STRONG => 1, UNSECURE => "32bit" } } ); In this example we are defining a variable named I<"Mode"> which may contain one or more of "CLEAR", "STRONG" and "UNSECURE" as value. The appropriate config entry may look like this: # rcfile Mode = CLEAR | UNSECURE The parser will create a hash which will be the value of the key "Mode". This hash will contain B flags which you have pre-defined, but only those which were set in the config will contain the pre-defined value, the other ones will be undefined. The resulting config structure would look like this after parsing: %config = ( Mode => { CLEAR => 1, UNSECURE => "32bit", STRONG => undef, } ); This method allows the user (or, the "maintainer" of the configfile for your application) to set multiple pre-defined values for one option. Please beware, that all occurrences of those variables will be handled this way, there is no way to distinguish between variables in different scopes. That means, if "Mode" would also occur inside a named block, it would also parsed this way. Values which are not defined in the hash-ref supplied to the parameter B<-FlagBits> and used in the corresponding variable in the config will be ignored. Example: # rcfile Mode = BLAH | CLEAR would result in this hash structure: %config = ( Mode => { CLEAR => 1, UNSECURE => undef, STRONG => undef, } ); "BLAH" will be ignored silently. =item B<-DefaultConfig> This can be a hash reference or a simple scalar (string) of a config. This causes the module to preset the resulting config hash with the given values, which allows you to set default values for particular config options directly. Note that you probably want to use this with B<-MergeDuplicateOptions>, otherwise a default value already in the configuration file will produce an array of two values. =item B<-Tie> B<-Tie> takes the name of a Tie class as argument that each new hash should be based off of. This hash will be used as the 'backing hash' instead of a standard Perl hash, which allows you to affect the way, variable storing will be done. You could, for example supply a tied hash, say Tie::DxHash, which preserves ordering of the keys in the config (which a standard Perl hash won't do). Or, you could supply a hash tied to a DBM file to save the parsed variables to disk. There are many more things to do in tie-land, see L to get some interesting ideas. If you want to use the B<-Tie> feature together with B<-DefaultConfig> make sure that the hash supplied to B<-DefaultConfig> must be tied to the same Tie class. Make sure that the hash which receives the generated hash structure (e.g. which you are using in the assignment: %hash = $config->getall()) must be tied to the same Tie class. Example: use Config::General qw(ParseConfig); use Tie::IxHash; tie my %hash, "Tie::IxHash"; %hash = ParseConfig( -ConfigFile => shift(), -Tie => "Tie::IxHash" ); =item B<-InterPolateVars> If set to a true value, variable interpolation will be done on your config input. See L for more information. =item B<-InterPolateEnv> If set to a true value, environment variables can be used in configs. This implies B<-InterPolateVars>. =item B<-AllowSingleQuoteInterpolation> By default variables inside single quotes will not be interpolated. If you turn on this option, they will be interpolated as well. =item B<-ExtendedAccess> If set to a true value, you can use object oriented (extended) methods to access the parsed config. See L for more information. =item B<-StrictObjects> By default this is turned on, which causes Config::General to croak with an error if you try to access a non-existent key using the OOP-way (B<-ExtendedAcess> enabled). If you turn B<-StrictObjects> off (by setting to 0 or "no") it will just return an empty object/hash/scalar. This is valid for OOP-access 8via AUTOLOAD and for the methods obj(), hash() and value(). =item B<-StrictVars> By default this is turned on, which causes Config::General to croak with an error if an undefined variable with B turned on occurs in a config. Set to I (i.e. 0) to avoid such error messages. =item B<-SplitPolicy> You can influence the way how Config::General decides which part of a line in a config file is the key and which one is the value. By default it tries its best to guess. That means you can mix equalsign assignments and whitespace assignments. However, sometime you may wish to make it more strictly for some reason. In this case you can set B<-SplitPolicy>. The possible values are: 'guess' which is the default, 'whitespace' which causes the module to split by whitespace, 'equalsign' which causes it to split strictly by equal sign, or 'custom'. In the latter case you must also set B<-SplitDelimiter> to some regular expression of your choice. For example: -SplitDelimiter => '\s*:\s*' will cause the module to split by colon while whitespace which surrounds the delimiter will be removed. Please note that the delimiter used when saving a config (save_file() or save_string()) will be chosen according to the current B<-SplitPolicy>. If -SplitPolicy is set to 'guess' or 'whitespace', 3 spaces will be used to delimit saved options. If 'custom' is set, then you need to set B<-StoreDelimiter>. =item B<-SplitDelimiter> Set this to any arbitrary regular expression which will be used for option/value splitting. B<-SplitPolicy> must be set to 'custom' to make this work. =item B<-StoreDelimiter> You can use this parameter to specify a custom delimiter to use when saving configs to a file or string. You only need to set it if you want to store the config back to disk and if you have B<-SplitPolicy> set to 'custom'. However, this parameter takes precedence over whatever is set for B<-SplitPolicy>. Be very careful with this parameter. =item B<-CComments> Config::General is able to notice c-style comments (see section COMMENTS). But for some reason you might no need this. In this case you can turn this feature off by setting B<-CComments> to a false value('no', 0, 'off'). By default B<-CComments> is turned on. =item B<-BackslashEscape> B. =item B<-SlashIsDirectory> If you turn on this parameter, a single slash as the last character of a named block will be considered as a directory name. By default this flag is turned off, which makes the module somewhat incompatible to Apache configs, since such a setup will be normally considered as an explicit empty block, just as XML defines it. For example, if you have the following config: Index index.awk you will get such an error message from the parser: EndBlock "" has no StartBlock statement (level: 1, chunk 10)! This is caused by the fact that the config chunk below will be internally converted to: Index index.awk Now there is one '' too much. The proper solution is to use quotation to circumvent this error: Index index.awk However, a raw apache config comes without such quotes. In this case you may consider to turn on B<-SlashIsDirectory>. Please note that this is a new option (incorporated in version 2.30), it may lead to various unexpected side effects or other failures. You've been warned. =item B<-UseApacheIfDefine> Enables support for Apache ... . See -Define. =item B<-Define> Defines the symbols to be used for conditional configuration files. Allowed arguments: scalar, scalar ref, array ref or hash ref. Examples: -Define => 'TEST' -Define => \$testOrProduction -Define => [qw(TEST VERBOSE)] -Define => {TEST => 1, VERBOSE => 1} Sample configuration: Level Debug include test/*.cfg Level Notice include production/*.cfg =item B<-ApacheCompatible> Over the past years a lot of options has been incorporated into Config::General to be able to parse real Apache configs. The new B<-ApacheCompatible> option now makes it possible to tweak all options in a way that Apache configs can be parsed. This is called "apache compatibility mode" - if you will ever have problems with parsing Apache configs without this option being set, you'll get no help by me. Thanks :) The following options will be set: UseApacheInclude = 1 IncludeRelative = 1 IncludeDirectories = 1 IncludeGlob = 1 SlashIsDirectory = 1 SplitPolicy = 'whitespace' CComments = 0 UseApacheIfDefine = 1 Take a look into the particular documentation sections what those options are doing. Beside setting some options it also turns off support for explicit empty blocks. =item B<-UTF8> If turned on, all files will be opened in utf8 mode. This may not work properly with older versions of Perl. =item B<-SaveSorted> If you want to save configs in a sorted manner, turn this parameter on. It is not enabled by default. =item B<-NoEscape> If you want to use the data ( scalar or final leaf ) without escaping special character, turn this parameter on. It is not enabled by default. =item B<-NormalizeBlock> Takes a subroutine reference as parameter and gets the current block or blockname passed as parameter and is expected to return it in some altered way as a scalar string. The sub will be called before anything else will be done by the module itself (e.g. interpolation). Example: -NormalizeBlock => sub { my $x = shift; $x =~ s/\s*$//; $x; } This removes trailing whitespaces of block names. =item B<-NormalizeOption> Same as B<-NormalizeBlock> but applied on options only. =item B<-NormalizeValue> Same as B<-NormalizeBlock> but applied on values only. =item B<-AlwaysQuoteOutput> If set to true, then values containing whitespace will always quoted when calling C or C. =back =item getall() Returns a hash structure which represents the whole config. =item files() Returns a list of all files read in. =item save_file() Writes the config hash back to the hard disk. This method takes one or two parameters. The first parameter must be the filename where the config should be written to. The second parameter is optional, it must be a reference to a hash structure, if you set it. If you do not supply this second parameter then the internal config hash, which has already been parsed, will be used. Please note that any occurrence of comments will be ignored by getall() and thus be lost after you call this method. You need also to know that named blocks will be converted to nested blocks (which is the same from the perl point of view). An example: id 13 will become the following after saving: id 13 Example: $conf_obj->save_file("newrcfile", \%config); or, if the config has already been parsed, or if it didn't change: $conf_obj->save_file("newrcfile"); =item save_string() This method is equivalent to the previous save_file(), but it does not store the generated config to a file. Instead it returns it as a string, which you can save yourself afterwards. It takes one optional parameter, which must be a reference to a hash structure. If you omit this parameter, the internal config hash, which has already been parsed, will be used. Example: my $content = $conf_obj->save_string(\%config); or: my $content = $conf_obj->save_string(); =back =head1 CONFIG FILE FORMAT Lines beginning with B<#> and empty lines will be ignored. (see section COMMENTS!) Spaces at the beginning and the end of a line will also be ignored as well as tabulators. If you need spaces at the end or the beginning of a value you can surround it with double quotes. An option line starts with its name followed by a value. An equal sign is optional. Some possible examples: user max user = max user max If there are more than one statements with the same name, it will create an array instead of a scalar. See the example below. The method B returns a hash of all values. =head1 BLOCKS You can define a B of options. A B looks much like a block in the wellknown Apache config format. It starts with EBE and ends with E/BE. A block start and end cannot be on the same line. An example: host = muli user = moare dbname = modb dbpass = D4r_9Iu Blocks can also be nested. Here is a more complicated example: user = hans server = mc200 db = maxis passwd = D3rf$ user = tom db = unknown host = mila index int(100000) name char(100) prename char(100) city char(100) status int(10) allowed moses allowed ingram allowed joice The hash which the method B returns look like that: print Data::Dumper(\%hash); $VAR1 = { 'passwd' => 'D3rf$', 'jonas' => { 'tablestructure' => { 'prename' => 'char(100)', 'index' => 'int(100000)', 'city' => 'char(100)', 'name' => 'char(100)', 'status' => 'int(10)', 'allowed' => [ 'moses', 'ingram', 'joice', ] }, 'host' => 'mila', 'db' => 'unknown', 'user' => 'tom' }, 'db' => 'maxis', 'server' => 'mc200', 'user' => 'hans' }; If you have turned on B<-LowerCaseNames> (see new()) then blocks as in the following example: Owner root would produce the following hash structure: $VAR1 = { 'dir' => { 'attributes' => { 'owner' => "root", } } }; As you can see, the keys inside the config hash are normalized. Please note, that the above config block would result in a valid hash structure, even if B<-LowerCaseNames> is not set! This is because I does not use the block names to check if a block ends, instead it uses an internal state counter, which indicates a block end. If the module cannot find an end-block statement, then this block will be ignored. =head1 NAMED BLOCKS If you need multiple blocks of the same name, then you have to name every block. This works much like Apache config. If the module finds a named block, it will create a hashref with the left part of the named block as the key containing one or more hashrefs with the right part of the block as key containing everything inside the block(which may again be nested!). As examples says more than words: # given the following sample Limit Deny Options ExecCgi Index Limit DenyAll Options None # you will get: $VAR1 = { 'Directory' => { '/usr/frik' => { 'Options' => 'None', 'Limit' => 'DenyAll' }, '/usr/frisco' => { 'Options' => 'ExecCgi Index', 'Limit' => 'Deny' } } }; You cannot have more than one named block with the same name because it will be stored in a hashref and therefore be overwritten if a block occurs once more. =head1 WHITESPACE IN BLOCKS The normal behavior of Config::General is to look for whitespace in block names to decide if it's a named block or just a simple block. Sometimes you may need blocknames which have whitespace in their names. With named blocks this is no problem, as the module only looks for the first whitespace: would be parsed to: $VAR1 = { 'person' => { 'hugo gera' => { }, } }; The problem occurs, if you want to have a simple block containing whitespace: This would be parsed as a named block, which is not what you wanted. In this very case you may use quotation marks to indicate that it is not a named block: <"hugo gera"> The save() method of the module inserts automatically quotation marks in such cases. =head1 EXPLICIT EMPTY BLOCKS Beside the notation of blocks mentioned above it is possible to use explicit empty blocks. Normally you would write this in your config to define an empty block: To save writing you can also write: which is the very same as above. This works for normal blocks and for named blocks. =head1 IDENTICAL OPTIONS (ARRAYS) You may have more than one line of the same option with different values. Example: log log1 log log2 log log2 You will get a scalar if the option occurred only once or an array if it occurred more than once. If you expect multiple identical options, then you may need to check if an option occurred more than once: $allowed = $hash{jonas}->{tablestructure}->{allowed}; if (ref($allowed) eq "ARRAY") { @ALLOWED = @{$allowed}; else { @ALLOWED = ($allowed); } } The same applies to blocks and named blocks too (they are described in more detail below). For example, if you have the following config: user max user hannes then you would end up with a data structure like this: $VAR1 = { 'dir' => { 'blah' => [ { 'user' => 'max' }, { 'user' => 'hannes' } ] } }; As you can see, the two identical blocks are stored in a hash which contains an array(-reference) of hashes. Under some rare conditions you might not want this behavior with blocks (and named blocks too). If you want to get one single hash with the contents of both identical blocks, then you need to turn the B parameter B<-MergeDuplicateBlocks> on (see above). The parsed structure of the example above would then look like this: $VAR1 = { 'dir' => { 'blah' => { 'user' => [ 'max', 'hannes' ] } } }; As you can see, there is only one hash "dir->{blah}" containing multiple "user" entries. As you can also see, turning on B<-MergeDuplicateBlocks> does not affect scalar options (i.e. "option = value"). In fact you can tune merging of duplicate blocks and options independent from each other. If you don't want to allow more than one identical options, you may turn it off by setting the flag I in the B method to "no". If turned off, Config::General will complain about multiple occurring options with identical names! =head2 FORCE SINGLE VALUE ARRAYS You may also force a single config line to get parsed into an array by turning on the option B<-ForceArray> and by surrounding the value of the config entry by []. Example: hostlist = [ foo.bar ] Will be a singlevalue array entry if the option is turned on. If you want it to remain to be an array you have to turn on B<-ForceArray> during save too. =head1 LONG LINES If you have a config value, which is too long and would take more than one line, you can break it into multiple lines by using the backslash character at the end of the line. The Config::General module will concatenate those lines to one single-value. Example: command = cat /var/log/secure/tripwire | \ mail C<-s> "report from tripwire" \ honey@myotherhost.nl command will become: "cat /var/log/secure/tripwire | mail C<-s> 'report from twire' honey@myotherhost.nl" =head1 HERE DOCUMENTS You can also define a config value as a so called "here-document". You must tell the module an identifier which indicates the end of a here document. An identifier must follow a "<<". Example: message <. There is a special feature which allows you to use indentation with here documents. You can have any amount of whitespace or tabulators in front of the end identifier. If the module finds spaces or tabs then it will remove exactly those amount of spaces from every line inside the here-document. Example: message <> If you turned on B<-UseApacheInclude> (see B), then you can also use the following statement to include an external file: include externalconfig.rc This file will be inserted at the position where it was found as if the contents of this file were directly at this position. You can also recursively include files, so an included file may include another one and so on. Beware that you do not recursively load the same file, you will end with an error message like "too many open files in system!". By default included files with a relative pathname will be opened from within the current working directory. Under some circumstances it maybe possible to open included files from the directory, where the configfile resides. You need to turn on the option B<-IncludeRelative> (see B) if you want that. An example: my $conf = Config::General( -ConfigFile => "/etc/crypt.d/server.cfg" -IncludeRelative => 1 ); /etc/crypt.d/server.cfg: <> In this example Config::General will try to include I from I: /etc/crypt.d/acl.cfg The default behavior (if B<-IncludeRelative> is B set!) will be to open just I, wherever it is, i.e. if you did a chdir("/usr/local/etc"), then Config::General will include: /usr/local/etc/acl.cfg Include statements can be case insensitive (added in version 1.25). Include statements will be ignored within C-Comments and here-documents. By default, a config file will only be included the first time it is referenced. If you wish to include a file in multiple places, set B to true. But be warned: this may lead to infinite loops, so make sure, you're not including the same file from within itself! Example: # main.cfg class=Some::Class include printers.cfg # ... class=Another::Class include printers.cfg # ... Now C will be include in both the C and C objects. You will have to be careful to not recursively include a file. Behaviour in this case is undefined. =head1 COMMENTS A comment starts with the number sign B<#>, there can be any number of spaces and/or tab stops in front of the #. A comment can also occur after a config statement. Example: username = max # this is the comment If you want to comment out a large block you can use C-style comments. A B signals the begin of a comment block and the B<*/> signals the end of the comment block. Example: user = max # valid option db = tothemax /* user = andors db = toand */ In this example the second options of user and db will be ignored. Please beware of the fact, if the Module finds a B string which is the start of a comment block, but no matching end block, it will ignore the whole rest of the config file! B If you require the B<#> character (number sign) to remain in the option value, then you can use a backslash in front of it, to escape it. Example: bgcolor = \#ffffcc In this example the value of $config{bgcolor} will be "#ffffcc", Config::General will not treat the number sign as the begin of a comment because of the leading backslash. Inside here-documents escaping of number signs is NOT required! =head1 PARSER PLUGINS You can alter the behavior of the parser by supplying closures which will be called on certain hooks during config file processing and parsing. The general aproach works like this: sub ck { my($file, $base) = @_; print "_open() tries $file ... "; if ($file =~ /blah/) { print "ignored\n"; return (0); } else { print "allowed\n"; return (1, @_); } } my %c = ParseConfig( -IncludeGlob => 1, -UseApacheInclude => 1, -ConfigFile => shift, -Plug => { pre_open => *ck } ); Output: _open() tries cfg ... allowed _open() tries x/*.conf ... allowed _open() tries x/1.conf ... allowed _open() tries x/2.conf ... allowed _open() tries x/blah.conf ... ignored As you can see, we wrote a little sub which takes a filename and a base directory as parameters. We tell Config::General via the B parameter of B to call this sub everytime before it attempts to open a file. General processing continues as usual if the first value of the returned array is true. The second value of that array depends on the kind of hook being called. The following hooks are available so far: =over =item B Takes two parameters: filename and basedirectory. Has to return an array consisting of 3 values: - 1 or 0 (continue processing or not) - filename - base directory =item B Takes two parameters: the filehandle of the file to be read and an array containing the raw contents of said file. This hook will be applied in _read(). File contents are already available at this stage, comments will be removed, here-docs normalized and the like. This hook gets the unaltered, original contents. Has to return an array of 3 values: - 1 or 0 (continue processing or not) - the filehandle - an array of strings You can use this hook to apply your own normalizations or whatever. Be careful when returning the abort value (1st value of returned array 0), since in this case nothing else would be done on the contents. If it still contains comments or something, they will be parsed as legal config options. =item B Takes one parameter: a reference to an array containing the prepared config lines (after being processed by _read()). This hook will be applied in _read() when everything else has been done. Has to return an array of 2 values: - 1 or 0 (continue processing or not) [Ignored for post hooks] - a reference to an array containing the config lines =item B Takes 2 parameters: an option name and its value. This hook will be applied in _parse_value() before any processing. Has to return an array of 3 values: - 1 or 0 (continue processing or not) - option name - value of the option =item B Almost identical to pre_parse_value, but will be applied after _parse_value() is finished and all usual processing and normalization is done. =back Not implemented yet: hooks for variable interpolation and block parsing. =head1 OBJECT ORIENTED INTERFACE There is a way to access a parsed config the OO-way. Use the module B, which is supplied with the Config::General distribution. =head1 VARIABLE INTERPOLATION You can use variables inside your config files if you like. To do that you have to use the module B, which is supplied with the Config::General distribution. =head1 EXPORTED FUNCTIONS Config::General exports some functions too, which makes it somewhat easier to use it, if you like this. How to import the functions: use Config::General qw(ParseConfig SaveConfig SaveConfigString); =over =item B This function takes exactly all those parameters, which are allowed to the B method of the standard interface. Example: use Config::General qw(ParseConfig); my %config = ParseConfig(-ConfigFile => "rcfile", -AutoTrue => 1); =item B This function requires two arguments, a filename and a reference to a hash structure. Example: use Config::General qw(SaveConfig); .. SaveConfig("rcfile", \%some_hash); =item B This function requires a reference to a config hash as parameter. It generates a configuration based on this hash as the object-interface method B does. Example: use Config::General qw(ParseConfig SaveConfigString); my %config = ParseConfig(-ConfigFile => "rcfile"); .. # change %config something my $content = SaveConfigString(\%config); =back =head1 CONFIGURATION AND ENVIRONMENT No environment variables will be used. =head1 SEE ALSO I recommend you to read the following documents, which are supplied with Perl: perlreftut Perl references short introduction perlref Perl references, the rest of the story perldsc Perl data structures intro perllol Perl data structures: arrays of arrays Config::General::Extended Object oriented interface to parsed configs Config::General::Interpolated Allows one to use variables inside config files =head1 LICENSE AND COPYRIGHT Copyright (c) 2000-2025 Thomas Linden This library is free software; you can redistribute it and/or modify it under the same terms of the Artistic License 2.0. =head1 BUGS AND LIMITATIONS See rt.cpan.org for current bugs, if any. =head1 INCOMPATIBILITIES None known. =head1 DIAGNOSTICS To debug Config::General use the Perl debugger, see L. =head1 DEPENDENCIES Config::General depends on the modules L, L, L, which all are shipped with Perl. =head1 AUTHOR Thomas Linden =head1 VERSION 2.67 =cut Config-General-2.67/META.yml0000644000175000017500000000120414737432447014173 0ustar scipscip--- abstract: unknown author: - unknown build_requires: ExtUtils::MakeMaker: '0' configure_requires: ExtUtils::MakeMaker: '0' dynamic_config: 1 generated_by: 'ExtUtils::MakeMaker version 7.62, CPAN::Meta::Converter version 2.150010' license: artistic_2 meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: '1.4' name: Config-General no_index: directory: - t - inc requires: File::Glob: '0' File::Spec::Functions: '0' FileHandle: '0' IO::File: '0' resources: repository: https://github.com/TLINDEN/Config-General version: '2.67' x_serialization_backend: 'CPAN::Meta::YAML version 0.018' Config-General-2.67/MANIFEST0000644000175000017500000000135714737430122014050 0ustar scipscipChangelog example.cfg General/Extended.pm General/Interpolated.pm General.pm Makefile.PL MANIFEST META.yml META.json README t/apache-include.conf t/apache-include-opt.conf t/cfg.2 t/cfg.3 t/cfg.4 t/cfg.5 t/cfg.6 t/cfg.7 t/cfg.8 t/cfg.16 t/cfg.16a t/cfg.17 t/cfg.19 t/cfg.20.a t/cfg.20.b t/cfg.20.c t/cfg.34 t/cfg.39 t/cfg.40 t/cfg.41 t/cfg.42 t/cfg.43 t/cfg.45 t/cfg.46 t/cfg.51 t/cfg.55 t/cfg.58 t/cfg.59 t/complex.cfg t/complex/n1.cfg t/complex/n2.cfg t/dual-include.conf t/included.conf t/notincluded.conf.not t/run.t t/sub1/cfg.sub1 t/sub1/cfg.sub1b t/sub1/cfg.sub1c t/sub1/cfg.sub1d t/sub1/cfg.sub1e t/sub1/sub2/cfg.sub2 t/sub1/sub2/cfg.sub2b t/sub1/sub2/sub3/cfg.sub3 t/test.rc t/Tie/IxHash.pm t/Tie/README t/utf8_bom/bar.cfg t/utf8_bom/foo.cfg Config-General-2.67/t/0000755000175000017500000000000014737432447013170 5ustar scipscipConfig-General-2.67/t/cfg.160000644000175000017500000000072014225255304014061 0ustar scipscip# variable interpolation test me=blah pr=$me/blubber base = /usr uid = 501 base = /opt base = /usr # set $base to a new value in this scope log = ${base}/log/logfile # use braces home = $base/home/max # $base should be /usr, not /opt ! # block(name) test tag = dir mono = teri <$tag> bl = 1 <$tag mono> bl = 2 bl = 3 <$tag $mono> bl = 3 Config-General-2.67/t/cfg.460000644000175000017500000000012514225255304014063 0ustar scipscipfoo = bar blah = blubber test = $foo 'variable $blah should be kept' and '$foo too' Config-General-2.67/t/sub1/0000755000175000017500000000000014737432447014042 5ustar scipscipConfig-General-2.67/t/sub1/cfg.sub1d0000644000175000017500000000001514225255304015520 0ustar scipsciptest2 value2 Config-General-2.67/t/sub1/cfg.sub1c0000644000175000017500000000001314225255304015515 0ustar scipsciptest value Config-General-2.67/t/sub1/cfg.sub1b0000644000175000017500000000002114225255304015513 0ustar scipscipsub1b_seen = yup Config-General-2.67/t/sub1/sub2/0000755000175000017500000000000014737432447014715 5ustar scipscipConfig-General-2.67/t/sub1/sub2/cfg.sub2b0000644000175000017500000000002114225255304016367 0ustar scipscipsub2b_seen = yup Config-General-2.67/t/sub1/sub2/sub3/0000755000175000017500000000000014737432447015571 5ustar scipscipConfig-General-2.67/t/sub1/sub2/sub3/cfg.sub30000644000175000017500000000012414225255304017106 0ustar scipscipfruit = apple sub3_seen = yup <> <> Config-General-2.67/t/sub1/sub2/cfg.sub20000644000175000017500000000012314225255304016230 0ustar scipscipfruit = pear sub2_seen = yup <> <> Config-General-2.67/t/sub1/cfg.sub1e0000644000175000017500000000001514225255304015521 0ustar scipsciptest3 value3 Config-General-2.67/t/sub1/cfg.sub10000644000175000017500000000004314225255304015355 0ustar scipscipfruit = mango sub1_seen = yup Config-General-2.67/t/Tie/0000755000175000017500000000000014737432447013711 5ustar scipscipConfig-General-2.67/t/Tie/README0000644000175000017500000000025114225255304014552 0ustar scipscipThis module exists here just to satisfy 'make test' because it tests the -tie functionality. It is NOT part of Config::General itself, which doesn't depend on it. Tom Config-General-2.67/t/Tie/IxHash.pm0000644000175000017500000003313514225255304015423 0ustar scipscip# # Tie/IxHash.pm # # Indexed hash implementation for Perl # # See below for documentation. # require 5.003; package Tie::IxHash; use integer; require Tie::Hash; @ISA = qw(Tie::Hash); $VERSION = $VERSION = '1.21'; # # standard tie functions # sub TIEHASH { my($c) = shift; my($s) = []; $s->[0] = {}; # hashkey index $s->[1] = []; # array of keys $s->[2] = []; # array of data $s->[3] = 0; # iter count bless $s, $c; $s->Push(@_) if @_; return $s; } #sub DESTROY {} # costly if there's nothing to do sub FETCH { my($s, $k) = (shift, shift); return exists( $s->[0]{$k} ) ? $s->[2][ $s->[0]{$k} ] : undef; } sub STORE { my($s, $k, $v) = (shift, shift, shift); if (exists $s->[0]{$k}) { my($i) = $s->[0]{$k}; $s->[1][$i] = $k; $s->[2][$i] = $v; $s->[0]{$k} = $i; } else { push(@{$s->[1]}, $k); push(@{$s->[2]}, $v); $s->[0]{$k} = $#{$s->[1]}; } } sub DELETE { my($s, $k) = (shift, shift); if (exists $s->[0]{$k}) { my($i) = $s->[0]{$k}; for ($i+1..$#{$s->[1]}) { # reset higher elt indexes $s->[0]{$s->[1][$_]}--; # timeconsuming, is there is better way? } delete $s->[0]{$k}; splice @{$s->[1]}, $i, 1; return (splice(@{$s->[2]}, $i, 1))[0]; } return undef; } sub EXISTS { exists $_[0]->[0]{ $_[1] }; } sub FIRSTKEY { $_[0][3] = 0; &NEXTKEY; } sub NEXTKEY { return $_[0][1][$_[0][3]++] if ($_[0][3] <= $#{$_[0][1]}); return undef; } # # # class functions that provide additional capabilities # # sub new { TIEHASH(@_) } # # add pairs to end of indexed hash # note that if a supplied key exists, it will not be reordered # sub Push { my($s) = shift; while (@_) { $s->STORE(shift, shift); } return scalar(@{$s->[1]}); } sub Push2 { my($s) = shift; $s->Splice($#{$s->[1]}+1, 0, @_); return scalar(@{$s->[1]}); } # # pop last k-v pair # sub Pop { my($s) = shift; my($k, $v, $i); $k = pop(@{$s->[1]}); $v = pop(@{$s->[2]}); if (defined $k) { delete $s->[0]{$k}; return ($k, $v); } return undef; } sub Pop2 { return $_[0]->Splice(-1); } # # shift # sub Shift { my($s) = shift; my($k, $v, $i); $k = shift(@{$s->[1]}); $v = shift(@{$s->[2]}); if (defined $k) { delete $s->[0]{$k}; for (keys %{$s->[0]}) { $s->[0]{$_}--; } return ($k, $v); } return undef; } sub Shift2 { return $_[0]->Splice(0, 1); } # # unshift # if a supplied key exists, it will not be reordered # sub Unshift { my($s) = shift; my($k, $v, @k, @v, $len, $i); while (@_) { ($k, $v) = (shift, shift); if (exists $s->[0]{$k}) { $i = $s->[0]{$k}; $s->[1][$i] = $k; $s->[2][$i] = $v; $s->[0]{$k} = $i; } else { push(@k, $k); push(@v, $v); $len++; } } if (defined $len) { for (keys %{$s->[0]}) { $s->[0]{$_} += $len; } $i = 0; for (@k) { $s->[0]{$_} = $i++; } unshift(@{$s->[1]}, @k); return unshift(@{$s->[2]}, @v); } return scalar(@{$s->[1]}); } sub Unshift2 { my($s) = shift; $s->Splice(0,0,@_); return scalar(@{$s->[1]}); } # # splice # # any existing hash key order is preserved. the value is replaced for # such keys, and the new keys are spliced in the regular fashion. # # supports -ve offsets but only +ve lengths # # always assumes a 0 start offset # sub Splice { my($s, $start, $len) = (shift, shift, shift); my($k, $v, @k, @v, @r, $i, $siz); my($end); # inclusive # XXX inline this ($start, $end, $len) = $s->_lrange($start, $len); if (defined $start) { if ($len > 0) { my(@k) = splice(@{$s->[1]}, $start, $len); my(@v) = splice(@{$s->[2]}, $start, $len); while (@k) { $k = shift(@k); delete $s->[0]{$k}; push(@r, $k, shift(@v)); } for ($start..$#{$s->[1]}) { $s->[0]{$s->[1][$_]} -= $len; } } while (@_) { ($k, $v) = (shift, shift); if (exists $s->[0]{$k}) { # $s->STORE($k, $v); $i = $s->[0]{$k}; $s->[1][$i] = $k; $s->[2][$i] = $v; $s->[0]{$k} = $i; } else { push(@k, $k); push(@v, $v); $siz++; } } if (defined $siz) { for ($start..$#{$s->[1]}) { $s->[0]{$s->[1][$_]} += $siz; } $i = $start; for (@k) { $s->[0]{$_} = $i++; } splice(@{$s->[1]}, $start, 0, @k); splice(@{$s->[2]}, $start, 0, @v); } } return @r; } # # delete elements specified by key # other elements higher than the one deleted "slide" down # sub Delete { my($s) = shift; for (@_) { # # XXX potential optimization: could do $s->DELETE only if $#_ < 4. # otherwise, should reset all the hash indices in one loop # $s->DELETE($_); } } # # replace hash element at specified index # # if the optional key is not supplied the value at index will simply be # replaced without affecting the order. # # if an element with the supplied key already exists, it will be deleted first. # # returns the key of replaced value if it succeeds. # sub Replace { my($s) = shift; my($i, $v, $k) = (shift, shift, shift); if (defined $i and $i <= $#{$s->[1]} and $i >= 0) { if (defined $k) { delete $s->[0]{ $s->[1][$i] }; $s->DELETE($k) ; #if exists $s->[0]{$k}; $s->[1][$i] = $k; $s->[2][$i] = $v; $s->[0]{$k} = $i; return $k; } else { $s->[2][$i] = $v; return $s->[1][$i]; } } return undef; } # # Given an $start and $len, returns a legal start and end (where start <= end) # for the current hash. # Legal range is defined as 0 to $#s+1 # $len defaults to number of elts upto end of list # # 0 1 2 ... # | X | X | X ... X | X | X | # -2 -1 (no -0 alas) # X's above are the elements # sub _lrange { my($s) = shift; my($offset, $len) = @_; my($start, $end); # both inclusive my($size) = $#{$s->[1]}+1; return undef unless defined $offset; if($offset < 0) { $start = $offset + $size; $start = 0 if $start < 0; } else { ($offset > $size) ? ($start = $size) : ($start = $offset); } if (defined $len) { $len = -$len if $len < 0; $len = $size - $start if $len > $size - $start; } else { $len = $size - $start; } $end = $start + $len - 1; return ($start, $end, $len); } # # Return keys at supplied indices # Returns all keys if no args. # sub Keys { my($s) = shift; return ( @_ == 1 ? $s->[1][$_[0]] : ( @_ ? @{$s->[1]}[@_] : @{$s->[1]} ) ); } # Returns values at supplied indices # Returns all values if no args. # sub Values { my($s) = shift; return ( @_ == 1 ? $s->[2][$_[0]] : ( @_ ? @{$s->[2]}[@_] : @{$s->[2]} ) ); } # # get indices of specified hash keys # sub Indices { my($s) = shift; return ( @_ == 1 ? $s->[0]{$_[0]} : @{$s->[0]}{@_} ); } # # number of k-v pairs in the ixhash # note that this does not equal the highest index # owing to preextended arrays # sub Length { return scalar @{$_[0]->[1]}; } # # Reorder the hash in the supplied key order # # warning: any unsupplied keys will be lost from the hash # any supplied keys that dont exist in the hash will be ignored # sub Reorder { my($s) = shift; my(@k, @v, %x, $i); return unless @_; $i = 0; for (@_) { if (exists $s->[0]{$_}) { push(@k, $_); push(@v, $s->[2][ $s->[0]{$_} ] ); $x{$_} = $i++; } } $s->[1] = \@k; $s->[2] = \@v; $s->[0] = \%x; return $s; } sub SortByKey { my($s) = shift; $s->Reorder(sort $s->Keys); } sub SortByValue { my($s) = shift; $s->Reorder(sort { $s->FETCH($a) cmp $s->FETCH($b) } $s->Keys) } 1; __END__ =head1 NAME Tie::IxHash - ordered associative arrays for Perl =head1 SYNOPSIS # simple usage use Tie::IxHash; tie HASHVARIABLE, Tie::IxHash [, LIST]; # OO interface with more powerful features use Tie::IxHash; TIEOBJECT = Tie::IxHash->new( [LIST] ); TIEOBJECT->Splice( OFFSET [, LENGTH [, LIST]] ); TIEOBJECT->Push( LIST ); TIEOBJECT->Pop; TIEOBJECT->Shift; TIEOBJECT->Unshift( LIST ); TIEOBJECT->Keys( [LIST] ); TIEOBJECT->Values( [LIST] ); TIEOBJECT->Indices( LIST ); TIEOBJECT->Delete( [LIST] ); TIEOBJECT->Replace( OFFSET, VALUE, [KEY] ); TIEOBJECT->Reorder( LIST ); TIEOBJECT->SortByKey; TIEOBJECT->SortByValue; TIEOBJECT->Length; =head1 DESCRIPTION This Perl module implements Perl hashes that preserve the order in which the hash elements were added. The order is not affected when values corresponding to existing keys in the IxHash are changed. The elements can also be set to any arbitrary supplied order. The familiar perl array operations can also be performed on the IxHash. =head2 Standard C Interface The standard C mechanism is available. This interface is recommended for simple uses, since the usage is exactly the same as regular Perl hashes after the C is declared. =head2 Object Interface This module also provides an extended object-oriented interface that can be used for more powerful operations with the IxHash. The following methods are available: =over 8 =item FETCH, STORE, DELETE, EXISTS These standard C methods mandated by Perl can be used directly. See the C entry in perlfunc(1) for details. =item Push, Pop, Shift, Unshift, Splice These additional methods resembling Perl functions are available for operating on key-value pairs in the IxHash. The behavior is the same as the corresponding perl functions, except when a supplied hash key already exists in the hash. In that case, the existing value is updated but its order is not affected. To unconditionally alter the order of a supplied key-value pair, first C the IxHash element. =item Keys Returns an array of IxHash element keys corresponding to the list of supplied indices. Returns an array of all the keys if called without arguments. Note the return value is mostly only useful when used in a list context (since perl will convert it to the number of elements in the array when used in a scalar context, and that may not be very useful). If a single argument is given, returns the single key corresponding to the index. This is usable in either scalar or list context. =item Values Returns an array of IxHash element values corresponding to the list of supplied indices. Returns an array of all the values if called without arguments. Note the return value is mostly only useful when used in a list context (since perl will convert it to the number of elements in the array when used in a scalar context, and that may not be very useful). If a single argument is given, returns the single value corresponding to the index. This is usable in either scalar or list context. =item Indices Returns an array of indices corresponding to the supplied list of keys. Note the return value is mostly only useful when used in a list context (since perl will convert it to the number of elements in the array when used in a scalar context, and that may not be very useful). If a single argument is given, returns the single index corresponding to the key. This is usable in either scalar or list context. =item Delete Removes elements with the supplied keys from the IxHash. =item Replace Substitutes the IxHash element at the specified index with the supplied value-key pair. If a key is not supplied, simply substitutes the value at index with the supplied value. If an element with the supplied key already exists, it will be removed from the IxHash first. =item Reorder This method can be used to manipulate the internal order of the IxHash elements by supplying a list of keys in the desired order. Note however, that any IxHash elements whose keys are not in the list will be removed from the IxHash. =item Length Returns the number of IxHash elements. =item SortByKey Reorders the IxHash elements by textual comparison of the keys. =item SortByValue Reorders the IxHash elements by textual comparison of the values. =back =head1 EXAMPLE use Tie::IxHash; # simple interface $t = tie(%myhash, Tie::IxHash, 'a' => 1, 'b' => 2); %myhash = (first => 1, second => 2, third => 3); $myhash{fourth} = 4; @keys = keys %myhash; @values = values %myhash; print("y") if exists $myhash{third}; # OO interface $t = Tie::IxHash->new(first => 1, second => 2, third => 3); $t->Push(fourth => 4); # same as $myhash{'fourth'} = 4; ($k, $v) = $t->Pop; # $k is 'fourth', $v is 4 $t->Unshift(neg => -1, zeroth => 0); ($k, $v) = $t->Shift; # $k is 'neg', $v is -1 @oneandtwo = $t->Splice(1, 2, foo => 100, bar => 101); @keys = $t->Keys; @values = $t->Values; @indices = $t->Indices('foo', 'zeroth'); @itemkeys = $t->Keys(@indices); @itemvals = $t->Values(@indices); $t->Replace(2, 0.3, 'other'); $t->Delete('second', 'zeroth'); $len = $t->Length; # number of key-value pairs $t->Reorder(reverse @keys); $t->SortByKey; $t->SortByValue; =head1 BUGS You cannot specify a negative length to C. Negative indexes are OK, though. Indexing always begins at 0 (despite the current C<$[> setting) for all the functions. =head1 TODO Addition of elements with keys that already exist to the end of the IxHash must be controlled by a switch. Provide C interface when it stabilizes in Perl. Rewrite using XSUBs for efficiency. =head1 AUTHOR Gurusamy Sarathy gsar@umich.edu Copyright (c) 1995 Gurusamy Sarathy. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =head1 VERSION Version 1.21 20 Nov 1997 =head1 SEE ALSO perl(1) =cut Config-General-2.67/t/cfg.60000644000175000017500000000030614225255304014000 0ustar scipscip# Comment test user = tom # a comment right after a line /* * C-style comment (multiline) */ passwd = sakkra /* oneline C-style comment */ host = blah.blubber # bar = baz Config-General-2.67/t/apache-include.conf0000644000175000017500000000013414225255304016662 0ustar scipscip include t/included.conf include "t/included.conf" Config-General-2.67/t/cfg.40000644000175000017500000000011014225255304013767 0ustar scipscip# Here-document test header = < EOFConfig-General-2.67/t/test.rc0000644000175000017500000000512114225255304014457 0ustar scipscip/* * Beispiel .redirect Datei. * * Wenn diese Datei nicht im $HOME des * jeweiligen Benutzers vorhanden ist, * oder wenn die vorhandene Datei aus * irgendeinem Grund ungültig ist(Syntax) * dann wird per Default alles an @domain * zum Benutzer weitergeleitet. * * Syntax: * Domain Blöcke beginnen mit und enden * mit (equivalent zu apache config). * Als Kommentare sind # sowie C-Style erlaubt(so * wie dieser hier). * Näheres zum Block siehe unten. * * Im Block kann man Variablen definieren, auf * die man dann innerhalb der Blöcke zu- * greifen kann (siehe sample!) * * * Im Block kann man Mailinglisten einrichten * allerdings rudimentär, d.h. es sind eigentlich nur * Verteiler, aber immerhin. Die entsprechende Adresse * muss im dazugehörigen Block definiert sein. * * Angegebene Emailadressen werden (zumindest im Moment) * nicht überprüft, also 1:1 übernommen, also Sorgfalt * walten lassen. * * Fragen/Kommentare/Kritik/Flames/Mecker an: * Thomas Linden * */ /* ********************************************************************* * Hier kann man Variablen definieren und später mittels * $variablenname verwenden. ********************************************************************* */ USER scip # via $USER verwendbar host manna host gorky /* ********************************************************************* * Für jede Domain muss ein Block vorhanden sein ********************************************************************* */ foo max@nasa.gov # foo@bar.de nach max@nasa.gov coderz %coderz # coderz@bar.de ist ein Verteiler, der # in definiert ist. @ $USER # alles andere an "scip" schicken. # Wenn nicht angegeben, kommen unbekannte # Adressen an den Absender zurück, z.B. # gibtsnet@bar.de würde "Unknown User" ver- # ursachen! /* ********************************************************************* * Definition einer "Mailingliste", gültige Empfänger müssen mit * dem Parameter "rcpt" definiert werden. Blöcke sind Domain- * unabhängig, d.h. sie müssen einen eindeutigen Namen haben. ********************************************************************* */ rcpt solaar.designer@packetstorm.org rcpt $USER rcpt machine@star.wars.de Config-General-2.67/t/complex.cfg0000644000175000017500000000100114225255304015273 0ustar scipscip# complexity test var1 = zero # comment var2 = zeppelin /* another comment */ /* to be ignored */ line = a\ long line var3 = blah set = $var3 ignore = \$set quote = this should be 'kept: $set' and not be '$set!' host = gw.intx.foo cmd = mart@${host}:22 onflag = yes offflag = No <> a [[weird]] heredoc = < <> Config-General-2.67/t/cfg.400000644000175000017500000000016714225255304014063 0ustar scipscip# should generate an error about invalid structure # array of scalars => hashref val = 1 val = 2 x = no Config-General-2.67/t/run.t0000644000175000017500000005333114737252357014166 0ustar scipscip# -*-perl-*- # testscript for Config::General Classes by Thomas Linden # # needs to be invoked using the command "make test" from # the Config::General source directory. # # Under normal circumstances every test should succeed. use Data::Dumper; use Test::More tests => 79; #use Test::More qw(no_plan); # ahem, we deliver the test code with a local copy of # the Tie::IxHash module so we can do tests on sorted # hashes without dependency to Tie::IxHash. use lib qw(t); use Tie::IxHash; my @WARNINGS_FOUND; BEGIN { $SIG{__WARN__} = sub { diag( "WARN: ", join( '', @_ ) ); push @WARNINGS_FOUND, @_ }; } ### 1 BEGIN { use_ok "Config::General"}; require_ok( 'Config::General' ); ### 2 - 7 foreach my $num (2..7) { my $cfg = "t/cfg.$num"; open T, "<$cfg"; my @file = ; close T; my $fst = $file[0]; chomp $fst; $fst =~ s/\#\s*//g; eval { my $conf = new Config::General($cfg); my %hash = $conf->getall; }; ok(!$@, "$fst"); } ### 8 my $conf = new Config::General("t/cfg.8"); my %hash = $conf->getall; $conf->save_file("t/cfg.out"); my $copy = new Config::General("t/cfg.out"); my %copyhash = $copy->getall; is_deeply(\%hash, \%copyhash, "Writing Config Hash to disk and compare with original"); # 8a like($copyhash{nocomment}, qr/this should appear/, "C-comments not processed in here-doc"); ### 9 $conf = new Config::General( -ExtendedAccess => 1, -ConfigFile => "t/test.rc"); ok($conf, "Creating a new object from config file"); ### 10 my $conf2 = new Config::General( -ExtendedAccess => 1, -ConfigFile => "t/test.rc", -AllowMultiOptions => "yes" ); ok($conf2, "Creating a new object using the hash parameter way"); ### 11 my $domain = $conf->obj("domain"); ok($domain, "Creating a new object from a block"); ### 12 my $addr = $domain->obj("bar.de"); ok($addr, "Creating a new object from a sub block"); ### 13 my @keys = $conf->keys("domain"); ok($#keys > -1, "Getting values from the object"); ### 14 # test various OO methods my $a; if ($conf->is_hash("domain")) { my $domains = $conf->obj("domain"); foreach my $domain ($conf->keys("domain")) { my $domain_obj = $domains->obj($domain); foreach my $address ($domains->keys($domain)) { $a = $domain_obj->value($address); } } } ok($a, "Using keys() and values()"); ### 15 # test AUTOLOAD methods eval { my $conf3 = new Config::General( -ExtendedAccess => 1, -ConfigHash => { name => "Moser", prename => "Hannes"} ); my $n = $conf3->name; my $p = $conf3->prename; $conf3->name("Meier"); $conf3->prename("Max"); $conf3->save_file("t/test.cfg"); }; ok (!$@, "Using AUTOLOAD methods"); ### 16 # testing variable interpolation my $conf16 = new Config::General(-ConfigFile => "t/cfg.16", -InterPolateVars => 1, -StrictVars => 0); my %h16 = $conf16->getall(); if($h16{etc}->{log} eq "/usr/log/logfile" and $h16{etc}->{users}->{home} eq "/usr/home/max" and exists $h16{dir}->{teri}->{bl}) { pass("Testing variable interpolation"); } else { fail("Testing variable interpolation"); } ### 16.a # testing variable interpolation with %ENV use my $env = "/home/theunexistent"; $ENV{HOME} = $env; my $conf16a = new Config::General(-ConfigFile => "t/cfg.16a", -InterPolateVars => 1, -InterPolateEnv => 1, -StrictVars => 0); my %h16a = $conf16a->getall(); if($h16a{etc}->{log} eq "$env/log/logfile") { pass("Testing environment variable interpolation"); } else { fail("Testing environment variable interpolation"); } ### 17 # testing value pre-setting using a hash my $conf17 = new Config::General( -file => "t/cfg.17", -DefaultConfig => { home => "/exports/home", logs => "/var/backlog", foo => { bar => "quux" } }, -InterPolateVars => 1, -MergeDuplicateOptions => 1, -MergeDuplicateBlocks => 1 ); my %h17 = $conf17->getall(); ok ($h17{home} eq "/home/users" && $h17{foo}{quux} eq "quux", "Testing value pre-setting using a hash"); ### 18 # testing value pre-setting using a string my $conf18 = new Config::General( -file => "t/cfg.17", # reuse the file -DefaultConfig => "home = /exports/home\nlogs = /var/backlog", -MergeDuplicateOptions => 1, -MergeDuplicateBlocks => 1 ); my %h18 = $conf18->getall(); ok ($h18{home} eq "/home/users", "Testing value pre-setting using a string"); ### 19 # testing various otion/value assignment notations my $conf19 = new Config::General(-file => "t/cfg.19"); my %h19 = $conf19->getall(); my $works = 1; foreach my $key (keys %h19) { if ($key =~ /\s/) { $works = 0; } } ok ($works, "Testing various otion/value assignment notations"); ### 20 # testing files() method my $conf20 = Config::General->new( -file => "t/cfg.20.a", -MergeDuplicateOptions => 1 ); my %h20 = $conf20->getall(); my %files = map { $_ => 1 } $conf20->files(); my %expected_files = map { $_ => 1 } ( 't/cfg.20.a', 't/cfg.20.b', 't/cfg.20.c', ); is_deeply (\%files, \%expected_files, "testing files() method"); ### 22 # testing improved IncludeRelative option # First try without -IncludeRelative # this should fail eval { my $conf21 = Config::General->new( -file => "t/sub1/sub2/sub3/cfg.sub3", -MergeDuplicateOptions => 1, ); }; ok ($@, "prevented from loading relative cfgs without -IncludeRelative"); ### 23 # Now try with -IncludeRelative # this should fail my $conf22 = Config::General->new( -file => "t/sub1/sub2/sub3/cfg.sub3", -MergeDuplicateOptions => 1, -IncludeRelative => 1, ); my %h22 = $conf22->getall; my %expected_h22 = ( 'sub3_seen' => 'yup', 'sub2_seen' => 'yup', 'sub2b_seen' => 'yup', 'sub1_seen' => 'yup', 'sub1b_seen' => 'yup', 'fruit' => 'mango', ); is_deeply(\%h22, \%expected_h22, "loaded relative to included files"); ### 24 # Testing IncludeDirectories option my $conf23 = Config::General->new( -String => "<>", -IncludeDirectories => 1 ); my %h23 = $conf23->getall; my %expected_h23 = ( fruit => 'mango', sub1_seen => 'yup', sub1b_seen => 'yup', test => 'value', test2 => 'value2', test3 => 'value3' ); is_deeply(\%h23, \%expected_h23, "including a directory with -IncludeDirectories"); ### 24 # Testing IncludeGlob option my $conf24 = Config::General->new( -String => "<>", -IncludeGlob => 1 ); my %h24 = $conf24->getall; my %expected_h24 = ( test => 'value', test2 => 'value2', test3 => 'value3' ); is_deeply(\%h24, \%expected_h24, "including multiple files via glob pattern with -IncludeGlob"); ### 25 # Testing block and block name quoting my $conf25 = Config::General->new( -String => < opt1 val1 <"block2 /"> opt2 val2 <"block 3" "/"> opt3 val3 opt4 val4 TEST -SlashIsDirectory => 1 ); my %h25 = $conf25->getall; my %expected_h25 = ( block => { '/' => { opt1 => 'val1' } }, 'block2 /' => { opt2 => 'val2' }, 'block 3' => { '/' => { opt3 => 'val3' } }, block4 => { '/' => { opt4 => 'val4' } } ); is_deeply(\%h25, \%expected_h25, "block and block name quoting"); ### 26 # Testing 0-value handling my $conf26 = Config::General->new( -String => < 0 TEST ); my %h26 = $conf26->getall; my %expected_h26 = ( foo => { 0 => { 0 => undef } }, ); is_deeply(\%h26, \%expected_h26, "testing 0-values in block names"); # # look if invalid input gets rejected right # ### 27 # testing invalid parameter calls, expected to fail my @pt = ( { p => {-ConfigHash => "StringNotHash"}, t => "-ConfigHash HASH required" }, { p => {-String => {}}, t => "-String STRING required" }, { p => {-ConfigFile => {}}, t => "-ConfigFile STRING required" }, { p => {-ConfigFile => "NoFile"}, t => "-ConfigFile STRING File must exist and be readable" } ); foreach my $C (@pt) { eval { my $cfg = new Config::General(%{$C->{p}}); }; ok ($@, "check parameter failure handling $C->{t}"); } ### 32 # check Flagbits my $cfg28 = new Config::General( -String => "Mode = CLEAR | UNSECURE", -FlagBits => { Mode => { CLEAR => 1, STRONG => 1, UNSECURE => "32bit" } } ); my %cfg28 = $cfg28->getall(); is_deeply(\%cfg28, { 'Mode' => { 'STRONG' => undef, 'UNSECURE' => '32bit', 'CLEAR' => 1 }}, "Checking -Flagbits resolving"); ### 33 # checking functional interface eval { my %conf = Config::General::ParseConfig(-ConfigFile => "t/test.rc"); Config::General::SaveConfig("t/test.rc.out", \%conf); my %next = Config::General::ParseConfig(-ConfigFile => "t/test.rc.out"); my @a = sort keys %conf; my @b = sort keys %next; if (@a != @b) { die "Re-parsed result differs from original"; } }; ok(! $@, "Testing functional interface $@"); ### 34 # testing -AutoTrue my $cfg34 = new Config::General(-AutoTrue => 1, -ConfigFile => "t/cfg.34"); my %cfg34 = $cfg34->getall(); my %expect34 = ( 'a' => { 'var6' => 0, 'var3' => 1, 'var1' => 1, 'var4' => 0, 'var2' => 1, 'var5' => 0 }, 'b' => { 'var6' => 0, 'var3' => 1, 'var1' => 1, 'var4' => 0, 'var2' => 1, 'var5' => 0 } ); is_deeply(\%cfg34, \%expect34, "Using -AutoTrue"); ### 35 # testing -SplitPolicy my %conf35 = Config::General::ParseConfig( -String => qq(var1 :: alpha var2 :: beta var3 = gamma # use wrong delimiter by purpose), -SplitPolicy => 'custom', -SplitDelimiter => '\s*::\s*' ); my %expect35 = ( 'var3 = gamma' => undef, 'var1' => 'alpha', 'var2' => 'beta' ); is_deeply(\%conf35, \%expect35, "Using -SplitPolicy and custom -SplitDelimiter"); ### Include both my $conf36 = Config::General->new( -ConfigFile => "t/dual-include.conf", -IncludeAgain => 1 ); my %C36 = $conf36->getall; is_deeply( \%C36, { bit => { one => { honk=>'bonk' }, two => { honk=>'bonk' } } }, "Included twice" ); ### Include once { my @expected_warning; local $SIG{__WARN__} = sub { push @expected_warning, @_}; my $conf37 = Config::General->new( "t/dual-include.conf" ); my %C37 = $conf37->getall; is_deeply( \%C37, { bit => { one => { honk=>'bonk' }, two => {} } }, "Included once-only" ); is( @expected_warning, 1, "1 Expected warning" ); like( $expected_warning[0], qr/File .* already loaded. Use -IncludeAgain to load it again./ms, "Warns about a file already being loaded" ); } ### apache-style Include my $conf38 = Config::General->new( -ConfigFile => "t/apache-include.conf", -IncludeAgain => 1, -UseApacheInclude => 1 ); my %C38 = $conf38->getall; is_deeply( \%C38, { bit => { one => { honk=>'bonk' }, two => { honk=>'bonk' } } }, "Apache-style include" ); # verify fix for rt#107108, test support for IncludeOptional my $conf38n = Config::General->new( -ConfigFile => "t/apache-include-opt.conf", -IncludeAgain => 1, -IncludeGlob => 1, -UseApacheInclude => 1 ); my %C38n = $conf38n->getall; is_deeply( \%C38n, { bit => { one => { nink=>'ack' }, two => { honk=>'bonk' } } }, "Apache-style IncludeOptional" ); #### 39 verifies bug rt#27225 # testing variable scope. # a variable shall resolve to the value defined in the current # scope, not a previous outer scope. my $conf39 = new Config::General(-ConfigFile => "t/cfg.39", -InterPolateVars => 1, -StrictVars => 0); my %conf39 = $conf39->getall(); isnt($conf39{outer}->{b1}->{inner}->{ivar}, $conf39{outer}->{b2}->{inner}->{ivar}, "Variable scope test"); ### 40 - 42 verify if structural error checks are working foreach my $pos (40 .. 43) { eval { my $conf = new Config::General(-ConfigFile => "t/cfg.$pos"); }; ok($@ =~ /^Config::General/, "$pos: Structural error checks"); } my $conf44; eval { $conf44 = new Config::General(-String => [ 'foo bar' ]); }; ok(! $@, "-String arrayref"); is_deeply({ $conf44->getall }, { foo => 'bar' }, "-String arrayref contents"); # verifies bug rt#35122 my $conf45 = new Config::General(-ConfigFile => "t/cfg.45", -InterPolateVars => 1, -StrictVars => 0); my %conf45 = $conf45->getall(); my $expect45 = { 'block1' => { 'param5' => 'value3', 'param4' => 'value1', 'param2' => 'value3' }, 'block2' => { 'param7' => 'value2', 'param6' => 'value1' }, 'param2' => 'value2', 'param1' => 'value1' }; is_deeply($expect45, \%conf45, "Variable precedence"); # verifies bug rt#35766 my $conf46 = new Config::General(-ConfigFile => "t/cfg.46", -InterPolateVars => 1, -StrictVars => 0); my %conf46 = $conf46->getall(); my $expect46 = { 'blah' => 'blubber', 'test' => 'bar \'variable $blah should be kept\' and \'$foo too\'', 'foo' => 'bar' }; is_deeply($expect46, \%conf46, "Variables inside single quotes"); # complexity test # check the combination of various features my $conf47 = new Config::General( -ConfigFile => "t/complex.cfg", -InterPolateVars => 1, -DefaultConfig => { this => "that", default => "imported" }, -MergeDuplicateBlocks => 1, -MergeDuplicateOptions => 1, -StrictVars => 1, -SplitPolicy => 'custom', -SplitDelimiter => '\s*=\s*', -IncludeGlob => 1, -IncludeAgain => 1, -IncludeRelative => 1, -AutoTrue => 1, -FlagBits => { someflags => { LOCK => 1, RW => 2, TAINT => 3 } }, -StoreDelimiter => ' = ', -SlashIsDirectory => 1, -SaveSorted => 1 ); my %conf47 = $conf47->getall(); my $expect47 = { 'var3' => 'blah', 'z1' => { 'blak' => '11111', 'nando' => '9999' }, 'a' => { 'b' => { 'm' => { '9323' => { 'g' => '000', 'long' => 'another long line' } }, 'x' => '9323', 'z' => 'rewe' } }, 'onflag' => 1, 'var2' => 'zeppelin', 'ignore' => '$set', # escaped $ should get to plain $, not \\$! 'quote' => 'this should be \'kept: $set\' and not be \'$set!\'', 'x5' => { 'klack' => '11111' }, 'set' => 'blah', 'line' => 'along line', 'this' => 'that', 'imported' => 'got that from imported config', 'someflags' => { 'RW' => 2, 'LOCK' => 1, 'TAINT' => 3 }, 'var1' => 'zero', 'offflag' => 0, 'cmd' => 'mart@gw.intx.foo:22', 'default' => 'imported', 'host' => 'gw.intx.foo', 'nando' => '11111', 'auch ätzendes' => 'muss gehen', 'Directory' => { '/' => { 'mode' => '755' } }, 'hansa' => { 'z1' => { 'blak' => '11111', 'nando' => '9999' }, 'Directory' => { '/' => { 'mode' => '755' } }, 'block' => { '0' => { 'value' => 0 } }, 'x5' => { 'klack' => '11111' }, 'Files' => { '~/*.pl' => { 'Options' => '+Indexes' } }, 'nando' => '11111' }, 'block' => { '0' => { 'value' => 0 } }, 'Files' => { '~/*.pl' => { 'Options' => '+Indexes' } }, 'a [[weird]] heredoc' => 'has to work too!' }; #scip is_deeply($expect47, \%conf47, "complexity test"); # check if sorted save works $conf47->save_file("t/complex.out", \%conf47); open T, "; close T; my $sorted = qq( imported = got that from imported config line = along line nando = 11111 offflag = 0 onflag = 1); if ($got47 =~ /\Q$sorted\E/) { pass("Testing sorted save"); } else { fail("Testing sorted save"); } tie my %hash48, "Tie::IxHash"; my $ostr48 = "zeppelin 1 beach 2 anathem 3 mercury 4\n"; my $cfg48 = new Config::General( -String => $ostr48, -Tie => "Tie::IxHash" ); %hash48 = $cfg48->getall(); my $str48 = $cfg48->save_string(\%hash48); is( $str48, $ostr48, "tied hash test"); # check for undef and -w { my $ostr49 = "foo\n"; local $^W = 1; my $cfg49 = new Config::General( -String => $ostr49 ); my %hash49 = $cfg49->getall(); ok( exists $hash49{foo}, "value for undefined key found"); is( $hash49{foo}, undef, "value returned as expected - undef"); # repeat with interpolation turned on $cfg49 = new Config::General( -String => $ostr49, -InterPolateVars => 1 ); %hash49 = $cfg49->getall(); ok( exists $hash49{foo}, "value for undefined key found"); is( $hash49{foo}, undef, "value returned as expected - undef"); $^W = 0; } # verifies bug fix rt#54580 # Test handling of values containing *many* single-quoted strings # when -InterPolateVars option is set my $dupcount50 = 2000; my $ostr50; foreach my $counter ( reverse 1 .. $dupcount50 ) { $ostr50 .= " 'luck${counter}'"; } $ostr50 =~ s{\A }{}; my $cfgsrc50 = 'test_single_many ' . $ostr50; $cfg50 = new Config::General( -String => $cfgsrc50, -InterPolateVars => 1 ); %hash50 = $cfg50->getall(); is($hash50{test_single_many}, $ostr50, "value with single-quote strings is as expected" ); # check for escaped chars my $cfg51 = new Config::General( -ConfigFile => "t/cfg.51" ); my %hash51 = $cfg51->getall(); is($hash51{dollar}, '$foo', "keep escaped dollar character"); is($hash51{backslash}, 'contains \ backslash', "keep escaped backslash character"); is($hash51{prize}, '18 $', "keep un-escaped dollar character"); is($hash51{hostparam}, q("'wsh.dir'"), "keep escaped quote character"); is($hash51{bgcolor}, '#fff', "keep escaped number sign"); # now save it to a file and re-read it in and see if everything remains escaped $cfg51->save_file("t/cfg.51.out"); $cfg51 = new Config::General( -ConfigFile => "t/cfg.51.out", -InterPolateVars => 1 ); my %hash51new = $cfg51->getall(); is_deeply(\%hash51, \%hash51new, "compare saved config containing escaped chars"); # check if forced single value arrays remain my $cfg52 = new Config::General( -String => "habeas = [ corpus ]", -ForceArray => 1); my %hash52 = $cfg52->getall(); my @array52 = qw(corpus); is_deeply($hash52{habeas}, \@array52, "check -ForceArray single value arrays"); $cfg52->save_file("t/cfg.52.out"); $cfg52 = new Config::General( -ConfigFile => "t/cfg.52.out", -ForceArray => 1); my %hash52new = $cfg52->getall(); is_deeply(\%hash52new, \%hash52, "check -ForceArray single value arrays during save()"); my $cfg53 = new Config::General(-AllowSingleQuoteInterpolation => 1, -String => "got = 1\nhave = '\$got'", -InterPolateVars => 1 ); my %hash53 = $cfg53->getall(); is($hash53{have}, "'1'", "check -AllowSingleQuoteInterpolation"); # Make sure no warnings were seen during the test. ok( !@WARNINGS_FOUND, "No unexpected warnings seen" ); # check if disabling escape chars does work my $cfg54 = new Config::General(-NoEscape => 1, -String => qq(val = \\\$notavar:\\blah\n)); my %hash54 = $cfg54->getall(); is($hash54{val}, qq(\\\$notavar:\\blah), "check -NoEscape"); # check for line continuation followed by empty line (rt.cpan.org#39814) my $cfg55 = new Config::General( -ConfigFile => "t/cfg.55" ); my %hash55 = $cfg55->getall(); is($hash55{b}, "nochop", "check continuation followed by empty line"); my $cfg56 = Config::General->new(); eval { $cfg56->save_file("t/56.out", { "new\nline" => 9, "brack 8 }); }; ok($@, "catch special chars in keys"); # UTF8[BOM] tests my $cfg57 = "t/utf8_bom/foo.cfg"; my $expected57 = {foo => {"\x{e9}" => "\x{e8}", bar => {"\x{f4}" => "\x{ee}"}}}; for my $bool (0, 1) { my $conf = Config::General->new(-ConfigFile => $cfg57, -IncludeRelative => 1, -UTF8 => $bool); my %hash = $conf->getall; is_deeply \%hash, $expected57, "-UTF8 => $bool"; } # IFDEF tests my $cfg58 = "t/cfg.58"; my $expected58 = { level => "debug" }; my %defs = ( scalar => 'TEST', array => ['TEST'], hash => {'TEST' => 1} ); foreach my $def (keys %defs) { my $conf = Config::General->new(-ConfigFile => $cfg58, -UseApacheIfDefine => 1, -Define => $defs{$def}); my %hash = $conf->getall(); is_deeply \%hash, $expected58, "UseApacheIfDefine, -Define => $def"; } # force quoting my $cfg59 = "t/cfg.59"; my $expected59 = qq(foo "bar baz" ); # newline is important here, as we check write output my $conf59 = Config::General->new( -ConfigFile => $cfg59, -AlwaysQuoteOutput => 1); my $got59 = $conf59->save_string(); is_deeply \$expected59, \$got59, "quotes"; Config-General-2.67/t/cfg.50000644000175000017500000000027614225255304014005 0ustar scipscip# Multiline option test command = ssh -f -g orpheus.0x49.org \ -l azrael -L:34777samir.okir.da.ru:22 \ -L:31773:shane.sol1.rocket.de:22 \ 'exec sleep 99999990' Config-General-2.67/t/cfg.430000644000175000017500000000011714225255304014061 0ustar scipscip# should generate an error about invalid structure val = 1 x = 2 Config-General-2.67/t/cfg.510000644000175000017500000000015214225255304014057 0ustar scipscipdollar = \$foo backslash = contains \\ backslash prize = 18 $ hostparam = "\"'wsh.dir'\"" bgcolor = \#fff Config-General-2.67/t/cfg.580000644000175000017500000000005414225255304014067 0ustar scipscip level debug Config-General-2.67/t/cfg.170000644000175000017500000000005414225255304014062 0ustar scipsciphome = /home/users quux = $bar Config-General-2.67/t/cfg.30000644000175000017500000000011414225255304013772 0ustar scipscip# Array content test domain b0fh.org domain l0pht.com domain infonexus.comConfig-General-2.67/t/notincluded.conf.not0000644000175000017500000000001414225255304017124 0ustar scipsciphonk=NONONO Config-General-2.67/t/cfg.16a0000644000175000017500000000007014225255304014220 0ustar scipscip log = ${HOME}/log/logfile # use braces Config-General-2.67/t/utf8_bom/0000755000175000017500000000000014737432447014713 5ustar scipscipConfig-General-2.67/t/utf8_bom/bar.cfg0000644000175000017500000000003214225255304016116 0ustar scipscip ô = î Config-General-2.67/t/utf8_bom/foo.cfg0000644000175000017500000000006014225255304016136 0ustar scipscip é = è <> Config-General-2.67/t/cfg.420000644000175000017500000000021314225255304014055 0ustar scipscip# should generate an error about invalid structure # array of hashrefs => scalar x = no val = 3 x = no Config-General-2.67/t/complex/0000755000175000017500000000000014737432447014637 5ustar scipscipConfig-General-2.67/t/complex/n2.cfg0000644000175000017500000000031214225255304015616 0ustar scipscip mode = 755 Options = +Indexes nando = 11111 blak = $nando nando = 9999 klack = $nando value = 0 Config-General-2.67/t/complex/n1.cfg0000644000175000017500000000027714225255304015627 0ustar scipscip x = 9323 z = 000 g = $z long = another long \ line /* please ignore this */ z = rewe Config-General-2.67/t/cfg.450000644000175000017500000000036214225255304014065 0ustar scipscipparam1 = value1 param2 = value2 param2 = value3 param4 = $param1 # expect: "value1" param5 = $param2 # expect: "value3" param6 = $param1 # expect: "value1" param7 = $param2 # expect: "value2" Config-General-2.67/t/cfg.80000644000175000017500000000146614225255304014012 0ustar scipscip name stein age 25 name bird age 31 domain nix.to domain b0fh.org domain foo.bar message < host = blah.blubber user1 hans user2 max quoted = "this one contains whitespace at the end " quotedwithquotes = " holy crap, it contains \"masked quotes\" and 'single quotes' " Config-General-2.67/t/dual-include.conf0000644000175000017500000000014214225255304016365 0ustar scipscip <> <> Config-General-2.67/t/cfg.20.a0000644000175000017500000000005414225255304014273 0ustar scipscipseen_cfg.20.a = true <> Config-General-2.67/t/cfg.190000644000175000017500000000033014225255304014061 0ustar scipscip# # these options must all in # msg[\d] keys. # msg1 = "Das ist ein Test" msg2 = "Das = ein Test" msg3 "Das ist ein Test" msg4 "Das = ein Test" msg6 = < test = foo ivar = $test test = bar ivar = $test Config-General-2.67/t/cfg.20.c0000644000175000017500000000005614225255304014277 0ustar scipscipseen_cfg.20.c = true last = cfg.20.c Config-General-2.67/t/cfg.340000644000175000017500000000026214225255304014062 0ustar scipscip var1 = yes var2 = on var3 = true var4 = no var5 = off var6 = false var1 = Yes var2 = On var3 = TRUE var4 = nO var5 = oFf var6 = False Config-General-2.67/t/cfg.590000644000175000017500000000002014737171502014066 0ustar scipscipfoo = "bar baz" Config-General-2.67/t/cfg.70000644000175000017500000000015614225255304014004 0ustar scipscip# Case insensitive block test name stein age 25 Config-General-2.67/t/cfg.550000644000175000017500000000005214225255304014062 0ustar scipscipa = 1 b = nochop\ c = should stay alone Config-General-2.67/t/included.conf0000644000175000017500000000001214225255304015602 0ustar scipsciphonk=bonk Config-General-2.67/t/cfg.20.b0000644000175000017500000000005414225255304014274 0ustar scipscipseen_cfg.20.b = true <> Config-General-2.67/t/cfg.410000644000175000017500000000014614225255304014061 0ustar scipscip# should generate an error about invalid structure # scalar => hashref val = 1 x = no Config-General-2.67/t/cfg.20000644000175000017500000000031414225255304013773 0ustar scipscip# Nested block test name stein age 25 color \#000000 name bird age 31 color \#ffffff Config-General-2.67/t/apache-include-opt.conf0000644000175000017500000000017214225255304017464 0ustar scipscip IncludeOptional t/included.conf nink ack IncludeOptional t/notincluded.conf Config-General-2.67/General/0000755000175000017500000000000014737432447014302 5ustar scipscipConfig-General-2.67/General/Interpolated.pm0000644000175000017500000002143014737167104017265 0ustar scipscip# # Config::General::Interpolated - special Class based on Config::General # # Copyright (c) 2001 by Wei-Hon Chen . # Copyright (c) 2000-2022 by Thomas Linden . # All Rights Reserved. Std. disclaimer applies. # Licensed under the terms of the Artistic License 2.0. # package Config::General::Interpolated; $Config::General::Interpolated::VERSION = "2.16"; use strict; use Carp; use Config::General; # Import stuff from Config::General our @ISA = qw(Config::General); sub new { # # overwrite new() with our own version # and call the parent class new() # croak "Deprecated method Config::General::Interpolated::new() called.\n" ."Use Config::General::new() instead and set the -InterPolateVars flag.\n"; } sub _set_regex { # # set the regex for finding vars # # the following regex is provided by Autrijus Tang # , and I made some modifications. # thanx, autrijus. :) my $regex = qr{ (^|\G|[^\\]) # $1: can be the beginning of the line # or the beginning of next match # but can't begin with a '\' \$ # dollar sign (\{)? # $2: optional opening curly ([a-zA-Z0-9][a-zA-Z0-9_\-\.:\+]*) # $3: capturing variable name (fix of #33447+118746) (?(2) # $4: if there's the opening curly... \} # ... match closing curly ) }x; return $regex; } sub _interpolate { # # interpolate a scalar value and keep the result # on the varstack. # # called directly by Config::General::_parse_value() # my ($this, $config, $key, $value) = @_; my $quote_counter = 100; # some dirty trick to circumvent single quoted vars to be interpolated # we remove all quotes and replace them with unique random literals, # which will be replaced after interpolation with the original quotes # fixes bug rt#35766 my %quotes; if(! $this->{AllowSingleQuoteInterpolation} ) { $value =~ s/(\'[^\']+?\')/ my $key = "QUOTE" . ($quote_counter++) . "QUOTE"; $quotes{ $key } = $1; $key; /gex; } $value =~ s{$this->{regex}}{ my $con = $1; my $var = $3; my $var_lc = $this->{LowerCaseNames} ? lc($var) : $var; if (exists $config->{__stack}->{$var_lc}) { $con . $config->{__stack}->{$var_lc}; } elsif ($this->{InterPolateEnv}) { # may lead to vulnerabilities, by default flag turned off if (defined($ENV{$var})) { $con . $ENV{$var}; } else { $con; } } elsif ($this->{StrictVars}) { croak "Use of uninitialized variable (\$$var) while loading config entry: $key = $value\n"; } else { # be cool $con; } }egx; # re-insert unaltered quotes # fixes bug rt#35766 foreach my $quote (keys %quotes) { $value =~ s/$quote/$quotes{$quote}/; } return $value; }; sub _interpolate_hash { # # interpolate a complete hash and keep the results # on the varstack. # # called directly by Config::General::new() # my ($this, $config) = @_; # bugfix rt.cpan.org#46184, moved code from _interpolate() to here. if ($this->{InterPolateEnv}) { # may lead to vulnerabilities, by default flag turned off for my $key (keys %ENV){ $config->{__stack}->{$key}=$ENV{$key}; } } $config = $this->_var_hash_stacker($config); return $config; } sub _var_hash_stacker { # # build a varstack of a given hash ref # my ($this, $config) = @_; foreach my $key (keys %{$config}) { next if($key eq "__stack"); if (ref($config->{$key}) eq "ARRAY" ) { $config->{$key} = $this->_var_array_stacker($config->{$key}, $key); } elsif (ref($config->{$key}) eq "HASH") { my $tmphash = $config->{$key}; $tmphash->{__stack} = $config->{__stack}; $config->{$key} = $this->_var_hash_stacker($tmphash); } else { # SCALAR $config->{__stack}->{$key} = $config->{$key}; } } return $config; } sub _var_array_stacker { # # same as _var_hash_stacker but for arrayrefs # my ($this, $config, $key) = @_; my @new; foreach my $entry (@{$config}) { if (ref($entry) eq "HASH") { $entry = $this->_var_hash_stacker($entry); } elsif (ref($entry) eq "ARRAY") { # ignore this. Arrays of Arrays cannot be created/supported # with Config::General, because they are not accessible by # any key (anonymous array-ref) next; } else { #### $config->{__stack}->{$key} = $config->{$key}; # removed. a array of scalars (eg: option = [1,2,3]) cannot # be used for interpolation (which one shall we use?!), so # we ignore those types of lists. # found by fbicknel, fixes rt.cpan.org#41570 } push @new, $entry; } return \@new; } sub _clean_stack { # # recursively empty the variable stack # my ($this, $config) = @_; #return $config; # DEBUG foreach my $key (keys %{$config}) { if ($key eq "__stack") { delete $config->{__stack}; next; } if (ref($config->{$key}) eq "ARRAY" ) { $config->{$key} = $this->_clean_array_stack($config->{$key}); } elsif (ref($config->{$key}) eq "HASH") { $config->{$key} = $this->_clean_stack($config->{$key}); } } return $config; } sub _clean_array_stack { # # same as _var_hash_stacker but for arrayrefs # my ($this, $config) = @_; my @new; foreach my $entry (@{$config}) { if (ref($entry) eq "HASH") { $entry = $this->_clean_stack($entry); } elsif (ref($entry) eq "ARRAY") { # ignore this. Arrays of Arrays cannot be created/supported # with Config::General, because they are not accessible by # any key (anonymous array-ref) next; } push @new, $entry; } return \@new; } 1; __END__ =head1 NAME Config::General::Interpolated - Parse variables within Config files =head1 SYNOPSIS use Config::General; $conf = Config::General->new( -ConfigFile => 'configfile', -InterPolateVars => 1 ); =head1 DESCRIPTION This is an internal module which makes it possible to interpolate Perl style variables in your config file (i.e. C<$variable> or C<${variable}>). Normally you don't call it directly. =head1 VARIABLES Variables can be defined everywhere in the config and can be used afterwards as the value of an option. Variables cannot be used as keys or as part of keys. If you define a variable inside a block or a named block then it is only visible within this block or within blocks which are defined inside this block. Well - let's take a look to an example: # sample config which uses variables basedir = /opt/ora user = t_space sys = unix instance = INTERN owner = $user # "t_space" logdir = $basedir/log # "/opt/ora/log" sys = macos misc1 = ${sys}_${instance} # macos_INTERN misc2 = $user # "t_space"
This will result in the following structure: { 'basedir' => '/opt/ora', 'user' => 't_space' 'sys' => 'unix', 'table' => { 'intern' => { 'sys' => 'macos', 'logdir' => '/opt/ora/log', 'instance' => 'INTERN', 'owner' => 't_space', 'procs' => { 'misc1' => 'macos_INTERN', 'misc2' => 't_space' } } } As you can see, the variable B has been defined twice. Inside the block a variable ${sys} has been used, which then were interpolated into the value of B defined inside the block, not the sys variable one level above. If sys were not defined inside the
block then the "global" variable B would have been used instead with the value of "unix". Variables inside double quotes will be interpolated, but variables inside single quotes will B interpolated. This is the same behavior as you know of Perl itself. In addition you can surround variable names with curly braces to avoid misinterpretation by the parser. =head1 NAMING CONVENTIONS Variable names must: =over =item * start with a US-ASCII letter(a-z or A-Z) or a digit (0-9). =item * contain only US-ASCII letter(a-z or A-Z), digits (0-9), the dash (-) colon (:), dot (.), underscore (_) and plus (+) characters. =back For added clarity variable names can be surrounded by curly braces. =head1 SEE ALSO L =head1 AUTHORS Thomas Linden Autrijus Tang Wei-Hon Chen =head1 COPYRIGHT Copyright 2001 by Wei-Hon Chen Eplasmaball@pchome.com.twE. Copyright 2002-2022 by Thomas Linden . This program is free software; you can redistribute it and/or modify it under the terms of the Artistic License 2.0. See L =head1 VERSION 2.16 =cut Config-General-2.67/General/Extended.pm0000644000175000017500000003512314737167104016377 0ustar scipscip# # Config::General::Extended - special Class based on Config::General # # Copyright (c) 2000-2022 Thomas Linden . # All Rights Reserved. Std. disclaimer applies. # Licensed under the Artistic License 2.0. # # namespace package Config::General::Extended; # yes we need the hash support of new() in 1.18 or higher! use Config::General 1.18; use FileHandle; use Carp; # inherit new() and so on from Config::General our @ISA = qw(Config::General); use strict; $Config::General::Extended::VERSION = "2.07"; sub new { croak "Deprecated method Config::General::Extended::new() called.\n" ."Use Config::General::new() instead and set the -ExtendedAccess flag.\n"; } sub getbypath { my ($this, $path) = @_; my $xconfig = $this->{config}; $path =~ s#^/##; $path =~ s#/$##; my @pathlist = split /\//, $path; my $index; foreach my $element (@pathlist) { if($element =~ /^([^\[]*)\[(\d+)\]$/) { $element = $1; $index = $2; } else { $index = undef; } if(ref($xconfig) eq "ARRAY") { return {}; } elsif (! exists $xconfig->{$element}) { return {}; } if(ref($xconfig->{$element}) eq "ARRAY") { if(! defined($index) ) { #croak "$element is an array but you didn't specify an index to access it!\n"; $xconfig = $xconfig->{$element}; } else { if(exists $xconfig->{$element}->[$index]) { $xconfig = $xconfig->{$element}->[$index]; } else { croak "$element doesn't have an element with index $index!\n"; } } } else { $xconfig = $xconfig->{$element}; } } return $xconfig; } sub obj { # # returns a config object from a given key # or from the current config hash if the $key does not exist # or an empty object if the content of $key is empty. # my($this, $key) = @_; # just create the empty object, just in case my $empty = $this->SUPER::new( -ExtendedAccess => 1, -ConfigHash => {}, %{$this->{Params}} ); if (exists $this->{config}->{$key}) { if (!$this->{config}->{$key}) { # be cool, create an empty object! return $empty } elsif (ref($this->{config}->{$key}) eq "ARRAY") { my @objlist; foreach my $element (@{$this->{config}->{$key}}) { if (ref($element) eq "HASH") { push @objlist, $this->SUPER::new( -ExtendedAccess => 1, -ConfigHash => $element, %{$this->{Params}} ); } else { if ($this->{StrictObjects}) { croak "element in list \"$key\" does not point to a hash reference!\n"; } # else: skip this element } } return \@objlist; } elsif (ref($this->{config}->{$key}) eq "HASH") { return $this->SUPER::new( -ExtendedAccess => 1, -ConfigHash => $this->{config}->{$key}, %{$this->{Params}} ); } else { # nothing supported if ($this->{StrictObjects}) { croak "key \"$key\" does not point to a hash reference!\n"; } else { # be cool, create an empty object! return $empty; } } } else { # even return an empty object if $key does not exist return $empty; } } sub value { # # returns a value of the config hash from a given key # this can be a hashref or a scalar # my($this, $key, $value) = @_; if (defined $value) { $this->{config}->{$key} = $value; } else { if (exists $this->{config}->{$key}) { return $this->{config}->{$key}; } else { if ($this->{StrictObjects}) { croak "Key \"$key\" does not exist within current object\n"; } else { return ""; } } } } sub hash { # # returns a value of the config hash from a given key # as hash # my($this, $key) = @_; if (exists $this->{config}->{$key}) { return %{$this->{config}->{$key}}; } else { if ($this->{StrictObjects}) { croak "Key \"$key\" does not exist within current object\n"; } else { return (); } } } sub array { # # returns a value of the config hash from a given key # as array # my($this, $key) = @_; if (exists $this->{config}->{$key}) { return @{$this->{config}->{$key}}; } if ($this->{StrictObjects}) { croak "Key \"$key\" does not exist within current object\n"; } else { return (); } } sub is_hash { # # return true if the given key contains a hashref # my($this, $key) = @_; if (exists $this->{config}->{$key}) { if (ref($this->{config}->{$key}) eq "HASH") { return 1; } else { return; } } else { return; } } sub is_array { # # return true if the given key contains an arrayref # my($this, $key) = @_; if (exists $this->{config}->{$key}) { if (ref($this->{config}->{$key}) eq "ARRAY") { return 1; } else { return; } } else { return; } } sub is_scalar { # # returns true if the given key contains a scalar(or number) # my($this, $key) = @_; if (exists $this->{config}->{$key} && !ref($this->{config}->{$key})) { return 1; } return; } sub exists { # # returns true if the key exists # my($this, $key) = @_; if (exists $this->{config}->{$key}) { return 1; } else { return; } } sub keys { # # returns all keys under in the hash of the specified key, if # it contains keys (so it must be a hash!) # my($this, $key) = @_; if (!$key) { if (ref($this->{config}) eq "HASH") { return map { $_ } keys %{$this->{config}}; } else { return (); } } elsif (exists $this->{config}->{$key} && ref($this->{config}->{$key}) eq "HASH") { return map { $_ } keys %{$this->{config}->{$key}}; } else { return (); } } sub delete { # # delete the given key from the config, if any # and return what is deleted (just as 'delete $hash{key}' does) # my($this, $key) = @_; if (exists $this->{config}->{$key}) { return delete $this->{config}->{$key}; } else { return undef; } } sub configfile { # # sets or returns the config filename # my($this,$file) = @_; if ($file) { $this->{configfile} = $file; } return $this->{configfile}; } sub find { my $this = shift; my $key = shift; return undef unless $this->exists($key); if (@_) { return $this->obj($key)->find(@_); } else { return $this->obj($key); } } sub AUTOLOAD { # # returns the representing value, if it is a scalar. # my($this, $value) = @_; my $key = $Config::General::Extended::AUTOLOAD; # get to know how we were called $key =~ s/.*:://; # remove package name! if (defined $value) { # just set $key to $value! $this->{config}->{$key} = $value; } elsif (exists $this->{config}->{$key}) { if ($this->is_hash($key)) { croak "Key \"$key\" points to a hash and cannot be automatically accessed\n"; } elsif ($this->is_array($key)) { croak "Key \"$key\" points to an array and cannot be automatically accessed\n"; } else { return $this->{config}->{$key}; } } else { if ($this->{StrictObjects}) { croak "Key \"$key\" does not exist within current object\n"; } else { # be cool return undef; # bugfix rt.cpan.org#42331 } } } sub DESTROY { my $this = shift; $this = (); } # keep this one 1; =head1 NAME Config::General::Extended - Extended access to Config files =head1 SYNOPSIS use Config::General; $conf = Config::General->new( -ConfigFile => 'configfile', -ExtendedAccess => 1 ); =head1 DESCRIPTION This is an internal module which makes it possible to use object oriented methods to access parts of your config file. Normally you don't call it directly. =head1 METHODS =over =item configfile('filename') Set the filename to be used by B to "filename". It returns the current configured filename if called without arguments. =item obj('key') Returns a new object (of Config::General::Extended Class) from the given key. Short example: Assume you have the following config: age 23 age 56 blah blubber blah gobble leer and already read it in using B, then you can get a new object from the "individual" block this way: $individual = $conf->obj("individual"); Now if you call B on I<$individual> (just for reference) you would get: $VAR1 = ( martin => { age => 13 } ); Or, here is another use: my $individual = $conf->obj("individual"); foreach my $person ($conf->keys("individual")) { $man = $individual->obj($person); print "$person is " . $man->value("age") . " years old\n"; } See the discussion on B and B below. If the key from which you want to create a new object is empty, an empty object will be returned. If you run the following on the above config: $obj = $conf->obj("other")->obj("leer"); Then $obj will be empty, just like if you have had run this: $obj = Config::General::Extended->new( () ); Read operations on this empty object will return nothing or even fail. But you can use an empty object for I a new config using write operations, i.e.: $obj->someoption("value"); See the discussion on B below. If the key points to a list of hashes, a list of objects will be returned. Given the following example config: you could write code like this to access the list the OOP way: my $objlist = $conf->obj("option"); foreach my $option (@{$objlist}) { print $option->name; } Please note that the list will be returned as a reference to an array. Empty elements or non-hash elements of the list, if any, will be skipped. =item hash('key') This method returns a hash(if it B one!) from the config which is referenced by "key". Given the sample config above you would get: my %sub_hash = $conf->hash("individual"); print Dumper(\%sub_hash); $VAR1 = { martin => { age => 13 } }; =item array('key') This the equivalent of B mentioned above, except that it returns an array. Again, we use the sample config mentioned above: $other = $conf->obj("other"); my @blahs = $other->array("blah"); print Dumper(\@blahs); $VAR1 = [ "blubber", "gobble" ]; =item value('key') This method returns the scalar value of a given key. Given the following sample config: name = arthur age = 23 you could do something like that: print $conf->value("name") . " is " . $conf->value("age") . " years old\n"; You can use this method also to set the value of "key" to something if you give over a hash reference, array reference or a scalar in addition to the key. An example: $conf->value("key", \%somehash); # or $conf->value("key", \@somearray); # or $conf->value("key", $somescalar); Please note, that this method does not complain about existing values within "key"! =item is_hash('key') is_array('key') is_scalar('key') As seen above, you can access parts of your current config using hash, array or scalar methods. But you are right if you guess, that this might become problematic, if for example you call B on a key which is in real not a hash but a scalar. Under normal circumstances perl would refuse this and die. To avoid such behavior you can use one of the methods is_hash() is_array() is_scalar() to check if the value of "key" is really what you expect it to be. An example(based on the config example from above): if($conf->is_hash("individual") { $individual = $conf->obj("individual"); } else { die "You need to configure a "individual" block!\n"; } =item exists('key') This method returns just true if the given key exists in the config. =item keys('key') Returns an array of the keys under the specified "key". If you use the example config above you could do that: print Dumper($conf->keys("individual"); $VAR1 = [ "martin", "joseph" ]; If no key name was supplied, then the keys of the object itself will be returned. You can use this method in B loops as seen in an example above(obj() ). =item delete('key') This method removes the given key and all associated data from the internal hash structure. If 'key' contained data, then this data will be returned, otherwise undef will be returned. =item find(@list) Given a list of nodes, ->find will search for a tree that branches in just this way, returning the Config::General::Extended object it finds at the bottom if it exists. You can also search partway down the tree and ->find should return where you left off. For example, given the values B and the following tree ( tags omitted for brevity): ... ... BAR = shoo B will find the object at I with the value BAR = shoo and return it. =back =head1 AUTOLOAD METHODS Another useful feature is implemented in this class using the B feature of perl. If you know the keynames of a block within your config, you can access to the values of each individual key using the method notation. See the following example and you will get it: We assume the following config: name = Moser prename = Peter birth = 12.10.1972 Now we read it in and process it: my $conf = Config::General::Extended->new("configfile"); my $person = $conf->obj("person"); print $person->prename . " " . $person->name . " is " . $person->age . " years old\n"; This notation supports only scalar values! You need to make sure, that the block does not contain any subblock or multiple identical options(which will become an array after parsing)! If you access a non-existent key this way, Config::General will croak an error. You can turn this behavior off by setting B<-StrictObjects> to 0 or "no". In this case undef will be returned. Of course you can use this kind of methods for writing data too: $person->name("Neustein"); This changes the value of the "name" key to "Neustein". This feature behaves exactly like B, which means you can assign hash or array references as well and that existing values under the given key will be overwritten. =head1 COPYRIGHT Copyright (c) 2000-2022 Thomas Linden This library is free software; you can redistribute it and/or modify it under the terms of the Artistic License 2.0. =head1 BUGS none known yet. =head1 AUTHOR Thomas Linden =head1 VERSION 2.07 =cut Config-General-2.67/META.json0000644000175000017500000000213014737432447014342 0ustar scipscip{ "abstract" : "unknown", "author" : [ "unknown" ], "dynamic_config" : 1, "generated_by" : "ExtUtils::MakeMaker version 7.62, CPAN::Meta::Converter version 2.150010", "license" : [ "artistic_2" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "Config-General", "no_index" : { "directory" : [ "t", "inc" ] }, "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "runtime" : { "requires" : { "File::Glob" : "0", "File::Spec::Functions" : "0", "FileHandle" : "0", "IO::File" : "0" } } }, "release_status" : "stable", "resources" : { "repository" : { "url" : "https://github.com/TLINDEN/Config-General" } }, "version" : "2.67", "x_serialization_backend" : "JSON::PP version 4.06" }