File Coverage

lib/Config/IniHashReadDeep.pm
Criterion Covered Total %
statement 4 6 66.6
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 6 8 75.0


line stmt bran cond sub pod time code
1             package Config::IniHashReadDeep; ## Loads INI config as deep hashes
2              
3              
4 4     4   5772 use strict;
  4         4  
  4         107  
5              
6              
7 4     4   2849 use Config::IniHash;
  0            
  0            
8              
9             use vars qw(@ISA @EXPORT %EXPORT_TAGS $VERSION);
10              
11             use Exporter;
12              
13             our $VERSION='0.04';
14              
15              
16             @ISA = qw(Exporter);
17              
18              
19              
20             %EXPORT_TAGS = ( all => [qw(
21             get_ini_file
22             )] );
23              
24             Exporter::export_ok_tags('all');
25              
26              
27              
28              
29             # It is a wrapper using Config::IniHash, but only for loading and it does something
30             # special; using the entries as paths, delimited by '.' and building a hash tree of it.
31             #
32             #
33             # SYNOPSIS
34             # ========
35             #
36             # use Config::IniHashReadDeep 'get_ini_file';
37             #
38             # my $inihash = get_ini_file( $file );
39             #
40             # # or OO style:
41             #
42             # use Config::IniHashReadDeep;
43             # use Data::Dumper;
44             #
45             # my $ini = Config::IniHashReadDeep->new( $file )->get_ini();
46             #
47             # print Dumper($ini);
48             #
49             #
50             # Example ini file:
51             #
52             #
53             # [main]
54             # test=5
55             # foo.bar=123
56             # foo.more=77
57             #
58             # [digits]
59             # with.counting.000=111
60             # with.counting.001=112
61             # with.counting.002=113
62             #
63             #
64             # [digitsmore]
65             # with.counting.001.foo=111f
66             # with.counting.003.bar=111b
67             #
68             #
69             # The example above will print that:
70             #
71             #
72             #
73             #
74             # $VAR1 = {
75             # 'digitsmore' => {
76             # 'with' => {
77             # 'counting' => [
78             # undef,
79             # {
80             # 'foo' => '111f'
81             # },
82             # undef,
83             # {
84             # 'bar' => '111b'
85             # }
86             # ]
87             # }
88             # },
89             # 'main' => {
90             # 'test' => '5',
91             # 'foo' => {
92             # 'bar' => '123',
93             # 'more' => '77'
94             # }
95             # },
96             # 'digits' => {
97             # 'with' => {
98             # 'counting' => [
99             # '111',
100             # '112',
101             # '113'
102             # ]
103             # }
104             # }
105             # };
106             #
107             #
108             #
109             # Paths
110             # =====
111             # The paths used in the ini must be delimited with dotts. But you can set an own delimiter via setting
112             # 'delimiter' in the constructor.
113             #
114             # Please do not use the basis of a path to store a value in, that can not work. WRONG:
115             #
116             # [main]
117             # test=5
118             # test.more=5
119             #
120             # It will cause an error like "Can't use string ("5") as a HASH ref".
121             # The hash can not contain a value and a link to a deeper instance of the hash.
122             #
123             # Numbers
124             # =======
125             # If you use numbers in the path elements, it will use an ARRAY instead of an HASH to place the value.
126             # Please note, that starting with high numbers or leaving gaps in numbers, causes undef entries.
127             # It will be up to you later to check wether there is a value or not.
128             #
129             # Using numbers gives you the possibility to order entries.
130             #
131             #
132             # LICENSE
133             # =======
134             # You can redistribute it and/or modify it under the conditions of LGPL.
135             #
136             # AUTHOR
137             # ======
138             # Andreas Hernitscheck ahernit(AT)cpan.org
139              
140              
141              
142              
143              
144             # At least it takes a filename. For further values, please have a look into L
145             #
146             # You may set a different 'delimiter'.
147             sub new { # $inihash (%options)
148             my $pkg=shift;
149             my $this={};
150              
151             bless $this,$pkg;
152              
153             my $first=shift;
154             my @v=@_;
155             my $v={@v};
156              
157             my $ini = ReadINI($first,@v);
158              
159             $this->{'inihash_flat'} = $ini;
160              
161             if ( $v->{'delimiter'} ){
162             $this->{'delimiter'} = $v->{'delimiter'};
163             }
164              
165            
166              
167             my $deepini = $this->_unflatten_inihash();
168              
169             $this->{'inihash_deep'} = $deepini;
170              
171             return $this;
172             }
173              
174              
175             # Returns the deep ini
176             sub get_ini { # \%inihash
177             my $this = shift;
178             my $ini = $this->{'inihash_deep'};
179              
180             return $ini;
181             }
182              
183             # this is not a method, but a static function, which you can
184             # also import like that:
185             #
186             # use Config::IniHashReadDeep 'get_ini_file';
187             # my $inihash = get_ini_file( $file );
188             #
189             sub get_ini_file { # \%inihash ($filename)
190             my $file=shift;
191             my $ini = Config::IniHashReadDeep->new( $file )->get_ini();
192             return $ini;
193             }
194              
195              
196             sub _unflatten_inihash {
197             my $this = shift;
198             my $ini = $this->{'inihash_flat'};
199             my $deepini = {};
200              
201             # if not global
202             $this->{'STORE'}||={};
203              
204             my $delim = $this->{'delimiter'} || '.';
205              
206             foreach my $block (keys %$ini) { ## each block in ini
207              
208             foreach my $e (keys %{ $ini->{$block} }){
209              
210             my $value = $ini->{$block}->{$e};
211              
212             ## location holds the direct reference to the final store (right element of the tree)
213             my $location = $this->_hash_location_by_path( path => "$block$delim$e" );
214              
215             # stores value to location
216             ${ $location } = $value;
217              
218             }
219              
220             }
221              
222             $deepini = $this->{'STORE'};
223              
224             return $deepini;
225             }
226              
227              
228              
229              
230             # builds a recursive hash reference by given path.
231             # takes hash values like:
232             # location = reference to formal hashnode
233             # path = a path like abc.def.more
234             sub _hash_location_by_path {
235             my $this = shift;
236             my $v={@_};
237             my $path = $v->{'path'} || '';
238             my $exec_last = $v->{'exec_last'};
239             my $dont_create_undef_entry = $v->{'dont_create_undef_entry'};
240            
241             my $location = $v->{'location'} || \$this->{'STORE'} ; # ref to a location
242             my $pathlocation;
243              
244             my $delim = $this->{'delimiter'} || '.';
245              
246             ## remove beginning char
247             if (index($path,$delim) == 0){
248             $path=~ s|^.||;
249             }
250              
251              
252             my $delimesc = '\\'.$delim;
253            
254              
255            
256             my @path = split( /$delimesc/, $path );
257              
258             if (scalar(@path) == 0){ die "path has to less elements" };
259              
260             my $first = shift @path; # takes first and shorten path
261              
262              
263             if ( scalar( @path ) ){ # more path elements?
264            
265             # $pathlocation = \${ $location }->{ $first };
266              
267             if ($first =~ m/^\d+$/){ ## if it is a digit, make an array
268             $pathlocation = \${ $location }->[ $first ];
269             }else{
270             $pathlocation = \${ $location }->{ $first };
271             }
272              
273              
274             # recursive step down the path
275             $pathlocation = $this->_hash_location_by_path( path => join($delim,@path),
276             location => $pathlocation,
277             remove => $v->{'remove'},
278             exec_last => $exec_last,
279             dont_create_undef_entry => $dont_create_undef_entry,
280             );
281              
282              
283             }else{ # last path element?
284              
285             if ($v->{'remove'}){
286             delete ${ $location }->{ $first };
287             $dont_create_undef_entry = 1;
288             }
289              
290             if ($exec_last){
291             &$exec_last( location => $location, key => $first );
292             }
293              
294             ## same line again. it seems to be one too much, but it isnt,
295             ## that line creates also an undef value, that exists what
296             ## changes the data. exec subs may work different.
297             if ( !$dont_create_undef_entry ){
298              
299             if ($first =~ m/^\d+$/){ ## if it is a digit, make an array
300             $pathlocation = \${ $location }->[ $first ];
301             }else{
302             $pathlocation = \${ $location }->{ $first };
303             }
304              
305            
306             }
307              
308             }
309              
310              
311            
312              
313              
314              
315             return $pathlocation;
316             }
317              
318              
319              
320              
321             1;
322             #################### pod generated by Pod::Autopod - keep this line to make pod updates possible ####################
323              
324             =head1 NAME
325              
326             Config::IniHashReadDeep - Loads INI config as deep hashes
327              
328              
329             =head1 SYNOPSIS
330              
331              
332             use Config::IniHashReadDeep 'get_ini_file';
333              
334             my $inihash = get_ini_file( $file );
335              
336             # or OO style:
337              
338             use Config::IniHashReadDeep;
339             use Data::Dumper;
340              
341             my $ini = Config::IniHashReadDeep->new( $file )->get_ini();
342              
343             print Dumper($ini);
344              
345              
346             Example ini file:
347              
348              
349             [main]
350             test=5
351             foo.bar=123
352             foo.more=77
353              
354             [digits]
355             with.counting.000=111
356             with.counting.001=112
357             with.counting.002=113
358              
359              
360             [digitsmore]
361             with.counting.001.foo=111f
362             with.counting.003.bar=111b
363              
364              
365             The example above will print that:
366              
367              
368              
369              
370             $VAR1 = {
371             'digitsmore' => {
372             'with' => {
373             'counting' => [
374             undef,
375             {
376             'foo' => '111f'
377             },
378             undef,
379             {
380             'bar' => '111b'
381             }
382             ]
383             }
384             },
385             'main' => {
386             'test' => '5',
387             'foo' => {
388             'bar' => '123',
389             'more' => '77'
390             }
391             },
392             'digits' => {
393             'with' => {
394             'counting' => [
395             '111',
396             '112',
397             '113'
398             ]
399             }
400             }
401             };
402              
403              
404              
405              
406              
407             =head1 DESCRIPTION
408              
409             It is a wrapper using Config::IniHash, but only for loading and it does something
410             special; using the entries as paths, delimited by '.' and building a hash tree of it.
411              
412              
413              
414              
415             =head1 REQUIRES
416              
417             L
418              
419             L
420              
421              
422             =head1 METHODS
423              
424             =head2 new
425              
426             my $inihash = $this->new(%options);
427              
428             At least it takes a filename. For further values, please have a look into L
429              
430             You may set a different 'delimiter'.
431              
432              
433             =head2 get_ini
434              
435             my \%inihash = $this->get_ini();
436              
437             Returns the deep ini
438              
439              
440             =head2 get_ini_file
441              
442             my \%inihash = get_ini_file($filename);
443              
444             this is not a method, but a static function, which you can
445             also import like that:
446              
447             use Config::IniHashReadDeep 'get_ini_file';
448             my $inihash = get_ini_file( $file );
449              
450              
451              
452              
453             =head1 Paths
454              
455             The paths used in the ini must be delimited with dotts. But you can set an own delimiter via setting
456             'delimiter' in the constructor.
457              
458              
459              
460             =head1 Numbers
461              
462             If you use numbers in the path elements, it will use an ARRAY instead of an HASH to place the value.
463             Please note, that starting with high numbers or leaving gaps in numbers, causes undef entries.
464             It will be up to you later to check wether there is a value or not.
465              
466             Using numbers gives you the possibility to order entries.
467              
468              
469              
470              
471             =head1 AUTHOR
472              
473             Andreas Hernitscheck ahernit(AT)cpan.org
474              
475              
476             =head1 LICENSE
477              
478             You can redistribute it and/or modify it under the conditions of LGPL.
479              
480              
481              
482             =cut
483