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