File Coverage

blib/lib/Util/H2O/More.pm
Criterion Covered Total %
statement 192 204 94.1
branch 41 46 89.1
condition 22 35 62.8
subroutine 33 35 94.2
pod 17 19 89.4
total 305 339 89.9


line stmt bran cond sub pod time code
1 12     12   1314447 use strict;
  12         25  
  12         423  
2 12     12   121 use warnings;
  12         19  
  12         807  
3              
4             package Util::H2O::More;
5 12     12   4179 use parent q/Exporter/;
  12         3121  
  12         66  
6 12     12   6136 use Util::H2O ();
  12         90413  
  12         884  
7              
8             our @EXPORT_OK = (qw/baptise opt2h2o h2o o2h d2o o2d o2h2o ini2h2o ini2o h2o2ini HTTPTiny2h2o o2ini Getopt2h2o ddd dddie tr4h2o yaml2h2o yaml2o/);
9             our $VERSION = q{0.4.3};
10              
11 12     12   104 use feature 'state';
  12         22  
  12         21462  
12              
13             # quick hack to export h2o, uses proper
14             # Util::H2O::h2o called with full namespace
15             sub h2o {
16 128     128 1 732802 return Util::H2O::h2o @_;
17             }
18              
19             # maintains basically a count to create non-colliding
20             # unique $pkg names (basically what Util::H2O::h2o does
21             # if $pkg is not specified using -class
22             # monatomically increasing uuid
23             sub _uuid {
24 11     11   18 state $uuid = 0;
25 11         49 return ++$uuid;
26             }
27              
28             # non-recursive option
29             sub baptise ($$@) {
30 11     11 1 326124 my ( $ref, $pkg, @default_accessors );
31 11         22 my $pos0 = shift;
32              
33             # check pos0 for '-recurse'
34 11 100       45 if ( $pos0 eq q{-recurse} ) {
35 9         25 ( $ref, $pkg, @default_accessors ) = @_;
36             }
37             else {
38 2         2 $ref = $pos0;
39 2         5 ( $pkg, @default_accessors ) = @_;
40             }
41              
42 11         16 my $self;
43 11         45 my $real_pkg = sprintf qq{%s::_%s}, $pkg, _uuid;
44              
45             # uses -isa to inherit from $pkg; -class to bless with a package name
46             # derived from $pkg
47 11 100       27 if ( $pos0 eq q{-recurse} ) {
48 9         30 $self = h2o -recurse, -isa => $pkg, -class => $real_pkg, $ref, @default_accessors;
49             }
50             else {
51 2         5 $self = h2o -isa => $pkg, -class => $real_pkg, $ref, @default_accessors;
52             }
53              
54 11         3014 return $self;
55             }
56              
57             # make keys legal for use as accessor, provides original keys via "__og_keys" accessor
58             sub tr4h2o($) {
59 1     1 1 232206 my $hash_ref = shift;
60 1         3 my $new_hashref = {};
61              
62             # List::Util::pairmap was not happy being require'd for some reason
63             # so iterate and replace keys explicitly; store original key in resulting
64             # hashref via __og_keys
65 1         4 foreach my $og_k ( keys %$hash_ref ) {
66 2         5 my $k = $og_k;
67 2         7 $k =~ tr/a-zA-Z0-9/_/c;
68 2         6 $new_hashref->{$k} = $hash_ref->{$og_k};
69              
70             # save old key via __og_keys
71 2         7 $new_hashref->{__og_keys}->{$k} = $og_k;
72             }
73 1         7 return $new_hashref;
74             }
75              
76             # preconditioner for use with Getopt::Long flags; returns just the flag name given
77             # a list of option descriptors, e.g., qw/option1=s option2=i option3/;
78              
79             # Getopt to keys
80             sub opt2h2o(@) {
81 4     4 1 162067 my @getopt_def = @_;
82 4         11 my @flags_only = map { m/([^=!|\s]+)/g; $1 } @getopt_def;
  16         56  
  16         48  
83 4         31 return @flags_only;
84             }
85              
86             # wrapper around opt2h2o (yeah!)
87             sub Getopt2h2o(@) {
88 3     3 1 13310 my $autoundef;
89 3 100 33     36 if ( @_ && $_[0] && !ref$_[0] && $_[0]=~/^-autoundef/ ) {
      66        
      66        
90 1         3 $autoundef = shift;
91             }
92 3         12 my ( $ARGV_ref, $defaults, @opts ) = @_;
93 3   50     9 $defaults //= {};
94 3 100       9 if ($autoundef) {
95             $defaults->{AUTOLOAD} = sub {
96 1     1   3510 my $self = shift;
97 1         2 our $AUTOLOAD;
98 1         10 ( my $key = $AUTOLOAD ) =~ s/.*:://;
99 1 50       5 die qq{Getopt2h2o: Won't set value for non-existing key. Need it? Let the module author know!\n} if @_;
100 1         6 return undef;
101 1         7 };
102             }
103 3         39 my $o = h2o -meth, $defaults, opt2h2o(@opts);
104 3         632 require Getopt::Long;
105 3         15 Getopt::Long::GetOptionsFromArray( $ARGV_ref, $o, @opts ); # Note, @ARGV is passed by reference
106 3         3112 return $o;
107             }
108              
109             # general form of method used to give accessors to Config::Tiny in Util::H2O's
110             # POD documentation
111             sub o2h2o($) {
112 6     6 1 409457 my $ref = shift;
113 6         13 return h2o -recurse, { %{$ref} };
  6         34  
114             }
115              
116             # more specific helper app that uses Config::Tiny->read and o2h2o to get a config
117             # object back from an .ini; requries Config::Tiny
118             sub ini2h2o($) {
119 4     4 1 2435 my $filename = shift;
120 4         29 require Config::Tiny;
121 4         27 return o2h2o( Config::Tiny->read($filename) );
122             }
123              
124             # back compat
125             sub ini2o($) {
126 2     2 0 2908 return ini2h2o(shift);
127             }
128              
129             # write out the INI file
130             sub h2o2ini($$) {
131 2     2 1 2630 my ( $config, $filename ) = @_;
132 2         14 require Config::Tiny;
133 2         11 return Config::Tiny->new( Util::H2O::o2h $config)->write($filename);
134             }
135              
136             # back compat
137             sub o2ini($$) {
138 1     1 0 4032 return h2o2ini( shift, shift );
139             }
140              
141             # return a dereferences hash (non-recursive); reverse of `h2o'
142             sub o2h($) {
143 28     28 1 2316 $Util::H2O::_PACKAGE_REGEX = qr/::_[0-9A-Fa-f]+\z/; # makes internal package name more generic for baptise created references
144 28         92 my $ref = Util::H2O::o2h @_;
145 28 50       2459 if ( ref $ref ne q{HASH} ) {
146 0         0 die qq{o2h: Could not fully remove top-level reference. Probably an issue with \$Util::H2O_PACKAGE_REGEX\n};
147             }
148 28         125 return $ref;
149             }
150              
151             sub d2o(@); # forward declaration to get rid of "too early" warning
152             sub a2o($);
153              
154             # accepts '-autoundef' flag that will insert all keys/getters to be checked
155             # i.e., if (not $myref->doesntexist) { ... } rather than if (not exists $myref->{doesntexist}) { ... }
156             sub d2o(@) {
157 427     427 1 16279 my ($autoundef);
158             # basically how Util::H2O::h2o does it, if we have more options
159             # then we should use the `while` form of this ...
160 427 100 66     2522 if ( @_ && $_[0] && !ref$_[0] && $_[0]=~/^-autoundef/ ) {
      100        
      100        
161 150         240 $autoundef = shift;
162             }
163 427         712 my $thing = shift;
164              
165 427         744 my $isa = ref $thing;
166              
167 427 100       985 if ( $isa eq q{ARRAY} ) {
    100          
168 59         178 a2o $thing;
169 59         404 foreach my $element (@$thing) {
170 203 100       379 if ($autoundef) { # 'd2o -autoundef, $hash'
171 91         135 d2o $autoundef, $element;
172             }
173             else {
174 112         232 d2o $element;
175             }
176             }
177             }
178             elsif ( $isa eq q{HASH} ) {
179 86         286 foreach my $keys ( keys %$thing ) {
180 176 100       470 if ($autoundef) { # 'd2o -autoundef, $hash'
181 50         99 d2o $autoundef, $thing->{$keys};
182             }
183             else {
184 126         337 d2o $thing->{$keys};
185             }
186             }
187 86 100       236 if ($autoundef) { # 'd2o -autoundef, $hash'
188             $thing->{AUTOLOAD} = sub {
189 5     5   2036 my $self = shift;
190 5         7 our $AUTOLOAD;
191 5         35 ( my $key = $AUTOLOAD ) =~ s/.*:://;
192 5 50       15 die qq{d2o: Won't set value for non-existing key. Need it? Let the module author know!\n} if @_;
193 5         23 return undef;
194 21         136 };
195 21         51 h2o -meth, $thing;
196             }
197             else { # default behavior
198 65         157 h2o $thing;
199             }
200             }
201 427         13297 return $thing;
202             }
203              
204             # blesses ARRAY ref as a container and gives it some virtual methods
205             # useful in the context of containing HASH refs that get objectified
206             # by h2o
207             sub a2o($) {
208 12     12   90 no strict 'refs';
  12         23  
  12         19904  
209              
210 59     59 1 97 my $array_ref = shift;
211              
212             # uses lexical scop of the 'if' to a bless $array_ref (an ARRAY ref)
213             # and assigns to it some virtual methods for making dealing with
214             # the "lists of C references easier, as a container
215              
216 59         484 my $a2o_pkg = sprintf( qq{%s::__a2o_%d::vmethods}, __PACKAGE__, int rand 100_000_000 ); # internal a2o
217              
218 59         692 bless $array_ref, $a2o_pkg;
219              
220             ## add vmethod to wrap around array_refs
221              
222             # return item at index INDEX
223             my $GET = sub {
224 1     1   6 my ( $self, $i ) = @_;
225 1 50       2 return undef if $i > $#{$self}; # prevent ARRAY from growing just to get an undef back
  1         6  
226 0         0 return $self->[$i];
227 59         313 };
228 59         104 *{"${a2o_pkg}::get"} = $GET;
  59         347  
229 59         91 *{"${a2o_pkg}::i"} = $GET;
  59         264  
230              
231             # return rereferenced ARRAY
232 59     4   193 my $ALL = sub { my $self = shift; return @$self; };
  4         5862  
  4         16  
233 59         91 *{"${a2o_pkg}::all"} = $ALL;
  59         257  
234              
235             # returns value returned by the 'scalar' keyword, alias also to 'count'
236 59     29   243 my $SCALAR = sub { my $self = shift; return scalar @$self; };
  29         628  
  29         266  
237 59         97 *{"${a2o_pkg}::scalar"} = $SCALAR;
  59         239  
238 59         98 *{"${a2o_pkg}::count"} = $SCALAR;
  59         220  
239              
240             # 'push' will apply "d2o" to all elements pushed
241 59     12   267 my $PUSH = sub { my ( $self, @i ) = @_; d2o \@i; push @$self, @i; return \@i };
  12         9609  
  12         50  
  12         31  
  12         35  
242 59         95 *{"${a2o_pkg}::push"} = $PUSH;
  59         351  
243              
244             # 'pop' intentionally does NOT apply "o2d" to anyarray_ref pop'd
245 59     8   174 my $POP = sub { my $self = shift; return pop @$self };
  8         31027  
  8         26  
246 59         100 *{"${a2o_pkg}::pop"} = $POP;
  59         256  
247              
248             # 'unshift' will apply "d2o" to all elements unshifted
249 59     12   225 my $UNSHIFT = sub { my ( $self, @i ) = @_; d2o \@i; unshift @$self, @i; return \@i };
  12         8316  
  12         51  
  12         26  
  12         28  
250 59         97 *{"${a2o_pkg}::unshift"} = $UNSHIFT;
  59         233  
251              
252             # 'shift' intentionally does NOT apply "o2d" to anyarray_ref shift'd
253 59     16   249 my $SHIFT = sub { my $self = shift; return shift @$self };
  16         47005  
  16         49  
254 59         89 *{"${a2o_pkg}::shift"} = $SHIFT;
  59         243  
255              
256 59         137 return $array_ref;
257             }
258              
259             # includes internal dereferencing so to be compatible
260             # with the behavior of Util::H2O::o2h
261             sub o2d($); # forward declaration to get rid of "too early" warning
262              
263             sub o2d($) {
264 143     143 1 3158 my $thing = shift;
265 143 100       258 return $thing if not $thing;
266 140         198 my $isa = ref $thing;
267 140 100       317 if ( $isa =~ m/^Util::H2O::More::__a2o/ ) {
    100          
268 18         72 my @_thing = @$thing;
269 18         32 $thing = \@_thing;
270 18         34 foreach my $element (@$thing) {
271 95         186 $element = o2d $element;
272             }
273             }
274             elsif ( $isa =~ m/^Util::H2O::_/ ) {
275 17         41 foreach my $key ( keys %$thing ) {
276 38         328 $thing->{$key} = o2d $thing->{$key};
277             }
278 17         48 $thing = Util::H2O::o2h $thing;
279             }
280 140         1479 return $thing;
281             }
282              
283             # handy, poor man's debug wrappers
284              
285             sub ddd(@) {
286 0     0 1 0 require Data::Dumper;
287 0         0 foreach my $ref (@_) {
288 0         0 print STDERR Data::Dumper::Dumper($ref);
289             }
290             }
291              
292             sub dddie(@) {
293 0     0 1 0 require Data::Dumper;
294 0         0 foreach my $ref (@_) {
295 0         0 print STDERR Data::Dumper::Dumper($ref);
296             }
297 0         0 die qq{died due to use of dddie};
298             }
299              
300             # YAML configuration support - may return more than 1 reference
301             sub yaml2h2o($) {
302 6     6 1 227241 require YAML;
303 6         9104 my $file_or_yaml = shift; # may be a file or a string
304 6         15 my @yaml = (); # yaml can have multiple objects serialized, via ---
305              
306             # determine if YAML or file name
307 6         43 my @lines = split /\n/, $file_or_yaml;
308              
309             # if a file, use YAML::LoadFile
310 6 50 33     40 if ( @lines == 1 and -e $file_or_yaml ) {
    100          
311 0         0 @yaml = YAML::LoadFile($file_or_yaml);
312             }
313              
314             # if not a file, assume YAML string and use YAML::Load
315             elsif ($lines[0] eq q{---}) {
316 4         19 @yaml = YAML::Load($file_or_yaml);
317             }
318              
319             # die because not supported content $file_or_yaml - it is neither
320             else {
321 2         21 die qq{Provided parameter looks like neither a file name nor a valid YAML snippet.\n};
322             }
323              
324             # iterate over 1 or more serialized objects that were deserialized
325             # from the YAML, applie C to it due to the potential presence
326             # of ARRAY references
327 4         67570 my @obs = ();
328 4         14 foreach my $y (@yaml) {
329 8         31 push @obs, d2o $y;
330             }
331              
332 4         52 return @obs;
333             }
334              
335             # back compat
336             sub yaml2o($) {
337 3     3 1 7049 return yaml2h2o(shift);
338             }
339              
340             # NOTE: no h2o2yaml or o2yaml, but can add one if somebody needs it ... please file an issue on the tracker (GH these days)
341              
342             # This method assumes a response HASH reference returned by HTTP::Tiny; so
343             # it looks for $ref->{content}, and if anything is found there it will attempt
344             # to turn it into a Perl data structure usin JSON::XS::Maybe::decode_json; it
345             # them applies "d2o -autoundef" to it; if the JSON decode fails, the error will
346             # be hidden silently and the original content will be retained in the provided
347             # response reference (also available via ->content by virtu of h2o being applied).
348             # To force the JSON decode error to propagate up so that it may be caught, use
349             # the "-autothrow" option, e.g.;
350             # HTTPTiny2h2o -autothrow, $ref_with_bad_JSON; # propagates decode_json exception from "malformed" JSON
351             # HTTPTiny2h2o $ref_with_bad_JSON; # hides bad decode, "->content" accessor created to return original content
352             # HTTPTiny2h2o $ref_with_good_JSON; # h2o applied to $ref, "d2o -autoundef" applied to value of ->{content}
353             sub HTTPTiny2h2o(@) {
354 7     7 1 189412 my $autothrow;
355 7 100 33     44 if ( @_ && $_[0] && !ref$_[0] && $_[0]=~/^-autothrow/ ) {
      66        
      66        
356 1         1 $autothrow = shift;
357             }
358 7         10 my $ref = shift;
359 7 100 66     25 if (ref $ref eq q{HASH} and exists $ref->{content}) {
360 6         31 require JSON::MaybeXS; # tries to load the JSON module you want, (by default, exports decode_json, encode_json)
361 6         14 h2o $ref, qw/content/;
362 6 100       537 if ($ref->content) {
363             # allows exception from decode_json to be raised if -autothrow
364             # and the JSON is determined to be malformed
365 5 100       25 if ($autothrow) {
366             # the JSON decode will die on bad JSON
367 1         3 my $JSON = JSON::MaybeXS::decode_json($ref->content);
368 0         0 my $content= d2o -autoundef, $JSON;
369 0         0 $ref->content($content);
370             }
371             # default is hide any malformed JSON exception, effectively
372             # leaving the ->content untouched
373             else {
374 4         6 eval {
375             # the JSON decode will die on bad JSON
376 4         7 my $JSON = JSON::MaybeXS::decode_json($ref->content);
377 3         88 my $content= d2o -autoundef, $JSON;
378 3         7 $ref->content($content);
379             }
380             }
381             }
382             else {
383 1         7 my $content= d2o -autoundef, {};
384 1         3 $ref->content($content);
385             }
386             }
387             else {
388 1         8 die qq{Provided parameter must be a proper HASH reference returned by HTTP::Tiny that contains a 'content' HASH key.};
389             }
390              
391 5         32 return $ref;
392             }
393              
394             1;
395              
396             __END__