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