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