| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package Data::Tubes::Util; | 
| 2 |  |  |  |  |  |  |  | 
| 3 |  |  |  |  |  |  | # vim: ts=3 sts=3 sw=3 et ai : | 
| 4 |  |  |  |  |  |  |  | 
| 5 | 37 |  |  | 37 |  | 140475 | use strict; | 
|  | 37 |  |  |  |  | 103 |  | 
|  | 37 |  |  |  |  | 1235 |  | 
| 6 | 37 |  |  | 37 |  | 210 | use warnings; | 
|  | 37 |  |  |  |  | 80 |  | 
|  | 37 |  |  |  |  | 1228 |  | 
| 7 | 37 |  |  | 37 |  | 1049 | use English qw< -no_match_vars >; | 
|  | 37 |  |  |  |  | 6626 |  | 
|  | 37 |  |  |  |  | 248 |  | 
| 8 | 37 |  |  | 37 |  | 13706 | use Exporter 'import'; | 
|  | 37 |  |  |  |  | 85 |  | 
|  | 37 |  |  |  |  | 2332 |  | 
| 9 |  |  |  |  |  |  | our $VERSION = '0.738'; | 
| 10 |  |  |  |  |  |  |  | 
| 11 | 37 |  |  | 37 |  | 1324 | use Log::Log4perl::Tiny qw< :easy :dead_if_first >; | 
|  | 37 |  |  |  |  | 27991 |  | 
|  | 37 |  |  |  |  | 287 |  | 
| 12 |  |  |  |  |  |  |  | 
| 13 |  |  |  |  |  |  | our @EXPORT_OK = qw< | 
| 14 |  |  |  |  |  |  | args_array_with_options | 
| 15 |  |  |  |  |  |  | assert_all_different | 
| 16 |  |  |  |  |  |  | generalized_hashy | 
| 17 |  |  |  |  |  |  | load_module | 
| 18 |  |  |  |  |  |  | load_sub | 
| 19 |  |  |  |  |  |  | metadata | 
| 20 |  |  |  |  |  |  | normalize_args | 
| 21 |  |  |  |  |  |  | normalize_filename | 
| 22 |  |  |  |  |  |  | pump | 
| 23 |  |  |  |  |  |  | read_file | 
| 24 |  |  |  |  |  |  | read_file_maybe | 
| 25 |  |  |  |  |  |  | resolve_module | 
| 26 |  |  |  |  |  |  | shorter_sub_names | 
| 27 |  |  |  |  |  |  | sprintffy | 
| 28 |  |  |  |  |  |  | test_all_equal | 
| 29 |  |  |  |  |  |  | traverse | 
| 30 |  |  |  |  |  |  | trim | 
| 31 |  |  |  |  |  |  | tube | 
| 32 |  |  |  |  |  |  | unzip | 
| 33 |  |  |  |  |  |  | >; | 
| 34 |  |  |  |  |  |  |  | 
| 35 |  |  |  |  |  |  | sub _load_module { | 
| 36 | 13 |  |  | 13 |  | 32 | my $module = shift; | 
| 37 | 13 |  |  |  |  | 88 | (my $packfile = $module . '.pm') =~ s{::}{/}gmxs; | 
| 38 | 13 |  |  |  |  | 4354 | require $packfile; | 
| 39 | 13 |  |  |  |  | 299 | return $module; | 
| 40 |  |  |  |  |  |  | } ## end sub _load_module | 
| 41 |  |  |  |  |  |  |  | 
| 42 |  |  |  |  |  |  | sub args_array_with_options { | 
| 43 | 111 |  |  | 111 | 1 | 618 | my %defaults = %{pop @_}; | 
|  | 111 |  |  |  |  | 516 |  | 
| 44 | 111 | 100 | 100 |  |  | 776 | %defaults = (%defaults, %{pop @_}) | 
|  | 55 |  |  |  |  | 212 |  | 
| 45 |  |  |  |  |  |  | if @_ && (ref($_[-1]) eq 'HASH'); | 
| 46 | 111 |  |  |  |  | 461 | return ([@_], \%defaults); | 
| 47 |  |  |  |  |  |  | } ## end sub args_array_with_options | 
| 48 |  |  |  |  |  |  |  | 
| 49 |  |  |  |  |  |  | sub assert_all_different { | 
| 50 | 24 | 50 | 33 | 24 | 1 | 127 | my $keys = (@_ && ref($_[0])) ? $_[0] : \@_; | 
| 51 | 24 |  |  |  |  | 51 | my %flag_for; | 
| 52 | 24 |  |  |  |  | 66 | for my $key (@$keys) { | 
| 53 | 75 | 50 |  |  |  | 218 | die {message => $key} if $flag_for{$key}++; | 
| 54 |  |  |  |  |  |  | } | 
| 55 | 24 |  |  |  |  | 102 | return 1; | 
| 56 |  |  |  |  |  |  | } ## end sub assert_all_different | 
| 57 |  |  |  |  |  |  |  | 
| 58 |  |  |  |  |  |  | sub _compile_capture { | 
| 59 | 9 |  |  | 9 |  | 32 | my %h = @_; | 
| 60 | 37 |  |  | 37 |  | 24136 | use feature 'state'; | 
|  | 37 |  |  |  |  | 95 |  | 
|  | 37 |  |  |  |  | 16542 |  | 
| 61 |  |  |  |  |  |  |  | 
| 62 | 9 |  |  |  |  | 17 | state $quoted = qr{(?mxs: | 
| 63 |  |  |  |  |  |  | (?: "(?: [^\\"]+ | \\. )*") # double quotes | 
| 64 |  |  |  |  |  |  | | (?: '[^']*')              # single quotes | 
| 65 |  |  |  |  |  |  | )}; | 
| 66 |  |  |  |  |  |  |  | 
| 67 |  |  |  |  |  |  | my ($key, $value, $kvs, $cs) = | 
| 68 | 9 |  |  |  |  | 25 | @h{qw< key value key_value_separator chunks_separator>}; | 
| 69 |  |  |  |  |  |  |  | 
| 70 | 9 | 50 |  |  |  | 20 | if (!defined($key)) { | 
| 71 | 9 |  |  |  |  | 15 | my $admitted = $h{key_admitted}; | 
| 72 | 9 | 50 |  |  |  | 18 | $admitted = qr{[\Q$admitted\E]} unless ref $admitted; | 
| 73 | 9 |  |  |  |  | 313 | $key = qr{(?mxs: $quoted | (?:(?:$admitted | \\.)+?))}; | 
| 74 |  |  |  |  |  |  | } | 
| 75 |  |  |  |  |  |  |  | 
| 76 | 9 | 50 |  |  |  | 33 | if (!defined($value)) { | 
| 77 | 9 |  |  |  |  | 14 | my $admitted = $h{value_admitted}; | 
| 78 | 9 | 50 |  |  |  | 18 | $admitted = qr{[\Q$admitted\E]} unless ref $admitted; | 
| 79 | 9 |  |  |  |  | 222 | $value = qr{(?mxs: $quoted | (?:(?:$admitted | \\.)+?))}; | 
| 80 |  |  |  |  |  |  | } | 
| 81 |  |  |  |  |  |  |  | 
| 82 | 9 |  |  |  |  | 57 | my $close = qr{(?<close>$h{close})}; | 
| 83 | 9 |  |  |  |  | 625 | return qr{(?mxs: | 
| 84 |  |  |  |  |  |  | (?: (?<key> $key) $kvs)?  # optional key with kv-separator | 
| 85 |  |  |  |  |  |  | (?<value> $value)         # a value, for sure | 
| 86 |  |  |  |  |  |  | (?: $close | $cs $close?) # close or chunk separator next | 
| 87 |  |  |  |  |  |  | )}; | 
| 88 |  |  |  |  |  |  | } ## end sub _compile_capture | 
| 89 |  |  |  |  |  |  |  | 
| 90 |  |  |  |  |  |  | sub generalized_hashy { | 
| 91 | 37 |  |  | 37 |  | 291 | use feature 'state'; | 
|  | 37 |  |  |  |  | 101 |  | 
|  | 37 |  |  |  |  | 78820 |  | 
| 92 | 12 |  |  | 12 | 1 | 3112 | state $admitted_default = qr{[^\\'":=\s,;\|/]}; | 
| 93 |  |  |  |  |  |  | state $kvdecoder        = sub { | 
| 94 | 37 |  |  | 37 |  | 108 | my $kv = shift; | 
| 95 | 37 |  |  |  |  | 65 | my $first = substr $kv, 0, 1; | 
| 96 | 37 | 100 | 100 |  |  | 123 | $kv = substr $kv, 1, length($kv) - 2 | 
| 97 |  |  |  |  |  |  | if ($first eq q{'}) || ($first eq q{"}); | 
| 98 | 37 | 100 |  |  |  | 91 | $kv =~ s{\\(.)}{$1}gmxs unless $first eq q{'}; | 
| 99 | 37 |  |  |  |  | 68 | return $kv; | 
| 100 | 12 |  |  |  |  | 32 | }; | 
| 101 |  |  |  |  |  |  | state $default_handler_for = { | 
| 102 |  |  |  |  |  |  | open                => qr{(?mxs: \s* )}, | 
| 103 |  |  |  |  |  |  | key_value_separator => qr{(?mxs: \s* [:=] \s*)}, | 
| 104 |  |  |  |  |  |  | chunks_separator    => qr{(?mxs: \s* [\s,;\|/] \s*)}, | 
| 105 |  |  |  |  |  |  | close               => qr{(?mxs: \s*\z)}, | 
| 106 |  |  |  |  |  |  | key_admitted        => $admitted_default, | 
| 107 |  |  |  |  |  |  | value_admitted      => $admitted_default, | 
| 108 |  |  |  |  |  |  | key_decoder         => $kvdecoder, | 
| 109 |  |  |  |  |  |  | value_decoder       => $kvdecoder, | 
| 110 |  |  |  |  |  |  | key_duplicate       => sub { | 
| 111 | 1 |  |  | 1 |  | 3 | my ($h, $k, $v) = @_; | 
| 112 | 1 | 50 |  |  |  | 6 | $h->{$k} = [$h->{$k}] unless ref $h->{$k}; | 
| 113 | 1 |  |  |  |  | 2 | push @{$h->{$k}}, $v; | 
|  | 1 |  |  |  |  | 3 |  | 
| 114 |  |  |  |  |  |  | }, | 
| 115 | 12 |  |  |  |  | 39 | }; | 
| 116 | 12 |  |  |  |  | 36 | my $args = normalize_args(@_, [$default_handler_for, 'text']); | 
| 117 |  |  |  |  |  |  | $args->{key_default} = delete $args->{default_key} | 
| 118 | 12 | 100 |  |  |  | 41 | if exists $args->{default_key}; | 
| 119 | 12 |  |  |  |  | 18 | my $text = $args->{text}; | 
| 120 |  |  |  |  |  |  |  | 
| 121 | 12 |  |  |  |  | 108 | my %h = (%$default_handler_for, %$args); | 
| 122 | 12 |  | 66 |  |  | 65 | my $capture = $h{capture} ||= _compile_capture(%h); | 
| 123 | 12 |  |  |  |  | 40 | my %retval = (capture => $capture); | 
| 124 | 12 | 100 |  |  |  | 46 | return {%retval, failure => 'undefined input'} unless defined $text; | 
| 125 |  |  |  |  |  |  |  | 
| 126 | 9 |  |  |  |  | 15 | my $len = length $text; | 
| 127 | 9 |  | 50 |  |  | 42 | pos($text) = my $startpos = $args->{pos} || 0; | 
| 128 | 9 |  |  |  |  | 36 | %retval = (%retval, pos => $startpos, res => ($len - $startpos)); | 
| 129 |  |  |  |  |  |  |  | 
| 130 |  |  |  |  |  |  | # let's check open first, no need to define anything otherwise | 
| 131 | 9 | 50 |  |  |  | 73 | $text =~ m{\G$h{open}}gmxs or return {%retval, failure => 'no opening'}; | 
| 132 |  |  |  |  |  |  |  | 
| 133 |  |  |  |  |  |  | my ($dkey, $dupkey, $kdec, $vdec) = | 
| 134 | 9 |  |  |  |  | 27 | @h{qw< key_default key_duplicate key_decoder value_decoder >}; | 
| 135 | 9 |  |  |  |  | 14 | my ($closed, %hash); | 
| 136 | 9 |  | 66 |  |  | 33 | while (!$closed && pos($text) < length($text)) { | 
| 137 | 23 |  |  |  |  | 31 | my $pos = pos($text); | 
| 138 | 23 | 100 |  |  |  | 802 | $text =~ m{\G$capture}gcmxs | 
| 139 |  |  |  |  |  |  | or return { | 
| 140 |  |  |  |  |  |  | %retval, | 
| 141 |  |  |  |  |  |  | failure => "failed match at $pos", | 
| 142 |  |  |  |  |  |  | failpos => $pos | 
| 143 |  |  |  |  |  |  | }; | 
| 144 |  |  |  |  |  |  |  | 
| 145 |  |  |  |  |  |  | my $key = | 
| 146 |  |  |  |  |  |  | exists($+{key}) ? ($kdec      ? $kdec->($+{key}) : $+{key}) | 
| 147 | 22 | 50 |  |  |  | 195 | : defined($dkey)  ? (ref($dkey) ? $dkey->()        : $dkey) | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
| 148 |  |  |  |  |  |  | :                   undef; | 
| 149 |  |  |  |  |  |  | return { | 
| 150 | 22 | 100 |  |  |  | 59 | %retval, | 
| 151 |  |  |  |  |  |  | failure => 'stand-alone value, no default key set', | 
| 152 |  |  |  |  |  |  | failpos => $pos | 
| 153 |  |  |  |  |  |  | } | 
| 154 |  |  |  |  |  |  | unless defined $key; | 
| 155 |  |  |  |  |  |  |  | 
| 156 | 21 | 50 |  |  |  | 54 | my $value = $vdec ? $vdec->($+{value}) : $+{value}; | 
| 157 |  |  |  |  |  |  |  | 
| 158 | 21 | 100 |  |  |  | 67 | if (!exists $hash{$key}) { | 
|  |  | 50 |  |  |  |  |  | 
| 159 | 19 |  |  |  |  | 44 | $hash{$key} = $value; | 
| 160 |  |  |  |  |  |  | } | 
| 161 |  |  |  |  |  |  | elsif ($dupkey) { | 
| 162 | 2 |  |  |  |  | 6 | $dupkey->(\%hash, $key, $value); | 
| 163 |  |  |  |  |  |  | } | 
| 164 |  |  |  |  |  |  | else { | 
| 165 |  |  |  |  |  |  | return { | 
| 166 | 0 |  |  |  |  | 0 | %retval, | 
| 167 |  |  |  |  |  |  | failure => "duplicate key $key", | 
| 168 |  |  |  |  |  |  | failpos => $pos | 
| 169 |  |  |  |  |  |  | }; | 
| 170 |  |  |  |  |  |  | } ## end else [ if (!exists $hash{$key...})] | 
| 171 |  |  |  |  |  |  |  | 
| 172 | 20 |  |  |  |  | 111 | $closed = exists $+{close}; | 
| 173 |  |  |  |  |  |  | } ## end while (!$closed && pos($text...)) | 
| 174 |  |  |  |  |  |  |  | 
| 175 | 6 | 50 |  |  |  | 14 | return {%retval, failure => 'no closure found'} unless $closed; | 
| 176 |  |  |  |  |  |  |  | 
| 177 | 6 |  |  |  |  | 12 | my $pos = pos $text; | 
| 178 |  |  |  |  |  |  | return { | 
| 179 | 6 |  |  |  |  | 58 | %retval, | 
| 180 |  |  |  |  |  |  | pos  => $pos, | 
| 181 |  |  |  |  |  |  | res  => ($len - $pos), | 
| 182 |  |  |  |  |  |  | hash => \%hash, | 
| 183 |  |  |  |  |  |  | }; | 
| 184 |  |  |  |  |  |  | } ## end sub generalized_hashy | 
| 185 |  |  |  |  |  |  |  | 
| 186 |  |  |  |  |  |  | sub load_module { | 
| 187 | 7 |  |  | 7 | 1 | 32 | return _load_module(resolve_module(@_)); | 
| 188 |  |  |  |  |  |  | } ## end sub load_module | 
| 189 |  |  |  |  |  |  |  | 
| 190 |  |  |  |  |  |  | sub load_sub { | 
| 191 | 18 |  |  | 18 | 1 | 41 | my ($locator, $prefix) = @_; | 
| 192 | 18 | 50 |  |  |  | 164 | my ($module, $sub) = | 
| 193 |  |  |  |  |  |  | ref($locator) ? @$locator : $locator =~ m{\A(.*)::(\w+)\z}mxs; | 
| 194 | 18 |  |  |  |  | 67 | $module = resolve_module($module, $prefix); | 
| 195 |  |  |  |  |  |  |  | 
| 196 |  |  |  |  |  |  | # optimistic first | 
| 197 | 18 |  | 66 |  |  | 228 | return $module->can($sub) // _load_module($module)->can($sub); | 
| 198 |  |  |  |  |  |  | } ## end sub load_sub | 
| 199 |  |  |  |  |  |  |  | 
| 200 |  |  |  |  |  |  | sub metadata { | 
| 201 | 3 |  |  | 3 | 1 | 7 | my $input = shift; | 
| 202 | 3 |  |  |  |  | 23 | my %args  = normalize_args( | 
| 203 |  |  |  |  |  |  | @_, | 
| 204 |  |  |  |  |  |  | { | 
| 205 |  |  |  |  |  |  | chunks_separator    => ' ', | 
| 206 |  |  |  |  |  |  | key_value_separator => '=', | 
| 207 |  |  |  |  |  |  | default_key         => '', | 
| 208 |  |  |  |  |  |  | } | 
| 209 |  |  |  |  |  |  | ); | 
| 210 |  |  |  |  |  |  |  | 
| 211 |  |  |  |  |  |  | # split data into chunks, un-escape on the fly | 
| 212 | 3 |  |  |  |  | 11 | my $separator = $args{chunks_separator}; | 
| 213 | 3 |  |  |  |  | 8 | my $qs        = quotemeta($separator); | 
| 214 | 3 |  |  |  |  | 88 | my $regexp    = qr/((?:\\.|[^\\$qs])+)(?:$qs+)?/; | 
| 215 | 3 |  |  |  |  | 47 | my @chunks    = map { s{\\(.)}{$1}g; $_ } $input =~ m{$regexp}gc; | 
|  | 7 |  |  |  |  | 17 |  | 
|  | 7 |  |  |  |  | 20 |  | 
| 216 |  |  |  |  |  |  |  | 
| 217 |  |  |  |  |  |  | # ensure we consumed the whole $input | 
| 218 | 3 | 50 |  |  |  | 16 | die {message => | 
| 219 |  |  |  |  |  |  | "invalid metadata (separator: '$separator', input: [$input])\n" | 
| 220 |  |  |  |  |  |  | } | 
| 221 |  |  |  |  |  |  | if pos($input) < length($input); | 
| 222 |  |  |  |  |  |  |  | 
| 223 | 3 |  |  |  |  | 12 | $separator = $args{key_value_separator}; | 
| 224 |  |  |  |  |  |  | return { | 
| 225 |  |  |  |  |  |  | map { | 
| 226 | 3 |  |  |  |  | 8 | my ($k, $v) = _split_pair($_, $separator); | 
|  | 7 |  |  |  |  | 22 |  | 
| 227 | 7 | 100 |  |  |  | 45 | defined($v) ? ($k, $v) : ($args{default_key} => $k); | 
| 228 |  |  |  |  |  |  | } @chunks | 
| 229 |  |  |  |  |  |  | }; | 
| 230 |  |  |  |  |  |  | } ## end sub metadata | 
| 231 |  |  |  |  |  |  |  | 
| 232 |  |  |  |  |  |  | sub normalize_args { | 
| 233 | 225 |  |  | 225 | 1 | 3777 | my $defaults = pop(@_); | 
| 234 |  |  |  |  |  |  |  | 
| 235 | 225 |  |  |  |  | 374 | my %retval; | 
| 236 | 225 | 100 |  |  |  | 758 | if (ref($defaults) eq 'ARRAY') { | 
| 237 | 184 |  |  |  |  | 419 | ($defaults, my $key) = @$defaults; | 
| 238 | 184 | 100 | 66 |  |  | 868 | $retval{$key} = shift(@_) | 
| 239 |  |  |  |  |  |  | if (scalar(@_) % 2) && (ref($_[0]) ne 'HASH'); | 
| 240 |  |  |  |  |  |  | } | 
| 241 |  |  |  |  |  |  | %retval = ( | 
| 242 |  |  |  |  |  |  | %$defaults,    # defaults go first | 
| 243 |  |  |  |  |  |  | %retval,       # anything already present goes next | 
| 244 | 225 | 100 | 100 |  |  | 2081 | ((@_ && ref($_[0]) eq 'HASH') ? %{$_[0]} : @_),    # then... the rest | 
|  | 10 |  |  |  |  | 35 |  | 
| 245 |  |  |  |  |  |  | ); | 
| 246 |  |  |  |  |  |  |  | 
| 247 | 225 | 100 |  |  |  | 1461 | return %retval if wantarray(); | 
| 248 | 33 |  |  |  |  | 79 | return \%retval; | 
| 249 |  |  |  |  |  |  | } ## end sub normalize_args | 
| 250 |  |  |  |  |  |  |  | 
| 251 |  |  |  |  |  |  | sub normalize_filename { | 
| 252 | 25 |  |  | 25 | 1 | 59 | my ($filename, $default_handle) = @_; | 
| 253 | 25 | 100 |  |  |  | 85 | return unless defined $filename; | 
| 254 | 24 | 100 |  |  |  | 77 | return $filename       if ref($filename) eq 'GLOB'; | 
| 255 | 23 | 100 |  |  |  | 69 | return $filename       if ref($filename) eq 'SCALAR'; | 
| 256 | 14 | 50 |  |  |  | 33 | return $default_handle if $filename eq '-'; | 
| 257 | 14 | 50 |  |  |  | 50 | return $filename       if $filename =~ s{\Afile:}{}mxs; | 
| 258 | 14 | 50 |  |  |  | 57 | if (my ($handlename) = $filename =~ m{\Ahandle:(?:std)?(.*)\z}imxs) { | 
| 259 | 0 |  |  |  |  | 0 | $handlename = lc $handlename; | 
| 260 | 0 | 0 |  |  |  | 0 | return \*STDOUT if $handlename eq 'out'; | 
| 261 | 0 | 0 |  |  |  | 0 | return \*STDIN  if $handlename eq 'err'; | 
| 262 | 0 | 0 |  |  |  | 0 | return \*STDERR if $handlename eq 'in'; | 
| 263 | 0 |  |  |  |  | 0 | LOGDIE "normalize_filename: invalid filename '$filename', " | 
| 264 |  |  |  |  |  |  | . "use 'file:$filename' if name is correct"; | 
| 265 |  |  |  |  |  |  | } ## end if (my ($handlename) =...) | 
| 266 | 14 |  |  |  |  | 42 | return $filename; | 
| 267 |  |  |  |  |  |  | } ## end sub normalize_filename | 
| 268 |  |  |  |  |  |  |  | 
| 269 |  |  |  |  |  |  | sub pump { | 
| 270 | 13 |  |  | 13 | 1 | 30 | my ($iterator, $sink) = @_; | 
| 271 | 13 | 50 |  |  |  | 37 | if ($sink) { | 
| 272 | 0 |  |  |  |  | 0 | while (my @items = $iterator->()) { | 
| 273 | 0 |  |  |  |  | 0 | $sink->(@items); | 
| 274 |  |  |  |  |  |  | } | 
| 275 | 0 |  |  |  |  | 0 | return; | 
| 276 |  |  |  |  |  |  | } | 
| 277 | 13 |  |  |  |  | 27 | my $wa = wantarray(); | 
| 278 | 13 | 50 |  |  |  | 35 | if (! defined $wa) { | 
| 279 | 0 |  |  |  |  | 0 | while (my @items = $iterator->()) {} | 
| 280 | 0 |  |  |  |  | 0 | return; | 
| 281 |  |  |  |  |  |  | } | 
| 282 | 13 |  |  |  |  | 21 | my @records; | 
| 283 | 13 |  |  |  |  | 35 | while (my @items = $iterator->()) { | 
| 284 | 19 |  |  |  |  | 126 | push @records, @items; | 
| 285 |  |  |  |  |  |  | } | 
| 286 | 13 | 100 |  |  |  | 99 | return $wa ? @records : \@records; | 
| 287 |  |  |  |  |  |  | } | 
| 288 |  |  |  |  |  |  |  | 
| 289 |  |  |  |  |  |  | sub read_file { | 
| 290 | 16 |  |  | 16 | 1 | 7748 | my %args = normalize_args( | 
| 291 |  |  |  |  |  |  | @_, | 
| 292 |  |  |  |  |  |  | [ | 
| 293 |  |  |  |  |  |  | {binmode => ':encoding(UTF-8)'}, | 
| 294 |  |  |  |  |  |  | 'filename',    # default key for "straight" unnamed parameter | 
| 295 |  |  |  |  |  |  | ] | 
| 296 |  |  |  |  |  |  | ); | 
| 297 | 16 | 100 |  |  |  | 76 | defined(my $filename = normalize_filename($args{filename}, \*STDIN)) | 
| 298 |  |  |  |  |  |  | or LOGDIE 'read_file(): undefined filename'; | 
| 299 |  |  |  |  |  |  |  | 
| 300 | 15 |  |  |  |  | 26 | my $fh; | 
| 301 | 15 | 50 |  |  |  | 37 | if (ref($filename) eq 'GLOB') { | 
| 302 | 0 |  |  |  |  | 0 | $fh = $filename; | 
| 303 |  |  |  |  |  |  | } | 
| 304 |  |  |  |  |  |  | else { | 
| 305 | 15 | 100 |  |  |  | 547 | open $fh, '<', $filename | 
| 306 |  |  |  |  |  |  | or LOGDIE "read_file() for <$args{filename}>: open(): $OS_ERROR"; | 
| 307 |  |  |  |  |  |  | } | 
| 308 |  |  |  |  |  |  |  | 
| 309 | 14 | 50 |  |  |  | 63 | if (defined $args{binmode}) { | 
| 310 |  |  |  |  |  |  | binmode $fh, $args{binmode} | 
| 311 | 14 | 100 |  | 1 |  | 215 | or LOGDIE "read_file(): binmode()" | 
|  | 1 |  |  | 1 |  | 7 |  | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 6 |  | 
|  | 1 |  |  |  |  | 7 |  | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 7 |  | 
| 312 |  |  |  |  |  |  | . " for $args{filename} failed: $OS_ERROR"; | 
| 313 |  |  |  |  |  |  | } | 
| 314 |  |  |  |  |  |  |  | 
| 315 | 13 |  |  |  |  | 1845 | local $INPUT_RECORD_SEPARATOR; | 
| 316 | 13 |  |  |  |  | 487 | return <$fh>; | 
| 317 |  |  |  |  |  |  | } ## end sub read_file | 
| 318 |  |  |  |  |  |  |  | 
| 319 |  |  |  |  |  |  | sub read_file_maybe { | 
| 320 | 52 |  |  | 52 | 1 | 98 | my $x = shift; | 
| 321 | 52 | 100 |  |  |  | 160 | return read_file(@$x) if ref($x) eq 'ARRAY'; | 
| 322 | 42 |  |  |  |  | 105 | return $x; | 
| 323 |  |  |  |  |  |  | } | 
| 324 |  |  |  |  |  |  |  | 
| 325 |  |  |  |  |  |  | sub resolve_module { | 
| 326 | 68 |  |  | 68 | 1 | 200 | my ($module, $prefix) = @_; | 
| 327 |  |  |  |  |  |  |  | 
| 328 |  |  |  |  |  |  | # Force a first character transforming from new interface if after 0.734 | 
| 329 | 68 | 100 |  |  |  | 268 | if ($Data::Tubes::API_VERSION gt '0.734') { | 
| 330 | 64 | 100 |  |  |  | 340 | $module = '+' . $module unless $module =~ s{^[+^]}{!}mxs; | 
| 331 |  |  |  |  |  |  | } | 
| 332 |  |  |  |  |  |  |  | 
| 333 | 68 |  |  |  |  | 277 | my ($first) = substr $module, 0, 1; | 
| 334 | 68 | 100 |  |  |  | 253 | return substr $module, 1 if $first eq '!'; | 
| 335 |  |  |  |  |  |  |  | 
| 336 | 51 |  | 100 |  |  | 228 | $prefix //= 'Data::Tubes::Plugin'; | 
| 337 | 51 | 100 |  |  |  | 172 | if ($first eq '+') { | 
|  |  | 100 |  |  |  |  |  | 
| 338 | 49 |  |  |  |  | 130 | $module = substr $module, 1; | 
| 339 |  |  |  |  |  |  | } | 
| 340 |  |  |  |  |  |  | elsif ($module =~ m{::}mxs) { | 
| 341 | 1 |  |  |  |  | 3 | $prefix = undef; | 
| 342 |  |  |  |  |  |  | } | 
| 343 | 51 | 100 |  |  |  | 149 | return $module unless defined $prefix; | 
| 344 | 50 |  |  |  |  | 214 | return $prefix . '::' . $module; | 
| 345 |  |  |  |  |  |  | } | 
| 346 |  |  |  |  |  |  |  | 
| 347 |  |  |  |  |  |  | sub shorter_sub_names { | 
| 348 | 33 |  |  | 33 | 1 | 166 | my $stash = shift(@_) . '::'; | 
| 349 |  |  |  |  |  |  |  | 
| 350 | 37 |  |  | 37 |  | 408 | no strict 'refs'; | 
|  | 37 |  |  |  |  | 84 |  | 
|  | 37 |  |  |  |  | 35171 |  | 
| 351 |  |  |  |  |  |  |  | 
| 352 |  |  |  |  |  |  | # isolate all subs | 
| 353 |  |  |  |  |  |  | my %sub_for = | 
| 354 | 33 | 100 |  |  |  | 658 | map { *{$stash . $_}{CODE} ? ($_ => *{$stash . $_}{CODE}) : (); } | 
|  | 2944 |  |  |  |  | 3397 |  | 
|  | 2944 |  |  |  |  | 7307 |  | 
|  | 983 |  |  |  |  | 2382 |  | 
| 355 |  |  |  |  |  |  | keys %$stash; | 
| 356 |  |  |  |  |  |  |  | 
| 357 |  |  |  |  |  |  | # iterate through inputs, work only on isolated subs and don't | 
| 358 |  |  |  |  |  |  | # consider shortened ones | 
| 359 | 33 |  |  |  |  | 263 | for my $prefix (@_) { | 
| 360 | 33 |  |  |  |  | 242 | while (my ($name, $sub) = each %sub_for) { | 
| 361 | 983 | 100 |  |  |  | 2629 | next if index($name, $prefix) < 0; | 
| 362 | 188 |  |  |  |  | 384 | my $shortname = substr $name, length($prefix); | 
| 363 | 188 |  |  |  |  | 252 | *{$stash . $shortname} = $sub; | 
|  | 188 |  |  |  |  | 1065 |  | 
| 364 |  |  |  |  |  |  | } | 
| 365 |  |  |  |  |  |  | } ## end for my $prefix (@_) | 
| 366 |  |  |  |  |  |  |  | 
| 367 | 33 |  |  |  |  | 194 | return; | 
| 368 |  |  |  |  |  |  | } ## end sub shorter_sub_names | 
| 369 |  |  |  |  |  |  |  | 
| 370 |  |  |  |  |  |  | sub _split_pair { | 
| 371 | 7 |  |  | 7 |  | 17 | my ($input, $separator) = @_; | 
| 372 | 7 |  |  |  |  | 12 | my $qs     = quotemeta($separator); | 
| 373 | 7 |  |  |  |  | 76 | my $regexp = qr{(?mxs:\A((?:\\.|[^\\$qs])+)$qs(.*)\z)}; | 
| 374 | 7 |  |  |  |  | 59 | my ($first, $second) = $input =~ m{$regexp}; | 
| 375 | 7 | 100 |  |  |  | 26 | ($first, $second) = ($input, undef) unless defined($first); | 
| 376 | 7 |  |  |  |  | 15 | $first =~ s{\\(.)}{$1}gmxs;    # unescape metadata | 
| 377 | 7 |  |  |  |  | 28 | return ($first, $second); | 
| 378 |  |  |  |  |  |  | } ## end sub _split_pair | 
| 379 |  |  |  |  |  |  |  | 
| 380 |  |  |  |  |  |  | sub sprintffy { | 
| 381 | 34 |  |  | 34 | 1 | 73 | my ($template, $substitutions) = @_; | 
| 382 | 34 |  |  |  |  | 68 | my $len = length $template; | 
| 383 | 34 |  |  |  |  | 92 | pos($template) = 0;            # initialize | 
| 384 | 34 |  |  |  |  | 71 | my @chunks; | 
| 385 |  |  |  |  |  |  | QUEST: | 
| 386 | 34 |  |  |  |  | 79 | while (pos($template) < $len) { | 
| 387 | 65 |  |  |  |  | 384 | $template =~ m{\G (.*?) (% | \z)}mxscg; | 
| 388 | 65 |  |  |  |  | 207 | my ($plain, $term) = ($1, $2); | 
| 389 | 65 |  |  |  |  | 121 | my $pos = pos($template); | 
| 390 | 65 |  |  |  |  | 108 | push @chunks, $plain; | 
| 391 | 65 | 100 |  |  |  | 145 | last unless $term;          # got a percent, have to continue | 
| 392 |  |  |  |  |  |  | CANDIDATE: | 
| 393 | 31 |  |  |  |  | 143 | for my $candidate ([qr{%} => '%'], @$substitutions) { | 
| 394 | 62 |  |  |  |  | 137 | my ($regex, $value) = @$candidate; | 
| 395 | 62 | 100 |  |  |  | 1267 | $template =~ m{\G$regex}cg or next CANDIDATE; | 
| 396 | 31 | 50 |  |  |  | 156 | $value = $value->() if ref($value) eq 'CODE'; | 
| 397 | 31 |  |  |  |  | 70 | push @chunks, $value; | 
| 398 | 31 |  |  |  |  | 132 | next QUEST; | 
| 399 |  |  |  |  |  |  | } ## end CANDIDATE: for my $candidate ([qr{%}...]) | 
| 400 |  |  |  |  |  |  |  | 
| 401 |  |  |  |  |  |  | # didn't find a matchin thing... time to complain | 
| 402 | 0 |  |  |  |  | 0 | die {message => "invalid sprintffy template '$template'"}; | 
| 403 |  |  |  |  |  |  | } ## end QUEST: while (pos($template) < $len) | 
| 404 | 34 |  |  |  |  | 143 | return join '', @chunks; | 
| 405 |  |  |  |  |  |  | } ## end sub sprintffy | 
| 406 |  |  |  |  |  |  |  | 
| 407 |  |  |  |  |  |  | sub test_all_equal { | 
| 408 | 17 |  |  | 17 | 1 | 39 | my $reference = shift; | 
| 409 | 17 |  |  |  |  | 39 | for my $candidate (@_) { | 
| 410 | 16 | 100 |  |  |  | 55 | return if $candidate ne $reference; | 
| 411 |  |  |  |  |  |  | } | 
| 412 | 13 |  |  |  |  | 63 | return 1; | 
| 413 |  |  |  |  |  |  | } ## end sub test_all_equal | 
| 414 |  |  |  |  |  |  |  | 
| 415 |  |  |  |  |  |  | sub traverse { | 
| 416 | 38 |  |  | 38 | 1 | 107 | my ($data, @keys) = @_; | 
| 417 | 38 |  |  |  |  | 91 | for my $key (@keys) { | 
| 418 | 58 | 50 |  |  |  | 168 | if (ref($data) eq 'HASH') { | 
|  |  | 0 |  |  |  |  |  | 
| 419 | 58 |  |  |  |  | 116 | $data = $data->{$key}; | 
| 420 |  |  |  |  |  |  | } | 
| 421 |  |  |  |  |  |  | elsif (ref($data) eq 'ARRAY') { | 
| 422 | 0 |  |  |  |  | 0 | $data = $data->[$key]; | 
| 423 |  |  |  |  |  |  | } | 
| 424 |  |  |  |  |  |  | else { | 
| 425 | 0 |  |  |  |  | 0 | return undef; | 
| 426 |  |  |  |  |  |  | } | 
| 427 | 58 | 50 |  |  |  | 143 | return undef unless defined $data; | 
| 428 |  |  |  |  |  |  | } ## end for my $key (@keys) | 
| 429 | 38 |  |  |  |  | 122 | return $data; | 
| 430 |  |  |  |  |  |  | } ## end sub traverse | 
| 431 |  |  |  |  |  |  |  | 
| 432 |  |  |  |  |  |  | sub trim { | 
| 433 | 15 |  |  | 15 | 1 | 199 | s{\A\s+|\s+\z}{}gmxs for @_; | 
| 434 |  |  |  |  |  |  | } | 
| 435 |  |  |  |  |  |  |  | 
| 436 |  |  |  |  |  |  | sub tube { | 
| 437 | 18 |  |  | 18 | 1 | 38 | my $opts = {}; | 
| 438 | 18 | 100 | 66 |  |  | 107 | $opts = shift(@_) if (@_ && ref($_[0]) eq 'HASH'); | 
| 439 | 18 | 50 |  |  |  | 63 | my @prefix = exists($opts->{prefix}) ? ($opts->{prefix}) : (); | 
| 440 | 18 |  |  |  |  | 38 | my $locator = shift; | 
| 441 | 18 |  |  |  |  | 66 | return load_sub($locator, @prefix)->(@_); | 
| 442 |  |  |  |  |  |  | } | 
| 443 |  |  |  |  |  |  |  | 
| 444 |  |  |  |  |  |  | sub unzip { | 
| 445 | 24 | 50 | 33 | 24 | 1 | 158 | my $items = (@_ && ref($_[0])) ? $_[0] : \@_; | 
| 446 | 24 |  |  |  |  | 58 | my $n_items = scalar @$items; | 
| 447 | 24 |  |  |  |  | 45 | my (@evens, @odds); | 
| 448 | 24 |  |  |  |  | 35 | my $i = 0; | 
| 449 | 24 |  |  |  |  | 67 | while ($i < $n_items) { | 
| 450 | 75 |  |  |  |  | 156 | push @evens, $items->[$i++]; | 
| 451 | 75 | 100 |  |  |  | 223 | push @odds, $items->[$i++] if $i < $n_items; | 
| 452 |  |  |  |  |  |  | } | 
| 453 | 24 |  |  |  |  | 90 | return (\@evens, \@odds); | 
| 454 |  |  |  |  |  |  | } ## end sub unzip | 
| 455 |  |  |  |  |  |  |  | 
| 456 |  |  |  |  |  |  | 1; |