blib/lib/Mojo/Util.pm | |||
---|---|---|---|
Criterion | Covered | Total | % |
statement | 343 | 343 | 100.0 |
branch | 126 | 132 | 95.4 |
condition | 46 | 52 | 88.4 |
subroutine | 78 | 78 | 100.0 |
pod | 36 | 36 | 100.0 |
total | 629 | 641 | 98.1 |
line | stmt | bran | cond | sub | pod | time | code |
---|---|---|---|---|---|---|---|
1 | package Mojo::Util; | ||||||
2 | 101 | 101 | 721 | use Mojo::Base -strict; | |||
101 | 191 | ||||||
101 | 4093 | ||||||
3 | |||||||
4 | 101 | 101 | 597 | use Carp qw(carp croak); | |||
101 | 198 | ||||||
101 | 8369 | ||||||
5 | 101 | 101 | 63717 | use Data::Dumper (); | |||
101 | 684326 | ||||||
101 | 3311 | ||||||
6 | 101 | 101 | 700 | use Digest::MD5 qw(md5 md5_hex); | |||
101 | 223 | ||||||
101 | 7160 | ||||||
7 | 101 | 101 | 55469 | use Digest::SHA qw(hmac_sha1_hex sha1 sha1_hex); | |||
101 | 313638 | ||||||
101 | 9573 | ||||||
8 | 101 | 101 | 58089 | use Encode qw(find_encoding); | |||
101 | 983654 | ||||||
101 | 7665 | ||||||
9 | 101 | 101 | 748 | use Exporter qw(import); | |||
101 | 206 | ||||||
101 | 3217 | ||||||
10 | 101 | 101 | 1883 | use File::Basename qw(dirname); | |||
101 | 222 | ||||||
101 | 10647 | ||||||
11 | 101 | 101 | 76416 | use Getopt::Long qw(GetOptionsFromArray); | |||
101 | 1272920 | ||||||
101 | 560 | ||||||
12 | 101 | 101 | 77709 | use IO::Compress::Gzip; | |||
101 | 4219255 | ||||||
101 | 6254 | ||||||
13 | 101 | 101 | 48764 | use IO::Poll qw(POLLIN POLLPRI); | |||
101 | 85754 | ||||||
101 | 7550 | ||||||
14 | 101 | 101 | 57610 | use IO::Uncompress::Gunzip; | |||
101 | 1515356 | ||||||
101 | 5857 | ||||||
15 | 101 | 101 | 793 | use List::Util qw(min); | |||
101 | 240 | ||||||
101 | 11517 | ||||||
16 | 101 | 101 | 47682 | use MIME::Base64 qw(decode_base64 encode_base64); | |||
101 | 65983 | ||||||
101 | 6942 | ||||||
17 | 101 | 101 | 57349 | use Pod::Usage qw(pod2usage); | |||
101 | 3831983 | ||||||
101 | 8867 | ||||||
18 | 101 | 101 | 62432 | use Socket qw(inet_pton AF_INET6 AF_INET); | |||
101 | 388794 | ||||||
101 | 18616 | ||||||
19 | 101 | 101 | 44079 | use Sub::Util qw(set_subname); | |||
101 | 32349 | ||||||
101 | 6381 | ||||||
20 | 101 | 101 | 778 | use Symbol qw(delete_package); | |||
101 | 262 | ||||||
101 | 5108 | ||||||
21 | 101 | 101 | 51275 | use Time::HiRes (); | |||
101 | 132467 | ||||||
101 | 2680 | ||||||
22 | 101 | 101 | 58029 | use Unicode::Normalize (); | |||
101 | 207804 | ||||||
101 | 7064 | ||||||
23 | |||||||
24 | # Check for monotonic clock support | ||||||
25 | 101 | 101 | 852 | use constant MONOTONIC => !!eval { Time::HiRes::clock_gettime(Time::HiRes::CLOCK_MONOTONIC()) }; | |||
101 | 254 | ||||||
101 | 232 | ||||||
101 | 661 | ||||||
26 | |||||||
27 | # Punycode bootstring parameters | ||||||
28 | use constant { | ||||||
29 | 101 | 240525 | PC_BASE => 36, | ||||
30 | PC_TMIN => 1, | ||||||
31 | PC_TMAX => 26, | ||||||
32 | PC_SKEW => 38, | ||||||
33 | PC_DAMP => 700, | ||||||
34 | PC_INITIAL_BIAS => 72, | ||||||
35 | PC_INITIAL_N => 128 | ||||||
36 | 101 | 101 | 13080 | }; | |||
101 | 291 | ||||||
37 | |||||||
38 | # To generate a new HTML entity table run this command | ||||||
39 | # perl examples/entities.pl > lib/Mojo/resources/html_entities.txt | ||||||
40 | my %ENTITIES; | ||||||
41 | { | ||||||
42 | # Don't use Mojo::File here due to circular dependencies | ||||||
43 | my $path = File::Spec->catfile(dirname(__FILE__), 'resources', 'html_entities.txt'); | ||||||
44 | |||||||
45 | open my $file, '<', $path or croak "Unable to open html entities file ($path): $!"; | ||||||
46 | my $lines = do { local $/; <$file> }; | ||||||
47 | |||||||
48 | for my $line (split /\n/, $lines) { | ||||||
49 | next unless $line =~ /^(\S+)\s+U\+(\S+)(?:\s+U\+(\S+))?/; | ||||||
50 | $ENTITIES{$1} = defined $3 ? (chr(hex $2) . chr(hex $3)) : chr(hex $2); | ||||||
51 | } | ||||||
52 | } | ||||||
53 | |||||||
54 | # Characters that should be escaped in XML | ||||||
55 | my %XML = ('&' => '&', '<' => '<', '>' => '>', '"' => '"', '\'' => '''); | ||||||
56 | |||||||
57 | # "Sun, 06 Nov 1994 08:49:37 GMT" and "Sunday, 06-Nov-94 08:49:37 GMT" | ||||||
58 | my $EXPIRES_RE = qr/(\w+\W+\d+\W+\w+\W+\d+\W+\d+:\d+:\d+\W*\w+)/; | ||||||
59 | |||||||
60 | # Header key/value pairs | ||||||
61 | my $QUOTED_VALUE_RE = qr/\G=\s*("(?:\\\\|\\"|[^"])*")/; | ||||||
62 | my $UNQUOTED_VALUE_RE = qr/\G=\s*([^;, ]*)/; | ||||||
63 | |||||||
64 | # HTML entities | ||||||
65 | my $ENTITY_RE = qr/&(?:\#((?:[0-9]{1,7}|x[0-9a-fA-F]{1,6}));|(\w+[;=]?))/; | ||||||
66 | |||||||
67 | # Encoding and pattern cache | ||||||
68 | my (%ENCODING, %PATTERN); | ||||||
69 | |||||||
70 | our @EXPORT_OK = ( | ||||||
71 | qw(b64_decode b64_encode camelize class_to_file class_to_path decamelize decode deprecated dumper encode), | ||||||
72 | qw(extract_usage getopt gunzip gzip header_params hmac_sha1_sum html_attr_unescape html_unescape humanize_bytes), | ||||||
73 | qw(md5_bytes md5_sum monkey_patch network_contains punycode_decode punycode_encode quote scope_guard secure_compare), | ||||||
74 | qw(sha1_bytes sha1_sum slugify split_cookie_header split_header steady_time tablify term_escape trim unindent), | ||||||
75 | qw(unquote url_escape url_unescape xml_escape xor_encode) | ||||||
76 | ); | ||||||
77 | |||||||
78 | # Aliases | ||||||
79 | monkey_patch(__PACKAGE__, 'b64_decode', \&decode_base64); | ||||||
80 | monkey_patch(__PACKAGE__, 'b64_encode', \&encode_base64); | ||||||
81 | monkey_patch(__PACKAGE__, 'hmac_sha1_sum', \&hmac_sha1_hex); | ||||||
82 | monkey_patch(__PACKAGE__, 'md5_bytes', \&md5); | ||||||
83 | monkey_patch(__PACKAGE__, 'md5_sum', \&md5_hex); | ||||||
84 | monkey_patch(__PACKAGE__, 'sha1_bytes', \&sha1); | ||||||
85 | monkey_patch(__PACKAGE__, 'sha1_sum', \&sha1_hex); | ||||||
86 | |||||||
87 | # Use a monotonic clock if possible | ||||||
88 | monkey_patch(__PACKAGE__, 'steady_time', | ||||||
89 | 119189 | 119189 | 1 | 223943 | MONOTONIC ? sub () { Time::HiRes::clock_gettime(Time::HiRes::CLOCK_MONOTONIC()) } : \&Time::HiRes::time); | ||
90 | |||||||
91 | sub camelize { | ||||||
92 | 42 | 42 | 1 | 1786 | my $str = shift; | ||
93 | 42 | 100 | 206 | return $str if $str =~ /^[A-Z]/; | |||
94 | |||||||
95 | # CamelCase words | ||||||
96 | return join '::', map { | ||||||
97 | 39 | 153 | join('', map { ucfirst lc } split /_/) | ||||
53 | 153 | ||||||
81 | 413 | ||||||
98 | } split /-/, $str; | ||||||
99 | } | ||||||
100 | |||||||
101 | sub class_to_file { | ||||||
102 | 10 | 10 | 1 | 2882 | my $class = shift; | ||
103 | 10 | 59 | $class =~ s/::|'//g; | ||||
104 | 10 | 51 | $class =~ s/([A-Z])([A-Z]*)/$1 . lc $2/ge; | ||||
18 | 77 | ||||||
105 | 10 | 29 | return decamelize($class); | ||||
106 | } | ||||||
107 | |||||||
108 | 836 | 836 | 1 | 308681 | sub class_to_path { join '.', join('/', split(/::|'/, shift)), 'pm' } | ||
109 | |||||||
110 | sub decamelize { | ||||||
111 | 28 | 28 | 1 | 3516 | my $str = shift; | ||
112 | 28 | 100 | 173 | return $str if $str !~ /^[A-Z]/; | |||
113 | |||||||
114 | # snake_case words | ||||||
115 | return join '-', map { | ||||||
116 | 23 | 103 | join('_', map {lc} grep {length} split /([A-Z]{1}[^A-Z]*)/) | ||||
26 | 147 | ||||||
49 | 278 | ||||||
98 | 165 | ||||||
117 | } split /::/, $str; | ||||||
118 | } | ||||||
119 | |||||||
120 | sub decode { | ||||||
121 | 7332 | 7332 | 1 | 26860 | my ($encoding, $bytes) = @_; | ||
122 | 7332 | 100 | 11094 | return undef unless eval { $bytes = _encoding($encoding)->decode("$bytes", 1); 1 }; | |||
7332 | 16514 | ||||||
7247 | 70748 | ||||||
123 | 7247 | 23763 | return $bytes; | ||||
124 | } | ||||||
125 | |||||||
126 | sub deprecated { | ||||||
127 | 2 | 2 | 1 | 4157 | local $Carp::CarpLevel = 1; | ||
128 | 2 | 100 | 268 | $ENV{MOJO_FATAL_DEPRECATIONS} ? croak @_ : carp @_; | |||
129 | } | ||||||
130 | |||||||
131 | 258 | 258 | 1 | 4254 | sub dumper { Data::Dumper->new([@_])->Indent(1)->Sortkeys(1)->Terse(1)->Useqq(1)->Dump } | ||
132 | |||||||
133 | 10330 | 10330 | 1 | 45992 | sub encode { _encoding($_[0])->encode("$_[1]", 0) } | ||
134 | |||||||
135 | sub extract_usage { | ||||||
136 | 26 | 100 | 26 | 1 | 3070 | my $file = @_ ? "$_[0]" : (caller)[1]; | |
137 | |||||||
138 | 26 | 359 | open my $handle, '>', \my $output; | ||||
139 | 26 | 972 | pod2usage -exitval => 'noexit', -input => $file, -output => $handle; | ||||
140 | 26 | 460672 | $output =~ s/^.*\n|\n$//; | ||||
141 | 26 | 147 | $output =~ s/\n$//; | ||||
142 | |||||||
143 | 26 | 102 | return unindent($output); | ||||
144 | } | ||||||
145 | |||||||
146 | sub getopt { | ||||||
147 | 116 | 100 | 116 | 1 | 13148 | my ($array, $opts) = map { ref $_[0] eq 'ARRAY' ? shift : $_ } \@ARGV, []; | |
232 | 1084 | ||||||
148 | |||||||
149 | 116 | 777 | my $save = Getopt::Long::Configure(qw(default no_auto_abbrev no_ignore_case), @$opts); | ||||
150 | 116 | 10760 | my $result = GetOptionsFromArray $array, @_; | ||||
151 | 116 | 43058 | Getopt::Long::Configure($save); | ||||
152 | |||||||
153 | 116 | 10836 | return $result; | ||||
154 | } | ||||||
155 | |||||||
156 | sub gunzip { | ||||||
157 | 2 | 2 | 1 | 1077 | my $compressed = shift; | ||
158 | 2 | 50 | 45 | IO::Uncompress::Gunzip::gunzip \$compressed, \my $uncompressed | |||
159 | or croak "Couldn't gunzip: $IO::Uncompress::Gunzip::GzipError"; | ||||||
160 | 2 | 4323 | return $uncompressed; | ||||
161 | } | ||||||
162 | |||||||
163 | sub gzip { | ||||||
164 | 54 | 54 | 1 | 11438 | my $uncompressed = shift; | ||
165 | 54 | 50 | 344 | IO::Compress::Gzip::gzip \$uncompressed, \my $compressed or croak "Couldn't gzip: $IO::Compress::Gzip::GzipError"; | |||
166 | 54 | 170845 | return $compressed; | ||||
167 | } | ||||||
168 | |||||||
169 | sub header_params { | ||||||
170 | 16 | 16 | 1 | 3553 | my $value = shift; | ||
171 | |||||||
172 | 16 | 28 | my $params = {}; | ||||
173 | 16 | 98 | while ($value =~ /\G[;\s]*([^=;, ]+)\s*/gc) { | ||||
174 | 20 | 47 | my $name = $1; | ||||
175 | |||||||
176 | # Quoted value | ||||||
177 | 20 | 100 | 66 | 136 | if ($value =~ /$QUOTED_VALUE_RE/gco) { $params->{$name} //= unquote($1) } | ||
4 | 100 | 52 | |||||
178 | |||||||
179 | # Unquoted value | ||||||
180 | 15 | 66 | 102 | elsif ($value =~ /$UNQUOTED_VALUE_RE/gco) { $params->{$name} //= $1 } | |||
181 | } | ||||||
182 | |||||||
183 | 16 | 100 | 129 | return ($params, substr($value, pos($value) // 0)); | |||
184 | } | ||||||
185 | |||||||
186 | 33867 | 33867 | 1 | 52985 | sub html_attr_unescape { _html(shift, 1) } | ||
187 | 2634 | 2634 | 1 | 22101 | sub html_unescape { _html(shift, 0) } | ||
188 | |||||||
189 | sub humanize_bytes { | ||||||
190 | 19 | 19 | 1 | 2538 | my $size = shift; | ||
191 | |||||||
192 | 19 | 100 | 85 | my $prefix = $size < 0 ? '-' : ''; | |||
193 | |||||||
194 | 19 | 100 | 61 | return "$prefix${size}B" if ($size = abs $size) < 1024; | |||
195 | 16 | 100 | 65 | return $prefix . _round($size) . 'KiB' if ($size /= 1024) < 1024; | |||
196 | 11 | 100 | 29 | return $prefix . _round($size) . 'MiB' if ($size /= 1024) < 1024; | |||
197 | 8 | 100 | 31 | return $prefix . _round($size) . 'GiB' if ($size /= 1024) < 1024; | |||
198 | 2 | 15 | return $prefix . _round($size /= 1024) . 'TiB'; | ||||
199 | } | ||||||
200 | |||||||
201 | sub monkey_patch { | ||||||
202 | 53146 | 53146 | 1 | 139167 | my ($class, %patch) = @_; | ||
203 | 101 | 101 | 957 | no strict 'refs'; | |||
101 | 279 | ||||||
101 | 3964 | ||||||
204 | 101 | 101 | 757 | no warnings 'redefine'; | |||
101 | 348 | ||||||
101 | 39821 | ||||||
205 | 53146 | 354775 | *{"${class}::$_"} = set_subname("${class}::$_", $patch{$_}) for keys %patch; | ||||
53363 | 662122 | ||||||
206 | } | ||||||
207 | |||||||
208 | sub network_contains { | ||||||
209 | 99 | 99 | 1 | 7135 | my ($cidr, $addr) = @_; | ||
210 | 99 | 100 | 100 | 438 | return undef unless length $cidr && length $addr; | ||
211 | |||||||
212 | # Parse inputs | ||||||
213 | 93 | 301 | my ($net, $mask) = split m!/!, $cidr, 2; | ||||
214 | 93 | 261 | my $v6 = $net =~ /:/; | ||||
215 | 93 | 100 | 100 | 352 | return undef if $v6 xor $addr =~ /:/; | ||
216 | |||||||
217 | # Convert addresses to binary | ||||||
218 | 91 | 100 | 408 | return undef unless $net = inet_pton($v6 ? AF_INET6 : AF_INET, $net); | |||
100 | |||||||
219 | 89 | 100 | 352 | return undef unless $addr = inet_pton($v6 ? AF_INET6 : AF_INET, $addr); | |||
100 | |||||||
220 | 87 | 100 | 174 | my $length = $v6 ? 128 : 32; | |||
221 | |||||||
222 | # Apply mask if given | ||||||
223 | 87 | 100 | 427 | $addr &= pack "B$length", '1' x $mask if defined $mask; | |||
224 | |||||||
225 | # Compare | ||||||
226 | 87 | 747 | return 0 == unpack "B$length", ($net ^ $addr); | ||||
227 | } | ||||||
228 | |||||||
229 | # Direct translation of RFC 3492 | ||||||
230 | sub punycode_decode { | ||||||
231 | 23 | 23 | 1 | 2493 | my $input = shift; | ||
232 | 101 | 101 | 924 | use integer; | |||
101 | 255 | ||||||
101 | 855 | ||||||
233 | |||||||
234 | 23 | 55 | my ($n, $i, $bias, @output) = (PC_INITIAL_N, 0, PC_INITIAL_BIAS); | ||||
235 | |||||||
236 | # Consume all code points before the last delimiter | ||||||
237 | 23 | 100 | 183 | push @output, split(//, $1) if $input =~ s/(.*)\x2d//s; | |||
238 | |||||||
239 | 23 | 71 | while (length $input) { | ||||
240 | 219 | 332 | my ($oldi, $w) = ($i, 1); | ||||
241 | |||||||
242 | # Base to infinity in steps of base | ||||||
243 | 219 | 320 | for (my $k = PC_BASE; 1; $k += PC_BASE) { | ||||
244 | 458 | 687 | my $digit = ord substr $input, 0, 1, ''; | ||||
245 | 458 | 100 | 690 | $digit = $digit < 0x40 ? $digit + (26 - 0x30) : ($digit & 0x1f) - 1; | |||
246 | 458 | 536 | $i += $digit * $w; | ||||
247 | 458 | 543 | my $t = $k - $bias; | ||||
248 | 458 | 100 | 739 | $t = $t < PC_TMIN ? PC_TMIN : $t > PC_TMAX ? PC_TMAX : $t; | |||
100 | |||||||
249 | 458 | 100 | 760 | last if $digit < $t; | |||
250 | 239 | 340 | $w *= PC_BASE - $t; | ||||
251 | } | ||||||
252 | |||||||
253 | 219 | 378 | $bias = _adapt($i - $oldi, @output + 1, $oldi == 0); | ||||
254 | 219 | 319 | $n += $i / (@output + 1); | ||||
255 | 219 | 295 | $i = $i % (@output + 1); | ||||
256 | 219 | 543 | splice @output, $i++, 0, chr $n; | ||||
257 | } | ||||||
258 | |||||||
259 | 23 | 150 | return join '', @output; | ||||
260 | } | ||||||
261 | |||||||
262 | # Direct translation of RFC 3492 | ||||||
263 | sub punycode_encode { | ||||||
264 | 64 | 64 | 1 | 15854 | my $output = shift; | ||
265 | 101 | 101 | 35782 | use integer; | |||
101 | 268 | ||||||
101 | 481 | ||||||
266 | |||||||
267 | 64 | 141 | my ($n, $delta, $bias) = (PC_INITIAL_N, 0, PC_INITIAL_BIAS); | ||||
268 | |||||||
269 | # Extract basic code points | ||||||
270 | 64 | 219 | my @input = map {ord} split //, $output; | ||||
553 | 816 | ||||||
271 | 64 | 394 | $output =~ s/[^\x00-\x7f]+//gs; | ||||
272 | 64 | 168 | my $h = my $basic = length $output; | ||||
273 | 64 | 100 | 189 | $output .= "\x2d" if $basic > 0; | |||
274 | |||||||
275 | 64 | 128 | for my $m (sort grep { $_ >= PC_INITIAL_N } @input) { | ||||
553 | 987 | ||||||
276 | 260 | 100 | 480 | next if $m < $n; | |||
277 | 218 | 347 | $delta += ($m - $n) * ($h + 1); | ||||
278 | 218 | 282 | $n = $m; | ||||
279 | |||||||
280 | 218 | 305 | for my $c (@input) { | ||||
281 | |||||||
282 | 3630 | 100 | 5693 | if ($c < $n) { $delta++ } | |||
2033 | 100 | 2475 | |||||
283 | elsif ($c == $n) { | ||||||
284 | 260 | 310 | my $q = $delta; | ||||
285 | |||||||
286 | # Base to infinity in steps of base | ||||||
287 | 260 | 360 | for (my $k = PC_BASE; 1; $k += PC_BASE) { | ||||
288 | 581 | 736 | my $t = $k - $bias; | ||||
289 | 581 | 100 | 965 | $t = $t < PC_TMIN ? PC_TMIN : $t > PC_TMAX ? PC_TMAX : $t; | |||
100 | |||||||
290 | 581 | 100 | 1005 | last if $q < $t; | |||
291 | 321 | 432 | my $o = $t + (($q - $t) % (PC_BASE - $t)); | ||||
292 | 321 | 100 | 702 | $output .= chr $o + ($o < 26 ? 0x61 : 0x30 - 26); | |||
293 | 321 | 606 | $q = ($q - $t) / (PC_BASE - $t); | ||||
294 | } | ||||||
295 | |||||||
296 | 260 | 50 | 557 | $output .= chr $q + ($q < 26 ? 0x61 : 0x30 - 26); | |||
297 | 260 | 525 | $bias = _adapt($delta, $h + 1, $h == $basic); | ||||
298 | 260 | 320 | $delta = 0; | ||||
299 | 260 | 407 | $h++; | ||||
300 | } | ||||||
301 | } | ||||||
302 | |||||||
303 | 218 | 259 | $delta++; | ||||
304 | 218 | 290 | $n++; | ||||
305 | } | ||||||
306 | |||||||
307 | 64 | 302 | return $output; | ||||
308 | } | ||||||
309 | |||||||
310 | sub quote { | ||||||
311 | 21 | 21 | 1 | 2906 | my $str = shift; | ||
312 | 21 | 127 | $str =~ s/(["\\])/\\$1/g; | ||||
313 | 21 | 95 | return qq{"$str"}; | ||||
314 | } | ||||||
315 | |||||||
316 | 18 | 18 | 1 | 2701 | sub scope_guard { Mojo::Util::_Guard->new(cb => shift) } | ||
317 | |||||||
318 | sub secure_compare { | ||||||
319 | 86 | 86 | 1 | 12349 | my ($one, $two) = @_; | ||
320 | 86 | 177 | my $r = length $one != length $two; | ||||
321 | 86 | 100 | 202 | $two = $one if $r; | |||
322 | 86 | 1575 | $r |= ord(substr $one, $_) ^ ord(substr $two, $_) for 0 .. length($one) - 1; | ||||
323 | 86 | 472 | return $r == 0; | ||||
324 | } | ||||||
325 | |||||||
326 | sub slugify { | ||||||
327 | 14 | 14 | 1 | 2473 | my ($value, $allow_unicode) = @_; | ||
328 | |||||||
329 | 14 | 100 | 35 | if ($allow_unicode) { | |||
330 | |||||||
331 | # Force unicode semantics by upgrading string | ||||||
332 | 6 | 82 | utf8::upgrade($value = Unicode::Normalize::NFKC($value)); | ||||
333 | 6 | 52 | $value =~ s/[^\w\s-]+//g; | ||||
334 | } | ||||||
335 | else { | ||||||
336 | 8 | 67 | $value = Unicode::Normalize::NFKD($value); | ||||
337 | 101 | 101 | 91939 | $value =~ s/[^a-zA-Z0-9_\p{PosixSpace}-]+//g; | |||
101 | 280 | ||||||
101 | 1668 | ||||||
8 | 65 | ||||||
338 | } | ||||||
339 | 14 | 35 | (my $new = lc trim($value)) =~ s/[-\s]+/-/g; | ||||
340 | |||||||
341 | 14 | 5950 | return $new; | ||||
342 | } | ||||||
343 | |||||||
344 | 995 | 995 | 1 | 6241 | sub split_cookie_header { _header(shift, 1) } | ||
345 | 206 | 206 | 1 | 3576 | sub split_header { _header(shift, 0) } | ||
346 | |||||||
347 | sub tablify { | ||||||
348 | 18 | 18 | 1 | 2603 | my $rows = shift; | ||
349 | |||||||
350 | 18 | 34 | my @spec; | ||||
351 | 18 | 43 | for my $row (@$rows) { | ||||
352 | 87 | 176 | for my $i (0 .. $#$row) { | ||||
353 | 176 | 100 | 375 | ($row->[$i] //= '') =~ y/\r\n//d; | |||
354 | 176 | 243 | my $len = length $row->[$i]; | ||||
355 | 176 | 100 | 100 | 500 | $spec[$i] = $len if $len >= ($spec[$i] // 0); | ||
356 | } | ||||||
357 | } | ||||||
358 | |||||||
359 | 18 | 69 | my @fm = (map({"\%-${_}s"} @spec[0 .. $#spec - 1]), '%s'); | ||||
23 | 100 | ||||||
360 | 18 | 41 | return join '', map { sprintf join(' ', @fm[0 .. $#$_]) . "\n", @$_ } @$rows; | ||||
87 | 438 | ||||||
361 | } | ||||||
362 | |||||||
363 | sub term_escape { | ||||||
364 | 4 | 4 | 1 | 2590 | my $str = shift; | ||
365 | 4 | 26 | $str =~ s/([\x00-\x09\x0b-\x1f\x7f\x80-\x9f])/sprintf '\\x%02x', ord $1/ge; | ||||
16 | 62 | ||||||
366 | 4 | 70 | return $str; | ||||
367 | } | ||||||
368 | |||||||
369 | sub trim { | ||||||
370 | 1370 | 1370 | 1 | 5011 | my $str = shift; | ||
371 | 1370 | 4331 | $str =~ s/^\s+//; | ||||
372 | 1370 | 4066 | $str =~ s/\s+$//; | ||||
373 | 1370 | 3994 | return $str; | ||||
374 | } | ||||||
375 | |||||||
376 | sub unindent { | ||||||
377 | 37 | 37 | 1 | 3349 | my $str = shift; | ||
378 | 37 | 100 | 222 | my $min = min map { m/^([ \t]*)/; length $1 || () } split /\n/, $str; | |||
426 | 795 | ||||||
426 | 1051 | ||||||
379 | 37 | 100 | 726 | $str =~ s/^[ \t]{0,$min}//gm if $min; | |||
380 | 37 | 584 | return $str; | ||||
381 | } | ||||||
382 | |||||||
383 | sub unquote { | ||||||
384 | 48 | 48 | 1 | 2546 | my $str = shift; | ||
385 | 48 | 50 | 276 | return $str unless $str =~ s/^"(.*)"$/$1/g; | |||
386 | 48 | 131 | $str =~ s/\\\\/\\/g; | ||||
387 | 48 | 111 | $str =~ s/\\"/"/g; | ||||
388 | 48 | 124 | return $str; | ||||
389 | } | ||||||
390 | |||||||
391 | sub url_escape { | ||||||
392 | 5814 | 5814 | 1 | 19515 | my ($str, $pattern) = @_; | ||
393 | |||||||
394 | 5814 | 100 | 10294 | if ($pattern) { | |||
395 | 5799 | 100 | 13273 | unless (exists $PATTERN{$pattern}) { | |||
396 | 133 | 1436 | (my $quoted = $pattern) =~ s!([/\$\[])!\\$1!g; | ||||
397 | 133 | 50 | 26161 | $PATTERN{$pattern} = eval "sub { \$_[0] =~ s/([$quoted])/sprintf '%%%02X', ord \$1/ge }" or croak $@; | |||
398 | } | ||||||
399 | 5799 | 127892 | $PATTERN{$pattern}->($str); | ||||
400 | } | ||||||
401 | 15 | 94 | else { $str =~ s/([^A-Za-z0-9\-._~])/sprintf '%%%02X', ord $1/ge } | ||||
22 | 126 | ||||||
402 | |||||||
403 | 5814 | 22547 | return $str; | ||||
404 | } | ||||||
405 | |||||||
406 | sub url_unescape { | ||||||
407 | 7526 | 7526 | 1 | 17645 | my $str = shift; | ||
408 | 7526 | 15552 | $str =~ s/%([0-9a-fA-F]{2})/chr hex $1/ge; | ||||
787 | 2888 | ||||||
409 | 7526 | 16564 | return $str; | ||||
410 | } | ||||||
411 | |||||||
412 | sub xml_escape { | ||||||
413 | 486 | 100 | 66 | 486 | 1 | 8347 | return $_[0] if ref $_[0] && ref $_[0] eq 'Mojo::ByteStream'; |
7982 | 100 | 100 | 7982 | 63866 | |||
414 | 485 | 50 | 954 | my $str = shift // ''; | |||
6188 | 100 | 12683 | |||||
415 | 485 | 989 | $str =~ s/([&<>"'])/$XML{$1}/ge; | ||||
42 | 161 | ||||||
6188 | 12852 | ||||||
7688 | 21088 | ||||||
416 | 485 | 1813 | return $str; | ||||
6187 | 14467 | ||||||
417 | } | ||||||
418 | |||||||
419 | sub xor_encode { | ||||||
420 | 294 | 294 | 1 | 3158 | my ($input, $key) = @_; | ||
421 | |||||||
422 | # Encode with variable key length | ||||||
423 | 294 | 502 | my $len = length $key; | ||||
424 | 294 | 489 | my $buffer = my $output = ''; | ||||
425 | 294 | 2158 | $output .= $buffer ^ $key while length($buffer = substr($input, 0, $len, '')) == $len; | ||||
426 | 294 | 1747 | return $output .= $buffer ^ substr($key, 0, length $buffer, ''); | ||||
427 | } | ||||||
428 | |||||||
429 | sub _adapt { | ||||||
430 | 479 | 479 | 830 | my ($delta, $numpoints, $firsttime) = @_; | |||
431 | 101 | 101 | 2224529 | use integer; | |||
101 | 269 | ||||||
101 | 710 | ||||||
432 | |||||||
433 | 479 | 100 | 739 | $delta = $firsttime ? $delta / PC_DAMP : $delta / 2; | |||
434 | 479 | 613 | $delta += $delta / $numpoints; | ||||
435 | 479 | 599 | my $k = 0; | ||||
436 | 479 | 785 | while ($delta > ((PC_BASE - PC_TMIN) * PC_TMAX) / 2) { | ||||
437 | 110 | 128 | $delta /= PC_BASE - PC_TMIN; | ||||
438 | 110 | 171 | $k += PC_BASE; | ||||
439 | } | ||||||
440 | |||||||
441 | 479 | 790 | return $k + (((PC_BASE - PC_TMIN + 1) * $delta) / ($delta + PC_SKEW)); | ||||
442 | } | ||||||
443 | |||||||
444 | 17662 | 66 | 17662 | 122735 | sub _encoding { $ENCODING{$_[0]} //= find_encoding($_[0]) // croak "Unknown encoding '$_[0]'" } | ||
66 | |||||||
445 | |||||||
446 | sub _entity { | ||||||
447 | 991 | 991 | 2333 | my ($point, $name, $attr) = @_; | |||
448 | |||||||
449 | # Code point | ||||||
450 | 991 | 100 | 2128 | return chr($point !~ /^x/ ? $point : hex $point) unless defined $name; | |||
100 | |||||||
451 | |||||||
452 | # Named character reference | ||||||
453 | 950 | 1249 | my $rest = my $last = ''; | ||||
454 | 950 | 1696 | while (length $name) { | ||||
455 | return $ENTITIES{$name} . reverse $rest | ||||||
456 | 988 | 100 | 100 | 5468 | if exists $ENTITIES{$name} && (!$attr || $name =~ /;$/ || $last !~ /[A-Za-z0-9=]/); | ||
100 | |||||||
457 | 48 | 111 | $rest .= $last = chop $name; | ||||
458 | } | ||||||
459 | 10 | 52 | return '&' . reverse $rest; | ||||
460 | } | ||||||
461 | |||||||
462 | sub _header { | ||||||
463 | 1201 | 1201 | 2547 | my ($str, $cookie) = @_; | |||
464 | |||||||
465 | 1201 | 2078 | my (@tree, @part); | ||||
466 | 1201 | 4229 | while ($str =~ /\G[,;\s]*([^=;, ]+)\s*/gc) { | ||||
467 | 798 | 2030 | push @part, $1, undef; | ||||
468 | 798 | 100 | 2947 | my $expires = $cookie && @part > 2 && lc $1 eq 'expires'; | |||
469 | |||||||
470 | # Special "expires" value | ||||||
471 | 798 | 100 | 100 | 5249 | if ($expires && $str =~ /\G=\s*$EXPIRES_RE/gco) { $part[-1] = $1 } | ||
120 | 100 | 301 | |||||
100 | |||||||
472 | |||||||
473 | # Quoted value | ||||||
474 | 42 | 103 | elsif ($str =~ /$QUOTED_VALUE_RE/gco) { $part[-1] = unquote $1 } | ||||
475 | |||||||
476 | # Unquoted value | ||||||
477 | 538 | 1246 | elsif ($str =~ /$UNQUOTED_VALUE_RE/gco) { $part[-1] = $1 } | ||||
478 | |||||||
479 | # Separator | ||||||
480 | 798 | 100 | 2914 | next unless $str =~ /\G[;\s]*,\s*/gc; | |||
481 | 107 | 314 | push @tree, [@part]; | ||||
482 | 107 | 380 | @part = (); | ||||
483 | } | ||||||
484 | |||||||
485 | # Take care of final part | ||||||
486 | 1201 | 100 | 5044 | return [@part ? (@tree, \@part) : @tree]; | |||
487 | } | ||||||
488 | |||||||
489 | sub _html { | ||||||
490 | 36501 | 36501 | 53749 | my ($str, $attr) = @_; | |||
491 | 36501 | 47844 | $str =~ s/$ENTITY_RE/_entity($1, $2, $attr)/geo; | ||||
991 | 1835 | ||||||
492 | 36501 | 168264 | return $str; | ||||
493 | } | ||||||
494 | |||||||
495 | sub _options { | ||||||
496 | |||||||
497 | # Hash or name (one) | ||||||
498 | 1801 | 100 | 1801 | 5571 | return ref $_[0] eq 'HASH' ? (undef, %{shift()}) : @_ if @_ == 1; | ||
996 | 100 | 3476 | |||||
499 | |||||||
500 | # Name and values (odd) | ||||||
501 | 349 | 100 | 1054 | return shift, @_ if @_ % 2; | |||
502 | |||||||
503 | # Name and hash or just values (even) | ||||||
504 | 269 | 100 | 1182 | return ref $_[1] eq 'HASH' ? (shift, %{shift()}) : (undef, @_); | |||
18 | 103 | ||||||
505 | } | ||||||
506 | |||||||
507 | # This may break in the future, but is worth it for performance | ||||||
508 | 760 | 760 | 13249 | sub _readable { !!(IO::Poll::_poll(@_[0, 1], my $m = POLLIN | POLLPRI) > 0) } | |||
509 | |||||||
510 | 16 | 100 | 16 | 181 | sub _round { $_[0] < 10 ? int($_[0] * 10 + 0.5) / 10 : int($_[0] + 0.5) } | ||
511 | |||||||
512 | sub _stash { | ||||||
513 | 20923 | 20923 | 37553 | my ($name, $object) = (shift, shift); | |||
514 | |||||||
515 | # Hash | ||||||
516 | 20923 | 100 | 100 | 109076 | return $object->{$name} //= {} unless @_; | ||
517 | |||||||
518 | # Get | ||||||
519 | 1316 | 100 | 100 | 5189 | return $object->{$name}{$_[0]} unless @_ > 1 || ref $_[0]; | ||
520 | |||||||
521 | # Set | ||||||
522 | 1086 | 100 | 3882 | my $values = ref $_[0] ? $_[0] : {@_}; | |||
523 | 1086 | 3642 | @{$object->{$name}}{keys %$values} = values %$values; | ||||
1086 | 3078 | ||||||
524 | |||||||
525 | 1086 | 4471 | return $object; | ||||
526 | } | ||||||
527 | |||||||
528 | sub _teardown { | ||||||
529 | 826 | 50 | 826 | 40825 | return unless my $class = shift; | ||
530 | |||||||
531 | # @ISA has to be cleared first because of circular references | ||||||
532 | 101 | 101 | 99068 | no strict 'refs'; | |||
101 | 306 | ||||||
101 | 9642 | ||||||
533 | 826 | 1175 | @{"${class}::ISA"} = (); | ||||
826 | 11911 | ||||||
534 | 826 | 3475 | delete_package $class; | ||||
535 | } | ||||||
536 | |||||||
537 | package Mojo::Util::_Guard; | ||||||
538 | 101 | 101 | 725 | use Mojo::Base -base; | |||
101 | 228 | ||||||
101 | 1001 | ||||||
539 | |||||||
540 | 18 | 18 | 1157 | sub DESTROY { shift->{cb}() } | |||
541 | |||||||
542 | 1; | ||||||
543 | |||||||
544 | =encoding utf8 | ||||||
545 | |||||||
546 | =head1 NAME | ||||||
547 | |||||||
548 | Mojo::Util - Portable utility functions | ||||||
549 | |||||||
550 | =head1 SYNOPSIS | ||||||
551 | |||||||
552 | use Mojo::Util qw(b64_encode url_escape url_unescape); | ||||||
553 | |||||||
554 | my $str = 'test=23'; | ||||||
555 | my $escaped = url_escape $str; | ||||||
556 | say url_unescape $escaped; | ||||||
557 | say b64_encode $escaped, ''; | ||||||
558 | |||||||
559 | =head1 DESCRIPTION | ||||||
560 | |||||||
561 | L |
||||||
562 | |||||||
563 | =head1 FUNCTIONS | ||||||
564 | |||||||
565 | L |
||||||
566 | |||||||
567 | =head2 b64_decode | ||||||
568 | |||||||
569 | my $bytes = b64_decode $b64; | ||||||
570 | |||||||
571 | Base64 decode bytes with L |
||||||
572 | |||||||
573 | =head2 b64_encode | ||||||
574 | |||||||
575 | my $b64 = b64_encode $bytes; | ||||||
576 | my $b64 = b64_encode $bytes, "\n"; | ||||||
577 | |||||||
578 | Base64 encode bytes with L |
||||||
579 | |||||||
580 | =head2 camelize | ||||||
581 | |||||||
582 | my $camelcase = camelize $snakecase; | ||||||
583 | |||||||
584 | Convert C |
||||||
585 | |||||||
586 | # "FooBar" | ||||||
587 | camelize 'foo_bar'; | ||||||
588 | |||||||
589 | # "FooBar::Baz" | ||||||
590 | camelize 'foo_bar-baz'; | ||||||
591 | |||||||
592 | # "FooBar::Baz" | ||||||
593 | camelize 'FooBar::Baz'; | ||||||
594 | |||||||
595 | =head2 class_to_file | ||||||
596 | |||||||
597 | my $file = class_to_file 'Foo::Bar'; | ||||||
598 | |||||||
599 | Convert a class name to a file. | ||||||
600 | |||||||
601 | # "foo_bar" | ||||||
602 | class_to_file 'Foo::Bar'; | ||||||
603 | |||||||
604 | # "foobar" | ||||||
605 | class_to_file 'FOO::Bar'; | ||||||
606 | |||||||
607 | # "foo_bar" | ||||||
608 | class_to_file 'FooBar'; | ||||||
609 | |||||||
610 | # "foobar" | ||||||
611 | class_to_file 'FOOBar'; | ||||||
612 | |||||||
613 | =head2 class_to_path | ||||||
614 | |||||||
615 | my $path = class_to_path 'Foo::Bar'; | ||||||
616 | |||||||
617 | Convert class name to path, as used by C<%INC>. | ||||||
618 | |||||||
619 | # "Foo/Bar.pm" | ||||||
620 | class_to_path 'Foo::Bar'; | ||||||
621 | |||||||
622 | # "FooBar.pm" | ||||||
623 | class_to_path 'FooBar'; | ||||||
624 | |||||||
625 | =head2 decamelize | ||||||
626 | |||||||
627 | my $snakecase = decamelize $camelcase; | ||||||
628 | |||||||
629 | Convert C |
||||||
630 | |||||||
631 | # "foo_bar" | ||||||
632 | decamelize 'FooBar'; | ||||||
633 | |||||||
634 | # "foo_bar-baz" | ||||||
635 | decamelize 'FooBar::Baz'; | ||||||
636 | |||||||
637 | # "foo_bar-baz" | ||||||
638 | decamelize 'foo_bar-baz'; | ||||||
639 | |||||||
640 | =head2 decode | ||||||
641 | |||||||
642 | my $chars = decode 'UTF-8', $bytes; | ||||||
643 | |||||||
644 | Decode bytes to characters with L |
||||||
645 | |||||||
646 | =head2 deprecated | ||||||
647 | |||||||
648 | deprecated 'foo is DEPRECATED in favor of bar'; | ||||||
649 | |||||||
650 | Warn about deprecated feature from perspective of caller. You can also set the C |
||||||
651 | variable to make them die instead with L |
||||||
652 | |||||||
653 | =head2 dumper | ||||||
654 | |||||||
655 | my $perl = dumper {some => 'data'}; | ||||||
656 | |||||||
657 | Dump a Perl data structure with L |
||||||
658 | |||||||
659 | =head2 encode | ||||||
660 | |||||||
661 | my $bytes = encode 'UTF-8', $chars; | ||||||
662 | |||||||
663 | Encode characters to bytes with L |
||||||
664 | |||||||
665 | =head2 extract_usage | ||||||
666 | |||||||
667 | my $usage = extract_usage; | ||||||
668 | my $usage = extract_usage '/home/sri/foo.pod'; | ||||||
669 | |||||||
670 | Extract usage message from the SYNOPSIS section of a file containing POD documentation, defaults to using the file this | ||||||
671 | function was called from. | ||||||
672 | |||||||
673 | # "Usage: APPLICATION test [OPTIONS]\n" | ||||||
674 | extract_usage; | ||||||
675 | |||||||
676 | =head1 SYNOPSIS | ||||||
677 | |||||||
678 | Usage: APPLICATION test [OPTIONS] | ||||||
679 | |||||||
680 | =cut | ||||||
681 | |||||||
682 | =head2 getopt | ||||||
683 | |||||||
684 | getopt | ||||||
685 | 'H|headers=s' => \my @headers, | ||||||
686 | 't|timeout=i' => \my $timeout, | ||||||
687 | 'v|verbose' => \my $verbose; | ||||||
688 | getopt $array, | ||||||
689 | 'H|headers=s' => \my @headers, | ||||||
690 | 't|timeout=i' => \my $timeout, | ||||||
691 | 'v|verbose' => \my $verbose; | ||||||
692 | getopt $array, ['pass_through'], | ||||||
693 | 'H|headers=s' => \my @headers, | ||||||
694 | 't|timeout=i' => \my $timeout, | ||||||
695 | 'v|verbose' => \my $verbose; | ||||||
696 | |||||||
697 | Extract options from an array reference with L |
||||||
698 | to using C<@ARGV>. The configuration options C |
||||||
699 | |||||||
700 | # Extract "charset" option | ||||||
701 | getopt ['--charset', 'UTF-8'], 'charset=s' => \my $charset; | ||||||
702 | say $charset; | ||||||
703 | |||||||
704 | =head2 gunzip | ||||||
705 | |||||||
706 | my $uncompressed = gunzip $compressed; | ||||||
707 | |||||||
708 | Uncompress bytes with L |
||||||
709 | |||||||
710 | =head2 gzip | ||||||
711 | |||||||
712 | my $compressed = gzip $uncompressed; | ||||||
713 | |||||||
714 | Compress bytes with L |
||||||
715 | |||||||
716 | =head2 header_params | ||||||
717 | |||||||
718 | my ($params, $remainder) = header_params 'one=foo; two="bar", three=baz'; | ||||||
719 | |||||||
720 | Extract HTTP header field parameters until the first comma according to L |
||||||
721 | Note that this function is B |
||||||
722 | |||||||
723 | =head2 hmac_sha1_sum | ||||||
724 | |||||||
725 | my $checksum = hmac_sha1_sum $bytes, 'passw0rd'; | ||||||
726 | |||||||
727 | Generate HMAC-SHA1 checksum for bytes with L |
||||||
728 | |||||||
729 | # "11cedfd5ec11adc0ec234466d8a0f2a83736aa68" | ||||||
730 | hmac_sha1_sum 'foo', 'passw0rd'; | ||||||
731 | |||||||
732 | =head2 html_attr_unescape | ||||||
733 | |||||||
734 | my $str = html_attr_unescape $escaped; | ||||||
735 | |||||||
736 | Same as L"html_unescape">, but handles special rules from the L | ||||||
737 | for HTML attributes. | ||||||
738 | |||||||
739 | # "foo=bar<est=baz" | ||||||
740 | html_attr_unescape 'foo=bar<est=baz'; | ||||||
741 | |||||||
742 | # "foo=bar | ||||||
743 | html_attr_unescape 'foo=bar<est=baz'; | ||||||
744 | |||||||
745 | =head2 html_unescape | ||||||
746 | |||||||
747 | my $str = html_unescape $escaped; | ||||||
748 | |||||||
749 | Unescape all HTML entities in string. | ||||||
750 | |||||||
751 | # " " |
||||||
752 | html_unescape '<div>'; | ||||||
753 | |||||||
754 | =head2 humanize_bytes | ||||||
755 | |||||||
756 | my $str = humanize_bytes 1234; | ||||||
757 | |||||||
758 | Turn number of bytes into a simplified human readable format. | ||||||
759 | |||||||
760 | # "1B" | ||||||
761 | humanize_bytes 1; | ||||||
762 | |||||||
763 | # "7.5GiB" | ||||||
764 | humanize_bytes 8007188480; | ||||||
765 | |||||||
766 | # "13GiB" | ||||||
767 | humanize_bytes 13443399680; | ||||||
768 | |||||||
769 | # "-685MiB" | ||||||
770 | humanize_bytes -717946880; | ||||||
771 | |||||||
772 | =head2 md5_bytes | ||||||
773 | |||||||
774 | my $checksum = md5_bytes $bytes; | ||||||
775 | |||||||
776 | Generate binary MD5 checksum for bytes with L |
||||||
777 | |||||||
778 | =head2 md5_sum | ||||||
779 | |||||||
780 | my $checksum = md5_sum $bytes; | ||||||
781 | |||||||
782 | Generate MD5 checksum for bytes with L |
||||||
783 | |||||||
784 | # "acbd18db4cc2f85cedef654fccc4a4d8" | ||||||
785 | md5_sum 'foo'; | ||||||
786 | |||||||
787 | =head2 monkey_patch | ||||||
788 | |||||||
789 | monkey_patch $package, foo => sub {...}; | ||||||
790 | monkey_patch $package, foo => sub {...}, bar => sub {...}; | ||||||
791 | |||||||
792 | Monkey patch functions into package. | ||||||
793 | |||||||
794 | monkey_patch 'MyApp', | ||||||
795 | one => sub { say 'One!' }, | ||||||
796 | two => sub { say 'Two!' }, | ||||||
797 | three => sub { say 'Three!' }; | ||||||
798 | |||||||
799 | =head2 punycode_decode | ||||||
800 | |||||||
801 | my $str = punycode_decode $punycode; | ||||||
802 | |||||||
803 | Punycode decode string as described in L |
||||||
804 | |||||||
805 | # "bücher" | ||||||
806 | punycode_decode 'bcher-kva'; | ||||||
807 | |||||||
808 | =head2 network_contains | ||||||
809 | |||||||
810 | my $bool = network_contains $network, $address; | ||||||
811 | |||||||
812 | Check that a given address is contained within a network in CIDR form. If the network is a single address, the | ||||||
813 | addresses must be equivalent. | ||||||
814 | |||||||
815 | # True | ||||||
816 | network_contains('10.0.0.0/8', '10.10.10.10'); | ||||||
817 | network_contains('10.10.10.10', '10.10.10.10'); | ||||||
818 | network_contains('fc00::/7', 'fc::c0:ff:ee'); | ||||||
819 | |||||||
820 | # False | ||||||
821 | network_contains('10.0.0.0/29', '10.10.10.10'); | ||||||
822 | network_contains('10.10.10.12', '10.10.10.10'); | ||||||
823 | network_contains('fc00::/7', '::1'); | ||||||
824 | |||||||
825 | =head2 punycode_encode | ||||||
826 | |||||||
827 | my $punycode = punycode_encode $str; | ||||||
828 | |||||||
829 | Punycode encode string as described in L |
||||||
830 | |||||||
831 | # "bcher-kva" | ||||||
832 | punycode_encode 'bücher'; | ||||||
833 | |||||||
834 | =head2 quote | ||||||
835 | |||||||
836 | my $quoted = quote $str; | ||||||
837 | |||||||
838 | Quote string. | ||||||
839 | |||||||
840 | =head2 scope_guard | ||||||
841 | |||||||
842 | my $guard = scope_guard sub {...}; | ||||||
843 | |||||||
844 | Create anonymous scope guard object that will execute the passed callback when the object is destroyed. | ||||||
845 | |||||||
846 | # Execute closure at end of scope | ||||||
847 | { | ||||||
848 | my $guard = scope_guard sub { say "Mojo!" }; | ||||||
849 | say "Hello"; | ||||||
850 | } | ||||||
851 | |||||||
852 | =head2 secure_compare | ||||||
853 | |||||||
854 | my $bool = secure_compare $str1, $str2; | ||||||
855 | |||||||
856 | Constant time comparison algorithm to prevent timing attacks. The secret string should be the second argument, to avoid | ||||||
857 | leaking information about the length of the string. | ||||||
858 | |||||||
859 | =head2 sha1_bytes | ||||||
860 | |||||||
861 | my $checksum = sha1_bytes $bytes; | ||||||
862 | |||||||
863 | Generate binary SHA1 checksum for bytes with L |
||||||
864 | |||||||
865 | =head2 sha1_sum | ||||||
866 | |||||||
867 | my $checksum = sha1_sum $bytes; | ||||||
868 | |||||||
869 | Generate SHA1 checksum for bytes with L |
||||||
870 | |||||||
871 | # "0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33" | ||||||
872 | sha1_sum 'foo'; | ||||||
873 | |||||||
874 | =head2 slugify | ||||||
875 | |||||||
876 | my $slug = slugify $string; | ||||||
877 | my $slug = slugify $string, $bool; | ||||||
878 | |||||||
879 | Returns a URL slug generated from the input string. Non-word characters are removed, the string is trimmed and | ||||||
880 | lowercased, and whitespace characters are replaced by a dash. By default, non-ASCII characters are normalized to ASCII | ||||||
881 | word characters or removed, but if a true value is passed as the second parameter, all word characters will be allowed | ||||||
882 | in the result according to unicode semantics. | ||||||
883 | |||||||
884 | # "joel-is-a-slug" | ||||||
885 | slugify 'Joel is a slug'; | ||||||
886 | |||||||
887 | # "this-is-my-resume" | ||||||
888 | slugify 'This is: my - résumé! ☃ '; | ||||||
889 | |||||||
890 | # "this-is-my-résumé" | ||||||
891 | slugify 'This is: my - résumé! ☃ ', 1; | ||||||
892 | |||||||
893 | =head2 split_cookie_header | ||||||
894 | |||||||
895 | my $tree = split_cookie_header 'a=b; expires=Thu, 07 Aug 2008 07:07:59 GMT'; | ||||||
896 | |||||||
897 | Same as L"split_header">, but handles C |
||||||
898 | |||||||
899 | =head2 split_header | ||||||
900 | |||||||
901 | my $tree = split_header 'foo="bar baz"; test=123, yada'; | ||||||
902 | |||||||
903 | Split HTTP header value into key/value pairs, each comma separated part gets its own array reference, and keys without | ||||||
904 | a value get C |
||||||
905 | |||||||
906 | # "one" | ||||||
907 | split_header('one; two="three four", five=six')->[0][0]; | ||||||
908 | |||||||
909 | # "two" | ||||||
910 | split_header('one; two="three four", five=six')->[0][2]; | ||||||
911 | |||||||
912 | # "three four" | ||||||
913 | split_header('one; two="three four", five=six')->[0][3]; | ||||||
914 | |||||||
915 | # "five" | ||||||
916 | split_header('one; two="three four", five=six')->[1][0]; | ||||||
917 | |||||||
918 | # "six" | ||||||
919 | split_header('one; two="three four", five=six')->[1][1]; | ||||||
920 | |||||||
921 | =head2 steady_time | ||||||
922 | |||||||
923 | my $time = steady_time; | ||||||
924 | |||||||
925 | High resolution time elapsed from an arbitrary fixed point in the past, resilient to time jumps if a monotonic clock is | ||||||
926 | available through L |
||||||
927 | |||||||
928 | =head2 tablify | ||||||
929 | |||||||
930 | my $table = tablify [['foo', 'bar'], ['baz', 'yada']]; | ||||||
931 | |||||||
932 | Row-oriented generator for text tables. | ||||||
933 | |||||||
934 | # "foo bar\nyada yada\nbaz yada\n" | ||||||
935 | tablify [['foo', 'bar'], ['yada', 'yada'], ['baz', 'yada']]; | ||||||
936 | |||||||
937 | =head2 term_escape | ||||||
938 | |||||||
939 | my $escaped = term_escape $str; | ||||||
940 | |||||||
941 | Escape all POSIX control characters except for C<\n>. | ||||||
942 | |||||||
943 | # "foo\\x09bar\\x0d\n" | ||||||
944 | term_escape "foo\tbar\r\n"; | ||||||
945 | |||||||
946 | =head2 trim | ||||||
947 | |||||||
948 | my $trimmed = trim $str; | ||||||
949 | |||||||
950 | Trim whitespace characters from both ends of string. | ||||||
951 | |||||||
952 | # "foo bar" | ||||||
953 | trim ' foo bar '; | ||||||
954 | |||||||
955 | =head2 unindent | ||||||
956 | |||||||
957 | my $unindented = unindent $str; | ||||||
958 | |||||||
959 | Unindent multi-line string. | ||||||
960 | |||||||
961 | # "foo\nbar\nbaz\n" | ||||||
962 | unindent " foo\n bar\n baz\n"; | ||||||
963 | |||||||
964 | =head2 unquote | ||||||
965 | |||||||
966 | my $str = unquote $quoted; | ||||||
967 | |||||||
968 | Unquote string. | ||||||
969 | |||||||
970 | =head2 url_escape | ||||||
971 | |||||||
972 | my $escaped = url_escape $str; | ||||||
973 | my $escaped = url_escape $str, '^A-Za-z0-9\-._~'; | ||||||
974 | |||||||
975 | Percent encode unsafe characters in string as described in L |
||||||
976 | used defaults to C<^A-Za-z0-9\-._~>. | ||||||
977 | |||||||
978 | # "foo%3Bbar" | ||||||
979 | url_escape 'foo;bar'; | ||||||
980 | |||||||
981 | =head2 url_unescape | ||||||
982 | |||||||
983 | my $str = url_unescape $escaped; | ||||||
984 | |||||||
985 | Decode percent encoded characters in string as described in L |
||||||
986 | |||||||
987 | # "foo;bar" | ||||||
988 | url_unescape 'foo%3Bbar'; | ||||||
989 | |||||||
990 | =head2 xml_escape | ||||||
991 | |||||||
992 | my $escaped = xml_escape $str; | ||||||
993 | |||||||
994 | Escape unsafe characters C<&>, C |
||||||
995 | objects. | ||||||
996 | |||||||
997 | # "<div>" | ||||||
998 | xml_escape ' '; |
||||||
999 | |||||||
1000 | # " " |
||||||
1001 | use Mojo::ByteStream qw(b); | ||||||
1002 | xml_escape b(' '); |
||||||
1003 | |||||||
1004 | =head2 xor_encode | ||||||
1005 | |||||||
1006 | my $encoded = xor_encode $str, $key; | ||||||
1007 | |||||||
1008 | XOR encode string with variable length key. | ||||||
1009 | |||||||
1010 | =head1 SEE ALSO | ||||||
1011 | |||||||
1012 | L |
||||||
1013 | |||||||
1014 | =cut |