File Coverage

blib/lib/Hash/DefHash.pm
Criterion Covered Total %
statement 202 221 91.4
branch 107 130 82.3
condition 49 69 71.0
subroutine 29 37 78.3
pod 31 31 100.0
total 418 488 85.6


line stmt bran cond sub pod time code
1             ## no critic: Modules::ProhibitAutomaticExportation
2              
3             package Hash::DefHash;
4              
5             our $DATE = '2018-09-10'; # DATE
6             our $VERSION = '0.070'; # VERSION
7              
8 1     1   464 use 5.010001;
  1         7  
9 1     1   4 use strict;
  1         2  
  1         15  
10 1     1   4 use warnings;
  1         1  
  1         41  
11              
12 1     1   4 use Scalar::Util qw(blessed);
  1         2  
  1         117  
13 1     1   447 use String::Trim::More qw(trim_blank_lines);
  1         539  
  1         50  
14              
15 1     1   11 use Exporter qw(import);
  1         2  
  1         2501  
16             our @EXPORT = qw(defhash);
17              
18             our $re_prop = qr/\A[A-Za-z_][A-Za-z0-9_]*\z/;
19             our $re_attr = qr/\A[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*\z/;
20             our $re_key = qr/
21             \A(?:
22             # 1 = property
23             ([A-Za-z_][A-Za-z0-9_]*)
24             (?:
25             (?:
26             # 2 = attr
27             \. ([A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*)
28             ) |
29             (?:
30             # 3 = (LANG) shortcut
31             \(([A-Za-z]{2}(?:_[A-Za-z]{2})?)\)
32             )
33             )?
34             |
35             # 4 = attr without property
36             \.([A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*)
37             )\z/x;
38              
39             sub defhash {
40             # avoid wrapping twice if already a defhash
41 6 100 66 6 1 5110 return $_[0] if blessed($_[0]) && $_[0]->isa(__PACKAGE__);
42              
43 5         14 __PACKAGE__->new(@_);
44             }
45              
46             sub new {
47 62     62 1 23432 my $class = shift;
48              
49 62         105 my ($hash, %opts) = @_;
50 62   50     114 $hash //= {};
51              
52 62         161 my $self = bless {hash=>$hash, parent=>$opts{parent}}, $class;
53 62 100 100     185 if ($opts{check} // 1) {
54 43         87 $self->check;
55             }
56 43         131 $self;
57             }
58              
59             sub hash {
60 3     3 1 12 my $self = shift;
61              
62 3         48 $self->{hash};
63             }
64              
65             sub check {
66 43     43 1 49 my $self = shift;
67 43         56 my $h = $self->{hash};
68              
69 43         116 for my $k (keys %$h) {
70 95 100       485 next if $k =~ $re_key;
71 19         202 die "Invalid hash key '$k'";
72             }
73 24         44 1;
74             }
75              
76             sub contents {
77 1     1 1 5 my $self = shift;
78 1         2 my $h = $self->{hash};
79              
80 1         2 my %props;
81 1         4 for my $k (keys %$h) {
82 11 50       56 my ($p_prop, $p_attr, $p_lang, $p_attr_wo_prop) = $k =~ $re_key
83             or die "Invalid hash key '$k'";
84 11         17 my $v = $h->{$k};
85 11 100       16 if (defined $p_prop) {
86 7 100       14 next if $p_prop =~ /\A_/;
87 6   100     16 $props{$p_prop} //= {};
88 6 100       13 if (defined $p_attr) {
    50          
89 5 100       9 next if $p_attr =~ /(?:\A|\.)_/;
90 3         8 $props{$p_prop}{$p_attr} = $v;
91             } elsif (defined $p_lang) {
92 0         0 $props{$p_prop}{"alt.lang.$p_lang"} = $v;
93             } else {
94 1         2 $props{$p_prop}{""} = $v;
95             }
96             } else {
97 4 100       12 next if $p_attr_wo_prop =~ /(?:\A|\.)_/;
98 2   100     7 $props{""} //= {};
99 2         4 $props{""}{$p_attr_wo_prop} = $v;
100             }
101             }
102 1         7 %props;
103             }
104              
105             sub props {
106 1     1 1 3 my $self = shift;
107 1         3 my $h = $self->{hash};
108              
109 1         1 my %props;
110 1         5 for my $k (keys %$h) {
111 11 50       52 my ($p_prop, $p_attr, $p_lang, $p_attr_wo_prop) = $k =~ $re_key
112             or die "Invalid hash key '$k'";
113 11 100 66     36 next if defined $p_attr || $p_lang || defined $p_attr_wo_prop;
      100        
114 2 100       6 next if $p_prop =~ /\A_/;
115 1         3 $props{$p_prop}++;
116             }
117 1         8 sort keys %props;
118             }
119              
120             sub prop {
121 6     6 1 111 my ($self, $prop) = @_;
122 6         9 my $h = $self->{hash};
123              
124 6 100       30 die "Property '$prop' not found" unless exists $h->{$prop};
125 4         14 $h->{$prop};
126             }
127              
128             sub get_prop {
129 38     38 1 62 my ($self, $prop) = @_;
130 38         45 my $h = $self->{hash};
131              
132 38         110 $h->{$prop};
133             }
134              
135             sub prop_exists {
136 5     5 1 12 my ($self, $prop) = @_;
137 5         9 my $h = $self->{hash};
138              
139 5         19 exists $h->{$prop};
140             }
141              
142             sub add_prop {
143 2     2 1 54 my ($self, $prop, $val) = @_;
144 2         4 my $h = $self->{hash};
145              
146 2 50       17 die "Invalid property name '$prop'" unless $prop =~ $re_prop;
147 2 100       12 die "Property '$prop' already exists" if exists $h->{$prop};
148 1         5 $h->{$prop} = $val;
149             }
150              
151             sub set_prop {
152 3     3 1 485 my ($self, $prop, $val) = @_;
153 3         5 my $h = $self->{hash};
154              
155 3 50       24 die "Invalid property name '$prop'" unless $prop =~ $re_prop;
156 3 100       9 if (exists $h->{$prop}) {
157 1         3 my $old = $h->{$prop};
158 1         1 $h->{$prop} = $val;
159 1         5 return $old;
160             } else {
161 2         5 $h->{$prop} = $val;
162 2         8 return undef;
163             }
164             }
165              
166             sub del_prop {
167 2     2 1 5 my ($self, $prop, $val) = @_;
168 2         5 my $h = $self->{hash};
169              
170 2 50       14 die "Invalid property name '$prop'" unless $prop =~ $re_prop;
171 2 100       5 if (exists $h->{$prop}) {
172 1         6 return delete $h->{$prop};
173             } else {
174 1         5 return undef;
175             }
176             }
177              
178             sub del_all_props {
179 2     2 1 9 my ($self, $delattrs) = @_;
180 2         4 my $h = $self->{hash};
181              
182 2         6 for my $k (keys %$h) {
183 22 50       129 my ($p_prop, $p_attr, $p_lang, $p_attr_wo_prop) = $k =~ $re_key
184             or die "Invalid hash key '$k'";
185 22 100 100     62 next if defined $p_prop && $p_prop =~ /\A_/;
186 20 100 100     46 next if defined $p_attr && $p_attr =~ /(?:\A|\.)_/;
187 16 100 100     41 next if defined $p_attr_wo_prop && $p_attr_wo_prop =~ /(?:\A|\.)_/;
188 12 100 66     35 if (defined $p_attr || defined $p_lang || defined $p_attr_wo_prop) {
      100        
189 10 100       19 delete $h->{$k} if $delattrs;
190             } else {
191 2         3 delete $h->{$k};
192             }
193             }
194             }
195              
196             sub attrs {
197 6     6 1 21 my ($self, $prop) = @_;
198 6   100     14 $prop //= "";
199 6         9 my $h = $self->{hash};
200              
201 6 100       14 unless ($prop eq '') {
202 4 50       24 die "Invalid property name '$prop'" unless $prop =~ $re_prop;
203             }
204              
205 6         9 my %attrs;
206 6         19 for my $k (keys %$h) {
207 56 50       271 my ($p_prop, $p_attr, $p_lang, $p_attr_wo_prop) = $k =~ $re_key
208             or die "Invalid hash key '$k'";
209 56   100     135 $p_prop //= '';
210 56         63 my $v = $h->{$k};
211 56 100       104 if (defined $p_attr) {
    50          
    100          
212 24 100       48 next unless $prop eq $p_prop;
213 7 100       18 next if $p_attr =~ /(?:\A|\.)_/;
214 3         7 $attrs{$p_attr} = $v;
215             } elsif (defined $p_lang) {
216 0 0       0 next unless $prop eq $p_prop;
217 0         0 $attrs{"alt.lang.$p_lang"} = $v;
218             } elsif (defined $p_attr_wo_prop) {
219 20 100       55 next unless $prop eq '';
220 6 100       19 next if $p_attr_wo_prop =~ /(?:\A|\.)_/;
221 2         5 $attrs{$p_attr_wo_prop} = $v;
222             }
223             }
224 6         37 %attrs;
225             }
226              
227             sub attr {
228 11     11 1 82 my ($self, $prop, $attr) = @_;
229 11   50     25 $prop //= "";
230 11         17 my $h = $self->{hash};
231              
232 11         18 my $k = "$prop.$attr";
233 11 100       36 die "Attribute '$attr' for property '$prop' not found" if !exists($h->{$k});
234 9         34 $h->{$k};
235             }
236              
237             sub get_attr {
238 4     4 1 25 my ($self, $prop, $attr) = @_;
239 4   50     24 $prop //= "";
240 4         7 my $h = $self->{hash};
241              
242 4         9 my $k = "$prop.$attr";
243 4         21 $h->{$k};
244             }
245              
246             sub attr_exists {
247 11     11 1 26 my ($self, $prop, $attr) = @_;
248 11   50     24 $prop //= "";
249 11         16 my $h = $self->{hash};
250              
251 11         19 my $k = "$prop.$attr";
252 11         76 exists $h->{$k};
253             }
254              
255             sub add_attr {
256 5     5 1 145 my ($self, $prop, $attr, $val) = @_;
257 5   50     17 $prop //= "";
258 5         11 my $h = $self->{hash};
259              
260 5 100       10 if ($prop ne '') {
261 3 50       21 die "Invalid property name '$prop'" unless $prop =~ $re_prop;
262             }
263 5 50       25 die "Invalid attribute name '$attr'" unless $attr =~ $re_attr;
264 5         11 my $k = "$prop.$attr";
265             die "Attribute '$attr' for property '$prop' already exists"
266 5 100       27 if exists($h->{$k});
267 3         11 $h->{$k} = $val;
268             }
269              
270             sub set_attr {
271 4     4 1 10 my ($self, $prop, $attr, $val) = @_;
272 4   50     15 $prop //= "";
273 4         15 my $h = $self->{hash};
274              
275 4 100       9 if ($prop ne '') {
276 2 50       13 die "Invalid property name '$prop'" unless $prop =~ $re_prop;
277             }
278 4 50       23 die "Invalid attribute name '$attr'" unless $attr =~ $re_attr;
279 4         9 my $k = "$prop.$attr";
280 4 100       10 if (exists($h->{$k})) {
281 2         3 my $old = $h->{$k};
282 2         3 $h->{$k} = $val;
283 2         14 return $old;
284             } else {
285 2         6 $h->{$k} = $val;
286 2         8 return undef;
287             }
288             }
289              
290             sub del_attr {
291 4     4 1 10 my ($self, $prop, $attr) = @_;
292 4   50     11 $prop //= "";
293 4         37 my $h = $self->{hash};
294              
295 4 100       11 if ($prop ne '') {
296 2 50       15 die "Invalid property name '$prop'" unless $prop =~ $re_prop;
297             }
298 4 50       24 die "Invalid attribute name '$attr'" unless $attr =~ $re_attr;
299 4         8 my $k = "$prop.$attr";
300 4 100       11 if (exists($h->{$k})) {
301 2         10 return delete $h->{$k};
302             } else {
303 2         9 return undef;
304             }
305             }
306              
307             sub del_all_attrs {
308 2     2 1 8 my ($self, $prop) = @_;
309 2   50     5 $prop //= "";
310 2         4 my $h = $self->{hash};
311              
312 2         6 for my $k (keys %$h) {
313 20 50       89 my ($p_prop, $p_attr, $p_lang, $p_attr_wo_prop) = $k =~ $re_key
314             or die "Invalid hash key '$k'";
315 20 100       41 if (defined $p_attr) {
    50          
    100          
316 8 100       14 next unless $prop eq $p_prop;
317 4 100       12 next if $p_attr =~ /(?:\A|\.)_/;
318             } elsif ($p_lang) {
319 0 0       0 next unless $prop eq $p_prop;
320             } elsif (defined $p_attr_wo_prop) {
321 8 100       15 next unless $prop eq '';
322 4 100       11 next if $p_attr_wo_prop =~ /(?:\A|\.)_/;
323             } else {
324 4         8 next;
325             }
326 4         8 delete $h->{$k};
327             }
328             }
329              
330             sub defhash_v {
331 0     0 1 0 my ($self) = @_;
332 0   0     0 $self->get_prop('defhash_v') // 1;
333             }
334              
335             sub v {
336 0     0 1 0 my ($self) = @_;
337 0   0     0 $self->get_prop('v') // 1;
338             }
339              
340             sub default_lang {
341 35     35 1 73 my ($self) = @_;
342 35         37 my $par;
343 35 100       61 if ($self->{parent}) {
344 17         33 $par = $self->{parent}->default_lang;
345             }
346 35   100     50 my $res = $self->get_prop('default_lang') // $par // $ENV{LANG} // "en_US";
      66        
      50        
347 35 100       62 $res = "en_US" if $res eq "C";
348 35         68 $res;
349             }
350              
351             sub name {
352 0     0 1 0 my ($self) = @_;
353 0         0 $self->get_prop('name');
354             }
355              
356             sub summary {
357 0     0 1 0 my ($self) = @_;
358 0         0 $self->get_prop('summary');
359             }
360              
361             sub description {
362 0     0 1 0 my ($self) = @_;
363 0         0 $self->get_prop('description');
364             }
365              
366             sub tags {
367 0     0 1 0 my ($self) = @_;
368 0         0 $self->get_prop('tags');
369             }
370              
371             sub get_prop_lang {
372 10     10 1 4349 my ($self, $prop, $lang, $opts) = @_;
373 10         27 my $h = $self->{hash};
374 10   50     41 $opts //= {};
375              
376 10         16 my $deflang = $self->default_lang;
377 10   66     24 $lang //= $deflang;
378 10   50     26 my $mark = $opts->{mark_different_lang} // 1;
379             #print "deflang=$deflang, lang=$lang, mark_different_lang=$mark\n";
380              
381 10         12 my @k;
382 10 100       19 if ($lang eq $deflang) {
383 6         15 @k = ([$lang, $prop, 0]);
384             } else {
385 4         14 @k = ([$lang, "$prop.alt.lang.$lang", 0], [$deflang, $prop, $mark]);
386             }
387              
388 10         15 for my $k (@k) {
389             #print "k=".join(", ", @$k)."\n";
390 11         20 my $v = $h->{$k->[1]};
391 11 100       29 if (defined $v) {
392 10 100       15 if ($k->[2]) {
393 1         5 my $has_nl = $v =~ s/\R\z//;
394 1 50       6 $v = "{$k->[0] $v}" . ($has_nl ? "\n" : "");
395             }
396 10         23 return trim_blank_lines($v);
397             }
398             }
399 0           return undef;
400             }
401              
402             sub get_prop_all_langs {
403 0     0 1   die "Not yet implemented";
404             }
405              
406             sub set_prop_lang {
407 0     0 1   die "Not yet implemented";
408             }
409              
410             1;
411             # ABSTRACT: Manipulate defhash
412              
413             __END__