File Coverage

blib/lib/Hash/DefHash.pm
Criterion Covered Total %
statement 220 242 90.9
branch 97 130 74.6
condition 51 76 67.1
subroutine 30 38 78.9
pod 31 31 100.0
total 429 517 82.9


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