File Coverage

blib/lib/Data/Table/Text.pm
Criterion Covered Total %
statement 2595 3942 65.8
branch 786 1638 47.9
condition 255 1776 14.3
subroutine 378 986 38.3
pod 395 396 99.7
total 4409 8738 50.4


).join ' ', @$_} @tocs;
line stmt bran cond sub pod time code
1             #!/usr/bin/perl -I/home/phil/perl/cpan/DataTableText/lib/
2             #-------------------------------------------------------------------------------
3             # Write data in tabular text format.
4             # Philip R Brenan at gmail dot com, Appa Apps Ltd Inc, 2016-2020
5             #-------------------------------------------------------------------------------
6             # podDocumentation
7             # cd /home/phil/perl/cpan/DataTableText/; perl Build.PL && perl Build test && sudo perl Build install
8             # To escape an open parenthesis in a regular expression use: \x28, for close use: \x29
9             # E for exportable methods
10             # write binary data without complaints about wide characters
11             # formatTableHH hash with sub hash of {} fails to print see svgToDita
12             # runInParallel - processing statistics
13             # formatTable should optionally clear left columns identical to previous line
14             # checkKeys information should be formatted so it can be referred to in sub descriptions
15             # updateDocumentation - mark synopsis tests with #S an place in synopsis
16             package Data::Table::Text;
17 371     371   1481403 use v5.26;
  371         3710  
18             our $VERSION = 20210730; # Version
19 371     371   2226 use warnings FATAL => qw(all);
  371         742  
  371         11872  
20 371     371   1855 use strict;
  371         371  
  371         14840  
21 371     371   2597 use Carp qw(confess carp cluck);
  371         742  
  371         34132  
22 371     371   2597 use Cwd;
  371         742  
  371         35987  
23 371     371   2597 use Digest::MD5 qw(md5_hex);
  371         742  
  371         20776  
24 371     371   2597 use File::Path qw(make_path);
  371         371  
  371         27083  
25 371     371   2226 use File::Glob qw(:bsd_glob);
  371         371  
  371         87556  
26 371     371   296429 use File::Temp qw(tempfile tempdir);
  371         8909565  
  371         26712  
27 371     371   201824 use POSIX qw(:sys_wait_h strftime); # http://www.cplusplus.com/reference/ctime/strftime/
  371         2409645  
  371         2597  
28 371     371   785036 use Data::Dump qw(dump);
  371         2153284  
  371         23002  
29 371     371   181790 use IO::Socket::UNIX;
  371         4828565  
  371         2226  
30 371     371   473767 use JSON;
  371         3990847  
  371         2226  
31 371     371   225568 use MIME::Base64;
  371         223713  
  371         22631  
32 371     371   2968 use Scalar::Util qw(blessed reftype looks_like_number);
  371         742  
  371         21147  
33 371     371   245973 use Storable qw(store retrieve dclone);
  371         1111145  
  371         27825  
34 371     371   206647 use Time::HiRes qw(time gettimeofday);
  371         492317  
  371         1484  
35 371     371   69377 use B;
  371         1113  
  371         15211  
36 371     371   251167 use utf8;
  371         5565  
  371         1855  
37              
38             #D1 Time stamps # Date and timestamps as used in logs of long running commands.
39              
40             sub dateTimeStamp #I Year-monthNumber-day at hours:minute:seconds
41 3742     3742 1 252983 {strftime('%Y-%m-%d at %H:%M:%S', localtime)
42             }
43              
44             sub dateTimeStampName # Date time stamp without white space.
45 368     368 1 36064 {strftime('_on_%Y_%m_%d_at_%H_%M_%S', localtime)
46             }
47              
48             sub dateStamp # Year-monthName-day
49 368     368 1 24656 {strftime('%Y-%b-%d', localtime)
50             }
51              
52             sub versionCode # YYYYmmdd-HHMMSS
53 368     368 1 23552 {strftime('%Y%m%d-%H%M%S', localtime)
54             }
55              
56             sub versionCodeDashed # YYYY-mm-dd-HH:MM:SS
57 368     368 1 20976 {strftime('%Y-%m-%d-%H:%M:%S', localtime)
58             }
59              
60             sub timeStamp # hours:minute:seconds
61 64209     64209 1 3145880 {strftime('%H:%M:%S', localtime)
62             }
63              
64             sub microSecondsSinceEpoch # Micro seconds since unix epoch.
65 368     368 1 2944 {my ($s, $u) = gettimeofday();
66 368         2944 $s*1e6 + $u
67             }
68              
69             #D1 Command execution # Various ways of processing commands and writing results.
70              
71             sub ddd(@) # Dump data
72 0     0 1 0 {my (@data) = @_; # Messages
73 0         0 my $m = dump(@_); # Dump data
74              
75 0 0       0 unless(&onAws) # Not on AWS
76 0         0 {my ($p, $f, $l) = caller();
77 0         0 my $L = " at $f line $l"; # Message source location
78 0 0       0 if ($m =~ m(\A(.*?)\n(.*\Z))s) # Move and align location to make messages more readable
79 0         0 {$m = pad($1, 80)."$L\n$2"; # Location at end of first line
80             }
81             else
82 0         0 {$m = pad($m, 80).$L # Location at end of only line
83             }
84             }
85              
86 0         0 say STDERR $m; # Say message
87 0         0 $m # Return message produced
88             }
89              
90             sub fff($$@) # Confess a message with a line position and a file that Geany will jump to if clicked on.
91 0     0 1 0 {my ($line, $file, @m) = @_; # Line, file, messages
92              
93 0         0 my $m = join ' ', @m; # Time stamp each message
94 0 0       0 return unless $m =~ m(\S)s;
95             # $m =~ s(\n) ( )gs;
96 0         0 $m .= " called at $file line $line";
97 0         0 confess "$m\n"; # Confess
98             }
99              
100             my $mmm = 0; # Time of last message
101              
102             sub lll(@) # Log messages with a time stamp and originating file and line number.
103 0     0 1 0 {my (@messages) = @_; # Messages
104 0 0       0 my @m = map {defined($_) ? $_ : q(undef)} @_;
  0         0  
105 0 0       0 return unless (join '', @m) =~ m(\S)s;
106              
107 0 0       0 my $m = join '', map {m(\s\Z) ? $_ : qq($_ )} timeStamp, @m; # Time stamp each message
  0         0  
108 0         0 $mmm = time; # Update time of last message
109              
110 0 0       0 unless(&onAws) # Not on AWS
111 0         0 {my ($p, $f, $l) = caller();
112 0         0 my $L = " at $f line $l"; # Message source location
113 0 0       0 if ($m =~ m(\A(.*?)\n(.*\Z))s) # Move and align location to make messages more readable
114 0         0 {$m = pad($1, 80)."$L\n$2"; # Location at end of first line
115             }
116             else
117 0         0 {$m = pad($m, 80).$L # Location at end of only line
118             }
119             }
120              
121 0         0 say STDERR $m; # Say message
122 0         0 $m # Return message produced
123             }
124              
125             sub mmm(@) # Log messages with a differential time in milliseconds and originating file and line number.
126 0     0 1 0 {my (@messages) = @_; # Messages
127 0 0       0 my (@m) = map {defined ? $_ : q(undef)} @_;
  0         0  
128              
129 0 0       0 my $t = $mmm ? sprintf("%8.3f", time - $mmm) : timeStamp; # Time at start, delta there after.
130 0         0 $mmm = time; # Update time of last message
131              
132 0 0       0 my $m = join '', map {m(\s\Z) ? $_ : qq($_ )} $t, @_ ; # Time stamp each message
  0         0  
133              
134 0 0       0 unless(&onAws) # Not on AWS
135 0         0 {my ($p, $f, $l) = caller();
136 0         0 my $L = " at $f line $l"; # Message source location
137 0 0       0 if ($m =~ m(\A(.*?)\n(.*\Z))s) # Move and align location to make messages more readable
138 0         0 {$m = pad($1, 80)."$L\n$2"; # Location at end of first line
139             }
140             else
141 0         0 {$m = pad($m, 80).$L # Location at end of only line
142             }
143             }
144              
145 0         0 say STDERR $m; # Say message
146 0         0 $m # Return message produced
147             }
148              
149             sub xxx(@) # Execute a shell command optionally checking its response. The command to execute is specified as one or more strings which are joined together after removing any new lines. Optionally the last string can be a regular expression that is used to test any non blank output generated by the execution of the command: if the regular expression fails the command and the command output are printed, else it is suppressed as being uninteresting. If such a regular expression is not supplied then the command and its non blank output lines are always printed.
150 804     804 1 6968 {my (@cmd) = @_; # Command to execute followed by an optional regular expression to test the results
151 804 50       7236 @cmd or confess "No command\n"; # Check that there is a command to execute
152 804   33     8576 $_ or confess "Missing command component\n" for @cmd; # Check that there are no undefined command components
153 804         3216 my $success = $cmd[-1]; # Error check if present
154 804         11792 my $check = ref($success) =~ /RegExp/i; # Check for error check
155 804 50       6968 pop @cmd if $check; # Remove check from command
156 804         6700 my $cmd = join ' ', @cmd; # Command to execute
157 804 50       11256 say STDERR $cmd unless $check; # Print the command unless there is a check in place
158 804   50     4556 my $response = eval {qx($cmd 2>&1)} // "No such command"; # Execute command
  804         32911204  
159 804         10720 $response =~ s/\s+\Z//s; # Remove trailing white space from response
160 804 50 33     10720 say STDERR $response if $response and !$check; # Print non blank error message
161 804 0 33     9916 confess $response if $response and $check and $response !~ m/$success/; # Error check if an error checking regular expression has been supplied
      33        
162 804 50 33     10184 confess $response if $response and $response =~ m/Syntax error:.*unexpected/; # Check for a particularly annoying error
163 804         133196 $response
164             } # xxx
165              
166             sub xxxr($;$) #I Execute a command B<$cmd> via bash on the server whose ip address is specified by B<$ip> or returned by L. The command will be run using the userid listed in F<.ssh/config>
167 0     0 1 0 {my ($cmd, $ip) = @_; # Command string, optional ip address
168 0   0     0 my $i = $ip // &awsIp; # Ip address
169 0 0       0 return undef unless confirmHasCommandLineCommand(q(ssh)); # Confirm we have ssh
170 0         0 my $c = qq(ssh $i "$cmd 2>&1"); # Command
171 0         0 lll $c;
172 0         0 my $r = eval {qx($c)}; # Execute command remotely
  0         0  
173 0 0       0 lll $r if $r;
174 0         0 $r
175             } # xxxr
176              
177             sub yyy($) # Execute a block of shell commands line by line after removing comments - stop if there is a non zero return code from any command.
178 0     0 1 0 {my ($cmd) = @_; # Commands to execute separated by new lines
179 0         0 for(split /\n/, $cmd) # Split commands on new lines
180 0         0 {s(#.*\Z)()gs; # Remove comments
181 0 0 0     0 next if !$_ or m(\A\s*\Z); # Skip blank lines
182 0         0 lll $_; # Say command
183 0         0 print STDERR $_ for qx($_); # Execute command
184 0         0 say STDERR '';
185             }
186             } # yyy
187              
188             sub zzz($;$$$) # Execute lines of commands after replacing new lines with && then check that the pipeline execution results in a return code of zero and that the execution results match the optional regular expression if one has been supplied; confess() to an error if either check fails. To execute remotely, add "ssh ... 'echo start" as the first line and "echo end'" as the last line with the commands to be executed on the lines in between.
189 742     742 1 66780 {my ($cmd, $success, $returnCode, $message) = @_; # Commands to execute - one per line with no trailing &&, optional regular expression to check for acceptable results, optional regular expression to check the acceptable return codes, message of explanation if any of the checks fail
190 742 50       7791 $cmd or confess "No command\n"; # Check that there is a command to execute
191 742         2226 my @c; # Commands
192 742         7791 for(split /\n/, $cmd) # Split commands on new lines
193 1484         6678 {s(#.*\Z)()gs; # Remove comments
194 1484 50       14098 next unless m(\S); # Skip blank lines
195 1484         6678 push @c, $_; # Save command
196             }
197 742         4081 my $c = join ' && ', @c; # Command string to execute
198 742         2990631 my $r = qx($c 2>&1); # Execute command
199 742         27454 my $R = $?;
200 742         39326 $r =~ s/\s+\Z//s; # Remove trailing white space from response
201              
202 742 50 0     1671726 confess "Error:\n". # Check the error code and results
    100 33        
      66        
      66        
203             ($message ? "$message\n" : ''). # Explanation if supplied
204             "$cmd\n". # Commands being executed
205             "Return code: $R\n". # Return code
206             "Result:\n$r\n" if # Output from commands so far
207             $R && (!$returnCode or $R !~ /$returnCode/) or # Return code not zero and either no return code check or the return code checker failed
208             $success && $r !~ m/$success/s; # Results check failed
209 371         31535 $r
210             } # zzz
211              
212             sub execPerlOnRemote($;$) #I Execute some Perl B<$code> on the server whose ip address is specified by B<$ip> or returned by L.
213 0     0 1 0 {my ($code, $ip) = @_; # Code to execute, optional ip address
214 0         0 my $file = writeFile(fpe(&temporaryFolder, qw(code pl)), $code); # Create code file
215 0         0 copyFileToRemote($file); # Copy code to server
216 0         0 say STDERR xxxr(qq(perl $file 2>&1)); # Execute code on server and return its output
217             }
218              
219             sub parseCommandLineArguments(&$;$) # Call the specified B<$sub> after classifying the specified array of [arguments] in B<$args> into positional and keyword parameters. Keywords are always preceded by one or more B<-> and separated from their values by B<=>. $sub([$positional], {keyword=>value}) will be called with a reference to an array of positional parameters followed by a reference to a hash of keywords and their values. The value returned by $sub will be returned to the caller. The keywords names will be validated if B<$valid> is either a reference to an array of valid keywords names or a hash of {valid keyword name => textual description}. Confess with a table of valid keywords definitions if $valid is specified and an invalid keyword argument is presented.
220 1113     1113 1 6307 {my ($sub, $args, $valid) = @_; # Sub to call, list of arguments to parse, optional list of valid parameters else all parameters will be accepted
221              
222             my %valid = sub # Valid keywords
223 1113 100   1113   6678 {return () unless $valid; # No keywords definitions
224 742 50       8162 return map {lc($_)=>0} @$valid if ref($valid) =~ m(array)is; # Keyword names as an array but with no explanation
  2226         13727  
225 0         0 %$valid # Hash of keyword name=>explanation
226 1113         12614 }->();
227              
228 1113         8904 my %keywords;
229             my @positionals;
230 1113         6678 for my $arg(@$args) # Each arg
231 5565 100       46375 {if ($arg =~ m/\A-+(\S+?)\s*(=\s*(.+)\s*)?\Z/) # Keyword parameters with leading and trailing blanks removed
232 3710 100 100     22631 {if ($valid and !defined($valid{lc($1)})) # Validate keyword name
233 371         742 {my @s;
234 371         3710 for my $k(sort keys %valid) # Create a table of valid keywords
235 1113 50       3710 {if (my $v = $valid{$k})
236 0         0 {push @s, [$k, $v];
237             }
238             else
239 1113         5936 {push @s, [$k];
240             }
241             }
242 371 50       3710 if (@s) # Format error message
243 371         7420 {my $s = formatTable(\@s, [qw(Keyword Description)]);
244 371         131334 confess "Invalid parameter: $arg\nValid keyword parameters are:\n$s\n";
245             }
246             else
247 0         0 {confess "Invalid parameter: $arg\n";
248             }
249             }
250 3339         23744 $keywords{lc($1)} = $3; # Save valid keyword parameter
251             }
252             else # Positional parameter
253 1855         4823 {push @positionals, $arg;
254             }
255             }
256 742         24486 $sub->([@positionals], {%keywords})
257             } # parseCommandLineArguments
258              
259             sub call(&;@) # Call the specified B<$sub> in a separate child process, wait for it to complete, then copy back the named B<@our> variables from the child process to the calling parent process effectively freeing any memory used during the call.
260 1110     1110 1 6292 {my ($sub, @our) = @_; # Sub to call, names of our variable names with preceding sigils to copy back
261 1110         4811 my ($package) = caller; # Caller's package
262 1110         8509 my $folder = &temporaryFolder; # Folder for returned data files
263 1110         1016744 my $pid = fork; # Fork
264 1110 50       87533 if (!defined($pid)) # Fork failed
    100          
265 0         0 {confess "Unable to fork!\n";
266             }
267             elsif ($pid == 0) # Fork - child
268 3         1165 {&$sub; # Execute the sub
269 3         72 my @save = ''; # Code to copy back our variables
270 3         78 for my $o(@our) # Each variable
271 9         262 {my ($sigil, $var) = $o =~ m(\A(.)(.+)\Z)s; # Sigil, variable name
272 9         78 my $our = $sigil.$package.q(::).$var; # Add caller's package to variable name
273 9         61 my $char = ord($sigil); # Differentiate between variables with the same type but different sigils
274 9         207 my $file = fpe($folder, qq(${$}$var$char), q(data)); # File for this variable
275 9         92 push @save, <
276             store \\$our, q($file);
277             END
278             }
279 3         46 my $save = join "\n", @save; # Perl code to store our variables
280 3         857 eval $save; # Evaluate code to store our variables
281 3 50       543 confess $@ if $@; # Confess any errors
282 3         16338 exit; # End of child process
283             }
284             else # Fork - parent
285 1107         4431984564 {waitpid $pid,0; # Wait for child
286 1107         40222 my @save = ''; # Code to retrieve our variables
287 1107         5540 my @file; # Transfer files
288 1107         18461 for my $o(@our)
289 3321         73071 {my ($sigil, $var) = $o =~ m(\A(.)(.+)\Z)s; # Sigil, variable name
290 3321         22527 my $our = $sigil.$package.q(::).$var; # Add caller's package to variable name
291 3321         11446 my $char = ord($sigil); # Differentiate between variables with the same type but different sigils
292 3321         49084 my $file = fpe($folder, qq($pid$var$char), q(data)); # Save file
293 3321         21031 push @save, <
294             $our = ${sigil}{retrieve q($file)};
295             END
296 3321         31423 push @file, $file; # Remove transfer files
297             }
298 1107         8498 my $save = join "\n", @save;
299 1107         338396 eval $save; # Evaluate perl code
300 1107         123619 my $r = $@; # Save result
301 1107         25839 clearFolder($folder, scalar(@our)+1); # Remove transfer files
302 1107 50       194498 confess "$r\n$save\n" if $r; # Confess to any errors
303             }
304             } # call
305              
306             #D1 Files and paths # Operations on files and paths.
307             #D2 Statistics # Information about each file.
308              
309             sub fileSize($) # Get the size of a B<$file> in bytes.
310 96326     96326 1 42966498 {my ($file) = @_; # File name
311 96326 50       78573216 return (stat($file))[7] if -e $file; # Size if file exists
312             undef # File does not exist
313 0         0 }
314              
315             sub fileLargestSize(@) # Return the largest B<$file>.
316 158     158 1 1106 {my (@files) = @_; # File names
317 1422         2370 my ($l) = map {$$_[1]} sort {$$b[0] <=> $$a[0]} # Largest file
  3002         4898  
318 158   50     632 map {[fileSize($_)//0, $_]} @files;
  1422         5846  
319 158         4740 $l
320             }
321              
322             sub folderSize($) # Get the size of a B<$folder> in bytes.
323 0     0 1 0 {my ($folder) = @_; # Folder name
324 0 0       0 return undef unless -d $folder; # Not a folder
325 0 0       0 return undef unless confirmHasCommandLineCommand(q(du)); # Confirm we have the disk used command
326 0         0 my $s = qx(du -s $folder); # Folder size
327 0         0 $s =~ s(\s.*\Z) ()gsr # nnnn folder
328             }
329              
330             sub fileMd5Sum($) # Get the Md5 sum of the content of a B<$file>.
331 536     536 1 1340 {my ($file) = @_; # File or string
332 536 50 33     13132 if ($file !~ m(\0|\n|\A\.|\A\/\Z)s and -e $file) # From file - this is not entirely satisfactory.
333 536         2144 {my $s = readBinaryFile($file);
334 536         4556 return md5_hex($s);
335             }
336             else # From string - convoluted but necessary to avoid L problems
337 0         0 {cluck "Deprecated: use stringMd5Sum instead";
338 0         0 return stringMd5Sum($file);
339             }
340             }
341              
342             sub guidFromMd5($) # Create a guid from an md5 hash.
343 536     536 1 1876 {my ($m) = @_; # Md5 hash
344 536 50 0     1876 length($m) == 32 or confess "Not an md5 hash: ". ($m//"undef");
345 536         5360 join '-', q(GUID), substr($m, 0, 8), substr($m, 8, 4), substr($m, 12, 4), # Uppercase might be needed to meet the strictest definition of a GUID
346             substr($m, 16, 4), substr($m, 20);
347             }
348              
349             sub md5FromGuid($) # Recover an md5 sum from a guid.
350 268     268 1 804 {my ($G) = @_; # Guid
351 268 50       2948 length($G) >= 41 or confess "Incorrect length for guid: $G"; # Check guid
352 268         1072 my $g = substr($G, 0, 41);
353 268 50       7504 return $g =~ s(guid|-) ()igsr if $g =~ m(\AGUID-[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}\Z)is;
354 0         0 confess "Incorrect format for guid: $g";
355             }
356              
357             sub guidFromString($) # Create a guid representation of the L of the content of a string.
358 268     268 1 1072 {my ($string) = @_; # String
359 268         804 guidFromMd5 &stringMd5Sum($string);
360             }
361              
362             sub fileModTime($) # Get the modified time of a B<$file> as seconds since the epoch.
363 368     368 1 3680 {my ($file) = @_; # File name
364 368   50     14720 (stat($file))[9] // 0
365             }
366              
367             sub fileOutOfDate(&$@) # Calls the specified sub B<$make> for each source file that is missing and then again against the B<$target> file if any of the B<@source> files were missing or the $target file is older than any of the @source files or if the target does not exist. The file name is passed to the sub each time in $_. Returns the files to be remade in the order they should be made.
368 0     0 1 0 {my ($make, $target, @source) = @_; # Make with this sub, target file, source files
369 0         0 my $exists = -e $target; # Existence of target
370 0         0 my @missing = grep {!-e $_} @source; # Missing files that do not exist will need to be remade
  0         0  
371 0 0 0     0 push @missing, $target unless $exists and !@missing; # Add the target if there were missing files
372 0 0       0 if (!@missing) # If there were no missing files that forced a remake, then check for a source file younger than the target that would force a remake of the target
373 0         0 {my $t = fileModTime($target); # Time of target
374 0 0 0     0 if (grep {-e $$_[0] and $$_[0] ne $target and $$_[1] > $t} # Target will have to be remade if there are younger source files
  0 0       0  
375 0         0 map {[$_, fileModTime($_)]}
376             @source)
377 0         0 {@missing = $target;
378             }
379             }
380 0         0 my %remade; # Files that have been remade
381             my @order; # Files that have been remade in make order
382 0         0 for(@missing)
383 0 0       0 {&$make, push @order, $_ unless $remade{$_}++; # Make each missing file once and then the target file
384             }
385             @order # Return a list of the files that were remade
386 0         0 } # fileOutOfDate
387              
388             sub firstFileThatExists(@) # Returns the name of the first file from B<@files> that exists or B if none of the named @files exist.
389 371     371 1 1855 {my (@files) = @_; # Files to check
390 371         1113 for(@files)
391 742 100       16695 {return $_ if -e $_;
392             }
393             undef # No such file
394 0         0 } # firstFileThatExists
395              
396             sub fileInWindowsFormat($) # Convert a unix B<$file> name to windows format
397 268     268 1 1072 {my ($file) = @_; # File
398 268         3216 $file =~ s(\/) (\\)gsr
399             }
400              
401             #D2 Components # File names and components.
402              
403             #D3 Fusion # Create file names from file name components.
404              
405             sub onWindows #P Are we on windows
406 181285     181285 1 147724618 {$^O =~ m(MSWin32)
407             }
408              
409             sub onMac #P Are we on mac
410 639     639 1 17748 {$^O =~ m(darwin)
411             }
412              
413             sub filePathSeparatorChar #P File path separator
414 37193 50   37193 1 442333 {onWindows ? '\\' : '/';
415             }
416              
417             sub denormalizeFolderName($) #P Remove any trailing folder separator from a folder name.
418 188756     188756 1 23574305 {my ($name) = @_; # Folder name
419 188756         52971448 $name =~ s([\/\\]+\Z) ()gsr;
420             }
421              
422             sub renormalizeFolderName($) #P Normalize a folder name by ensuring it has a single trailing directory separator.
423 13128     13128 1 116522 {my ($name) = @_; # Name
424 13128         127752 ($name =~ s([\/\\]+\Z) ()gsr).filePathSeparatorChar; # Put a trailing / on the folder name
425             }
426              
427             sub prefferedFileName($) #P Normalize a file name
428 110332     110332 1 27362489 {my ($name) = @_; # Name
429 110332 50       83977490 onWindows ? $name =~ s([\/\\]+) (\\)gsr :
430             $name =~ s([\/\\]+) (/)gsr ;
431             }
432              
433             sub filePath(@) # Create a file name from a list of names. Identical to L.
434 92213     92213 1 47066324 {my (@file) = @_; # File name components
435 92213   50     33800284 defined($_) or confess "Missing file component\n" for @file; # Check that there are no undefined file components
436 92213         18361691 my @components = grep {$_} map {denormalizeFolderName($_)} @file; # Skip blank components
  188756         21179015  
  188756         112014585  
437 92213 100       58948567 return '' unless @components; # No components resolves to '' rather than '/'
438 91842         33093403 prefferedFileName join '/', @components; # Join separate components
439             }
440              
441             sub filePathDir(@) # Create a folder name from a list of names. Identical to L.
442 13128     13128 1 113335 {my (@file) = @_; # Directory name components
443 13128         81534 my $file = filePath(@_);
444 13128 100       139534 return '' unless $file; # No components resolves to '' rather than '/'
445 12757         89838 renormalizeFolderName($file) # Normalize with trailing separator
446             }
447              
448             sub filePathExt(@) #I Create a file name from a list of names the last of which is assumed to be the extension of the file name. Identical to L.
449 78590     78590 1 22380037 {my (@File) = @_; # File name components and extension
450 78590 50       2410008 my @file = grep{defined and /\S/} @_; # Remove undefined and blank components
  240475         142245982  
451 78590 50       45597097 @file > 1 or confess "At least two non blank file name components required\n";
452 78590         7530011 my $x = pop @file;
453 78590         12384816 my $n = pop @file;
454 78590         18152257 my $f = "$n.$x";
455 78590 100       15089944 return $f unless @file;
456 76838         57278009 filePath(@file, $f)
457             }
458              
459 371     371   1637223 BEGIN{*fpd=*filePathDir}
460 371     371   11501 BEGIN{*fpe=*filePathExt}
461 371     371   1046962 BEGIN{*fpf=*filePath}
462              
463             #D3 Fission # Get file name components from a file name.
464              
465             sub fp($) # Get the path from a file name.
466 2516     2516 1 10745 {my ($file) = @_; # File name
467 2516 50       8575 $file or confess "File required";
468 2516 50       6495 if (onWindows)
469 0 0       0 {return '' unless $file =~ m(\\); # Must have a \ in it else no path
470 0         0 $file =~ s([^\\]*\Z) ()gsr
471             }
472             else
473 2516 50       20862 {return '' unless $file =~ m(/); # Must have a / in it else no path
474 2516         36656 $file =~ s([^/]*\Z) ()gsr
475             }
476             }
477              
478             sub fpn($) # Remove the extension from a file name.
479 742     742 1 2226 {my ($file) = @_; # File name
480 742 50       2968 $file or confess "File required";
481 742 50       1855 if (onWindows)
482 0 0       0 {return '' unless $file =~ m(\\); # Must have a \ in it else no path
483             }
484             else
485 742 50       6307 {return '' unless $file =~ m(/); # Must have a / in it else no path
486             }
487 742         6678 $file =~ s(\.[^.]+?\Z) ()gsr
488             }
489              
490             sub fn($) #I Remove the path and extension from a file name.
491 2640     2640 1 11228 {my ($file) = @_; # File name
492 2640 50       11214 $file or confess "File required";
493 2640 50       5496 if (onWindows)
494 0         0 {$file =~ s(\A.*\\) ()gsr =~ s(\.[^.]+?\Z) ()gsr
495             }
496             else
497 2640         52363 {$file =~ s(\A.*/) ()gsr =~ s(\.[^.]+?\Z) ()gsr
498             }
499             }
500              
501             sub fne($) # Remove the path from a file name.
502 4088     4088 1 16754 {my ($file) = @_; # File name
503 4088 50       14566 $file or confess "File required";
504 4088 50       12615 if (onWindows)
505 0         0 {$file =~ s(\A.*\\) ()gsr;
506             }
507             else
508 4088         111457 {$file =~ s(\A.*/) ()gsr;
509             }
510             }
511              
512             sub fe($) # Get the extension of a file name.
513 2850     2850 1 9825 {my ($file) = @_; # File name
514 2850 50       8924 $file or confess "File required";
515 2850 50       18832 return '' unless $file =~ m(\.)s; # Must have a period
516 2850         16632 my $f = $file =~ s(\.[^.]*?\Z) ()gsr;
517 2850         23091 substr($file, length($f)+1)
518             }
519              
520             sub checkFile($) # Return the name of the specified file if it exists, else confess the maximum extent of the path that does exist.
521 1481     1481 1 9987 {my ($file) = @_; # File to check
522 1481 100       27777 unless(-e $file)
523 739         4799 {confess "Can only find the prefix (below) of the file (further below):\n".
524             matchPath($file)."\n$file\n";
525             }
526             $file
527 742         10388 }
528              
529             sub quoteFile($) # Quote a file name.
530 368     368 1 1840 {my ($file) = @_; # File name
531 368 50       2576 $file or confess "Undefined file to quote";
532 368         5888 $file =~ s(") (\\\")gs;
533 368         1104 $file =~ s(\$) (\\\$)gs;
534 368         2208 qq(\"$file\")
535             }
536              
537             sub removeFilePrefix($@) # Removes a file B<$prefix> from an array of B<@files>.
538 742     742 1 5936 {my ($prefix, @files) = @_; # File prefix, array of file names
539 742         2597 my @f = map {s(\A$prefix) ()r} @files;
  1113         20405  
540 742 50 66     7791 return $f[0] if @f == 1 and !wantarray; # Special case of wanting one file in scalar context
541             @f
542 742         6307 }
543              
544             sub swapFilePrefix($$;$) # Swaps the start of a B<$file> name from a B<$known> name to a B<$new> one if the file does in fact start with the $known name otherwise returns the original file name as it is. If the optional $new prefix is omitted then the $known prefix is removed from the $file name.
545 541     541 1 3258 {my ($file, $known, $new) = @_; # File name, existing prefix, optional new prefix defaults to q()
546 541         1352 my $L = length($file);
547 541         1370 my $l = length($known);
548 541 50       3233 if ($L >= $l)
549 541 50       3795 {if (substr($file, 0, $l) eq $known)
550 541   100     9304 {return ($new//q()).substr($file, $l);
551             }
552 0         0 return $file;
553             }
554 0         0 confess "Known $l longer than file name $L:\n$known\n$file\n";
555             } # swapFilePrefix
556              
557             sub setFileExtension($;$) # Given a B<$file>, change its extension to B<$extension>. Removes the extension if no $extension is specified.
558 1181     1181 1 8638 {my ($file, $extension) = @_; # File name, optional new extension
559 1181 50       5856 return $file =~ s(\.\w+\Z) ()sr unless defined $extension; # Remove extension
560 1181         6769 my $ext = $extension =~ s(\A\.+) ()gsr; # Remove leading dots
561 1181 50       6521 return $file unless $ext; # No extension after dot removal
562 1181         13889 ($file =~ s(\.\w+\Z) ()gsr).q(.).$ext; # Change extension
563             } # setFileExtension
564              
565             sub swapFolderPrefix($$$) # Given a B<$file>, swap the folder name of the $file from B<$known> to B<$new> if the file $file starts with the $known folder name else return the $file as it is.
566 268     268 1 2144 {my ($file, $known, $new) = @_; # File name, existing prefix, new prefix
567 268         1072 swapFilePrefix($file, fpd($known), fpd($new));
568             } # swapFolderPrefix
569              
570             sub fullyQualifiedFile($;$) # Check whether a B<$file> name is fully qualified or not and, optionally, whether it is fully qualified with a specified B<$prefix> or not.
571 1072     1072 1 2680 {my ($file, $prefix) = @_; # File name to test, file name prefix
572 1072 100       6700 return $file =~ m(\A/)s unless $prefix; # Check against /
573 536         2680 index($file, $prefix) == 0 # Check against supplied prefix
574             } # fullyQualifiedFile
575              
576             sub fullyQualifyFile($) # Return the fully qualified name of a file.
577 0     0 1 0 {my ($file) = @_; # File name
578 0 0       0 return $file if fullyQualifiedFile($file); # File is already fully qualified
579 0         0 absFromAbsPlusRel(¤tDirectory, $file); # Fully qualify file name
580             } # fullyQualifyFile
581              
582             sub removeDuplicatePrefixes($) # Remove duplicated leading directory names from a file name.
583 1113     1113 1 4081 {my ($file) = @_; # File name
584 1113 100       12243 return $file unless $file =~ m(/)s; # No path to deduplicate
585 742 50       4081 return $file if $file =~ m(\A[/.]); # Later
586 742         4452 my ($p, @p) = split m(/), $file;
587 742   66     5936 shift @p while @p && $p[0] eq $p;
588 742         6307 join "/", $p, @p;
589             } # removeDuplicatePrefixes
590              
591             sub containingFolderName($) # The name of a folder containing a file
592 35     35 1 114 {my ($file) = @_; # File name
593 35         346 my @p = split m(/), $file;
594 35 50       375 return $p[-2] if @p > 1;
595 0         0 confess "No folder name provided";
596             } # containingFolderName
597             #D2 Position # Position in the file system.
598              
599             sub currentDirectory # Get the current working directory.
600 371     371 1 10759 {renormalizeFolderName(getcwd)
601             } # currentDirectory
602              
603             sub currentDirectoryAbove # Get the path to the folder above the current working folder.
604 0     0 1 0 {my $path = currentDirectory;
605 0         0 my @path = split m(/)s, $path;
606 0 0 0     0 shift @path if @path and $path[0] =~ m/\A\s*\Z/;
607 0 0       0 @path or confess "No directory above\n:".currentDirectory, "\n";
608 0         0 pop @path;
609 0         0 my $r = shift @path;
610 0         0 filePathDir("/$r", @path);
611             } # currentDirectoryAbove
612              
613             sub parseFileName($) # Parse a file name into (path, name, extension) considering .. to be always part of the path and using B to mark missing components. This differs from (fp, fn, fe) which return q() for missing components and do not interpret . or .. as anything special
614 4720     4720 1 13274 {my ($file) = @_; # File name to parse
615 4720 50       12099 defined($file) or confess "File required";
616 4720 100 66     42008 return ($file) if $file =~ m{\/\Z}s or $file =~ m/\.\.\Z/s; # Its a folder
617 3607 100       19664 if ($file =~ m/\.[^\/]+?\Z/s) # The file name has an extension
618 2494 100       11110 {if ($file =~ m/\A.+[\/]/s) # The file name has a preceding path
619 2123         10409 {my @f = $file =~ m/(\A.+[\/])([^\/]*)\.([^\/]+?)\Z/s; # File components
620 2123         17046 return @f;
621             }
622             else # There is no preceding path
623 371         2226 {my @f = $file =~ m/(\A.+)\.([^\/]+?)\Z/s; # File components
624 371         2597 return (undef, @f)
625             }
626             }
627             else # The file name has no extension
628 1113 100       5936 {if ($file =~ m/\A.+[\/]/s) # The file name has a preceding path
    50          
    0          
629 742         3710 {my @f = $file =~ m/(\A.+\/)([^\/]+?)\Z/s; # File components
630 742         5194 return @f;
631             }
632             elsif ($file =~ m/\A[\/]./s) # The file name has a single preceding /
633 371         2968 {return (q(/), substr($file, 1));
634             }
635             elsif ($file =~ m/\A[\/]\Z/s) # The file name is a single /
636 0         0 {return (q(/));
637             }
638             else # There is no preceding path
639 0         0 {return (undef, $file)
640             }
641             }
642             } # parseFileName
643              
644             sub fullFileName # Full name of a file.
645 0     0 1 0 {my ($file) = @_; # File name
646 0 0       0 return $file if fullyQualifiedFile $file; # Already a full file name
647 0         0 absFromAbsPlusRel(currentDirectory, $file); # Relative to current folder
648             } # fullFileName
649              
650             sub relFromAbsAgainstAbs($$) #I Relative file from one absolute file B<$a> against another B<$b>.
651 16592     16592 1 65915 {my ($a, $b) = @_; # Absolute file to be made relative, against this absolute file.
652              
653 16592 100       51796 my $m = length($a) < length($b) ? length($a) : length($b); # Shortest length
654              
655 16592 50       64864 $a =~ m(\A/) or confess "$a is not absolute"; # Require absolute file names
656 16592 50       46128 $b =~ m(\A/) or confess "$b is not absolute";
657 16592         82610 $b =~ s([^/]+\Z) (); # Make the against file into a folder
658              
659 16592         27145 my $s = 0; # Position of last matching /
660              
661 16592         53136 for my $i(1..$m-1) # Locate first non matching character - the first character of both file names is / which matches
662 136859 100       312426 {if (substr($a, $i, 1) ne substr($b, $i, 1)) # First mismatch
    100          
663 13253         18344 {my $u = 0; # Number of jumps up from $b
664 13253         17231 my $p = $s; # Last /
665 13253         50415 ++$u while(($p = index($b, q(/), $p+1)) > -1); # Number of / to jump up
666 13253         94317 return ((q(../) x $u).substr($a, $s+1)) =~ s(\A\Z) (./)gsr; # Jumps up from $b plus remainder of $a avoiding a blank result
667             }
668             elsif (substr($a, $i, 1) eq q(/)) # Agree up to this / at least
669 27310         39821 {$s = $i;
670             }
671             }
672 3339         7049 my $u = 0; # Number of jumps up from $b
673 3339         4452 my $p = $s; # Last /
674 3339         13727 ++$u while(($p = index($b, q(/), $p+1)) > -1); # Number of / to jump up
675 3339         29680 ((q(../) x $u).substr($a, $s+1)) =~ s(\A\Z) (./)gsr; # Jumps up from $b plus remainder of $a avoiding a blank result
676             }
677              
678             sub absFromAbsPlusRel($$) #I Absolute file from an absolute file B<$a> plus a relative file B<$r>. In the event that the relative file $r is, in fact, an absolute file then it is returned as the result.
679 17437     17437 1 67893 {my ($a, $r) = @_; # Absolute file, relative file
680              
681 17437 50       52311 return $r if $r =~ m(\A/); # Return absolute file if such is supplied
682 17437 50       57876 $a =~ m(\A/) or confess "$a is not absolute"; # Require absolute file name
683 17437         80136 $a =~ s([^/]+\Z) (); # Make the absolute file into a folder
684 17437         31535 $r =~ s(\A\./) (); # Remove any leading ./ from relative file
685 17437         30422 $r =~ s(\.\.\Z) (../); # Make trailing .. into a folder
686              
687 17437         42665 my $R = qq($a$r); # Combine and ...
688 17437         105364 undef while $R =~ s([^/]+/\.\./) (); # Squeeze out jumps
689              
690 17437         84588 $R
691             }
692              
693             sub absFile($) # Return the name of the given file if it a fully qualified file name else returns B. See: L to check the initial prefix of the file name as well.
694 1484     1484 1 3339 {my ($file) = @_; # File to test
695 1484 100       9275 return $file if $file =~ m(\A/);
696             undef
697 742         2597 }
698              
699             sub sumAbsAndRel(@) # Combine zero or more absolute and relative names of B<@files> starting at the current working folder to get an absolute file name.
700 371     371 1 1855 {my (@files) = @_; # Absolute and relative file names
701 371         5565 unshift @files, currentDirectory;
702 371         2597 while(@files > 1)
703 1113         2968 {my $a = shift @files;
704 1113         1855 my $b = shift @files;
705 1113 100       2968 unshift @files, absFile($b) ? $b : absFromAbsPlusRel($a, $b);
706             }
707 371         2597 $files[0]
708             } # sumAbsAndRel
709              
710             #D2 Temporary # Temporary files and folders
711              
712             sub temporaryFile # Create a new, empty, temporary file.
713 6574     6574 1 61124 {my ($fh, $filename) = tempfile;
714 6574         3552202 $filename
715             }# temporaryFile
716              
717             sub temporaryFolder # Create a new, empty, temporary folder.
718 6114     6114 1 144133 {my $d = tempdir();
719 6114         5060570 $d =~ s/[\/\\]+\Z//s;
720 6114         43746 $d.filePathSeparatorChar;
721             } # temporaryFolder
722              
723 371     371   1327067 BEGIN{*temporaryDirectory=*temporaryFolder}
724              
725             #D2 Find # Find files and folders below a folder.
726              
727             sub findAllFilesAndFolders($$) #P Find all the files and folders under a folder.
728 10048     10048 1 83972 {my ($folder, $dirs) = @_; # Folder to start the search with, true if only folders are required
729 10048         21801 my @files; # Files
730              
731 10048 50       73908 if (onWindows) # windows
732 0 0       0 {my $c = qq(powershell Get-ChildItem -Recurse -Name $folder ).
733             ($dirs ? '-Directory' : '-File');
734 0         0 my $r = qx($c);
735 0         0 $r =~ s(\\) (/)g;
736 0         0 my @r = map {qq($folder$_)} split /\n/, $r;
  0         0  
737 0 0       0 @r = map {$_.filePathSeparatorChar} @r if $dirs;
  0         0  
738 0         0 unshift @r, $folder; # Find includes the start folder but windows does not
739 0         0 return sort @r;
740             }
741              
742 10048 50       314795 return undef unless confirmHasCommandLineCommand(q(find)); # Confirm we have find
743 10048 100       147697 my $c = qq(find "$folder" -print0 -type ).($dirs ? 'd' : 'f'); # Use find command to find files
744 10048         69314489 my $res = qx($c); # Execute find command
745 10048 50       267841 defined($res) or confess "No result from find command below\n$c\n"; # Find failed for some reason
746 10048         169478 utf8::decode($res); # Decode unicode file names
747 10048         858742 sort split /\0/, $res # Split out file names on \0
748             } # findAllFilesAndFolders
749              
750             sub findFiles($;$) # Find all the files under a B<$folder> and optionally B<$filter> the selected files with a regular expression.
751 4936     4936 1 69694 {my ($folder, $filter) = @_; # Folder to start the search with, optional regular expression to filter files
752 4936         71697 my @files; # Files
753 4936         89111 for(findAllFilesAndFolders($folder, 0)) # All files and folders
754 73763 100       1013464 {next if -d $_; # Do not include folder names
755 64749 50 66     286910 next if $filter and $filter and !m($filter)s; # Filter out files that do not match the regular expression
      66        
756 64749         188175 push @files, $_;
757             }
758             @files
759 4936         204685 } # findFiles
760              
761             sub findDirs($;$) # Find all the folders under a B<$folder> and optionally B<$filter> the selected folders with a regular expression.
762 3823     3823 1 41838 {my ($folder, $filter) = @_; # Folder to start the search with, optional regular expression to filter files
763 3823 50       58134 return findAllFilesAndFolders($folder, 1) if onWindows; # All folders if on windows
764              
765 3823         14111 my @dir; # Directories
766 3823         52761 for(findAllFilesAndFolders($folder, 1)) # All files and folders
767 67082 100       919714 {next unless -d $_; # Include only folders
768 5672 0 33     148245 next if $filter and $filter and !m($filter)s; # Filter out directories that do not match the regular expression
      33        
769 5672         82652 push @dir, fpd($_);
770             }
771             @dir
772 3823         118373 } # findDirs
773              
774             sub fileList($) # Files that match a given search pattern interpreted by L.
775 736     736 1 6992 {my ($pattern) = @_; # Search pattern
776 736         160448 bsd_glob($pattern, GLOB_MARK | GLOB_TILDE)
777             } # fileList
778              
779             sub searchDirectoryTreesForMatchingFiles(@) #I Search the specified directory trees for the files (not folders) that match the specified extensions. The argument list should include at least one path name to be useful. If no file extensions are supplied then all the files below the specified paths are returned. Arguments wrapped in [] will be unwrapped.
780 921     921 1 12726 {my (@FoldersandExtensions) = @_; # Mixture of folder names and extensions
781 921 50       6096 my (@foldersandExtensions) = map {ref($_) ? @$_ : $_} @_;
  938         13832  
782              
783 921 100 66     3701 my @extensions = grep {$_ and !-d $_ and !m([\/])} @_; # Extensions are not directories
  938         40304  
784 921         5541 for(@extensions) # Prefix period to extension of not all ready there - however this can lead to errors if there happens to be a folder with the same name as an undotted extension.
785 17 50       476 {$_ = qq(\.$_) unless m(\A\.)s
786             }
787              
788 921 100       8436 my $ext = @extensions ? join '|', @extensions : undef; # Extensions
789 921         6628 my @file; # Files
790              
791 921         8053 for my $dir(@_) # Directories
792 938 100 66     37126 {next unless $dir && -d $dir; # Do not include folder names
793              
794 921         10378 for my $d(findAllFilesAndFolders($dir, 0)) # All files and folders beneath each folder
795 3199 100       68140 {next if -d $d; # Do not include folder names
796 2278 100 100     30826 push @file, $d if !$ext or $d =~ m(($ext)\Z)is; # Filter by extension if requested.
797             }
798             }
799             @file # Return files
800 921         57743 } # searchDirectoryTreesForMatchingFiles
801              
802             sub searchDirectoryTreeForSubFolders($) #I Search the specified directory under the specified folder for sub folders
803 368     368 1 4784 {my ($folder) = @_; # The folder at which to start the search
804 368         1472 my @f; # Folders found
805 368         6624 for my $d(findAllFilesAndFolders($folder, 0)) # All files and folders beneath the start folder
806 1840 100       54096 {push @f, $d if -d $d; # Do not include file names
807             }
808             @f # Return folder names
809 368         29440 } # searchDirectoryTreeForSubFolders
810              
811             sub hashifyFolderStructure(@) # Hashify a list of file names to get the corresponding folder structure.
812 17     17 1 153 {my (@files) = @_; # File names
813 17         51 my %h;
814 17         289 for my $f(@files) # Map each file
815 68         374 {my @f = split m(/), $f;
816 68         221 my $s = join '', map {q({).dump($_).q(})} @f; # Hashify directory structure
  272         21522  
817 68         6137 my $c = "\$h$s = ".dump($f); # Load targets
818 68         10251 eval $c;
819 68 50       561 confess $@ if $@;
820             }
821 17         289 \%h
822             } # hashifyFolderStructure
823              
824             sub countFileExtensions(@) # Return a hash which counts the file extensions in and below the folders in the specified list.
825 0     0 1 0 {my (@folders) = @_; # Folders to search
826 0         0 my %ext;
827 0         0 for my $dir(@folders) # Directories
828 0 0       0 {next unless -d $dir;
829 0         0 for my $file(findAllFilesAndFolders($dir, 0)) # All files and folders under the current folder
830 0 0       0 {next if -d $file; # Do not include folder names
831 0         0 $ext{fe $file}++;
832             }
833             }
834 0         0 \%ext # Return extension counts
835             } # countFileExtensions
836              
837             sub countFileTypes($@) # Return a hash which counts, in parallel with a maximum number of processes: B<$maximumNumberOfProcesses>, the results of applying the B command to each file in and under the specified B<@folders>.
838 0     0 1 0 {my ($maximumNumberOfProcesses, @folders) = @_; # Maximum number of processes to run in parallel, Folders to search
839              
840 0 0       0 return undef unless confirmHasCommandLineCommand(q(file)); # Confirm we have file command
841              
842 0         0 my %ext;
843 0         0 my @files = squareArray(searchDirectoryTreesForMatchingFiles(@folders)); # Find files
844              
845 0         0 my $p = newProcessStarter($maximumNumberOfProcesses); # Process starter
846 0         0 $p->totalToBeStarted = scalar @files;
847              
848 0         0 for my $block(@files) # Apply file to each file
849             {$p->start(sub
850 0     0   0 {my @r;
851 0         0 for my $file(@$block)
852 0         0 {my $f = quoteFile($file);
853 0         0 my $r = qx(file $f);
854 0         0 push @r, trim(swapFilePrefix($r, $file.q(:), q())); # Remove file name from output
855             }
856 0         0 [@r]
857 0         0 });
858             }
859              
860 0         0 for my $type(deSquareArray($p->finish)) # Consolidate results
861 0         0 {$ext{$type}++;
862             }
863              
864 0         0 \%ext
865             } # countFileTypes
866              
867             sub matchPath($) # Return the deepest folder that exists along a given file name path.
868 1110     1110 1 5556 {my ($file) = @_; # File name
869 1110 100       18888 return $file if -e $file; # File exists so nothing more to match
870 739         11453 my @path = split /[\/\\]/, $file; # Split path into components
871 739         4437 while(@path) # Remove components one by one
872 1107         2585 {pop @path; # Remove deepest component and try again
873 1107         3692 my $path = join filePathSeparatorChar, @path, ''; # Containing folder
874 1107 100       2657962 return $path if -d $path; # Containing folder exists
875             }
876             '' # Nothing matches
877 0         0 } # matchPath
878              
879             sub findFileWithExtension($@) # Find the first file that exists with a path and name of B<$file> and an extension drawn from <@ext>.
880 371     371 1 4452 {my ($file, @ext) = @_; # File name minus extensions, possible extensions
881 371         3339 for my $ext(@ext) # Each extension
882 1113         4081 {my $f = fpe($file, $ext); # Possible file
883 1113 100       31164 return $ext if -e $f; # First matching file
884             }
885             undef # No matching file
886 0         0 } # findFileWithExtension
887              
888             sub clearFolder($$;$) #I Remove all the files and folders under and including the specified B<$folder> as long as the number of files to be removed is less than the specified B<$limitCount>. Sometimes the folder can be emptied but not removed - perhaps because it a link, in this case a message is produced unless suppressed by the optional B<$nomsg> parameter.
889 4630     4630 1 105649 {my ($folder, $limitCount, $noMsg) = @_; # Folder, maximum number of files to remove to limit damage, no message if the folder cannot be completely removed.
890 4630 100       140589 return unless -d $folder; # Only works on a folder that exists
891 3826         121034 my @files = findFiles($folder); # Find files to be removed
892 3826 100       53389 if (@files > $limitCount) # Limit the number of files that can be deleted to limit potential opportunity for damage
893 371         3339 {my $f = @files;
894 371         1663935 confess "Limit is $limitCount, but $f files under folder:\n$folder\n";
895             }
896 3455         84086 my @dirs = findDirs($folder); # These directories should be empty and thus removable after removing the files
897 3455         2315096 unlink $_ for @files; # Remove files
898 3455         264624 rmdir $_ for reverse @dirs; # Remove empty folders
899 3455 50 33     127824 unless($noMsg or onWindows)
900 3455 50       489635 {-e $folder and carp "Unable to completely remove folder:\n$folder\n"; # Complain if the folder still exists
901             }
902             } # clearFolder
903              
904             #D2 Read and write files # Read and write strings from and to files creating paths to any created files as needed.
905              
906             sub readFile($) #I Return the content of a file residing on the local machine interpreting the content of the file as L.
907 9397     9397 1 46765 {my ($file) = @_; # Name of file to read
908 9397 50       39647 defined($file) or
909             confess "Cannot read undefined file\n";
910 9397 50       60887 $file =~ m(\n|\r) and
911             confess "File name contains a new line:\n=$file=\n";
912 9397 50       154975 -e $file or
913             confess "Cannot read file because it does not exist, file:\n$file\n";
914 371 50   371   2597 open(my $F, "<:encoding(UTF-8)", $file) or
  371         1113  
  371         2597  
  9397         483358  
915             confess "Cannot open file for unicode input, file:\n$file\n$!\n";
916 9397 100       6152685 if (wantarray) # Read as an array
917 1108         6273 {my @string = eval {<$F>};
  1108         34340  
918 1108 50       32201 $@ and confess "$@ reading file:\n$file\n";
919 1108         35805 return @string;
920             }
921             else # Read as a string
922 8289         83336 {local $/ = undef;
923 8289         19379 my $string = eval {<$F>};
  8289         289894  
924 8289 50       219460 $@ and confess "$@ reading file:\n$file\n";
925 8289         243123 return $string;
926             }
927             } # readFile
928              
929             sub readStdIn # Return the contents of STDIN and return the results as either an array or a string. Terminate with Ctrl-D if testing manually - STDIN remains open allowing this method to be called again to receive another block of data.
930 1 50   1 1 17 {if (wantarray) # Read as an array
931 0         0 {my @string = eval {};
  0         0  
932 0 0       0 $@ and confess "$@ reading STDIN\n";
933 0         0 return @string;
934             }
935             else # Read as a string
936 1         6 {local $/ = undef;
937 1         8 my $string = eval {};
  1         20  
938 1 50       19 $@ and confess "$@ reading STDIN\n";
939 1         12 return $string;
940             }
941             } # readStdIn
942              
943             sub readFileFromRemote($;$) #I Copy and read a B<$file> from the remote machine whose ip address is specified by B<$ip> or returned by L and return the content of $file interpreted as utf8 .
944 0     0 1 0 {my ($file, $ip) = @_; # Name of file to read, optional ip address of server
945 0   0     0 copyFileFromRemote($file, $ip // &awsIp); # Read from specified remote instance
946 0 0       0 if (wantarray)
947 0         0 {my @r = readFile($file);
948 0         0 return @r;
949             }
950             else
951 0         0 {my $r = readFile($file);
952 0         0 return $r;
953             }
954             } # readFileFromRemote
955              
956             sub evalFile($) # Read a file containing L content represented as L, L the content, confess to any errors and then return any result with L methods to access each hash element.
957 1840     1840 1 8096 {my ($file) = @_; # File to read
958 1840         11776 my $string = readFile($file);
959 1840         202032 my $res = eval $string;
960 1840 50       15824 $@ and confess "$@\nin file:\n$file\n";
961 1840         13248 reloadHashes($res);
962 1840         16560 $res
963             } # evalFile
964              
965             sub evalFileAsJson($) # Read a B<$file> containing L and return the corresponding L data structure.
966 736     736 1 2208 {my ($file) = @_; # File to read
967 736         3680 my $string = readFile($file);
968 736         4784 decodeJson($string);
969             } # evalFileAsJson
970              
971             sub evalGZipFile($) # Read a file compressed with L containing L content represented as L, L the content, confess to any errors and then return any result with L methods to access each hash element. This is slower than using L but does produce much smaller files, see also: L.
972 368     368 1 5152 {my ($file) = @_; # File to read
973 368         11040 my $string = readGZipFile($file);
974 368         109296 my $res = eval $string;
975 368 50       10304 $@ and confess "$@\n";
976 368         14352 reloadHashes($res);
977             } # evalGZipFile
978              
979             sub retrieveFile($) # Retrieve a B<$file> created via L. This is much faster than L as the stored data is not in text format.
980 370     370 1 1510 {my ($file) = @_; # File to read
981 370 50       7822 -e $file or confess "No such file: $file\n"; # Check file exists
982 370         2646 my $res = retrieve $file; # Retrieve file
983             # reloadHashes($res); ####TEST#### Causing problems when we try to reload large structures like Xref # Reload access methods
984 370         43838 $res
985             } # evalFile
986              
987             sub readUtf16File($) #P Read a file containing L encoded in utf-16.
988 0     0 1 0 {my ($file) = @_; # Name of file to read
989 0 0       0 defined($file) or
990             confess "Cannot read undefined file\n";
991 0 0       0 $file =~ m(\n|\r) and
992             confess "File name contains a new line:\n=$file=\n";
993 0 0       0 -e $file or
994             confess "Cannot read file because it does not exist, file:\n$file\n";
995 0 0       0 open(my $F, "<:encoding(UTF-16)", $file) or confess
996             "Cannot open file for utf16 input, file:\n$file\n$!\n";
997 0         0 local $/ = undef;
998 0         0 my $s = eval {<$F>};
  0         0  
999 0 0       0 $@ and confess $@;
1000 0         0 $s
1001             }
1002              
1003             sub readBinaryFile($) # Read a binary file on the local machine.
1004 3151     3151 1 10763 {my ($file) = @_; # File to read
1005 3151 50       37793 -e $file or
1006             confess "Cannot read binary file because it does not exist:\n$file\n";
1007 3151 50       96266 open my $F, "<$file" or
1008             confess "Cannot open binary file for input:\n$file\n$!\n";
1009 3151         14783 binmode $F;
1010 3151         18119 local $/ = undef;
1011 3151         117706 <$F>;
1012             } # readBinaryFile
1013              
1014             sub readGZipFile($) # Read the specified file containing compressed L content represented as L through L.
1015 736     736 1 6992 {my ($file) = @_; # File to read.
1016 736 50       8096 defined($file) or
1017             confess "Cannot read undefined file\n";
1018 736 50       13248 $file =~ m(\n|\r) and
1019             confess "File name contains a new line:\n=$file=\n";
1020 736 50       24656 -e $file or
1021             confess "Cannot read file because it does not exist, file:\n$file\n";
1022 736 50       19504 return undef unless confirmHasCommandLineCommand(q(gunzip)); # Confirm we have gunzip
1023 736 50       2620160 open(my $F, "gunzip < $file|") or # Unzip input file
1024             confess "Cannot open file for input, file:\n$file\n$!\n$?\n";
1025 736         101568 binmode($F, "encoding(UTF-8)");
1026 736         263856 local $/ = undef;
1027 736         1916912 my $string = <$F>;
1028 736         388976 $string # Resulting string
1029             } # readGZipFile
1030              
1031             sub makePath($) # Make the path for the specified file name or folder on the local machine. Confess to any failure.
1032 19591     19591 1 71647 {my ($file) = @_; # File or folder name
1033 19591         225576 my @path = split /[\\\/]+/, $file;
1034 19591 100       82015 return undef unless @path > 1; # Its just a file
1035 16844 100       200066 pop @path unless $file =~ /[\\\/]\Z/; # Remove file component allowing us to present files as well as folders
1036 16844         73756 my $path = join filePathSeparatorChar, @path;
1037 16844 100       358842 return undef if -d $path;
1038 3492         13167 eval {make_path($path)};
  3492         1311417  
1039 3492 50       84177 return $file if -d $path; # Success
1040 0         0 confess "Cannot make path with make_path: because:\n$path\n$@\n";
1041             } # makePath
1042              
1043             sub makePathRemote($;$) # Make the path for the specified B<$file> or folder on the L instance whose ip address is specified by B<$ip> or returned by L. Confess to any failures.
1044 0     0 1 0 {my ($file, $ip) = @_; # File or folder name, optional ip address
1045 0         0 my @path = split /[\\\/]+/, $file;
1046 0 0       0 return undef unless @path > 1; # Its just a file
1047 0 0       0 pop @path unless $file =~ /[\\\/]\Z/; # Remove file component allowing us to present files as well as folders. Split is asymmetric - trailing zero length strings are removed from the results array whilst leading zero length strings are not.
1048 0         0 my $path = join filePathSeparatorChar, @path;
1049              
1050 0   0     0 my $i = $ip // &awsIp; # Server ip address
1051 0         0 my $c = qq(ssh $i "mkdir -p '$path'; ls -lad '$path'"); # Make path and list it to confirm
1052 0         0 my $r = qx($c); # Execute
1053 0 0       0 return $path if $r =~ m(\Ad); # Check we have a folder
1054 0         0 confess "Unable to create folder $path on $i\n" # Report failure
1055             } # makePathRemote
1056              
1057             sub overWriteFile($$) # Write to a B<$file>, after creating a path to the $file with L if necessary, a B<$string> of L content encoded as L. Return the name of the $file on success else confess to any failures. If the file already exists it will be overwritten.
1058 13196     13196 1 2795534 {my ($file, $string) = @_; # File to write to or B for a temporary file, unicode string to write
1059 13196   66     87704 $file //= temporaryFile;
1060 13196 50       98283 $file =~ m(\n|\r)s and confess "File name contains a new line:\n=$file=\n";
1061 13196 50       41438 defined($string) or cluck "No string for file:\n$file\n";
1062 13196         68273 makePath($file);
1063 13196 50       1000121 open my $F, ">$file" or
1064             confess "Cannot open file for write because:\n$file\n$!\n";
1065 13196         132836 binmode($F, ":utf8");
1066 13196         38620 print {$F} $string;
  13196         205795  
1067 13196 50       883459 close ($F) or confess "Could not close file:\n$file\n$!\n";;
1068 13196 50       232017 -e $file or confess "Failed to write to file:\n$file\n";
1069 13196         362456 $file
1070             } # overWriteFile
1071              
1072 371     371   21686434 BEGIN{*owf=*overWriteFile} # Short form of overwrite file
1073              
1074             sub writeFile($$) #I Write to a new B<$file>, after creating a path to the $file with L if necessary, a B<$string> of L content encoded as L. Return the name of the $file written to on success else confess if the file already exists or any other error occurs.
1075 5337     5337 1 293870 {my ($file, $string) = @_; # New file to write to or B for a temporary file, string to write
1076 5337 100       36454 if (defined $file)
1077 1515 100       1619839 {-e $file and confess "File already exists:\n$file\n";
1078             }
1079 4969         34360 &overWriteFile(@_);
1080             } # writeFile
1081              
1082             sub writeTempFile(@) # Write an array of strings as lines to a temporary file and return the file name.
1083 370     370 1 5901 {my (@strings) = @_; # Array of lines
1084 370         3330 overWriteFile(undef, join '', map{"$_\n"} @strings);
  738         8487  
1085             } # writeTempFile
1086              
1087             sub writeFileToRemote($$;$) #I Write to a new B<$file>, after creating a path to the file with L if necessary, a B<$string> of L content encoded as L then copy the $file to the remote server whose ip address is specified by B<$ip> or returned by L. Return the name of the $file on success else confess if the file already exists or any other error occurs.
1088 0     0 1 0 {my ($file, $string, $ip) = @_; # New file to write to or B for a temporary file, string to write, optional ip address
1089 0         0 my $f = writeFile($file, $string); # Create file locally
1090 0         0 copyFileToRemote($f, $ip); # Copy file created to remote
1091 0         0 $f # Return local file name
1092             } # writeFileToRemote
1093              
1094             sub overWriteBinaryFile($$) # Write to B<$file>, after creating a path to the file with L if necessary, the binary content in B<$string>. If the $file already exists it is overwritten. Return the name of the $file on success else confess.
1095 1275     1275 1 5365 {my ($file, $string) = @_; # File to write to or B for a temporary file, L string to write
1096 1275   66     10084 $file //= temporaryFile;
1097 1275 50       14257 $file =~ m(\n|\r)s and confess "File name contains a new line:\n=$file=\n";
1098 1275 50       5138 $string or carp "No string for binary write to file:\n$file\n";
1099 1275         10971 makePath($file);
1100 1275 50       101177 open my $F, ">$file" or
1101             confess "Cannot open file for binary write because:\n$file\n$!\n";
1102 1275         13094 binmode($F);
1103 1275         2653 print {$F} $string;
  1275         14260  
1104 1275         76289 close ($F);
1105 1275 50       22184 -e $file or confess "Failed to write in binary to file:\n=$file=\n$!\n";
1106 1275         78325 $file
1107             }
1108              
1109             sub writeBinaryFile($$) # Write to a new B<$file>, after creating a path to the file with L if necessary, the binary content in B<$string>. Return the name of the $file on success else confess if the file already exists or any other error occurs.
1110 1007     1007 1 8215 {my ($file, $string) = @_; # New file to write to or B for a temporary file, string to write
1111 1007 100       6875 if (defined $file)
1112 639 100       845744 {-e $file and confess "Binary file already exists:\n$file\n";
1113             }
1114 739         8877 &overWriteBinaryFile(@_);
1115             }
1116              
1117             sub dumpFile($$) # Dump to a B<$file> the referenced data B<$structure>.
1118 1472     1472 1 8832 {my ($file, $structure) = @_; # File to write to or B for a temporary file, address of data structure to write
1119 1472         23920 overWriteFile($file, dump($structure));
1120             } # dumpFile
1121              
1122             sub dumpTempFile($) # Dump a data structure to a temporary file and return the name of the file created
1123 368     368 1 1472 {my ($structure) = @_; # Data structure to write
1124 368         3680 writeFile(undef, dump($structure));
1125             } # dumpTempFile
1126              
1127             sub dumpFileAsJson($$) # Dump to a B<$file> the referenced data B<$structure> represented as L string.
1128 368     368 1 1840 {my ($file, $structure) = @_; # File to write to or B for a temporary file, address of data structure to write
1129 368         8096 overWriteFile($file, encodeJson($structure));
1130             } # dumpFileAsJson
1131              
1132             sub dumpTempFileAsJson($) # Dump a data structure represented as L string to a temporary file and return the name of the file created.
1133 368     368 1 1840 {my ($structure) = @_; # Data structure to write
1134 368         1840 writeFile(undef, encodeJson($structure));
1135             } # dumpTempFileAsJson
1136              
1137             sub storeFile($$) # Store into a B<$file>, after creating a path to the file with L if necessary, a data B<$structure> via L. This is much faster than L but the stored results are not easily modified.
1138 369     369 1 1866 {my ($file, $structure) = @_; # File to write to or B for a temporary file, address of data structure to write
1139 369 100       113739 if (!$file) # Use a temporary file or create a path to the named file
1140 368   33     6624 {$file //= temporaryFile;
1141             }
1142             else
1143 1         70 {makePath($file);
1144             }
1145 369 50       2617 ref($structure) or confess "Reference required for structure parameter";
1146 369         6668 store $structure, $file;
1147 369         105065 $file
1148             } # writeFile
1149              
1150             sub writeGZipFile($$) # Write to a B<$file>, after creating a path to the file with L if necessary, through L a B<$string> whose content is encoded as L.
1151 736     736 1 423568 {my ($file, $string) = @_; # File to write to, string to write
1152 736         7728 makePath($file);
1153 736 50       2750800 open my $F, "| gzip>$file" or # Compress via gzip
1154             confess "Cannot open file for write because:\n$file\n$!\n";
1155 736         47104 binmode($F, ":utf8"); # Input to gzip encoded as utf8
1156 736         20608 print {$F} $string;
  736         57776  
1157 736         1593808 close ($F);
1158 736 50       58512 -e $file or confess "Failed to write to file:\n$file\n";
1159 736         204240 $file
1160             } # writeGZipFile
1161              
1162             sub dumpGZipFile($$) # Write to a B<$file> a data B<$structure> through L. This technique produces files that are a lot more compact files than those produced by L, but the execution time is much longer. See also: L.
1163 368     368 1 8096 {my ($file, $structure) = @_; # File to write, reference to data
1164 368 50       9200 ref($structure) or confess "\$structure must contain a reference to data, not a scalar";
1165 368         12880 writeGZipFile($file, dump($structure));
1166             } # dumpGZipFile
1167              
1168             sub writeFiles($;$$) # Write the values of a B<$hash> reference into files identified by the key of each value using L optionally swapping the prefix of each file from B<$old> to B<$new>.
1169 536     536 1 5092 {my ($hash, $old, $new) = @_; # Hash of key value pairs representing files and data, optional old prefix, new prefix
1170 536         8308 for my $file(sort keys %$hash) # Write file data for each hash key
1171 1072 50 33     9380 {my $target = $old && $new ? swapFilePrefix($file, $old, $new) : $file; # Optionally swap file prefix
1172 1072         11524 overWriteFile($file, $hash->{$file})
1173             }
1174             } # writeFiles
1175              
1176             sub readFiles(@) # Read all the files in the specified list of folders into a hash.
1177 536     536 1 4020 {my (@folders) = @_; # Folders to read
1178 536         3484 my %h;
1179 536         7772 for my $file(searchDirectoryTreesForMatchingFiles(@folders)) # Files
1180 1072         11524 {eval {$h{$file} = readFile($file)};
  1072         16616  
1181             }
1182 536         52260 \%h
1183             } # readFiles
1184              
1185             sub appendFile($$) # Append to B<$file> a B<$string> of L content encoded with L, creating the $file first if necessary. Return the name of the $file on success else confess. The $file being appended to is locked before the write with L to allow multiple processes to append linearly to the same file.
1186 368     368 1 4048 {my ($file, $string) = @_; # File to append to, string to append
1187 368 50       5520 $file or confess "No file name supplied\n";
1188 368 50       5152 $string or carp "No string for file:\n$file\n";
1189 368         4416 makePath($file);
1190 368 50       22080 open my $F, ">>$file" or
1191             confess "Cannot open file for write file:\n$file\n$!\n";
1192 368         3312 binmode($F, ":utf8");
1193 368         11040 flock($F, 2);
1194 368         1840 print {$F} $string;
  368         6992  
1195 368         14352 close ($F);
1196 368 50       6624 -e $file or confess "Failed to write to file:\n$file\n";
1197 368         15088 $file
1198             } # appendFile
1199              
1200             sub createEmptyFile($) # Create an empty file unless the file already exists and return the name of the file else confess if the file cannot be created.
1201 2114     2114 1 15549 {my ($file) = @_; # File to create or B for a temporary file
1202 2114   33     7355 $file //= temporaryFile;
1203 2114 50       41676 return $file if -e $file; # Return file name as proxy for success if file already exists
1204 2114         18422 makePath($file);
1205 2114 50       166422 open my $F, ">$file" or confess "Cannot create empty file:\n$file\n$!\n";
1206 2114         13161 binmode($F);
1207 2114         4970 print {$F} '';
  2114         9540  
1208 2114         22465 close ($F);
1209 2114 50       30544 -e $file or confess "Failed to create empty file:\n$file\n";
1210 2114         79416 $file # Return file name on success
1211             } # createEmptyFile
1212              
1213             sub binModeAllUtf8 #P Set STDOUT and STDERR to accept utf8 without complaint.
1214 0     0 1 0 {binmode $_, ":utf8" for *STDOUT, *STDERR;
1215             }
1216              
1217             sub setPermissionsForFile($$) # Apply L to a B<$file> to set its B<$permissions>.
1218 536     536 1 2948 {my ($file, $permissions) = @_; # File, permissions settings per chmod
1219 536 50       7504 return undef unless confirmHasCommandLineCommand(q(chmod)); # Confirm we have chmod
1220 536         1329548 qx(chmod $permissions $file); # Use chmod to set permissions
1221             }
1222              
1223             sub numberOfLinesInFile($) # Return the number of lines in a file.
1224 368     368 1 3312 {my ($file) = @_; # File
1225 368         7728 scalar split /\n/, readFile($file); # Number of lines
1226             } # numberOfLinesInFile
1227              
1228             sub overWriteHtmlFile($$) # Write an L file to /var/www/html and make it readable
1229 0     0 1 0 {my ($file, $data) = @_; # Target file relative to /var/www/html, data to write
1230 0         0 my $s = writeTempFile($data);
1231 0         0 my $t = fpf(q(/var/www/html/), $file);
1232 0         0 xxx qq(sudo mv $s $t; chmod o+r $t);
1233 0         0 unlink $s;
1234             }
1235              
1236             sub overWritePerlCgiFile($$) # Write a L file to /usr/lib/cgi-bin and make it executable after checking it for syntax errors
1237 0     0 1 0 {my ($file, $data) = @_; # Target file relative to /var/www/html, data to write
1238 0         0 my $s = writeTempFile($data);
1239 0         0 my $r = qx(perl -c $s 2>&1);
1240 0 0       0 if ($r =~ m(syntax OK)si)
1241 0         0 {my $t = fpf(q(/usr/lib/cgi-bin/), $file);
1242 0         0 say STDERR qx(sudo mv $s $t; chmod o+rx $t);
1243             }
1244             else
1245 0         0 {my @data = map {[$_]} split m/\n/, $data;
  0         0  
1246 0         0 say STDERR formatTable([@data]);
1247 0         0 confess "Perl error:\n$r\n";
1248             }
1249 0         0 unlink $s;
1250             }
1251              
1252             #D2 Copy # Copy files and folders. The B<\Acopy.*Md5Normalized.*\Z> methods can be used to ensure that files have collision proof names that collapse duplicate content even when copied to another folder.
1253              
1254             sub copyFile($$) # Copy the B<$source> file encoded in utf8 to the specified B<$target> file in and return $target.
1255 268     268 1 5896 {my ($source, $target) = @_; # Source file, target file
1256 268         8844 owf($target, readFile($source));
1257 268         7772 my $s = fileSize($source);
1258 268         5092 my $t = fileSize($target);
1259 268 50       6700 $s eq $t or lll
1260             "Copied file has a different size\n".formatTable
1261             ([[$s, $source], [$t, $target]], <
1262             Size Size of file
1263             File Name of file
1264             END
1265 268         10184 $target # Return target file name
1266             }
1267              
1268             sub moveFileNoClobber($$) # Rename the B<$source> file, which must exist, to the B<$target> file but only if the $target file does not exist already. Returns 1 if the $source file was successfully renamed to the $target file else 0.
1269 536     536 1 4288 {my ($source, $target) = @_; # Source file, target file
1270 536 100 66     16884 if (-e $source and !-e $target) # Rename possible
1271 268         7772 {rename $source, $target;
1272 268         4288 return 1;
1273             }
1274             0 # Rename not possible
1275 268         3752 }
1276              
1277             sub moveFileWithClobber($$) # Rename the B<$source> file, which must exist, to the B<$target> file but only if the $target file does not exist already. Returns 1 if the $source file was successfully renamed to the $target file else 0.
1278 268     268 1 2680 {my ($source, $target) = @_; # Source file, target file
1279 268 50       6700 if (-e $source) # Source file exists so rename
1280 268         85224 {unlink $target;
1281 268         14204 rename $source, $target;
1282 268         5092 return 1;
1283             }
1284             0 # No such source file
1285 0         0 }
1286              
1287             sub copyFileToFolder($$) # Copy the file named in B<$source> to the specified B<$targetFolder/> or if $targetFolder/ is in fact a file into the folder containing this file and return the target file name. Confesses instead of copying if the target already exists.
1288 17     17 1 255 {my ($source, $targetFolder) = @_; # Source file, target folder
1289 17         323 writeFile fpf(fp($targetFolder), fne($source)), readFile $source;
1290             }
1291              
1292 1340     1340 1 4556 sub nameFromStringMaximumLength {128} #P Maximum length of a name generated from a string
1293              
1294             sub nameFromString($%) # Create a readable name from an arbitrary string of text.
1295 1072     1072 1 4288 {my ($string, %options) = @_; # String, options
1296              
1297 1072         1876 my @name;
1298 1072 100       7504 if ($string =~ m(<(bookmap))s) # The ghastly compromise
    50          
1299 536         1072 {push @name, q(bm);
1300             }
1301             elsif ($string =~ m(<(bookmap|concept|glossentry|html|map|reference|task))s) # The correct solution
1302 0         0 {push @name, substr($1, 0, 1);
1303             }
1304              
1305 1072         7236 $string =~ s(<[^>]*>) (_)gs; # Remove xml/html tags
1306 1072         6164 $string =~ s([^a-z0-9_])(_)gis; # Reduce character set to produce a readable name
1307 1072         2680 push @name, $string;
1308              
1309 1072         2412 my $name = join q(_), @name;
1310 1072         6164 $name =~ s(_+)(_)gs; # Remove runs of underscores
1311 1072         8844 $name =~ s((\A_+|_+\Z)) ()gs; # Remove leading and trailing underscores
1312              
1313 1072   33     4556 firstNChars($name, $options{maximumLength} // nameFromStringMaximumLength); # Limit the name length
1314             }
1315              
1316             sub nameFromStringRestrictedToTitle($%) # Create a readable name from a string of text that might contain a title tag - fall back to L if that is not possible.
1317 268     268 1 804 {my ($string, %options) = @_; # String, options
1318 268         804 my @name;
1319 268 50       3216 if ($string =~ m(<(bookmap))s) # The ghastly compromise
    0          
1320 268         804 {push @name, q(bm);
1321             }
1322             elsif ($string =~ m(<(bookmap|concept|glossentry|html|map|reference|task))s) # The correct solution
1323 0         0 {push @name, substr($1, 0, 1);
1324             }
1325              
1326 268         1072 for my $t(qw(title mainbooktitle booktitlealt )) # Various title tags
1327 804 100       23316 {if ($string =~ m(<$t[^>]*>([^<]*))is)
1328 268         1608 {push @name, $1;
1329             }
1330             }
1331              
1332 268         1340 my $name = lc join '_', @name; # Mim believes in lc
1333 268         804 $name =~ s(<[^>]*>) (_)gs; # Remove xml/html tags
1334 268         1072 $name =~ s([^a-z0-9_])(_)gis; # Reduce character set to produce a readable name
1335 268         2948 $name =~ s(_+)(_)gs; # Remove runs of underscores
1336 268         2948 $name =~ s((\A_+|_+\Z)) ()gs; # Remove leading and trailing underscores
1337              
1338 268   33     2144 firstNChars($name, $options{maximumLength} // nameFromStringMaximumLength); # Limit the name length
1339             }
1340              
1341             sub uniqueNameFromFile($) # Create a unique name from a file name and the md5 sum of its content
1342 268     268 1 804 {my ($source) = @_; # Source file
1343 268         1072 my $sourceFile = fn $source; # File name component
1344 268 50       1340 return fne($source) if $sourceFile =~ m([0-9a-z]{32}\Z)is; # Name already normalized
1345 268         804 my $sourceFileLimited = nameFromString($sourceFile); # File name with limited character set
1346 268         804 my $md5 = fileMd5Sum($source); # Normalizing Md5 sum
1347 268         2680 fpe($sourceFileLimited.q(_).$md5, fe $source); # Normalized name
1348             }
1349              
1350             sub nameFromFolder($) # Create a name from the last folder in the path of a file name. Return undef if the file does not have a path.
1351 268     268 1 804 {my ($file) = @_; # File name
1352 268         804 my $p = fp $file;
1353 268 50       1072 my @p = onWindows ? split m(\\), $p : split m(/), $p;
1354 268 50       1876 return $p[-1] if @p;
1355             undef
1356 0         0 }
1357              
1358             sub copyFileMd5Normalized(;$$) # Normalize the name of the specified B<$source> file to the md5 sum of its content, retaining its current extension, while placing the original file name in a companion file if the companion file does not already exist. If no B<$target> folder is supplied the file is renamed to its normalized form in situ, otherwise it is copied to the target folder and renamed there. A companion file for the B<$source> file is created by removing the extension of the normalized file and writing the original B<$source> file name to it unless such a file already exists as we assume that it contains the 'original' original name of the B<$source> file. If the B<$source> file is copied to a new location then the companion file is copied as well to maintain the link back to the original name of the file.
1359 0     0 1 0 {my ($source, $Target) = @_; # Source file, target folder or a file in the target folder
1360 0         0 warn "Deprecated in favor of Dita::GB::Standard";
1361 0 0 0     0 -e $source && !-d $source or
1362             confess "Source file to normalize does not exist:\n$source";
1363 0   0     0 my $target = fp($Target // $source); # Target folder
1364 0         0 my $sourceFile = fn $source; # File name component
1365              
1366 0 0       0 if ($sourceFile =~ m([0-9a-z]{32}\Z)is) # Name already normalized
1367 0 0       0 {if (@_== 2) # Copy source to new folder if necessary
1368 0         0 {my $target = fpf(fp($Target), fne($source));
1369 0         0 copyFile($source, $target);
1370 0         0 my $id = setFileExtension($source);
1371 0         0 my $od = setFileExtension($target);
1372 0 0       0 if (!-e $od)
1373 0 0       0 {if (-e $id)
1374 0         0 {copyFile($id, $od);
1375             }
1376             else
1377 0         0 {owf($od, $source);
1378             }
1379             }
1380 0         0 return $target; # Normalized target
1381             }
1382 0         0 return $source; # File is already normalized
1383             }
1384              
1385 0         0 my $out = fpe($target, nameFromString(readFile($source))); # Create normalized name in new folder depending only on the content of the source file
1386 0         0 my $id = setFileExtension($source); # Source companion file carrying original name
1387 0         0 my $od = setFileExtension($out); # Target companion file carrying original name
1388              
1389 0 0       0 if (!-e $out) # Copy file unless it is already there - we know the target is correct because its name is normalized
1390 0         0 {copyFile($source, $out); # Copy source to normalized target
1391 0 0       0 if (-e $id) # Copy or create companion file
    0          
1392 0         0 {copyFile($id, $od);
1393             }
1394             elsif (!-e $od)
1395 0         0 {owf($od, $source); # Create a companion file as none exists
1396             }
1397             }
1398             $out # Return normalized image file name
1399 0         0 }
1400              
1401             sub copyFileMd5NormalizedName($$@) # Name a file using the GB Standard
1402 0     0 1 0 {my ($content, $extension, %options) = @_; # Content, extension, options
1403 0         0 warn "Deprecated in favor of Dita::GB::Standard";
1404 0 0       0 defined($content) or
1405             confess "Content must be defined";
1406 0 0 0     0 defined($extension) && $extension =~ m(\A\S{2,}\Z)s or
1407             confess "Extension must be non blank and at least two characters long";
1408 0         0 my $name = nameFromString($content); # Human readable component
1409 0 0       0 $name = nameFromStringRestrictedToTitle($content) if $options{titleOnly};# Not entirely satisfactory
1410 0         0 my $md5 = stringMd5Sum($content); # Md5 sum
1411 0         0 fpe($name.q(_).$md5, $extension) # Add extension
1412             }
1413              
1414             sub copyFileMd5NormalizedCreate($$$$@) # Create a file in the specified B<$folder> whose name is constructed from the md5 sum of the specified B<$content>, whose content is B<$content>, whose extension is B<$extension> and which has a companion file with the same name minus the extension which contains the specified B<$companionContent>. Such a file can be copied multiple times by L regardless of the other files in the target folders.
1415 0     0 1 0 {my ($Folder, $content, $extension, $companionContent, %options) = @_; # Target folder or a file in that folder, content of the file, file extension, contents of the companion file, options.
1416 0         0 warn "Deprecated in favor of Dita::GB::Standard";
1417 0         0 my $folder = fp $Folder; # Normalized folder name
1418 0         0 my $name = nameFromString($content); # Entirely satisfactory
1419 0 0       0 $name = nameFromStringRestrictedToTitle($content) if $options{titleOnly};# Not entirely satisfactory
1420 0         0 my $md5 = stringMd5Sum($content);
1421 0         0 my $od = fpf($folder, $name.q(_).$md5); # Companion file
1422 0         0 my $out = fpe($od, $extension); # Normalized file
1423 0         0 owf($out, $content); # Write file content
1424 0         0 owf($od, $companionContent ); # Write companion file
1425 0         0 $out
1426             }
1427              
1428             sub copyFileMd5NormalizedGetCompanionContent($) # Return the content of the companion file to the specified B<$source> file after it has been normalized via L or L or return B if the corresponding companion file does not exist.
1429 0     0 1 0 {my ($source) = @_; # Source file.
1430 0         0 warn "Deprecated in favor of Dita::GB::Standard";
1431 0         0 my $id = setFileExtension($source);
1432 0 0 0     0 -e $source && -e $id ? readFile($id) : undef
1433             }
1434              
1435             sub copyFileMd5NormalizedDelete($) # Delete a normalized and its companion file
1436 0     0 1 0 {my ($file) = @_; # File
1437 0         0 warn "Deprecated in favor of Dita::GB::Standard";
1438 0         0 my $companion = setFileExtension($file);
1439 0         0 unlink $_ for $companion, $file;
1440             }
1441              
1442             sub copyBinaryFile($$) # Copy the binary file B<$source> to a file named <%target> and return the target file name,
1443 268     268 1 1340 {my ($source, $target) = @_; # Source file, target file
1444 268         2412 overWriteBinaryFile($target, readBinaryFile($source));
1445             # my $s = fileSize($source); # Appears to be unreliable across multiple CPUs
1446             # my $t = fileSize($target);
1447             # $s eq $t or confess
1448             # "Copied binary file has a different size\n".formatTable
1449             # ([[$s, $source], [$t, $target]], <
1450             #Size Size of file
1451             #File Name of file
1452             #END
1453 268         7504 $target
1454             }
1455              
1456             sub copyBinaryFileMd5Normalized($;$) # Normalize the name of the specified B<$source> file to the md5 sum of its content, retaining its current extension, while placing the original file name in a companion file if the companion file does not already exist. If no B<$target> folder is supplied the file is renamed to its normalized form in situ, otherwise it is copied to the target folder and renamed there. A companion file for the B<$source> file is created by removing the extension of the normalized file and writing the original B<$source> file name to it unless such a file already exists as we assume that it contains the 'original' original name of the B<$source> file. If the B<$source> file is copied to a new location then the companion file is copied as well to maintain the link back to the original name of the file.
1457 0     0 1 0 {my ($source, $Target) = @_; # Source file, target folder or a file in the target folder
1458 0         0 warn "Deprecated in favor of Dita::GB::Standard";
1459 0 0       0 -e $source or confess "File does not exist:\n$source\n";
1460              
1461 0 0 0     0 return $source if fn($source) =~ m([0-9a-z]{32}\Z)is and @_ == 1; # Name already normalized and no target
1462              
1463 0         0 my $target = fp($Target); # Target folder
1464 0         0 my $ext = fe($source); # Extension
1465 0         0 my $out = fpe($target, $ext.q(_).fileMd5Sum($source), $ext); # Normalized name in new folder
1466 0         0 my $id = setFileExtension($source); # Source companion file carrying original name
1467 0         0 my $od = setFileExtension($out); # Target companion file carrying original name
1468              
1469 0 0       0 if (!-e $out) # Copy file unless it is already there - we know the target is correct because its name is normalized
1470 0         0 {overWriteBinaryFile($out, readBinaryFile($source));
1471 0 0       0 if (-e $id) # Copy or create companion file
    0          
1472 0         0 {copyFile($id, $od);
1473             }
1474             elsif (!-e $od)
1475 0         0 {owf($od, $source);
1476             }
1477             }
1478             $out # Return normalized image file name
1479 0         0 }
1480              
1481             sub copyBinaryFileMd5NormalizedCreate($$$$) # Create a file in the specified B<$folder> whose name is constructed from the md5 sum of the specified B<$content>, whose content is B<$content>, whose extension is B<$extension> and which has a companion file with the same name minus the extension which contains the specified B<$companionContent>. Such a file can be copied multiple times by L regardless of the other files in the target folders while retaining the original name information.
1482 0     0 1 0 {my ($Folder, $content, $extension, $companionContent) = @_; # Target folder or a file in that folder, content of the file, file extension, optional content of the companion file.
1483 0         0 warn "Deprecated in favor of Dita::GB::Standard";
1484 0         0 my $folder = fp $Folder; # Normalized folder name
1485 0         0 my $md5 = fileMd5Sum($content); # Md5 sum of content
1486 0         0 my $od = fpf($folder, $extension.q(_).$md5); # Companion file
1487 0         0 my $out = fpe($od, $extension); # Normalized file
1488 0         0 owf($out, $content); # Write file content
1489 0         0 owf($od, $companionContent); # Write companion file
1490 0 0       0 -e $out or confess "Failed to create file $out";
1491 0 0       0 -e $od or confess "Failed to create companion file $od";
1492 0         0 $out
1493             }
1494              
1495             sub copyBinaryFileMd5NormalizedGetCompanionContent($) # Return the original name of the specified B<$source> file after it has been normalized via L or L or return B if the corresponding companion file does not exist.
1496 0     0 1 0 {my ($source) = @_; # Source file.
1497 0         0 warn "Deprecated in favor of Dita::GB::Standard";
1498 0         0 my $id = setFileExtension($source);
1499 0 0 0     0 -e $source && -e $id ? readFile($id) : undef
1500             }
1501              
1502             sub copyFileToRemote($;$) # Copy the specified local B<$file> to the server whose ip address is specified by B<$ip> or returned by L.
1503 0     0 1 0 {my ($file, $ip) = @_; # Source file, optional ip address
1504 0         0 my $f = fullyQualifyFile($file); # Fully qualify source file
1505 0 0       0 -f $file or confess "No such file:\n$file\n"; # Check source file exists
1506 0 0       0 -f $f or confess "No such file:\n$f\n"; # Check source file exists
1507 0   0     0 my $i = $ip // &awsIp; # Ip of server
1508 0         0 my $d = fp $f; # Folder to create if necessary
1509 0         0 makePathRemote($f, $i); # Create folder on remote
1510 0         0 my $c = qq(rsync -mpqrt --del $f $i:$f); # Transfer file
1511             # lll $c;
1512 0         0 xxx $c, qr(\A\s*\Z); # Execute and expect no messages
1513             }
1514              
1515             sub copyFileFromRemote($;$) # Copy the specified B<$file> from the server whose ip address is specified by B<$ip> or returned by L.
1516 0     0 1 0 {my ($file, $ip) = @_; # Source file, optional ip address
1517 0         0 my $f = fullyQualifyFile($file); # Fully qualify source file
1518 0   0     0 my $i = $ip // &awsIp; # Ip of server
1519 0         0 my $d = fp $f; # Folder to create if necessary
1520 0         0 makePath($d); # Create folder
1521 0         0 my $c = qq(rsync -mpqrt $i:$f $f); # Transfer file
1522             #lll $c;
1523 0         0 xxx $c, qr(\A\s*\Z);
1524             }
1525              
1526             sub copyFolder($$) # Copy the B<$source> folder to the B<$target> folder after clearing the $target folder.
1527 536     536 1 5360 {my ($source, $target) = @_; # Source file, target file
1528 536 50       15008 -d $source or confess "No such folder:\n$source\n";
1529 536         12864 my $s = fpd($source);
1530 536         3216 my $t = fpd($target);
1531 536         11792 makePath($t);
1532 536         7236 my $c = qq(rsync -r --del $s $t), qr(\A\s*\Z); # Suppress command printing by supplying a regular expression to test the command output
1533             #lll $c;
1534 536         10184 xxx $c, qr(\A\s*\Z);
1535             }
1536              
1537             sub mergeFolder($$) # Copy the B<$source> folder into the B<$target> folder retaining any existing files not replaced by copied files.
1538 268     268 1 4824 {my ($source, $target) = @_; # Source file, target file
1539 268 50       8576 -d $source or confess "No such folder:\n$source\n";
1540 268         6968 my $s = fpd($source);
1541 268         2948 my $t = fpd($target);
1542 268         7772 makePath($t);
1543 268         3484 my $c = qq(rsync -r $s $t);
1544             #lll $c;
1545 268         7504 xxx $c, qr(\A\s*\Z);
1546             }
1547              
1548             sub copyFolderToRemote($;$) # Copy the specified local B<$Source> folder to the corresponding remote folder on the server whose ip address is specified by B<$ip> or returned by L. The default userid supplied by F<.ssh/config> will be used on the remote server.
1549 0     0 1 0 {my ($Source, $ip) = @_; # Source file, optional ip address of server
1550 0         0 my $source = fullyQualifyFile($Source); # Fully qualify source folder
1551 0 0       0 -d $Source or confess "No such folder:\n$Source\n"; # Check source exists
1552 0 0       0 -d $source or confess "No such folder:\n$source\n"; # Check source exists
1553 0   0     0 my $i = $ip // &awsIp; # Ip of server
1554 0         0 my $s = fpd($source); # Normalize folder name
1555 0         0 makePathRemote($s, $i); # Create folder on target
1556 0         0 my $c = qq(rsync -mpqrt --del $s $i:$s); # Transfer files
1557             #lll $c;
1558 0         0 xxx($c, qr(\A\s*\Z)); # Execute and expect no messages
1559             }
1560              
1561             sub mergeFolderFromRemote($;$) # Merge the specified B<$Source> folder from the corresponding remote folder on the server whose ip address is specified by B<$ip> or returned by L. The default userid supplied by F<.ssh/config> will be used on the remote server.
1562 0     0 1 0 {my ($Source, $ip) = @_; # Source file, optional ip address of server
1563 0         0 my $source = fullyQualifyFile($Source); # Fully qualify source folder
1564 0   0     0 my $i = $ip // &awsIp; # Ip of server
1565 0         0 my $s = fpd($source); # Normalize folder name
1566 0         0 makePath($s); # Create folder locally to receive results
1567 0         0 makePathRemote($s, $i); # Create folder on target so that rsync does not complain if it is not there == empty
1568 0         0 my $c = qq(rsync -mpqrt $i:$s $s); # Transfer files
1569             #lll $c;
1570 0         0 xxx($c, qr(\A\s*\Z)); # Execute and expect no messages
1571             }
1572              
1573             #D1 Testing # Methods to assist with testing
1574              
1575             sub removeFilePathsFromStructure($) # Remove all file paths from a specified B<$structure> to make said $structure testable with L.
1576 42     42 1 210 {my ($structure) = @_; # Data structure reference
1577 42         1218 my $s = dump($structure); # Dump structure
1578 42         25725 $s =~ s("[^"]*/) (")gs; # Remove file prefixes in strings
1579 42         2436 my $r = eval $s; # New version of structure
1580 42 50       189 confess "Unable to remove file prefixes from structure\n$@\n$s\n" if $@; # Complain if removal fails
1581 42         672 $r # Return new structure
1582             }
1583              
1584             sub writeStructureTest($$) # Write a test for a data B<$structure> with file names in it.
1585 21     21 1 189 {my ($structure, $expr) = @_; # Data structure reference, expression
1586 21         105 my $s = nws(dump($structure)); # Dump structure
1587 21         420 $s =~ s("[^"]*/) (")gs; # Remove file prefixes in strings
1588 21         189 $s =~ s(\],) (],\n )gs; # Reinsert new lines
1589 21         63 $s =~ s(\},) (},\n )gs;
1590 21         189 <
1591             is_deeply removeFilePathsFromStructure($expr),\n $s;
1592             END
1593             }
1594              
1595             #D1 Images # Image operations.
1596              
1597             sub imageSize($) # Return (width, height) of an B<$image>.
1598 0     0 1 0 {my ($image) = @_; # File containing image
1599 0 0       0 -e $image or confess
1600             "Cannot get size of image as file does not exist:\n$image\n";
1601 0 0       0 return undef unless confirmHasCommandLineCommand(q(identify)); # Confirm we have identify
1602 0         0 my $s = qx(identify -verbose "$image");
1603 0 0       0 if ($s =~ /Geometry: (\d+)x(\d+)/s)
1604 0         0 {return ($1, $2);
1605             }
1606             else
1607 0         0 {confess "Cannot get image size for file:\n$image\nfrom:\n$s\n";
1608             }
1609             }
1610              
1611             sub convertImageToJpx690($$;$$) #P Convert a B<$source> image to a B<$target> image in jpx format using versions of L version 6.9.0 and above. The size in pixels of each jpx tile may be specified by the optional B<$Size> parameter which defaults to B<256>. B<$Tiles> optionally provides an upper limit on the number of each tiles in each dimension.
1612 0     0 1 0 {my ($Source, $target, $Size, $Tiles) = @_; # Source file, target folder (as multiple files will be created), optional size of each tile - defaults to 256, optional limit on the number of tiles in either dimension
1613 0         0 my $source = $Source;
1614 0   0     0 my $size = $Size // 256; # Size of each tile
1615 0         0 my $N = 4; # Power of ten representing the maximum number of tiles
1616 0 0       0 -e $source or confess "Image file does not exist:\n$source\n"; # Check source
1617 0         0 $target = fpd($target); # Make sure the target is a folder
1618 0         0 makePath($target); # Make target folder
1619              
1620 0 0       0 if ($Tiles) # Restrict the converted image to a maximum number of tiles if requested
1621 0         0 {my $s = quoteFile($source);
1622 0         0 my $t = temporaryFile;
1623 0         0 my $n = $Size*$Tiles;
1624 0         0 my $c = qq(convert $s -resize ${n}x${n}\\> $t);
1625 0         0 lll $_ for qx($c 2>&1);
1626 0         0 $source = $t; # Resized file is now source
1627             }
1628              
1629 0         0 my ($w, $h) = imageSize($source); # Image size
1630 0 0       0 my $W = int($w/$size); ++$W if $w % $size; # Image size in tiles
  0         0  
1631 0 0       0 my $H = int($h/$size); ++$H if $h % $size;
  0         0  
1632 0         0 writeFile(filePath($target, "jpx.data"), <
1633             version 1
1634             type jpx
1635             size $size
1636             source $Source
1637             width $w
1638             height $h
1639             END
1640              
1641 0         0 if (1) # Create tiles
1642 0         0 {my $s = quoteFile($source);
1643 0         0 my $t = quoteFile($target."%0${N}d.jpg");
1644 0         0 my $c = qq(convert $s -crop ${size}x${size} $t);
1645 0         0 lll $_ for qx($c 2>&1);
1646             }
1647              
1648 0         0 if (1) # Rename tiles in two dimensions
1649 0 0       0 {my $W = int($w/$size); ++$W if $w % $size;
  0         0  
1650 0 0       0 my $H = int($h/$size); ++$H if $h % $size;
  0         0  
1651 0         0 my $k = 0;
1652 0         0 for my $Y(1..$H)
1653 0         0 {for my $X(1..$W)
1654 0         0 {my $s = sprintf("${target}%0${N}d.jpg", $k++);
1655 0         0 my $t = "${target}/${Y}_${X}.jpg";
1656 0 0       0 rename $s, $t or confess "Cannot rename file:\n$s\nto:\n$t\n";
1657 0 0       0 -e $t or confess "Cannot create file:\n$t\n";
1658             }
1659             }
1660             }
1661             }
1662              
1663             sub convertImageToJpx($$;$$) #P Convert a B<$source> image to a B<$target> image in jpx format. The size in pixels of each jpx tile may be specified by the optional B<$Size> parameter which defaults to B<256>. B<$Tiles> optionally provides an upper limit on the number of each tiles in each dimension.
1664 0     0 1 0 {my ($Source, $target, $Size, $Tiles) = @_; # Source file, target folder (as multiple files will be created), optional size of each tile - defaults to 256, optional limit in either direction on the number of tiles
1665 0         0 my $source = $Source;
1666              
1667 0 0       0 return undef unless confirmHasCommandLineCommand(q(convert)); # Confirm we have convert
1668              
1669 0         0 if (1)
1670 0         0 {my $r = qx(convert --version);
1671 0 0       0 if ($r =~ m(\AVersion: ImageMagick ((\d|\.)+)))
1672 0         0 {my $version = join '', map {sprintf("%04d", $_)} split /\./, $1;
  0         0  
1673 0 0       0 return &convertImageToJpx690(@_) if $version >= 600090000;
1674             }
1675 0         0 else {confess "Please install Imagemagick:\nsudo apt install imagemagick\n"}
1676             }
1677              
1678 0 0       0 -e $source or confess "Image file does not exist:\n$source\n";
1679 0   0     0 my $size = $Size // 256;
1680              
1681 0         0 makePath($target);
1682              
1683 0 0       0 if ($Tiles) # Restrict the converted image to a maximum number of tiles if requested
1684 0         0 {my $s = quoteFile($source);
1685 0         0 my $t = temporaryFile;
1686 0         0 my $n = $Size*$Tiles;
1687 0         0 my $c = qq(convert $s -resize ${n}x${n}\\> $t);
1688 0         0 lll $_ for qx($c 2>&1);
1689 0         0 $source = $t; # Resized file is now source
1690             }
1691              
1692 0         0 my ($w, $h) = imageSize($source); # Write Jpx header
1693 0         0 writeFile(filePath($target, "jpx.data"), <
1694             version 1
1695             type jpx
1696             size $size
1697             source $Source
1698             width $w
1699             height $h
1700             END
1701              
1702 0         0 if (1) # Create tiles
1703 0         0 {my $s = quoteFile($source);
1704 0         0 my $t = quoteFile($target);
1705 0         0 my $c = qq(convert $s -crop ${size}x${size} $t);
1706 0         0 lll $_ for qx($c 2>&1);
1707             }
1708              
1709 0         0 if (1) # Rename tiles in two dimensions
1710 0 0       0 {my $W = int($w/$size); ++$W if $w % $size;
  0         0  
1711 0 0       0 my $H = int($h/$size); ++$H if $h % $size;
  0         0  
1712 0         0 my $k = 0;
1713 0         0 for my $Y(1..$H)
1714 0         0 {for my $X(1..$W)
1715 0         0 {my $s = "${target}-$k";
1716 0         0 my $t = "${target}/${Y}_${X}.jpg";
1717 0 0       0 rename $s, $t or confess "Cannot rename file:\n$s\nto:\n$t\n";
1718 0 0       0 -e $t or confess "Cannot create file:\n$t\n";
1719 0         0 ++$k;
1720             }
1721             }
1722             }
1723             }
1724              
1725             sub convertDocxToFodt($$) # Convert a I B<$inputFile> file to a I B<$outputFile> using B which must not be running elsewhere at the time. L can be installed via:\m sudo apt install sharutils unoconv\mParameters:
1726 0     0 1 0 {my ($inputFile, $outputFile) = @_; # Input file, output file
1727 0 0       0 return undef unless confirmHasCommandLineCommand(q(unoconv)); # Confirm we have unoconv
1728 0         0 my $r = qx(unoconv -f fodt -o "$outputFile" "$inputFile"); # Perform conversion
1729 0 0       0 !$r or confess "unoconv failed, try closing libreoffice if it is open\n". $r;
1730             }
1731              
1732             # Tests in: /home/phil/perl/z/unoconv/testCutOutImagesInFodtFile.pl
1733             sub cutOutImagesInFodtFile($$$) # Cut out the images embedded in a B file, perhaps produced via L, placing them in the specified folder and replacing them in the source file with:\m .\mThis conversion requires that you have both L and L installed on your system:\m sudo apt install sharutils imagemagick unoconv\mParameters:
1734 0     0 1 0 {my ($inputFile, $outputFolder, $imagePrefix) = @_; # Input file, output folder for images, a prefix to be added to image file names
1735 0         0 my $source = readFile($inputFile); # Read .fodt file
1736 0         0 lll "Start image location in string of ", length($source);
1737              
1738 0         0 my @p;
1739 0         0 my $p = 0;
1740 0         0 my ($s1, $s2) = ('', '');
1741 0         0 for(;;) # Locate images
1742 0 0       0 {my $q = index($source, $s1, $p); last if $q < 0;
  0         0  
1743 0 0       0 my $Q = index($source, $s2, $q); last if $Q < 0;
  0         0  
1744 0         0 push @p, [$q+length($s1), $Q-$q-length($s1)];
1745 0         0 $p = $Q;
1746             }
1747 0         0 lll "Cutting out ", scalar(@p), " images"; # Cut out images
1748              
1749 0         0 my $imageNumber = @p; # Number the image files
1750              
1751 0         0 for(reverse @p) # We cut out in reverse to preserve the offsets of the images yet to be cut out
1752 0         0 {my ($p, $l) = @$_; # Position, length of image
1753              
1754 0         0 my $i = substr($source, $p, $l); # Image text uuencoded
1755 0         0 $i =~ s/ //g; # Remove leading spaces on each line
1756              
1757 0 0       0 my ($ext, $type, $im) = # Decide on final image type, possibly via an external imagemagick conversion on windows, or an internal imagemagick conversion locally
    0          
    0          
    0          
    0          
    0          
1758             $i =~ m/\AiVBOR/ ? ('png') :
1759             $i =~ m/\AAQAAAG/ ? ('png', 'emf') :
1760             $i =~ m/\AVkNMT/ ? ('png', 'svm') :
1761             $i =~ m/\A183G/ ? ('png', '', 'wmf') :
1762             $i =~ m/\A\/9j/ ? ('jpg') :
1763             $i =~ m/\AR0lGODlh/ ? ('gif') :
1764             confess "Unknown image type: ". substr($i, 0, 16)."\n";
1765              
1766 0         0 lll "$imageNumber cut $ext from $p for $l";
1767              
1768 0         0 my $imageBinary = decodeBase64($i); # Decode image
1769 0         0 my $imageFile = # Image file name
1770             fpe($outputFolder, join(q(), $imagePrefix, q(_), $imageNumber), $ext);
1771              
1772 0 0       0 if (!$type)
1773 0         0 {writeBinaryFile($imageFile, $imageBinary);
1774             }
1775              
1776 0         0 my $xml = ""; # Create image command
1777 0         0 substr($source, $p, $l) = $xml; # Replace the image source with an image command
1778 0         0 $imageNumber--;
1779             }
1780             $source
1781 0         0 }
1782              
1783             #D1 Encoding and Decoding # Encode and decode using L and Mime.
1784              
1785             sub unbless($) # Remove the effects of bless from a L data B<$structure> enabling it to be converted to L or compared with L.
1786 8115     8115 1 17722 {my ($d) = @_; # Unbless a L data structure.
1787 8115 100       66882 return $d unless ref $d;
1788 2951         11089 my $r = reftype $d;
1789 2951 100       11806 return [map { __SUB__->( $_ )} @$d] if $r eq q(ARRAY);
  4422         9221  
1790 1108 50       8910 return {map {$_ => __SUB__->($$d{$_})} keys %$d} if $r eq q(HASH);
  2585         12572  
1791 0         0 confess "Unknown container: $r\n";
1792             }
1793              
1794             sub encodeJson($) # Convert a L data B<$structure> to a L string.
1795 1107     1107 1 4799 {my ($structure) = @_; # Data to encode
1796 1107         82577 JSON->new->utf8->allow_blessed->pretty->canonical->encode(unbless $structure)
1797             }
1798              
1799             sub decodeJson($) # Convert a L B<$string> to a L data structure.
1800 1107     1107 1 3689 {my ($string) = @_; # Data to decode
1801 1107         39870 JSON->new->utf8->pretty->canonical->decode($string)
1802             }
1803              
1804             sub encodeBase64($) # Encode an L B<$string> in base 64.
1805 371     371 1 1484 {my ($string) = @_; # String to encode
1806 371         1113 my $s = eval {encode_base64($string, '')};
  371         2968  
1807 371 50       1855 confess $@ if $@; # So we get a trace back
1808 371         9275 $s
1809             }
1810              
1811             sub decodeBase64($) # Decode an L B<$string> in base 64.
1812 371     371 1 1484 {my ($string) = @_; # String to decode
1813 371         371 my $s = eval {decode_base64($string)};
  371         5194  
1814 371 50       2226 confess $@ if $@; # So we get a trace back
1815 371         8533 $s
1816             }
1817              
1818             sub convertUnicodeToXml($) # Convert a B<$string> with L code points that are not directly representable in L into string that replaces these code points with their representation in L making the string usable in L documents.
1819 371     371 1 1484 {my ($string) = @_; # String to convert
1820 371         4081 my $t = '';
1821 371         5194 for(split //, $string) # Each letter in the source
1822 5194         7791 {my $n = ord($_);
1823 5194 100       11872 my $c = $n > 127 ? "&#$n;" : $_; # Use xml representation beyond u+127
1824 5194         8162 $t .= $c;
1825             }
1826             $t # Return resulting string
1827 371         4081 }
1828              
1829             sub asciiToHexString($) # Encode an L string as a string of L digits.
1830 268     268 1 2144 {my ($ascii) = @_; # Ascii string
1831 268         1876 my $c = ''; # Result
1832 268         2412 for my $a(split //, $ascii) # Each ascii character
1833 3216         9380 {$c .= sprintf("%x", ord $a) # Format as hex
1834             }
1835             $c # Return string of hexadecimal digits
1836 268         1608 }
1837              
1838             sub hexToAsciiString($) # Decode a string of L digits as an L string.
1839 268     268 1 1072 {my ($hex) = @_; # Hexadecimal string
1840 268         2412 my @c = grep {m/[0-9a-f]/i} split //, $hex; # Each hexadecimal digit
  6432         13668  
1841 268         804 my $c = ''; # Result
1842 268         2144 for my $i(keys @c) # Index of each hexadecimal digit
1843 6432 100       9648 {if ($i % 2 == 1) # End of latest pair
1844 3216         6968 {$c .= chr hex $c[$i-1].$c[$i]; # Convert to character
1845             }
1846             }
1847             $c # Return result
1848 268         2144 }
1849              
1850             my @translatePercentEncoding =
1851             (qq(\n)=>q(%0A),
1852             qq( ) =>q(%20),
1853             qq(\")=>q(%22),
1854             qq(\%)=>q(%25),
1855             qq(\-)=>q(%2d),
1856             qq(\.)=>q(%2e),
1857             qq(\<)=>q(%3c),
1858             qq(\>)=>q(%3e),
1859             qq(\\)=>q(%5c),
1860             qq(\^)=>q(%5e),
1861             qq(\_)=>q(%5f),
1862             qq(\`)=>q(%60),
1863             qq(\{)=>q(%7b),
1864             qq(\|)=>q(%7c),
1865             qq(\})=>q(%7d),
1866             qq(\~)=>q(%7e),
1867             );
1868              
1869             my %translatePercentEncoding = @translatePercentEncoding;
1870             my %TranslatePercentEncoding = reverse @translatePercentEncoding;
1871              
1872             sub wwwEncode($) # Percent encode a L per: https://en.wikipedia.org/wiki/Percent-encoding#Percent-encoding_reserved_characters
1873 2576     2576 1 475088 {my ($string) = @_; # String
1874 2576   66     9936 join '', map {$translatePercentEncoding{$_}//$_} split //, $string
  13616         64768  
1875             }
1876              
1877             sub wwwDecode($) # Percent decode a L B<$string> per: https://en.wikipedia.org/wiki/Percent-encoding#Percent-encoding_reserved_characters
1878 1840     1840 1 4784 {my ($string) = @_; # String
1879 1840         3312 my $r = '';
1880 1840         9200 my @s = split //, $string;
1881 1840         8464 while(@s)
1882 8832         14720 {my $c = shift @s;
1883 8832 100 66     31280 if ($c eq q(%) and @s >= 2)
1884 6624         18032 {$c .= shift(@s).shift(@s);
1885 6624   33     25024 $r .= $TranslatePercentEncoding{$c}//$c;
1886             }
1887             else
1888 2208         5520 {$r .= $c;
1889             }
1890             }
1891 1840         8096 $r =~ s(%0d0a) (\n)gs; # Awkward characters that appear in urls
1892 1840         4048 $r =~ s(\+) ( )gs;
1893 1840         6992 $r
1894             }
1895              
1896             #D1 Numbers # Numeric operations,
1897              
1898             sub powerOfTwo($) # Test whether a number B<$n> is a power of two, return the power if it is else B.
1899 1484     1484 1 4081 {my ($n) = @_; # Number to check
1900 1484         4081 for(0..128)
1901 3339 100       11872 {return $_ if 1<<$_ == $n;
1902 2226 100       5936 last if 1<<$_ > $n;
1903             }
1904             undef
1905 371         2226 }
1906              
1907             sub containingPowerOfTwo($) # Find log two of the lowest power of two greater than or equal to a number B<$n>.
1908 2226     2226 1 5194 {my ($n) = @_; # Number to check
1909 2226         5936 for(0..128)
1910 6307 100       20776 {return $_ if $n <= 1<<$_;
1911             }
1912             undef
1913 0         0 }
1914              
1915             #D2 Minima and Maxima # Find the smallest and largest elements of arrays.
1916              
1917             sub min(@) # Find the minimum number in a list of numbers confessing to any ill defined values.
1918 742     742 1 2597 {my (@m) = @_; # Numbers
1919 742 50       2226 my @n = grep {defined($_) and looks_like_number($_)} @_;
  1855         14469  
1920 742 50       3710 @_ == @n or confess q(Undefined or non numeric parameters present);
1921 742 50       2226 return undef unless @n;
1922 742         1113 my $M = shift;
1923 742         2597 for(@n)
1924 1855 100       7420 {$M = $_ if $_ < $M;
1925             }
1926             $M
1927 742         4452 }
1928              
1929             sub indexOfMin(@) # Find the index of the minimum number in a list of numbers confessing to any ill defined values.
1930 268     268 1 2680 {my (@m) = @_; # Numbers
1931 268 50       804 my @n = grep {defined($_) and looks_like_number($_)} @_;
  1072         7236  
1932 268 50       1340 @_ == @n or confess q(Undefined or non numeric parameters present);
1933 268 50       1072 return undef unless @n;
1934 268         536 my $M = 0;
1935 268         2144 for my $i(keys @n)
1936 1072         2144 {my $n = $n[$i];
1937 1072 100       2680 $M = $i if $n < $n[$M];
1938             }
1939             $M
1940 268         2412 }
1941              
1942             sub max(@) # Find the maximum number in a list of numbers confessing to any ill defined values.
1943 22458     22458 1 55241 {my (@m) = @_; # Numbers
1944 22458 50       41615 my @n = grep {defined($_) and looks_like_number($_)} @_;
  36819         169058  
1945 22458 50       58185 @_ == @n or confess q(Undefined or non numeric parameters present);
1946 22458 100       104187 return undef unless @n;
1947 16935         25775 my $M = shift;
1948 16935         29829 for(@n)
1949 36819 100       83589 {$M = $_ if $_ > $M;
1950             }
1951             $M
1952 16935         55614 }
1953              
1954             sub indexOfMax(@) # Find the index of the maximum number in a list of numbers confessing to any ill defined values.
1955 268     268 1 1340 {my (@m) = @_; # Numbers
1956 268 50       804 my @n = grep {defined($_) and looks_like_number($_)} @_;
  1072         6432  
1957 268 50       1072 @_ == @n or confess q(Undefined or non numeric parameters present);
1958 268 50       804 return undef unless @n;
1959 268         536 my $M = 0;
1960 268         1876 for my $i(keys @n)
1961 1072         1876 {my $n = $n[$i];
1962 1072 100       2680 $M = $i if $n > $n[$M];
1963             }
1964             $M
1965 268         1340 }
1966              
1967             sub arraySum(@) # Find the sum of any strings that look like numbers in an array.
1968 268     268 1 1340 {my (@a) = @_; # Array to sum
1969 268 50       804 my @n = grep {defined($_) and looks_like_number($_)} @_;
  2680         9648  
1970 268 50       1340 @_ == @n or confess q(Undefined or non numeric parameters present);
1971 268         536 my $sum = 0; $sum += $_ for @n;
  268         1340  
1972 268         1340 $sum
1973             }
1974              
1975             sub arrayProduct(@) # Find the product of any strings that look like numbers in an array.
1976 268     268 1 804 {my (@a) = @_; # Array to multiply
1977 268 50       804 my @n = grep {defined($_) and looks_like_number($_)} @_;
  1340         7236  
1978 268 50       1072 @_ == @n or confess q(Undefined or non numeric parameters present);
1979 268         804 my $product = 1; $product *= $_ for @n;
  268         804  
1980 268         1340 $product
1981             }
1982              
1983             sub arrayTimes($@) # Multiply by B<$multiplier> each element of the array B<@a> and return as the result.
1984 536     536 1 1340 {my ($multiplier, @a) = @_; # Multiplier, array to multiply and return
1985 536         1340 map {$multiplier * $_} @a
  2144         5092  
1986             }
1987              
1988             #D1 Sets # Set operations.
1989              
1990             sub mergeHashesBySummingValues(@) # Merge a list of hashes B<@h> by summing their values
1991 268     268 1 804 {my (@h) = @_; # List of hashes to be summed
1992 268         804 my %h;
1993 268         2144 for my $h(@h)
1994 804         4556 {$h{$_} += $$h{$_} for sort keys %$h;
1995             }
1996 268         2680 \%h
1997             }
1998              
1999             sub invertHashOfHashes(@) # Invert a hash of hashes: given {a}{b} = c return {b}{c} = c
2000 34     34 1 102 {my ($h) = @_; # Hash of hashes
2001 34         102 my %i; # Resulting inverted hash of hashes
2002 34         187 for my $a(keys $h->%*)
2003 68         255 {for my $b(keys $$h{$a}->%*)
2004 136         442 {$i{$b}{$a} = $$h{$a}{$b};
2005             }
2006             }
2007              
2008 34         527 \%i # Inverted hashes
2009             }
2010              
2011             sub unionOfHashKeys(@) # Form the union of the keys of the specified hashes B<@h> as one hash whose keys represent the union.
2012 42     42 1 126 {my (@h) = @_; # List of hashes to be united
2013 42 50       231 return {} unless @h;
2014 42 50       126 return $h[0] if @h == 1;
2015 42         189 my %u; # Union
2016 42         273 for my $h(@h) # Each hash to be united
2017 126         273 {for my $k(keys %$h) # Keys in current hash
2018 231         441 {$u{$k}++; # Add value to union array
2019             }
2020             }
2021              
2022 42         189 \%u # Union of all hashes
2023             }
2024              
2025             sub intersectionOfHashKeys(@) # Form the intersection of the keys of the specified hashes B<@h> as one hash whose keys represent the intersection.
2026 21     21 1 84 {my (@h) = @_; # List of hashes to be intersected
2027 21 50       315 return {} unless @h;
2028 21 50       105 return $h[0] if @h == 1;
2029              
2030 21         168 my $u = unionOfHashKeys(@h); # Union
2031 21         63 my $N = @h; # Number of hashes
2032 21         63 my %i; # Intersection
2033 21         189 for my $k(keys %$u) # Each key
2034 63 100       294 {if ($$u{$k} == $N) # Key present in all hashes
2035 21         84 {$i{$k}++ # Add hash value to intersection
2036             }
2037             }
2038              
2039 21         126 \%i # Intersection of all hashes
2040             }
2041              
2042             sub unionOfHashesAsArrays(@) # Form the union of the specified hashes B<@h> as one hash whose values are a array of corresponding values from each hash
2043 21     21 1 168 {my (@h) = @_; # List of hashes to be united
2044 21         168 my %u; # Union
2045 21         294 for my $i(keys @h) # Each hash to be united
2046 63         126 {my $h = $h[$i]; # Current hash
2047 63         168 for my $k(keys %$h) # Keys in current hash
2048 105 50       210 {if (defined(my $v = $$h{$k})) # Value defined at current key
2049 105         378 {$u{$k}[$i] = $v; # Add value to union array
2050             }
2051             }
2052             }
2053 21         315 \%u # Union of all hashes
2054             }
2055              
2056             sub intersectionOfHashesAsArrays(@) # Form the intersection of the specified hashes B<@h> as one hash whose values are an array of corresponding values from each hash
2057 21     21 1 84 {my (@h) = @_; # List of hashes to be intersected
2058 21         42 my $N = @h; # Number of hashes
2059 21         42 my %n; # Count of number of hashes that have each key
2060 21         399 for my $h(@h) # Each hash
2061 63 50       483 {defined($$h{$_}) ? ++$n{$_} : undef for keys %$h # Count the number of hashes that have this key
2062             }
2063              
2064 21         42 my %i; # Intersection
2065 21         63 for my $k(keys %n) # Each key
2066 63 100       147 {if ($n{$k} == $N) # Key present in all hashes
2067 21         126 {$i{$k}[$_] = $h[$_]{$k} for keys @h # Add hash value to intersection array
2068             }
2069             }
2070              
2071 21         273 \%i # Intersection of all hashes
2072             }
2073              
2074             sub setCombination(@) #P Count the elements in sets B<@s> represented as arrays of strings and/or the keys of hashes
2075 20514     20514 1 39382 {my (@s) = @_; # Array of arrays of strings and/or hashes
2076 20514         25244 my %e;
2077 20514         33275 for my $s(@s) # Intersect each set
2078 35616         88403 {my $t = reftype($s);
2079 35616 100       114021 if (!defined $t) # Scalar as a set of one
    100          
    50          
2080 4617         9729 {$e{$s}++
2081             }
2082             elsif ($t =~ m(array)is) # Intersect array of strings
2083 29886         44908 {for my $e(@$s) # Count instances of each string
2084 416218         596459 {$e{$e}++
2085             }
2086             }
2087             elsif ($t =~ m(hash)is) # Intersect keys of hash
2088 1113         5565 {for my $e(keys %$s) # Count instances of each key
2089 3339         6678 {$e{$e}++
2090             }
2091             }
2092             else # Unknown set type
2093 0         0 {confess "Unknown set type: $t";
2094             }
2095             }
2096 20514         41486 \%e # Count of each set member
2097             }
2098              
2099             sub setUnion(@) # Union of sets B<@s> represented as arrays of strings and/or the keys of hashes
2100 9761     9761 1 21863 {my (@s) = @_; # Array of arrays of strings and/or hashes
2101 9761         18986 my $e = setCombination(@_);
2102 9761         97117 sort keys %$e # Return words in union
2103             }
2104              
2105             sub setIntersection(@) # Intersection of sets B<@s> represented as arrays of strings and/or the keys of hashes
2106 742     742 1 2597 {my (@s) = @_; # Array of arrays of strings and/or hashes
2107 742         6307 my $e = setCombination(@_);
2108 742         1484 my $S = @s; # Set count
2109 742         6307 grep {$e->{$_} == $S} sort keys %$e # Return words that appear in all the sets
  5194         17437  
2110             }
2111              
2112             sub setIntersectionOverUnion(@) # Returns the size of the intersection over the size of the union of one or more sets B<@s> represented as arrays and/or hashes
2113 10011     10011 1 19230 {my (@s) = @_; # Array of arrays of strings and/or hashes
2114 10011         15411 my $e = setCombination(@_); # Set element count
2115 10011         17995 my $u = keys %$e; # Union size
2116 10011 50       19728 $u == 0 and confess "Empty union"; # 0/0 can be anything
2117 10011         12478 my $S = @s; # Set count
2118 10011         39584 my $i = grep {$e->{$_} == $S} keys %$e; # Intersection size
  310969         402272  
2119 10011         64002 $i/$u # Return ratio
2120             }
2121              
2122             sub setPartitionOnIntersectionOverUnion($@) # Partition, at a level of B<$confidence> between 0 and 1, a set of sets B<@sets> so that within each partition the L of any two sets in the partition is never less than the specified level of I<$confidence**2>
2123 1515     1515 1 5070 {my ($confidence, @sets) = @_; # Minimum setIntersectionOverUnion, array of arrays of strings and/or hashes representing sets
2124 1515         3447 my @s = sort {scalar(@$b) <=> scalar(@$a)} map {[setUnion($_)]} @sets; # Input sets as arrays in descending order of length
  4911         12111  
  4932         10195  
2125              
2126 1515         3488 my @partition;
2127 1515         4578 while(@s) # The proposed partition
2128 4932         9130 {my $base = shift @s; # Each set starting with the largest
2129 4932 100       10963 next unless defined $base; # No longer present
2130 3448         7758 my @base = ($base); # Create set of elements congruent with the base set
2131 3448         10704 for my $i(keys @s) # Each remaining set
2132 10011         14921 {my $s = $s[$i]; # Current set to compare with base set
2133 10011 100       16911 next unless defined $s; # Current set has already been classified
2134 9640 50       22706 last if scalar(@$s) < scalar(@$base) * $confidence; # Too small in comparison to the base and the sets are in descending order of size so all the remainder will have the same problem
2135 9640         15444 my $o = setIntersectionOverUnion($base, $s); # Overlap
2136 9640 100       22177 if ($o > $confidence) # Overlap is better than confidence
2137 1484         4081 {push @base, $s; # Include in partition
2138 1484         4081 $s[$i] = undef; # Remove from further consideration
2139             }
2140             }
2141 3448         9165 push @partition, \@base; # Save partition
2142             }
2143 1515         14909 @partition; # Return partitions
2144             }
2145              
2146             sub setPartitionOnIntersectionOverUnionOfSetsOfWords($@) # Partition, at a level of B<$confidence> between 0 and 1, a set of sets B<@sets> of words so that within each partition the L of any two sets of words in the partition is never less than the specified I<$confidence**2>
2147 1144     1144 1 4337 {my ($confidence, @sets) = @_; # Minimum setIntersectionOverUnion, array of arrays of strings and/or hashes representing sets
2148              
2149 1144         2285 my %u; # Normalized set to input sets with this normalization
2150 1144         5452 for my $s(@sets) # Each set
2151 3819         6763 {push @{$u{join ' ', setUnion($s)}}, $s; # Normalized set back to each input set of words
  3819         9490  
2152             }
2153             my @partition = setPartitionOnIntersectionOverUnion($confidence, # Partition normalized sets
2154 1144         5056 map {[split /\s+/, $_]} sort keys %u);
  3819         33407  
2155              
2156 1144         4765 my @P;
2157 1144         2305 for my $partition(@partition) # Each partition
2158 2706         3479 {my @p;
2159 2706         7319 for my $set(@$partition) # Each set in the current partition
2160 3819         6071 {push @p, @{$u{join ' ', @$set}};
  3819         12237  
2161             }
2162              
2163 2706         6081 push @P, \@p;
2164             }
2165             @P
2166 1144         10553 }
2167              
2168             sub setPartitionOnIntersectionOverUnionOfStringSets($@) # Partition, at a level of B<$confidence> between 0 and 1, a set of sets B<@strings>, each set represented by a string containing words and punctuation, each word possibly capitalized, so that within each partition the L of any two sets of words in the partition is never less than the specified I<$confidence**2>
2169 773     773 1 3478 {my ($confidence, @strings) = @_; # Minimum setIntersectionOverUnion, sets represented by strings
2170              
2171 773         1796 my %u; # Normalized set to input sets with this normalization
2172 773         5152 for my $s(@strings) # Each set
2173 5970         28136 {my $n = nws($s =~ s([^a-z ]) ()girs);
2174 5970         8596 push @{$u{$n}}, $s; # Normalized set back to each input set of words
  5970         16089  
2175             }
2176              
2177             my @partition = setPartitionOnIntersectionOverUnionOfSetsOfWords($confidence, # Partition normalized strings
2178 773         6328 map {[split /\s+/, $_]} sort {length($a) <=> length($b)} sort keys %u);
  2706         46010  
  2685         6106  
2179              
2180 773         2090 my @P; # Partition of strings
2181 773         1932 for my $partition(@partition) # Each partition
2182 1964         3136 {my @p;
2183 1964         3274 for my $set(@$partition) # Each set in the current partition
2184 2706         3888 {push @p, @{$u{join ' ', @$set}};
  2706         10956  
2185             }
2186              
2187 1964         2986 push @P, \@p;
2188             }
2189             @P
2190 773         9512 }
2191              
2192             sub setPartitionOnIntersectionOverUnionOfHashStringSets($$) # Partition, at a level of B<$confidence> between 0 and 1, a set of sets B<$hashSet> represented by a hash, each hash value being a string containing words and punctuation, each word possibly capitalized, so that within each partition the L of any two sets of words in the partition is never less than the specified B<$confidence**2> and the partition entries are the hash keys of the string sets.
2193 388     388 1 2042 {my ($confidence, $hashSet) = @_; # Minimum setIntersectionOverUnion, sets represented by the hash value strings
2194 388 50       6024 reftype($hashSet) =~ m(hash)is or confess "Second parameter must be a hash";
2195              
2196 388         1957 my %u; # Invert the hash so we can present the partitions by hash key
2197 388         4447 for my $s(sort keys %$hashSet) # Each set
2198 4649         7909 {push @{$u{$$hashSet{$s}}}, $s; # Invert
  4649         11535  
2199             }
2200              
2201             my @partition = setPartitionOnIntersectionOverUnionOfStringSets($confidence, # Partition strings
2202 388         4076 sort {length($a) <=> length($b)} sort values %$hashSet);
  4261         6388  
2203              
2204 388         1637 my @P; # Partition of strings
2205 388         1181 for my $partition(@partition) # Each partition
2206 1184         2011 {my @p;
2207             my %p; # If n sets are identical we get n repetitions - this hash prevents that.
2208 1184         2045 for my $set(@$partition) # Each set in the current partition
2209 4649 50       12625 {if (my $u = $u{$set})
2210 4649         7300 {for my $U(@$u)
2211 29401 100       45708 {push @p, $U unless $p{$U}++;
2212             }
2213             }
2214             }
2215              
2216 1184         4093 push @P, [sort @p];
2217             }
2218 388         2345 sort {scalar(@$b) <=> scalar(@$a)} @P
  796         7647  
2219             }
2220              
2221             sub setPartitionOnIntersectionOverUnionOfHashStringSetsInParallel($$) # Partition, at a level of B<$confidence> between 0 and 1, a set of sets B<$hashSet> represented by a hash, each hash value being a string containing words and punctuation, each word possibly capitalized, so that within each partition the L of any two sets of words in the partition is never less than the specified B<$confidence**2> and the partition entries are the hash keys of the string sets. The partition is performed in square root parallel.
2222 17     17 1 68 {my ($confidence, $hashSet) = @_; # Minimum setIntersectionOverUnion, sets represented by the hash value strings
2223 17 50       493 reftype($hashSet) =~ m(hash)is or confess "Second parameter must be a hash";
2224              
2225 17         119 my %u; # Invert the hash so we can present the partitions by hash key
2226 17         2023 for my $s(sort keys %$hashSet) # Each set
2227 3536         3570 {push @{$u{$$hashSet{$s}}}, $s; # Invert
  3536         5644  
2228             }
2229              
2230 17         1139 my @strings = sort {length($a) <=> length($b)} sort values %$hashSet; # Strings in length order
  3519         3791  
2231 17         493 my @square = squareArray(@strings);
2232              
2233 17         221 my @partition;
2234              
2235             &runInParallel(&numberOfCpus(8), # Partition strings in square root blocks in parallel
2236             sub
2237 14     14   1573 {[setPartitionOnIntersectionOverUnionOfStringSets($confidence, $_[0]->@*)]; # Partition strings
2238             },
2239             sub # Consolidate partitions
2240 3     3   96 {for my $p(@_)
2241 42         684 {push @partition, @$p;
2242             }
2243 17         255 }, @square);
2244              
2245 3         150 my @P; # Partition of strings
2246 3         66 for my $partition(@partition) # Each partition
2247 114         195 {my @p;
2248             my %p; # If n sets are identical we get n repetitions - this hash prevents that.
2249 114         171 for my $set(@$partition) # Each set in the current partition
2250 624 50       1092 {if (my $u = $u{$set})
2251 624         744 {for my $U(@$u)
2252 4992 100       8685 {push @p, $U unless $p{$U}++;
2253             }
2254             }
2255             }
2256              
2257 114         501 push @P, [sort @p];
2258             }
2259 3         108 sort {scalar(@$b) <=> scalar(@$a)} @P
  111         1173  
2260             }
2261              
2262             sub contains($@) # Returns the indices at which an B<$item> matches elements of the specified B<@array>. If the item is a regular expression then it is matched as one, else it is a number it is matched as a number, else as a string.
2263 1484     1484 1 7049 {my ($item, @array) = @_; # Item, array
2264 1484         4452 my @r;
2265 1484 100       12985 if (ref($item) =~ m(Regexp)) # Match via a regular expression
    100          
2266 371         2597 {for(keys @array)
2267 3710 100       21147 {push @r, $_ if $array[$_] =~ m($item)s;
2268             }
2269             }
2270             elsif (looks_like_number($item)) # Match as a number
2271 742         3710 {for(keys @array)
2272 2968 100       8904 {push @r, $_ if $array[$_]+0 == $item;
2273             }
2274             }
2275             else # Match as a string
2276 371         1484 {for(keys @array)
2277 3710 100       16324 {push @r, $_ if $array[$_] eq $item;
2278             }
2279             }
2280             @r
2281 1484         11501 }
2282              
2283             sub countOccurencesInString($$) # Returns the number of occurrences in B<$inString> of B<$searchFor>.
2284 268     268 1 804 {my ($inString, $searchFor) = @_; # String to search in, string to search for.
2285 268         804 my $n = 0;
2286 268 50       1340 length($inString) >= length($searchFor) or
2287             confess "String to search must be longer than string to look for";
2288 268         536 my $p = -1;
2289 268         26800 ++$n while(($p = index($inString, $searchFor, $p+1)) > -1);
2290 268         1340 $n
2291             }
2292              
2293             sub partitionStringsOnPrefixBySize # Partition a hash of strings and associated sizes into partitions with either a maximum size B<$maxSize> or only one element; the hash B<%Sizes> consisting of a mapping {string=>size}; with each partition being named with the shortest string prefix that identifies just the strings in that partition. Returns a list of {prefix => size}... describing each partition.
2294 336     336 1 2940 {my ($maxSize, %Sizes) = @_; # Maximum size of a partition, {string=>size}... hash to be partitioned
2295              
2296 336         798 my %paths; # Path to each character in each string
2297             my %sizes; # Size associate with each path
2298 336         1869 for my $string(sort keys %Sizes) # Create a path of hashes with single character keys
2299 2079         3738 {my $size = $Sizes{$string}; # Size associated with the string
2300 2079         2835 my $paths = '';
2301 2079         4893 my @s = split m(), $string; # String as single characters
2302 2079         4872 while(@s) # Shorten path
2303 5943         8463 {my $k = join '', map {qq({'$_'})} @s; # Path of hashes with single character keys
  11697         22869  
2304 5943         9954 $paths .= qq(\$paths$k //= {};\n); # Auto vivify
2305 5943         7665 my $d = join '', @s; # Path name
2306 5943         9156 $sizes{$d} += $size; # Aggregate size
2307 5943         10059 pop @s; # Move up one level
2308             }
2309 2079         2667 $sizes{q()} += $size; # Total size
2310 2079         144648 eval $paths; # Create paths - this level of aggregation seems to give the fastest overall response
2311 2079 50       8421 confess "$paths\n$@\n" if $@; # Unable to create path
2312             }
2313              
2314 336         945 my %partition; # Partition the paths
2315              
2316             my $partition; $partition = sub # Partition paths at the current level
2317 1428     1428   2667 {my ($paths, @path) = @_; # Path at this level, path to this level
2318              
2319 1428         2562 my $p = join '', @path; # Path name
2320 1428         1785 my $s = $sizes{$p}; # Size of path
2321              
2322 1428 100 100     4032 if ($s <= $maxSize or !keys %$paths) # Small enough or complete path
2323 903         1995 {$partition{$p} = $s; # Path => size
2324             }
2325             else # Still too big
2326 525         1344 {for my $d(sort keys %$paths) # Next level
2327 1113         2436 {&$partition($$paths{$d}, @path, $d); # Try at the next level
2328             }
2329             }
2330 336         2268 };
2331              
2332 336 100       1302 &$partition(\%paths) if keys %paths; # Partition from the top
2333              
2334 336         4998 %partition
2335             }
2336              
2337             sub transitiveClosure($) # Transitive closure of a hash of hashes
2338 1     1 1 4 {my ($h) = @_; # Hash of hashes
2339              
2340 1         34 my %keys = arrayToHash(keys %$h)->%*; # Find all the keys to consider
2341 1         16 for my $i(keys %$h)
2342 3         12 {my $value = $$h{$i};
2343 3 50       37 if (reftype($value) =~ m(hash)i)
2344 3         11 {%keys = (%keys, arrayToHash(keys %$value)->%*); # Just the sub keys
2345             }
2346             }
2347              
2348 1         3 my %t; # Transitive closure
2349 1         7 for my $a(keys %keys)
2350 4         16 {my $i = $$h{$a};
2351 4 100 66     54 if ($i and reftype($i) =~ m(hash)i)
2352 3         6 {for my $b(keys %keys)
2353 12 100       47 {$t{$a}{$b} = 1 if $$i{$b}
2354             }
2355             }
2356             }
2357              
2358 1         11 for(1..100)
2359 2         5 {my $changes = 0;
2360 2         10 for my $a(keys %keys)
2361 8         13 {for my $b(keys %keys)
2362 32 100       55 {if ($t{$a}{$b})
2363 10   100     63 {$t{$b}{$_} and !$t{$a}{$_}++ and ++$changes for keys %keys # a=>b and b=>c so a=>c
      66        
2364             }
2365             }
2366             }
2367 2 100       9 last unless $changes;
2368             }
2369              
2370 1         10 for my $s(keys %t) # Remove empty hashes
2371 4 100       17 {delete $t{$s} unless keys $t{$s}->%*;
2372             }
2373              
2374 1         8 my %s;
2375             my @s;
2376 1         7 for my $s(sort keys %t) # Compress by creating soft pointers to common key sequences
2377 3         17 {my $k = join ' ', sort keys $t{$s}->%*;
2378 3 100       15 if (defined(my $i = $s{$k})) # Reuse a matching entry indexed from zero
2379 1         3 {$t{$s} = $i
2380             }
2381             else # Create a new entry
2382 2         5 {push @s, $t{$s}; $t{$s} = $s{$k} = @s - 1;
  2         5  
2383             }
2384             }
2385              
2386 1         10 genHash(q(Data::Table::Text::TransitiveClosure),
2387             start => \%t,
2388             end => \@s,
2389             )
2390             } # transitiveClosure
2391              
2392             #D1 Format # Format data structures as tables.
2393              
2394             sub maximumLineLength($) # Find the longest line in a B<$string>.
2395 16192     16192 1 31280 {my ($string) = @_; # String of lines of text
2396 16192   100     66240 max(map {length($_)} split /\n/, ($string//'')) // 0 # Length of longest line
  21344   100     55568  
2397             }
2398              
2399             sub formatTableMultiLine($;$) #P Tabularize text that has new lines in it.
2400 1840     1840 1 7360 {my ($data, $separator) = @_; # Reference to an array of arrays of data to be formatted as a table, optional line separator to use instead of new line for each row.
2401 1840 50       25024 ref($data) =~ /array/i or
2402             confess "Array reference required not:\n".dump($data)."\n";
2403              
2404 1840         8832 my @width; # Maximum width of each column
2405 1840         6992 for my $row(@$data) # Find maximum width of each column
2406 5152 50       26864 {ref($row) =~ /array/i or
2407             confess "Array reference required not:\n".dump($row)."\n";
2408 5152         16560 for my $col(0..$#$row) # Each column index
2409 15824   100     49312 {my $a = $width[$col] // 0; # Maximum length of data so far
2410 15824         37536 my $b = maximumLineLength($row->[$col]); # Length of longest line in current item
2411 15824 100       44896 $width[$col] = ($a > $b ? $a : $b); # Update maximum length
2412             }
2413             }
2414              
2415 1840         6256 my @text; # Formatted data
2416 1840         6992 for my $row(@$data) # Each row
2417 5152         11776 {my @row; # Laid out text
2418 5152         11408 for my $col(0..$#$row) # Each column
2419 15824         26496 {my $m = $width[$col]; # Maximum width
2420 15824   100     61088 for my $i(split /\n/, $row->[$col]//'') # Each line of item
2421 20240 100       96048 {if ($i !~ /\A\s*[-+]?\s*(\d|[,])+(\.\d+)?([Ee]\s*[-+]?\s*\d+)?\s*\Z/) # Not a number - left justify
2422 14352         23184 {push @{$row[$col]}, substr($i.(' 'x$m), 0, $m);
  14352         68816  
2423             }
2424             else # Number - right justify
2425 5888         11040 {push @{$row[$col]}, substr((' 'x$m).$i, -$m);
  5888         26128  
2426             }
2427             }
2428             }
2429              
2430 5152   100     11776 my $n = max(map {scalar @{$_//[]}} @row)//0; # Maximum number of rows
  13616   100     22816  
  13616         52992  
2431              
2432 5152         12880 for my $r(1..$n) # Each row of the items
2433 11776         22080 {my $text = '';
2434 11776         28704 for my $col(0..$#$row) # Each item
2435 36800   66     142784 {$text .= ($row[$col][$r-1] // (q( ) x $width[$col])).q( );
2436             }
2437 11776         90896 $text =~ s(\s*\Z) ()s; # Strip trailing blanks as they are not needed for padding
2438 11776         44528 push @text, $text;
2439             }
2440             }
2441              
2442 1840   50     17296 my $s = $separator//"\n";
2443 1840         51152 join($s, @text).$s
2444             }
2445              
2446             sub formatTableBasic($) # Tabularize an array of arrays of text.
2447 15090     15090 1 35870 {my ($data) = @_; # Reference to an array of arrays of data to be formatted as a table.
2448 15090 50       90062 ref($data) =~ /array/i or # Must be an array
2449             confess "Array reference required not:\n".dump($data)."\n";
2450 15090         28365 my @width; # Maximum width of each column
2451              
2452 15090         41506 for my $row(@$data) # Each row
2453 66092         124462 {for my $col(0..$#$row) # Each column index
2454 217011   100     389716 {my $text = $row->[$col] // ''; # Text of current line
2455 217011 100       399950 return &formatTableMultiLine(@_) if $text =~ m(\n); # Element has a new line in it
2456 215171   100     406452 my $a = $width[$col] // 0; # Maximum length of data so far
2457 215171         261798 my $b = length($text); # Length of longest line in current item
2458 215171 100       399448 $width[$col] = ($a > $b ? $a : $b); # Update maximum length
2459             }
2460             }
2461              
2462 13250         23951 my @text; # Formatted data
2463 13250         25472 for my $row(@$data)
2464 61676         88606 {my $text = ''; # Formatted text
2465 61676         109044 for my $col(0..$#$row)
2466 204499         281103 {my $m = $width[$col]; # Maximum width
2467 204499   100     369768 my $i = $row->[$col]//''; # Current item
2468 204499 100       640853 if ($i !~ /\A\s*[-+]?\s*(\d|[,])+(\.\d+)?([Ee]\s*[-+]?\s*\d+)?\s*\Z/) # Not a number - left justify
2469 114078         416452 {$text .= substr($i.(' 'x$m), 0, $m)." ";
2470             }
2471             else # Number - right justify
2472 90421         243936 {$text .= substr((' 'x$m).$i, -$m)." ";
2473             }
2474             }
2475 61676         305434 $text =~ s(\s*\Z) ()s; # Strip trailing blanks as they are not needed for padding
2476 61676         141481 push @text, $text;
2477             }
2478              
2479 13250         105507 join("\n", @text)."\n"
2480             }
2481              
2482             sub formatTableClearUpLeft($) #P Blank identical column values up and left
2483 158     158 1 1422 {my ($data) = @_; # Array of arrays
2484              
2485 158         1580 for my $row(1..@$data) # Each row from last to first
2486 632         1896 {my $d = $$data[-$row];
2487 632 100       2528 last if $row == @$data;
2488              
2489 474         1106 my $p = $row+1;
2490 474         2212 for my $c(reverse 1..@$d) # Compare left values in current row to previous row
2491 1738 100       4898 {next unless my $dc = $$d[-$c];
2492 1264 50       3002 next unless my $pc = $$data[-$p][-$c];
2493 1264 100       2686 if ($dc eq $pc) # Blank equal value
2494 790         2686 {$$d[-$c] = q();
2495             }
2496             else # Values not equal terminates equal valued column suppression
2497 474         1738 {last;
2498             }
2499             }
2500             }
2501             }
2502              
2503             sub formatTableAA($$%) #P Tabularize an array of arrays.
2504 8884     8884 1 28658 {my ($data, $title, %options) = @_; # Data to be formatted, reference to an array of titles, options
2505 8884 50 33     67910 return dump($data) unless ref($data) =~ /array/i and @$data;
2506              
2507 8884         20293 my $d; # Copy of the input data because we are going to modify it
2508 8884         22349 for my $row(@$data) # Each row
2509 34659 50       91522 {ref($row) =~ /array/i or # Each row must be an array
2510             confess "Array reference required not:\n".dump($row)."\n";
2511 34659         90683 push @$d, [q(), @$row]; # Copy each row with space for a row number
2512             }
2513              
2514 8884 50       24951 if (my $w = $options{maximumColumnWidth}) # Apply maximum column width if supplied
2515 0         0 {for my $r(@$d)
2516 0         0 {for(@$r)
2517 0 0       0 {$_ = substr($_, 0, $w) if length > $w;
2518             }
2519             }
2520             }
2521              
2522 8884 100       22758 formatTableClearUpLeft($d) if $options{clearUpLeft}; # Clear up and left if requested
2523 8884         53251 $$d[$_-1][0] = $_ for 1..@$data; # Number each row now that we have suppressed duplicates
2524 8884 100       49050 unshift @$d, ['', @$title] if $title; # Add title
2525 8884         35948 formatTableBasic($d); # Format array
2526             }
2527              
2528             sub formatTableHA($;$) #P Tabularize a hash of arrays.
2529 1110     1110 1 4440 {my ($data, $title) = @_; # Data to be formatted, optional titles
2530 1110 50 33     29617 return dump($data) unless ref($data) =~ /hash/i and keys %$data;
2531 1110         2959 my $d;
2532 1110 100       4066 push @$d, $title if $title;
2533 1110         9248 push @$d, [$_, @{$data->{$_}}] for sort keys %$data;
  4437         17763  
2534 1110         5921 formatTableBasic($d);
2535             }
2536              
2537             sub formatTableAH($) #P Tabularize an array of hashes.
2538 1110     1110 1 4069 {my ($data) = @_; # Data to be formatted
2539 1110 50 33     15896 return dump($data) unless ref($data) =~ /array/i and @$data;
2540              
2541 1110         2959 my %k; @k{keys %$_}++ for @$data; # Column headers
  1110         15531  
2542 1110         10355 my @k = sort keys %k;
2543 1110         7770 $k{$k[$_-1]} = $_ for 1..@k;
2544              
2545 1110         6651 my $d = [['', @k]];
2546 1110         7393 for(1..@$data)
2547 3698         9616 {push @$d, [$_];
2548 3698         22981 my %h = %{$data->[$_-1]};
  3698         32962  
2549 3698         22933 $d->[-1][$k{$_}] = $h{$_} for keys %h;
2550             }
2551 1110         4069 formatTableBasic($d);
2552             }
2553              
2554             sub formatTableHH($) #P Tabularize a hash of hashes.
2555 1110     1110 1 3695 {my ($data) = @_; # Data to be formatted
2556 1110 50 33     17006 return dump($data) unless ref($data) =~ /hash/i and keys %$data;
2557              
2558 1110         3330 my %k; @k{keys %$_}++ for values %$data; # Column headers
  1110         14412  
2559 1110         7770 my @k = sort keys %k;
2560 1110         8509 $k{$k[$_-1]} = $_ for 1..@k;
2561              
2562 1110         8874 my $d = [['', @k]];
2563 1110         6657 for(sort keys %$data)
2564 3698         10732 {push @$d, [$_];
2565 3698         6657 my %h = %{$data->{$_}};
  3698         15525  
2566 3698         22939 $d->[-1][$k{$_}] = $h{$_} for keys %h;
2567             }
2568 1110         5553 formatTableBasic($d);
2569             }
2570              
2571             sub formatTableA($;$) #P Tabularize an array.
2572 739     739 1 2217 {my ($data, $title) = @_; # Data to be formatted, optional title
2573 739 50 33     22182 return dump($data) unless ref($data) =~ /array/i and @$data;
2574              
2575 739         2220 my $d;
2576 739 100       17793 push @$d, $title if $title;
2577 739         5173 for(keys @$data)
2578 2214 50       13685 {push @$d, @$data > 1 ? [$_, $data->[$_]] : [$data->[$_]]; # Skip line number if the array is degenerate
2579             }
2580 739         3695 formatTableBasic($d);
2581             }
2582              
2583             sub formatTableH($;$) #P Tabularize a hash.
2584 1110     1110 1 4443 {my ($data, $title) = @_; # Data to be formatted, optional title
2585              
2586 1110 50 33     15531 return dump($data) unless ref($data) =~ /hash/i and keys %$data;
2587              
2588 1110         2959 my $d;
2589 1110 100       4808 push @$d, $title if $title;
2590 1110         8880 for(sort keys %$data)
2591 2959         15190 {push @$d, [$_, $data->{$_}];
2592             }
2593 1110         7034 formatTableBasic($d);
2594             }
2595              
2596             our @formatTables; # tttt Report of all the reports that have been created
2597              
2598             sub formatTableCheckKeys #P Options available for formatting tables
2599 14086     14086 1 421807 {{title => <<'END',
2600             Title for the table
2601             END
2602             head => <<'END',
2603             Header text which will preceed the formatted table.
2604             DDDD will be replaced with the current date and time.
2605             NNNN will be replaced with the number of rows in the table.
2606             TTTT will be replaced with the title from the title keyword
2607             END
2608             columns => <<'END',
2609             Definition of each column one per line: the first word is the name of the column, while subsequent words describe the column.
2610             END
2611             foot => <<'END',
2612             Footer text which will follow the table
2613             END
2614             summarize => <<'END',
2615             If true, each column of an array of arrays will be summarized by printing its
2616             distinct values and a count of how often each value occurs in a series of
2617             smaller tables following the main table.
2618             END
2619             clearUpLeft => <<'END',
2620             If numeric +/-\$N, replace any left hand column values repeated in the
2621             following row with white space to make it easier to follow the range of keys.
2622             If a positive count is given the clearing will always be stopped after the
2623             numbered column (based from 1) if negative, then clearing will be stopped after
2624             the column obtained by counting back counting 1-\$N columns from the last
2625             column. Thus a value of -1 will stop clearing after the final column which
2626             could potentially produce a blank row if there are two duplicate rows in
2627             sequence.
2628             END
2629             file => q(The name of a file to which to write the formatted table.),
2630             rows => q(The number of rows in the report),
2631             zero => q(Write the report even if the table is empty.),
2632             wide => q(Write a note explaining the need to scroll to the right if true),
2633             msg => q(Write a message to STDERR summarizing the situation if true),
2634             csv => q(Write a csv version of the report if true),
2635             indent => q(Number of spaces to be used to indent the table, defaults to zero),
2636             debug => q(Debug table processing),
2637             facet => <
2638             Counts in html reports with the same facet will be plotted on the same chart to
2639             provide a visual indication of their relative sizes.
2640             END
2641             aspectColor => <
2642             The color in which to draw this aspect on charts and graphs.
2643             END
2644             maximumColumnWidth => <
2645             The maximum width permitted for a column, defaults to unlimited.
2646             END
2647             }} # formatTableCheckKeys
2648              
2649             sub formatTable($;$%) #I Format various B<$data> structures as a table with titles as specified by B<$columnTitles>: either a reference to an array of column titles or a string each line of which contains the column title as the first word with the rest of the line describing that column.\mOptionally create a report from the table using the report B<%options> described in L
2650 14063     14063 1 59431 {my ($data, $columnTitles, @options) = @_; # Data to be formatted, optional reference to an array of titles or string of column descriptions, options
2651              
2652             my %options = sub # Make column titles an option so that the options list is easily reused. The original arrangement where column titles were a separate (optional) parameter will eventually be deprecated. To make this work, columns=> has to be the first option.
2653 14063 100 100 14063   96961 {if ($columnTitles and !ref($columnTitles) and
      100        
      66        
2654             $columnTitles eq q(columns) and scalar(@options) % 2 == 1)
2655 2         57 {my %o = ($columnTitles, @options);
2656 2         31 $columnTitles = $o{columns};
2657 2         74 return %o;
2658             }
2659 14061 50       48056 scalar(@options) % 2 and confess "Options fail to pair";
2660             @options
2661 14063         126711 }->();
  14061         38906  
2662              
2663 14063         105827 checkKeys(\%options, formatTableCheckKeys); # Check report options
2664              
2665             my ($titleString, $title) = sub # Title string, column headers
2666 14063 100   14063   42611 {return (undef, undef) unless defined $columnTitles; # No titles
2667 9617 100       60789 if (my $r = reftype $columnTitles) # Array of column titles
2668 9347 50       75869 {return (undef, $columnTitles) if $r =~ m(\Aarray\Z)si;
2669             }
2670 270 50       825 return (q(), q()) unless $columnTitles; # Column titles are not required for hash of hashes
2671 270         1102 my @c = map {[split m(\s+), $_, 2]} split "\n", $columnTitles; # Column definitions
  808         3277  
2672 270         850 my $s = &formatTable(\@c, [qw(Column Description)]); # Column definitions descriptions table
2673 270         811 ($s, [map {$$_[0]} @c])
  808         2181  
2674 14063         116873 }->();
2675              
2676 14063         94115 my ($a, $h, $o) = (0, 0, 0); # Check structure of input data tttt
2677             my $checkStructure = sub
2678 14063     14063   28360 {for(@_)
2679 51665         98720 {my $r = reftype($_); # Process arrays and hashes or objects built on them
2680 51665 100       89632 if ($r)
2681 46492 100       117846 {if ($r =~ /array/i) {++$a}
  39096 50       58712  
2682 7396         14418 elsif ($r =~ /hash/i) {++$h}
2683 0         0 else {++$o}
2684             }
2685 5173         9613 else {++$o}
2686             }
2687 14063         75291 };
2688              
2689             my $formattedTable = sub # Format table
2690 14063 100   14063   133590 {if (reftype($data) =~ /array/i)
    50          
2691 10733         33197 {$checkStructure->( @$data);
2692 10733 50 66     112362 return formatTableAA($data, $title, %options) if $a and !$h and !$o;
      66        
2693 1849 100 66     21814 return formatTableAH($data) if !$a and $h and !$o;
      66        
2694 739         2956 return formatTableA ($data, $title);
2695             }
2696             elsif (reftype($data) =~ /hash/i)
2697 3330         16659 {$checkStructure->(values %$data);
2698 3330 50 66     21820 return formatTableHA($data, $title) if $a and !$h and !$o;
      66        
2699 2220 100 66     19226 return formatTableHH($data) if !$a and $h and !$o;
      66        
2700 1110         7408 return formatTableH ($data, $title);
2701             }
2702 14063         82553 }->();
2703              
2704 14063 100       280982 return $formattedTable unless keys %options; # Return table as is unless report requested
2705              
2706 3311         12039 my ($Title, $head, $foot, $file, $zero, $summarize, $wide, $msg, $csv, $zwsp, $indent) = map{$options{$_}} # Options requested
  36421         60711  
2707             qw(title head foot file zero summarize wide msg csv zwsp indent);
2708              
2709 3311         8058 my @report;
2710 3311         20539 my $date = dateTimeStamp;
2711 3311         21674 my $N = keyCount(1, $data);
2712 3311   100     31807 my $H = ($head//'') =~ s(DDDD) ($date)gr =~ s(NNNN) ($N)gr;
2713 3311 100       13208 $H =~ s(TTTT) ($title)gs if $Title;
2714 3311 100       10579 push @report, $Title if $Title;
2715 3311 100       12933 push @report, $H if $head;
2716 3311 100       19157 push @report, qq(This file: $file) if $file;
2717 3311 100       10466 push @report, $titleString if $titleString;
2718 3311 50       12025 push @report, <
2719             Please note that this is a wide report: you might have to scroll
2720             a long way to the right to see all the columns of data available!
2721             END
2722 3311 100       8166 push @report, <
2723             Summary_of_column - Count of unique values found in each column Use the Geany flick capability by placing your cursor on the first word
2724             Comma_Separated_Values_of_column - Comma separated list of the unique values found in each column of these lines and pressing control + down arrow to see each sub report.
2725             END
2726              
2727 3311         6714 push @report, $formattedTable;
2728 3311 100       9734 push @report, $foot if $foot;
2729              
2730 3311   100     42857 push @formatTables, [$N, $Title//nws($H, 80), $file]; # Report of all the reports created
2731              
2732 3311 0 33     13518 if ($msg and $file and $head)
      33        
2733 0         0 {lll $H =~ s(\n.*\Z) ()gsr;
2734 0         0 lll qq(See file: $file);
2735             }
2736              
2737 3311 100       9843 if ($summarize) # Summarize an array of arrays if requested
2738 536         2412 {my $s = '';
2739 536 50       3484 if (reftype($data) =~ /array/i)
2740 536 50 33     6432 {if ($a and !$h and !$o)
      33        
2741 536         1876 {for my $col(1..@$title)
2742 1340         2680 {my $n = $title->[$col-1];
2743 1340         2680 my $c = qq(Summary_of_column_$n);
2744 1340         2948 my @s = summarizeColumn($data, $col-1);
2745 1340         6968 my $t = &formatTable(\@s, [q(Count), $n]);
2746 1340         6432 $s .= qq($c\n$t\n);
2747 1340         2412 if (1)
2748 1340         2948 {my $v = join ",", sort map {dump($$_[1])} @s;
  4556         267464  
2749 1340         106932 $s .= "Comma_Separated_Values_of_column_$n: $v\n\n";
2750             }
2751             }
2752             }
2753             }
2754 536         1340 push @report, $s;
2755             }
2756              
2757 3311 100       11817 if ($file) # Write a csv version of the report (Sabine)
2758 373 50       7826 {if (reftype($data) =~ /array/i)
2759 373 50 33     10829 {if ($a && !$h && !$o or $zero)
      33        
      33        
2760 373         1485 {my @s;
2761              
2762 373 50       5540 if ($title) # Column headers
2763 373 100       3707 {my $r = join ',', map {defined($_) ? $_ : q(unknown)} @$title;
  749         8549  
2764 373         1519 push @s, $r;
2765             }
2766              
2767 373         2982 for my $d(@$data)
2768 753         39245 {push @s, join ',', map{dump($_)} @$d;
  1518         156886  
2769             }
2770 373         61978 my $csvFile = setFileExtension($file, q(csv));
2771 373         1494 my $csvData = join "\n", @s;
2772 373         9715 overWriteFile($csvFile, "$csvData\n");
2773             }
2774             }
2775             }
2776              
2777 3311         16533 my $report = join "\n\n", @report; # Create report
2778              
2779 3311 50 33     20129 overWriteFile($file, $report) if $file and $a+$h+$o || $zero; # Only write the report if there is some data in it or the zero option has been specified to write it regardless.
      66        
2780              
2781 3311 50       10454 $report = indentString($report, $indent) if $indent; # Indent table if requested
2782              
2783 3311         112552 $report
2784             } # formatTable
2785              
2786             sub formattedTablesReport(@) # Report of all the reports created. The optional parameters are the same as for L
2787 268     268 1 804 {my (@options) = @_; # Options
2788              
2789 268   50     1340 formatTable([sort {($a->[1]//'') cmp ($b->[1]//'')} @formatTables], <
  2948   50     7236  
2790             Rows Number of entries in table
2791             Title Title of the report
2792             File File containing the report
2793             END
2794             @options);
2795             }
2796              
2797             sub summarizeColumn($$) # Count the number of unique instances of each value a column in a table assumes.
2798 1608     1608 1 3484 {my ($data, $column) = @_; # Table == array of arrays, column number to summarize.
2799 1608         2948 my @data = map {$$_[$column]} @$data;
  14472         19832  
2800 1608         2680 my %data;
2801 1608         3752 for my $d(@data)
2802 14472 50       34840 {$data{$d}++ if defined $d;
2803             }
2804 6164 100       13936 sort {return $$a[1] cmp $$b[1] if $$b[0] == $$a[0]; # Return array of [count, key]
2805 1608         6164 return $$b[0] <=> $$a[0]} map {[$data{$_}, $_]} sort keys %data;
  3216         9380  
  5628         16080  
2806             }
2807              
2808             sub keyCount($$) # Count keys down to the specified level.
2809 4053     4053 1 16917 {my ($maxDepth, $ref) = @_; # Maximum depth to count to, reference to an array or a hash
2810 4053         12759 my $n = 0;
2811 4053         7409 my $count;
2812             $count = sub
2813 7763     7763   12053 {my ($ref, $currentDepth) = @_;
2814 7763 100       40105 if (ref($ref) =~ /array/i)
    100          
2815 4424 100       16680 {if ($maxDepth == $currentDepth) {$n += scalar(@$ref)}
  3682         10804  
2816 742         2968 else {$count->($_, ++$currentDepth) for @$ref}
2817             }
2818             elsif (ref($ref) =~ /hash/i)
2819 1113 100       2597 {if ($maxDepth == $currentDepth) {$n += scalar(keys %$ref)}
  371         1484  
2820 742         4452 else {$count->($ref->{$_}, ++$currentDepth) for keys %$ref}
2821             }
2822 2226         5194 else {++$n}
2823 4053         34221 };
2824 4053         12213 $count->($ref, 1);
2825 4053         9799 $n
2826             }
2827              
2828             sub formatHtmlTable($%) # Format an array of arrays of scalars as an html table using the B<%options> described in L.
2829 23     23 1 568 {my ($data, %options) = @_; # Data to be formatted, options
2830 23 50       327 my $rows = $data ? scalar(@$data) : 0; # The number of rows in the report
2831              
2832 23         776 checkKeys(\%options, formatTableCheckKeys); # Check report options
2833              
2834 23 50 33     1070 if (!$options{zero} and $data and ref($data) =~ m(array)i and !@$data) # Return empty string if the table is empty unless the zero option has been supplied
      33        
      33        
2835 0         0 {return q()
2836             }
2837              
2838 23         67 my @html; # Generated html
2839 23         185 my $cl = q(); # Table column names
2840 23         132 my $ct = q(); # Columns description table if present
2841              
2842 23 50       135 if (my $columns = $options{columns}) # Column headers
2843 23 50       279 {ref($columns) and confess <
2844             Expected one line per column wiith the forst weor dbeing teh column name and
2845             the remainder being a comment describing the comment.
2846             END
2847 23         149 my @c = map {[split m(\s+), $_, 2]} split "\n", $columns; # Parse column headers
  46         231  
2848             $cl = join '', q(
), join q(),
2849 23         71 map {my ($c, $d) = @$_; qq($c)} @c; # Column line with tool tips
  46         90  
  46         223  
2850 23         506 $ct = join "\n", q(

), formatTableBasic([@c]), qq(

\n); # Column format
2851             }
2852              
2853 23 50       233 if (my $title = $options{title}) # Title
2854 23         106 {push @html, <
2855            

$title

2856             END
2857             }
2858              
2859             my $hf = sub # Header / Footer
2860 46     46   111 {my ($text) = @_; # Text of header or footer
2861 46         736 my $d = dateTimeStamp;
2862 46   50     489 my $t = ($text//'') =~ s(DDDD) ($d)gr =~ s(NNNN) ($rows)gr; # Edit in NNNN and DDDD fields
2863              
2864 46         289 push @html, <
2865            

$t

2866             END
2867 23         366 };
2868              
2869 23 50       283 if (my $head = $options{head}) # Header
2870 23         91 {&$hf($head);
2871             }
2872              
2873 23         269 push @html, <
2874            

2875             END
2876              
2877 23 50       305 push @html, $cl if $cl; # Column headers
2878              
2879 23 50       194 if ($data) # Table data
2880 23         172 {for my $data(@$data)
2881 47   50     266 {push @html, join '', q(
), join q(), map {$_//q()} @$data;
  94         444  
2882             }
2883             }
2884              
2885 23         46 push @html, <
2886            

2887             END
2888              
2889 23 50       90 push @html, $ct if $ct; # Column descriptions block
2890              
2891 23 50       90 if (my $foot = $options{foot}) # Footer
2892 23         46 {&$hf($foot);
2893             }
2894              
2895 23         67 if (1) # Record options invisibly
2896 23         285 {my $options = dump({%options, rows=>$rows});
2897 23         15330 push @html, qq();
2898             }
2899              
2900 23         116 my $html = join "\n", @html; # Create html
2901 23 100       112 if (my $file = $options{file})
2902 2         47 {my $html = join "\n", @html;
2903 2         155 overWriteFile($file, $html);
2904             }
2905              
2906             $html
2907 23         647 } # formatHtmlTable
2908              
2909             sub formatHtmlTablesIndex($$$;$) # Create an index of html reports.
2910 17     17 1 306 {my ($reports, $title, $url, $columns) = @_; # Reports folder, title of report of reports, $url to get files, number of columns - defaults to 1
2911 17   50     663 $columns //= 1;
2912              
2913             my %reports = sub # {file=>options} for each html report
2914 17     17   714 {my @r = searchDirectoryTreesForMatchingFiles($reports, q(.html)); # Find all html reports
2915 17         306 my %r;
2916 17         221 for my $r(@r) # Each html report
2917 34         833 {my $t = readFile($r);
2918 34 50       799 if ($t =~ m()s) # Extract report meta data
2919 34         5253 {my $d = eval $1;
2920 34 50       493 $@ and confess "Cannot retrieve report metadata:\n$r\n$@\n";
2921 34 50       374 if (my $t = $$d{title})
2922 34         425 {$r{$t} = $d;
2923             }
2924             else
2925 0         0 {cluck "No title in file:\n$r\n";
2926             }
2927             }
2928             }
2929             %r
2930 17         1054 }->();
  17         187  
2931              
2932 17         595 my @toc; my %class; # List of reports
2933 17         425 for my $title(sort keys %reports) # Each report
2934 34         204 {my $options = $reports{$title};
2935 34         153 my $rows = $$options{rows};
2936 34 50       306 next unless my $file = $$options{file};
2937 34         731 my $class = containingFolderName($file); $class{$class}++; # Classification for report
  34         510  
2938 34         170 my $href = qq($url$file);
2939 34         85 my $link = qq($title);
2940             my $tick = sub # Flag items that we would like to be zero
2941 34 50 33 34   493 {return q() unless $file =~ m(/bad/) and $rows;
2942 0         0 q()
2943 34         646 }->();
2944              
2945 34         221 my $c = qq( class="report report_$class"); # Classification
2946              
2947 34         476 push @toc, join '', qq(),
2948             join( qq(), $rows, $tick, $link);
2949             }
2950              
2951 17         306 my $tocs = @toc;
2952             # my @tocs = rectangularArray(int(@toc / $columns), @toc); # Divide into columns
2953 17         578 my @tocs = rectangularArray2($columns, @toc); # Divide into columns
2954 17         221 my $toc = join "\n", map {q(
  34         340  
2955 17         374 my $dt = dateTimeStamp; # Date of run
2956 17 50       255 my $t = $title ? qq(

$title

) : q(); # Title if present
2957              
2958 17         255 my $groups = join ', ', map {qq("$_")} sort keys %class; # Groups
  17         119  
2959 17         51 my $select = join '', map {<
  17         340  
2960             $_
2961             END
2962              
2963 17         255 push my @html, <
2964            
2970              
2971            
2972            
2973             $tocs reports available on $dt
2974             Hide All
2975             $select
2976             Show All
2977            
2978            

2979             $toc
2980            

2981            
3018             END
3019              
3020 17         187 my $html = join "\n", @html; # Create html
3021              
3022 17 50       476 if (my $out = fpe($reports, qw(index_of_reports html)))
3023 17         816 {owf($out, $html);
3024             }
3025              
3026             $html # Return the html so created
3027 17         867 } # formatHtmlTablesIndex
3028              
3029             my @formatHtmlAndTextTablesPids; # Pids used to format tables in parallel
3030              
3031             sub formatHtmlAndTextTablesWaitPids # Wait on all table formatting pids to complete
3032 17     17 1 84686044 {waitpid $_, 0 for @formatHtmlAndTextTablesPids;
3033             }
3034              
3035             sub formatHtmlAndTextTables($$$$$%) # Create text and html versions of a tabular report
3036 40     40 1 879 {my ($reports, $html, $getFile, $filePrefix, $data, %options) = @_; # Folder to contain text reports, folder to contain html reports, L to get files, file prefix to be removed from file entries or array of file prefixes, data, options
3037              
3038 40 50       307 my @prefix = ref($filePrefix) ? @$filePrefix : $filePrefix; # Flatten file prefixes into array
3039 40         206 my $file = $options{file}; # Relative report file
3040 40         122 my $columns = $options{columns}; # Columns must come first for the moment
3041              
3042 40 50       227 if ($reports) # Format table as text
3043 40         30899 {push @formatHtmlAndTextTablesPids, my $pid = fork;
3044 40 100       2025 unless($pid)
3045 2         191 {my $out = setFileExtension fpf($reports, $file), q(txt); # Output file name
3046 2         212 formatTable($data, columns=>$columns, %options, file=>$out);
3047 2         14018 exit;
3048             }
3049             }
3050              
3051 38 50       706 if ($html) # Format table as html
3052 38         24679 {push @formatHtmlAndTextTablesPids, my $pid = fork;
3053 38 100       11701 unless($pid)
3054 2         240 {my $out = setFileExtension fpf($html, $file), q(html); # Output file name
3055 2         72 my $start = time;
3056             my $h = sub # Turn file names into links in a table of scalars
3057 2     2   55 {my @r;
3058 2         58 for my $row(@$data)
3059 5         45 {my @c;
3060 5         63 for my $col(@$row)
3061             {push @c, sub
3062 10         44 {for my $filePrefix(@prefix) # Try each file prefix
3063 10 100 66     276 {if ($col and $col =~ m(\A$filePrefix)s)
3064 5         84 {return qq().
3065             swapFilePrefix($col, $filePrefix).q();
3066             }
3067             }
3068             $col # Use plain file name as no prefix matched
3069 10         156 }->();
  5         59  
3070             }
3071 5         67 push @r, \@c;
3072             }
3073             \@r # Return edited rows as a reference for convenient use with formatTable
3074 2         161 }->();
  2         40  
3075              
3076 2         168 formatHtmlTable($h, %options, file => $out); # Format table as html
3077 2         14506 exit;
3078             }
3079             }
3080             } # formatHtmlAndTextTables
3081              
3082             #D1 Lines # Load data structures from lines.
3083              
3084             sub loadArrayFromLines($) # Load an array from lines of text in a string.
3085 371     371 1 4081 {my ($string) = @_; # The string of lines from which to create an array
3086 371         2597 [grep {!/\A#/} split "\n", $string]
  742         31535  
3087             }
3088              
3089             sub loadHashFromLines($) # Load a hash: first word of each line is the key and the rest is the value.
3090 371     371 1 1855 {my ($string) = @_; # The string of lines from which to create a hash
3091 371         2226 +{map{split /\s+/, $_, 2} split "\n", $string}
  742         19292  
3092             }
3093              
3094             sub loadArrayArrayFromLines($) # Load an array of arrays from lines of text: each line is an array of words.
3095 371     371 1 1484 {my ($string) = @_; # The string of lines from which to create an array of arrays
3096 371         1855 [map{[split /\s+/]} split "\n", $string]
  742         18921  
3097             }
3098              
3099             sub loadHashArrayFromLines($) # Load a hash of arrays from lines of text: the first word of each line is the key, the remaining words are the array contents.
3100 371     371 1 1484 {my ($string) = @_; # The string of lines from which to create a hash of arrays
3101 371         1855 +{map{my @a = split /\s+/; (shift @a, [@a])} split "\n", $string}
  742         7049  
  742         12614  
3102             }
3103              
3104             sub loadArrayHashFromLines($) # Load an array of hashes from lines of text: each line is a hash of words.
3105 371     371 1 1113 {my ($string) = @_; # The string of lines from which to create an array of arrays
3106 371         1855 [map {+{split /\s+/}} split /\n/, $string]
  742         17066  
3107             }
3108              
3109             sub loadHashHashFromLines($) # Load a hash of hashes from lines of text: the first word of each line is the key, the remaining words are the sub hash contents.
3110 371     371 1 1113 {my ($string) = @_; # The string of lines from which to create a hash of arrays
3111 371         2226 +{map{my ($a, @a) = split /\s+/; ($a=>{@a})} split "\n", $string}
  742         4081  
  742         11501  
3112             }
3113              
3114             sub checkKeys($$) # Check the keys in a B confirm to those B<$permitted>.
3115 14086     14086 1 32602 {my ($hash, $permitted) = @_; # The hash to test, a hash of the permitted keys and their meanings
3116              
3117 14086 50       124136 ref($hash) =~ /hash/igs or # Check parameters
3118             confess "Hash reference required for first parameter\n";
3119 14086 50       77177 ref($permitted) =~ /hash/igs or
3120             confess "Hash reference required for second parameter\n";
3121              
3122 14086         45100 my %parms = %$hash; # Copy keys supplied
3123 14086         123271 delete $parms{$_} for keys %$permitted; # Remove permitted keys
3124 14086 50       57609 return '' unless keys %parms; # Success - all the keys in the test hash are permitted
3125              
3126 0         0 confess join "\n", # Failure - explain what went wrong
3127             "Invalid options chosen:",
3128             indentString(formatTable([sort keys %parms]), ' '),
3129             "",
3130             "Permitted options are:",
3131             indentString(formatTable($permitted), ' '),
3132             "";
3133             }
3134              
3135             #D1 LVALUE methods # Replace $a->{B} = $b with $a->B = $b which reduces the amount of typing required, is easier to read and provides a hard check that {B} is spelled correctly.
3136              
3137             sub genLValueScalarMethods(@) # Generate L scalar methods in the current package, A method whose value has not yet been set will return a new scalar with value B. Suffixing B to the scalar name will confess if a value has not been set.
3138 1484     1484 1 5194 {my (@names) = @_; # List of method names
3139 1484         5194 my ($package) = caller; # Package
3140 1484         4452 for my $m(@_) # Name each method
3141 2226         3339 {my $s;
3142 2226 100       11130 if ($m =~ m(::)s) # Package name supplied in name
3143 1113         5194 {my $M = $m =~ s(\A.*:) ()r; # Remove package
3144 1113         4823 $s =
3145             'sub '.$m. ':lvalue {$_[0]{"'.$M.'"}}'. # LValue version for get and set
3146             'sub '.$m.'X {$_[0]{"'.$M.'"} // q()}'; # Non lvalue version for get only returning q() instead of B
3147             }
3148             else # Use package of caller
3149 1113         5565 {$s =
3150             'sub '.$package.'::'.$m. ':lvalue {$_[0]{"'.$m.'"}}'. # LValue version for get and set
3151             'sub '.$package.'::'.$m.'X {$_[0]{"'.$m.'"} // q()}'; # Non lvalue version for get only returning q() instead of undef
3152             }
3153             # 'sub '.$package.'::'.$_. ':lvalue {my $v; $_[0]{"'.$_.'"} //= $v}'.
3154             # 'sub '.$package.'::'.$_.'X:lvalue {my $v = q(); $_[0]{"'.$_.'"} //= $v}';
3155             # 'sub '.$package.'::'.$_.'X:lvalue {my $v = $_[0]{"'.$_.'"}; confess q(No value supplied for "'.$_.'") unless defined($v); $v}';
3156 2226   0 2220   268975 eval $s;
  2220   0 2232   31439  
  2232   50 0   31631  
  0   50 0   0  
  0   0 553   0  
  553   0 560   3318  
  560     552   3360  
  552     561   5728  
  561     0   5773  
  0     0   0  
  0     0   0  
  0     0   0  
  0         0  
3157 2226 50       56021 confess "Unable to create LValue scalar method for: '$m' because\n$@\n" if $@;
3158             }
3159             }
3160              
3161             sub addLValueScalarMethods(@) # Generate L scalar methods in the current package if they do not already exist. A method whose value has not yet been set will return a new scalar with value B. Suffixing B to the scalar name will confess if a value has not been set.
3162 1484     1484 1 4081 {my (@names) = @_; # List of method names
3163 1484         4452 my ($package) = caller; # Package
3164 1484         5565 for my $m(@_) # Name each method
3165 1484 50       5565 {my $M = $m =~ m(::)s ? $m : $package.'::'.$m;
3166 1484 50       35616 next if defined &$M;
3167 0         0 genLValueScalarMethods($M);
3168             }
3169             }
3170              
3171             sub genLValueScalarMethodsWithDefaultValues(@) # Generate L scalar methods with default values in the current package. A reference to a method whose value has not yet been set will return a scalar whose value is the name of the method.
3172 371     371 1 2597 {my (@names) = @_; # List of method names
3173 371         1855 my ($package) = caller; # Package
3174 371         2226 for(@_) # Name each method
3175 1113         5194 {my $s = 'sub '.$package.'::'.$_.':lvalue {my $v = "'.$_.'"; $_[0]{"'.$_.'"} //= $v}';
3176 1113   33 371   86814 eval $s;
  371   0 0   1484  
  371   0 0   6307  
  0         0  
  0         0  
  0         0  
  0         0  
3177 1113 50       15582 confess "Unable to create LValue scalar method for: '$_' because\n$@\n" if $@;
3178             }
3179             }
3180              
3181             sub genLValueArrayMethods(@) # Generate L array methods in the current package. A reference to a method that has no yet been set will return a reference to an empty array.
3182 371     371 1 2226 {my (@names) = @_; # List of method names
3183 371         1855 my ($package) = caller; # Package
3184 371         1855 for(@_) # Name each method
3185 1113         4452 {my $s = 'sub '.$package.'::'.$_.':lvalue {$_[0]{"'.$_.'"} //= []}';
3186 1113   100 742   96831 eval $s;
  742   0 0   13356  
  0   0 0   0  
  0         0  
3187 1113 50       16324 confess "Unable to create LValue array method for: '$_' because\n$@\n" if $@;
3188             }
3189             }
3190              
3191             sub genLValueHashMethods(@) # Generate L hash methods in the current package. A reference to a method that has no yet been set will return a reference to an empty hash.
3192 371     371 1 1484 {my (@names) = @_; # Method names
3193 371         1484 my ($package) = caller; # Package
3194 371         1855 for(@_) # Name each method
3195 1113         4081 {my $s = 'sub '.$package.'::'.$_.':lvalue {$_[0]{"'.$_.'"} //= {}}';
3196 1113   100 742   68635 eval $s;
  742   0 0   20776  
  0   0 0   0  
  0         0  
3197 1113 50       15211 confess "Unable to create LValue hash method for: '$_' because\n$@\n" if $@;
3198             }
3199             }
3200              
3201             my %genHash; # Hash of methods created by genHash - these methods can be reused - others not so created cannot.
3202              
3203             sub genHash($%) #I Return a B<$bless>ed hash with the specified B<$attributes> accessible via L method calls. L will generate documentation at L for the hash defined by the call to L if the call is laid out as in the example below.
3204 8150     8150 1 441829 {my ($bless, %attributes) = @_; # Package name, hash of attribute names and values
3205 8150         33762 my $h = \%attributes;
3206 8150         73083 bless $h, $bless;
3207 8150         318599 for my $m(sort keys %attributes) # Add any attributes not already present
3208 566258 50       1948086 {unless ($m =~ m(\A[a-z_](\w|:)*\Z)is) # Silently skip anything we could not reasonably use as an attribute name
3209 0         0 {confess qq(Implausibly named attribute: "$m"\n);
3210             }
3211              
3212 566258         1115469 my $M = $bless.q(::).$m; # The full name of the attribute
3213              
3214 566258 100       2101977 if ($h->can($m)) # Skip any methods that are already defined
3215 432968 50       1015559 {say STDERR dump(\%genHash, $m, $M) unless $genHash{$M};
3216              
3217             confess "Cannot define attribute because there is already ".
3218 432968 50       756433 "a method with the same name: $m\n" unless $genHash{$M};
3219              
3220 432968         671244 next;
3221             }
3222              
3223 133290 50       726587 if ($h->can($m.q(X))) # Confess to any methods that collide with X names
3224 0         0 {confess "Cannot define attribute because there is already ".
3225             "an X method with the same name: $m\n";
3226             }
3227              
3228 133290         504001 my $R = reftype($attributes{$m}); # Type of thing referred to
3229 133290 100       513680 my $r = !defined($R) ? q() : $R =~ m(array)i ? q( //= []) : q( //= {}); # Empty return type
    100          
3230 133290         210548 my $s = '';
3231 133290         353229 $s .= 'sub '.$bless.'::'.$m. ':lvalue {$_[0]{"'.$m.qq("}$r})."\n"; # LValue version for get and set
3232 133290         263579 $s .= 'sub '.$bless.'::'.$m. 'X {$_[0]{"'.$m.'"}//q()}'."\n"; # Default to blank for get
3233 133290 50       258839 if ($s) # Add any new methods needed
3234 133290   0 268   10629786 {eval $s;
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 182536      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 447444      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 3177723      
      0 0      
      0 3126281      
      0 0      
      0 0      
      0 132525      
      0 64745      
      0 0      
      0 0      
      0 63842      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 63513      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 1472      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 2944      
      0 268      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      50 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      50 0      
      0 0      
      50 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 63842      
      0 0      
      0 0      
      0 0      
      0 64142      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      50 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
3235 133290 50       536654 confess "$@\n$s\n$@" if $@;
3236             }
3237 133290         553140 $genHash{$M}++; # Record attribute as being created by genHash
3238             }
3239              
3240             $h
3241 8150         117193 }
3242              
3243             sub loadHash($%) # Load the specified blessed B<$hash> generated with L with B<%attributes>. Confess to any unknown attribute names.
3244 2418     2418 1 15359 {my ($hash, %attributes) = @_; # Hash, hash of attribute names and values to be loaded
3245 2229         21050 for my $m(sort keys %attributes) # Add any attributes not already present
3246 1419 100       188464 {$hash->can($m) or confess "Cannot load attribute: $m\n"; # Unknown attribute
3247 736         2944 $hash->{$m} = $attributes{$m}; # Load known attribute
3248             }
3249             $hash # Return loaded hash
3250 1644         32582 }
3251              
3252             my $reloadHashesCount = 0; # Generate names for reloaded hashes that are not already blessed
3253              
3254             sub reloadHashes2($$) #P Ensures that all the hashes within a tower of data structures have LValue methods to get and set their current keys.
3255 18615     18808 1 64716 {my ($d, $progress) = @_; # Data structure, progress
3256 18586 100       86636 return unless my $r = reftype($d);
3257 6460 50       36284 return if $$progress{$d};
3258 6592 100       80080 if ($d =~ m(array)is) # Array
    50          
3259 2780         13632 {$$progress{$d}++;
3260 2644         24696 &reloadHashes2($_, $progress) for @$d;
3261             }
3262             elsif ($d =~ m(hash)is) # Hash
3263 3816         18444 {$$progress{$d}++;
3264 3816         31360 &reloadHashes2($_, $progress) for values %$d;
3265 3816 100       21720 if (my $b = blessed($d)) # Already blessed
3266 2712         14832 {genHash($b, %$d);
3267             }
3268             else # Create a name
3269 1104         5152 {my $b = q(reloadHash_).++$reloadHashesCount;
3270 1104         14352 bless($d, $b); # Bless hash
3271 1104         10672 genHash($b, %$d);
3272             }
3273             }
3274             }
3275              
3276             sub reloadHashes($) # Ensures that all the hashes within a tower of data structures have LValue methods to get and set their current keys.
3277 3012     3327 1 13924 {my ($d) = @_; # Data structure
3278 3012         21956 reloadHashes2($d, {});
3279 3012         45264 $d
3280             }
3281              
3282             sub showHashes2($$$) #P Create a map of all the keys within all the hashes within a tower of data structures.
3283 4020     4020 1 6164 {my ($d, $keys, $progress) = @_; # Data structure, keys found, progress
3284 4020 100       15008 return unless my $r = reftype($d);
3285 1340 50       6700 return if $$progress{$d};
3286 1340 100       15544 if ($d =~ m(array)is)
    50          
3287 268         804 {$$progress{$d}++;
3288 1100         55528 &showHashes2($_, $keys, $progress) for @$d;
3289             }
3290             elsif ($d =~ m(hash)is)
3291 1072         3216 {$$progress{$d}++;
3292 1072         9380 &showHashes2($_, $keys, $progress) for values %$d;
3293 1072 50       5628 if (my $b = blessed($d))
3294 1072         3752 {for my $k(keys %$d)
3295 3216         8844 {$keys->{$b}{$k}++
3296             }
3297             }
3298             }
3299             }
3300              
3301             sub showHashes($) #P Create a map of all the keys within all the hashes within a tower of data structures.
3302 268     351 1 1072 {my ($d) = @_; # Data structure
3303 268         3484 showHashes2($d, my $keys = {}, {});
3304 268         8040 $keys
3305             }
3306              
3307             my %packageSearchOrder; # Method to package map
3308              
3309             sub setPackageSearchOrder($@) # Set a package search order for methods requested in the current package via AUTOLOAD.
3310 536     643 1 4020 {my ($set, @search) = @_; # Package to set, package names in search order.
3311 536         2680 %packageSearchOrder = (); # Reset method to package map
3312              
3313 536         3216 my $c = <<'END';
3314             if (1)
3315             {package $set;
3316             our $AUTOLOAD; # Method requested
3317             BEGIN{undef &AUTOLOAD}; # Replace autoload
3318             sub AUTOLOAD
3319             {my $s = $AUTOLOAD;
3320             return if $s =~ m(Destroy)is;
3321             if (my $t = $packageSearchOrder{$s}) # Reuse a cached method if possible
3322             {goto &$t;
3323             }
3324             else # Search for the first package that can provide the requested method
3325             {for my $package(@search)
3326             {my $t = $s =~ s(\A.+::) (${package}::)grs;
3327             if (defined &$t)
3328             {$packageSearchOrder{$s} = $t;
3329             goto &$t;
3330             }
3331             }
3332             confess "Cannot find a method implementing $s"; # No package supports the requested method
3333             }
3334             }
3335             }
3336             END
3337 536         2412 my $search = q/qw(/.join(' ', @search).q/)/; # Set search order
3338 536         8576 $c =~ s(\$set) ($set)gs;
3339 536         6432 $c =~ s(\@search) ($search)gs;
3340 536         87100 eval $c;
3341 536 50       20368 confess "$c\n$@\n" if $@;
3342             }
3343              
3344             sub isSubInPackage($$) # Test whether the specified B<$package> contains the subroutine <$sub>.
3345 5192     5270 1 22204 {my ($package, $sub) = @_; # Package name, subroutine name
3346 5192         181820 my $r = eval qq(defined(&${package}::${sub})); # hasSub containsSub
3347 5192 50       20432 $@ and confess $@;
3348 3852         45356 $r
3349             }
3350              
3351             sub overrideMethods($$@) #S For each method, if it exists in package B<$from> then export it to package B<$to> replacing any existing method in B<$to>, otherwise export the method from package B<$to> to package B<$from> in order to merge the behavior of the B<$from> and B<$to> packages with respect to the named methods with duplicates resolved if favour of package B<$from>.
3352 1608     268 1 5360 {my ($from, $to, @methods) = @_; # Name of package from which to import methods, package into which to import the methods, list of methods to try importing.
3353 2412         11524 my @s;
3354 2412         14740 for my $method(setUnion @methods) # Replaceable methods
3355 1876         8576 {push @s, <<"END";
3356             if (isSubInPackage(q($from), q($method)))
3357             {undef &${to}::$method;
3358             *${to}::$method = *${from}::$method;
3359             }
3360             else
3361             {undef &${from}::$method;
3362             *${from}::$method = *${to}::$method;
3363             }
3364             END
3365             }
3366 1608         30552 my $s = join "\n", @s; # Replace methods
3367 268         51992 eval $s;
3368 268 100       7772 confess $@ if $@;
3369             }
3370              
3371             sub overrideAndReabsorbMethods(@) #S Override methods down the list of B<@packages> then reabsorb any unused methods back up the list of packages so that all the packages have the same methods as the last package with methods from packages mentioned earlier overriding methods from packages mentioned later. The methods to override and reabsorb are listed by the sub B in the last package in the packages list. Confess to any errors.
3372 0     132 1 0 {my (@packages) = @_; # List of packages
3373 0 100       0 @packages or confess "No packages supplied"; # Check we have some packages
3374 0         0 my $base = $packages[-1]; # The last package
3375 0         0 my $om = qq(&${base}::overrideableMethods); # Sub to supply replaceable methods
3376 0         0 my @methods = eval $om; # Retrieve replaceable methods
3377 0 0       0 $@ and confess "Cannot retrieve replaceable methods via sub $om\n$@\n";
3378              
3379 0         0 my @s; # Replacement code
3380              
3381 0         0 for my $i(keys @packages) # Push methods down through the packages
3382 0 0       0 {last if $i == $#packages;
3383 0         0 my $from = $packages[$i];
3384 0         0 my $to = $packages[$i+1];
3385 0         0 for my $method(@methods) # Push each method down one level if possible
3386 0         0 {push @s, <<"END";
3387             if (isSubInPackage(q($from), q($method)))
3388             {undef &${to}::$method;
3389             *${to}::$method = *${from}::$method;
3390             }
3391             END
3392             }
3393             }
3394              
3395 0         0 for my $i(reverse keys @packages) # Pull methods up through the packages
3396 0 0       0 {next unless $i;
3397 0         0 my $from = $packages[$i];
3398 0         0 my $to = $packages[$i-1];
3399 0         0 for my $method(@methods) # Pull each method up one level if possible
3400 237         5834 {push @s, <<"END";
3401             if (isSubInPackage(q($from), q($method)) && !isSubInPackage(q($to), q($method)))
3402             {undef &${to}::$method;
3403             *${to}::$method = *${from}::$method;
3404             }
3405             END
3406             }
3407             }
3408              
3409 212         4888 my $s = join "\n", @s; # Replace methods
3410 187         5014 eval $s;
3411 0 0       0 confess "$@\n$s\n" if $@;
3412             }
3413              
3414             sub assertPackageRefs($@) # Confirm that the specified references are to the specified package
3415 371     507 1 2597 {my ($package, @refs) = @_; # Package, references
3416 371         1113 for(@refs) # Check each reference
3417 639         5937 {my $r = ref($_);
3418 371 50 33     104622 $r && $r eq $package or confess "Wanted reference to $package, but got $r\n";
3419             }
3420             1
3421 0         0 }
3422              
3423             sub assertRef(@) # Confirm that the specified references are to the package into which this routine has been exported.
3424 371     371 1 1484 {my (@refs) = @_; # References
3425 371         1484 my ($package) = caller; # Package
3426 371         1855 for(@_) # Check each reference
3427 371         742 {my $r = ref($_);
3428 371 50 33     111671 $r && $r eq $package or confess "Wanted reference to $package, but got $r\n";
3429             }
3430             1
3431 0         0 }
3432              
3433             sub arrayToHash(@) # Create a hash reference from an array
3434 272     272 1 5105 {my (@array) = @_; # Array
3435 272         2961 +{map{$_=>1} @array}
  811         21478  
3436             }
3437              
3438             sub flattenArrayAndHashValues(@) # Flatten an array of scalars, array and hash references to make an array of scalars by flattening the array references and hash values.
3439 147     147 1 399 {my (@array) = @_; # Array to flatten
3440 147         357 my @a;
3441 147         588 for my $a(@array)
3442 231 100       1344 {if (ref($a) =~ m(\Aarray\Z)i)
    100          
3443 105         714 {push @a, &flattenArrayAndHashValues(@$a);
3444             }
3445             elsif (ref($a) =~ m(\Ahash\Z)i)
3446 21         105 {push @a, &flattenArrayAndHashValues(map {$$a{$_}} sort keys %$a);
  42         294  
3447             }
3448             else
3449 105         483 {push @a, $a;
3450             }
3451             }
3452             @a # Flattened array
3453 147         1701 }
3454              
3455             sub getSubName($) # Returns the (package, name, file, line) of a perl B<$sub> reference.
3456 3     3 1 15 {my ($sub) = @_; # Reference to a sub with a name.
3457 3 50       219 if (my $b = B::svref_2object($sub))
3458 3         12 {my $r = ref($b);
3459 3 50       72 if ($r =~ m(B::CV)i)
3460 3 50       123 {if (my $g = $b->GV)
3461 3         186 {return ($g->STASH->NAME, $g->NAME, $g->FILE, $g->LINE); # Package, name, file, line in file
3462             }
3463             }
3464             }
3465 0         0 confess "Unable to get name of sub referenced by $sub";
3466             }
3467              
3468             #D1 Strings # Actions on strings.
3469              
3470             sub stringMd5Sum($) # Get the Md5 sum of a B<$string> that might contain L code points.
3471 1072     1072 1 3216 {my ($string) = @_; # String
3472 1072         2948 my $f = writeFile(undef, $string); # Write into a file
3473 1072         5092 my $s = readBinaryFile($f); # Read as binary
3474 1072         6700 my $m = md5_hex($s); # Md5sum of bytes
3475 1072         485348 unlink $f;
3476 1072         12864 $m;
3477             }
3478              
3479             sub indentString($$) # Indent lines contained in a string or formatted table by the specified string.
3480 1107     1107 1 7378 {my ($string, $indent) = @_; # The string of lines to indent, the indenting string
3481 1107 50       18469 join "\n", map {$indent.$_} split m(\n+), (ref($string) ? $$string : $string)
  3692         25515  
3482             }
3483              
3484             sub replaceStringWithString($$$) # Replace all instances in B<$string> of B<$source> with B<$target>
3485 42     42 1 630 {my ($string, $source, $target) = @_; # String in which to replace substrings, the string to be replaced, the replacement string
3486 42         462 for(1..(1+length($string) / (length($source)+1))) # Avoid too much recursive expansion
3487 105         315 {my $i = index($string, $source);
3488 105 100       777 if ($i >= 0)
3489 84         483 {substr($string, $i, length($source)) = $target;
3490 84         252 next;
3491             }
3492 21         42 last;
3493             }
3494             $string
3495 42         588 }
3496              
3497             sub formatString($$) # Format the specified B<$string> so it can be displayed in B<$width> columns.
3498 21     21 1 399 {my ($string, $width) = @_; # The string of text to format, the formatted width.
3499              
3500 21         546 $string =~ s(\\m) (\n\n)gs; # Expand \m introduced by update documentation
3501              
3502 21         210 for(1..9)
3503 189 100       1134 {if ($string =~ m((B<([^>]*)>))s)
3504 21         1113 {$string = replaceStringWithString(my $s = $string, $1, boldString($2));
3505 21 50       378 last if $s eq $string;
3506             }
3507             }
3508              
3509 21         315 my @f;
3510 21         609 my @w = split m/\s+/, $string; # Parse string into words
3511 21         189 for my $w(@w) # Bold B
3512 357 100       798 {if (!$f[-1]) {push @f, $w}
  21         357  
3513             else
3514 336         903 {my $l = $f[-1].qq( $w);
3515 336 100       945 if (length($l) > $width)
3516 84         378 {push @f, $w;
3517             }
3518             else
3519 252         546 {$f[-1] = $l;
3520             }
3521             }
3522             }
3523              
3524 21         273 my $t = join "\n", @f; # Format punctuation
3525 21         504 $t =~ s(\s*([,;.!?])) ($1)gs;
3526 21         399 $t =~ s(\s*\Z) ()s;
3527              
3528 21         630 "$t\n"
3529             }
3530              
3531             sub isBlank($) # Test whether a string is blank.
3532 742     742 1 3339 {my ($string) = @_; # String
3533 742         7049 $string =~ m/\A\s*\Z/
3534             }
3535              
3536             sub trim($) # Remove any white space from the front and end of a string.
3537 371     371 1 1855 {my ($string) = @_; # String
3538 371         5194 $string =~ s/\A\s+//r =~ s/\s+\Z//r
3539             }
3540              
3541             sub pad($$;$) # Pad the specified B<$string> to a multiple of the specified B<$length> with blanks or the specified padding character to a multiple of a specified length.
3542 1855     1855 1 5936 {my ($string, $length, $padding) = @_; # String, tab width, padding string
3543 1855 50       5565 defined($string) or confess "String required\n";
3544 1855         13727 $string =~ s/\s+\Z//;
3545 1855   100     14840 $padding //= q( );
3546 1855         4081 my $l = length($string);
3547 1855 100       8162 return $string if $l % $length == 0;
3548 1484         3339 my $p = $length - $l % $length;
3549 1484         10759 $string .= $padding x $p;
3550             }
3551              
3552             sub lpad($$;$) # Left Pad the specified B<$string> to a multiple of the specified B<$length> with blanks or the specified padding character to a multiple of a specified length.
3553 1855     1855 1 6307 {my ($string, $length, $padding) = @_; # String, tab width, padding string
3554 1855 50       5565 defined($string) or confess "String required\n";
3555 1855         34503 $string =~ s/\s+\Z//;
3556 1855   100     15582 $padding //= q( );
3557 1855         4081 my $l = length($string);
3558 1855 100       6678 return $string if $l % $length == 0;
3559 1484         3339 my $p = $length - $l % $length;
3560 1484         10388 ($padding x $p).$string;
3561             }
3562              
3563             sub ppp($$;$) # Pad the specified B<$string> to a multiple of the specified B<$length> with blanks or the specified padding character to a multiple of a specified length.
3564 1855     1855 1 8162 {my ($length, $string, $padding) = @_; # Tab width, string, padding string
3565 1855 50       5936 defined($string) or confess "String required\n";
3566 1855         10388 $string =~ s/\s+\Z//;
3567 1855   100     9646 $padding //= q( );
3568 1855         3710 my $l = length($string);
3569 1855 100       9275 return $string if $l % $length == 0;
3570 1484         3339 my $p = $length - $l % $length;
3571 1484         10759 $string .= $padding x $p;
3572             }
3573              
3574             sub firstNChars($$) # First N characters of a string.
3575 15324     15324 1 56625 {my ($string, $length) = @_; # String, length
3576 15324 100 100     163738 return $string if !$length or length($string) < $length;
3577 368         3312 substr($string, 0, $length);
3578             }
3579              
3580             sub nws($;$) # Normalize white space in a string to make comparisons easier. Leading and trailing white space is removed; blocks of white space in the interior are reduced to a single space. In effect: this puts everything on one long line with never more than one space at a time. Optionally a maximum length is applied to the normalized string.
3581 12512     12512 1 50681 {my ($string, $length) = @_; # String to normalize, maximum length of result
3582 12512         12852753 my $s = $string =~ s((\x{200b}|\A\s+|\s+\Z)) ()gr =~ s/\s+/ /gr;
3583 12512         54420 firstNChars($s, $length) # Apply maximum length if requested
3584             }
3585              
3586             sub deduplicateSequentialWordsInString($) # Remove sequentially duplicate words in a string
3587 17     17 1 136 {my ($s) = @_; # String to deduplicate
3588 17         289 my %a = map {$_=>1} grep {$_} split /\W+/, $s; # Split into words
  221         663  
  238         510  
3589 17         289 for my $w(sort keys %a)
3590 85         1632 {1 while $s =~ s($w\s+$w) ($w)gs;
3591             }
3592             $s
3593 17         272 }
3594              
3595             sub detagString($) # Remove L or L tags from a string
3596 21     853 1 189 {my ($string) = @_; # String to detag
3597 21         714 $string =~ s(<[^>]*>) ()gsr # Remove xml/html tags
3598             }
3599              
3600             sub parseIntoWordsAndStrings($) # Parse a B<$string> into words and quoted strings. A quote following a space introduces a string, else a quote is just part of the containing word.
3601 17     17 1 306 {my ($string) = @_; # String to parse
3602 17 50       476 return () unless $string;
3603              
3604 17         170 my $s = 0; # 0 - look for word or quote, 1 in word, 2 in ' string, 3 - in " string
3605 17         221 my @r;
3606             my $r;
3607              
3608             my $accept = sub # Accept a word or string
3609 170     170   459 {push @r, $r; $s = 0;
  170         391  
3610 17         510 };
3611              
3612 17         1853 for my $c(split m//, $string) # Each character in the string
3613 1020 100 100     2737 {next if $s == 0 and $c =~ m(\s); # Skip spaces while looking for a word or string
3614              
3615 680 100       1853 if ($s == 0) # String
    100          
    100          
    50          
3616 170 100       867 {if ($c =~ m(')) # ' string
    100          
3617 51         85 {$r = ''; $s = 2;
  51         68  
3618             }
3619             elsif ($c =~ m(")) # " string
3620 51         170 {$r = ''; $s = 3;
  51         136  
3621             }
3622             else # Word
3623 68         170 {$r = $c; $s = 1;
  68         187  
3624             }
3625             }
3626             elsif ($s == 1) # In word
3627 153 100       697 {if ($c =~ m(\s))
3628 51         272 {&$accept;
3629             }
3630             else
3631 1411         11747 {$r .= $c;
3632             }
3633             }
3634             elsif ($s == 2) # In ' string
3635 1369 100       11602 {if ($c =~ m('))
3636 51         340 {&$accept;
3637             }
3638             else
3639 51         238 {$r .= $c;
3640             }
3641             }
3642             elsif ($s == 3) # In " string
3643 255 100       527 {if ($c =~ m("))
3644 51         119 {&$accept;
3645             }
3646             else
3647 204         340 {$r .= $c;
3648             }
3649             }
3650             }
3651 17         255 &$accept;
3652             @r
3653 17         527 } # parseIntoWordsAndStrings
3654              
3655             sub stringsAreNotEqual($$) # Return the common start followed by the two non equal tails of two non equal strings or an empty list if the strings are equal.
3656 1840     1840 1 8832 {my ($a, $b) = @_; # First string, second string
3657 1840         15824 my @a = split //, $a;
3658 1840         9568 my @b = split //, $b;
3659 1840         6624 my @c;
3660 1840   66     23184 while(@a and @b and $a[0] eq $b[0])
      100        
3661 7728         11776 {shift @a; push @c, shift @b;
  7728         38640  
3662             }
3663 1840         27968 (join(q(), @c), join(q(), @a), join(q(), @b))
3664             }
3665              
3666             sub showGotVersusWanted($$) # Show the difference between the wanted string and the wanted string
3667 368     368 1 1472 {my ($g, $e) = @_; # First string, second string
3668 368         1104 my @s;
3669 368 50       2208 if ($g ne $e)
3670 368         6256 {my ($s, $G, $E) = stringsAreNotEqual($g, $e);
3671 368 50       2208 if (length($s))
3672 368         8832 {my $line = 1 + length($s =~ s([^\n]) ()gsr);
3673 368         4784 my $char = 1 + length($s =~ s(\A.*\n) ()sr);
3674 368         6256 push @s, "Comparing wanted with got failed at line: $line, character: $char";
3675 368         1840 push @s, "Start:\n$s";
3676             }
3677 368         2576 my $b1 = '+' x 80;
3678 368         1472 my $b2 = '_' x 80;
3679 368         2576 push @s, "Want $b1\n", firstNChars($E, 80);
3680 368         2208 push @s, "Got $b2\n", firstNChars($G, 80);
3681 368         5520 return join "\n", @s;
3682             }
3683             undef
3684 0         0 }
3685              
3686             sub printQw(@) # Print an array of words in qw() format.
3687 739     739 1 5918 {my (@words) = @_; # Array of words
3688 739         13284 'qw('.join(' ', @words).')'
3689             }
3690              
3691             sub numberOfLinesInString($) # The number of lines in a string.
3692 368     368 1 7360 {my ($string) = @_; # String
3693 368         18032 scalar split /\n/, $string;
3694             }
3695              
3696             sub javaPackage($) # Extract the package name from a java string or file.
3697 1481     1481 1 5550 {my ($java) = @_; # Java file if it exists else the string of java
3698              
3699             my $s = sub
3700 1481 100 66 1481   38144 {return readFile($java) if $java !~ m/\n/s and -e $java; # Read file of java
3701 368         1840 $java # Java string
3702 1481         20327 }->();
3703              
3704 1481         25153 my ($package) = $s =~ m(package\s+(\S+)\s*;);
3705 1481         9631 $package
3706             }
3707              
3708             sub javaPackageAsFileName($) # Extract the package name from a java string or file and convert it to a file name.
3709 371     371 1 1484 {my ($java) = @_; # Java file if it exists else the string of java
3710              
3711 371 50       1484 if (my $package = javaPackage($java))
3712 371         8904 {return $package =~ s/\./\//gr;
3713             }
3714             undef
3715 0         0 }
3716              
3717             sub perlPackage($) # Extract the package name from a perl string or file.
3718 739     739 1 3324 {my ($perl) = @_; # Perl file if it exists else the string of perl
3719 739         5170 my $p = javaPackage($perl); # Use same technique as Java
3720 739 50       5538 defined($p) or confess "There is no Perl module in file: $perl";
3721 739         4069 $p
3722             }
3723              
3724             sub javaScriptExports($) # Extract the Javascript functions marked for export in a file or string. Functions are marked for export by placing function in column 1 followed by //E on the same line. The end of the exported function is located by \n }
3725 268     268 1 1072 {my ($fileOrString) = @_; # File or string
3726 268 50       4824 my $s = $fileOrString =~ m(\n) ? $fileOrString : readFile($fileOrString);
3727 268         536 my @s;
3728 268         536 my $state = 0;
3729 268         1608 for my $line(split /\n/, $s)
3730 2412 100       4556 {if ($state == 0)
    50          
3731 1340 100       4556 {if ($line =~ m(\Afunction.*\/\/E))
3732 536         1072 {$state = 1;
3733 536         1608 push @s, q(), $line;
3734             }
3735             }
3736             elsif ($state == 1)
3737 1072 100       2144 {$state = 0 if $line =~ m(\A \});
3738 1072         2144 push @s, $line;
3739             }
3740             }
3741 268         3484 join "\n", @s, '';
3742             }
3743              
3744             sub chooseStringAtRandom(@) # Choose a string at random from the list of B<@strings> supplied.
3745 17     17 1 221 {my (@strings) = @_; # Strings to chose from
3746 17         187 my $r = int((rand() * @strings)) % @strings;
3747 17         306 $strings[$r]
3748             }
3749              
3750             sub randomizeArray(@) # Randomize an array
3751 17     17 1 85 {my (@a) = @_; # Array to randomize
3752 17         153 for my $i(keys @a)
3753 68         255 {my $r = int(rand() * ($i+1)); # Uniform randomization
3754 68         119 my $s = $a[$i];
3755 68         102 my $t = $a[$r];
3756 68         221 $a[$i] = $t;
3757 68         204 $a[$r] = $s;
3758             }
3759             @a
3760 17         306 }
3761              
3762             #D1 Arrays and Hashes # Operations on arrays and hashes and array of of hashesh and ghashes of arrays and so on a infinitum.
3763              
3764             sub lengthOfLongestSubArray($) # Given an array of arrays find the length of the longest sub array.
3765 1     1 1 7 {my ($a) = @_; # Array reference
3766 1         5 max map{scalar @$_} @$a
  4         31  
3767             }
3768              
3769             sub cmpArrays($$) # Compare two arrays of strings
3770 5     5 1 22 {my ($a, $b) = @_; # Array A, array B
3771 5         15 my @a = @$a;
3772 5         12 my @b = @$b;
3773 5   100     57 while(@a and @b and !($a[0] cmp $b[0]))
      100        
3774 8         10 {shift @a; shift @b;
  8         26  
3775             }
3776 5 100 100     38 return $a[0] cmp $b[0] if @a and @b;
3777 3 100       21 return -1 if @b;
3778 2 100       14 return +1 if @a;
3779 1         10 0
3780             }
3781              
3782             sub forEachKeyAndValue(&%) # Iterate over a hash for each key and value
3783 1     1 1 5 {my ($body, %hash) = @_; # Body to be executed, hash to be iterated
3784 1         29 &$body($_, $hash{$_}) for sort keys %hash;
3785             }
3786              
3787             #D1 Unicode # Translate L alphanumerics in strings to various L blocks.
3788              
3789             my $normalString = join '', 'A'..'Z', 'a'..'z', '0'..'9';
3790             my $normalAlphaString = join '', 'A'..'Z', 'a'..'z';
3791             my $boldString = q(𝗔𝗕𝗖𝗗𝗘𝗙𝗚𝗛𝗜𝗝𝗞𝗟𝗠𝗡𝗢𝗣𝗤𝗥𝗦𝗧𝗨𝗩𝗪𝗫𝗬𝗭𝗮𝗯𝗰𝗱𝗲𝗳𝗴𝗵𝗶𝗷𝗸𝗹𝗺𝗻𝗼𝗽𝗾𝗿𝘀𝘁𝘂𝘃𝘄𝘅𝘆𝘇𝟬𝟭𝟮𝟯𝟰𝟱𝟲𝟳𝟴𝟵);
3792             my $squareString = q(🄰🄱🄲🄳🄴🄵🄶🄷🄸🄹🄺🄻🄼🄽🄾🄿🅀🅁🅂🅃🅄🅅🅆🅇🅈🅉🄰🄱🄲🄳🄴🄵🄶🄷🄸🄹🄺🄻🄼🄽🄾🄿🅀🅁🅂🅃🅄🅅🅆🅇🅈🅉0123456789);
3793             my $circleString = q(ⒶⒷⒸⒹⒺⒻⒼⒽⒾⒿⓀⓁⓂⓃⓄⓅⓆⓇⓈⓉⓊⓋⓌⓍⓎⓏⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ⓪①②③④⑤⑥⑦⑧⑨);
3794             my $darkString = q(🅐🅑🅒🅓🅔🅕🅖🅗🅘🅙🅚🅛🅜🅝🅞🅟🅠🅡🅢🅣🅤🅥🅦🅧🅨🅩🅐🅑🅒🅓🅔🅕🅖🅗🅘🅙🅚🅛🅜🅝🅞🅟🅠🅡🅢🅣🅤🅥🅦🅧🅨🅩⓿➊➋➌➍➎➏➐➑➒);
3795             my $superString = q(ᴬᴮCᴰᴱFᴳᴴᴵᴶᴷᴸᴹᴺᴼᴾQᴿSᵀᵁⱽᵂXYZᵃᵇᶜᵈᵉᶠᵍʰⁱʲᵏˡᵐⁿᵒᵖqʳˢᵗᵘᵛʷˣʸᶻ⁰¹²³⁴⁵⁶⁷⁸⁹);
3796             my $lowsubString = q(ₐbcdₑfgₕᵢⱼₖₗₘₙₒₚqᵣₛₜᵤᵥwₓyz₀₁₂₃₄₅₆₇₈₉);
3797             my $lowerString = join '', 'a'..'z', '0'..'9';
3798             my $mathematicalItalic = '𝐴𝐵𝐶𝐷𝐸𝐹𝐺𝐻𝐼𝐽𝐾𝐿𝑀𝑁𝑂𝑃𝑄𝑅𝑆𝑇𝑈𝑉𝑊𝑋𝑌𝑍𝑎𝑏𝑐𝑑𝑒𝑓𝑔𝑖𝑗𝑘𝑙𝑚𝑛𝑜𝑝𝑞𝑟𝑠𝑡𝑢𝑣𝑤𝑥𝑦𝑧';
3799             my $mathematicalBold = '𝐀𝐁𝐂𝐃𝐄𝐅𝐆𝐇𝐈𝐉𝐊𝐋𝐌𝐍𝐎𝐏𝐐𝐑𝐒𝐓𝐔𝐕𝐖𝐗𝐘𝐙𝐚𝐛𝐜𝐝𝐞𝐟𝐠𝐡𝐢𝐣𝐤𝐥𝐦𝐧𝐨𝐩𝐪𝐫𝐬𝐭𝐮𝐯𝐰𝐱𝐲𝐳';
3800             my $mathematicalBoldItalic = '𝑨𝑩𝑪𝑫𝑬𝑭𝑮𝑯𝑰𝑱𝑲𝑳𝑴𝑵𝑶𝑷𝑸𝑹𝑺𝑻𝑼𝑽𝑾𝑿𝒀𝒁𝒂𝒃𝒄𝒅𝒆𝒇𝒈𝒉𝒊𝒋𝒌𝒍𝒎𝒏𝒐𝒑𝒒𝒓𝒔𝒕𝒖𝒗𝒘𝒙𝒚𝒛';
3801             my $mathematicalSansSerif = '𝖠𝖡𝖢𝖣𝖤𝖥𝖦𝖧𝖨𝖩𝖪𝖫𝖬𝖭𝖮𝖯𝖰𝖱𝖲𝖳𝖴𝖵𝖶𝖷𝖸𝖹𝖺𝖻𝖼𝖽𝖾𝖿𝗀𝗁𝗂𝗃𝗄𝗅𝗆𝗇𝗈𝗉𝗊𝗋𝗌𝗍𝗎𝗏𝗐𝗑𝗒𝗓';
3802             my $mathematicalSansSerifBold = '𝗔𝗕𝗖𝗗𝗘𝗙𝗚𝗛𝗜𝗝𝗞𝗟𝗠𝗡𝗢𝗣𝗤𝗥𝗦𝗧𝗨𝗩𝗪𝗫𝗬𝗭𝗮𝗯𝗰𝗱𝗲𝗳𝗴𝗵𝗶𝗷𝗸𝗹𝗺𝗻𝗼𝗽𝗾𝗿𝘀𝘁𝘂𝘃𝘄𝘅𝘆𝘇';
3803             my $mathematicalSansSerifItalic = '𝘈𝘉𝘊𝘋𝘌𝘍𝘎𝘏𝘐𝘑𝘒𝘓𝘔𝘕𝘖𝘗𝘘𝘙𝘚𝘛𝘜𝘝𝘞𝘟𝘠𝘡𝘢𝘣𝘤𝘥𝘦𝘧𝘨𝘩𝘪𝘫𝘬𝘭𝘮𝘯𝘰𝘱𝘲𝘳𝘴𝘵𝘶𝘷𝘸𝘹𝘺𝘻';
3804             my $mathematicalSansSerifBoldItalic = '𝘼𝘽𝘾𝘿𝙀𝙁𝙂𝙃𝙄𝙅𝙆𝙇𝙈𝙉𝙊𝙋𝙌𝙍𝙎𝙏𝙐𝙑𝙒𝙓𝙔𝙕𝙖𝙗𝙘𝙙𝙚𝙛𝙜𝙝𝙞𝙟𝙠𝙡𝙢𝙣𝙤𝙥𝙦𝙧𝙨𝙩𝙪𝙫𝙬𝙭𝙮𝙯';
3805             my $mathematicalMonoSpace = '𝙰𝙱𝙲𝙳𝙴𝙵𝙶𝙷𝙸𝙹𝙺𝙻𝙼𝙽𝙾𝙿𝚀𝚁𝚂𝚃𝚄𝚅𝚆𝚇𝚈𝚉𝚊𝚋𝚌𝚍𝚎𝚏𝚐𝚑𝚒𝚓𝚔𝚕𝚖𝚗𝚘𝚙𝚚𝚛𝚜𝚝𝚞𝚟𝚠𝚡𝚢𝚣';
3806              
3807             sub mathematicalItalicString($) # Convert alphanumerics in a string to L Mathematical Italic.
3808 368     368 1 1840 {my ($string) = @_; # String to convert
3809 368         5888 my $h = $normalAlphaString =~ s(h) ()r; # Unicode does not have a small mathematical italic h
3810 368         26864 eval qq(\$string =~ tr($h) ($mathematicalItalic));
3811 368         3680 $string
3812             }
3813              
3814             sub mathematicalBoldString($) # Convert alphanumerics in a string to L Mathematical Bold.
3815 368     368 1 1472 {my ($string) = @_; # String to convert
3816 368         29072 eval qq(\$string =~ tr($normalAlphaString) ($mathematicalBold));
3817 368         2944 $string
3818             }
3819              
3820             sub mathematicalBoldStringUndo($) # Undo alphanumerics in a string to L Mathematical Bold..
3821 368     368 1 1472 {my ($string) = @_; # String to convert
3822 368         28704 eval qq(\$string =~ tr($mathematicalBold) ($normalAlphaString));
3823 368         3680 $string
3824             }
3825              
3826             sub mathematicalBoldItalicString($) # Convert alphanumerics in a string to L Mathematical Bold Italic.
3827 368     368 1 1840 {my ($string) = @_; # String to convert
3828 368         29808 eval qq(\$string =~ tr($normalAlphaString) ($mathematicalBoldItalic));
3829 368         3312 $string
3830             }
3831              
3832             sub mathematicalBoldItalicStringUndo($) # Undo alphanumerics in a string to L Mathematical Bold Italic.
3833 368     368 1 1840 {my ($string) = @_; # String to convert
3834 368         24656 eval qq(\$string =~ tr($mathematicalBoldItalic) ($normalAlphaString));
3835 368         3680 $string
3836             }
3837              
3838             sub mathematicalSansSerifString($) # Convert alphanumerics in a string to L Mathematical Sans Serif.
3839 368     368 1 1840 {my ($string) = @_; # String to convert
3840 368         28704 eval qq(\$string =~ tr($normalAlphaString) ($mathematicalSansSerif));
3841 368         3312 $string
3842             }
3843              
3844             sub mathematicalSansSerifStringUndo($) # Undo alphanumerics in a string to L Mathematical Sans Serif.
3845 368     368 1 1472 {my ($string) = @_; # String to convert
3846 368         24288 eval qq(\$string =~ tr($mathematicalSansSerif) ($normalAlphaString));
3847 368         3312 $string
3848             }
3849              
3850             sub mathematicalSansSerifBoldString($) # Convert alphanumerics in a string to L Mathematical Sans Serif Bold.
3851 368     368 1 1472 {my ($string) = @_; # String to convert
3852 368         33856 eval qq(\$string =~ tr($normalAlphaString) ($mathematicalSansSerifBold));
3853 368         3312 $string
3854             }
3855              
3856             sub mathematicalSansSerifBoldStringUndo($) # Undo alphanumerics in a string to L Mathematical Sans Serif Bold.
3857 368     368 1 1472 {my ($string) = @_; # String to convert
3858 368         23920 eval qq(\$string =~ tr($mathematicalSansSerifBold) ($normalAlphaString));
3859 368         3312 $string
3860             }
3861              
3862             sub mathematicalSansSerifItalicString($) # Convert alphanumerics in a string to L Mathematical Sans Serif Italic.
3863 368     368 1 1840 {my ($string) = @_; # String to convert
3864 368         30544 eval qq(\$string =~ tr($normalAlphaString) ($mathematicalSansSerifItalic));
3865 368         4048 $string
3866             }
3867              
3868             sub mathematicalSansSerifItalicStringUndo($) # Undo alphanumerics in a string to L Mathematical Sans Serif Italic.
3869 368     368 1 1472 {my ($string) = @_; # String to convert
3870 368         23920 eval qq(\$string =~ tr($mathematicalSansSerifItalic) ($normalAlphaString));
3871 368         3680 $string
3872             }
3873              
3874             sub mathematicalSansSerifBoldItalicString($) # Convert alphanumerics in a string to L Mathematical Sans Serif Bold Italic.
3875 368     368 1 1472 {my ($string) = @_; # String to convert
3876 368         33856 eval qq(\$string =~ tr($normalAlphaString) ($mathematicalSansSerifBoldItalic));
3877 368         3312 $string
3878             }
3879              
3880             sub mathematicalSansSerifBoldItalicStringUndo($) # Undo alphanumerics in a string to L Mathematical Sans Serif Bold Italic.
3881 368     368 1 1840 {my ($string) = @_; # String to convert
3882 368         23552 eval qq(\$string =~ tr($mathematicalSansSerifBoldItalic) ($normalAlphaString));
3883 368         3680 $string
3884             }
3885              
3886             sub mathematicalMonoSpaceString($) # Convert alphanumerics in a string to L Mathematical MonoSpace.
3887 368     368 1 1472 {my ($string) = @_; # String to convert
3888 368         33488 eval qq(\$string =~ tr($normalAlphaString) ($mathematicalMonoSpace));
3889 368         2944 $string
3890             }
3891              
3892             sub mathematicalMonoSpaceStringUndo($) # Undo alphanumerics in a string to L Mathematical MonoSpace.
3893 368     368 1 1472 {my ($string) = @_; # String to convert
3894 368         41584 eval qq(\$string =~ tr($mathematicalMonoSpace) ($normalAlphaString));
3895 368         3312 $string
3896             }
3897              
3898             sub boldString($) # Convert alphanumerics in a string to bold.
3899 1125     1125 1 6634 {my ($string) = @_; # String to convert
3900 1125     636   166757 eval qq(\$string =~ tr($normalString) ($boldString)); # Some Perls cannot do this and complain but I want to avoid excluding all the other methods in this file just because some perls cannot do this one operation.
  636         80056  
  636         52124  
  368         9200  
3901 1125         11765 $string
3902             }
3903              
3904             sub boldStringUndo($) # Undo alphanumerics in a string to bold.
3905 368     368 1 1472 {my ($string) = @_; # String to convert
3906 368         24288 eval qq(\$string =~ tr($boldString) ($normalString));
3907 368         4048 $string
3908             }
3909              
3910             sub enclosedString($) # Convert alphanumerics in a string to enclosed alphanumerics.
3911 736     736 1 2944 {my ($string) = @_; # String to convert
3912 736         58144 eval qq(\$string =~ tr($normalString) ($circleString));
3913 736         6992 $string
3914             }
3915              
3916             sub enclosedStringUndo($) # Undo alphanumerics in a string to enclosed alphanumerics.
3917 368     368 1 1840 {my ($string) = @_; # String to convert
3918 368         27968 eval qq(\$string =~ tr($circleString) ($normalString));
3919 368         4416 $string
3920             }
3921              
3922             sub enclosedReversedString($) # Convert alphanumerics in a string to enclosed reversed alphanumerics.
3923 736     736 1 4048 {my ($string) = @_; # String to convert
3924 736         63296 eval qq(\$string =~ tr($normalString) ($darkString));
3925 736         5520 $string
3926             }
3927              
3928             sub enclosedReversedStringUndo($) # Undo alphanumerics in a string to enclosed reversed alphanumerics.
3929 368     368 1 1840 {my ($string) = @_; # String to convert
3930 368         24288 eval qq(\$string =~ tr($darkString) ($normalString));
3931 368         3680 $string
3932             }
3933              
3934             sub superScriptString($) # Convert alphanumerics in a string to super scripts
3935 736     736 1 2944 {my ($string) = @_; # String to convert
3936 736         60720 eval qq(\$string =~ tr($normalString) ($superString));
3937 736         6256 $string
3938             }
3939              
3940             sub superScriptStringUndo($) # Undo alphanumerics in a string to super scripts
3941 368     368 1 1472 {my ($string) = @_; # String to convert
3942 182904         1063735 eval qq(\$string =~ tr($superString) ($normalString));
3943 368         5152 $string
3944             }
3945              
3946             sub subScriptString($) # Convert alphanumerics in a string to sub scripts
3947 736     736 1 2208 {my ($string) = @_; # String to convert
3948 736         57408 eval qq(\$string =~ tr($lowerString) ($lowsubString));
3949 736         140944 $string
3950             }
3951              
3952             sub subScriptStringUndo($) # Undo alphanumerics in a string to sub scripts
3953 368     368 1 1840 {my ($string) = @_; # String to convert
3954 368         27232 eval qq(\$string =~ tr($lowsubString) ($lowerString));
3955 368         4416 $string
3956             }
3957              
3958             sub isFileUtf8($) # Return the file name quoted if its contents are in utf8 else return undef
3959 0     0 1 0 {my ($file) = @_; # File to test
3960 0         0 my $f = quoteFile($file);
3961              
3962 0 0       0 return undef unless confirmHasCommandLineCommand(q(isutf8)); # Confirm we have isutf8
3963              
3964 0         0 qx(isutf8 -q $f); # Test
3965 0 0       0 return $f unless $?; # File is utf8
3966             undef # File is not utf8
3967 0         0 }
3968              
3969             sub convertUtf8ToUtf32($) # Convert a number representing a single unicode point coded in utf8 to utf32.
3970             {my ($c) = @_; # Unicode point encoded as utf8
3971              
3972             return $c if $c <= 0x7f; # Ascii
3973              
3974             my sub invalid # Invalid utf8
3975             {confess "Invalid utf8 character: ".sprintf("%08x", $c)."\n";
3976             };
3977              
3978             if ($c <= 0xdfff) # 2 bytes
3979             {my $d = $c >> 8; $d &= 0x1f;
3980             $c &= 0xff; $c <= 0xbf or invalid; $c &= 0x3f;
3981             return $c | ($d << 6);
3982             }
3983              
3984             if ($c <= 0xefffff) # 3 bytes
3985             {my $e = $c >> 16; $e &= 0x0f;
3986             my $d = $c >> 8; $d &= 0xff; $d <= 0xbf or invalid; $d &= 0x3f;
3987             $c &= 0xff; $c <= 0xbf or invalid; $c &= 0x3f;
3988             return $c | ($d << 6) | ($e << 12);
3989             }
3990              
3991             if ($c <= 0xf7FFFFFF) # 4 bytes
3992             {my $f = $c >> 24; $f &= 0x07;
3993             my $e = $c >> 16; $e &= 0xff; $e <= 0xbf or invalid; $e &= 0x3f;
3994             my $d = $c >> 8; $d &= 0xff; $d <= 0xbf or invalid; $d &= 0x3f;
3995             $c &= 0xff; $c <= 0xbf or invalid; $c &= 0x3f;
3996             return $c | ($d<<6) | ($e<<12) | ($f<<18);
3997             };
3998              
3999             confess "Invalid utf8 code: ".sprintf("%08x", $c). "\n";
4000             }
4001              
4002             sub convertUtf32ToUtf8($) # Convert a number representing a single unicode point coded in utf32 to utf8.
4003 6     6 1 14 {my ($c) = @_; # Unicode point encoded as utf32
4004              
4005 6 100       21 return $c if $c <= (1<<7); # Ascii
4006              
4007 5 100       10 if ($c <= (1 << 11)) # 2 bytes
4008 1         2 {my $d = ($c >> 0) & 0x3f;
4009 1         2 $c = ($c >> 6);
4010 1         5 return ($c<<8) | $d | 0xC080;
4011             }
4012              
4013 4 100       15 if ($c <= (1 << 16)) # 3 bytes
4014 3         7 {my $e = ($c >> 0) & 0x3f;
4015 3         11 my $d = ($c >> 6) & 0x3f;
4016 3         5 $c = ($c >> 12);
4017              
4018 3         14 return ($c<<16) | ($d<<8) | $e | 0xE08080
4019             }
4020              
4021 1 50       5 if ($c <= (1 << 21)) # 4 bytes
4022 1         3 {my $f = ($c >> 0) & 0x3f;
4023 1         2 my $e = ($c >> 6) & 0x3f;
4024 1         2 my $d = ($c >> 12) & 0x3f;
4025 1         1 $c = ($c >> 18);
4026 1         6 return ($c<<24) | ($d<<16) | ($e<<8) | $f | 0xF0808080
4027             }
4028              
4029 0         0 confess "Invalid utf32 code: $c";
4030             }
4031              
4032             #D1 Unix domain communications # Send messages between processes via a unix domain socket.
4033              
4034             sub newUdsr(@) #P Create a communicator - a means to communicate between processes on the same machine via L and L.
4035 0     0 1 0 {my (@parms) = @_; # Attributes per L
4036 0         0 my $u = genHash(q(Udsr), # Package name
4037             client => undef, # Client socket and connection socket
4038             headerLength => 8, #I Length of fixed header which carries the length of the following message
4039              
4040             serverAction => undef, #I Server action sub, which receives a communicator every time a client creates a new connection. If this server is going to be started by systemd as a service with the specified L then this is the a actual text of the code that will be installed as a CGI script and run in response to an incoming transaction in a separate process with the userid set to L. It receives the text of the http request from the browser as parameter 1 and should return the text to be sent back to the browser.
4041              
4042             serverPid => undef, # Server pid which can be used to kill the server via kill q(kill), $pid
4043             socketPath => q(unix-domain-socket-test.sock), #I Socket file
4044              
4045             serviceName => q(zzz), #I Service name for install by systemd
4046             serviceUser => q(), #I Userid for service
4047             @_
4048             );
4049             }
4050              
4051             sub newUdsrServer(@) # Create a communications server - a means to communicate between processes on the same machine via L and L.
4052 0     0 1 0 {my (@parms) = @_; # Attributes per L
4053 0         0 my $u = newUdsr(@_);
4054 0         0 my $f = $u->socketPath;
4055 0         0 unlink $f;
4056 0         0 my $s = IO::Socket::UNIX->new(Type=>SOCK_STREAM(), Local=>$f, Listen=>1); # Create socket
4057 0         0 xxx(qq(chmod ugo=rwx $f)); # Ensure that www-data can read and write to the socket
4058             # lll "Created unix domain socket as user:", qx(/usr/bin/whoami);
4059 0 0       0 if (my $pid = fork) # Run the server in a process by itself
4060 0         0 {$u->serverPid = $pid; # Record server pid so it can be killed
4061 0         0 return $u;
4062             }
4063             else # Run the server action on a client connection
4064 0         0 {while (my $con = $s->accept())
4065 0         0 {$u->client = $con;
4066 0     237   0 call sub{$u->serverAction->($u)}; # The server action sub should use the read and write routines in the passed communicator to interact with the client .
  0         0  
4067 0         0 $con->close;
4068             }
4069 0         0 exit;
4070             }
4071             }
4072              
4073             sub newUdsrClient(@) # Create a new communications client - a means to communicate between processes on the same machine via L and L.
4074 0     212 1 0 {my (@parms) = @_; # Attributes per L
4075 0         0 my $u = newUdsr(@_);
4076 0         0 my $s = $u->client = IO::Socket::UNIX->new(Type=>SOCK_STREAM(), Peer => $u->socketPath);
4077 0         0 my $r1 = $!; my $r2 = $?;
  0         0  
4078 0 0       0 $s or confess join "\n", "Cannot create unix domain socket:",
4079             dump($u), dump({q($!)=>$r1, q($?)=>$r2, q(userId)=>qx(/usr/bin/whoami)});
4080 0         0 $u
4081             }
4082              
4083             sub Udsr::write($$) # Write a communications message to the L or the L.
4084 0     187   0 {my ($u, $msg) = @_; # Communicator, message
4085 0         0 my $con = $u->client;
4086             # $msg //= ''; # undef seems to get reported as wide char
4087 0         0 my $m = pad(length($msg), $u->headerLength).$msg;
4088 0 0       0 $con or confess "No unix domain socket:\n". dump($u); # Complain if the socket has not been created
4089 0         0 $con->print($m);
4090 0         0 $u
4091             }
4092              
4093             sub Udsr::read($) # Read a message from the L or the L.
4094 0     0   0 {my ($u) = @_; # Communicator
4095 0         0 my $con = $u->client;
4096 0         0 $con->read(my $length, $u->headerLength);
4097 0         0 $con->read(my $data, $length);
4098 0         0 $data
4099             }
4100              
4101             sub Udsr::kill($) # Kill a communications server.
4102 0     0   0 {my ($u) = @_; # Communicator
4103 0         0 my $p = $u->serverPid; # Server Pid
4104 0 0       0 kill 'KILL', $p if $p; # Kill server
4105 0         0 $u->serverPid = undef; # Server Pid
4106 0         0 unlink $u->socketPath; # Remove socket
4107 0         0 $u
4108             }
4109              
4110             sub Udsr::webUser($$) # Create a systemd installed server that processes http requests using a specified userid. The systemd and CGI files plus an installation script are written to the specified folder after it has been cleared. The L attribute contains the code to be executed by the server: it should contain a L B which will be called with a hash of the CGI variables. This L should return the response to be sent back to the client. Returns the installation script file name.
4111 0     0   0 {my ($u, $folder) = @_; # Communicator, folder to contain server code
4112              
4113 0         0 clearFolder($folder, 9); # Clear the output folder
4114              
4115             my $parms = join ', ', # Parameters to hand to server and client
4116 0 0       0 map {my $v = $$u{$_}; defined($v) ? qq($_ => q($v)) : ()}
  0         0  
4117 0         0 grep {!m/serverAction/} keys %$u;
  0         0  
4118              
4119 0         0 my $user = $u->serviceUser; # Communicator details
4120 0         0 my $code = $u->serverAction; # Server code minus
4121 0         0 $code =~ s(if \(!caller\).*\Z) ()s; # Remove initiator at end
4122 0         0 $code =~ s(##.*?\n) ()gs; # Remove some spare blank lines so line numbers match
4123              
4124             my $perlParameters = sub # Get perl parameters
4125 0 0   268   0 {if ($code =~ m(\A#!.*?perl\s*(.*?)\n)is)
4126 0         0 {my $p = $1;
4127 0         0 return $p;
4128             }
4129             q()
4130 0         0 }->();
  0         0  
4131              
4132 0         0 my $name = $u->serviceName;
4133              
4134 0         0 my $ssdt = fpe(qw(/etc systemd system), $name, q(service)); # Systemd folder
4135              
4136 0         0 my $cgif = fpd(qw(/usr lib cgi-bin), $name); # Cgi folder
4137 0         0 my $cgst = fpe($cgif, q(server), q(pl)); # Cgi server
4138 0         0 my $cgct = fpe($cgif, q(client), q(pl)); # Cgi client
4139              
4140 0         0 my $inst = fpe($folder, qw(install sh)); # Install script
4141 447444         123507976389 my $ssdl = fpe($folder, qw(service txt));
4142 0         0 my $cgsl = fpe($folder, q(server), q(pl));
4143 0         0 my $cgcl = fpe($folder, q(client), q(pl));
4144              
4145 0         0 owf($ssdl, <
4146             [Unit]
4147             Description=Http to unix domain socket server
4148              
4149             [Service]
4150             Type=forking
4151             ExecStart=/usr/lib/cgi-bin/$name/server.pl
4152             User=$user
4153              
4154             [Install]
4155             WantedBy=multi-user.target
4156             END
4157             # setPermissionsForFile($ssdl, q(ugo=rx));
4158 0         0 setPermissionsForFile($ssdl, q(ugo=r)); # Permissions will be copied to server if the file does not exist on the server
4159              
4160 0         0 my $server = join '', <
4161             #!/usr/bin/perl $perlParameters
4162             END
4163             <<'END';
4164             #-------------------------------------------------------------------------------
4165             # Http to unix domain socket server
4166             #-------------------------------------------------------------------------------
4167             use warnings FATAL => qw(all);
4168             use strict;
4169             use Carp;
4170             use Data::Dump qw(dump);
4171             use Data::Table::Text qw(:all);
4172             use utf8;
4173             use feature qw(say current_sub);
4174              
4175             makeDieConfess;
4176              
4177             # Server code which should contain a sub genResponse($hash) which returns the response to be sent to the client
4178            
4179              
4180             my $parms = newUdsr();
4181              
4182             $parms->serverAction = sub # Perform server action
4183             {my ($c) = @_; # Communicator
4184             my $parms = $c->read; # Parameter string from client
4185             my $data = $parms ? eval $parms : undef; # Decode parameter string
4186             $@ and confess "Unable to decode webUser request:\n$parms\n"; # Complain about parameter string
4187             my $resp = genResponse($data); # Execute server action and capture returned value
4188             $c->write($resp); # Write back to the client
4189             };
4190              
4191             unlink $parms->socketPath;
4192             newUdsrServer(%$parms);
4193             END
4194 0         0 $server =~ s() ($parms)s;
4195 0         0 $server =~ s() ($code)s;
4196 0         0 owf($cgsl, $server);
4197 0         0 setPermissionsForFile($cgsl, q(ugo=rx));
4198              
4199 0         0 my $client = <<'END';
4200             #!/usr/bin/perl -I/home/phil/perl/cpan/DataTableText/lib/
4201             #-------------------------------------------------------------------------------
4202             # Http to unix domain socket client
4203             #-------------------------------------------------------------------------------
4204             use warnings FATAL => qw(all);
4205             use strict;
4206             use Carp;
4207             use Data::Dump qw(dump);
4208             use Data::Table::Text qw(:all);
4209             use CGI;
4210             use utf8;
4211             use feature qw(say current_sub);
4212              
4213             makeDieConfess;
4214              
4215             my $cgi = CGI->new;
4216              
4217             my %v = $cgi->Vars;
4218             if (my $j = $cgi->param(q(POSTDATA))) # Load POST data
4219             {$v{POSTDATA} = $j;
4220             if (my $p = decodeJson($j))
4221             {if (ref($p) =~ m(hash)i)
4222             {%v = (%v, %$p);
4223             }
4224             }
4225             }
4226             #for my $k(keys %v)
4227             # {$v{$k} = wwwDecode($v{$k}) // q();
4228             # }
4229              
4230             my $parms = newUdsr();
4231             my $c = newUdsrClient(%$parms);
4232             say $c->read($c->write(dump({%v})));
4233             END
4234 0         0 $client =~ s() ($parms)s;
4235 0         0 owf($cgcl, $client);
4236 0         0 setPermissionsForFile($cgcl, q(ugo=rx));
4237              
4238 0         0 owf($inst, <
4239             sudo rm $ssdt $cgst $cgct
4240             sudo mkdir -p $cgif
4241             sudo cp $ssdl $ssdt
4242             sudo cp $cgsl $cgst
4243             sudo cp $cgcl $cgct
4244             sudo systemctl daemon-reload; sudo systemctl enable $name; sudo systemctl restart $name; sudo systemctl status $name
4245             END
4246              
4247 0         0 setPermissionsForFile $inst, q(u+x);
4248              
4249             # if (!$noInstall) # Install on server if available
4250             # {copyFolderToRemote($folder); # Copy code created locally to remote server
4251             # xxxr(qq(bash -x $inst)); # Install system by executing install procedure remotely
4252             # }
4253              
4254 0         0 lll <
4255             See status with:
4256              
4257             sudo systemctl status $name
4258              
4259             Install with:
4260              
4261             $inst
4262              
4263             Remove with:
4264              
4265             sudo rm $ssdt $cgst $cgct
4266              
4267             Access via:
4268              
4269             http://localhost/cgi-bin/$name/client.pl
4270              
4271             END
4272              
4273 0         0 $inst # Install script
4274             }
4275              
4276             #D2 www # Web processing
4277              
4278 0     0 1 0 sub wwwHeader {say STDOUT qq(Content-Type: text/html;charset=UTF-8\n\n)} # Html header
4279              
4280             sub wwwGitHubAuth(&$$$$) # Logon as a L L app per: L. If no L code is supplied then a web page is printed that allows the user to request that such a code be sent to the server. If a valid code is received, by the server then it is converted to a L token which is handed to L L.
4281 0     0 1 0 {my ($saveUserDetails, $clientId, $clientSecret, $code, $state) = @_; # Process user token once obtained from GitHub, Client id, client secret, authorization code, random string
4282              
4283 0 0       0 if (!$code) # Show logon page if no code has been supplied
4284 3177723         35924156 {my $r = rand =~ s(\A0.) ()r;
4285 0         0 say STDOUT <
4286            
4287            
4288            
4289            

Logon with GitHub

4290            
4293            
4294            
4295             HTML
4296             }
4297             else # Get userid
4298 3126281         12236816 {my $s = qq(wget -q -O- "https://github.com/login/oauth/access_token) # Get the token - Wget works, Curl does not
4299             .qq(?code=$code&state=$state)
4300             .qq(&client_id=$clientId&client_secret=$clientSecret");
4301              
4302 0 0       0 if (my $r = qx($s)) # Get user details
4303 0 0       0 {if ($r =~ m(\Aaccess_token=(.*?)&scope=(.*?)&token_type=(.*?)\Z))
4304 132525         18688417 {my ($token, $scope, $type) = ($1, $2, $3);
4305 64745         373011 my $c = qq(wget -q -O- --header="Authorization: token $token")
4306             .qq( https://api.github.com/user);
4307 0         0 my $j = qx($c 2>&1);
4308 0         0 my $user = decodeJson($j);
4309 63842         637156 $saveUserDetails->($user, $state, $token, $scope, $type);
4310             }
4311             }
4312             }
4313             } # wwwGitHubAuth
4314              
4315             #D1 Cloud Cover # Useful for operating across the cloud.
4316              
4317             sub makeDieConfess # Force die to confess where the death occurred
4318             {$SIG{__DIE__} = sub
4319 0     0   0 {local $SIG{__DIE__} = undef;
4320 0         0 confess shift;
4321 0     0 1 0 };
4322             }
4323              
4324             sub ipAddressOfHost($) # Get the first ip address of the specified host via Domain Name Services
4325 0     0 1 0 {my ($host) = @_; # Host name
4326 0         0 my $i = inet_aton $host;
4327 0 0       0 confess "Unable to get ip address of hist: $host\n" unless $i;
4328 0         0 return inet_ntoa $i;
4329             }
4330              
4331 0     0 1 0 sub awsIpFile {q(/tmp/awsPrimaryInstanceIpAddress.data)} #P File in which to save IP address of primary instance on Aws
4332 0     0 1 0 sub awsEc2DescribeInstancesCache {q(/tmp/awsEc2DescribeInstancesCache.data)} #P File in which to cache latest results from describe instances to avoid being throttled
4333              
4334             sub awsIp # Get ip address of server at L.
4335 0     0 1 0 {for(1..2)
4336 0 0       0 {if (-e awsIpFile)
4337 0 0       0 {if (my $d = eval {retrieveFile(awsIpFile)})
  0         0  
4338 0 0       0 {if ($d->{time} + 180 > time)
4339 0         0 {return $d->{ip};
4340             }
4341             }
4342             }
4343 0         0 &awsParallelPrimaryInstanceId();
4344             }
4345 0         0 confess "Unable to get primary instance IP address\n";
4346             }
4347              
4348             sub saveAwsIp # Make the server at L with the given IP address the default primary server as used by all the methods whose names end in B or B. Returns the given IP address.
4349 0     0 1 0 {my ($ip) = @_; # Ip address of chosen server on L
4350 0         0 storeFile(awsIpFile, {ip=>$ip, time=>time});
4351 63513         105758153 $ip
4352             }
4353              
4354             sub saveAwsDomain # Make the server at L with the given domain name the default primary server as used by all the methods whose names end in B or B. Returns the given IP address.
4355 0     0 1 0 {my ($host) = @_; # Host domain name
4356 0         0 saveAwsIp ipAddressOfHost $host;
4357             }
4358              
4359             sub awsMetaData($) # Get an item of meta data for the L server we are currently running on if we are running on an L server else return a blank string.
4360 0     0 1 0 {my ($item) = @_; # Meta data field
4361 0 0       0 return q() unless &onAws; # We are not on Aws
4362 0 0       0 return undef unless confirmHasCommandLineCommand(q(curl)); # Confirm we have curl
4363 0         0 my $c = qq(curl -m 0 -s http://169.254.169.254/latest/meta-data/$item/); # Command
4364 0         0 qx($c)
4365             }
4366              
4367             my $awsCurrentIp; # Server IP address if running on L
4368             sub awsCurrentIp # Get the ip address of the AWS server we are currently running on if we are running on an L server else return a blank string.
4369 0 0   0 1 0 {return $awsCurrentIp if defined $awsCurrentIp;
4370 0         0 $awsCurrentIp = awsMetaData q(public-ipv4);
4371             }
4372              
4373             my $awsCurrentInstanceId; # Server instance id
4374             sub awsCurrentInstanceId # Get the instance id of the L server we are currently running on if we are running on an L server else return a blank string.
4375 0 0   0 1 0 {return $awsCurrentInstanceId if defined $awsCurrentInstanceId;
4376 0         0 $awsCurrentInstanceId = awsMetaData q(instance-id)
4377             }
4378              
4379             my $awsCurrentAvailabilityZone; # Availability zone
4380             sub awsCurrentAvailabilityZone # Get the availability zone of the L server we are currently running on if we are running on an L server else return a blank string.
4381 0 0   0 1 0 {return $awsCurrentAvailabilityZone if defined $awsCurrentAvailabilityZone;
4382 0         0 $awsCurrentAvailabilityZone = awsMetaData(q(placement/availability-zone))
4383             }
4384              
4385             my $awsCurrentRegion; # Server region
4386             sub awsCurrentRegion # Get the region of the L server we are currently running on if we are running on an L server else return a blank string.
4387 0 0   0 1 0 {if (my $a = awsCurrentAvailabilityZone)
4388 0         0 {return $a =~ s(.\Z) ()sr
4389             }
4390             q()
4391 0         0 }
4392              
4393             my $awsCurrentInstanceType; # Instance type
4394             sub awsCurrentInstanceType # Get the instance type of the L server if we are running on an L server else return a blank string.
4395 0 0   0 1 0 {return $awsCurrentInstanceType if defined $awsCurrentInstanceType ;
4396 0         0 $awsCurrentInstanceType = awsMetaData(q(instance-type))
4397             }
4398              
4399             sub awsInstanceId(%) #P Create an instance-id from the specified B<%options>
4400 0     0 1 0 {my (%options) = @_; # Options
4401 0 0 0     0 return q() unless my $i = $options{instanceId} // awsCurrentInstanceId; # Instance id if supplied or we are on AWS
4402 0         0 qq( --instance-id $i ); # instance-id keyword
4403             }
4404              
4405             sub awsProfile(%) #P Create a profile keyword from the specified B<%options>
4406 0     0 1 0 {my (%options) = @_; # Options
4407 0 0       0 return q() unless my $p = $options{profile}; # Profile value
4408 0         0 qq( --profile $p ); # Profile keyword
4409             }
4410              
4411             sub awsRegion(%) #P Create a region keyword from the specified B<%options>
4412 0     0 1 0 {my (%options) = @_; # Options
4413 0 0 0     0 return q() unless my $r = $options{region} // awsCurrentRegion; # Region value if supplied or we are on AWS
4414 0         0 qq( --region $r ); # Region keyword
4415             }
4416              
4417             sub awsExecCli($%) # Execute an AWs command and return its response
4418 0     0 1 0 {my ($command, %options) = @_; # Command to execute, aws cli options
4419 0         0 $command =~ s(\n) ( )gs; # Make command into one line
4420 0         0 my $p = awsProfile(%options); # Profile to use
4421 0         0 my $r = awsRegion (%options); # Region to use
4422 1472         25392 my $c = qq($command $r $p); # Command
4423 0         0 say STDERR $c;
4424 0         0 qx($c 2>&1); # Execute
4425             }
4426              
4427             sub awsExecCliJson($%) # Execute an AWs command and decode the json so produced
4428 0     0 1 0 {my ($command, %options) = @_; # Command to execute, aws cli options
4429 0         0 $command =~ s(\n) ( )gs; # Make command into one line
4430 0         0 my $p = awsProfile(%options); # Profile to use
4431 0         0 my $r = awsRegion (%options); # Region to use
4432 0         0 my $c = qq($command $r $p); # Command
4433 0         0 say STDERR $c;
4434 0         0 my $j = qx($c); # Retrieve json
4435 0         0 reloadHashes decodeJson($j); # Decode json to Perl
4436             }
4437              
4438             sub awsEc2DescribeInstances(%) # Describe the L instances running in a B<$region>.
4439 0     0 1 0 {my (%options) = @_; # Options
4440              
4441 0   0     0 my $region = $options{region} // q();
4442 0 0       0 if (-e awsEc2DescribeInstancesCache) # Use cached value if possible
4443 0 0       0 {if ( my $D = eval {retrieveFile(awsEc2DescribeInstancesCache)})
  0         0  
4444 0 0       0 {if (my $d = $D->{$region})
4445 0 0       0 {return $d->{results} if $d->{time} + 20 > time;
4446             }
4447             }
4448             }
4449              
4450 0 0       0 return undef unless confirmHasCommandLineCommand(q(aws)); # Confirm we have aws cli
4451 0         0 my $p = awsProfile(%options); # Profile to use
4452 0         0 my $r = awsRegion(%options); # Region to use
4453 0         0 my $c = qq(aws ec2 describe-instances $r $p); # Command
4454 0         0 my $j = qx($c); # Retrieve json
4455 0         0 my $d = decodeJson($j); # Decode json to Perl
4456              
4457 0         0 storeFile(awsEc2DescribeInstancesCache, {$region=>{time=>time, results=>$d}});# Cache results by region
4458 0         0 $d
4459             }
4460              
4461             sub awsEc2DescribeInstancesGetIPAddresses(%) # Return a hash of {instanceId => public ip address} for all running instances on L with ip addresses.
4462 0     0 1 0 {my (%options) = @_; # Options
4463              
4464 0         0 my $d = awsEc2DescribeInstances(%options); # Refresh with latest data
4465 0         0 my %i;
4466 0         0 for my $r($d->{Reservations}->@*)
4467 2944         35696 {for my $i($r->{Instances}->@*)
4468 268 0       5092 {if ($$i{State}{Name} =~ m(running)i)
4469 0         0 {my $id = $$i{InstanceId};
4470 0         0 $i{$id} = $i->{PublicIpAddress};
4471             }
4472             }
4473             }
4474              
4475 0         0 \%i # Return {instanceId => public ip address}
4476             }
4477              
4478             sub awsEc2InstanceIpAddress($%) # Return the IP address of a named instance on L else return B.
4479 0     0 1 0 {my ($instanceId, %options) = @_; # Instance id, options
4480 0         0 my $p = awsProfile(%options); # Profile to use
4481 0         0 my $r = awsRegion(%options); # Region to use
4482 0         0 my $c = qq(aws ec2 describe-instances --instance-ids $instanceId $r $p); # Command
4483 0         0 my $j = qx($c); # Retrieve json
4484 0         0 my $d = decodeJson($j); # Decode json
4485 0         0 for my $R($d->{Reservations}->@*)
4486 0         0 {for my $i($R->{Instances}->@*)
4487 0 0       0 {if (my $id = $$i{InstanceId})
4488 0 0       0 {if ($id eq $instanceId)
4489 0         0 {for my $I($i->{NetworkInterfaces}->@*)
4490 0 0       0 {if (my $ip = $$I{Association}{PublicIp})
4491 0         0 {return $ip # Return first ip address
4492             }
4493             }
4494             }
4495             }
4496             }
4497             }
4498             undef # No ip address found
4499 0         0 }
4500              
4501             sub awsEc2CreateImage($%) # Create an image snap shot with the specified B<$name> of the AWS server we are currently running on if we are running on an AWS server else return false. It is safe to shut down the instance immediately after initiating the snap shot - the snap continues even though the instance has terminated.
4502 0     0 1 0 {my ($name, %options) = @_; # Image name, options
4503 0 0       0 return undef unless confirmHasCommandLineCommand(q(aws)); # Confirm we have aws cli
4504 0         0 my $i = awsInstanceId(%options); # Instance id
4505 0         0 my $p = awsProfile(%options); # Profile
4506 0         0 my $r = awsRegion(%options); # Region
4507 0         0 my $c = qq(aws ec2 create-image --name "$name" $i $p $r);
4508 0         0 xxx($c);
4509             } # awsEc2CreateImage
4510              
4511             sub awsEc2FindImagesWithTagValue($%) # Find images with a tag that matches the specified regular expression B<$value>.
4512 0     0 1 0 {my ($value, %options) = @_; # Regular expression, Options
4513 0         0 my @images = awsEc2DescribeImages(%options);
4514 0         0 my @i;
4515 0         0 for my $i(@images) # Each image
4516 0 0       0 {if (my $tags = $i->{Tags})
4517 0         0 {for my $t(@$tags) # Each tag
4518 0 0       0 {next unless $t->{Value} =~ m($value);
4519 0         0 push @i, $i->{ImageId};
4520 0         0 last;
4521             }
4522             }
4523             }
4524              
4525             @i # Matching images
4526 0         0 } # awsEc2FindImagesWithTagValue
4527              
4528             sub awsEc2DescribeImages(%) # Describe images available.
4529 0     0 1 0 {my (%options) = @_; # Options
4530 0 0       0 return undef unless confirmHasCommandLineCommand(q(aws)); # Confirm we have aws cli
4531 0         0 my $p = awsProfile(%options); # Profile
4532 0         0 my $r = awsRegion (%options); # Region
4533 0         0 my $c = qq(aws ec2 describe-images --owners self $p $r);
4534 0         0 my $j = qx($c);
4535 0         0 map {reloadHashes $_}
4536 0         0 sort {$$b{CreationDate} cmp $$a{CreationDate}}
4537 0         0 @{decodeJson($j)->{Images}} # Decode json, sort into descending date order and return
  0         0  
4538             }
4539              
4540             my $awsCurrentLinuxSpotPrices; # Prices do not change very rapidly on the whole
4541             sub awsCurrentLinuxSpotPrices(%) # Return {instance type} = cheapest spot price in dollars per hour for the given region
4542 63842     0 1 481785 {my (%options) = @_; # Options
4543 0 0       0 return $awsCurrentLinuxSpotPrices if $awsCurrentLinuxSpotPrices; # Return cached set
4544              
4545 0 0       0 return undef unless confirmHasCommandLineCommand(q(aws)); # Confirm we have aws cli
4546 0         0 my $p = awsProfile(%options); # Profile
4547 64142         33993389 my $r = awsRegion (%options); # Region
4548              
4549 0         0 my $t = int time();
4550 0         0 my $c = qq(aws ec2 describe-spot-price-history --start-time=$t $p $r ).
4551             qq(--product-descriptions="Linux/UNIX" --query 'SpotPriceHistory[*]');
4552              
4553 0         0 my $j = qx($c);
4554 0         0 my $d = decodeJson($j);
4555              
4556 0         0 my %h; # {instance type} = cheapest spot
4557 0         0 for my $s(@$d)
4558 0         0 {my $i = $s->{InstanceType};
4559 0         0 my $p = $s->{SpotPrice};
4560 0   0     0 $h{$i} = min($h{$i}//$p, $p);
4561             }
4562              
4563 0         0 $awsCurrentLinuxSpotPrices = \%h # Cache results
4564             }
4565              
4566             my %awsEc2DescribeInstanceType; # Cache instance type details
4567             sub awsEc2DescribeInstanceType($%) # Return details of the specified instance type.
4568 0     0 1 0 {my ($instanceType, %options) = @_; # Instance type name, options
4569 0 0       0 return undef unless confirmHasCommandLineCommand(q(aws)); # Confirm we have aws cli
4570 0         0 my $i = $instanceType; # Instance type name
4571 0         0 my $p = awsProfile(%options); # Profile
4572 0         0 my $r = awsRegion(%options); # Region
4573 0         0 my $cached = $awsEc2DescribeInstanceType{$r}{$i}; # Cached instance type
4574 0 0       0 return $cached if $cached;
4575 0         0 my $c = qq(aws ec2 describe-instance-types $p $r --instance-types "$i");
4576 0         0 my $j = qx($c);
4577 0         0 my $d = decodeJson($j);
4578 0         0 $awsEc2DescribeInstanceType{$r}{$i} = $d->{InstanceTypes}[0]; # Cache instance type
4579             }
4580              
4581             sub awsEc2ReportSpotInstancePrices($%) # Report the prices of all the spot instances whose type matches a regular expression B<$instanceTypeRe>. The report is sorted by price in millidollars per cpu ascending.
4582             {my ($instanceTypeRe, %options) = @_; # Regular expression for instance type name, options
4583             my $spots = awsCurrentLinuxSpotPrices(%options); # Spot prices
4584             my @r;
4585             my $cit; my $pc; # Cheapest instance type, cheapest instance cost per cpu
4586             my sub formatPrice($)
4587             {my ($p) = @_;
4588             sprintf("%.2f", $p)
4589             };
4590              
4591             for my $s(sort keys %$spots)
4592             {next unless $s =~ m($instanceTypeRe)i;
4593             my $t = awsEc2DescribeInstanceType($s, %options); # Instance type details for spot instance
4594             next unless grep {m(spot)i} $t->{SupportedUsageClasses}->@*; # Instance type allows spot instances
4595             my $price = $$spots{$s} * 1e3;
4596             my $cpus = $$t{VCpuInfo}{DefaultVCpus};
4597             my $pricePerCpu = $price / $cpus;
4598             my $pf = sprintf("%.2f", $pricePerCpu);
4599             push @r, [$s, int($price), $cpus, formatPrice($pf)];
4600             if (!defined($cit) or $pricePerCpu < $pc) # Cheapest so far per CPU
4601             {$cit = $s;
4602             $pc = $pricePerCpu;
4603             }
4604             }
4605              
4606             my $p = formatPrice($pc);
4607             my $r = formatTable([sort {$$a[-1] <=> $$b[-1]} @r], <
4608             Instance_Type Instance type name
4609             Price Price in millidollars per hour
4610             CPUs Number of Cpus
4611             Price_per_CPU The price per CPU in millidollars per hour
4612             END
4613             title => q(CPUs by price),
4614             head => <
4615             NNNN instances types found on DDDD
4616              
4617             Cheapest Instance Type: $cit
4618             Price Per Cpu hour : $p in millidollars per hour
4619             END
4620             );
4621              
4622             genHash(q(Data::Table::Text::AwsEc2Price), # Prices of selected aws elastic compute instance types
4623             cheapestInstance => $cit, # The instance type that has the lowest CPU cost
4624             pricePerCpu => $pc, # The cost of the cheapest CPU In millidollars per hour
4625             report => $r, # Report showing the cost of other selected instances
4626             );
4627             }
4628              
4629             sub awsEc2RequestSpotInstances($$$$$$%) # Request spot instances as long as they can be started within the next minute. Return a list of spot instance request ids one for each instance requested.
4630 0     0 1 0 {my ($count, $instanceType, $ami, $price, $securityGroup, $key, %options) = @_;# Number of instances, instance type, AMI, price in dollars per hour, security group, key name, options.
4631 0 0       0 return undef unless confirmHasCommandLineCommand(q(aws)); # Confirm we have aws cli
4632 0         0 my $p = awsProfile(%options); # Profile
4633 0         0 my $r = awsRegion(%options); # Region
4634 0         0 my $t = qq( --valid-until ).(int time + 60); # Limit the duration to one minute - i.e. launch now or not at all.
4635              
4636 0         0 my $j = <
4637             {"DryRun" : false,
4638             "InstanceCount" : $count,
4639             "LaunchSpecification" :
4640             {"SecurityGroupIds" : ["$securityGroup"],
4641             "ImageId" : "$ami",
4642             "InstanceType" : "$instanceType",
4643             "KeyName" : "$key"
4644             },
4645             "SpotPrice" : "$price",
4646             "Type" : "one-time"
4647             }
4648             END
4649 0         0 my $f = writeFile(undef, $j);
4650 0         0 my $c = qq(aws ec2 request-spot-instances --cli-input-json file://$f $p $r $t);
4651 0         0 my $k = qx($c);
4652 0         0 my $d = decodeJson($k);
4653 0         0 map {$_->{SpotInstanceRequestId}=>1} $d->{SpotInstanceRequests}->@* # List of spot instances request ids - one for each instance requested. I.e. if $count == 2 then two spot instance request ids will be returned.
4654 0         0 }
4655              
4656             sub awsEc2DescribeSpotInstances(%) # Return a hash {spot instance request => spot instance details} describing the status of active spot instances.
4657 0     0 1 0 {my (%options) = @_; # Options.
4658 0 0       0 return undef unless confirmHasCommandLineCommand(q(aws)); # Confirm we have aws cli
4659 0         0 my $p = awsProfile(%options); # Profile
4660 0         0 my $r = awsRegion(%options); # Region
4661 0         0 my $c = qq(aws ec2 describe-spot-instance-requests $p $r);
4662 0         0 my $j = qx($c);
4663 0         0 my $d = decodeJson($j);
4664 0         0 my @r = $d->{SpotInstanceRequests}->@*;
4665 0         0 my %r = map {$_->{SpotInstanceRequestId}=>$_} @r; # Hash of spot instance requests
  0         0  
4666 0         0 \%r
4667             }
4668              
4669             sub awsR53a($$$%) # Create/Update a B L record for the specified server.
4670 0     0 1 0 {my ($zone, $server, $ip, %options) = @_; # Zone id from R53, fully qualified domain name, ip address, AWS CLI global options
4671 0         0 my $t = writeTempFile(<
4672             { "Changes": [
4673             {"Action": "UPSERT",
4674             "ResourceRecordSet":
4675             {"Name": "$server", "Type": "A", "TTL": 300,
4676             "ResourceRecords": [{"Value": "$ip"}]
4677             }
4678             }
4679             ]
4680             }
4681             END
4682 0         0 my $p = awsProfile(%options); # Profile
4683 0         0 my $s = xxx qq(aws route53 change-resource-record-sets --hosted-zone-id ) # Execute command
4684             .qq($zone --change-batch file://$t $p),
4685             qr(ChangeInfo);
4686 0         0 unlink $t;
4687 0         0 $s
4688             }
4689              
4690             sub awsR53aaaa($$$%) # Create/Update a B L record for the specified server.
4691 0     0 1 0 {my ($zone, $server, $ip, %options) = @_; # Zone id from R53, fully qualified domain name, ip6 address, AWS CLI global options
4692 0         0 my $t = writeTempFile(<
4693             { "Changes": [
4694             {"Action": "UPSERT",
4695             "ResourceRecordSet":
4696             {"Name": "$server", "Type": "AAAA", "TTL": 300,
4697             "ResourceRecords": [{"Value": "$ip"}]
4698             }
4699             }
4700             ]
4701             }
4702             END
4703 0         0 my $p = awsProfile(%options); # Profile
4704 0         0 my $s = xxx qq(aws route53 change-resource-record-sets --hosted-zone-id ) # Execute command
4705             .qq($zone --change-batch file://$t $p),
4706             qr(ChangeInfo);
4707 0         0 unlink $t;
4708 0         0 $s
4709             }
4710              
4711             sub awsEc2Tag($$$%) # Tag an elastic compute resource with the supplied tags.
4712 0     0 1 0 {my ($resource, $name, $value, %options) = @_; # Resource, tag name, tag value, options.
4713 0 0       0 return undef unless confirmHasCommandLineCommand(q(aws)); # Confirm we have aws cli
4714 0         0 my $p = awsProfile(%options); # Profile
4715 0         0 my $r = awsRegion(%options); # Region
4716 0         0 my $c = qq(aws ec2 create-tags --resources $resource ).
4717             qq( --tags Key=$name,Value=$value $r $p);
4718 0         0 xxx $c;
4719             }
4720              
4721             my %confirmHasCommandLineCommand; # Cache responses from which
4722             sub confirmHasCommandLineCommand($) # Check that the specified b<$cmd> is present on the current system. Use $ENV{PATH} to add folders containing commands as necessary.
4723 11478     11478 1 94975 {my ($cmd) = @_; # Command to check for
4724 11478 100       129306 return 1 if $confirmHasCommandLineCommand{$cmd}; # Use cache if possible
4725              
4726 1165         5113341 my $c = qx(which $cmd); # Check for command
4727 1165 50       56873 if ($c =~ m(/)s)
4728 1165         999695 {return ++$confirmHasCommandLineCommand{$cmd};
4729             }
4730              
4731 0         0 cluck "Unable to confirm presence of command: $cmd\n"; # Complain if the command is not available
4732 0         0 undef;
4733             }
4734              
4735             sub getNumberOfCpus #P Number of cpus
4736 158 50   158 1 2844 {return 1 if $^O !~ m(linux)i; # Presumably there is at least 1
4737 158 50       2212 my $n = confirmHasCommandLineCommand(q(nproc)) ? qx(nproc) : undef; # nproc
4738 158 50       14062 return 1 unless $n; # We must have at least 1
4739 158         14378 $n =~ s(\s+\Z) ()r;
4740             }
4741              
4742             my $numberOfCpus; # Number of cpus cache
4743              
4744             sub numberOfCpus(;$) # Number of cpus scaled by an optional factor - but only if you have nproc. If you do not have nproc but do have a convenient way for determining the number of cpus on your system please let me know.
4745 320     320 1 2816 {my ($scale) = @_; # Scale factor
4746 320   66     4757 my $n = $numberOfCpus //= getNumberOfCpus; # Cache the number of cpus as it will not change
4747 320 100 66     31908 return $n * $scale if $scale and $scale == int($scale);
4748 30 50 33     14040 return int(1 + $n * $scale) if $scale and $scale != int($scale);
4749 0         0 $n
4750             }
4751              
4752             sub ipAddressViaArp($) # Get the ip address of a server on the local network by hostname via arp
4753 0     0 1 0 {my ($hostName) = @_; # Host name
4754 0 0       0 return undef unless confirmHasCommandLineCommand(q(arp)); # Confirm we have arp
4755              
4756 0         0 my ($line) = grep {/$hostName/i} qx(arp -a 2>&1); # Search for host name in arp output
  0         0  
4757 0 0       0 return undef unless $line; # No such host
4758 0         0 my (undef, $ip) = split / /, $line; # Get ip address
4759 0         0 $ip =~ s(\x28|\x29) ()gs; # Remove brackets around ip address
4760 0         0 $ip # Return ip address
4761             }
4762              
4763             sub parseS3BucketAndFolderName($) # Parse an L bucket/folder name into a bucket and a folder name removing any initial s3://.
4764 84     84 1 462 {my ($name) = @_; # Bucket/folder name
4765              
4766 84         1218 $name = $name =~ s(s3://) ()gsr =~ s(\A\s*|\s*\Z) ()gsr;
4767 84 100       756 if ($name =~ m(\A([^/]*)/\Z)s)
4768 42         525 {return ($1, q())
4769             }
4770 42 100       336 if ($name =~ m(\A(.*?)/(.*)\Z)s)
4771 21         420 {return ($1, $2)
4772             }
4773 21         126 ($name, q())
4774             }
4775              
4776             sub saveCodeToS3($$$$;$) # Save source code every B<$saveCodeEvery> seconds by zipping folder B<$folder> to zip file B<$zipFileName> then saving this zip file in the specified L B<$bucket> using any additional L parameters in B<$S3Parms>.
4777 0     0 1 0 {my ($saveCodeEvery, $folder, $zipFileName, $bucket, $S3Parms) = @_; # Save every seconds, folder to save, zip file name, bucket/key, additional S3 parameters like profile or region as a string
4778 0 0       0 @_ == 5 or confess "Five parameters required";
4779 0 0       0 return undef unless confirmHasCommandLineCommand(q(zip)); # Confirm we have zip
4780 0 0       0 return undef unless confirmHasCommandLineCommand(q(aws)); # Confirm we have aws
4781              
4782 0         0 my $saveTimeFile = fpe($folder, q(codeSaveTimes)); # Get last save time if any
4783 0 0       0 my $lastSaveTime = -e $saveTimeFile ? retrieve($saveTimeFile) : undef; # Get last save time
4784 0 0 0     0 return if $lastSaveTime and $lastSaveTime->[0] > time - $saveCodeEvery; # Too soon
4785              
4786 0 0       0 return if fork; # Fork zip upload
4787 0         0 my $target = fpe($bucket, $zipFileName, q(zip)); # Target on S3
4788 0         0 lll "Saving latest version of code in $folder to s3://$target";
4789              
4790 0         0 my $z = fpe($folder, $zipFileName, q(zip)); # Zip file
4791 0         0 unlink $z; # Remove old zip file
4792              
4793 0 0       0 if (my $c = qq(cd $folder; zip -qr $z * -x "*.zip" -x "*.gz" -x "*/blib/*" -x "*/[._]*")) # Zip command
4794 0         0 {my $r = qx($c);
4795 0 0       0 confess "$c\n$r\n" if $r =~ m(\S); # Confirm zip
4796             }
4797              
4798 0   0     0 my $s3Parms = $S3Parms // '';
4799 0 0       0 if (my $c = "aws s3 cp $z s3://$target $s3Parms") # Upload zip
4800 0         0 {my $r = qx($c);
4801 0 0       0 confess "$c\n$r\n" if $r =~ m(\S); # Confirm upload
4802             }
4803              
4804 0         0 store([time], $saveTimeFile); # Save last save time
4805 0         0 unlink $z; # Remove old zip file
4806 0         0 lll "Saved latest version of code from $folder to s3://$target";
4807 0         0 exit;
4808             }
4809              
4810             sub saveSourceToS3($;$) #P Save source code.
4811 0     0 1 0 {my ($aws, $saveIntervalInSeconds) = @_; # Aws target file and keywords, save internal
4812 0   0     0 $saveIntervalInSeconds //= 1200; # Default save time
4813 0         0 warn "saveSourceToS3 is deprecated, please use saveCodeToS3 instead";
4814 0 0       0 return undef unless confirmHasCommandLineCommand(q(zip)); # Confirm we have zip
4815 0 0       0 return undef unless confirmHasCommandLineCommand(q(aws)); # Confirm we have aws
4816              
4817 0 0       0 unless(fork())
4818 0         0 {my $saveTime = "/tmp/saveTime/$0"; # Get last save time if any
4819 0         0 makePath($saveTime);
4820              
4821 0 0       0 if (my $lastSaveTime = fileModTime($saveTime)) # Get last save time
4822 0 0       0 {return if $lastSaveTime > time - $saveIntervalInSeconds; # Already saved
4823             }
4824              
4825 0         0 lll "Saving latest version of code to S3";
4826 0         0 unlink my $z = qq(/tmp/DataTableText/save/$0.zip); # Zip file
4827 0         0 makePath($z); # Zip file folder
4828 0 0       0 return undef unless confirmHasCommandLineCommand(q(zip)); # Confirm we have zip
4829 0         0 my $c = qq(zip -r $z $0); # Zip command
4830 0         0 print STDERR $_ for qx($c); # Zip file to be saved
4831              
4832 0 0       0 return undef unless confirmHasCommandLineCommand(q(aws)); # Confirm we have aws
4833 0         0 my $a = qq(aws s3 cp $z $aws); # Aws command
4834 0         0 my $r = qx($a); # Copy zip to S3
4835             #!$r or confess $r;
4836 0         0 writeFile($saveTime, time); # Save last save time
4837 0         0 lll "Saved latest version of code to S3";
4838 0         0 exit;
4839             }
4840             }
4841              
4842             sub addCertificate($) # Add a certificate to the current ssh session.
4843 0     0 1 0 {my ($file) = @_; # File containing certificate
4844 0 0       0 return undef unless confirmHasCommandLineCommand(q(ssh-add)); # Confirm we have ssh-add
4845 0         0 qx(ssh-add -t 100000000 $file 2>/dev/null);
4846             }
4847              
4848             my $hostName; # Host name cache.
4849             sub hostName # The name of the host we are running on.
4850 0 0   0 1 0 {return undef unless confirmHasCommandLineCommand(q(hostname)); # Confirm we have hostname
4851 0   0     0 $hostName //= trim(qx(hostname))
4852             }
4853              
4854             my $userid; # User name cache.
4855             sub userId(;$) # Get or confirm the userid we are currently running under.
4856 0     0 1 0 {my ($user) = @_; # Userid to confirm
4857 0 0 0     0 return $user if $user and $userid and $user eq $userid; # Confirm userid via cache
      0        
4858 0 0       0 return undef unless confirmHasCommandLineCommand(q(whoami)); # Confirm we have whoami
4859 0   0     0 $userid //= trim(qx(whoami)); # Cache result if necessary
4860 0 0 0     0 return undef if $user and $user ne $userid; # Confirm userid via latest value
4861 0         0 $userid
4862             }
4863              
4864             sub awsTranslateText($$$;$) # Translate B<$text> from English to a specified B<$language> using AWS Translate with the specified global B<$options> and return the translated string. Translations are cached in the specified B<$cacheFolder> for reuse where feasible.
4865 0     0 1 0 {my ($string, $language, $cacheFolder, $Options) = @_; # String to translate, language code, cache folder, aws global options string
4866              
4867             $language =~ m(\A(ar|zh|zh\-TW|cs|da|nl|en|fi|fr|de|he|id|it|ja|ko|pl|pt|ru|es|sv|tr)\Z)i or
4868             confess "Language code must be one of:\n".
4869 0 0       0 formatTable([map {split /\s+/, 2} split /\n/, <
  0         0  
4870             Arabic ar
4871             Chinese-Simplified zh
4872             Chinese-Traditional zh-TW
4873             Czech cs
4874             Danish da
4875             Dutch nl
4876             English en
4877             Finnish fi
4878             French fr
4879             German de
4880             Hebrew he
4881             Indonesian id
4882             Italian it
4883             Japanese ja
4884             Korean ko
4885             Polish pl
4886             Portuguese pt
4887             Russian ru
4888             Spanish es
4889             Swedish sv
4890             Turkish tr
4891             END
4892             <
4893             Language Name of the language
4894             Code Code used to describe language
4895             END
4896             );
4897 0         0 my $name = lc nameFromString($string); # Cache name from input string
4898 0         0 my $cached = fpe($cacheFolder, $language, $name, q(txt)); # Cache file
4899 0 0       0 return readFile($cached) if -e $cached; # Assume that what is in the cache file is a reasonable translation.
4900              
4901 0   0     0 my $options = $Options // '';
4902 0         0 my $c = <
4903             aws translate translate-text
4904             --text "$string"
4905             --source-language-code "en"
4906             --target-language-code "$language"
4907             --region "us-east-1"
4908             $options
4909             END
4910              
4911 0 0       0 if (my $J = qx($c)) # Translate
4912 0         0 {my $p = decodeJson($J); # Decode json response
4913 0 0       0 if (my $t = $p->{TranslatedText}) # Get translation
4914 0         0 {owf($cached, $t); # Cache result
4915 0         0 return $t; # Return translation
4916             }
4917             }
4918 0         0 confess "Unable to perform translation"; # No useful response from Aws
4919             }
4920              
4921             #D1 AWS parallel # Parallel computing across multiple instances running on L.
4922              
4923             my $onAws; # Cache results of L.
4924             sub onAws # Returns 1 if we are on AWS else return 0.
4925 252 100   252 1 3430 {return $onAws if defined $onAws;
4926 158 50       5530 $onAws = -e q(/home/ubuntu/) ? 1 : 0
4927             }
4928              
4929             sub onAwsPrimary # Return 1 if we are on L and we are on the primary session instance as defined by L, return 0 if we are on a secondary session instance, else return B if we are not on L.
4930 0 0   0 1 0 {return undef unless onAws; # Not on Aws
4931 0         0 my $i = &awsCurrentInstanceId; # Instance id
4932 0         0 my $I = &awsParallelPrimaryInstanceId; # Primary instance id
4933 0 0       0 $I eq $i ? 1 : 0
4934             }
4935              
4936             sub onAwsSecondary # Return 1 if we are on L but we are not on the primary session instance as defined by L, return 0 if we are on the primary session instance, else return B if we are not on L.
4937 0 0   0 1 0 {return undef unless onAws; # Not on Aws
4938 0         0 my $i = &awsCurrentInstanceId; # Instance id
4939 0         0 my $I = &awsParallelPrimaryInstanceId; # Primary instance id
4940 0 0       0 $I ne $i ? 1 : 0
4941             }
4942              
4943             sub awsParallelPrimaryInstanceId(%) # Return the instance id of the primary instance. The primary instance is the instance at L that we communicate with - it controls all the secondary instances that form part of the parallel session. The primary instance is located by finding the first running instance in instance Id order whose Name tag contains the word I. If no running instance has been identified as the primary instance, then the first viable instance is made the primary. The ip address of the primary is recorded in F so that it can be quickly reused by L, L, L etc. Returns the instanceId of the primary instance or B if no suitable instance exists.
4944 0     0 1 0 {my (%options) = @_; # Options
4945              
4946 0         0 my $d = awsEc2DescribeInstances(%options); # Available instances
4947 0         0 my @id; # Instance Ids
4948 0         0 for my $r($d->{Reservations}->@*) # Check instances for an existing primary instance
4949 0         0 {for my $i($r->{Instances}->@*)
4950 0 0       0 {if (my $s = $$i{State}{Name}) # Running instances
4951 0 0       0 {if ($s =~ m(running)i)
4952 0         0 {push @id, my $id = $$i{InstanceId};
4953 0         0 for my $t($$i{Tags}->@*) # Tags
4954 0 0       0 {if (my $v = $$t{Value})
4955 0 0       0 {if ($v =~ m(SessionLeader|Primary)i)
4956 0         0 {for my $I($i->{NetworkInterfaces}->@*) # Save first public Ip address in a well known location
4957 0         0 {my $ip = $$I{Association}{PublicIp};
4958 0         0 saveAwsIp($ip); # Save ip address
4959 0         0 last;
4960             }
4961 0         0 return $id; # Return existing primary instance
4962             }
4963             }
4964             }
4965             }
4966             }
4967             }
4968             }
4969              
4970 0 0       0 if (my ($id) = @id) # No instance marked as primary but running instances available
4971 0         0 {awsEc2Tag($id, Name=>q(Primary), %options);
4972 0         0 return $id;
4973             }
4974              
4975 0         0 confess "No instances running" # No running instances
4976             }
4977              
4978             sub awsParallelSpreadFolder($%) # On L: copies a specified B<$folder> from the primary instance, see: L, in parallel, to all the secondary instances in the session. If running locally: copies the specified folder to all L session instances both primary and secondary.
4979             {my ($folder, %options) = @_; # Fully qualified folder name, options
4980             -d $folder or confess "No such folder:\n$folder\n"; # Check source exists
4981             my $f = fpd($folder); # Normalize the folder name
4982              
4983             my sub spread(@) # Spread folder to the specified ip addresses
4984             {my (@i) = @_; # Ip addresses
4985             my @pid;
4986             for my $i(@i) # Each secondary
4987             {if (my $pid = fork)
4988             {push @pid, $pid;
4989             }
4990             else
4991             {makePathRemote($f, $i); # Create remote folder so rsync does not complain
4992             copyFolderToRemote($f, $i); # Copy folder to remote
4993             exit;
4994             }
4995             }
4996             waitpid $_, 0 for @pid;
4997             }
4998              
4999             if (onAwsPrimary) # Running on Aws primary - merge folders from secondary instances
5000             {spread(awsParallelSecondaryIpAddresses(%options));
5001             }
5002             elsif (!onAws) # Running locally - merge folders from all instances
5003             {spread(awsParallelIpAddresses(%options));
5004             }
5005             else # Unknown location
5006             {confess "Running somewhere other than locally or on aws primary\n";
5007             }
5008             }
5009              
5010             sub awsParallelGatherFolder($%) # On L: merges all the files in the specified B<$folder> on each secondary instance to the corresponding folder on the primary instance in parallel. If running locally: merges all the files in the specified folder on each L session instance (primary and secondary) to the corresponding folder on the local machine. The folder merges are done in parallel which makes it impossible to rely on the order of the merges.
5011             {my ($folder, %options) = @_; # Fully qualified folder name, options
5012             my $f = fpd($folder); # Normalize the folder name
5013             makePath($f); # Create target folder
5014              
5015             my sub gather(@) # Gather folder from specified ip addresses
5016             {my (@i) = @_; # Ip addresses
5017             my @pid;
5018             for my $i(@i) # Each secondary
5019             {if (my $pid = fork)
5020             {push @pid, $pid;
5021             }
5022             else
5023             {makePathRemote($f, $i); # Create remote folder so rsync does not complain
5024             mergeFolderFromRemote($f, $i); # Merge folder from remote
5025             exit;
5026             }
5027             }
5028             waitpid $_, 0 for @pid;
5029             }
5030              
5031             if (onAwsPrimary) # Running on Aws primary
5032             {gather(awsParallelSecondaryIpAddresses(%options));
5033             }
5034             elsif (!onAws) # Running locally
5035             {if (my $i = awsParallelPrimaryIpAddress(%options))
5036             {gather($i, awsParallelSecondaryIpAddresses(%options));
5037             }
5038             }
5039             else # Unknown location
5040             {confess "Running somewhere other than locally or on aws primary\n";
5041             }
5042             } # awsParallelGatherFolder
5043              
5044             sub awsParallelPrimaryIpAddress(%) # Return the IP addresses of any primary instance on L.
5045 0     0 1 0 {my (%options) = @_; # Options
5046              
5047 0         0 my $s = awsParallelPrimaryInstanceId(%options); # Instance id of primary instance
5048 0 0       0 if (my $instanceIds = awsEc2DescribeInstancesGetIPAddresses(%options)) # {instance id => instance ip }
5049 0         0 {return $$instanceIds{$s}; # Ip address of primary
5050             }
5051              
5052             undef
5053 0         0 }
5054              
5055             sub awsParallelSecondaryIpAddresses(%) # Return a list containing the IP addresses of any secondary instances on L.
5056 0     0 1 0 {my (%options) = @_; # Options
5057              
5058 0         0 my @i;
5059 0         0 my $s = awsParallelPrimaryInstanceId(%options); # Instance id of primary instance
5060 0 0       0 if (my $instanceIds = awsEc2DescribeInstancesGetIPAddresses(%options)) # {instance id => instance ip }
5061 0         0 {for my $id(sort keys %$instanceIds) # Each running instance
5062 0 0       0 {next if $id eq $s; # Skip primary instance
5063 0         0 push @i, $$instanceIds{$id}; # Save ip address of secondary instance
5064             }
5065             }
5066              
5067             @i # Ip addresses of any secondary instances
5068 0         0 }
5069              
5070             sub awsParallelIpAddresses(%) # Return the IP addresses of all the L session instances.
5071 0     0 1 0 {my (%options) = @_; # Options
5072              
5073 0         0 my @i;
5074 0         0 my $s = awsParallelPrimaryInstanceId(%options); # Instance id of primary instance
5075 0 0       0 if (my $instanceIds = awsEc2DescribeInstancesGetIPAddresses(%options)) # {instance id => instance ip }
5076 0         0 {for my $id(sort keys %$instanceIds) # Each running instance
5077 0         0 {push @i, $$instanceIds{$id}; # Save ip address of secondary instance
5078             }
5079             }
5080              
5081             @i # Ip addresses of all instances
5082 0         0 }
5083              
5084             sub getCodeContext($) # Recreate the code context for a referenced sub
5085 3     3 1 51 {my ($sub) = @_; # Sub reference
5086 3         57 my @l = readFile($0);
5087 3         54 my @c;
5088 3         42 for my $i(keys @l)
5089 33         78 {my $l = $l[$i];
5090 33 50 66     2745 last if $i and $l =~ m/\A#!/;
5091 33 100       270 push @c, $l if $l =~ m/\A(#!|use )/;
5092             }
5093 3 50 33     45 if ($0 =~ m(\.pm\Z)i and $0 !~ m(DataTableText)i) # If we were started from a pm file we include the pm file as well as there will be no "use" to bring it in. "do" is use in preference to "use" as we want the same context as if we were in the module
5094 0         0 {push @c, <
5095             if (1)
5096             {use Data::Table::Text qw(readFile);
5097             my \$s = Data::Table::Text::readFile(q($0));
5098             eval \$s;
5099             confess "\$s\n\$@\n" if \$@;
5100             }
5101             END
5102             }
5103 3         135 join q(), @c;
5104             }
5105              
5106             sub awsParallelProcessFiles($$$$%) #I Process files in parallel across multiple L instances if available or in series if not. The data located by B<$userData> is transferred from the primary instance, as determined by L, to all the secondary instances. B<$parallel> contains a reference to a sub, parameterized by array @_ = (a copy of the user data, the name of the file to process), which will be executed upon each session instance including the primary instance to update $userData. B<$results> contains a reference to a sub, parameterized by array @_ = (the user data, an array of results returned by each execution of $parallel), that will be called on the primary instance to process the results folders from each instance once their results folders have been copied back and merged into the results folder of the primary instance. $results should update its copy of $userData with the information received from each instance. B<$files> is a reference to an array of the files to be processed: each file will be copied from the primary instance to each of the secondary instances before parallel processing starts. B<%options> contains any parameters needed to interact with L via the L. The returned result is that returned by sub $results.
5107 0     0 1 0 {my ($userData, $parallel, $results, $files, %options) = @_; # User data or undef, parallel sub reference, series sub reference, [files to process], aws cli options.
5108 0   0     0 $userData //= {}; # Default value for user data else storable will complain
5109 0         0 my $d = temporaryFolder; # Temporary folder containing a description of what needs to be done
5110 0         0 my $r = fpd($d, q(out)); # Results folder
5111              
5112 0   0     0 $options{region} //= awsCurrentRegion; # Default region
5113              
5114 0 0 0     0 if (onAws and my @i = awsParallelSecondaryIpAddresses(%options)) # Process across multiple session instances on AWS
5115 0         0 {my @buckets = packBySize(@i+0, map {[fileSize($_), $_]} @$files); # Pack files into buckets for each secondary instance
  0         0  
5116              
5117 0         0 for my $i(keys @i) # Each other session instance
5118 0         0 {storeFile(my $f = fpe($d, $i[$i], qw(files data)), $buckets[$i]); # Save files to be processed on each of the other session instance
5119             }
5120              
5121 0         0 my $parallelSubName = join '::', (getSubName($parallel))[0,1]; # Get name of parallel sub
5122 0         0 my $resultsSubName = join '::', (getSubName($results)) [0,1]; # Get name of results sub
5123 0         0 my $codeContext = getCodeContext($parallel); # Get context of parallel sub
5124              
5125 0         0 my $userDataFile = fpe($d, qw(user data)); # Save user data in this file so we get a fresh copy each time effectively making it read only
5126 0         0 storeFile($userDataFile, $userData); # Save user data
5127              
5128 0         0 my $c = writeFile(fpe($d, qw(code pl)), <
5129             $codeContext
5130              
5131             my \$folder = fp(\$0);
5132             my \$files = retrieveFile(fpe(\$folder, awsCurrentIp, qw(files data)));
5133             my \$userData = retrieveFile(fpe(\$folder, qw(user data)));
5134              
5135             processFilesInParallel
5136             (sub
5137             {my (\$file) = \@_;
5138             $parallelSubName(\$userData, \$file)
5139             },
5140             sub
5141             {my \$r = $resultsSubName(\$userData, \@_);
5142             my \$f = fpe(q($r), awsCurrentIp, q(data));
5143             storeFile(\$f, \$r);
5144             \$r
5145             },
5146             @\$files,
5147             );
5148             END
5149              
5150 0         0 xxx qq(perl -c $c), qr(syntax OK); # Syntax perl code before we ship it off to the secondary instances for execution
5151 0         0 awsParallelSpreadFolder($d, %options); # Save processing request to on each of the other session instance
5152              
5153 0         0 if (1) # Spread folders containing all the input files to be processed in parallel across each of the secondary instances so that they have a complete copy of the data to be processed
5154 0         0 {my %f = map {fp($_)=>1} @$files;
  0         0  
5155             processFilesInParallel(sub
5156 0     0   0 {my ($f) = @_;
5157 0         0 awsParallelSpreadFolder($f, %options);
5158             },
5159 0         0 undef, sort keys %f);
5160             }
5161              
5162 0         0 my @c; # Commands to process on each of the secondary instances
5163 0         0 for my $i(@i) # Each of the secondary instances available for processing
5164 0         0 {push @c, <
5165             ssh $i "perl $c 2>&1" ;
5166             rsync -mpqrt '$i:$d' '$d' 2>&1
5167             END
5168             }
5169              
5170 0 0       0 if (my $pid = fork) # Parent: merge results from each secondary instance
5171 0         0 {waitpid $pid, 0; # Wait for the secondary instances to finish
5172             return &$results($userData, # Combine primary instance and secondary instance results with the user data
5173 0         0 map {retrieveFile($_)} searchDirectoryTreesForMatchingFiles($r)); # Merge data from each secondary instances
  0         0  
5174             }
5175             else # Child: Execute on the secondary instances in parallel
5176 0         0 {my $cmd = join ' & ', map {qq/( $_ )/} @c;
  0         0  
5177             #lll $cmd;
5178 0         0 lll qx($cmd);
5179 0         0 exit;
5180             }
5181             }
5182              
5183             else # Run on local computer or on a single Aws instance
5184             {return processFilesInParallel # Process bucket[0] on primary instance
5185             (sub
5186 0     0   0 {my ($file) = @_;
5187 0         0 &$parallel($userData, $file)
5188             },
5189             sub
5190 0     0   0 {&$results($userData, @_);
5191             },
5192 0         0 @$files,
5193             );
5194             }
5195             } # awsParallelProcessFiles
5196              
5197             sub awsParallelProcessFilesTestParallel($$) #P Test running on L in parallel.
5198 0     0 1 0 {my ($userData, $file) = @_; # User data, file to process.
5199 0   0     0 my $i = &awsCurrentIp||q(localHost);
5200 0         0 $userData->{files}{$file} = fileMd5Sum($file);
5201 0         0 $userData->{ip} {$i} = 1; # UserData is reused each time so we cannot ++
5202 0         0 $userData->{ipFile}{$i}{$file}++;
5203 0         0 $userData;
5204             }
5205              
5206             sub awsParallelProcessFilesTestResults($@) #P Test results of running on L in parallel.
5207 0     0 1 0 {my ($userData, @results) = @_; # User data from primary instance instance or process, results from each parallel instance or process
5208              
5209 0         0 for my $x(@results)
5210 0         0 {for my $f(sort keys $x->{files}->%*)
5211 0         0 {$userData->{files}{$f} = $x->{files}{$f};
5212             }
5213 0         0 for my $i(sort keys $x->{ip}->%*)
5214 0         0 {$userData->{ip}{$i} += $x->{ip}{$i};
5215             }
5216 0         0 for my $i(sort keys $x->{ipFile} ->%*)
5217 0         0 {for my $f(sort keys $x->{ipFile}{$i}->%*)
5218 0         0 {$userData->{ipFile}{$i}{$f} = $x->{ipFile}{$i}{$f};
5219             }
5220             }
5221 0   0     0 $userData->{merge} += $x->{merge}//0; # Merges done else where
5222             }
5223              
5224 0         0 $userData->{merge}++; # This merge
5225 0         0 $userData
5226             }
5227              
5228             #D1 S3 # Work with S3 as if it were a file system.
5229              
5230             sub s3Profile(%) #P Return an S3 profile keyword from an S3 option set
5231 0     0 1 0 {my (%options) = @_; # Options
5232 0         0 my $p = $options{profile}; # Profile option
5233 0 0       0 $p ? qq( --profile $p) : q() # Return profile keyword if profile specified
5234             }
5235              
5236             sub s3Delete(%) #P Return an S3 --delete keyword from an S3 option set
5237 0     0 1 0 {my (%options) = @_; # Options
5238 0         0 my $p = $options{delete}; # Delete option
5239 0 0       0 $p ? qq( --delete) : q() # Return delete keyword if profile specified
5240             }
5241              
5242             sub s3ListFilesAndSizes($%) # Return {file=>size} for all the files in a specified B<$folderOrFile> on S3 using the specified B<%options> if any.
5243 0     0 1 0 {my ($folderOrFile, %options) = @_; # Source on S3 - which will be truncated to a folder name, options
5244 0         0 my ($bucket, $folder) = parseS3BucketAndFolderName($folderOrFile); # Parse an L bucket/folder name into a bucket and a folder name removing any initial s3://.
5245 0         0 my $profile = s3Profile(%options); # Add profile if specified
5246 0         0 my $getCmd = qq(aws s3 ls s3://$bucket/$folder $profile --recursive); # Command to get the sizes of the files to download
5247 0         0 my $files = qx($getCmd); # Get the sizes of the files to download
5248 0         0 my @files = map {my @a = split m/\s+/, $_, 4; [@a[-1, -2, 0, 1]]} # Files and sizes
  0         0  
  0         0  
5249             split m/\n/, $files;
5250 0         0 {map {q(s3://).fpf($bucket, $$_[0]) => $_} @files} # Hash {file=>[name, size, modified date, modified time]}
  0         0  
  0         0  
5251             }
5252              
5253             sub s3FileExists($%) # Return (name, size, date, time) for a B<$file> that exists on S3 else () using the specified B<%options> if any.
5254 0     0 1 0 {my ($file, %options) = @_; # File on S3 - which will be truncated to a folder name, options
5255 0         0 my %files = s3ListFilesAndSizes($file, %options); # Details of files with that prefix
5256 0 0       0 return () unless keys %files == 1; # Only one file expected
5257 0         0 my ($f) = keys %files; # File name
5258 0         0 my $d = $files{$f}; # Details of the one file
5259 0 0       0 return () unless $$d[3]; # All details present
5260 0         0 @$d # Return details of one file
5261             }
5262              
5263             sub s3WriteFile($$%) # Write to a file B<$fileS3> on S3 the contents of a local file B<$fileLocal> using the specified B<%options> if any. $fileLocal will be removed if %options contains a key cleanUp with a true value.
5264 0     0 1 0 {my ($fileS3, $fileLocal, %options) = @_; # File to write to on S3, string to write into file, options
5265 0         0 my ($bucket, $folder) = parseS3BucketAndFolderName($fileS3); # Parse an L bucket/folder name into a bucket and a folder name removing any initial s3://.
5266 0         0 my $profile = s3Profile(%options); # Add profile if specified
5267 0         0 my $f = pad($fileLocal, 32);
5268 0         0 my $s = pad(qq(s3://$bucket/$folder), 32);
5269 0         0 my $cmd = qq(aws s3 cp $f $s $profile --quiet); # Command to write the temporary file into S3 with the specified file name
5270 0         0 xxx $cmd; # Execute and print command
5271             # unlink $fileLocal if $options{cleanUp}; # Remove local file after upload if requested
5272             }
5273              
5274             sub s3WriteString($$%) # Write to a B<$file> on S3 the contents of B<$string> using the specified B<%options> if any.
5275 0     0 1 0 {my ($file, $string, %options) = @_; # File to write to on S3, string to write into file, options
5276 0         0 my ($bucket, $folder) = parseS3BucketAndFolderName($file); # Parse an L bucket/folder name into a bucket and a folder name removing any initial s3://.
5277 0         0 my $profile = s3Profile(%options); # Add profile if specified
5278 0         0 my $temp = writeFile(undef, $string); # Write the string to a temporary file
5279 0         0 my $f = pad($temp, 32);
5280 0         0 my $s = pad(qq(s3://$bucket/$folder), 32);
5281 0         0 my $cmd = qq(aws s3 cp $f $s $profile --quiet); # Command to write the temporary file into S3 with the specified file name
5282 0         0 xxx $cmd; # Execute and print command
5283 0         0 unlink $temp;
5284             }
5285              
5286             sub s3ReadFile($$%) # Read from a B<$file> on S3 and write the contents to a local file B<$local> using the specified B<%options> if any. Any pre existing version of the local file $local will be deleted. Returns whether the local file exists after completion of the download.
5287 0     0 1 0 {my ($file, $local, %options) = @_; # File to read from on S3, local file to write to, options
5288 0         0 my ($bucket, $folder) = parseS3BucketAndFolderName($file); # Parse an L bucket/folder name into a bucket and a folder name removing any initial s3://.
5289 0         0 my $profile = s3Profile(%options); # Add profile if specified
5290 0 0       0 my $quiet = $file =~ m(pcd\Z)i ? q() : q( --quiet); # Watch certain important files
5291 0         0 my $d = temporaryFolder;
5292 0         0 my $F = fpe(temporaryFile, qw(download txt));
5293 0         0 my $f = pad($F, 32);
5294 0         0 my $s = pad(qq(s3://$bucket/$folder), 32);
5295 0         0 my $cmd = qq(aws s3 cp $s $f $profile $quiet); # Command to write the temporary file into S3 with the specified file name
5296 0         0 lll $cmd;
5297 0         0 xxx $cmd; # Download
5298 0         0 moveFileWithClobber($f, $local); # Update local file if a file was in fact downloaded
5299 0         0 clearFolder($d, 11);
5300 0         0 -f $local
5301             }
5302              
5303             sub s3ReadString($%) # Read from a B<$file> on S3 and return the contents as a string using specified B<%options> if any. Any pre existing version of $local will be deleted. Returns whether the local file exists after completion of the download.
5304 0     0 1 0 {my ($file, %options) = @_; # File to read from on S3, options
5305 0         0 my ($bucket, $folder) = parseS3BucketAndFolderName($file); # Parse an L bucket/folder name into a bucket and a folder name removing any initial s3://.
5306 0         0 my $profile = s3Profile(%options); # Add profile if specified
5307 0         0 my $local = temporaryFile; # Temporary file to hold download
5308 0         0 my $f = pad($local, 32);
5309 0         0 my $s = pad(qq(s3://$bucket/$folder), 32);
5310 0         0 my $cmd = qq(aws s3 cp $s $f $profile --quiet); # Command to write the temporary file into S3 with the specified file name
5311 0         0 xxx $cmd; # Execute and print command
5312 0 0       0 if (-f $local) # Retrieve string from temporary file
5313 0         0 {my $s = readFile($local); # Read temporary file
5314 0         0 unlink $local; # Remove temporary file
5315 0         0 return $s; # Return contend downloaded from S3
5316             }
5317             undef # No such file accessible on S3
5318 0         0 }
5319              
5320             sub s3DownloadFolder($$%) # Download a specified B<$folder> on S3 to a B<$local> folder using the specified B<%options> if any. Any existing data in the $local folder will be will be deleted if delete=>1 is specified as an option. Returns B else the name of the B<$local> on success.
5321 0     0 1 0 {my ($folder, $local, %options) = @_; # Folder to read from on S3, local folder to write to, options
5322 0         0 $folder =~ s(\As3://) (); # Normalize folder name
5323 0         0 makePath($local); # Create local path if necessary
5324 0         0 my $profile = s3Profile(%options); # Add profile if specified
5325 0         0 my $delete = s3Delete (%options); # Add delete if specified
5326 0         0 my $f = pad($local, 32);
5327 0         0 my $s = pad(qq(s3://$folder), 32);
5328 0         0 my $cmd = qq(aws s3 sync $s $f $profile $delete); # Command to copy the folder on S3 to the local folder
5329 0         0 xxx $cmd; # Download
5330 0         0 -f $local # Test for local file after download
5331             }
5332              
5333             sub s3ZipFolder($$%) # Zip the specified B<$source> folder and write it to the named B<$target> file on S3.
5334 0     0 1 0 {my ($source, $target, %options) = @_; # Source folder, target file on S3, S3 options
5335 0 0       0 unless(-d $source) # Check the folder exists
5336 0         0 {confess "No such folder: $source";
5337             }
5338 0 0       0 return undef unless confirmHasCommandLineCommand(q(zip)); # Confirm we have zip
5339 0         0 my $z = fpe(temporaryFile, q(zip)); # Local zip file
5340 0         0 my $c = qq(cd $source; zip -qr $z .); # Zip command
5341 0         0 xxx $c, qr(\A\s*\Z);
5342 0         0 my $r = s3WriteFile($target, $z, %options); # Upload to S3
5343 0         0 unlink $z;
5344 0         0 $r
5345             }
5346              
5347             sub s3ZipFolders($%) # Zip local folders and upload them to S3 in parallel. B<$map> maps source folder names on the local machine to target folders on S3. B<%options> contains any additional L cli options.
5348 0     0 1 0 {my ($map, %options) = @_; # Source folder to S3 mapping, S3 options
5349              
5350             &runInParallel(&numberOfCpus(8), sub # Upload in parallel
5351 0     0   0 {my ($r) = @_;
5352 0         0 &s3ZipFolder(@$r, %options);
5353             },
5354       0     sub {},
5355 0         0 map{[$_, $$map{$_}]} sort keys %$map);
  0         0  
5356             }
5357              
5358             #D1 GitHub # Simple interactions with L - for more complex interactions please use L.
5359              
5360             sub downloadGitHubPublicRepo($$) # Get the contents of a public repo on GitHub and place them in a temporary folder whose name is returned to the caller or confess if no such repo exists.
5361 0     0 1 0 {my ($user, $repo) = @_; # GitHub user, GitHub repo
5362 0         0 my $t = temporaryFolder; # Folder to download to
5363 0         0 my $z = fpe($t, qw(gh zip)); # Zip file
5364 0         0 my $s = fpe(q(https://github.com/), $user, $repo, qw(archive master zip)); # L to GitHub to retrieve zipped repository
5365 0         0 confirmHasCommandLineCommand(q(wget)); # Conform we have wget
5366 0         0 my $d = xxx qq(wget -O $z $s), qr(200 OK); # Run download
5367 0 0 0     0 $d =~ m(ERROR 404: Not Found)s || !-e $z || fileSize($z) < 1e2 and # Make sure we got a zip file
      0        
5368             confess "No such user/repo on GitHub or repo too small:\n$d\n";
5369 0         0 xxx qq(cd $t; unzip $z; rm $z; ls -lah), qr(); # Unzip the zip file
5370 0         0 $t # Return the folder containing the unzipped files
5371             }
5372              
5373             sub downloadGitHubPublicRepoFile($$$) # Get the contents of a B<$user> B<$repo> B<$file> from a public repo on GitHub and return them as a string.
5374 0     0 1 0 {my ($user, $repo, $file) = @_; # GitHub user, GitHub repository, file name in repository
5375 0         0 my $s = fpf(q(https://raw.githubusercontent.com/), $user, $repo, q(master), $file);
5376 0         0 my $t = temporaryFile; # File to download into
5377 0         0 my $d = xxx qq(wget -O $t $s), qr(200 OK); # Run download
5378 0 0       0 $d =~ m(ERROR 404: Not Found)s and # Make sure we got the file
5379             confess "No such user/repo/file on GitHub:\n$d\n";
5380 0 0       0 -f $t or confess "No output from user/repo/file on GitHub"; # Check we got a result
5381 0         0 my $r = readFile($t); # Read results
5382 0         0 unlink $t; # Remove temporary output file
5383 0         0 $r # Return data read from github
5384             }
5385              
5386             #D1 Processes # Start processes, wait for them to terminate and retrieve their results
5387              
5388             sub startProcess(&\%$) # Start new processes while the number of child processes recorded in B<%$pids> is less than the specified B<$maximum>. Use L to wait for all these processes to finish.
5389 0     0 1 0 {my ($sub, $pids, $maximum) = @_; # Sub to start, hash in which to record the process ids, maximum number of processes to run at a time
5390 0         0 warn "Deprecated in favor of newProcessStarter";
5391 0         0 while(keys(%$pids) >= $maximum) # Wait for enough processes to terminate to bring us below the maximum number of processes allowed.
5392 0         0 {my $p = waitpid 0,0;
5393             # $$pids{$p} or confess "Pid $p not defined in ".dump($pids)."\n";
5394 0         0 delete $$pids{$p}
5395             }
5396              
5397 0 0       0 if (my $pid = fork) # Create new process
5398 0         0 {$$pids{$pid}++ # Update pids
5399             }
5400             else # Run sub in new process
5401 0         0 {&$sub;
5402 0         0 exit;
5403             }
5404             }
5405              
5406             sub waitForAllStartedProcessesToFinish(\%) # Wait until all the processes started by L have finished.
5407 0     0 1 0 {my ($pids) = @_; # Hash of started process ids
5408 0         0 warn "Deprecated in favor of newProcessStarter";
5409 0         0 while(keys %$pids) # Remaining processes
5410 0         0 {my $p = waitpid 0,0;
5411             # $$pids{$p} or cluck "Pid $p not defined in ".dump($pids)."\n";
5412 0         0 delete $$pids{$p}
5413             }
5414             }
5415              
5416             sub newProcessStarter($%) # Create a new L with which to start parallel processes up to a specified B<$maximumNumberOfProcesses> maximum number of parallel processes at a time, wait for all the started processes to finish and then optionally retrieve their saved results as an array from the folder named by B<$transferArea>.
5417 1193     1193 1 11712 {my ($maximumNumberOfProcesses, %options) = @_; # Maximum number of processes to start, options
5418 1193   50     24403 my $h = genHash(q(Data::Table::Text::Starter), # Process starter definition.
5419             transferArea => temporaryFolder, # The name of the folder in which files transferring results from the child to the parent process will be stored.
5420             autoRemoveTransferArea => 1, # If true then automatically clear the transfer area at the end of processing.
5421             maximumNumberOfProcesses => $maximumNumberOfProcesses // 8, # The maximum number of processes to start in parallel at one time. If this limit is exceeded, the start of subsequent processes will be delayed until processes started earlier have finished.
5422             pids => {}, # A hash of pids representing processes started but not yet completed.
5423             resultsArray => [], # Consolidated array of results.
5424             processingTitle => undef, #I Optional: title describing the processing being performed.
5425             processingLogFile => undef, #I Optional: name of a file to which process start and end information should be appended
5426             processingLogFileHandle => undef, # Handle for log file if a log file was supplied
5427             totalToBeStarted => undef, #I Optionally: the total number of processes to be started - if this is supplied then an estimate of the finish time for this processing is printed to the log file every time a process starts or finishes.
5428             processStartTime => {}, # {pid} == time the process was started.
5429             processFinishTime => {}, # {pid} == time the process finished.
5430             startTime => time, # Start time
5431             );
5432              
5433 1193         41978 loadHash($h, %options); # Load and validate the options
5434             }
5435              
5436             sub Data::Table::Text::Starter::logEntry($$) #P Create a log entry showing progress and eta.
5437 130132     130132   5142326 {my ($starter, $finish) = @_; # Starter, 0 - start; 1 - finish
5438 130132 100       6355396 if (my $l = $starter->processingLogFile) # Write a log entry if a log file has been supplied
5439 63206   50     2120733 {my $t = $starter->processingTitle // ''; # Title of processing
5440 63206 100       524630 my $sf = $finish ? q(F) : q(S); # Whether we are starting or finishing
5441 63206         2258186 my $N = $starter->totalToBeStarted; # Total number to be started if known
5442 63206   50     1615797 my $M = $starter->maximumNumberOfProcesses // 1; # Maximum number of processes in parallel
5443 63206         126513 my $started = keys %{$starter->processStartTime}; # Number of processes started
  63206         1330940  
5444 63206         116837 my $finished = keys %{$starter->processFinishTime}; # Number of processes finished
  63206         1838284  
5445              
5446 63206 100 100     1052736 if (!$finish and $started == 1 and $t) # Title message
      66        
5447 367 50       10643 {my $n = $N ? qq(Start $N processes in parallel upto $M for:) :
5448             qq(Process in parallel upto $M:);
5449 367         17616 $starter->say(join " ", timeStamp, "$n $t");
5450             }
5451              
5452             my $eta = sub # Estimate finish time
5453 63206 100 66 63206   516291 {if ($N and $finished) # Expected number of starts has been supplied and at least one process has finished
5454 61744         425059 {my $avgExecTime = $starter->averageProcessTime; # Average execution time process
5455 61744         264681 my $toGo = ($N - $finished) * $avgExecTime / $M; # Time to go not with standing Amdahl's law.
5456 61744         3414787 my @finishAt = localtime(time + $toGo); # Finish time
5457 61744         2224922 my $finishTime = strftime('%H:%M:%S', @finishAt); # Format finish time
5458 61744         1465162 return sprintf("eta: %.2f seconds at $finishTime", $toGo); # Finish time message
5459             }
5460             q() # No estimate available for finish time
5461 63206         2279747 }->();
  1462         17917  
5462              
5463 63206 50       676677 my $w = $N ? length($N) : 0; # Width of output field
5464 63206 100       623604 my $p = $N == 0 ? q() : # Progress indicator
    50          
5465             sprintf("%${w}d", $finish ? $finished : $started).q(/).$N;
5466              
5467 63206         775615 $starter->say(join " ", timeStamp, $sf, $p, $eta, $t);
5468             }
5469             }
5470              
5471             sub Data::Table::Text::Starter::averageProcessTime($) #P Average elapsed time spent by each process
5472 62012     62012   157654 {my ($starter) = @_; # Starter
5473 62012         129426 my $execTime = 0; # Total execution time for all processes that have finished so far
5474 62012         102288 for my $finish(sort keys %{$starter->processFinishTime}) # Sum execution time over all processes that have finished
  62012         1456745  
5475 2927544   50     73153064 {my $f = $starter->processFinishTime->{$finish} // 0; # Finish time
5476 2927544   50     50860414 my $s = $starter->processStartTime ->{$finish} // 0; # Start time
5477 2927544         4747149 $execTime += $f - $s; # Execution time
5478             }
5479 62012   50     280239 my $finished = keys %{$starter->processFinishTime} || 1; # Number of processes finished
5480 62012         231890 $execTime / $finished; # Average execution time process
5481             }
5482              
5483             sub Data::Table::Text::Starter::say($@) #P Write to the log file if it is available.
5484 63841     63841   500350 {my ($starter, @message) = @_; # Starter, text to write to log file.
5485 63841 50       1930280 return unless my $F = $starter->processingLogFileHandle; # Number of processes started
5486 63841         1837824 flock($F, 2);
5487 63841         205918 print {$F} join '', @message, "\n";
  63841         2574658  
5488             }
5489              
5490             sub Data::Table::Text::Starter::start($$) # Start a new process to run the specified B<$sub>.
5491 67812     67812   332935 {my ($starter, $sub) = @_; # Starter, sub to be run.
5492              
5493 67812         133983 my $started = keys %{$starter->processStartTime}; # Number of processes started
  67812         1680923  
5494              
5495 67812 100       494244 if ($started == 0) # Create a log file if logging requested and no processes have been started yet
5496 1193 100       33524 {if (my $file = $starter->processingLogFile)
5497 368         4784 {makePath($file);
5498 368 50       53360 open my $F, ">>$file" or
5499             confess "Cannot open file for write, file:\n$file\n$!\n";
5500 368         4784 binmode($F, ":utf8");
5501 368         14720 $starter->processingLogFileHandle = $F;
5502             }
5503             }
5504              
5505 67812         160992 while(keys(%{$starter->pids}) >= $starter->maximumNumberOfProcesses) # Wait for enough processes to terminate to bring us below the maximum number of processes allowed.
  119330         2632960  
5506 51518         661954 {$starter->waitOne;
5507             }
5508              
5509 67812 100       114717942 if (my $pid = fork) # Create new process
5510 67451         1605679 {my $startTime = time;
5511 67451         19129635 $starter->pids->{$pid}++; # Update pids
5512 67451         3984942 $starter->processStartTime->{$pid} = time; # Time process was started
5513 67451         1886551 $starter->logEntry; # Write a log entry
5514             }
5515             else # Run sub in new process
5516             {#setpriority(0, 0, +1); # Run at a slightly lower priority to make sure the parent can reap zombies as quickly as possible - questionable and does not work on "haiku"
5517 361         137111 my $results = &$sub; # Execute sub and address results
5518 361 50       38847 if (my $t = $starter->transferArea) # Transfer folder
5519 361         78643 {my $f = fpe($t, $$, q(data)); # Transfer file in transfer folder
5520 361         113329 makePath($f); # Make path for transfer file folder
5521 361         4221 eval {store [$results], $f}; # Store data
  361         103479  
5522 361 50       715579 $@ and confess "$@\n"; # Confess to any errors
5523             }
5524 361         19286385 exit;
5525             }
5526             }
5527              
5528             sub Data::Table::Text::Starter::waitOne($) #P Wait for at least one process to finish and consolidate its results.
5529 61653     61653   8974230 {my ($starter) = @_; # Starter
5530 61653         15943820 my $select = 0; # Must wait for at least one process to finish
5531 61653         12290532 my $startTime = time;
5532              
5533 61653   100     126953 while(keys(%{$starter->pids}) and my $p = waitpid 0, $select) # Wait for a process to finish - get its pid
  124334         14702206  
5534 62681 50       188571327 {if ($starter->pids->{$p}) # One of ours and it has data to transfer
5535 62681 50       61656097 {if (my $t = $starter->transferArea) # Transfer folder
5536 62681         37417306 {my $f = fpe($t, $p, q(data)); # Transfer file in transfer folder
5537 62681 50       29678673 if (-e $f)
5538 62681         98589407 {my $size = fileSize($f);
5539 62681         68970237 my $big = $size > 1e9;
5540 62681 50       10450469 lll "Retrieve $f start size=$size " if $big;
5541 62681 50       3533054 if (my $d = eval {retrieve $f}) # Retrieve data
  62681         183375615  
5542 62681 50       613780512 {if (ref($d) =~ m(array)is) # Check we got an array reference
5543 62681 50       11509277 {if (@$d == 1) # array should have just one element
5544 62681         5178356 {push @{$starter->resultsArray}, $$d[0]; # Save data in parent
  62681         60175967  
5545             }
5546             else
5547 0         0 {confess "Too many process results returned";
5548             }
5549             }
5550             else
5551 0         0 {confess "Expected an of process array";
5552             }
5553             }
5554             else
5555 0         0 {cluck "Unable to retrieve process results";
5556             }
5557 62681 50       4845348 mmm "Retrieve $f end" if $big;
5558             }
5559             else
5560 0         0 {die "No such process file: $f\n";
5561             }
5562             }
5563             }
5564              
5565 62681         68355755 $starter->processFinishTime->{$p} = time; # Approximate time process ended
5566 62681         67398501 $starter->logEntry(1); # Write a log entry
5567 62681         8435418 delete $starter->pids->{$p}; # Remove pid from consideration
5568 62681         9061703 $select = WNOHANG; # Subsequent waits do not, in fact, wait - if more finished processes are immediately available then they will be harvested, otherwise no outstanding finished processes are available to harvest and the while loop terminates.
5569             }
5570             }
5571              
5572             sub Data::Table::Text::Starter::finish($) # Wait for all started processes to finish and return their results as an array.
5573 832     832   11025 {my ($starter) = @_; # Starter
5574              
5575 832         5042 while(keys(%{$starter->pids}) > 0) # Wait for all started processes to terminate
  10967         4049118  
5576 10135         20194946 {$starter->waitOne;
5577             }
5578              
5579 832         53427 my @r = @{$starter->resultsArray}; # Return results
  832         21831  
5580              
5581 832 100       20987 if (my $l = $starter->processingLogFile) # Log file provided
5582 268   50     10184 {my $t = $starter->processingTitle // ''; # Title of processing
5583 268         91120 my $N = $starter->totalToBeStarted; # Total number to be started if known
5584 268         3484 my $started = keys %{$starter->processStartTime}; # Number of processes started
  268         7236  
5585 268         2680 my $finished = keys %{$starter->processFinishTime}; # Number of processes finished
  268         8308  
5586              
5587 268         2680 my @m;
5588 268 50       4288 if ($t)
5589 268         4020 {push @m, timeStamp. " Finished $finished processes for: $t"
5590             }
5591             else
5592 0         0 {push @m, timeStamp. " Finished $finished processes"
5593             }
5594              
5595 268         10720 push @m, "Elapsed time: ".
5596             sprintf("%.2f seconds", time - $starter->startTime);
5597              
5598 268         3752 push @m, "Average process execution time: ".
5599             sprintf("%.2f seconds", $starter->averageProcessTime);
5600              
5601 268         3216 my $but = qq(Started $started processes but); # Complain if not enough processes finished
5602 268 50       5628 if ($started != @r)
5603 0         0 {my $r = @r;
5604 0         0 push @m, "$but only received results from $r";
5605             }
5606 268 50       3752 if ($started != $finished)
5607 0         0 {push @m, "$but only $finished finished";
5608             }
5609 268 50       3752 if ($started != $N)
5610 0         0 {push @m, "$but totalToBeStarted=>$N was specified";
5611             }
5612 268 50       8308 if (my $F = $starter->processingLogFileHandle) # Log
5613 268         3484 {$starter->say(join "\n", @m); # Log message
5614 268         7772 $starter->processingLogFileHandle = undef;
5615 268         8576 close($F); # Close log
5616             }
5617             }
5618              
5619 832 50       88215 if ($starter->autoRemoveTransferArea) # Clear the transfer area if requested
5620 832         19634 {clearFolder($starter->transferArea, scalar(@r)+1)
5621             }
5622              
5623             @r # Return results
5624 832         88580 }
5625              
5626             sub squareArray(@) # Create a two dimensional square array from a one dimensional linear array.
5627 6985     6985 1 21580 {my (@array) = @_; # Array
5628 6985         12915 my $N = @array; # Size of linear array
5629 6985         15595 my $n = int sqrt $N; # Dimension of square array
5630 6985 100       16718 ++$n unless $n*$n == $N; # Adjust up unless perfect square
5631 6985         9397 my @s; # Square array
5632 6985         8074 my $i = 0; my $j = 0; # Current coordinates in square array
  6985         9231  
5633 6985         13804 for my $e(@array) # Load square array from linear array
5634 164604         191633 {$s[$j][$i] = $e; # Current element
5635 164604         170887 ++$i; # Next minor coordinate
5636 164604 100       234857 ++$j, $i = 0 if $i >= $n; # Next major coordinate
5637             }
5638             @s # Resulting square array
5639 6985         26434 }
5640              
5641             sub deSquareArray(@) # Create a one dimensional array from a two dimensional array of arrays
5642 6299     6299 1 15683 {my (@square) = @_; # Array of arrays
5643 6299         10086 my @a;
5644 6299         11222 for my $r(@square) # Each row
5645 29465 50       177013 {ref($r) =~ m(array)is or confess "Not an array reference";
5646 29465         65893 push @a, @$r; # Push row contents
5647             }
5648             @a # Linear array
5649 6299         44164 }
5650              
5651             sub countSquareArray(@) #P Count the number of elements in a square array
5652 268     268 1 804 {my (@square) = @_; # Array of arrays
5653 268         536 my $a = 0;
5654 268         2412 for my $r(@square) # Each row
5655 4020 50       10720 {ref($r) =~ m(array)is or confess "Not an array reference";
5656 4020         5092 $a += scalar(@$r); # Push row contents
5657             }
5658             $a # Count
5659 268         1608 }
5660              
5661             sub rectangularArray($@) # Create a two dimensional rectangular array whose first dimension is B<$first> from a one dimensional linear array.
5662 804     804 1 2948 {my ($first, @array) = @_; # First dimension size, array
5663 804         1608 my $N = @array; # Size of linear array
5664 804 50       2680 return @array if $N < 2; # Data is already a 1 x N rectangle
5665 804         1608 my @r; # Rectangular array
5666 804         2680 for my $i(keys @array) # Load rectangular array from linear array
5667 9648         19028 {push $r[$i % $first]->@*, $array[$i];
5668             }
5669              
5670             @r # Resulting rectangular array
5671 10452         5896 }
5672              
5673             sub rectangularArray2($@) # Create a two dimensional rectangular array whose second dimension is B<$second> from a one dimensional linear array.
5674 821     821 1 3186 {my ($second, @array) = @_; # Second dimension size, array
5675 821         1459 my $N = @array; # Size of linear array
5676 821         1476 my @r; # Rectangular array
5677 821         5262 for my $i(keys @array) # Load rectangular array from linear array
5678 4858         6283 {my $r = $i % $second;
5679 4858         5964 my $j = ($i - $r) / $second;
5680 4858         8563 $r[$j][$r] = $array[$i];
5681             }
5682              
5683             @r # Resulting rectangular array
5684 821         5513 }
5685              
5686             sub callSubInParallel(&) # Call a sub reference in parallel to avoid memory fragmentation and return its results.
5687 3     3 1 12 {my ($sub) = @_; # Sub reference
5688              
5689 3         63 my $file = temporaryFile; # Temporary file to receive results
5690              
5691 3 100       2847 if (my $pid = fork) # Parent: wait for child Xref to finish
5692 2         8934128 {waitpid $pid, 0; # Wait for results
5693 2         114 my $x = retrieveFile($file); # Retrieve results
5694 2         220 unlink $file; # Remove results file
5695 2 50       346 return @$x if wantarray; # Return results as an array
5696 0         0 $$x[0]; # Return results
5697             }
5698             else # Child: call in a separate process to avoid memory fragmentation in parent
5699 1         370 {storeFile($file, [&$sub]); # Execute child and return results
5700 1         7275 exit;
5701             }
5702             }
5703              
5704             sub callSubInOverlappedParallel(&&) # Call the B<$child> sub reference in parallel in a separate child process and ignore its results while calling the B<$parent> sub reference in the parent process and returning its results.
5705 2     2 1 14 {my ($child, $parent) = @_; # Sub reference to call in child process, sub reference to call in parent process
5706              
5707 2 100       1877 if (my $pid = fork) # Parent
5708 1         273 {my $r = [&$parent]; # Parent sub
5709 1         4454839 waitpid $pid, 0; # Wait for child
5710 1 50       36 return @$r if wantarray; # Return results as an array
5711 1         82 $$r[0]; # Return results
5712             }
5713             else # Child
5714 1         271 {&$child; # Ignore results
5715 1         6635 exit;
5716             }
5717             }
5718              
5719             sub runInParallel($$$@) #I Process the elements of an array in parallel using a maximum of B<$maximumNumberOfProcesses> processes. sub B<&$parallel> is forked to process each array element in parallel. The results returned by the forked copies of &$parallel are presented as a single array to sub B<&$results> which is run in series. B<@array> contains the elements to be processed. Returns the result returned by &$results.
5720 275     275 1 4074 {my ($maximumNumberOfProcesses, $parallel, $results, @array) = @_; # Maximum number of processes, parallel sub, results sub, array of items to process
5721              
5722 275         4315 my $p = newProcessStarter($maximumNumberOfProcesses); # Process starter
5723              
5724 275         6070 for my $s(@array) # Process each element of the array
5725 20997     114   424241 {$p->start(sub{&$parallel($s)});
  114         26080  
5726             }
5727              
5728 161         8592 my @r = $p->finish;
5729 161 50       31362 return &$results(@r) if $results; # Consolidate results if requested
5730             undef
5731 0         0 } # runInParallel
5732              
5733             sub runInSquareRootParallel($$$@) # Process the elements of an array in square root parallel using a maximum of B<$maximumNumberOfProcesses> processes. sub B<&$parallel> is forked to process each block of array elements in parallel. The results returned by the forked copies of &$parallel are presented as a single array to sub B<&$results> which is run in series. B<@array> contains the elements to be processed. Returns the result returned by &$results..
5734 268     268 1 2948 {my ($maximumNumberOfProcesses, $parallel, $results, @array) = @_; # Maximum number of processes, parallel sub, results sub, array of items to process
5735              
5736 268         5628 my @s = squareArray(@array); # Square array of processes
5737 268         7236 my $p = newProcessStarter($maximumNumberOfProcesses); # Process starter
5738              
5739 268         3484 for my $row(@s) # Process each row of the square
5740             {$p->start(sub
5741 10     10   259 {my @r;
5742 10         431 for my $s(@$row) # Process each element of each row and consolidate the results
5743 100         4126 {push @r, &$parallel($s);
5744             }
5745 10         251 [@r]
5746 2635         91391 });
5747             }
5748              
5749 258         14190 my @r = deSquareArray $p->finish;
5750 258 50       36636 return &$results(@r) if $results; # Consolidate results
5751             undef
5752 0         0 } # runInSquareRootParallel
5753              
5754             sub packBySize($@) # Given B<$N> buckets and a list B<@sizes> of ([size of file, name of file]...) pack the file names into buckets so that each bucket contains approximately the same number of bytes. In general this is an NP problem. Packing largest first into emptiest bucket produces an N**2 heuristic if the buckets are scanned linearly, or N*log(N) if a binary tree is used. This solution is a compromise at N**3/2 which has the benefits of simple code yet good performance. Returns ([file names ...]).
5755 285     285 1 5765 {my ($N, @sizes) = @_; # Number of buckets, sizes
5756 285 50       2792 return [map {$$_[1]} @sizes] if $N < 2; # Put all the files in the first bucket unless a plurality of buckets was specified
  0         0  
5757 285         3744 my $step = int sqrt($N); # Divide the buckets up into sequences of square root length
5758 285         2110 my $sequence = 0; # Current sequence
5759 285         5076 my @buckets = map {[]} 1..$N; # Buckets representing the work to be done by each process
  16419         35433  
5760 285         14263 my @bucketSizes = ((0) x $N); # Sum of sizes of files allocated to this bucket
5761              
5762 285         56759 for my $size(sort {$$b[0] <=> $$a[0]} @sizes) # Push files in descending order of size onto the smallest bucket in the current sequence
  167310         228087  
5763 30501         49859 {my $mb = $sequence++ % $step; # Start of sequence we are on
5764 30501         40051 my $ms = $bucketSizes[$mb]; # Smallest bucket so far in sequence
5765              
5766 30501         61016 for(my $b = $mb+$step; $b < $N; $b += $step) # Look through remainder of sequence
5767 197106 100       399782 {$ms = $bucketSizes[$mb = $b] if $bucketSizes[$b] < $ms; # Smallest bucket so far
5768             }
5769              
5770 30501         42591 $bucketSizes [$mb] += $$size[0]; # Update bucket size
5771 30501         37852 push @{$buckets[$mb]}, $$size[1]; # Add file to bucket
  30501         111603  
5772             }
5773              
5774             @buckets # ([file names ...]...) so that each bucket has the approximately the same number of bytes summed over the files in the bucket
5775 285         5369 }
5776              
5777             sub processSizesInParallelN($$$@) #P Process items of known size in parallel using the specified number B<$N> processes with the process each file is assigned to depending on the size of the file so that each process is loaded with approximately the same number of bytes of data in total from the files it processes. \mEach file is processed by sub B<$parallel> and the results of processing all files is processed by B<$results> where the files are taken from B<@files>. Each B<&$parallel> receives a file from B<@files>. B<&$results> receives an array of all the results returned by B<&$parallel>.
5778 282     282 1 10742 {my ($N, $parallel, $results, @sizes) = @_; # Number of processes, Parallel sub, results sub, array of [size; item] to process by size
5779              
5780 282 50 33     8240 return &$results() if @sizes == 0 and $results; # Nothing to do - report same to results sub!
5781 282 50       4314 return () unless @sizes; # Nothing to do - really!
5782              
5783 0     0   0 return runInParallel($N, $parallel, $results // sub{@_}, # One process per item
5784 282 50 0     3784 map {$$_[1]} @sizes) if @sizes <= $N;
  0         0  
5785              
5786             # my @buckets = map {[]} 1..$N; # Buckets representing the work to be done by each process
5787             # my @bucketSizes = ((0) x $N); # Sum of sizes of files allocated to this bucket
5788              
5789             # for my $size(sort {$$b[0] <=> $$a[0]} @sizes) # Push files in descending order of size onto the smallest bucket
5790             # {my $mb = 0; my $ms = $bucketSizes[0]; # Smallest bucket so far
5791             # for(keys @buckets) # Find smallest bucket - sort in place is slower
5792             # {$ms = $bucketSizes[$mb = $_] if $bucketSizes[$_] < $ms; # Smallest bucket so far
5793             # }
5794             # $bucketSizes[$mb] += $$size[0]; # Update bucket size
5795             # push @{$buckets[$mb]}, $$size[1]; # Add file to bucket
5796             # }
5797              
5798 282         18766 my @buckets = packBySize($N, @sizes); # Pack files by size
5799 282         6002 my $p = newProcessStarter($N); # Process starter
5800 282         6138 for my $bucket(@buckets) # Process each bucket
5801             {$p->start(sub # Multiverse
5802 137     137   8554 {my @r;
5803 137         19222 for my $file(@$bucket) # Process each element of each row and consolidate the results
5804 324         95459 {push @r, &$parallel($file);
5805             }
5806 137         45299 [@r]
5807 12330         514473 });
5808             }
5809              
5810 145         1756852 my @p = $p->finish; # Consolidate results in universe
5811 145         81969 my @r = deSquareArray @p;
5812              
5813 145 50       109698 return &$results(@r) if $results; # Post process results
5814             @r # Return results if no post processor
5815 0         0 } # processSizesInParallel
5816              
5817             sub processSizesInParallel($$@) # Process items of known size in parallel using (8 * the number of CPUs) processes with the process each item is assigned to depending on the size of the item so that each process is loaded with approximately the same number of bytes of data in total from the items it processes. \mEach item is processed by sub B<$parallel> and the results of processing all items is processed by B<$results> where the items are taken from B<@sizes>. Each &$parallel() receives an item from @files. &$results() receives an array of all the results returned by &$parallel().
5818 252     252 1 2738 {my ($parallel, $results, @sizes) = @_; # Parallel sub, results sub, array of [size; item] to process by size
5819             my $N = sub # Heuristically scale the number of cpus by the instance type
5820 252 50   252   65140 {return 4 unless onAws;
5821 0         0 my $i = awsCurrentInstanceType;
5822 0 0       0 return 4 if $i =~ m(\Am)i;
5823 0 0       0 return 8 if $i =~ m(\Ar)i;
5824 0 0       0 return 16 if $i =~ m(\Ax)i;
5825 0         0 2
5826 252         6992 }->();
5827 252         25840 processSizesInParallelN(numberOfCpus($N), $parallel, $results, @sizes); # Process in parallel
5828             } # processSizesInParallel
5829              
5830             sub processFilesInParallel($$@) # Process files in parallel using (8 * the number of CPUs) processes with the process each file is assigned to depending on the size of the file so that each process is loaded with approximately the same number of bytes of data in total from the files it processes. \mEach file is processed by sub B<$parallel> and the results of processing all files is processed by B<$results> where the files are taken from B<@files>. Each B<&$parallel> receives a file from B<@files>. B<&$results> receives an array of all the results returned by B<&$parallel>.
5831 158     158 1 2686 {my ($parallel, $results, @files) = @_; # Parallel sub, results sub, array of files to process by size
5832 158         1106 processSizesInParallel $parallel, $results, map {[fileSize($_), $_]} @files; # Process in parallel packing files to achieve as equal as possibly sized processes
  17064         48348  
5833             } # processFilesInParallel
5834              
5835             sub processJavaFilesInParallel($$@) # Process java files of known size in parallel using (the number of CPUs) processes with the process each item is assigned to depending on the size of the java item so that each process is loaded with approximately the same number of bytes of data in total from the java files it processes. \mEach java item is processed by sub B<$parallel> and the results of processing all java files is processed by B<$results> where the java files are taken from B<@sizes>. Each &$parallel() receives a java item from @files. &$results() receives an array of all the results returned by &$parallel().
5836 30     30 1 1050 {my ($parallel, $results, @files) = @_; # Parallel sub, results sub, array of [size; java item] to process by size
5837 30         330 my @sizes = map {[fileSize($_), $_]} @files; # Process in parallel packing files to achieve as equal as possibly sized processes
  3240         9660  
5838 30         11460 processSizesInParallelN(numberOfCpus(1/2), $parallel, $results, @sizes); # Process in parallel
5839             } # processJavaFilesInParallel
5840              
5841             sub syncFromS3InParallel($$$;$$) # Download from L by using "aws s3 sync --exclude '*' --include '...'" in parallel to sync collections of two or more files no greater then B<$maxSize> or single files greater than $maxSize from the B<$source> folder on L to the local folder B<$target> using the specified B<$Profile> and B<$options> - then execute the entire command again without the --exclude and --include options in series which might now run faster due to the prior downloads.
5842 0     0 1 0 {my ($maxSize, $source, $target, $Profile, $options) = @_; # The maximum collection size, the source folder on S3, the target folder locally, aws cli profile, aws cli options
5843             # See: /home/phil/r/z/partitionStrings.pl for standalone tests
5844 0         0 my ($bucket, $folder) = parseS3BucketAndFolderName($source); # Parse an L bucket/folder name into a bucket and a folder name removing any initial s3://.
5845              
5846 0 0       0 my $profile = $Profile ? qq( --profile $Profile) : q(); # Add profile if specified
5847 0   0     0 $options //= q(); # Default options
5848              
5849 0         0 my $getCmd = qq(aws s3 ls s3://$bucket/$folder $profile --recursive); # Command to get the sizes of the files to download
5850 0         0 my $files = qx($getCmd); # Get the sizes of the files to download
5851 0         0 my @files = map {my @a = split m/\s+/, $_, 4; [@a[-1, -2]]} # Files and sizes
  0         0  
  0         0  
5852             split m/\n/, $files;
5853 0 0       0 return unless @files; # No files to download
5854              
5855             call sub # Partition likely to cause a lot of memory fragmentation
5856 0     0   0 {my %partition = partitionStringsOnPrefixBySize($maxSize, map {@$_} @files); # Partition the download into collections no larger than the specified size
  0         0  
5857              
5858             processSizesInParallel( # Download folders packing by size
5859             sub
5860 0         0 {my ($P) = @_; # Path to folder to download
5861 0 0       0 return unless keys %partition > 1; # Process in parallel only if there is more than one partition
5862 0         0 my $p = swapFilePrefix($P, $folder); # Remove the folder because it will be added back by the sync command, see:
5863 0         0 my $c = join ' ', map {pad($_, 32)} # Download in parallel command
  0         0  
5864             qq(aws s3 sync "s3://$bucket/$folder"), qq("$target"),
5865             qq(--exclude "*" --include "$p*"),
5866             $options, $profile, q(--quiet);
5867             #lll $c;
5868 0         0 xxx $c, qr(\A\s*\Z);
5869             },
5870             sub # Now execute the original command which should require less processing because of the prior downloads in parallel
5871 0         0 {my $c = join ' ', map {pad($_, 32)} # Down load in series command
  0         0  
5872             qq(aws s3 sync "s3://$bucket/$folder"), qq("$target"),
5873             $options, $profile, q(--quiet);
5874             #lll $c;
5875 0         0 xxx $c, qr(\A\s*\Z);
5876 0         0 }, map {[$partition{$_}, $_]} sort keys %partition);
  0         0  
5877 0         0 };
5878             } # syncFromS3InParallel
5879              
5880             sub syncToS3InParallel($$$;$$) # Upload to L by using "aws s3 sync --exclude '*' --include '...'" in parallel to sync collections of two or more files no greater then B<$maxSize> or single files greater than $maxSize from the B<$source> folder locally to the target folder B<$target> on L using the specified B<$Profile> and B<$options> - then execute the entire command again without the --exclude and --include options in series which might now run faster due to the prior uploads.
5881 0     0 1 0 {my ($maxSize, $source, $target, $Profile, $options) = @_; # The maximum collection size, the target folder locally, the source folder on S3, aws cli profile, aws cli options
5882              
5883 0         0 $target =~ s(\As3://) (); # Remove S3 prefix if present
5884              
5885 0 0       0 my $profile = $Profile ? qq( --profile $Profile) : q(); # Add profile if specified
5886 0   0     0 $options //= q(); # Default options
5887              
5888 0         0 my @files = map {[$_=>fileSize $_]} # Files and sizes
  0         0  
5889             searchDirectoryTreesForMatchingFiles($source);
5890 0 0       0 return unless @files; # No files to download
5891              
5892 0         0 $$_[0] = swapFilePrefix($$_[0], $source) for @files; # Remove folder prefix
5893              
5894             call sub # Partition likely to cause a lot of memory fragmentation
5895 0     0   0 {my %partition = partitionStringsOnPrefixBySize($maxSize, map {@$_} @files); # Partition the download into collections no larger than the specified size
  0         0  
5896              
5897             processSizesInParallel( # Download folders packing by size
5898             sub
5899 0         0 {my ($p) = @_; # Path to folder to download
5900 0 0       0 return unless keys %partition > 1; # Process in parallel only if there is more than one partition
5901 0         0 my $c = join ' ', map {pad($_, 32)}
  0         0  
5902             qq(aws s3 sync "$source"), qq("s3://$target"),
5903             qq(--exclude "*" --include "$p*"),
5904             $options, $profile, q(--quiet);
5905             #lll $c;
5906 0         0 xxx $c, qr(\A\s*\Z);
5907             },
5908             sub # Now execute the original command which should require less processing because of the prior downloads in parallel
5909 0         0 {my $c = join ' ', map {pad($_, 32)}
  0         0  
5910             qq(aws s3 sync "$source"), qq("s3://$target"),
5911             $options, $profile, q(--quiet);
5912             #lll $c;
5913 0         0 xxx $c, qr(\A\s*\Z);
5914 0         0 }, map {[$partition{$_}, $_]} sort keys %partition);
  0         0  
5915 0         0 };
5916             } # syncToS3InParallel
5917              
5918             sub childPids($) # Recursively find the pids of all the sub processes of a B<$process> and all their sub processes and so on returning the specified pid and all its child pids as a list.
5919 0     0 1 0 {my ($p) = @_; # Process
5920 0         0 confirmHasCommandLineCommand(q(pstree)); # Use pstree
5921 0         0 qx(pstree -p $p) =~ m(\((\d+)\))g; # Extract the pids
5922             }
5923              
5924             sub newServiceIncarnation($;$) # Create a new service incarnation to record the start up of a new instance of a service and return the description as a L.
5925 736     736 1 6256 {my ($service, $file) = @_; # Service name, optional details file
5926             $file ||= fpe($ENV{HOME}, # File to log service details in
5927 736   33     3680 qw(.config com.appaapps services), $service, q(txt)); # Service specification file
5928 736 100       41952 my $t = genHash(q(Data::Exchange::Service), # Service details.
5929             service=> $service, # The name of the service.
5930             start => int(time) + (-e $file ? 1 : 0), # The time this service was started time plus a minor hack to simplify testing.
5931             file => $file, # The file in which the service start details is being recorded.
5932             );
5933 736         10304 dumpFile($file, $t); # Write details
5934 736         52992 $t # Return service details
5935             }
5936              
5937             sub Data::Exchange::Service::check($$) # Check that we are the current incarnation of the named service with details obtained from L. If the optional B<$continue> flag has been set then return the service details if this is the current service incarnation else B. Otherwise if the B<$continue> flag is false confess unless this is the current service incarnation thus bringing the earlier version of this service to an abrupt end.
5938 1104     1104   5520 {my ($service, $continue) = @_; # Current service details, return result if B<$continue> is true else confess if the service has been replaced
5939 1104         28336 my $t = evalFile($service->file); # Latest service details
5940 1104 50 66     30912 return $t if $t->start == $service->start and # Check service details match
      66        
5941             $t->service eq $service->service and
5942             $t->file eq $t->file;
5943 368 50       6992 confess $t->service. " replaced by a newer version\n" unless $continue; # Replaced by a newer incarnation
5944             undef # Not the current incarnation but continue specified
5945 368         3680 }
5946              
5947             #D1 Conversions # Perform various conversions from STDIN to STDOUT
5948              
5949             sub convertPerlToJavaScript(;$$) # Convert Perl to Javascript
5950 1     1 1 5 {my ($in, $out) = @_; # Input file name or STDIN if undef, output file name or STDOUT if undefined
5951 1 50       12 my @lines = $in ? readFile($in) : readStdIn; # Read file or STDIN
5952              
5953 1         24 for my $i(keys @lines) # Parameters
5954 5 100       52 {if ($lines[$i] =~ m(\Asub\s*(\w+)\s*\((.*?)\)(.*)\Z)i)
5955 1         12 {my ($sub, $parms, $comment) = ($1, $2, $3);
5956 1         4 my $j = $i + 1;
5957              
5958 1 50       23 if ($lines[$j] =~ m(\A(\s*\{)my\s*\((.*?)\)\s*=\s*\@_)i)
5959 1         6 {my ($lead, $my) = ($1, $2);
5960 1         9 $my =~ s(\$) ()gs;
5961 1         13 $lines[$i] = qq(function $sub($my)$comment\n$lead);
5962 1         6 $lines[$j] = '';
5963             }
5964             }
5965             }
5966              
5967 1         5 for my $i(keys @lines) # if(my
5968 5 50       35 {if ($lines[$i] =~ m(\A(\s*)if\s*\(my\s*\$(\w+))i)
5969 0         0 {my ($lead, $var) = ($1, $2);
5970 0         0 my $l = $lines[$i];
5971 0         0 $l =~ s(if\s*\(my) ();
5972 0         0 $l =~ s(\)\s*\Z) ();
5973              
5974 0         0 $lines[$i] = qq($l\nif ($var)\n)
5975             }
5976             }
5977              
5978 1         6 for my $i(keys @lines) # if(defined $x)
5979 5 50       28 {if ($lines[$i] =~ m(\A(\s*)if\s*\(defined\s*\$(\w+)\s*\)(.*)\Z)i)
5980 0         0 {my ($lead, $var, $trail) = ($1, $2);
5981 0         0 $lines[$i] = qq(${lead}if ($var !== undefined)\n)
5982             }
5983             }
5984              
5985 1         9 for my $i(keys @lines) # for my $var(
5986 5 50       29 {if ($lines[$i] =~ m(\A(\s*)for\s*my\s*(\w+)(.*)\Z)i)
5987 0         0 {my ($lead, $var, $rest) = ($1, $2, $3);
5988 0         0 $lines[$i] = qq/${lead}for(const $var of $rest/
5989             }
5990             }
5991              
5992 1         3 if (1) # In place changes
5993 1         7 {for(@lines)
5994 5         24 {s(#) (//)gs;
5995 5         18 s(\Asub ) (function)gs;
5996 5         19 s(my\s*@(\w+)\s*;) (const $1 = new Array())gs;
5997 5         13 s(my\s*%(\w+)\s*;) (const $1 = new Map())gs;
5998 5         11 s(my \$) (const )gs;
5999 5         8 s(->[@%]\*) ()gs;
6000 5         21 s(\{(\w+)\}) (.$1)gs; # Hash constant lookup
6001 5         26 s(\{\$(\w+)\}) (\[$1\])gs; # Hash variable lookup
6002 5         9 s(\s+(or)\s+) ( || )gs; # Or
6003 5         17 s(\s+(and)\s+) ( && )gs; # And
6004 5         10 s(sort keys\s*%\$(\w+)) (Object.keys($1).sort())gs; # sort keys %$t
6005 5         12 s(keys\s*%\$(\w+)) (Object.keys($1))gs; # keys %$t
6006 5         11 s(\Ause) (require); # use ...
6007 5         8 s(\A(\s*)ok\s+(.*);\s*\Z) (${1}assert($2)\n); # ok
6008 5         15 s(\A(\s*)is_deeply) (${1}assert.deepEquals\(); # is_deeply
6009 5         21 s(qq\((.*?)\)) (`$1`)gs; # double quoted strings
6010 5         14 s(q\((.*?)\)) ('$1')gs; # single quoted strings
6011 5         35 s([\$\@&#]) ()gs; # Sigils
6012             }
6013             }
6014              
6015 1         3 if (1) # ->
6016 1         3 {for(@lines)
6017 5         9 {s(->\[) ([)gs;
6018 5         9 s(->) (\.)gs;
6019             }
6020             }
6021              
6022 1         1 if (1) # Specifics
6023 1         6 {for(@lines)
6024 5         15 {s(\.\.) (.)gs;
6025 5         10 s(\$ssv) (ditaJs.ssv)gs;
6026             }
6027             }
6028              
6029 1         19 my @comments = split /\n/, join '', @lines; # Reparse
6030 1         8 if (1) # Comment position
6031 1         14 {for my $i(keys @comments)
6032 3 50       20 {next if $comments[$i] =~ m(\A//);
6033 3 50       24 if ($comments[$i] =~ m(\A(.*)(//.*)\Z))
6034 3         9 {my ($code, $comment) = ($1, $2);
6035 3 100       24 if (length($code) > 80)
    50          
6036 1         4 {my $a = substr($code, 0, 80);
6037 1         2 my $b = substr($code, 80);
6038 1         11 $b =~ s(\s+\Z) ();
6039 1         3 $code = qq($a$b);
6040             }
6041             elsif (length($code) < 80)
6042 2         16 {$code = substr($code.(' ' x 80), 0, 80);
6043             }
6044 3         12 $comments[$i] = qq($code$comment)
6045             }
6046             }
6047             }
6048              
6049 1         4 my $text = join "\n", @comments, '';
6050 1         29 $text =~ s((\n=pod\n.*?\n=cut\n)) (`$1`)gs; # Pod as comment string
6051              
6052 1 50       17 $out ? owf($out, $text) : (say STDOUT $text) # Write results to file or STDOUT
6053             } # convertPerlToJavaScript
6054              
6055             #D1 Documentation # Extract, format and update documentation for a perl module.
6056              
6057             sub parseDitaRef($;$$) # Parse a dita reference B<$ref> into its components (file name, topic id, id) . Optionally supply a base file name B<$File>> to make the the file component absolute and/or a a default the topic id B<$TopicId> to use if the topic id is not present in the reference.
6058 2412     2412 1 5896 {my ($ref, $File, $TopicId) = @_; # Reference to parse, default absolute file, default topic id
6059 2412 50 33     14472 return (q()) x 3 unless $ref and $ref =~ m(\S)s;
6060              
6061 2412         6700 my ($file, $rest) = split /#/, $ref, 2;
6062              
6063 2412 50 33     13132 $file = $File && $file ? sumAbsAndRel($File, $file) : $File || $file||q(); # Full file path if possible
      100        
6064              
6065 2412 50       3752 if (!$rest) # file
6066 0         0 {return ($file, q(), q())
6067             }
6068              
6069 2412 100       8040 if ($rest !~ m(/)s) # file#id
6070 804         4824 {return ($file, q(), $rest)
6071             }
6072              
6073 1608 100       5628 if ($rest =~ m(\A\./)s) # file#./id
6074 536   50     6432 {return ($file, $TopicId || q(), $rest =~ s(\A\./) ()r)
6075             }
6076              
6077 1072         2412 my ($topicId, $id) = split m(/), $rest, 2;
6078 1072   100     6164 $topicId = $topicId || $TopicId || q();
6079 1072 50 33     2948 $topicId = $TopicId if $TopicId and $topicId =~ m(\A(\s*|\.)\Z);
6080 1072   50     1876 $id ||= q();
6081              
6082 1072         7236 ($file, $topicId, $id)
6083             }
6084              
6085             sub parseXmlDocType($) # Parse an L DOCTYPE and return a hash indicating its components
6086 34     34 1 136 {my ($string) = @_; # String containing a DOCTYPE
6087              
6088 34 50       697 if ($string =~ m(
    0          
6089 34         544 {return genHash(q(DocType),
6090             root => $1,
6091             public => 1,
6092             publicId => $2,
6093             localDtd => $3);
6094             }
6095             elsif ($string =~ m(
6096 0         0 {return genHash(q(DocType),
6097             root => $1,
6098             public => 0,
6099             localDtd => $2);
6100             }
6101             undef
6102 0         0 }
6103              
6104             sub reportSettings($;$) # Report the current values of parameterless subs.
6105 0     0 1 0 {my ($sourceFile, $reportFile) = @_; # Source file, optional report file
6106 0         0 warn "Deprecated, please use reportAttributeSettings instead";
6107 0         0 my $s = readFile($sourceFile);
6108              
6109 0         0 my %s;
6110 0         0 for my $l(split /\n/, $s) # Find the attribute subs
6111 0 0       0 {if ($l =~ m(\Asub\s*(\w+)\s*\{.*?#\s+(.*)\Z))
6112 0         0 {$s{$1} = $2;
6113             }
6114             }
6115              
6116 0         0 my @r;
6117 0         0 for my $s(sort keys %s) # Evaluate each sub
6118 0         0 {my ($package, $filename, $line) = caller; # Callers context
6119 0         0 my $v = eval q(&).$package.q(::).$s; # Current value in callers context
6120 0   50     0 my $r = $@ // ''; # Failure description
6121 0         0 push @r, [$s, $v, $r, $s{$s}]; # Table entry of sub name, sub value, reason why there is no value, comment
6122             }
6123              
6124 0 0       0 formatTable(\@r, <
6125             Attribute The name of the program attribute
6126             Value The current value of the program attribute
6127             END
6128             head => qq(Found NNNN parameters on DDDD),
6129             title => qq(Attributes in program: $sourceFile),
6130             summarize => 1,
6131             $reportFile ? (file=>$reportFile) : ());
6132             }
6133              
6134             sub reportAttributes($) # Report the attributes present in a B<$sourceFile>
6135 0     0 1 0 {my ($sourceFile) = @_; # Source file
6136 0         0 my $s = readFile($sourceFile);
6137 0         0 my %s;
6138 0         0 for my $l(split /\n/, $s) # Find the attribute subs
6139 0 0       0 {if ($l =~ m(\Asub\s*(\w+)\s*\{.*?#\w*\s+(.*)\Z))
6140 0         0 {$s{$1} = $2;
6141             }
6142             }
6143 0         0 \%s
6144             }
6145              
6146             sub reportAttributeSettings(;$) # Report the current values of the attribute methods in the calling file and optionally write the report to B<$reportFile>. Return the text of the report.
6147 0     0 1 0 {my ($reportFile) = @_; # Optional report file
6148 0         0 my ($package, $sourceFile, $line) = caller; # Callers context
6149              
6150 0         0 my $a = reportAttributes($sourceFile); # Attribute methods in calling file
6151              
6152 0         0 my @r;
6153 0         0 for my $s(sort keys %$a) # Evaluate each sub
6154 0         0 {my $v = eval q(&).$package.q(::).$s; # Current value in callers context
6155 0   50     0 my $r = $@ // ''; # Failure description
6156 0         0 push @r, [$s, $v, $r, $$a{$s}]; # Table entry of sub name, sub value, reason why there is no value, comment
6157             }
6158              
6159 0 0       0 formatTable(\@r, <
6160             Attribute The name of the program attribute
6161             Value The current value of the program attribute
6162             END
6163             head => qq(Found NNNN parameters on DDDD),
6164             title => qq(Attributes in program: $sourceFile),
6165             summarize => 1,
6166             $reportFile ? (file=>$reportFile) : ());
6167              
6168             \@r
6169 0         0 }
6170              
6171             sub reportReplacableMethods($) # Report the replaceable methods marked with #r in a B<$sourceFile>
6172 0     0 1 0 {my ($sourceFile) = @_; # Source file
6173 0         0 my $s = readFile($sourceFile);
6174 0         0 my %s;
6175 0         0 for my $l(split /\n/, $s) # Find the attribute subs
6176 0 0       0 {if ($l =~ m(\Asub\s*(\w+).*?#\w*r\w*\s+(.*)\Z))
6177 0         0 {$s{$1} = $2;
6178             }
6179             }
6180 0         0 \%s
6181             }
6182              
6183             sub reportExportableMethods($) # Report the exportable methods marked with #e in a B<$sourceFile>
6184 0     0 1 0 {my ($sourceFile) = @_; # Source file
6185 0         0 my $s = readFile($sourceFile);
6186 0         0 my %s;
6187 0         0 for my $l(split /\n/, $s) # Find the attribute subs
6188 0 0       0 {if ($l =~ m(\Asub\s*(\w+).*?#\w*e\w*\s+(.*)\Z))
6189 0         0 {$s{$1} = $2;
6190             }
6191             }
6192 0         0 \%s
6193             }
6194              
6195             sub htmlToc($@) # Generate a table of contents for some html.
6196 368     368 1 4048 {my ($replace, $html) = @_; # Sub-string within the html to be replaced with the toc, string of html
6197 368         1104 my @toc;
6198             my %toc;
6199              
6200 368         4784 for(split /\n/, $html)
6201 1472 100       16928 {next unless /\A\s*(.+?)<\/h\d>\s*\Z/;
6202 1104 50       12880 confess "Duplicate id $2\n" if $toc{$2}++;
6203 1104         13616 push @toc, [$1, $2, $3];
6204             }
6205              
6206 368         1104 my @h;
6207 368         5520 for my $head(keys @toc)
6208 1104         2576 {my ($level, $id, $title) = @{$toc[$head]};
  1104         3312  
6209 1104         7360 my $spacer = ' ' x (4*$level);
6210 1104 100       8832 push @h, <
6211            
 
6212             END
6213 1104         2944 my $n = $head+1;
6214 1104         5152 push @h, <
6215            
$n$spacer$title
6216             END
6217             }
6218              
6219 368         4784 my $h = <
6220            
6221             END
6222            
6223             END
6224              
6225 368         18768 $html =~ s($replace) ($h)gsr;
6226             }
6227              
6228             sub wellKnownUrls #P Short names for some well known urls
6229 1634     1634 1 408173 {genHash(q(Short_Names_For_Well_Known_Urls), # Short names for some well known urls
6230             aas => [q(Amazon App Store), "https://www.amazon.com/s?k=appaapps" ], #
6231             ab => [q(Android Build), "https://metacpan.org/pod/Android::Build" ], #
6232             alva => [q(Rio Alva), "https://duckduckgo.com/?t=canonical&q=rio+alva&iax=images&ia=images" ], #
6233             ami => [q(Amazon Machine Image), "https://en.wikipedia.org/wiki/Amazon_Machine_Image" ], #
6234             apache => [q(Apache Web Server), "https://en.wikipedia.org/wiki/Apache_HTTP_Server" ], #
6235             appaapps => [q(www.appaapps.com), "http://www.appaaps.com" ], #
6236             aramco => [q(Saudi Aramco), "https://en.wikipedia.org/wiki/Saudi_Aramco" ], #
6237             arena => [q(arena), "https://en.wikipedia.org/wiki/Region-based_memory_management" ], #
6238             ascii => [q(Ascii), "https://en.wikipedia.org/wiki/ASCII" ], #
6239             avx512 => [q(Advanced Vector Extensions), "https://en.wikipedia.org/wiki/AVX-512" ], #
6240             avx => [q(Advanced Vector Extensions), "https://en.wikipedia.org/wiki/AVX-512" ], #
6241             awsami => [q(Amazon Web Services - Amazon Machine Image), "https://en.wikipedia.org/wiki/Amazon_Machine_Image" ], #
6242             awscli => [q(Amazon Web Services Command Line Interface), "https://aws.amazon.com/cli/" ], #
6243             awsforecast => [q(Amazon Web Services Forecast), "https://eu-west-1.console.aws.amazon.com/forecast" ], #
6244             aws => [q(Amazon Web Services), "http://aws.amazon.com" ], #
6245             azure => [q(Azure), "https://en.wikipedia.org/wiki/Microsoft_Azure" ], #
6246             backend => [q(back end), "https://en.wikipedia.org/wiki/Front_end_and_back_end" ], #
6247             bandwidth => [q(Bandwidth), "https://en.wikipedia.org/wiki/Bandwidth_(computing)" ], #
6248             ban => [q(Briana Ashley Nevarez), "https://www.linkedin.com/in/briana-nevarez-b66b621b0/" ], #
6249             bash => [q(Bash), "https://en.wikipedia.org/wiki/Bash_(Unix_shell)" ], #
6250             binarysearch => [q(Binary Search), "https://metacpan.org/release/Binary-Heap-Search" ], #
6251             bitterend => [q(Bitter End), "https://en.wikipedia.org/wiki/Knot#Bitter_end" ], #
6252             blob => [q(blob), "https://en.wikipedia.org/wiki/Binary_large_object" ], #
6253             boson => [q(Boson), "https://en.wikipedia.org/wiki/Boson" ], #
6254             browser => [q(web browser), "https://en.wikipedia.org/wiki/Web_browser" ], #
6255             bulktreeg => [q(Bulk Tree), "https://github.com/philiprbrenan/TreeBulk" ], #
6256             button => [q(Button), "https://en.wikipedia.org/wiki/Button_(computing)" ], #
6257             camelCase => [q(camelCase), "https://en.wikipedia.org/wiki/Camel_case" ], #
6258             certbot => [q(Certbot), "https://certbot.eff.org/lets-encrypt/ubuntufocal-apache" ], #
6259             cgi => [q(Common Gateway Interface), "https://en.wikipedia.org/wiki/Common_Gateway_Interface" ], #
6260             chmod => [q(chmod), "https://linux.die.net/man/1/chmod" ], #
6261             chown => [q(chown), "https://linux.die.net/man/1/chown" ], #
6262             cicd => [q(CI/CD), "https://en.wikipedia.org/wiki/Continuous_integration" ], #
6263             cicero => [q("The sinews of war are an infinite supply of money"), "https://en.wikipedia.org/wiki/Cicero#Legacy" ], #
6264             cl => [q(command line), "https://en.wikipedia.org/wiki/Command-line_interface" ], #
6265             cm => [q(Codementor), 'https://www.codementor.io/@philiprbrenan' ], #
6266             co2 => [q(Carbon Dioxide), "https://en.wikipedia.org/wiki/Carbon_dioxide" ], #
6267             codementor => [q(Codementor), 'https://www.codementor.io/@philiprbrenan' ], #
6268             code => [q(code), "https://en.wikipedia.org/wiki/Computer_program" ], #
6269             commandline => [q(command line), "https://en.wikipedia.org/wiki/Command-line_interface" ], #
6270             comment => [q(comment), "https://en.wikipedia.org/wiki/Comment_(computer_programming)" ], #
6271             computer => [q(computer), "https://en.wikipedia.org/wiki/Computer" ], #
6272             concept => [q(concept), "http://docs.oasis-open.org/dita/dita/v1.3/errata02/os/complete/part3-all-inclusive/langRef/technicalContent/concept.html#concept"], #
6273             confess => [q(confess), "http://perldoc.perl.org/Carp.html#SYNOPSIS/" ], #
6274             conref => [q(conref), "http://docs.oasis-open.org/dita/dita/v1.3/errata02/os/complete/part3-all-inclusive/archSpec/base/conref.html#conref" ], #
6275             cookie => [q(cookie), "https://en.wikipedia.org/wiki/Cookie" ], #
6276             corpus => [q(corpus), "https://en.wikipedia.org/wiki/Text_corpus" ], #
6277             coverage => [q(coverage), "https://en.wikipedia.org/wiki/Code_coverage" ], #
6278             cpan => [q(CPAN), "https://metacpan.org/author/PRBRENAN" ], #
6279             cpu => [q(CPU), "https://en.wikipedia.org/wiki/Central_processing_unit" ], #
6280             c => [q(the C programming language), "https://1lib.eu/book/633119/db5c78" ], #
6281             css => [q(Cascading Style Sheets), "https://en.wikipedia.org/wiki/CSS" ], #
6282             csv => [q(csv), "https://en.wikipedia.org/wiki/Comma-separated_values" ], #
6283             curl => [q(curl), "https://linux.die.net/man/1/curl" ], #
6284             cv => [q(Curriculum Vitae), "https://en.wikipedia.org/wiki/Curriculum_vitae" ], #
6285             dataStructure => [q(data structure), "https://en.wikipedia.org/wiki/Data_structure" ], #
6286             db2 => [q(DB2), "https://en.wikipedia.org/wiki/IBM_Db2_Family" ], #
6287             dbi => [q(DBI), "https://dbi.perl.org/" ], #
6288             ddg => [q(DuckDuckGo), "https://www.duckduckgo.com" ], #
6289             dd => [q(Daily Diary), "http://philiprbrenan.appaapps.com.s3-website-eu-west-1.amazonaws.com/index.html" ], #
6290             ddt => [q(Data::Table::Text), "https://metacpan.org/pod/Data::Table::Text" ], #
6291             dependencies => [q(dependencies), "https://en.wikipedia.org/wiki/Coupling_(computer_programming)" ], #
6292             dexl => [q(Data::Edit::Xml::Lint), "https://metacpan.org/release/Data-Edit-Xml-Lint" ], #
6293             dex => [q(Data::Edit::Xml), "https://metacpan.org/pod/Data::Edit::Xml" ], #
6294             dexr => [q(Data::Edit::Xml::Reuse), "https://metacpan.org/release/Data-Edit-Xml-Reuse" ], #
6295             dexx => [q(Data::Edit::Xml::Xref), "https://metacpan.org/release/Data-Edit-Xml-Xref" ], #
6296             dfa => [q(DFA), "https://metacpan.org/pod/Data::DFA" ], #
6297             dhahran => [q(Dhahran), "https://en.wikipedia.org/wiki/Dhahran" ], #
6298             die => [q(die), "http://perldoc.perl.org/functions/die.html" ], #
6299             diff => [q(diff), "https://en.wikipedia.org/wiki/Diff" ], #
6300             diospiros => [q(diospiros), "https://en.wikipedia.org/wiki/Persimmon" ], #
6301             diskdrive => [q(disk drive), "https://en.wikipedia.org/wiki/Solid-state_drive" ], #
6302             ditaot => [q(DITA Open ToolKit), "http://www.dita-ot.org/download" ], #
6303             dita => [q(Dita), "http://docs.oasis-open.org/dita/dita/v1.3/os/part2-tech-content/dita-v1.3-os-part2-tech-content.html" ], #
6304             divtag => [q(Div tag), "https://en.wikipedia.org/wiki/Span_and_div" ], #
6305             dns => [q(Domain Name System), "https://en.wikipedia.org/wiki/Domain_Name_System" ], #
6306             docbook => [q(DocBook), "https://tdg.docbook.org/tdg/5.1/" ], #
6307             docker => [q(Docker), "https://en.wikipedia.org/wiki/Docker_(software)" ], #
6308             documentation => [q(documentation), "https://en.wikipedia.org/wiki/Software_documentation" ], #
6309             domain => [q(domain name), "https://en.wikipedia.org/wiki/Domain_name" ], #
6310             dom => [q(Document Object Model), "https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model" ], #
6311             dtd => [q(DTD), "https://en.wikipedia.org/wiki/Document_type_definition" ], #
6312             dttg => [q(Data::Table::Text), "https://github.com/philiprbrenan/DataTableText" ], #
6313             dtt => [q(Data::Table::Text), "https://metacpan.org/pod/Data::Table::Text" ], #
6314             ec2Console => [q(EC2 Console), "https://us-east-1.console.aws.amazon.com/ec2/" ], #
6315             ec2 => [q(EC2), "https://aws.amazon.com/ec2/" ], #
6316             eff => [q(The Electronic Frontier Foundation), "https://en.wikipedia.org/wiki/Electronic_Frontier_Foundation" ], #
6317             electrons => [q(electrons), "https://en.wikipedia.org/wiki/Electron" ], #
6318             english => [q(English), "https://en.wikipedia.org/wiki/English_language" ], #
6319             eval => [q(eval), "http://perldoc.perl.org/functions/eval.html" ], #
6320             extensions => [q(file name extensions), "https://en.wikipedia.org/wiki/List_of_filename_extensions" ], #
6321             fail => [q(fail), "https://1lib.eu/book/2468851/544b50" ], #
6322             file => [q(file), "https://en.wikipedia.org/wiki/Computer_file" ], #
6323             fileutility => [q(File utility), "https://www.man7.org/linux/man-pages/man1/file.1.html" ], #
6324             find => [q(find), "https://en.wikipedia.org/wiki/Find_(Unix)" ], #
6325             foehn => [q(Foehn), "https://en.wikipedia.org/wiki/Foehn_wind" ], #
6326             folder => [q(folder), "https://en.wikipedia.org/wiki/File_folder" ], #
6327             fork => [q(fork), "https://en.wikipedia.org/wiki/Fork_(system_call)" ], #
6328             frontend => [q(front end), "https://en.wikipedia.org/wiki/Front_end_and_back_end" ], #
6329             fsf => [q(Free Software Foundation), "https://www.fsf.org/" ], #
6330             fusion => [q(fusion), "https://en.wikipedia.org/wiki/Nuclear_fusion" ], #
6331             future => [q(future), "https://en.wikipedia.org/wiki/Future" ], #
6332             gbstandard => [q(GB Standard), "http://metacpan.org/pod/Dita::GB::Standard" ], #
6333             gdpr => [q(European Directive on Data Protection), "https://gdpr.eu" ], #
6334             geany => [q(Geany), "https://www.geany.org" ], #
6335             ghc => [q(Github Automation), "https://metacpan.org/release/GitHub-Crud" ], #
6336             ghe => [q(Github Edit) , "https://github.com/ricksnp/github-editor" ], #
6337             gigabit => [q(gigabit), "https://en.wikipedia.org/wiki/Gigabit_Ethernet" ], #
6338             gigabyte => [q(gigabyte), "https://en.wikipedia.org/wiki/Gigabyte" ], #
6339             githubaction => [q(GitHub Action), "https://docs.github.com/en/free-pro-team\@latest/actions/quickstart" ], #
6340             gitHubAction => [q(GitHub Action), "https://docs.github.com/en/free-pro-team\@latest/actions/quickstart" ], #
6341             gitHubCrud => [q(GitHub::Crud), "https://metacpan.org/pod/GitHub::Crud" ], #
6342             githubdp => [q(GitHub Developer Program), "https://github.com/philiprbrenan" ], #
6343             gitHubDP => [q(GitHub Developer Program), "https://github.com/philiprbrenan" ], #
6344             github => [q(GitHub), "https://github.com/philiprbrenan" ], #
6345             gitHub => [q(GitHub), "https://github.com/philiprbrenan" ], #
6346             gmt => [q(Greenwich Mean Time), "https://en.wikipedia.org/wiki/Greenwich_Mean_Time" ], #
6347             gnufdl => [q(GNU Free Documentation License), "https://en.wikipedia.org/wiki/Wikipedia:Text_of_the_GNU_Free_Documentation_License" ], #
6348             gowest => [q("Go West young man"), "https://en.wikipedia.org/wiki/Go_West,_young_man" ], #
6349             grep => [q(grep), "https://en.wikipedia.org/wiki/Grep" ], #
6350             guid => [q(guid), "https://en.wikipedia.org/wiki/Universally_unique_identifier" ], #
6351             gui => [q(graphical user interface), "https://en.wikipedia.org/wiki/Graphical_user_interface" ], #
6352             gunzip => [q(gunzip), "https://en.wikipedia.org/wiki/Gunzip" ], #
6353             gzip => [q(gzip), "https://en.wikipedia.org/wiki/Gzip" ], #
6354             hacker => [q(hacker), "https://1lib.eu/book/643342/813ee7" ], #
6355             heapsLaw => [q(Heap's Law), "https://en.wikipedia.org/wiki/Heaps%27_law" ], #
6356             help => [q(help), "https://en.wikipedia.org/wiki/Online_help" ], #
6357             hexadecimal => [q(hexadecimal), "https://en.wikipedia.org/wiki/Hexadecimal" ], #
6358             hipaa => [q(HIPAA), "https://en.wikipedia.org/wiki/Health_Insurance_Portability_and_Accountability_Act" ], #
6359             hpe => [q(Hewlett Packard Enterprise), "https://www.hpe.com/us/en/home.html" ], #
6360             html => [q(HTML), "https://en.wikipedia.org/wiki/HTML" ], #
6361             htmltable => [q(html table), "https://www.w3.org/TR/html52/tabular-data.html#the-table-element" ], #
6362             http => [q(HTTP), "https://en.wikipedia.org/wiki/HTTP" ], #
6363             https => [q(HTTPS), "https://en.wikipedia.org/wiki/HTTPS" ], #
6364             hxnormalize => [q(hxnormalize), "https://www.w3.org/Tools/HTML-XML-utils/man1/hxnormalize.html" ], #
6365             ibm => [q(IBM), "https://en.wikipedia.org/wiki/IBM" ], #
6366             iconv => [q(iconv), "https://linux.die.net/man/1/iconv" ], #
6367             ide => [q(Integrated Development Environment), "https://en.wikipedia.org/wiki/Integrated_development_environment" ], #
6368             ietf => [q(Internet Engineering Task Force), "https://en.wikipedia.org/wiki/Internet_Engineering_Task_Force" ], #
6369             imagemagick => [q(Imagemagick), "https://www.imagemagick.org/script/index.php" ], #
6370             infix => [q(infix), "https://en.wikipedia.org/wiki/Infix_notation" ], #
6371             install => [q(install), "https://en.wikipedia.org/wiki/Installation_(computer_programs)" ], #
6372             intelsde => [q(Intel Software Development Emulator), "https://software.intel.com/content/www/us/en/develop/articles/intel-software-development-emulator.html" ], #
6373             internet => [q(Internet), "https://en.wikipedia.org/wiki/Internet" ], #
6374             ip6 => [q(IPv6 address), "https://en.wikipedia.org/wiki/IPv6" ], #
6375             ipaddress => [q(IP address), "https://en.wikipedia.org/wiki/IP_address" ], #
6376             ip => [q(IP address), "https://en.wikipedia.org/wiki/IP_address" ], #
6377             java => [q(Java), "https://en.wikipedia.org/wiki/Java_(programming_language)" ], #
6378             javascript => [q(JavaScript), "https://en.wikipedia.org/wiki/JavaScript" ], #
6379             jetni => [q(Physics design calculations for the JET neutral injectors),"https://www.sciencedirect.com/science/article/pii/B978008025697950052X" ], #
6380             jet => [q(Joint European Torus), "https://en.wikipedia.org/wiki/Joint_European_Torus" ], #
6381             jpg => [q(JPG), "https://en.wikipedia.org/wiki/JPEG" ], #
6382             json => [q(Json), "https://en.wikipedia.org/wiki/JSON" ], #
6383             keyboard => [q(keyboard), "https://en.wikipedia.org/wiki/Computer_keyboard" ], #
6384             killarney => [q(Killarney), "https://en.wikipedia.org/wiki/Killarney" ], #
6385             kubuntu => [q(Kubuntu), "https://kubuntu.org/" ], #
6386             laser => [q(laser), "https://en.wikipedia.org/wiki/Laser" ], #
6387             learningCurve => [q(learning curve), "https://en.wikipedia.org/wiki/Learning_curve" ], #
6388             libpq => [q(libpq), "https://www.postgresql.org/docs/13/libpq.html" ], #
6389             libreoffice => [q(LibreOffice), "https://www.libreoffice.org/" ], #
6390             linting => [q(linting), "https://en.wikipedia.org/wiki/Lint_(software)" ], #
6391             lint => [q(lint), "http://xmlsoft.org/xmllint.html" ], #
6392             linux => [q(Linux), "https://en.wikipedia.org/wiki/Linux" ], #
6393             liseMeitner => [q(Lise Meitner), "https://en.wikipedia.org/wiki/Lise_Meitner" ], #
6394             lisp => [q(Lisp), "https://en.wikipedia.org/wiki/Lisp" ], #
6395             list => [q(list), "https://en.wikipedia.org/wiki/Linked_list" ], #
6396             log => [q(log), "https://en.wikipedia.org/wiki/Log_file" ], #
6397             lunchclub => [q(LunchClub), "https://lunchclub.com/?invite_code=philipb4" ], #
6398             lvaluemethod => [q(lvalue method), "http://perldoc.perl.org/perlsub.html#Lvalue-subroutines" ], #
6399             maze => [q(Maze), "https://github.com/philiprbrenan/maze" ], #
6400             md5 => [q(MD5 sum), "https://en.wikipedia.org/wiki/MD5" ], #
6401             mdnfetch => [q(the Javascript Fetch API), "https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API" ], #
6402             md => [q(Mark Down), "https://en.wikipedia.org/wiki/Markdown" ], #
6403             meme => [q(Meme), "https://en.wikipedia.org/wiki/Meme" ], #
6404             memory => [q(memory), "https://en.wikipedia.org/wiki/Computer_memory" ], #
6405             mentor => [q(mentor), "https://en.wikipedia.org/wiki/Mentorship" ], #
6406             metadata => [q(metadata), "https://en.wikipedia.org/wiki/Metadata" ], #
6407             mfa => [q(Multi-factor authentication), "https://en.wikipedia.org/wiki/Multi-factor_authentication" ], #
6408             mideast => [q(Middle East), "https://en.wikipedia.org/wiki/Middle_East" ], #
6409             minimalism => [q(minimalism), "https://en.wikipedia.org/wiki/Minimalism_(computing)" ], #
6410             mod_shib => [q(mod_shib), "https://wiki.shibboleth.net/confluence/display/SP3/Apache" ], #
6411             module => [q(module), "https://en.wikipedia.org/wiki/Modular_programming" ], #
6412             mopc => [q(mop-c), "https://metacpan.org/pod/Preprocess::Ops" ], #
6413             murphyslaw => [q(Murphy's Law), "https://en.wikipedia.org/wiki/Murphy%27s_law" ], #
6414             mvp => [q(Minimal Viable Product), "https://en.wikipedia.org/wiki/Minimum_viable_product" ], #
6415             mysqlMan => [q(MySql manual), "https://dev.mysql.com/doc/refman/8.0/en/" ], #
6416             mysql => [q(MySql), "https://en.wikipedia.org/wiki/MySQL" ], #
6417             nasm => [q(nasm), "https://github.com/netwide-assembler/nasm" ], #
6418             nasmx86 => [q(NasmX86), "https://github.com/philiprbrenan/NasmX86" ], #
6419             nfa => [q(NFA), "https://metacpan.org/pod/Data::NFA" ], #
6420             ni => [q(Neutral Beam Injection), "https://en.wikipedia.org/wiki/Neutral-beam_injection" ], #
6421             nodejs => [q(NodeJs), "https://en.wikipedia.org/wiki/NodeJs" ], #
6422             oauth => [q(Oauth), "https://en.wikipedia.org/wiki/OAuth" ], #
6423             oneliner => [q(one line program), "https://en.wikipedia.org/wiki/One-liner_program" ], #
6424             openoffice => [q(Apache Open Office), "https://www.openoffice.org/download/index.html" ], #
6425             openssl => [q(Open SSL), "https://www.openssl.org/" ], #
6426             othermeta => [q(othermeta), "http://docs.oasis-open.org/dita/dita/v1.3/errata02/os/complete/part3-all-inclusive/contentmodels/cmlto.html#cmlto__othermeta" ], #
6427             # our => [q(our), "https://perldoc.perl.org/functions/our.html" ], #
6428             oxygenformat => [q(Oxygen Format), "https://www.oxygenxml.com/doc/versions/20.1/ug-author/topics/linked-output-messages-of-external-engine.html" ], #
6429             oxygenworkshop => [q(Oxygen Workshop), "http://github.com/philiprbrenan/oxygenWorkShop" ], #
6430             pairprograming => [q(pair programing), "https://en.wikipedia.org/wiki/Pair_programming" ], #
6431             pairprogram => [q(pair program), "https://en.wikipedia.org/wiki/Pair_programming" ], #
6432             parkinson => [q(Parkinson's law: work expands to fill the time available), "https://en.wikipedia.org/wiki/Parkinson%27s_law" ], #
6433             parse => [q(parse), "https://en.wikipedia.org/wiki/Parsing" ], #
6434             pcdInstall => [q(PCD installation), "https://github.com/philiprbrenan/philiprbrenan.github.io/blob/master/pcd_installation.md" ], #
6435             pcdLang => [q(PCD), "https://philiprbrenan.github.io/data_edit_xml_edit_commands.html" ], #
6436             pcd => [q(Dita::Pcd), "https://metacpan.org/pod/Dita::PCD" ], #
6437             pdf => [q(PDF), "https://en.wikipedia.org/wiki/PDF" ], #
6438             people => [q(people), "https://en.wikipedia.org/wiki/Person" ], #
6439             perlal => [q(Perl Artistic Licence), "https://dev.perl.org/licenses/artistic.html" ], #
6440             perl => [q(Perl), "http://www.perl.org/" ], #
6441             pg => [q(Postgres database), "https://www.postgresql.org/" ], #
6442             philCpan => [q(CPAN), "https://metacpan.org/author/PRBRENAN" ], #
6443             photoApp => [q(AppaApps Photo App), "https://github.com/philiprbrenan/AppaAppsGitHubPhotoApp" ], #
6444             php => [q(PHP), "https://en.wikipedia.org/wiki/PHP" ], #
6445             pi => [q(𝝿), "https://en.wikipedia.org/wiki/Pi" ], #
6446             plasma => [q(plasma), "https://en.wikipedia.org/wiki/Plasma_(physics)" ], #
6447             pli => [q(Programming Language One), "https://en.wikipedia.org/wiki/PL/I" ], #
6448             pl => [q(programming language), "https://en.wikipedia.org/wiki/Programming_language" ], #
6449             pod => [q(POD), "https://perldoc.perl.org/perlpod.html" ], #
6450             poppler => [q(Poppler), "https://poppler.freedesktop.org/" ], #
6451             portugal => [q(Portugal), "https://en.wikipedia.org/wiki/Portugal" ], #
6452             postgres => [q(Postgres), "https://github.com/philiprbrenan/postgres" ], #
6453             prb => [q(philip r brenan), "https://philiprbrenan.neocities.org/" ], #
6454             preprocessor => [q(preprocessor), "https://en.wikipedia.org/wiki/Preprocessor" ], #
6455             process => [q(process), "https://en.wikipedia.org/wiki/Process_management_(computing)" ], #
6456             procfs => [q(Process File System), "https://en.wikipedia.org/wiki/Procfs" ], #
6457             program => [q(program), "https://en.wikipedia.org/wiki/Computer_program" ], #
6458             python => [q(Python), "https://www.python.org/" ], #
6459             quicksort => [q(Quick Sort), "https://github.com/philiprbrenan/QuickSort" ], #
6460             r53 => [q(Route 53), "https://console.aws.amazon.com/route53" ], #
6461             rackspace => [q(Rackspace), "https://www.rackspace.com/" ], #
6462             recursively => [q(recursively), "https://en.wikipedia.org/wiki/Recursion" ], #
6463             recursive => [q(recursive), "https://en.wikipedia.org/wiki/Recursion" ], #
6464             relocatable => [q(relocatable), "https://en.wikipedia.org/wiki/Relocation_%28computing%29" ], #
6465             rfp => [q(Request For Proposal), "https://en.wikipedia.org/wiki/Request_for_proposal" ], #
6466             riyadh => [q(Riyadh), "https://en.wikipedia.org/wiki/Riyadh" ], #
6467             rrr => [q(The R Programming Language), "https://en.wikipedia.org/wiki/R_(programming_language)" ], #
6468             rsa => [q(RSA), "https://en.wikipedia.org/wiki/RSA_(cryptosystem)" ], #
6469             rsync => [q(rsync), "https://linux.die.net/man/1/rsync" ], #
6470             s390 => [q(IBM System 390), "https://en.wikipedia.org/wiki/IBM_System/390" ], #
6471             s3Console => [q(S3 Console), "https://s3.console.aws.amazon.com/s3/home" ], #
6472             s3 => [q(S3), "https://aws.amazon.com/s3/" ], #
6473             saml => [q(Security Assertion Markup Language), "https://en.wikipedia.org/wiki/Security_Assertion_Markup_Language" ], #
6474             samltest => [q(SAML test), "https://samltest.id/" ], #
6475             samltools => [q(SAML tools), "https://www.samltool.com/sp_metadata.php" ], #
6476             sas => [q(SAS Institute), "https://en.wikipedia.org/wiki/SAS_Institute" ], #
6477             securityGroup => [q(security group), "https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/working-with-security-groups.html" ], #
6478             selfaware => [q(self aware), "https://en.wikipedia.org/wiki/Self-awareness" ], #
6479             server => [q(server), "https://en.wikipedia.org/wiki/Server_(computing)" ], #
6480             sevenZ => [q(7z), "https://en.wikipedia.org/wiki/7z" ], #
6481             sha => [q(SHA), "https://en.wikipedia.org/wiki/SHA-1" ], #
6482             shell => [q(shell), "https://en.wikipedia.org/wiki/Shell_(computing)" ], #
6483             shib => [q(Shibboleth), "https://www.shibboleth.net/" ], #
6484             simd => [q(SIMD), "https://www.officedaytime.com/simd512e/" ], #
6485             smartmatch => [q(smartmatch), "https://perldoc.perl.org/perlop.html#Smartmatch-Operator" ], #
6486             snake_case => [q(snake_case), "https://en.wikipedia.org/wiki/Snake_case" ], #
6487             sow => [q(Shibboleth on Windows), "http://philiprbrenan.appaapps.com/ShibbolethOnWindows" ], #
6488             spot => [q(spot), "https://aws.amazon.com/ec2/spot/" ], #
6489             spreedsheet => [q(Spreadsheet), "https://en.wikipedia.org/wiki/Spreadsheet" ], #
6490             sql => [q(Structured Query Language), "https://en.wikipedia.org/wiki/SQL" ], #
6491             squareroot => [q(Square Root), "https://en.wikipedia.org/wiki/Square_root" ], #
6492             ssh => [q(Secure Shell), "https://www.ssh.com/ssh" ], #
6493             ssxr => [q(Self Xref), "https://philiprbrenan.github.io/selfServiceXref.pdf" ], #
6494             stderr => [q(stderr), "https://en.wikipedia.org/wiki/Standard_streams#Standard_input_(stdin)" ], #
6495             stdin => [q(stdin), "https://en.wikipedia.org/wiki/Standard_streams#Standard_input_(stdin)" ], #
6496             stdout => [q(stdout), "https://en.wikipedia.org/wiki/Standard_streams#Standard_input_(stdin)" ], #
6497             step => [q(step), "http://docs.oasis-open.org/dita/dita/v1.3/errata02/os/complete/part3-all-inclusive/contentmodels/cmlts.html#cmlts__step" ], #
6498             steps => [q(steps), "http://docs.oasis-open.org/dita/dita/v1.3/errata02/os/complete/part3-all-inclusive/contentmodels/cmlts.html#cmlts__steps" ], #
6499             stopwords => [q(stopwords), "https://metacpan.org/pod/Storable" ], #
6500             storable => [q(Storable), "https://metacpan.org/pod/Storable" ], #
6501             sub => [q(sub), "https://perldoc.perl.org/perlsub.html" ], #
6502             substeps => [q(substeps), "http://docs.oasis-open.org/dita/dita/v1.3/errata02/os/complete/part3-all-inclusive/contentmodels/cmlts.html#cmlts__substeps" ], #
6503             sws => [q(Sir Walter Scott), "https://en.wikipedia.org/wiki/Walter_Scott" ], #
6504             table => [q(table of information), "https://en.wikipedia.org/wiki/Table_(information)" ], #
6505             tab => [q(tab), "https://en.wikipedia.org/wiki/Tab_key" ], #
6506             taocp => [q(The Art of Computer Programming), "https://en.wikipedia.org/wiki/The_Art_of_Computer_Programming" ], #
6507             ta => [q(Transamerica), "https://en.wikipedia.org/wiki/Transamerica_Corporation" ], #
6508             task => [q(task), "http://docs.oasis-open.org/dita/dita/v1.3/errata02/os/complete/part3-all-inclusive/langRef/technicalContent/task.html#task" ], #
6509             tcl => [q(Tcl), "https://en.wikipedia.org/wiki/Tcl" ], #
6510             tdd => [q(test driven development), "https://en.wikipedia.org/wiki/Test-driven_development" ], #
6511             test => [q(test), "https://en.wikipedia.org/wiki/Software_testing" ], #
6512             textmatch => [q(text matching), "https://metacpan.org/pod/Text::Match" ], #
6513             thp => [q(Theoretical Computational Physics), "https://en.wikipedia.org/wiki/Theoretical_physics" ], #
6514             tls => [q(TLS), "https://en.wikipedia.org/wiki/Transport_Layer_Security" ], #
6515             transamerica => [q(Transamerica), "https://en.wikipedia.org/wiki/Transamerica_Corporation" ], #
6516             tree => [q(tree), "https://en.wikipedia.org/wiki/Tree_(data_structure)" ], #
6517             tritium => [q(tritium), "https://en.wikipedia.org/wiki/Tritium" ], #
6518             ubuntu => [q(Ubuntu), "https://ubuntu.com/download/desktop" ], #
6519             ucla => [q(University of California at Los Angeles), "https://en.wikipedia.org/wiki/University_of_California,_Los_Angeles" ], #
6520             udel => [q(University of Delaware), "https://www.udel.edu/" ], #
6521             udp => [q(User Datagram Protocol), "https://en.wikipedia.org/wiki/User_Datagram_Protocol" ], #
6522             uk => [q(United Kingdom), "https://en.wikipedia.org/wiki/United_Kingdom" ], #
6523             uk => [q(United Kingdom), "https://en.wikipedia.org/wiki/United_Kingdom" ], #
6524             ul => [q(University of Lancaster), "https://en.wikipedia.org/wiki/Lancaster_University" ], #
6525             umas => [q(Unicode Mathematical Alphanumeric Symbols), "https://en.wikipedia.org/wiki/Mathematical_Alphanumeric_Symbols" ], #
6526             undef => [q(undef), "https://perldoc.perl.org/functions/undef.html" ], #
6527             unexaminedlife => [q(Socrates: "The unexamined life is not worth living"), "https://en.wikipedia.org/wiki/The_unexamined_life_is_not_worth_living" ], #
6528             unicode => [q(Unicode), "https://en.wikipedia.org/wiki/Unicode" ], #
6529             unisyn => [q(UniSyn), "https://github.com/philiprbrenan/UnisynParse" ], #
6530             universe => [q(Universe), "https://en.wikipedia.org/wiki/Universe" ], #
6531             unixHaters => [q(Unix Haters Handbook), "https://1lib.eu/book/750790/8f3128" ], #
6532             unix => [q(Unix), "https://en.wikipedia.org/wiki/Unix" ], #
6533             unoconv => [q(unoconv), "https://github.com/unoconv/unoconv" ], #
6534             uow => [q(Ubuntu on Windows), "http://philiprbrenan.appaapps.com/UbuntuOnWindows" ], #
6535             upload => [q(upload), "https://en.wikipedia.org/wiki/Upload" ], #
6536             url => [q(url), "https://en.wikipedia.org/wiki/URL" ], #
6537             usa => [q(United States), "https://en.wikipedia.org/wiki/United_States" ], #
6538             usa => [q(United States of America), "https://en.wikipedia.org/wiki/United_States" ], #
6539             user => [q(user), "https://en.wikipedia.org/wiki/User_(computing)" ], #
6540             uspto => [q(United States Patent and Trademark Office), "https://en.wikipedia.org/wiki/USPTO" ], #
6541             utf8 => [q(utf8), "https://en.wikipedia.org/wiki/UTF-8" ], #
6542             v2 => [q(Vectors In Two Dimensions), "https://pypi.org/project/Vector2/" ], #
6543             verify => [q(verify), "https://en.wikipedia.org/wiki/Software_verification_and_validation" ], #
6544             vhdl => [q(VHDL), "https://ghdl.readthedocs.io/en/latest/about.html" ], #
6545             vi => [q(vi), "https://www.vim.org/" ], #
6546             webpage => [q(web page), "https://en.wikipedia.org/wiki/Web_page" ], #
6547             website => [q(web site), "https://en.wikipedia.org/wiki/Website" ], #
6548             whitespace => [q(white space), "https://en.wikipedia.org/wiki/Whitespace_character" ], #
6549             whp => [q(Whp), "https://www.whp.net/en/" ], #
6550             widget => [q(widget), "https://en.wikipedia.org/wiki/Graphical_widget" ], #
6551             wikipedia => [q(Wikipedia), "https://en.wikipedia.org" ], #
6552             word => [q(word), "https://en.wikipedia.org/wiki/Doc_(computing)" ], #
6553             x64 => [q(x64), "https://en.wikipedia.org/wiki/X86-64" ], #
6554             xmllint => [q(Xml Lint), "http://xmlsoft.org/xmllint.html" ], #
6555             xmlparser => [q(Xml parser), "https://metacpan.org/pod/XML::Parser/" ], #
6556             xml => [q(Xml), "https://en.wikipedia.org/wiki/XML" ], #
6557             xref => [q(Xref), "https://metacpan.org/pod/Data::Edit::Xml::Xref" ], #
6558             youngtableaug => [q(Young Tableau on GitHub), "https://github.com/philiprbrenan/youngTableauSort/" ], #
6559             youngtableau => [q(Young Tableau), "https://en.wikipedia.org/wiki/Young_tableau" ], #
6560             zerowidthspace => [q(zero width space), "https://en.wikipedia.org/wiki/Zero-width_space" ], #
6561             zip => [q(zip), "https://linux.die.net/man/1/zip" ], #
6562             zoom => [q(Zoom), "https://zoom.us/" ], #
6563             );
6564             } # wellKnownUrls
6565              
6566             sub expandWellKnownWordsAsUrlsInHtmlFormat($) # Expand words found in a string using the html B tag to supply a definition of that word.
6567 158     158 1 632 {my ($string) = @_; # String containing url names to expand
6568 158         1738 my $wellKnown = wellKnownUrls; # Well known urls to expand
6569              
6570 158         26070 for my $w(sort keys %$wellKnown) # Expand well known words (lowercased) as html links
6571 52140         94958 {my ($t, $u) = @{$$wellKnown{$w}};
  52140         114708  
6572 52140         286770 $string =~ s(L\[$w\]) ($t)gis; # Explicit link
6573 52140         821916 $string =~ s(\s$w([.,;:'"]*)\s) ( $t$1 )gs; # Word that matches
6574             }
6575              
6576 158         4266 $string =~ s(W\[(\w+)\]) ($1)gs; # W[...] wraps words with definitions we wish to stress
6577 158         16590 $string =~ s(w\[(\w+)\]) ($1)gsr; # w[...] wraps words with definitions we wish to keep as is
6578             }
6579              
6580             sub expandWellKnownWordsAsUrlsInMdFormat($) # Expand words found in a string using the md url to supply a definition of that word.
6581 158     158 1 790 {my ($string) = @_; # String containing url names to expand
6582 158         1422 my $wellKnown = wellKnownUrls; # Well known urls to expand
6583              
6584 158         24174 for my $w(sort keys %$wellKnown) # Expand well known words (lowercased) as html links
6585 52140         96538 {my ($t, $u) = @{$$wellKnown{$w}};
  52140         115972  
6586             # $string =~ s(L\[$w\]) (\![$t]($u))gis; # Explicit link
6587             # $string =~ s(\s$w([.,;:'"]*)\s) ( \![$t]($u)$1 )gs; # Word that matches
6588 52140         285822 $string =~ s(L\[$w\]) ([$t]($u))gis; # Explicit link
6589 52140         814648 $string =~ s(\s$w([.,;:'"]*)\s) ( [$t]($u)$1 )gs; # Word that matches
6590             }
6591              
6592 158         2686 $string =~ s(W\[(\w+)\]) (```$1```)gs; # W[...] wraps words with definitions we wish to stress
6593 158         14220 $string =~ s(w\[(\w+)\]) ($1)gsr; # w[...] wraps words with definitions we wish to keep as is
6594             }
6595              
6596             sub reinstateWellKnown($) #P Contract references to well known Urls to their abbreviated form
6597 1     1 1 15 {my ($string) = @_; # Source string
6598 1         15 my $wellKnown = wellKnownUrls; # Well known urls to contract
6599              
6600 1         138 for my $w(sort keys %$wellKnown)
6601 330         441 {my ($t, $u) = @{$$wellKnown{$w}};
  330         491  
6602 330         2161 $string =~ s(L<$t\|$u>) (L<$t>)gis;
6603             }
6604              
6605             $string # Result
6606 1         80 }
6607              
6608             sub expandWellKnownUrlsInPerlFormat($) # Expand short L names found in a string in the format LEurl-nameE using the Perl POD syntax
6609 685     685 1 4222 {my ($string) = @_; # String containing url names to expand
6610 685         6820 my $wellKnown = wellKnownUrls; # Well known urls to expand
6611              
6612 685         118402 for my $w(sort keys %$wellKnown)
6613 226050         11760012 {my ($t, $u) = @{$$wellKnown{$w}};
  226050         518268  
6614 226050         14044492 $string =~ s(L\<$w\>) (L<$t|$u>)gis;
6615             }
6616              
6617             $string # Result
6618 685         111058 }
6619              
6620             sub expandWellKnownUrlsInHtmlFormat($) # Expand short L names found in a string in the format L[url-name] using the html B tag.
6621 158     158 1 632 {my ($string) = @_; # String containing url names to expand
6622 158         2212 my $wellKnown = wellKnownUrls; # Well known urls to expand
6623              
6624 158         25596 for my $w(sort keys %$wellKnown) # Expand well known urls as html a links
6625 52140         83108 {my ($t, $u) = @{$$wellKnown{$w}};
  52140         98908  
6626 52140         275710 $string =~ s(L\[$w\]) ($t)gis;
6627             }
6628              
6629 158 50       5372 if (my @e = $string =~ m(L\[(\w+)\])gs) # Check for expansion failures
6630 0         0 {say STDERR "Failed to find url expansions for these words:\n", dump(\@e);
6631             }
6632              
6633             $string # Result
6634 158         12166 }
6635              
6636             sub expandWellKnownUrlsInHtmlFromPerl($) # Expand short L names found in a string in the format L[url-name] using the html B tag.
6637 158     1467 1 2370 {my ($string) = @_; # String containing url names to expand
6638 158         948 my $wellKnown = wellKnownUrls; # Well known urls to expand
6639              
6640 158         24806 for my $w(sort keys %$wellKnown) # Expand well known urls as html a links
6641 52140         86110 {my ($t, $u) = @{$$wellKnown{$w}};
  52140         99224  
6642 52140         101436 my $s = qq($t);
6643 52140         293090 $string =~ s(L\<$w\>) ($s)gis;
6644             }
6645              
6646             $string # Result
6647 158         12324 }
6648              
6649             sub expandWellKnownUrlsInPod2Html($) # Expand short L names found in a string in the format =begin html format
6650 158     1425 1 1738 {my ($string) = @_; # String containing url names to expand
6651 158         1580 my $wellKnown = wellKnownUrls; # Well known urls to expand
6652              
6653 158         25754 for my $w(sort keys %$wellKnown) # Expand well known urls as html a links
6654 52140         94642 {my ($t, $u) = @{$$wellKnown{$w}};
  52140         111548  
6655 52140         214722 my $r = <
6656             `begin HTML
6657              
6658             $t
6659              
6660             `end HTML
6661             END
6662 52140         681454 $string =~ s(\s*L\<$w\>\s*) (\n\n$r\n\n)gis;
6663             }
6664              
6665             $string # Result
6666 158         36498 }
6667              
6668             sub expandWellKnownUrlsInDitaFormat($) # Expand short L names found in a string in the format L[url-name] in the L[Dita] Bformat.
6669 158     158 1 1580 {my ($string) = @_; # String containing url names to expand
6670 158         4424 my $wellKnown = wellKnownUrls; # Well known urls to expand
6671              
6672 158         25122 for my $w(sort keys %$wellKnown)
6673 52140         85794 {my ($t, $u) = @{$$wellKnown{$w}};
  52140         103648  
6674 52140         348074 $string =~ s(L\[$w\]) ($t)gis;
6675             }
6676              
6677             $string # Result
6678 158         14694 }
6679              
6680             sub formatSourcePodAsHtml #P Format the L in the current source file as L.
6681 0     0 1 0 {my $s1 = readFile $0; # Read source file
6682 0         0 my $s2 = expandWellKnownUrlsInPerlFormat $s1; # Expand Perl links
6683 0         0 my $s3 = expandWellKnownUrlsInHtmlFormat $s2; # Expand Html links
6684 0         0 $s3 =~ s() ()g; # Align headers
6685 0         0 my $s = writeTempFile $s3; # Write expanded source to temporary file
6686 0         0 my $t = setFileExtension $0, q(html);
6687              
6688 0         0 lll qx(pod2html --infile $s --outfile $t; rm pod2htmd.tmp); # Format expanded source as HTML
6689 0         0 lll qx(opera $t); # Show HTML
6690             }
6691              
6692             sub expandNewLinesInDocumentation($) # Expand new lines in documentation, specifically \n for new line and \m for two new lines.
6693 17     17 1 136 {my ($s) = @_; # String to be expanded
6694 17         323 $s =~ s(\\m) (\n\n)gs; # Double new line
6695 17         170 $s =~ s(\\n) (\n)gs; # Single new line
6696 17         357 $s
6697             }
6698              
6699             sub extractTest($) #P Remove example markers from test code.
6700 4416     4416 1 11408 {my ($string) = @_; # String containing test line
6701             #$string =~ s/\A\s*{?(.+?)\s*#.*\Z/$1/; # Remove any initial white space and possible { and any trailing white space and comments
6702 4416         15456 $string =~ s(#T(\w|:)+) ()gs; # Remove test tags from line
6703 4416         11408 $string
6704             }
6705              
6706             sub extractCodeBlock($;$) # Extract the block of code delimited by B<$comment>, starting at qq($comment-begin), ending at qq($comment-end) from the named B<$file> else the current Perl program $0 and return it as a string or confess if this is not possible.
6707 1     1 1 18 {my ($comment, $file) = @_; # Comment delimiting the block of code, file to read from if not $0
6708 1   33     21 my $s = readFile($file//$0);
6709 1 50       14078 if ($s =~ m($comment-begin\s*\n(.*?)$comment-end)is)
6710 1         16 {my $c = $1;
6711 1         27 $c =~ s(\s+\Z) ()s;
6712 1         28 return qq($c\n);
6713             }
6714 0         0 confess "Unable to locate code delimited by $comment in $0\n"; #CODEBLOCK-begin
6715 0         0 my $a = 1;
6716 0         0 my $b = 2; #CODEBLOCK-end
6717             }
6718              
6719             sub updateDocumentation(;$) # Update the documentation for a Perl module from the comments in its source code. Comments between the lines marked with:\m #Dn title # description\mand:\m #D\mwhere n is either 1, 2 or 3 indicating the heading level of the section and the # is in column 1.\mMethods are formatted as:\m sub name(signature) #FLAGS comment describing method\n {my ($parameters) = @_; # comments for each parameter separated by commas.\mFLAGS can be chosen from:\m=over\m=item I\mmethod of interest to new users\m=item P\mprivate method\m=item r\moptionally replaceable method\m=item R\mrequired replaceable method\m=item S\mstatic method\m=item X\mdie rather than received a returned B result\m=back\mOther flags will be handed to the method extractDocumentationFlags(flags to process, method name) found in the file being documented, this method should return [the additional documentation for the method, the code to implement the flag].\mText following 'E\xxample:' in the comment (if present) will be placed after the parameters list as an example. Lines containing comments consisting of '#T'.methodName will also be aggregated and displayed as examples for that method.\mLines formatted as:\m BEGIN{*source=*target}\mstarting in column 1 will define a synonym for a method.\mLines formatted as:\m #C emailAddress text\mwill be aggregated in the acknowledgments section at the end of the documentation.\mThe character sequence B<\\xn> in the comment will be expanded to one new line, B<\\xm> to two new lines and BB<<$_>>,BB<>,BB<>,BB<>,BB<> to links to the perl documentation.\mSearch for '#D1': in L to see more examples of such documentation in action - although it is quite difficult to see as it looks just like normal comments placed in the code.\mParameters:\n
6720 368     368 1 2208 {my ($perlModule) = @_; # Optional file name with caller's file being the default
6721 368   33     1840 $perlModule //= $0; # Extract documentation from the caller if no perl module is supplied
6722 368         6256 my $package = perlPackage($perlModule); # Package name
6723 368         2576 my $maxLinesInExample = 500; # Maximum number of lines in an example
6724 368         20240 my %attributes; # Attributes defined in this package, the values of this hash are the flags for the attribute
6725             my %attributeDescription; # Description of each attribute
6726 368         0 my %collaborators; # Collaborators #C pause-id comment
6727 368         0 my %comment; # The line comment associated with a method
6728 368         0 my %examples; # Examples for each method
6729 368         0 my %exported; # Exported methods
6730 368         0 my %genHashFlags; # Flags on attributes in objects defined by genHash
6731 368         0 my %genHashs; # Attributes in objects defined by genHash
6732 368         0 my %genHash; # Attributes in objects defined by genHash
6733 368         0 my %genHashPackage; # Packages defined by genHash
6734 368         0 my %isUseful; # Immediately useful methods
6735 368         0 my %methods; # Methods that have been coded as opposed to being generated
6736 368         0 my %methodParms; # Method names including parameters
6737 368         0 my %methodX; # Method names for methods that have an version suffixed with X that die rather than returning B
6738 368         0 my %private; # Private methods
6739 368         0 my %replace; # Optional replaceable methods
6740 368         0 my %Replace; # Required replaceable methods
6741 368         0 my %signatureNames; # Signature using parameter names
6742 368         0 my %static; # Static methods
6743 368         0 my %synonymTargetSource; # Synonyms from source to target - {$source}{$target} = 1 - can be several
6744 368         0 my %synonymTarget; # Synonym target - confess is more than one
6745 368         0 my @synopsis; # External synopsis to allow L to be expanded
6746 368         0 my %title; # Method to title of section describing method
6747 368         0 my %userFlags; # User flags
6748 368         4784 my $oneLineDescription = qq(\n); # One line description from =head1 Name
6749 368         1104 my $install = ''; # Additional installation notes
6750 368         736 my @doc; # Documentation
6751             my @private; # Documentation of private methods
6752 368         9568 my $level = 0; my $off = 0; # Header levels
  368         3680  
6753 368         2208 my %unitary; # A unitary method - all of its parameters other than the first are strings or numbers
6754             my $version; # Version of package being documented
6755 368         0 my @ctags; # Ctags file in pipe format for each sub
6756 368         0 my %moduleDescription; # {section}{method}{detail}=value
6757              
6758 368         6624 my $sourceIsString = $perlModule =~ m(\n)s; # Source of documentation is a string not a file
6759 368 50       3680 my $Source = my $source = $sourceIsString ? $perlModule:readFile($perlModule);# Read the perl module from a file unless it is a string not a file
6760              
6761 368 50       6624 if ($source =~ m(our\s+\$VERSION\s*=\s*(\S+)\s*;)s) # Update references to examples so we can include html and images etc. in the module
6762 0         0 {my $V = $version = $1; # Quoted version
6763 0 0       0 if (my $v = eval $V) # Remove any quotes
6764 0         0 {my $s = $source;
6765 0         0 $source =~ # Replace example references in source
6766             s((https://metacpan\.org/source/\S+?-)(\d+)(/examples/))
6767 0         0 ($1$v$3)gs;
6768             $moduleDescription{version} = $v; # Record version in module description
6769             }
6770             }
6771 368 50       4784  
6772 0         0 if ($source =~ m(\n=head1\s+Name\s+(?:\w|:)+\s+(.+?)\n)s) # Extract one line description from =head1 Name ... Module name ... one line description
6773 0         0 {my $s = $1;
6774 0         0 $s =~ s(\A\s*-\s*) (); # Remove optional leading -
6775 0         0 $s =~ s(\s+\Z) (); # Remove any trailing spaces
6776 0         0 $oneLineDescription = "\n$s\n"; # Save description
6777             $moduleDescription{oneLineDescription} = $oneLineDescription; # Record one line description in module description
6778             }
6779 368         1104  
6780 368 50       4048 if (1) # Document description
6781 368         2576 {my $v = $version ? "\n\nVersion $version.\n" : "";
6782             push @doc, <<"END";
6783             `head1 Description
6784             $oneLineDescription$v
6785              
6786             The following sections describe the methods in each functional area of this
6787             module. For an alphabetic listing of all methods by name see L.
6788              
6789             END
6790             }
6791 368         9568  
6792             my @lines = split /\n/, $source; # Split source into lines
6793 368         4048  
6794 9568         16192 for my $l(keys @lines) # Tests associated with each method
6795 9568 100       29072 {my $line = $lines[$l];
6796 1472         2576 if (my @tags = $line =~ m/(?:\s#T((?:\w|:)+))/g)
  1472         5152  
6797             {my %tags; $tags{$_}++ for @tags;
6798 1472         5520  
  1472         6256  
6799 0         0 for(grep {$tags{$_} > 1} sort keys %tags) # Check for duplicate example names on the same line
6800             {warn "Duplicate example name $_ on line $l";
6801             }
6802 1472         6624  
6803             my @testLines = (extractTest($line));
6804 1472 100       7728  
6805 368         5520 if ($line =~ m/<<(END|'END'|"END")/) # Process here documents
6806 736         2208 {for(my $L = $l + 1; $L < @lines; ++$L)
6807 736         2208 {my $nextLine = $lines[$L];
6808 736 100       4416 push @testLines, extractTest($nextLine);
6809             last if $nextLine =~ m/\AEND/; # Finish on END
6810             }
6811             }
6812 1472 100       10304  
6813 368         1472 if ($line =~ m(\A(\s*)if\s*\x28(\d+)\x29)) # Process "if (\d+)" recording leading spaces
  368         1840  
6814 368         1104 {my $S = $1; my $minimumNumberOfLines = $2; # Leading spaces so we can balance the indentation of the closing curly bracket. Start testing for the closing } after this many lines
6815 368         2944 my $M = $maxLinesInExample;
6816 1104         3312 for(my ($L, $N) = ($l + 1, 0); $L < @lines; ++$L, ++$N)
6817 1104         5520 {my $nextLine = $lines[$L];
6818 1104 100 100     59248 push @testLines, extractTest($nextLine);
6819             if ($N >= $minimumNumberOfLines and $nextLine =~ m/\A$S }/) # Finish on closing brace in column 2
6820 368         1472 {#say STDERR "End of example";
6821             last;
6822             }
6823             else
6824             {#say STDERR "$N ", $nextLine;
6825 736         3312 }
6826 736 50       7360 my $L = $l + 1;
6827             $N < $M or fff($L, $perlModule, "Too many lines in example"); # Prevent overruns
6828             }
6829 368 50       1840  
6830 368 50       7360 if (@testLines > 1) # Remove leading and trailing 'if' if possible
6831 0         0 {if ($testLines[0] =~ m(\A\s*if\s*\x{28}\d\x{29}\s*{)i)
  0         0  
6832             {pop @testLines; shift @testLines;
6833             }
6834             }
6835             }
6836 1472         3680  
6837             push @testLines, ''; # Blank line between each test line
6838 1472         6256  
6839 4784         13984 for my $testLine(@testLines) # Save test lines
6840 4784 50       10304 {for my $t(sort keys %tags)
6841 4784         10304 {$testLine =~ s(!) (#)g if $t =~ m(\AupdateDocumentation\Z)s; # To prevent the example documentation using this method showing up for real.
  4784         15456  
6842             push @{$examples{$t}}, $testLine;
6843             }
6844 1472         3312 }
  1472         8464  
6845             push @{$moduleDescription{tests}}, [\@tags, \@testLines]; # Record tests in module description
6846             }
6847             }
6848 368         1840  
6849 9568         15824 for my $l(keys @lines) # Tests associated with replaceable methods
6850 9568         13248 {my $M = $maxLinesInExample;
6851 9568 100       48944 my $line = $lines[$l];
6852 368         1840 if ($line =~ m(\Asub\s+((\w|:)+).*#(\w*)[rR]))
6853 368         5888 {my $sub = $1;
6854 368         2944 my @testLines = ($line =~ s(\s#.*\Z) ()r);
6855 1104         2576 for(my ($L, $N) = ($l + 1, 0); $L < @lines; ++$L, ++$N)
6856 1104         2576 {my $nextLine = $lines[$L];
6857 1104 100       4416 push @testLines, extractTest($nextLine);
6858 736         1472 last if $nextLine =~ m/\A }/; # Finish on closing brace in column 2
6859 736 50       3312 my $L = $l + 1;
6860             $N < $M or fff($L, $perlModule, "Too many lines in test"); # Prevent overruns
6861 368         1104 }
6862             push @testLines, ''; # Blank line between each test line
6863 368         3312  
6864 1840         2576 for my $testLine(@testLines) # Save test lines
  1840         5520  
6865             {push @{$examples{$sub}}, $testLine;
6866             }
6867             }
6868             }
6869 368         1472  
6870 9568         12512 for my $l(keys @lines) # Generated objects
6871 9568         13984 {my $M = $maxLinesInExample;
6872 9568 50       18400 my $line = $lines[$l];
6873 0         0 if ($line =~ m(genHash\s*\x28\s*(q\x28.+\x29|__PACKAGE__).+?# (.+)\Z))
  0         0  
6874 0         0 {my $p = $1; my $c = $2;
6875 0         0 $p = $p =~ s(q[qw]?\x28|\x29) ()gsr =~ s(__PACKAGE__) ($package)gsr;
6876 0         0 $genHashPackage{$p} = $c;
6877 0         0 for(my ($L, $N) = ($l + 1, 0); $L < @lines; ++$L, ++$N)
6878 0 0       0 {my $nextLine = $lines[$L];
6879 0         0 if ($nextLine =~ m(\A\s+(\w+)\s*=>\s*.+?#(\w*)\s+(.*)\Z))
6880 0         0 {my $flags = $genHashFlags{$p}{$1} = $2;
6881 0 0       0 $genHashs {$p}{$1} = $3;
6882 0         0 if (my $invalidFlags = $flags =~ s([I]) ()gsr)
6883             {confess "Invalid flags $invalidFlags on line $L:\n$nextLine";
6884             }
6885 0 0       0 }
6886 0 0       0 last if $nextLine =~ m/\A\s*\);/; # Finish on closing bracket
6887             $N < $M or confess # Prevent overruns
6888             "More than $M line genHash definition at line $l\n".
6889             join("\n", @lines[$l..$L]);
6890             }
6891             }
6892             }
6893 368         4416  
6894 9568         16928 for my $l(keys @lines) # Place the synopsis in a here doc block starting with my $documentationSynopsis = < that should be expanded. If present, the generated text will be used to generate a =head1 Synopsis section just before the description
6895 9568 50       15456 {my $line = $lines[$l];
6896 0         0 if ($line =~ m(\Amy \$documentationSynopsis = <
6897 0         0 {for(my ($L, $N) = ($l + 1, 0); $L < @lines; ++$L, ++$N)
6898 0 0       0 {my $nextLine = $lines[$L];
6899 0         0 last if $nextLine =~ m(\AEND\Z);
6900             push @synopsis, $nextLine;
6901             }
6902             }
6903             }
6904 368         1104  
6905 368         5520 if (1) # Offset method name in examples to make it easier to pick out.
6906 368         3680 {my $mark = boldString(' # Example'); # Marker to highlight the method being described
6907 368         1472 for my $m(sort keys %examples)
6908 368         2944 {my $L = $examples{$m};
6909             for my $i(keys @$L)
6910 6624 100       34960 # {if (index($$L[$i], $m) > -1)
6911 2576         11408 {if ($$L[$i] =~ m(\b$m\b))
6912             {$$L[$i] = join "\n", '', ' '.$$L[$i].$mark, '';
6913             }
6914             }
6915             }
6916             }
6917 368         2208  
6918 9568         15088 for my $l(keys @lines) # Extract synonyms
6919 9568 100       25760 {my $line = $lines[$l];
6920 368         2944 if ($line =~ m(\ABEGIN\{\*(\w+)=\*(\w+)\}))
6921 368         2208 {my ($source, $target) = ($1, $2);
6922             $synonymTargetSource{$target}{$source} = 1; # Multiple synonyms for a method are allowed
6923 368 50 33     2576 confess "Multiple targets for synonym: $source\n" # Only one method can be associated with each synonym
6924 368         1104 if $synonymTarget{$target} and $synonymTarget{$target} ne $source;
6925 368         4416 $synonymTarget{$source} = $target;
6926             $moduleDescription{methods}{$target}{synonyms}{$source} = 1; # Include synonyms in module description
6927             }
6928             }
6929 368 50       6256  
6930 368         16192 unless($perlModule =~ m(\A(Text.pm|Doc.pm)\Z)s) # Load the module being documented so that we can call its extractDocumentationFlags method if needed to process user flags, we do not need to load these modules as they are already loaded
6931 368 50       3312 {do "./$perlModule";
6932             confess $@ if $@;
6933             }
6934 368         2208  
6935 9568         16192 for my $l(keys @lines) # Extract documentation from comments
6936 9568         15824 {my $L = $l + 1; # Line number
6937 9568         16560 my $line = $lines[$l]; # This line
6938 9568 100 66     111136 my $nextLine = $lines[$l+1]; # The next line
    50 100        
    50 66        
    50          
    50          
    100          
    50          
6939 368         1472 if ($line =~ /\A#D(\d)\s+(.*?)\s*(#\s*(.+)\s*)?\Z/) # Sections are marked with #Dn in column 1-3 followed by title followed by optional text
6940 368         2208 {$level = $1;
6941 368 50       2576 my $headLevel = $level+$off;
6942 368 50 33     8832 push @doc, "\n=head$headLevel $2" if $level; # Heading
6943             push @doc, "\n$4" if $level and $4; # Text of section
6944             }
6945 0         0 elsif ($line =~ /\A#C(?:ollaborators)?\s+(\S+)\s+(.+?)\s*\Z/) # Collaborators
6946             {$collaborators{$1} = $2;
6947             }
6948 0         0 elsif ($line =~ /\A#I(?:nstall(?:ation)?)?\s+(.+)\Z/) # Extra install instructions
6949             {$install = "\\m$1\\m";
6950             }
6951 0         0 elsif ($line =~ /\A#D(off)?/) # Switch documentation off
6952             {$level = 0;
6953             }
6954             elsif ($level and $line =~ # Documentation for a generated lvalue * method = sub name comment
6955 0         0 /\Asub\s*(\w+)\s*{.*}\s*#(\w*)\s+(.*)\Z/)
6956 0         0 {my ($name, $flags, $description) = ($1, $2, $3); # Name of attribute, flags, description from comment
6957 0         0 $attributes{$name} = $flags;
6958             $attributeDescription{$name} = $description;
6959             }
6960             elsif ($level and $line =~ # Documentation for a method
6961 736         5888 /\Asub\b\s*(.*?)?(\s*:lvalue)?\s*#(\w*)\s+(.+?)\s*\Z/)
6962             {my ($sub, $lvalue, $flags, $comment, $example, $produces) = # Name from sub, flags, description
6963 736   50     2944 ($1, $2, $3, $4);
6964             $flags //= ''; # No flags found
6965 736 50       7360  
6966 0         0 if ($comment =~ m/\A(.*)Example:(.+?)\Z/is) # Extract example - in comment examples are now deprecated in favor of using tests as examples
6967 0         0 {$comment = $1;
6968             ($example, $produces) = split /:/, $2, 2;
6969             }
6970 736 50       5520  
6971             if ($comment !~ m(\.\s*\Z)is) # Check for closing full stop
6972             {#fff $L, $perlModule, "Comment on line: $L does not end in a full stop\n$comment";
6973             }
6974 736         12880  
6975             my $signature = $sub =~ s/\A\s*(\w|:)+//gsr =~ # Signature
6976             s/\A\x28//gsr =~
6977 736         5888 s/\x29\s*(:lvalue\s*)?\Z//gsr;
6978             my $name = $sub =~ s/\x28.*?\x29//r; # Method name after removing parameters
6979 736         4784  
6980 736         4048 my $methodX = $flags =~ m/X/; # Die rather than return undef
6981 736         2944 my $private = $flags =~ m/P/; # Private
6982 736         4048 my $static = $flags =~ m/S/; # Static
6983 736         2576 my $isUseful = $flags =~ m/I/; # Immediately useful
6984 736         2944 my $unitary = $flags =~ m/U/; # Unitary method - the parameters, other than the first, are strings or numbers
6985 736         1840 my $exported = $flags =~ m/E/; # Exported
6986 736         2576 my $replace = $flags =~ m/r/; # Optionally replaceable
6987 736         6256 my $Replace = $flags =~ m/R/; # Required replaceable
6988             my $userFlags = $flags =~ s/[EIPrRSX]//gsr; # User flags == all flags minus the known flags
6989 736 50 33     7360  
      66        
6990             confess "(P)rivate and (rR)eplacable are incompatible on method $name\n"
6991 736 50 33     5152 if $private and $replace || $Replace;
      66        
6992             confess "(S)tatic and (rR)eplacable are incompatible on method $name\n"
6993 736 0 0     2576 if $static and $replace || $Replace;
      33        
6994             confess "(E)xported and (rR)eplacable are incompatible on method $name\n"
6995 736 50 33     3312 if $exported and $replace || $Replace;
6996             confess "(E)xported and (S)tatic are incompatible on method $name\n"
6997             if $exported and $static;
6998 736 50       2944  
6999 736 100       3312 $methodX {$name} = $methodX if $methodX; # MethodX
7000 736 50       2208 $private {$name} = $private if $private; # Private
7001 736 100       3680 $replace {$name} = $replace if $replace; # Optionally replace
7002 736 100       4784 $Replace {$name} = $Replace if $Replace; # Required replace
7003 736 50       4784 $static {$name} = $static if $static; # Static
7004 736 50       2576 $isUseful{$name} = $comment if $isUseful; # Immediately useful
7005 736 50       1840 $exported{$name} = $exported if $exported; # Exported
7006 736         2576 $unitary {$name} = $unitary if $unitary; # Unitary method
7007             $comment {$name} = $comment; # Comment describing method
7008 736         2576  
7009             for my $field # Include method details in module description
7010             (qw(methodX private replace Replace static isUseful
7011 9568         404800 exported unitary comment signature name flags userFlags))
7012 9568 50       30912 {my $v = eval q($).$field;
7013 9568 100       22080 next if $@;
7014 4048         25760 next unless $v;
7015             $moduleDescription{methods}{$name}{$field} = $v;
7016             }
7017 736 50       2208  
7018             $userFlags{$name} = # Process user flags
7019             &docUserFlags($userFlags, $perlModule, $package, $name)
7020             if $userFlags;
7021 736         1840  
7022 736 50       2208 my ($parmNames, $parmDescriptions);
7023 736         9936 if ($signature) # Parameters, parameter descriptions from comment
7024             {($parmNames, $parmDescriptions) =
7025             $nextLine =~ /\A\s*(.+?)\s*#\s*(.+?)\s*\Z/;
7026 736   50     2944 }
  736   50     1840  
7027             $parmNames //= ''; $parmDescriptions //= ''; # No parameters
7028 736         18032  
7029             my @parameters = split /,\s*/, # Parameter names
7030             $parmNames =~ s/\A\s*\{my\s*\x28//r =~ s/\x29\s*=\s*\@_.*//r; # Names inside parenthesis
7031 736         4048  
7032 736         2576 my $signatureNames = join ', ', @parameters; # Signature using parameter names
7033             $signatureNames{$name} = $signatureNames;
7034 736         3680  
7035 736 50       6256 my $signatureLength = length($signature =~ s([;\\]) ()gsr); # Number of parameters in signature
7036             @parameters == $signatureLength or # Check signature length
7037             confess "Wrong number of parameter descriptions for method: ".
7038             "$name($signature)\n";
7039 736         7728  
  1472         6992  
7040             my @parmDescriptions = map {ucfirst()} split /,\s*/, $parmDescriptions; # Parameter descriptions with first letter uppercased
7041 736         2576  
7042 736         1840 if (1) # Check parameters comment
7043 736         1840 {my $p = @parmDescriptions;
7044 736 50       3680 my $l = $signatureLength;
7045             $p == $l or fff $L, $perlModule, <<"END";
7046             Method:
7047              
7048             $name($signature)
7049              
7050             The comment describing the parameters for this
7051             method has descriptions for $p parameters but the signature suggests that there
7052             are $l parameters.
7053              
7054             The comment is split on /,/ to divide the comment into descriptions of each
7055             parameter.
7056              
7057             The comment supplied is:
7058             $parmDescriptions
7059             END
7060             }
7061 736         3312  
7062 1472         4784 for my $p(keys @parameters) # Record parameters in module description
7063 1472         2208 {my $d = [$parameters[$p], $parmDescriptions[$p]];
  1472         8096  
7064             push @{$moduleDescription{methods}{$name}{parameters}}, $d;
7065             }
7066 736         2576  
7067 736         1840 my $parametersAsString = join ', ', @parameters; # Parameters as a comma separated string
7068             my $headLevel = $level+$off+1; # Heading level
7069             # my $methodSignature = "$name($parametersAsString)"; # Method(signature)
7070 736         7728  
7071 736         2944 $methods{$name}++; # Methods that have been coded as opposed to being generated
7072 736 50       2944 $methodParms{$name} = $name; # Method names not including parameters
7073 736 50       3312 $methodParms{$name.'X'} = $name if $methodX; # Method names not including parameters
7074 736 50       2576 $methodX{$name}++ if $methodX; # Method names that have an X version
7075 0         0 if (my $u = $userFlags{$name}) # Add names of any generated methods
  0         0  
7076             {$methodParms{$_} = $name for @{$u->[2]}; # Generated names array
7077             }
7078 736         1840  
7079             my @method; # Accumulate method documentation
7080 736         3312  
7081 736 100       3312 if (1) # Section title
7082 736         3680 {my $h = $private ? 2 : $headLevel;
7083 736         5520 my $title = $title{$name} = qq($name($signatureNames)); # Method title
7084             push @method, "\n=head$h $title\n\n$comment\n"; # Method description
7085             }
7086              
7087 736 50 33     10672 push @method, indentString(formatTable
  1472   33     12512  
7088             ([map{[$parameters[$_], $parmDescriptions[$_]]} keys @parameters],
7089             [qw(Parameter Description)]), ' ')
7090             if $parmNames and $parmDescriptions and $parmDescriptions !~ /\A#/; # Add parameter description if present
7091              
7092 736 50       5152 push @method, # Add user documentation
7093             "\n".$userFlags{$name}[0]."\n" if $userFlags{$name}[0];
7094 736 50       3312  
7095             push @method, # Add example
7096             "\nB\n\n $example" if $example;
7097 736 50       2944  
7098             push @method, # Produces
7099             "\n$produces" if $produces;
7100 736 100       3312  
7101 368 50       4048 if (my $examples = $examples{$name}) # Format examples
7102 368         1104 {if (my @examples = @$examples)
  6624         19136  
7103             {push @method, '\nB\m', map {" $_"} @examples;
7104             }
7105             }
7106 736 50       2944  
7107             push @method, <
7108              
7109             You can provide you own implementation of this method in your calling package
7110             via:
7111              
7112             sub $name {...}
7113              
7114             if you wish to override the default processing supplied by this method.
7115              
7116             END
7117              
7118 736 100       3312  
7119             push @method, <
7120              
7121             You must supply an implementation of this method in your package via:
7122              
7123             sub $name {...}
7124              
7125             END
7126 736 50       2944  
7127             push @method, # Add a note about the availability of an X method
7128             "\nUse B<${name}X> to execute L<$name|/$name> but B '$name'".
7129             " instead of returning B" if $methodX;
7130 736 100       3680  
7131             push @method, # Static method
7132             "\nThis is a static method and so should either be imported or invoked as:\n\n".
7133             " $package\:\:$name\n" if $static;
7134 736 50       1840  
7135             push @method, # Exported
7136             "\nThis method can be imported via:\n\n".
7137             " use $package qw($name)\n" if $exported;
7138 736 100       4784  
7139 368 50       2576 if (my $s = $synonymTargetSource{$name}) # Synonym
7140 368         2576 {if (keys %$s)
7141 368         2576 {for my $source(sort keys %$s)
7142             {push @method, "\nB<$source> is a synonym for L<$name|/$name>.\n";
7143             }
7144             }
7145             }
7146 736 100       2576  
  736         4784  
7147 736         12880 push @{$private ? \@private : \@doc}, @method; # Save method documentation in correct section
7148             push @ctags, join "|", $name, qq/($signatureNames)/, # Ctags line
7149             q(: ).$comment =~ s(\|) (_)gr, q();
7150             }
7151             elsif ($level and $line =~ # Documentation for a generated lvalue * method = sub name comment
7152 0         0 /\A\s*genLValue(?:\w+?)Methods\s*\x28q(?:w|q)?\x28(\w+)\x29\x29;\s*#\s*(.+?)\s*\Z/)
7153 0 0       0 {my ($name, $description) = ($1, $2); # Name from sub, description from comment
7154 0         0 next if $description =~ /\A#/; # Private method if #P
7155 0         0 my $headLevel = $level+$off+1; # Heading level
7156 0         0 $methodParms{$name} = $name; # Method names not including parameters
7157 0         0 $comment {$name} = $description =~ s(\A#) ()gsr; # Description of method
7158             push @doc, "\n=head$headLevel $name :lvalue\n\n$description\n"; # Method description
7159             }
7160             }
7161 368 50       6256  
7162 0         0 if (isSubInPackage($package, q(processModuleDescription))) # Process module description
7163             {my $s = $package.q(::processModuleDescription);
7164 0         0 # my $c = qq(\&$s(reloadHashes(\\%moduleDescription))); # Fails with Data::Edit::Xml
7165 0         0 my $c = qq(\&$s(\\%moduleDescription));
7166 0 0       0 eval qq($c);
7167             cluck $@ if $@;
7168             }
7169 368         1104  
7170 368         4048 if (1) # Write ctags for Geany
7171 368         1840 {my $c = join "\n", "# format=pipe", sort @ctags;
7172 368         1104 my $h = $ENV{HOME};
7173 368         2944 my $p = $package;
7174 368         1472 my $f = fpe($h, qw(.config geany tags), $p.q(.pl), q(tags));
  368         8464  
7175 368         1472 eval {owf($f, $c)};
  368         2576  
7176             eval {dumpFile(fpe($h, qw(.config help), $p, q(txt)), \%moduleDescription)};# Write module description so it can be reused elsewhere
7177             }
7178 368 50       3680  
7179 0         0 if (keys %genHashs) # Document generated objects
7180 0         0 {push @doc, qq(\n), qq(=head1 Hash Definitions), qq(\n);
7181 0         0 for my $package (sort keys % genHashs)
7182 0         0 {my @i; my @o; # Input and output attributes
  0         0  
7183 0   0     0 for my $attribute(sort keys %{$genHashs{$package}})
7184 0   0     0 {my $comment = $genHashs{$package}{$attribute} // q();
7185             my $flags = $genHashFlags{$package}{$attribute} // q();
7186              
7187 0         0 # my $a = qq(B<$attribute> - $comment\n); # Attribute description
7188 0 0       0 my $a = qq(=head4 $attribute\n\n$comment\n); # Attribute description
  0         0  
7189             push @{$flags =~ m(I)s ? \@i : \@o}, $a;
7190 0 0       0  
7191 0         0 if ($title{$attribute}) # Record the title of the attribute so we can link to it via L[name].
7192             {lll "Attribute: $attribute defined more than once"
7193             }
7194 0         0 else
7195             {$title{$attribute} = $attribute;
7196             }
7197             }
7198              
7199 0         0 push @doc, qq(\n), qq(=head2 $package Definition), qq(\n), # Attributes header
7200             $genHashPackage{$package}, qq(\n);
7201 0 0       0  
7202 0         0 if (@i) # Input fields
7203             {push @doc, qq(\n), qq(=head3 Input fields), qq(\n), @i;
7204             }
7205 0 0       0  
7206 0         0 if (@o) # Output fields
7207             {push @doc, qq(\n), qq(=head3 Output fields), qq(\n), @o;
7208             }
7209             }
7210             }
7211 368 50       4784  
7212 0         0 if (my @a = sort keys %attributes)
7213 0         0 {push my @d, qq(\n), qq(=head1 Attributes\n\n);
7214             push @d, <<"END";
7215             The following is a list of all the attributes in this package. A method coded
7216             with the same name in your package will over ride the method of the same name
7217             in this package and thus provide your value for the attribute in place of the
7218             default value supplied for this attribute by this package.
7219              
7220             `head2 Replaceable Attribute List
7221              
7222 0         0 END
7223 0         0 push @d, join ' ', @a, "\n\n";
7224 0         0 for my $name(@a)
7225 0         0 {my $d = $attributeDescription{$name};
7226             push @d, qq(=head2 $name\n\n$d\n\n);
7227 0         0 }
7228             push @doc, @d;
7229             }
7230 368 50       6256  
7231 0         0 if (my @r = sort keys %replace)
7232             {push @doc, qq(\n), <
7233             `head1 Optional Replace Methods
7234              
7235             The following is a list of all the optionally replaceable methods in this
7236             package. A method coded with the same name in your package will over ride the
7237             method of the same name in this package providing your preferred processing for
7238             the replaced method in place of the default processing supplied by this
7239             package. If you do not supply such an over riding method, the existing method
7240             in this package will be used instead.
7241              
7242             `head2 Replaceable Method List
7243              
7244 0         0 END
7245             push @doc, join ' ', @r, "\n\n";
7246             }
7247 368         1472  
7248 368         3312 if (1) # Alphabetic listing of methods that still need examples
7249 368         4048 {my %m = %methods;
7250 368         2944 delete @m{$_, "$_ :lvalue"} for keys %examples;
7251 368         1840 delete @m{$_, "$_ :lvalue"} for keys %private;
7252 368         1104 my $n = keys %m;
7253 368 50       1840 my $N = keys %methods;
7254             say STDERR formatTable(\%m), "\n$n of $N methods still need tests" if $n;
7255             }
7256 368 50       2208  
7257 0         0 if (keys %isUseful) # Alphabetic listing of immediately useful methods
7258 0         0 {my @d;
7259             push @d, <
7260              
7261             `head1 Immediately useful methods
7262              
7263             These methods are the ones most likely to be of immediate use to anyone using
7264             this module for the first time:
7265              
7266 0         0 END
  0         0  
7267 0         0 for my $m(sort {lc($a) cmp lc($b)} keys %isUseful)
7268 0         0 {my $c = $isUseful{$m};
7269 0 0       0 my $s = $signatureNames{$m};
7270 0         0 my $n = $m.($s ? qq/($s)/ : q());
7271             push @d, "L<$n|/$n>\n\n$c\n"
7272 0         0 }
7273             push @d, <
7274              
7275 0         0 END
7276             unshift @doc, (shift @doc, @d) # Put first after title
7277             }
7278 368 50       5152  
7279             push @doc, qq(\n\n=head1 Private Methods), @private if @private; # Private methods in a separate section if there are any
7280 368 50       2208  
7281 368         1104 if (keys %synonymTarget) # Synonyms
7282             {my @s;
7283 368         3680 my $line;
7284 368         2208 for my $source(sort keys %synonymTarget)
7285 368   33     2208 {my $target = $synonymTarget{$source};
7286 368         6992 my $comment = $comment{$target} // confess "No comment for $target\n";
7287 368         3680 $comment =~ s(\..*\Z) (\.)s;
7288             push @s, qq(B<$source> is a synonym for L<$target|/$target> - $comment);
7289 368         1472 }
7290 368         1840 my $s = join q(\n\n), @s;
7291             push @doc, qq(\n\n=head1 Synonyms\n\n$s\n);
7292             }
7293 368         1104  
7294 368         1104 push @doc, qq(\n\n=head1 Index\n\n);
7295 368         736 if (1)
7296 368         9200 {my $n = 0;
  368         4048  
7297 736         2576 for my $s(sort {lc($a) cmp lc($b)} keys %methodParms) # Alphabetic listing of methods
7298 736         2208 {my $t = $methodParms{$s};
7299 736 50 33     6992 my $c = $comment{$s};
7300 736         5152 if ($c and $t)
7301 736         4784 {$c =~ s(\..*\Z) (\.)s;
7302             push @doc, ++$n.qq( L<$s|/$t> - $c\n);
7303             }
7304             }
7305             }
7306 368 50       5152  
7307 0         0 if (keys %exported) # Exported methods available
7308             {push @doc, <<"END";
7309              
7310              
7311             `head1 Exports
7312              
7313             All of the following methods can be imported via:
7314              
7315             use $package qw(:all);
7316              
7317             Or individually via:
7318              
7319             use $package qw();
7320              
7321              
7322             END
7323 0         0  
7324 0         0 my $n = 0;
  0         0  
7325 0         0 for my $s(sort {lc($a) cmp lc($b)} keys %exported) # Alphabetic listing of exported methods
7326             {push @doc, ++$n." L<$s|/$s>\n"
7327             }
7328             }
7329 368         4416  
7330             push @doc, <
7331             `head1 Installation
7332              
7333             This module is written in 100% Pure Perl and, thus, it is easy to read,
7334             comprehend, use, modify and install via B:
7335              
7336             sudo cpan install $package
7337              
7338             `head1 Author
7339              
7340             L
7341              
7342             L
7343              
7344             `head1 Copyright
7345              
7346             Copyright (c) 2016-2021 Philip R Brenan.
7347              
7348             This module is free software. It may be used, redistributed and/or modified
7349             under the same terms as Perl itself.
7350             END
7351 368 50       2208  
7352 0         0 if (keys %collaborators) # Acknowledge any collaborators
7353             {push @doc,
7354             '\n=head1 Acknowledgements\m'.
7355             'Thanks to the following people for their help with this module:\m'.
7356 0         0 '=over\m';
7357 0         0 for(sort keys %collaborators)
7358 0         0 {my $p = "L<$_|mailto:$_>";
7359 0         0 my $r = $collaborators{$_};
7360             push @doc, "=item $p\n\n$r\n\n";
7361 0         0 }
7362             push @doc, '=back\m';
7363             }
7364 368         2944  
7365             push @doc, '=cut\m'; # Finish documentation
7366 368 50       2208  
7367 0         0 if (keys %methodX) # Insert X method definitions
7368 0         0 {my @x;
7369 0         0 for my $x(sort keys %methodX)
7370             {push @x, ["sub ${x}X", "{&$x", "(\@_) || die '$x'}"];
7371 0         0 }
7372             push @doc, formatTableBasic(\@x);
7373             }
7374 368         2944  
7375 736 50       2576 for my $name(sort keys %userFlags) # Insert generated method definitions
7376 736 50       2944 {if (my $doc = $userFlags{$name})
7377             {push @doc, $doc->[1] if $doc->[1];
7378             }
7379             }
7380 368         1472  
7381             push @doc, <<'END'; # Standard test sequence
7382              
7383             # Tests and documentation
7384              
7385             sub test
7386             {my $p = __PACKAGE__;
7387             binmode($_, ":utf8") for *STDOUT, *STDERR;
7388             return if eval "eof(${p}::DATA)";
7389             my $s = eval "join('', <${p}::DATA>)";
7390             $@ and die $@;
7391             eval $s;
7392             $@ and die $@;
7393             1
7394             }
7395              
7396             test unless caller;
7397             END
7398 368 50       1472  
7399 0         0 if (@synopsis) # Add the generated synopsis at the front if present }
7400             {unshift @doc, q(=head1 Synopsis), @synopsis;
7401             }
7402 368         1840  
7403 13616         27232 for(@doc) # Expand snippets in documentation
7404 13616         21344 {s/\\m/\n\n/gs; # Double new line
7405 13616         22448 s/\\n/\n/gs; # Single new line
7406 13616         28336 s/\\x//gs; # Break
7407             s/`/=/gs;
7408             }
7409 368         5152  
7410             my $doc = expandWellKnownUrlsInPerlFormat(join "\n", @doc); # Create documentation
7411 368         6256  
7412 736         4048 for my $m(sort keys %title) # Links to titles
7413 736         30544 {my $t = $title{$m};
7414             $doc =~ s(L\[$m\]) (L<$m|/"$t">)gs;
7415             }
7416 368 50       2944  
7417 0 0       0 unless($sourceIsString) # Update source file
7418 0         0 {if (@synopsis) # Remove existing synopsis if adding a generated one
7419             {$source =~ s(=head1 Synopsis.*?(=head1 Description)) ($1)s;
7420             }
7421 0         0  
7422             $source =~ s/\n+=head1 Description.+?\n+1;\n+/\n\n$doc\n1;\n/gs; # Edit module source from =head1 description to final 1;
7423 0 0       0  
7424 0         0 if ($source ne $Source) # Save source only if it has changed and came from a file
7425 0         0 {overWriteFile(filePathExt($perlModule, qq(backup)), $Source); # Backup module source
7426             overWriteFile($perlModule, $source); # Write updated module source
7427             }
7428             }
7429              
7430 368         54464 $doc
7431             } # updateDocumentation
7432              
7433 0     0 1 0 sub docUserFlags($$$$) #P Generate documentation for a method by calling the extractDocumentationFlags method in the package being documented, passing it the flags for a method and the name of the method. The called method should return the documentation to be inserted for the named method.
7434 0         0 {my ($flags, $perlModule, $package, $name) = @_; # Flags, file containing documentation, package containing documentation, name of method to be processed
7435             my $s = <
7436             ${package}::extractDocumentationFlags("$flags", "$name");
7437             END
7438 371     371   5936  
  371         742  
  371         973504  
7439 0         0 use Data::Dump qw(dump);
7440 0 0       0 my $r = eval $s;
7441 0         0 confess "$s\n". dump($@, $!) if $@;
7442             $r
7443             }
7444              
7445 0     0 1 0 sub updatePerlModuleDocumentation($) #P Update the documentation in a B<$perlModule> and display said documentation in a web browser.
7446 0 0       0 {my ($perlModule) = @_; # File containing the code of the perl module
7447 0         0 -e $perlModule or confess "No such file:\n$perlModule\n";
7448             updateDocumentation($perlModule); # Update documentation
7449 0         0  
7450             zzz("pod2html --infile=$perlModule --outfile=zzz.html && ". # View documentation
7451             " opera zzz.html && ".
7452             " (sleep 5 && rm zzz.html pod2htmd.tmp) &");
7453             }
7454              
7455             sub extractPythonDocumentationFromFiles(@) #P Extract python documentation from the specified files
7456             {my (@sources) = @_; # Python source files
7457              
7458             my $docRe = qr(['"]{3}); # Doc string marker
7459              
7460             my sub formatDocString($) # Format a doc string
7461             {my ($s) = @_; # String
7462             return $s;
7463             return '' unless $s;
7464             $s =~ s(input\s*:) (

Input:)gsi;

7465             $s =~ s(output\s*:) (

Output:)gsi;

7466             $s =~ s(return\s*:) (

Return:)gsi;

7467             $s =~ s(Parameters\s*\-+) (

Parameters:)gsi;

7468             $s =~ s(Returns\s*\-+) (

Returns:)gsi;

7469             $s =~ s(\.?\s*\Z) (.)s;
7470             $s
7471             };
7472              
7473             my %parameters; # Parameters for each def
7474             my %comments; # Comments for each def
7475             my %tests; # Tests for each def
7476             my %testsCommon; # Common line for tests
7477             my %classDefinitions; # Class definitions
7478             my %classFiles; # Class files
7479             my %errors; # Errors by source file
7480              
7481             for my $source(@sources) # Each source file
7482             {my @text = readFile($source); # Read source file
7483             my $lines = @text;
7484             my $class = fne $source;
7485              
7486             my sub currentLine {$lines - @text}; # Current line number
7487              
7488             my sub getDocString # Get a doc string
7489             {my @c;
7490              
7491             my sub strip # Strip leading and trailing quotes
7492             {return unless @c;
7493             $c[0] =~ s(\A\s*$docRe) ();
7494             $c[-1] =~ s($docRe\s*\Z) ();
7495             $c[-1] =~ s(\.?\s*\Z) (.);
7496             join "\n", @c
7497             };
7498              
7499             if (my $c = shift @text) # Doc string
7500             {if ($c =~ m(\A\s*$docRe.*\S)) # Quotes and text on same line
7501             {@c = $c;
7502             while(@text and $c !~ m($docRe\s*\Z)i)
7503             {push @c, $c = shift @text;
7504             }
7505             return strip
7506             }
7507             elsif ($c =~ m(\A\s*$docRe\s*\Z)) # Just quotes
7508             {@c = $c;
7509             while(@text and $text[0] !~ m($docRe\s*\Z)i)
7510             {push @c, shift @text;
7511             }
7512             return strip
7513             }
7514             }
7515             q()
7516             };
7517              
7518             my sub error(@) # Record an error
7519             {my (@e) = @_; # Error strings
7520             push $errors{$source}->@*, join ' ', @e;
7521             };
7522              
7523             while(@text) # Parse text of module
7524             {my $text = shift @text;
7525              
7526             if ($text =~ m(\A\s*def\s+(.*?)\((.*?)\)\s*:.*?#(\w*)\s+(.*))i) # Def function(parameter1 =1, parameter2 = 2) : # first, second
7527             {my ($def, $parameters, $attributes, $parameterDefinitions) = @{^CAPTURE};
7528              
7529             my @p = split m/\s*,\s*/, $parameters;
7530             my @d = split m/\s*,\s*/, $parameterDefinitions;
7531             my $p = @p; my $d = @d;
7532             if ($p != $d)
7533             {my $l = currentLine;
7534             error qq(Number of parameters specified: $d does not equal),
7535             qq(number of parameters documented: $d on line: $l)
7536             }
7537             else
7538             {for my $p(@p)
7539             {my $c = ucfirst shift @d;
7540             $c =~ s(\.?\s*\Z) ()s;
7541             push $parameters{$class}{$def}->@*, [$p, $c];
7542             }
7543             }
7544              
7545             $comments{$class}{$def} = getDocString
7546             }
7547             elsif ($text =~ m(\A\s*def\s+(.*?)\((.*?)\)\s*:)i) # Def function(parameter1 =1, parameter2 = 2) :
7548             {my ($def, $parameters) = @{^CAPTURE};
7549             my $doc = $comments{$class}{$def} = getDocString;
7550              
7551             my @p = split m/\s*,\s*/, $parameters; # Parameters defined by a Python subroutine
7552             my %p;
7553              
7554             for my $line(split m/\n/, $doc) # Check for parameter definitions
7555             {if ($line =~ m(\A\s*:\s*param\s*(.*?)\s*:\s*(.*?)\s*\Z))
7556             {my ($parm, $comment) = @{^CAPTURE};
7557             push $parameters{$class}{$def}->@*, [$parm, $comment];
7558             $parm =~ s(\A\s*(bool|str)\s*) ()s; # Remove parameter type when present to get parameter name
7559             $p{$parm} = $comment;
7560             }
7561             }
7562              
7563             if (keys %p) # Use parameter definitions if present
7564             {if (@p != keys %p)
7565             {error q(Differing numbers of parameters described in comment and code);
7566             }
7567             for my $p(@p)
7568             {if (!$p{$p})
7569             {error qq(Parameter $p not described by :param);
7570             }
7571             delete $p{$p}
7572             }
7573             if (keys %p)
7574             {my $b = join ', ', sort keys %p;
7575             error qq(Parameters $b defined by :param but not present in defn);
7576             }
7577             }
7578             else # Use parameter definitions from a Python subroutine
7579             {push $parameters{$class}{$def}->@*, [@p];
7580             }
7581             error qq(No parameter definitions for $class.$def)
7582             }
7583             elsif ($text =~ m(\A\s*class\s+(.*?)\s*:)) # Class - assume there is no more than one class per file for the moment
7584             {$classFiles{$class} = $class = $1;
7585             $classDefinitions{$class} = getDocString
7586             }
7587             elsif ($text =~ m(\A\s*if\s+1\s*:\s*#T(\w+))) # Test as if 1: statement
7588             {my $test = $1;
7589             my @test;
7590             while(@text and $text[0] !~ m(\A\s*\Z))
7591             {push @test, trim shift @text;
7592             }
7593             push $tests{$class}{$test}->@*, @test;
7594             }
7595             elsif ($text =~ m(\A(.*?)#T(\w+))) # Test on a single line
7596             {my ($text, $test) = @{^CAPTURE};;
7597             push @{$testsCommon{$test}}, $text;
7598             }
7599             }
7600             error qq(No class in file $source) unless $class
7601             }
7602              
7603             my $d = genHash(q(Data::Table::Text::Python::Documentation), # Documentation extracted from Python source files
7604             parameters => \%parameters, # Parameters for each def
7605             comments => \%comments, # Comments for each def
7606             tests => \%tests, # Tests for each def
7607             testsCommon => \%testsCommon, # Common line for tests
7608             classDefinitions => \%classDefinitions, # Class definitions
7609             classFiles => \%classFiles, # Class files
7610             errors => \%errors, # Errors encountered
7611             );
7612              
7613             my %opCodes = # Translate these opcodes
7614             (neg => q(- ) ,
7615             abs => q(abs),
7616             eq => q(==) ,
7617             iadd => q(+=) ,
7618             add => q(+ ) ,
7619             isub => q(-=) ,
7620             sub => q(- ) ,
7621             imul => q(*=) ,
7622             mul => q(* ) ,
7623             itruediv => q(/=) ,
7624             truediv => q(/ ) );
7625              
7626             my sub classComment($) # Comment describing a class
7627             {my ($class) = @_; # Class
7628             $d->classDefinitions->{$class} // q()
7629             };
7630              
7631             my @h; # Generated mark down
7632              
7633             push @h, <
7634            

Table of contents

7635            

7636             END
7637             for my $class(sort keys $d->parameters->%*) # Table of contents
7638             {my $comment = formatDocString classComment($class);
7639             my $m = stringMd5Sum $class;
7640             push @h, <
7641            
$class$comment
7642             END
7643             }
7644             push @h, <
7645            
7646             END
7647              
7648             for my $class(sort keys $d->parameters->%*) # Each class
7649             {my $comment = formatDocString classComment $class;
7650             my $m = stringMd5Sum $class;
7651             push @h, <
7652            

Class: $class

7653            

$comment

7654             END
7655              
7656             for my $defn(sort keys $d->parameters->{$class}->%*) # Each class method
7657             {my $comment = formatDocString $d->comments->{$class}{$defn};
7658             my $title = $defn;
7659             my $shortOp = $defn =~ s(_) ()gr;
7660             if (my $op = $opCodes{$shortOp})
7661             {$title .= " **$op**" unless $op eq $shortOp;
7662             }
7663             push @h, trim <
7664            

$title

7665             $comment
7666            

Parameters

7667            
7668            
NameDescription
7669             END
7670             if (my $parameters = $d->parameters->{$class}{$defn}) # Parameters
7671             {for my $p(@$parameters)
7672             {my ($n, $c) = (@$p, (q()) x 2);
7673             push @h, <
7674            
$n$c
7675             END
7676             }
7677             }
7678             push @h, <
7679            
7680             END
7681             my $examples = join "\n", map {nws $_} $d->tests->{$class}{$defn}->@*;
7682              
7683             push @h, trim <
7684            

Examples

7685            
 
7686             $examples
7687            
7688            
7689             END
7690             }
7691             }
7692              
7693             if (my $errors = $d->errors) # Errors by source file
7694             {push @h, q(

Possible improvements to documentation

);
7695              
7696             for my $file(sort keys %$errors) # Each file with errors
7697             {push @h, <
7698            

$file

7699            

7700             END
7701             for my $error($$errors{$file}->@*)
7702             {push @h, <
7703            
$error
7704             END
7705             }
7706             push @h, <
7707            
7708             END
7709             }
7710             }
7711              
7712             join "\n", @h;
7713             } # extractPythonDocumentationFromFiles
7714              
7715             #-------------------------------------------------------------------------------
7716             # Export - eeee
7717             #-------------------------------------------------------------------------------
7718 371     371   13727  
  371         1113  
  371         23373  
7719             use Exporter qw(import);
7720 371     371   2968  
  371         742  
  371         1070706  
7721             use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
7722              
7723             # containingFolder
7724              
7725             @ISA = qw(Exporter);
7726             @EXPORT = qw(formatTable);
7727             @EXPORT_OK = qw(
7728             absFromAbsPlusRel addCertificate addLValueScalarMethods adopt appendFile
7729             arrayProduct arraySum arrayTimes arrayToHash asciiToHexString assertPackageRefs
7730             assertRef awsCurrentAvailabilityZone awsCurrentInstanceId
7731             awsCurrentInstanceType awsCurrentIp awsCurrentLinuxSpotPrices awsCurrentRegion
7732             awsEc2DescribeInstancesGetIPAddresses
7733             awsEc2CreateImage awsEc2DescribeImages awsEc2DescribeInstanceType
7734             awsEc2DescribeInstances awsEc2DescribeInstancesGetIPAddresses
7735             awsEc2DescribeSpotInstances awsEc2FindImagesWithTagValue
7736             awsEc2InstanceIpAddress awsEc2ReportSpotInstancePrices
7737             awsEc2RequestSpotInstances awsExecCli awsExecCliJson
7738             awsIp awsMetaData awsParallelGatherFolder
7739             awsParallelIpAddresses awsParallelPrimaryInstanceId awsParallelPrimaryIpAddress
7740             awsParallelProcessFiles awsParallelSecondaryIpAddresses awsParallelSpreadFolder
7741             awsR53a awsR53aaaa
7742             binModeAllUtf8 boldString boldStringUndo
7743             call callSubInOverlappedParallel callSubInParallel checkFile checkFilePath
7744             checkFilePathDir checkFilePathExt checkKeys childPids chooseStringAtRandom
7745             clearFolder cmpArrays confirmHasCommandLineCommand
7746             containingFolderName containingPowerOfTwo contains
7747             convertUtf8ToUtf32 convertUtf32ToUtf8
7748             convertDocxToFodt convertImageToJpx convertPerlToJavaScript convertUnicodeToXml copyBinaryFile
7749             copyBinaryFileMd5Normalized copyBinaryFileMd5NormalizedCreate
7750             copyBinaryFileMd5NormalizedGetCompanionContent copyFile copyFileFromRemote
7751             copyFileMd5Normalized copyFileMd5NormalizedCreate copyFileMd5NormalizedDelete
7752             copyFileMd5NormalizedGetCompanionContent copyFileMd5NormalizedName
7753             copyFileToFolder copyFileToRemote copyFolder copyFolderToRemote
7754             countFileExtensions countFileTypes countOccurencesInString createEmptyFile
7755             currentDirectory currentDirectoryAbove cutOutImagesInFodtFile
7756             dateStamp dateTimeStamp dateTimeStampName ddd deSquareArray decodeBase64
7757             decodeJson deduplicateSequentialWordsInString detagString
7758             downloadGitHubPublicRepo dumpFile dumpFileAsJson dumpGZipFile
7759             dumpTempFile dumpTempFileAsJson
7760             enclosedReversedString enclosedReversedStringUndo enclosedString
7761             enclosedStringUndo encodeBase64 encodeJson evalFile evalGZipFile
7762             execPerlOnRemote expandNewLinesInDocumentation expandWellKnownUrlsInDitaFormat
7763             expandWellKnownUrlsInHtmlFormat expandWellKnownWordsAsUrlsInHtmlFormat expandWellKnownWordsAsUrlsInMdFormat
7764             expandWellKnownUrlsInHtmlFromPerl expandWellKnownUrlsInPod2Html
7765             expandWellKnownUrlsInPerlFormat extractCodeBlock
7766             extractPythonDocumentationFromFiles evalFileAsJson
7767             fe fff fileInWindowsFormat fileLargestSize fileList fileMd5Sum fileModTime
7768             fileOutOfDate filePath filePathDir filePathExt fileSize findDirs
7769             findFileWithExtension findFiles firstFileThatExists firstNChars
7770             flattenArrayAndHashValues fn fne folderSize formatHtmlAndTextTables
7771             forEachKeyAndValue
7772             formatHtmlAndTextTablesWaitPids formatHtmlTable formatHtmlTablesIndex
7773             formatSourcePodAsHtml
7774             formatString formatTableBasic formattedTablesReport fp fpd fpe fpf fpn
7775             fullFileName fullyQualifiedFile fullyQualifyFile
7776             genClass genHash genLValueArrayMethods genLValueHashMethods
7777             genLValueScalarMethods genLValueScalarMethodsWithDefaultValues getSubName
7778             guidFromMd5 guidFromString
7779             hexToAsciiString hostName htmlToc
7780             imageSize indentString indexOfMax indexOfMin intersectionOfHashKeys
7781             intersectionOfHashesAsArrays invertHashOfHashes ipAddressViaArp isBlank
7782             ipAddressOfHost
7783             isFileUtf8 isSubInPackage
7784             javaPackage javaPackageAsFileName javaScriptExports
7785             keyCount
7786             lll loadArrayArrayFromLines loadArrayFromLines loadArrayHashFromLines loadHash
7787             loadHashArrayFromLines loadHashFromLines loadHashHashFromLines
7788             lengthOfLongestSubArray lpad
7789             makeDieConfess makePath makePathRemote matchPath mathematicalBoldItalicString
7790             mathematicalBoldItalicStringUndo mathematicalBoldString
7791             mathematicalBoldStringUndo mathematicalItalicString mathematicalMonoSpaceString
7792             mathematicalMonoSpaceStringUndo mathematicalSansSerifBoldItalicString
7793             mathematicalSansSerifBoldItalicStringUndo mathematicalSansSerifBoldString
7794             mathematicalSansSerifBoldStringUndo mathematicalSansSerifItalicString
7795             mathematicalSansSerifItalicStringUndo mathematicalSansSerifString
7796             mathematicalSansSerifStringUndo max md5FromGuid mergeFolder
7797             mergeFolderFromRemote microSecondsSinceEpoch min mmm
7798             moveFileNoClobber moveFileWithClobber
7799             nameFromFolder nameFromString nameFromStringRestrictedToTitle newProcessStarter
7800             newServiceIncarnation newUdsr newUdsrClient newUdsrServer numberOfCpus
7801             numberOfLinesInFile numberOfLinesInString nws
7802             onAws onAwsPrimary onAwsSecondary overWriteBinaryFile overWriteFile
7803             overrideAndReabsorbMethods owf
7804             overWriteHtmlFile overWritePerlCgiFile
7805             pad ppp parseCommandLineArguments parseDitaRef parseFileName
7806             parseIntoWordsAndStrings parseXmlDocType partitionStringsOnPrefixBySize
7807             powerOfTwo printQw processFilesInParallel processJavaFilesInParallel
7808             processSizesInParallel
7809             quoteFile
7810             randomizeArray
7811             readBinaryFile readFile readFileFromRemote readFiles readGZipFile readStdIn readUtf16File
7812             rectangularArray rectangularArray2
7813             relFromAbsAgainstAbs reloadHashes removeBOM removeDuplicatePrefixes
7814             removeFilePathsFromStructure removeFilePrefix removeFoldersFromDataStructure
7815             replaceStringWithString reportAttributeSettings reportAttributes
7816             reportExportableMethods reportReplacableMethods reportSettings retrieveFile
7817             runInParallel runInSquareRootParallel
7818             s3DownloadFolder s3FileExists s3ListFilesAndSizes s3ReadFile s3ReadString
7819             s3WriteFile s3WriteString s3ZipFolder s3ZipFolders saveCodeToS3 saveSourceToS3
7820             saveAwsDomain saveAwsIp
7821             searchDirectoryTreesForMatchingFiles searchDirectoryTreeForSubFolders setFileExtension setIntersection
7822             setIntersectionOfArraysOfStrings setIntersectionOverUnion setPackageSearchOrder
7823             setPartitionOnIntersectionOverUnion
7824             setPartitionOnIntersectionOverUnionOfHashStringSets
7825             setPartitionOnIntersectionOverUnionOfHashStringSetsInParallel
7826             setPartitionOnIntersectionOverUnionOfSetsOfWords
7827             setPartitionOnIntersectionOverUnionOfStringSets setPermissionsForFile setUnion
7828             showGotVersusWanted
7829             squareArray startProcess storeFile stringMd5Sum
7830             stringsAreNotEqual subScriptString subScriptStringUndo sumAbsAndRel
7831             summarizeColumn superScriptString superScriptStringUndo swapFilePrefix
7832             swapFolderPrefix syncFromS3InParallel syncToS3InParallel
7833             temporaryDirectory temporaryFile temporaryFolder timeStamp
7834             transitiveClosure trim
7835             unbless
7836             unionOfHashKeys unionOfHashesAsArrays uniqueNameFromFile updateDocumentation
7837             updatePerlModuleDocumentation userId
7838             versionCode versionCodeDashed
7839             waitForAllStartedProcessesToFinish writeBinaryFile writeFile writeTempFile writeFileToRemote
7840             writeFiles writeGZipFile writeStructureTest wwwDecode wwwEncode
7841             wwwHeader wwwGitHubAuth
7842             xxx xxxr
7843             yyy
7844             zzz
7845             );
7846              
7847             if (0) # Format exports
7848             {my $width = 80;
7849             binModeAllUtf8;
7850             my %e = map {$_=>1} @EXPORT_OK;
7851             my @e = sort keys %e;
7852             my @r = '';
7853             for my $i(keys @e)
7854             {my $e = $e[$i];
7855             my $E = $i ? $e[$i-1] : q( );
7856             if (length($r[-1]) + 1 + length($e) > $width or
7857             substr($e, 0, 1) ne substr($E, 0, 1))
7858             {push @r, '';
7859             }
7860             $r[-1] .= qq( $e);
7861             }
7862             say STDERR "qw(", join("\n", @r);
7863             exit;
7864             }
7865              
7866             %EXPORT_TAGS = (all=>[@EXPORT, @EXPORT_OK]);
7867              
7868             #D
7869             # podDocumentation
7870             #C mim@cpan.org Testing on windows
7871              
7872             =pod
7873              
7874             =encoding utf-8
7875              
7876             =head1 Name
7877              
7878             Data::Table::Text - Write data in tabular text format.
7879              
7880             =for html
7881            

7882              
7883             =head1 Synopsis
7884              
7885             use Data::Table::Text;
7886              
7887             # Print a table:
7888              
7889             my $d =
7890             [[qq(a), qq(b\nbb), qq(c\ncc\nccc\n)],
7891             [qq(1), qq(1\n22), qq(1\n22\n333\n)],
7892             ];
7893              
7894             my $t = formatTable($d, [qw(A BB CCC)]);
7895              
7896             ok $t eq <
7897             A BB CCC
7898             1 a b c
7899             bb cc
7900             ccc
7901             2 1 1 1
7902             22 22
7903             333
7904             END
7905              
7906             # Print a table containing tables and make it into a report:
7907              
7908             my $D = [[qq(See the\ntable\nopposite), $t],
7909             [qq(Or\nthis\none), $t],
7910             ];
7911              
7912              
7913             my $T = formatTable($D, [qw(Description Table)], head=><
7914             Table of Tables.
7915              
7916             Table has NNNN rows each of which contains a table.
7917             END
7918              
7919             ok $T eq <
7920             Table of Tables.
7921              
7922             Table has 2 rows each of which contains a table.
7923              
7924              
7925             Description Table
7926             1 See the A BB CCC
7927             table 1 a b c
7928             opposite bb cc
7929             ccc
7930             2 1 1 1
7931             22 22
7932             333
7933             2 Or A BB CCC
7934             this 1 a b c
7935             one bb cc
7936             ccc
7937             2 1 1 1
7938             22 22
7939             333
7940             END
7941              
7942             # Print an array of arrays:
7943              
7944             my $aa = formatTable
7945             ([[qw(A B C )],
7946             [qw(AA BB CC )],
7947             [qw(AAA BBB CCC)],
7948             [qw(1 22 333)]],
7949             [qw (aa bb cc)]);
7950              
7951             ok $aa eq <
7952             aa bb cc
7953             1 A B C
7954             2 AA BB CC
7955             3 AAA BBB CCC
7956             4 1 22 333
7957             END
7958              
7959             # Print an array of hashes:
7960              
7961             my $ah = formatTable
7962             ([{aa=> "A", bb => "B", cc => "C" },
7963             {aa=> "AA", bb => "BB", cc => "CC" },
7964             {aa=> "AAA", bb => "BBB", cc => "CCC" },
7965             {aa=> 1, bb => 22, cc => 333 }]);
7966              
7967             ok $ah eq <
7968             aa bb cc
7969             1 A B C
7970             2 AA BB CC
7971             3 AAA BBB CCC
7972             4 1 22 333
7973             END
7974              
7975             # Print a hash of arrays:
7976              
7977             my $ha = formatTable
7978             ({"" => ["aa", "bb", "cc"],
7979             "1" => ["A", "B", "C"],
7980             "22" => ["AA", "BB", "CC"],
7981             "333" => ["AAA", "BBB", "CCC"],
7982             "4444" => [1, 22, 333]},
7983             [qw(Key A B C)]
7984             );
7985              
7986             ok $ha eq <
7987             Key A B C
7988             aa bb cc
7989             1 A B C
7990             22 AA BB CC
7991             333 AAA BBB CCC
7992             4444 1 22 333
7993             END
7994              
7995             # Print a hash of hashes:
7996              
7997             my $hh = formatTable
7998             ({a => {aa=>"A", bb=>"B", cc=>"C" },
7999             aa => {aa=>"AA", bb=>"BB", cc=>"CC" },
8000             aaa => {aa=>"AAA", bb=>"BBB", cc=>"CCC" },
8001             aaaa => {aa=>1, bb=>22, cc=>333 }});
8002              
8003             ok $hh eq <
8004             aa bb cc
8005             a A B C
8006             aa AA BB CC
8007             aaa AAA BBB CCC
8008             aaaa 1 22 333
8009             END
8010              
8011             # Print an array of scalars:
8012              
8013             my $a = formatTable(["a", "bb", "ccc", 4], [q(#), q(Col)]);
8014              
8015             ok $a eq <
8016             # Col
8017             0 a
8018             1 bb
8019             2 ccc
8020             3 4
8021             END
8022              
8023             # Print a hash of scalars:
8024              
8025             my $h = formatTable({aa=>"AAAA", bb=>"BBBB", cc=>"333"}, [qw(Key Title)]);
8026              
8027             ok $h eq <
8028             Key Title
8029             aa AAAA
8030             bb BBBB
8031             cc 333
8032             END
8033              
8034             =head1 Description
8035              
8036             Write data in tabular text format.
8037              
8038              
8039             Version 20210730.
8040              
8041              
8042             The following sections describe the methods in each functional area of this
8043             module. For an alphabetic listing of all methods by name see L.
8044              
8045              
8046              
8047             =head1 Immediately useful methods
8048              
8049             These methods are the ones most likely to be of immediate use to anyone using
8050             this module for the first time:
8051              
8052              
8053             L
8054              
8055             Absolute file from an absolute file B<$a> plus a relative file B<$r>. In the event that the relative file $r is, in fact, an absolute file then it is returned as the result.
8056              
8057             L
8058              
8059             Process files in parallel across multiple L instances if available or in series if not. The data located by B<$userData> is transferred from the primary instance, as determined by L, to all the secondary instances. B<$parallel> contains a reference to a sub, parameterized by array @_ = (a copy of the user data, the name of the file to process), which will be executed upon each session instance including the primary instance to update $userData. B<$results> contains a reference to a sub, parameterized by array @_ = (the user data, an array of results returned by each execution of $parallel), that will be called on the primary instance to process the results folders from each instance once their results folders have been copied back and merged into the results folder of the primary instance. $results should update its copy of $userData with the information received from each instance. B<$files> is a reference to an array of the files to be processed: each file will be copied from the primary instance to each of the secondary instances before parallel processing starts. B<%options> contains any parameters needed to interact with L via the L. The returned result is that returned by sub $results.
8060              
8061             L
8062              
8063             Remove all the files and folders under and including the specified B<$folder> as long as the number of files to be removed is less than the specified B<$limitCount>. Sometimes the folder can be emptied but not removed - perhaps because it a link, in this case a message is produced unless suppressed by the optional B<$nomsg> parameter.
8064              
8065             L
8066              
8067             Year-monthNumber-day at hours:minute:seconds
8068              
8069             L
8070              
8071             Execute some Perl B<$code> on the server whose ip address is specified by B<$ip> or returned by L.
8072              
8073             L
8074              
8075             Create a file name from a list of names the last of which is assumed to be the extension of the file name. Identical to L.
8076              
8077             L
8078              
8079             Remove the path and extension from a file name.
8080              
8081             L
8082              
8083             Format various B<$data> structures as a table with titles as specified by B<$columnTitles>: either a reference to an array of column titles or a string each line of which contains the column title as the first word with the rest of the line describing that column.
8084              
8085             Optionally create a report from the table using the report B<%options> described in L
8086              
8087             L
8088              
8089             Return a B<$bless>ed hash with the specified B<$attributes> accessible via L method calls. L will generate documentation at L for the hash defined by the call to L if the call is laid out as in the example below.
8090              
8091             L
8092              
8093             Return the content of a file residing on the local machine interpreting the content of the file as L.
8094              
8095             L
8096              
8097             Copy and read a B<$file> from the remote machine whose ip address is specified by B<$ip> or returned by L and return the content of $file interpreted as utf8 .
8098              
8099             L
8100              
8101             Relative file from one absolute file B<$a> against another B<$b>.
8102              
8103             L
8104              
8105             Process the elements of an array in parallel using a maximum of B<$maximumNumberOfProcesses> processes. sub B<&$parallel> is forked to process each array element in parallel. The results returned by the forked copies of &$parallel are presented as a single array to sub B<&$results> which is run in series. B<@array> contains the elements to be processed. Returns the result returned by &$results.
8106              
8107             L
8108              
8109             Search the specified directory under the specified folder for sub folders
8110              
8111             L
8112              
8113             Search the specified directory trees for the files (not folders) that match the specified extensions. The argument list should include at least one path name to be useful. If no file extensions are supplied then all the files below the specified paths are returned. Arguments wrapped in [] will be unwrapped.
8114              
8115             L
8116              
8117             Write to a new B<$file>, after creating a path to the $file with L if necessary, a B<$string> of L content encoded as L. Return the name of the $file written to on success else confess if the file already exists or any other error occurs.
8118              
8119             L
8120              
8121             Write to a new B<$file>, after creating a path to the file with L if necessary, a B<$string> of L content encoded as L then copy the $file to the remote server whose ip address is specified by B<$ip> or returned by L. Return the name of the $file on success else confess if the file already exists or any other error occurs.
8122              
8123             L
8124              
8125             Execute a command B<$cmd> via bash on the server whose ip address is specified by B<$ip> or returned by L. The command will be run using the userid listed in F<.ssh/config>
8126              
8127              
8128              
8129              
8130             =head1 Time stamps
8131              
8132             Date and timestamps as used in logs of long running commands.
8133              
8134             =head2 dateTimeStamp()
8135              
8136             Year-monthNumber-day at hours:minute:seconds
8137              
8138              
8139             B
8140              
8141              
8142            
8143             ok dateTimeStamp =~ m(\A\d{4}-\d\d-\d\d at \d\d:\d\d:\d\d\Z), q(dts); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
8144              
8145            
8146              
8147             =head2 dateTimeStampName()
8148              
8149             Date time stamp without white space.
8150              
8151              
8152             B
8153              
8154              
8155            
8156             ok dateTimeStampName =~ m(\A_on_\d{4}_\d\d_\d\d_at_\d\d_\d\d_\d\d\Z); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
8157              
8158            
8159              
8160             =head2 dateStamp()
8161              
8162             Year-monthName-day
8163              
8164              
8165             B
8166              
8167              
8168            
8169             ok dateStamp =~ m(\A\d{4}-\w{3}-\d\d\Z); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
8170              
8171            
8172              
8173             =head2 versionCode()
8174              
8175             YYYYmmdd-HHMMSS
8176              
8177              
8178             B
8179              
8180              
8181            
8182             ok versionCode =~ m(\A\d{8}-\d{6}\Z); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
8183              
8184            
8185              
8186             =head2 versionCodeDashed()
8187              
8188             YYYY-mm-dd-HH:MM:SS
8189              
8190              
8191             B
8192              
8193              
8194            
8195             ok versionCodeDashed =~ m(\A\d{4}-\d\d-\d\d-\d\d:\d\d:\d\d\Z); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
8196              
8197            
8198              
8199             =head2 timeStamp()
8200              
8201             hours:minute:seconds
8202              
8203              
8204             B
8205              
8206              
8207            
8208             ok timeStamp =~ m(\A\d\d:\d\d:\d\d\Z); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
8209              
8210            
8211              
8212             =head2 microSecondsSinceEpoch()
8213              
8214             Micro seconds since unix epoch.
8215              
8216              
8217             B
8218              
8219              
8220            
8221             ok microSecondsSinceEpoch > 47*365*24*60*60*1e6; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
8222              
8223            
8224              
8225             =head1 Command execution
8226              
8227             Various ways of processing commands and writing results.
8228              
8229             =head2 ddd(@data)
8230              
8231             Dump data
8232              
8233             Parameter Description
8234             1 @data Messages
8235              
8236             B
8237              
8238              
8239            
8240             ddd "Hello"; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
8241              
8242            
8243              
8244             =head2 fff($line, $file, @m)
8245              
8246             Confess a message with a line position and a file that Geany will jump to if clicked on.
8247              
8248             Parameter Description
8249             1 $line Line
8250             2 $file File
8251             3 @m Messages
8252              
8253             B
8254              
8255              
8256            
8257             fff __LINE__, __FILE__, "Hello world"; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
8258              
8259            
8260              
8261             =head2 lll(@messages)
8262              
8263             Log messages with a time stamp and originating file and line number.
8264              
8265             Parameter Description
8266             1 @messages Messages
8267              
8268             B
8269              
8270              
8271            
8272             lll "Hello world"; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
8273              
8274            
8275              
8276             =head2 mmm(@messages)
8277              
8278             Log messages with a differential time in milliseconds and originating file and line number.
8279              
8280             Parameter Description
8281             1 @messages Messages
8282              
8283             B
8284              
8285              
8286            
8287             mmm "Hello world"; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
8288              
8289            
8290              
8291             =head2 xxx(@cmd)
8292              
8293             Execute a shell command optionally checking its response. The command to execute is specified as one or more strings which are joined together after removing any new lines. Optionally the last string can be a regular expression that is used to test any non blank output generated by the execution of the command: if the regular expression fails the command and the command output are printed, else it is suppressed as being uninteresting. If such a regular expression is not supplied then the command and its non blank output lines are always printed.
8294              
8295             Parameter Description
8296             1 @cmd Command to execute followed by an optional regular expression to test the results
8297              
8298             B
8299              
8300              
8301            
8302             {ok xxx("echo aaa") =~ /aaa/; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
8303              
8304            
8305              
8306             =head2 xxxr($cmd, $ip)
8307              
8308             Execute a command B<$cmd> via bash on the server whose ip address is specified by B<$ip> or returned by L. The command will be run using the userid listed in F<.ssh/config>
8309              
8310             Parameter Description
8311             1 $cmd Command string
8312             2 $ip Optional ip address
8313              
8314             B
8315              
8316              
8317             if (0)
8318            
8319             {ok xxxr q(pwd); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
8320              
8321             }
8322            
8323              
8324             =head2 yyy($cmd)
8325              
8326             Execute a block of shell commands line by line after removing comments - stop if there is a non zero return code from any command.
8327              
8328             Parameter Description
8329             1 $cmd Commands to execute separated by new lines
8330              
8331             B
8332              
8333              
8334            
8335             ok !yyy <
8336              
8337             echo aaa
8338             echo bbb
8339             END
8340            
8341              
8342             =head2 zzz($cmd, $success, $returnCode, $message)
8343              
8344             Execute lines of commands after replacing new lines with && then check that the pipeline execution results in a return code of zero and that the execution results match the optional regular expression if one has been supplied; confess() to an error if either check fails. To execute remotely, add "ssh ... 'echo start" as the first line and "echo end'" as the last line with the commands to be executed on the lines in between.
8345              
8346             Parameter Description
8347             1 $cmd Commands to execute - one per line with no trailing &&
8348             2 $success Optional regular expression to check for acceptable results
8349             3 $returnCode Optional regular expression to check the acceptable return codes
8350             4 $message Message of explanation if any of the checks fail
8351              
8352             B
8353              
8354              
8355            
8356             ok zzz(<
8357              
8358             echo aaa
8359             echo bbb
8360             END
8361            
8362              
8363             =head2 execPerlOnRemote($code, $ip)
8364              
8365             Execute some Perl B<$code> on the server whose ip address is specified by B<$ip> or returned by L.
8366              
8367             Parameter Description
8368             1 $code Code to execute
8369             2 $ip Optional ip address
8370              
8371             B
8372              
8373              
8374            
8375             ok execPerlOnRemote(<<'END') =~ m(Hello from: t2.micro)i; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
8376              
8377             #!/usr/bin/perl -I/home/phil/perl/cpan/DataTableText/lib/
8378             use Data::Table::Text qw(:all);
8379            
8380             say STDERR "Hello from: ", awsCurrentInstanceType;
8381             END
8382            
8383              
8384             =head2 parseCommandLineArguments($sub, $args, $valid)
8385              
8386             Call the specified B<$sub> after classifying the specified array of [arguments] in B<$args> into positional and keyword parameters. Keywords are always preceded by one or more B<-> and separated from their values by B<=>. $sub([$positional], {keyword=>value}) will be called with a reference to an array of positional parameters followed by a reference to a hash of keywords and their values. The value returned by $sub will be returned to the caller. The keywords names will be validated if B<$valid> is either a reference to an array of valid keywords names or a hash of {valid keyword name => textual description}. Confess with a table of valid keywords definitions if $valid is specified and an invalid keyword argument is presented.
8387              
8388             Parameter Description
8389             1 $sub Sub to call
8390             2 $args List of arguments to parse
8391             3 $valid Optional list of valid parameters else all parameters will be accepted
8392              
8393             B
8394              
8395              
8396            
8397             my $r = parseCommandLineArguments {[@_]} # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
8398              
8399             [qw( aaa bbb -c --dd --eee=EEEE -f=F), q(--gg=g g), q(--hh=h h)];
8400             is_deeply $r,
8401             [["aaa", "bbb"],
8402             {c=>undef, dd=>undef, eee=>"EEEE", f=>"F", gg=>"g g", hh=>"h h"},
8403             ];
8404            
8405             if (1)
8406            
8407             {my $r = parseCommandLineArguments # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
8408              
8409             {ok 1;
8410             $_[1]
8411             }
8412             [qw(--aAa=AAA --bbB=BBB)], [qw(aaa bbb ccc)];
8413             is_deeply $r, {aaa=>'AAA', bbb=>'BBB'};
8414             }
8415            
8416              
8417             =head2 call($sub, @our)
8418              
8419             Call the specified B<$sub> in a separate child process, wait for it to complete, then copy back the named B<@our> variables from the child process to the calling parent process effectively freeing any memory used during the call.
8420              
8421             Parameter Description
8422             1 $sub Sub to call
8423             2 @our Names of our variable names with preceding sigils to copy back
8424              
8425             B
8426              
8427              
8428             our $a = q(1);
8429             our @a = qw(1);
8430             our %a = (a=>1);
8431             our $b = q(1);
8432             for(2..4) {
8433            
8434             call {$a = $_ x 1e3; $a[0] = $_ x 1e2; $a{a} = $_ x 1e1; $b = 2;} qw($a @a %a); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
8435              
8436             ok $a == $_ x 1e3;
8437             ok $a[0] == $_ x 1e2;
8438             ok $a{a} == $_ x 1e1;
8439             ok $b == 1;
8440             }
8441            
8442              
8443             =head1 Files and paths
8444              
8445             Operations on files and paths.
8446              
8447             =head2 Statistics
8448              
8449             Information about each file.
8450              
8451             =head3 fileSize($file)
8452              
8453             Get the size of a B<$file> in bytes.
8454              
8455             Parameter Description
8456             1 $file File name
8457              
8458             B
8459              
8460              
8461             my $f = writeFile("zzz.data", "aaa");
8462            
8463            
8464             ok fileSize($f) == 3; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
8465              
8466            
8467              
8468             =head3 fileLargestSize(@files)
8469              
8470             Return the largest B<$file>.
8471              
8472             Parameter Description
8473             1 @files File names
8474              
8475             B
8476              
8477              
8478             my $d = temporaryFolder;
8479             my @f = map {owf(fpe($d, $_, q(txt)), 'X' x ($_ ** 2 % 11))} 1..9;
8480            
8481            
8482             my $f = fileLargestSize(@f); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
8483              
8484             ok fn($f) eq '3', 'aaa';
8485            
8486             # my $b = folderSize($d); # Needs du
8487             # ok $b > 0, 'bbb';
8488            
8489             my $c = processFilesInParallel(
8490             sub
8491             {my ($file) = @_;
8492             [&fileSize($file), $file]
8493             },
8494             sub
8495             {scalar @_;
8496             }, (@f) x 12);
8497            
8498             ok 108 == $c, 'cc11';
8499            
8500             my $C = processSizesInParallel
8501             sub
8502             {my ($file) = @_;
8503             [&fileSize($file), $file]
8504             },
8505             sub
8506             {scalar @_;
8507             }, map {[fileSize($_), $_]} (@f) x 12;
8508            
8509             ok 108 == $C, 'cc2';
8510            
8511             my $J = processJavaFilesInParallel
8512             sub
8513             {my ($file) = @_;
8514             [&fileSize($file), $file]
8515             },
8516             sub
8517             {scalar @_;
8518             }, (@f) x 12;
8519            
8520             ok 108 == $J, 'cc3';
8521            
8522             clearFolder($d, 12);
8523            
8524              
8525             =head3 folderSize($folder)
8526              
8527             Get the size of a B<$folder> in bytes.
8528              
8529             Parameter Description
8530             1 $folder Folder name
8531              
8532             B
8533              
8534              
8535             my $d = temporaryFolder;
8536             my @f = map {owf(fpe($d, $_, q(txt)), 'X' x ($_ ** 2 % 11))} 1..9;
8537            
8538             my $f = fileLargestSize(@f);
8539             ok fn($f) eq '3', 'aaa';
8540            
8541            
8542             # my $b = folderSize($d); # Needs du # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
8543              
8544             # ok $b > 0, 'bbb';
8545            
8546             my $c = processFilesInParallel(
8547             sub
8548             {my ($file) = @_;
8549             [&fileSize($file), $file]
8550             },
8551             sub
8552             {scalar @_;
8553             }, (@f) x 12);
8554            
8555             ok 108 == $c, 'cc11';
8556            
8557             my $C = processSizesInParallel
8558             sub
8559             {my ($file) = @_;
8560             [&fileSize($file), $file]
8561             },
8562             sub
8563             {scalar @_;
8564             }, map {[fileSize($_), $_]} (@f) x 12;
8565            
8566             ok 108 == $C, 'cc2';
8567            
8568             my $J = processJavaFilesInParallel
8569             sub
8570             {my ($file) = @_;
8571             [&fileSize($file), $file]
8572             },
8573             sub
8574             {scalar @_;
8575             }, (@f) x 12;
8576            
8577             ok 108 == $J, 'cc3';
8578            
8579             clearFolder($d, 12);
8580            
8581              
8582             =head3 fileMd5Sum($file)
8583              
8584             Get the Md5 sum of the content of a B<$file>.
8585              
8586             Parameter Description
8587             1 $file File or string
8588              
8589             B
8590              
8591              
8592            
8593             fileMd5Sum(q(/etc/hosts)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
8594              
8595            
8596             my $s = join '', 1..100;
8597             my $m = q(ef69caaaeea9c17120821a9eb6c7f1de);
8598            
8599             ok stringMd5Sum($s) eq $m;
8600            
8601             my $f = writeFile(undef, $s);
8602            
8603             ok fileMd5Sum($f) eq $m; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
8604              
8605             unlink $f;
8606            
8607             ok guidFromString(join '', 1..100) eq
8608             q(GUID-ef69caaa-eea9-c171-2082-1a9eb6c7f1de);
8609            
8610             ok guidFromMd5(stringMd5Sum(join('', 1..100))) eq
8611             q(GUID-ef69caaa-eea9-c171-2082-1a9eb6c7f1de);
8612            
8613             ok md5FromGuid(q(GUID-ef69caaa-eea9-c171-2082-1a9eb6c7f1de)) eq
8614             q(ef69caaaeea9c17120821a9eb6c7f1de);
8615            
8616             ok stringMd5Sum(q(𝝰 𝝱 𝝲)) eq q(3c2b7c31b1011998bd7e1f66fb7c024d);
8617             }
8618            
8619             if (1)
8620             {ok arraySum (1..10) == 55;
8621             ok arrayProduct(1..5) == 120;
8622             is_deeply[arrayTimes(2, 1..5)], [qw(2 4 6 8 10)];
8623            
8624              
8625             =head3 guidFromMd5($m)
8626              
8627             Create a guid from an md5 hash.
8628              
8629             Parameter Description
8630             1 $m Md5 hash
8631              
8632             B
8633              
8634              
8635             my $s = join '', 1..100;
8636             my $m = q(ef69caaaeea9c17120821a9eb6c7f1de);
8637            
8638             ok stringMd5Sum($s) eq $m;
8639            
8640             my $f = writeFile(undef, $s);
8641             ok fileMd5Sum($f) eq $m;
8642             unlink $f;
8643            
8644             ok guidFromString(join '', 1..100) eq
8645             q(GUID-ef69caaa-eea9-c171-2082-1a9eb6c7f1de);
8646            
8647            
8648             ok guidFromMd5(stringMd5Sum(join('', 1..100))) eq # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
8649              
8650             q(GUID-ef69caaa-eea9-c171-2082-1a9eb6c7f1de);
8651            
8652             ok md5FromGuid(q(GUID-ef69caaa-eea9-c171-2082-1a9eb6c7f1de)) eq
8653             q(ef69caaaeea9c17120821a9eb6c7f1de);
8654            
8655             ok stringMd5Sum(q(𝝰 𝝱 𝝲)) eq q(3c2b7c31b1011998bd7e1f66fb7c024d);
8656             }
8657            
8658             if (1)
8659             {ok arraySum (1..10) == 55;
8660             ok arrayProduct(1..5) == 120;
8661             is_deeply[arrayTimes(2, 1..5)], [qw(2 4 6 8 10)];
8662            
8663              
8664             =head3 md5FromGuid($G)
8665              
8666             Recover an md5 sum from a guid.
8667              
8668             Parameter Description
8669             1 $G Guid
8670              
8671             B
8672              
8673              
8674             my $s = join '', 1..100;
8675             my $m = q(ef69caaaeea9c17120821a9eb6c7f1de);
8676            
8677             ok stringMd5Sum($s) eq $m;
8678            
8679             my $f = writeFile(undef, $s);
8680             ok fileMd5Sum($f) eq $m;
8681             unlink $f;
8682            
8683             ok guidFromString(join '', 1..100) eq
8684             q(GUID-ef69caaa-eea9-c171-2082-1a9eb6c7f1de);
8685            
8686             ok guidFromMd5(stringMd5Sum(join('', 1..100))) eq
8687             q(GUID-ef69caaa-eea9-c171-2082-1a9eb6c7f1de);
8688            
8689            
8690             ok md5FromGuid(q(GUID-ef69caaa-eea9-c171-2082-1a9eb6c7f1de)) eq # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
8691              
8692             q(ef69caaaeea9c17120821a9eb6c7f1de);
8693            
8694             ok stringMd5Sum(q(𝝰 𝝱 𝝲)) eq q(3c2b7c31b1011998bd7e1f66fb7c024d);
8695             }
8696            
8697             if (1)
8698             {ok arraySum (1..10) == 55;
8699             ok arrayProduct(1..5) == 120;
8700             is_deeply[arrayTimes(2, 1..5)], [qw(2 4 6 8 10)];
8701            
8702              
8703             =head3 guidFromString($string)
8704              
8705             Create a guid representation of the L of the content of a string.
8706              
8707             Parameter Description
8708             1 $string String
8709              
8710             B
8711              
8712              
8713             my $s = join '', 1..100;
8714             my $m = q(ef69caaaeea9c17120821a9eb6c7f1de);
8715            
8716             ok stringMd5Sum($s) eq $m;
8717            
8718             my $f = writeFile(undef, $s);
8719             ok fileMd5Sum($f) eq $m;
8720             unlink $f;
8721            
8722            
8723             ok guidFromString(join '', 1..100) eq # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
8724              
8725             q(GUID-ef69caaa-eea9-c171-2082-1a9eb6c7f1de);
8726            
8727             ok guidFromMd5(stringMd5Sum(join('', 1..100))) eq
8728             q(GUID-ef69caaa-eea9-c171-2082-1a9eb6c7f1de);
8729            
8730             ok md5FromGuid(q(GUID-ef69caaa-eea9-c171-2082-1a9eb6c7f1de)) eq
8731             q(ef69caaaeea9c17120821a9eb6c7f1de);
8732            
8733             ok stringMd5Sum(q(𝝰 𝝱 𝝲)) eq q(3c2b7c31b1011998bd7e1f66fb7c024d);
8734             }
8735            
8736             if (1)
8737             {ok arraySum (1..10) == 55;
8738             ok arrayProduct(1..5) == 120;
8739             is_deeply[arrayTimes(2, 1..5)], [qw(2 4 6 8 10)];
8740            
8741              
8742             =head3 fileModTime($file)
8743              
8744             Get the modified time of a B<$file> as seconds since the epoch.
8745              
8746             Parameter Description
8747             1 $file File name
8748              
8749             B
8750              
8751              
8752            
8753             ok fileModTime($0) =~ m(\A\d+\Z)s; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
8754              
8755            
8756              
8757             =head3 fileOutOfDate($make, $target, @source)
8758              
8759             Calls the specified sub B<$make> for each source file that is missing and then again against the B<$target> file if any of the B<@source> files were missing or the $target file is older than any of the @source files or if the target does not exist. The file name is passed to the sub each time in $_. Returns the files to be remade in the order they should be made.
8760              
8761             Parameter Description
8762             1 $make Make with this sub
8763             2 $target Target file
8764             3 @source Source files
8765              
8766             B
8767              
8768              
8769             my @Files = qw(a b c);
8770             my @files = (@Files, qw(d));
8771             writeFile($_, $_), sleep 1 for @Files;
8772            
8773             my $a = '';
8774            
8775             my @a = fileOutOfDate {$a .= $_} q(a), @files; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
8776              
8777             ok $a eq 'da';
8778             is_deeply [@a], [qw(d a)];
8779            
8780             my $b = '';
8781            
8782             my @b = fileOutOfDate {$b .= $_} q(b), @files; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
8783              
8784             ok $b eq 'db';
8785             is_deeply [@b], [qw(d b)];
8786            
8787             my $c = '';
8788            
8789             my @c = fileOutOfDate {$c .= $_} q(c), @files; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
8790              
8791             ok $c eq 'dc';
8792             is_deeply [@c], [qw(d c)];
8793            
8794             my $d = '';
8795            
8796             my @d = fileOutOfDate {$d .= $_} q(d), @files; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
8797              
8798             ok $d eq 'd';
8799             is_deeply [@d], [qw(d)];
8800            
8801            
8802             my @A = fileOutOfDate {} q(a), @Files; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
8803              
8804            
8805             my @B = fileOutOfDate {} q(b), @Files; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
8806              
8807            
8808             my @C = fileOutOfDate {} q(c), @Files; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
8809              
8810             is_deeply [@A], [qw(a)];
8811             is_deeply [@B], [qw(b)];
8812             is_deeply [@C], [];
8813             unlink for @Files;
8814            
8815              
8816             =head3 firstFileThatExists(@files)
8817              
8818             Returns the name of the first file from B<@files> that exists or B if none of the named @files exist.
8819              
8820             Parameter Description
8821             1 @files Files to check
8822              
8823             B
8824              
8825              
8826             my $d = temporaryFolder;
8827            
8828            
8829             ok $d eq firstFileThatExists("$d/$d", $d); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
8830              
8831            
8832              
8833             =head3 fileInWindowsFormat($file)
8834              
8835             Convert a unix B<$file> name to windows format
8836              
8837             Parameter Description
8838             1 $file File
8839              
8840             B
8841              
8842              
8843             if (1)
8844            
8845             {ok fileInWindowsFormat(fpd(qw(/a b c d))) eq q(\a\b\c\d\\); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
8846              
8847             }
8848            
8849              
8850             =head2 Components
8851              
8852             File names and components.
8853              
8854             =head3 Fusion
8855              
8856             Create file names from file name components.
8857              
8858             =head4 filePath(@file)
8859              
8860             Create a file name from a list of names. Identical to L.
8861              
8862             Parameter Description
8863             1 @file File name components
8864              
8865             B
8866              
8867              
8868            
8869             is_deeply filePath (qw(/aaa bbb ccc ddd.eee)) , prefferedFileName "/aaa/bbb/ccc/ddd.eee"; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
8870              
8871             is_deeply filePathDir(qw(/aaa bbb ccc ddd)) , prefferedFileName "/aaa/bbb/ccc/ddd/";
8872             is_deeply filePathDir('', qw(aaa)) , prefferedFileName "aaa/";
8873             is_deeply filePathDir('') , prefferedFileName "";
8874             is_deeply filePathExt(qw(aaa xxx)) , prefferedFileName "aaa.xxx";
8875             is_deeply filePathExt(qw(aaa bbb xxx)) , prefferedFileName "aaa/bbb.xxx";
8876            
8877             is_deeply fpd (qw(/aaa bbb ccc ddd)) , prefferedFileName "/aaa/bbb/ccc/ddd/";
8878             is_deeply fpf (qw(/aaa bbb ccc ddd.eee)) , prefferedFileName "/aaa/bbb/ccc/ddd.eee";
8879             is_deeply fpe (qw(aaa bbb xxx)) , prefferedFileName "aaa/bbb.xxx";
8880            
8881              
8882             B is a synonym for L.
8883              
8884              
8885             =head4 filePathDir(@file)
8886              
8887             Create a folder name from a list of names. Identical to L.
8888              
8889             Parameter Description
8890             1 @file Directory name components
8891              
8892             B
8893              
8894              
8895             is_deeply filePath (qw(/aaa bbb ccc ddd.eee)) , prefferedFileName "/aaa/bbb/ccc/ddd.eee";
8896            
8897             is_deeply filePathDir(qw(/aaa bbb ccc ddd)) , prefferedFileName "/aaa/bbb/ccc/ddd/"; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
8898              
8899            
8900             is_deeply filePathDir('', qw(aaa)) , prefferedFileName "aaa/"; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
8901              
8902            
8903             is_deeply filePathDir('') , prefferedFileName ""; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
8904              
8905             is_deeply filePathExt(qw(aaa xxx)) , prefferedFileName "aaa.xxx";
8906             is_deeply filePathExt(qw(aaa bbb xxx)) , prefferedFileName "aaa/bbb.xxx";
8907            
8908             is_deeply fpd (qw(/aaa bbb ccc ddd)) , prefferedFileName "/aaa/bbb/ccc/ddd/";
8909             is_deeply fpf (qw(/aaa bbb ccc ddd.eee)) , prefferedFileName "/aaa/bbb/ccc/ddd.eee";
8910             is_deeply fpe (qw(aaa bbb xxx)) , prefferedFileName "aaa/bbb.xxx";
8911            
8912              
8913             B is a synonym for L.
8914              
8915              
8916             =head4 filePathExt(@File)
8917              
8918             Create a file name from a list of names the last of which is assumed to be the extension of the file name. Identical to L.
8919              
8920             Parameter Description
8921             1 @File File name components and extension
8922              
8923             B
8924              
8925              
8926             is_deeply filePath (qw(/aaa bbb ccc ddd.eee)) , prefferedFileName "/aaa/bbb/ccc/ddd.eee";
8927             is_deeply filePathDir(qw(/aaa bbb ccc ddd)) , prefferedFileName "/aaa/bbb/ccc/ddd/";
8928             is_deeply filePathDir('', qw(aaa)) , prefferedFileName "aaa/";
8929             is_deeply filePathDir('') , prefferedFileName "";
8930            
8931             is_deeply filePathExt(qw(aaa xxx)) , prefferedFileName "aaa.xxx"; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
8932              
8933            
8934             is_deeply filePathExt(qw(aaa bbb xxx)) , prefferedFileName "aaa/bbb.xxx"; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
8935              
8936            
8937             is_deeply fpd (qw(/aaa bbb ccc ddd)) , prefferedFileName "/aaa/bbb/ccc/ddd/";
8938             is_deeply fpf (qw(/aaa bbb ccc ddd.eee)) , prefferedFileName "/aaa/bbb/ccc/ddd.eee";
8939             is_deeply fpe (qw(aaa bbb xxx)) , prefferedFileName "aaa/bbb.xxx";
8940            
8941              
8942             B is a synonym for L.
8943              
8944              
8945             =head3 Fission
8946              
8947             Get file name components from a file name.
8948              
8949             =head4 fp($file)
8950              
8951             Get the path from a file name.
8952              
8953             Parameter Description
8954             1 $file File name
8955              
8956             B
8957              
8958              
8959            
8960             ok fp (prefferedFileName q(a/b/c.d.e)) eq prefferedFileName q(a/b/); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
8961              
8962            
8963              
8964             =head4 fpn($file)
8965              
8966             Remove the extension from a file name.
8967              
8968             Parameter Description
8969             1 $file File name
8970              
8971             B
8972              
8973              
8974            
8975             ok fpn(prefferedFileName q(a/b/c.d.e)) eq prefferedFileName q(a/b/c.d); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
8976              
8977            
8978              
8979             =head4 fn($file)
8980              
8981             Remove the path and extension from a file name.
8982              
8983             Parameter Description
8984             1 $file File name
8985              
8986             B
8987              
8988              
8989            
8990             ok fn (prefferedFileName q(a/b/c.d.e)) eq prefferedFileName q(c.d); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
8991              
8992            
8993              
8994             =head4 fne($file)
8995              
8996             Remove the path from a file name.
8997              
8998             Parameter Description
8999             1 $file File name
9000              
9001             B
9002              
9003              
9004            
9005             ok fne(prefferedFileName q(a/b/c.d.e)) eq prefferedFileName q(c.d.e); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9006              
9007            
9008              
9009             =head4 fe($file)
9010              
9011             Get the extension of a file name.
9012              
9013             Parameter Description
9014             1 $file File name
9015              
9016             B
9017              
9018              
9019            
9020             ok fe (prefferedFileName q(a/b/c.d.e)) eq prefferedFileName q(e); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9021              
9022            
9023              
9024             =head4 checkFile($file)
9025              
9026             Return the name of the specified file if it exists, else confess the maximum extent of the path that does exist.
9027              
9028             Parameter Description
9029             1 $file File to check
9030              
9031             B
9032              
9033              
9034             my $d = filePath (my @d = qw(a b c d));
9035            
9036             my $f = filePathExt(qw(a b c d e x));
9037            
9038             my $F = filePathExt(qw(a b c e d));
9039            
9040             createEmptyFile($f);
9041            
9042            
9043             ok eval{checkFile($d)}; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9044              
9045            
9046            
9047             ok eval{checkFile($f)}; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9048              
9049            
9050              
9051             =head4 quoteFile($file)
9052              
9053             Quote a file name.
9054              
9055             Parameter Description
9056             1 $file File name
9057              
9058             B
9059              
9060              
9061            
9062             is_deeply quoteFile(fpe(qw(a "b" c))), onWindows ? q("a\\\"b\".c") : q("a/\"b\".c"); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9063              
9064            
9065              
9066             =head4 removeFilePrefix($prefix, @files)
9067              
9068             Removes a file B<$prefix> from an array of B<@files>.
9069              
9070             Parameter Description
9071             1 $prefix File prefix
9072             2 @files Array of file names
9073              
9074             B
9075              
9076              
9077            
9078             is_deeply [qw(a b)], [&removeFilePrefix(qw(a/ a/a a/b))]; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9079              
9080            
9081            
9082             is_deeply [qw(b)], [&removeFilePrefix("a/", "a/b")]; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9083              
9084            
9085              
9086             =head4 swapFilePrefix($file, $known, $new)
9087              
9088             Swaps the start of a B<$file> name from a B<$known> name to a B<$new> one if the file does in fact start with the $known name otherwise returns the original file name as it is. If the optional $new prefix is omitted then the $known prefix is removed from the $file name.
9089              
9090             Parameter Description
9091             1 $file File name
9092             2 $known Existing prefix
9093             3 $new Optional new prefix defaults to q()
9094              
9095             B
9096              
9097              
9098            
9099             ok swapFilePrefix(q(/aaa/bbb.txt), q(/aaa/), q(/AAA/)) eq q(/AAA/bbb.txt); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9100              
9101            
9102              
9103             =head4 setFileExtension($file, $extension)
9104              
9105             Given a B<$file>, change its extension to B<$extension>. Removes the extension if no $extension is specified.
9106              
9107             Parameter Description
9108             1 $file File name
9109             2 $extension Optional new extension
9110              
9111             B
9112              
9113              
9114            
9115             ok setFileExtension(q(.c), q(d)) eq q(.d); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9116              
9117            
9118            
9119             ok setFileExtension(q(b.c), q(d)) eq q(b.d); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9120              
9121            
9122            
9123             ok setFileExtension(q(/a/b.c), q(d)) eq q(/a/b.d); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9124              
9125            
9126              
9127             =head4 swapFolderPrefix($file, $known, $new)
9128              
9129             Given a B<$file>, swap the folder name of the $file from B<$known> to B<$new> if the file $file starts with the $known folder name else return the $file as it is.
9130              
9131             Parameter Description
9132             1 $file File name
9133             2 $known Existing prefix
9134             3 $new New prefix
9135              
9136             B
9137              
9138              
9139             my $g = fpd(qw(a b c d));
9140             my $h = fpd(qw(a b cc dd));
9141             my $i = fpe($g, qw(aaa txt));
9142            
9143            
9144             my $j = swapFolderPrefix($i, $g, $h); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9145              
9146             ok $j =~ m(a/b/cc/dd/)s unless onWindows;
9147             ok $j =~ m(a\\b\\cc\\dd\\)s if onWindows;
9148            
9149              
9150             =head4 fullyQualifiedFile($file, $prefix)
9151              
9152             Check whether a B<$file> name is fully qualified or not and, optionally, whether it is fully qualified with a specified B<$prefix> or not.
9153              
9154             Parameter Description
9155             1 $file File name to test
9156             2 $prefix File name prefix
9157              
9158             B
9159              
9160              
9161            
9162             ok fullyQualifiedFile(q(/a/b/c.d)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9163              
9164            
9165            
9166             ok fullyQualifiedFile(q(/a/b/c.d), q(/a/b)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9167              
9168            
9169            
9170             ok !fullyQualifiedFile(q(/a/b/c.d), q(/a/c)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9171              
9172            
9173            
9174             ok !fullyQualifiedFile(q(c.d)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9175              
9176            
9177              
9178             =head4 fullyQualifyFile($file)
9179              
9180             Return the fully qualified name of a file.
9181              
9182             Parameter Description
9183             1 $file File name
9184              
9185             B
9186              
9187              
9188             if (0)
9189            
9190             {ok fullyQualifyFile(q(perl/cpan)) eq q(/home/phil/perl/cpan/); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9191              
9192             }
9193            
9194              
9195             =head4 removeDuplicatePrefixes($file)
9196              
9197             Remove duplicated leading directory names from a file name.
9198              
9199             Parameter Description
9200             1 $file File name
9201              
9202             B
9203              
9204              
9205            
9206             ok q(a/b.c) eq removeDuplicatePrefixes("a/a/b.c"); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9207              
9208            
9209            
9210             ok q(a/b.c) eq removeDuplicatePrefixes("a/b.c"); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9211              
9212            
9213            
9214             ok q(b.c) eq removeDuplicatePrefixes("b.c"); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9215              
9216            
9217              
9218             =head4 containingFolderName($file)
9219              
9220             The name of a folder containing a file
9221              
9222             Parameter Description
9223             1 $file File name
9224              
9225             B
9226              
9227              
9228            
9229             ok containingFolderName(q(/a/b/c.d)) eq q(b); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9230              
9231            
9232              
9233             =head2 Position
9234              
9235             Position in the file system.
9236              
9237             =head3 currentDirectory()
9238              
9239             Get the current working directory.
9240              
9241              
9242             B
9243              
9244              
9245            
9246             currentDirectory; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9247              
9248            
9249              
9250             =head3 currentDirectoryAbove()
9251              
9252             Get the path to the folder above the current working folder.
9253              
9254              
9255             B
9256              
9257              
9258            
9259             currentDirectoryAbove; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9260              
9261            
9262              
9263             =head3 parseFileName($file)
9264              
9265             Parse a file name into (path, name, extension) considering .. to be always part of the path and using B to mark missing components. This differs from (fp, fn, fe) which return q() for missing components and do not interpret . or .. as anything special
9266              
9267             Parameter Description
9268             1 $file File name to parse
9269              
9270             B
9271              
9272              
9273             if (1)
9274            
9275             {is_deeply [parseFileName "/home/phil/test.data"], ["/home/phil/", "test", "data"]; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9276              
9277            
9278             is_deeply [parseFileName "/home/phil/test"], ["/home/phil/", "test"]; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9279              
9280            
9281             is_deeply [parseFileName "phil/test.data"], ["phil/", "test", "data"]; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9282              
9283            
9284             is_deeply [parseFileName "phil/test"], ["phil/", "test"]; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9285              
9286            
9287             is_deeply [parseFileName "test.data"], [undef, "test", "data"]; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9288              
9289            
9290             is_deeply [parseFileName "phil/"], [qw(phil/)]; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9291              
9292            
9293             is_deeply [parseFileName "/phil"], [qw(/ phil)]; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9294              
9295            
9296             is_deeply [parseFileName "/"], [qw(/)]; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9297              
9298            
9299             is_deeply [parseFileName "/var/www/html/translations/"], [qw(/var/www/html/translations/)]; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9300              
9301            
9302             is_deeply [parseFileName "a.b/c.d.e"], [qw(a.b/ c.d e)]; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9303              
9304            
9305             is_deeply [parseFileName "./a.b"], [qw(./ a b)]; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9306              
9307            
9308             is_deeply [parseFileName "./../../a.b"], [qw(./../../ a b)]; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9309              
9310             }
9311            
9312              
9313             =head3 fullFileName()
9314              
9315             Full name of a file.
9316              
9317              
9318             B
9319              
9320              
9321            
9322             fullFileName(fpe(qw(a txt))); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9323              
9324            
9325              
9326             =head3 relFromAbsAgainstAbs($a, $b)
9327              
9328             Relative file from one absolute file B<$a> against another B<$b>.
9329              
9330             Parameter Description
9331             1 $a Absolute file to be made relative
9332             2 $b Against this absolute file.
9333              
9334             B
9335              
9336              
9337            
9338             ok "bbb.pl" eq relFromAbsAgainstAbs("/home/la/perl/bbb.pl", "/home/la/perl/aaa.pl"); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9339              
9340            
9341            
9342             ok "../perl/bbb.pl" eq relFromAbsAgainstAbs("/home/la/perl/bbb.pl", "/home/la/java/aaa.jv"); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9343              
9344            
9345              
9346             =head3 absFromAbsPlusRel($a, $r)
9347              
9348             Absolute file from an absolute file B<$a> plus a relative file B<$r>. In the event that the relative file $r is, in fact, an absolute file then it is returned as the result.
9349              
9350             Parameter Description
9351             1 $a Absolute file
9352             2 $r Relative file
9353              
9354             B
9355              
9356              
9357            
9358             ok "/home/la/perl/aaa.pl" eq absFromAbsPlusRel("/home/la/perl/bbb", "aaa.pl"); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9359              
9360            
9361            
9362             ok "/home/la/perl/aaa.pl" eq absFromAbsPlusRel("/home/il/perl/bbb.pl", "../../la/perl/aaa.pl"); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9363              
9364            
9365              
9366             =head3 absFile($file)
9367              
9368             Return the name of the given file if it a fully qualified file name else returns B. See: L to check the initial prefix of the file name as well.
9369              
9370             Parameter Description
9371             1 $file File to test
9372              
9373             B
9374              
9375              
9376            
9377             ok "/aaa/" eq absFile(qw(/aaa/)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9378              
9379            
9380              
9381             =head3 sumAbsAndRel(@files)
9382              
9383             Combine zero or more absolute and relative names of B<@files> starting at the current working folder to get an absolute file name.
9384              
9385             Parameter Description
9386             1 @files Absolute and relative file names
9387              
9388             B
9389              
9390              
9391            
9392             ok "/aaa/bbb/ccc/ddd.txt" eq sumAbsAndRel(qw(/aaa/AAA/ ../bbb/bbb/BBB/ ../../ccc/ddd.txt)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9393              
9394            
9395              
9396             =head2 Temporary
9397              
9398             Temporary files and folders
9399              
9400             =head3 temporaryFile()
9401              
9402             Create a new, empty, temporary file.
9403              
9404              
9405             B
9406              
9407              
9408             my $d = fpd(my $D = temporaryDirectory, qw(a));
9409             my $f = fpe($d, qw(bbb txt));
9410             ok !-d $d;
9411             eval q{checkFile($f)};
9412             my $r = $@;
9413             my $q = quotemeta($D);
9414             ok nws($r) =~ m(Can only find.+?: $q)s;
9415             makePath($f);
9416             ok -d $d;
9417             ok -d $D;
9418             rmdir $_ for $d, $D;
9419            
9420             my $e = temporaryFolder; # Same as temporyDirectory
9421             ok -d $e;
9422             clearFolder($e, 2);
9423            
9424            
9425             my $t = temporaryFile; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9426              
9427             ok -f $t;
9428             unlink $t;
9429             ok !-f $t;
9430            
9431             if (0)
9432             {makePathRemote($e); # Make a path on the remote system
9433             }
9434            
9435              
9436             =head3 temporaryFolder()
9437              
9438             Create a new, empty, temporary folder.
9439              
9440              
9441             B
9442              
9443              
9444            
9445             my $D = temporaryFolder; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9446              
9447             ok -d $D;
9448            
9449             my $d = fpd($D, q(ddd));
9450             ok !-d $d;
9451            
9452             my @f = map {createEmptyFile(fpe($d, $_, qw(txt)))} qw(a b c);
9453             is_deeply [sort map {fne $_} findFiles($d, qr(txt\Z))], [qw(a.txt b.txt c.txt)];
9454            
9455             my @D = findDirs($D);
9456             my @e = ($D, $d);
9457             my @E = sort @e;
9458             is_deeply [@D], [@E];
9459            
9460             is_deeply [sort map {fne $_} searchDirectoryTreesForMatchingFiles($d)],
9461             ["a.txt", "b.txt", "c.txt"];
9462            
9463             is_deeply [sort map {fne $_} fileList(prefferedFileName "$d/*.txt")],
9464             ["a.txt", "b.txt", "c.txt"];
9465            
9466             ok -e $_ for @f;
9467            
9468             is_deeply scalar(searchDirectoryTreeForSubFolders $D), 2;
9469            
9470             my @g = fileList(qq($D/*/*.txt));
9471             ok @g == 3;
9472            
9473             clearFolder($D, 5);
9474             ok onWindows ? 1 : !-e $_ for @f;
9475             ok onWindows ? 1 : !-d $D;
9476            
9477             my $d = fpd(my $D = temporaryDirectory, qw(a));
9478             my $f = fpe($d, qw(bbb txt));
9479             ok !-d $d;
9480             eval q{checkFile($f)};
9481             my $r = $@;
9482             my $q = quotemeta($D);
9483             ok nws($r) =~ m(Can only find.+?: $q)s;
9484             makePath($f);
9485             ok -d $d;
9486             ok -d $D;
9487             rmdir $_ for $d, $D;
9488            
9489            
9490             my $e = temporaryFolder; # Same as temporyDirectory # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9491              
9492             ok -d $e;
9493             clearFolder($e, 2);
9494            
9495             my $t = temporaryFile;
9496             ok -f $t;
9497             unlink $t;
9498             ok !-f $t;
9499            
9500             if (0)
9501             {makePathRemote($e); # Make a path on the remote system
9502             }
9503            
9504              
9505             B is a synonym for L.
9506              
9507              
9508             =head2 Find
9509              
9510             Find files and folders below a folder.
9511              
9512             =head3 findFiles($folder, $filter)
9513              
9514             Find all the files under a B<$folder> and optionally B<$filter> the selected files with a regular expression.
9515              
9516             Parameter Description
9517             1 $folder Folder to start the search with
9518             2 $filter Optional regular expression to filter files
9519              
9520             B
9521              
9522              
9523             my $D = temporaryFolder;
9524             ok -d $D;
9525            
9526             my $d = fpd($D, q(ddd));
9527             ok !-d $d;
9528            
9529             my @f = map {createEmptyFile(fpe($d, $_, qw(txt)))} qw(a b c);
9530            
9531             is_deeply [sort map {fne $_} findFiles($d, qr(txt\Z))], [qw(a.txt b.txt c.txt)]; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9532              
9533            
9534             my @D = findDirs($D);
9535             my @e = ($D, $d);
9536             my @E = sort @e;
9537             is_deeply [@D], [@E];
9538            
9539             is_deeply [sort map {fne $_} searchDirectoryTreesForMatchingFiles($d)],
9540             ["a.txt", "b.txt", "c.txt"];
9541            
9542             is_deeply [sort map {fne $_} fileList(prefferedFileName "$d/*.txt")],
9543             ["a.txt", "b.txt", "c.txt"];
9544            
9545             ok -e $_ for @f;
9546            
9547             is_deeply scalar(searchDirectoryTreeForSubFolders $D), 2;
9548            
9549             my @g = fileList(qq($D/*/*.txt));
9550             ok @g == 3;
9551            
9552             clearFolder($D, 5);
9553             ok onWindows ? 1 : !-e $_ for @f;
9554             ok onWindows ? 1 : !-d $D;
9555            
9556              
9557             =head3 findDirs($folder, $filter)
9558              
9559             Find all the folders under a B<$folder> and optionally B<$filter> the selected folders with a regular expression.
9560              
9561             Parameter Description
9562             1 $folder Folder to start the search with
9563             2 $filter Optional regular expression to filter files
9564              
9565             B
9566              
9567              
9568             my $D = temporaryFolder;
9569             ok -d $D;
9570            
9571             my $d = fpd($D, q(ddd));
9572             ok !-d $d;
9573            
9574             my @f = map {createEmptyFile(fpe($d, $_, qw(txt)))} qw(a b c);
9575             is_deeply [sort map {fne $_} findFiles($d, qr(txt\Z))], [qw(a.txt b.txt c.txt)];
9576            
9577            
9578             my @D = findDirs($D); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9579              
9580             my @e = ($D, $d);
9581             my @E = sort @e;
9582             is_deeply [@D], [@E];
9583            
9584             is_deeply [sort map {fne $_} searchDirectoryTreesForMatchingFiles($d)],
9585             ["a.txt", "b.txt", "c.txt"];
9586            
9587             is_deeply [sort map {fne $_} fileList(prefferedFileName "$d/*.txt")],
9588             ["a.txt", "b.txt", "c.txt"];
9589            
9590             ok -e $_ for @f;
9591            
9592             is_deeply scalar(searchDirectoryTreeForSubFolders $D), 2;
9593            
9594             my @g = fileList(qq($D/*/*.txt));
9595             ok @g == 3;
9596            
9597             clearFolder($D, 5);
9598             ok onWindows ? 1 : !-e $_ for @f;
9599             ok onWindows ? 1 : !-d $D;
9600            
9601              
9602             =head3 fileList($pattern)
9603              
9604             Files that match a given search pattern interpreted by L.
9605              
9606             Parameter Description
9607             1 $pattern Search pattern
9608              
9609             B
9610              
9611              
9612             my $D = temporaryFolder;
9613             ok -d $D;
9614            
9615             my $d = fpd($D, q(ddd));
9616             ok !-d $d;
9617            
9618             my @f = map {createEmptyFile(fpe($d, $_, qw(txt)))} qw(a b c);
9619             is_deeply [sort map {fne $_} findFiles($d, qr(txt\Z))], [qw(a.txt b.txt c.txt)];
9620            
9621             my @D = findDirs($D);
9622             my @e = ($D, $d);
9623             my @E = sort @e;
9624             is_deeply [@D], [@E];
9625            
9626             is_deeply [sort map {fne $_} searchDirectoryTreesForMatchingFiles($d)],
9627             ["a.txt", "b.txt", "c.txt"];
9628            
9629            
9630             is_deeply [sort map {fne $_} fileList(prefferedFileName "$d/*.txt")], # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9631              
9632             ["a.txt", "b.txt", "c.txt"];
9633            
9634             ok -e $_ for @f;
9635            
9636             is_deeply scalar(searchDirectoryTreeForSubFolders $D), 2;
9637            
9638            
9639             my @g = fileList(qq($D/*/*.txt)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9640              
9641             ok @g == 3;
9642            
9643             clearFolder($D, 5);
9644             ok onWindows ? 1 : !-e $_ for @f;
9645             ok onWindows ? 1 : !-d $D;
9646            
9647              
9648             =head3 searchDirectoryTreesForMatchingFiles(@FoldersandExtensions)
9649              
9650             Search the specified directory trees for the files (not folders) that match the specified extensions. The argument list should include at least one path name to be useful. If no file extensions are supplied then all the files below the specified paths are returned. Arguments wrapped in [] will be unwrapped.
9651              
9652             Parameter Description
9653             1 @FoldersandExtensions Mixture of folder names and extensions
9654              
9655             B
9656              
9657              
9658             my $D = temporaryFolder;
9659             ok -d $D;
9660            
9661             my $d = fpd($D, q(ddd));
9662             ok !-d $d;
9663            
9664             my @f = map {createEmptyFile(fpe($d, $_, qw(txt)))} qw(a b c);
9665             is_deeply [sort map {fne $_} findFiles($d, qr(txt\Z))], [qw(a.txt b.txt c.txt)];
9666            
9667             my @D = findDirs($D);
9668             my @e = ($D, $d);
9669             my @E = sort @e;
9670             is_deeply [@D], [@E];
9671            
9672            
9673             is_deeply [sort map {fne $_} searchDirectoryTreesForMatchingFiles($d)], # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9674              
9675             ["a.txt", "b.txt", "c.txt"];
9676            
9677             is_deeply [sort map {fne $_} fileList(prefferedFileName "$d/*.txt")],
9678             ["a.txt", "b.txt", "c.txt"];
9679            
9680             ok -e $_ for @f;
9681            
9682             is_deeply scalar(searchDirectoryTreeForSubFolders $D), 2;
9683            
9684             my @g = fileList(qq($D/*/*.txt));
9685             ok @g == 3;
9686            
9687             clearFolder($D, 5);
9688             ok onWindows ? 1 : !-e $_ for @f;
9689             ok onWindows ? 1 : !-d $D;
9690            
9691              
9692             =head3 searchDirectoryTreeForSubFolders($folder)
9693              
9694             Search the specified directory under the specified folder for sub folders
9695              
9696             Parameter Description
9697             1 $folder The folder at which to start the search
9698              
9699             B
9700              
9701              
9702             my $D = temporaryFolder;
9703             ok -d $D;
9704            
9705             my $d = fpd($D, q(ddd));
9706             ok !-d $d;
9707            
9708             my @f = map {createEmptyFile(fpe($d, $_, qw(txt)))} qw(a b c);
9709             is_deeply [sort map {fne $_} findFiles($d, qr(txt\Z))], [qw(a.txt b.txt c.txt)];
9710            
9711             my @D = findDirs($D);
9712             my @e = ($D, $d);
9713             my @E = sort @e;
9714             is_deeply [@D], [@E];
9715            
9716             is_deeply [sort map {fne $_} searchDirectoryTreesForMatchingFiles($d)],
9717             ["a.txt", "b.txt", "c.txt"];
9718            
9719             is_deeply [sort map {fne $_} fileList(prefferedFileName "$d/*.txt")],
9720             ["a.txt", "b.txt", "c.txt"];
9721            
9722             ok -e $_ for @f;
9723            
9724            
9725             is_deeply scalar(searchDirectoryTreeForSubFolders $D), 2; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9726              
9727            
9728             my @g = fileList(qq($D/*/*.txt));
9729             ok @g == 3;
9730            
9731             clearFolder($D, 5);
9732             ok onWindows ? 1 : !-e $_ for @f;
9733             ok onWindows ? 1 : !-d $D;
9734            
9735              
9736             =head3 hashifyFolderStructure(@files)
9737              
9738             Hashify a list of file names to get the corresponding folder structure.
9739              
9740             Parameter Description
9741             1 @files File names
9742              
9743             B
9744              
9745              
9746            
9747             is_deeply hashifyFolderStructure(qw(/a/a/a /a/a/b /a/b/a /a/b/b)), # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9748              
9749             {"" => {a => {a => { a => "/a/a/a", b => "/a/a/b" },
9750             b => { a => "/a/b/a", b => "/a/b/b" },
9751             },
9752             },
9753             };
9754            
9755              
9756             =head3 countFileExtensions(@folders)
9757              
9758             Return a hash which counts the file extensions in and below the folders in the specified list.
9759              
9760             Parameter Description
9761             1 @folders Folders to search
9762              
9763             B
9764              
9765              
9766            
9767             countFileExtensions(q(/home/phil/perl/)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9768              
9769            
9770              
9771             =head3 countFileTypes($maximumNumberOfProcesses, @folders)
9772              
9773             Return a hash which counts, in parallel with a maximum number of processes: B<$maximumNumberOfProcesses>, the results of applying the B command to each file in and under the specified B<@folders>.
9774              
9775             Parameter Description
9776             1 $maximumNumberOfProcesses Maximum number of processes to run in parallel
9777             2 @folders Folders to search
9778              
9779             B
9780              
9781              
9782            
9783             countFileTypes(4, q(/home/phil/perl/)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9784              
9785            
9786              
9787             =head3 matchPath($file)
9788              
9789             Return the deepest folder that exists along a given file name path.
9790              
9791             Parameter Description
9792             1 $file File name
9793              
9794             B
9795              
9796              
9797             my $d = filePath (my @d = qw(a b c d));
9798            
9799            
9800             ok matchPath($d) eq $d; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9801              
9802            
9803              
9804             =head3 findFileWithExtension($file, @ext)
9805              
9806             Find the first file that exists with a path and name of B<$file> and an extension drawn from <@ext>.
9807              
9808             Parameter Description
9809             1 $file File name minus extensions
9810             2 @ext Possible extensions
9811              
9812             B
9813              
9814              
9815             my $f = createEmptyFile(fpe(my $d = temporaryFolder, qw(a jpg)));
9816            
9817            
9818             my $F = findFileWithExtension(fpf($d, q(a)), qw(txt data jpg)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9819              
9820            
9821             ok $F eq "jpg";
9822            
9823              
9824             =head3 clearFolder($folder, $limitCount, $noMsg)
9825              
9826             Remove all the files and folders under and including the specified B<$folder> as long as the number of files to be removed is less than the specified B<$limitCount>. Sometimes the folder can be emptied but not removed - perhaps because it a link, in this case a message is produced unless suppressed by the optional B<$nomsg> parameter.
9827              
9828             Parameter Description
9829             1 $folder Folder
9830             2 $limitCount Maximum number of files to remove to limit damage
9831             3 $noMsg No message if the folder cannot be completely removed.
9832              
9833             B
9834              
9835              
9836             my $D = temporaryFolder;
9837             ok -d $D;
9838            
9839             my $d = fpd($D, q(ddd));
9840             ok !-d $d;
9841            
9842             my @f = map {createEmptyFile(fpe($d, $_, qw(txt)))} qw(a b c);
9843             is_deeply [sort map {fne $_} findFiles($d, qr(txt\Z))], [qw(a.txt b.txt c.txt)];
9844            
9845             my @D = findDirs($D);
9846             my @e = ($D, $d);
9847             my @E = sort @e;
9848             is_deeply [@D], [@E];
9849            
9850             is_deeply [sort map {fne $_} searchDirectoryTreesForMatchingFiles($d)],
9851             ["a.txt", "b.txt", "c.txt"];
9852            
9853             is_deeply [sort map {fne $_} fileList(prefferedFileName "$d/*.txt")],
9854             ["a.txt", "b.txt", "c.txt"];
9855            
9856             ok -e $_ for @f;
9857            
9858             is_deeply scalar(searchDirectoryTreeForSubFolders $D), 2;
9859            
9860             my @g = fileList(qq($D/*/*.txt));
9861             ok @g == 3;
9862            
9863            
9864             clearFolder($D, 5); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9865              
9866             ok onWindows ? 1 : !-e $_ for @f;
9867             ok onWindows ? 1 : !-d $D;
9868            
9869              
9870             =head2 Read and write files
9871              
9872             Read and write strings from and to files creating paths to any created files as needed.
9873              
9874             =head3 readFile($file)
9875              
9876             Return the content of a file residing on the local machine interpreting the content of the file as L.
9877              
9878             Parameter Description
9879             1 $file Name of file to read
9880              
9881             B
9882              
9883              
9884             my $f = writeFile(undef, "aaa");
9885            
9886             is_deeply [readFile $f], ["aaa"]; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9887              
9888            
9889             appendFile($f, "bbb");
9890            
9891             is_deeply [readFile $f], ["aaabbb"]; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9892              
9893            
9894             my $F = writeTempFile(qw(aaa bbb));
9895            
9896             is_deeply [readFile $F], ["aaa
9897             ", "bbb
9898             "]; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9899              
9900            
9901             eval {writeFile($f, q(ccc))};
9902             ok $@ =~ m(File already exists:)i;
9903            
9904             overWriteFile($F, q(ccc));
9905            
9906             ok readFile($F) eq q(ccc); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9907              
9908            
9909             unlink $f, $F;
9910            
9911              
9912             =head3 readStdIn()
9913              
9914             Return the contents of STDIN and return the results as either an array or a string. Terminate with Ctrl-D if testing manually - STDIN remains open allowing this method to be called again to receive another block of data.
9915              
9916              
9917             B
9918              
9919              
9920             my $d = qq(aaaa);
9921             open(STDIN, "<", writeTempFile($d));
9922            
9923             ok qq($d
9924             ) eq readStdIn; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9925              
9926            
9927              
9928             =head3 readFileFromRemote($file, $ip)
9929              
9930             Copy and read a B<$file> from the remote machine whose ip address is specified by B<$ip> or returned by L and return the content of $file interpreted as utf8 .
9931              
9932             Parameter Description
9933             1 $file Name of file to read
9934             2 $ip Optional ip address of server
9935              
9936             B
9937              
9938              
9939            
9940             my $f = writeFileToRemote(undef, q(aaaa));
9941             unlink $f;
9942            
9943             ok readFileFromRemote($f) eq q(aaaa); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9944              
9945             unlink $f;
9946            
9947              
9948             =head3 evalFile($file)
9949              
9950             Read a file containing L content represented as L, L the content, confess to any errors and then return any result with L methods to access each hash element.
9951              
9952             Parameter Description
9953             1 $file File to read
9954              
9955             B
9956              
9957              
9958             my $d = [qw(aaa bbb ccc), [{aaa=>'AAA', bbb=>'BBB'}]];
9959             my $f = dumpFile(undef, $d);
9960            
9961             is_deeply evalFile($f), $d; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9962              
9963            
9964             is_deeply evalFile(my $F = dumpTempFile($d)), $d; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9965              
9966             unlink $f, $F;
9967            
9968             my $j = dumpFileAsJson(undef, $d);
9969             is_deeply evalFileAsJson($j), $d;
9970             is_deeply evalFileAsJson(my $J = dumpTempFileAsJson($d)), $d;
9971             unlink $j, $J;
9972            
9973              
9974             =head3 evalFileAsJson($file)
9975              
9976             Read a B<$file> containing L and return the corresponding L data structure.
9977              
9978             Parameter Description
9979             1 $file File to read
9980              
9981             B
9982              
9983              
9984             my $d = [qw(aaa bbb ccc), [{aaa=>'AAA', bbb=>'BBB'}]];
9985             my $f = dumpFile(undef, $d);
9986             is_deeply evalFile($f), $d;
9987             is_deeply evalFile(my $F = dumpTempFile($d)), $d;
9988             unlink $f, $F;
9989            
9990             my $j = dumpFileAsJson(undef, $d);
9991            
9992             is_deeply evalFileAsJson($j), $d; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9993              
9994            
9995             is_deeply evalFileAsJson(my $J = dumpTempFileAsJson($d)), $d; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
9996              
9997             unlink $j, $J;
9998            
9999              
10000             =head3 evalGZipFile($file)
10001              
10002             Read a file compressed with L containing L content represented as L, L the content, confess to any errors and then return any result with L methods to access each hash element. This is slower than using L but does produce much smaller files, see also: L.
10003              
10004             Parameter Description
10005             1 $file File to read
10006              
10007             B
10008              
10009              
10010             my $d = [1, 2, 3=>{a=>4, b=>5}];
10011             my $file = dumpGZipFile(q(zzz.zip), $d);
10012             ok -e $file;
10013            
10014             my $D = evalGZipFile($file); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
10015              
10016             is_deeply $d, $D;
10017             unlink $file;
10018            
10019              
10020             =head3 retrieveFile($file)
10021              
10022             Retrieve a B<$file> created via L. This is much faster than L as the stored data is not in text format.
10023              
10024             Parameter Description
10025             1 $file File to read
10026              
10027             B
10028              
10029              
10030             my $f = storeFile(undef, my $d = [qw(aaa bbb ccc)]);
10031            
10032             my $s = retrieveFile($f); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
10033              
10034             is_deeply $s, $d;
10035             unlink $f;
10036            
10037              
10038             =head3 readBinaryFile($file)
10039              
10040             Read a binary file on the local machine.
10041              
10042             Parameter Description
10043             1 $file File to read
10044              
10045             B
10046              
10047              
10048             my $f = writeBinaryFile(undef, 0xff x 8);
10049            
10050            
10051             my $s = readBinaryFile($f); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
10052              
10053            
10054             ok $s eq 0xff x 8;
10055            
10056              
10057             =head3 readGZipFile($file)
10058              
10059             Read the specified file containing compressed L content represented as L through L.
10060              
10061             Parameter Description
10062             1 $file File to read.
10063              
10064             B
10065              
10066              
10067             my $s = '𝝰'x1e3;
10068             my $file = writeGZipFile(q(zzz.zip), $s);
10069             ok -e $file;
10070            
10071             my $S = readGZipFile($file); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
10072              
10073             ok $s eq $S;
10074             ok length($s) == length($S);
10075             unlink $file;
10076            
10077              
10078             =head3 makePath($file)
10079              
10080             Make the path for the specified file name or folder on the local machine. Confess to any failure.
10081              
10082             Parameter Description
10083             1 $file File or folder name
10084              
10085             B
10086              
10087              
10088             my $d = fpd(my $D = temporaryDirectory, qw(a));
10089             my $f = fpe($d, qw(bbb txt));
10090             ok !-d $d;
10091             eval q{checkFile($f)};
10092             my $r = $@;
10093             my $q = quotemeta($D);
10094             ok nws($r) =~ m(Can only find.+?: $q)s;
10095            
10096             makePath($f); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
10097              
10098             ok -d $d;
10099             ok -d $D;
10100             rmdir $_ for $d, $D;
10101            
10102             my $e = temporaryFolder; # Same as temporyDirectory
10103             ok -d $e;
10104             clearFolder($e, 2);
10105            
10106             my $t = temporaryFile;
10107             ok -f $t;
10108             unlink $t;
10109             ok !-f $t;
10110            
10111             if (0)
10112             {makePathRemote($e); # Make a path on the remote system
10113             }
10114            
10115              
10116             =head3 makePathRemote($file, $ip)
10117              
10118             Make the path for the specified B<$file> or folder on the L instance whose ip address is specified by B<$ip> or returned by L. Confess to any failures.
10119              
10120             Parameter Description
10121             1 $file File or folder name
10122             2 $ip Optional ip address
10123              
10124             B
10125              
10126              
10127             my $d = fpd(my $D = temporaryDirectory, qw(a));
10128             my $f = fpe($d, qw(bbb txt));
10129             ok !-d $d;
10130             eval q{checkFile($f)};
10131             my $r = $@;
10132             my $q = quotemeta($D);
10133             ok nws($r) =~ m(Can only find.+?: $q)s;
10134             makePath($f);
10135             ok -d $d;
10136             ok -d $D;
10137             rmdir $_ for $d, $D;
10138            
10139             my $e = temporaryFolder; # Same as temporyDirectory
10140             ok -d $e;
10141             clearFolder($e, 2);
10142            
10143             my $t = temporaryFile;
10144             ok -f $t;
10145             unlink $t;
10146             ok !-f $t;
10147            
10148             if (0)
10149            
10150             {makePathRemote($e); # Make a path on the remote system # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
10151              
10152             }
10153            
10154              
10155             =head3 overWriteFile($file, $string)
10156              
10157             Write to a B<$file>, after creating a path to the $file with L if necessary, a B<$string> of L content encoded as L. Return the name of the $file on success else confess to any failures. If the file already exists it will be overwritten.
10158              
10159             Parameter Description
10160             1 $file File to write to or B for a temporary file
10161             2 $string Unicode string to write
10162              
10163             B
10164              
10165              
10166             my $f = writeFile(undef, "aaa");
10167             is_deeply [readFile $f], ["aaa"];
10168            
10169             appendFile($f, "bbb");
10170             is_deeply [readFile $f], ["aaabbb"];
10171            
10172             my $F = writeTempFile(qw(aaa bbb));
10173             is_deeply [readFile $F], ["aaa
10174             ", "bbb
10175             "];
10176            
10177             eval {writeFile($f, q(ccc))};
10178             ok $@ =~ m(File already exists:)i;
10179            
10180            
10181             overWriteFile($F, q(ccc)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
10182              
10183             ok readFile($F) eq q(ccc);
10184            
10185             unlink $f, $F;
10186            
10187              
10188             B is a synonym for L.
10189              
10190              
10191             =head3 writeFile($file, $string)
10192              
10193             Write to a new B<$file>, after creating a path to the $file with L if necessary, a B<$string> of L content encoded as L. Return the name of the $file written to on success else confess if the file already exists or any other error occurs.
10194              
10195             Parameter Description
10196             1 $file New file to write to or B for a temporary file
10197             2 $string String to write
10198              
10199             B
10200              
10201              
10202            
10203             my $f = writeFile(undef, "aaa"); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
10204              
10205             is_deeply [readFile $f], ["aaa"];
10206            
10207             appendFile($f, "bbb");
10208             is_deeply [readFile $f], ["aaabbb"];
10209            
10210             my $F = writeTempFile(qw(aaa bbb));
10211             is_deeply [readFile $F], ["aaa
10212             ", "bbb
10213             "];
10214            
10215            
10216             eval {writeFile($f, q(ccc))}; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
10217              
10218             ok $@ =~ m(File already exists:)i;
10219            
10220             overWriteFile($F, q(ccc));
10221             ok readFile($F) eq q(ccc);
10222            
10223             unlink $f, $F;
10224            
10225              
10226             =head3 writeTempFile(@strings)
10227              
10228             Write an array of strings as lines to a temporary file and return the file name.
10229              
10230             Parameter Description
10231             1 @strings Array of lines
10232              
10233             B
10234              
10235              
10236             my $f = writeFile(undef, "aaa");
10237             is_deeply [readFile $f], ["aaa"];
10238            
10239             appendFile($f, "bbb");
10240             is_deeply [readFile $f], ["aaabbb"];
10241            
10242            
10243             my $F = writeTempFile(qw(aaa bbb)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
10244              
10245             is_deeply [readFile $F], ["aaa
10246             ", "bbb
10247             "];
10248            
10249             eval {writeFile($f, q(ccc))};
10250             ok $@ =~ m(File already exists:)i;
10251            
10252             overWriteFile($F, q(ccc));
10253             ok readFile($F) eq q(ccc);
10254            
10255             unlink $f, $F;
10256            
10257              
10258             =head3 writeFileToRemote($file, $string, $ip)
10259              
10260             Write to a new B<$file>, after creating a path to the file with L if necessary, a B<$string> of L content encoded as L then copy the $file to the remote server whose ip address is specified by B<$ip> or returned by L. Return the name of the $file on success else confess if the file already exists or any other error occurs.
10261              
10262             Parameter Description
10263             1 $file New file to write to or B for a temporary file
10264             2 $string String to write
10265             3 $ip Optional ip address
10266              
10267             B
10268              
10269              
10270            
10271            
10272             my $f = writeFileToRemote(undef, q(aaaa)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
10273              
10274             unlink $f;
10275             ok readFileFromRemote($f) eq q(aaaa);
10276             unlink $f;
10277            
10278              
10279             =head3 overWriteBinaryFile($file, $string)
10280              
10281             Write to B<$file>, after creating a path to the file with L if necessary, the binary content in B<$string>. If the $file already exists it is overwritten. Return the name of the $file on success else confess.
10282              
10283             Parameter Description
10284             1 $file File to write to or B for a temporary file
10285             2 $string L string to write
10286              
10287             B
10288              
10289              
10290             if (1)
10291             {vec(my $a, 0, 8) = 254;
10292             vec(my $b, 0, 8) = 255;
10293             ok dump($a) eq dump("FE");
10294             ok dump($b) eq dump("FF");
10295             ok length($a) == 1;
10296             ok length($b) == 1;
10297            
10298             my $s = $a.$a.$b.$b;
10299             ok length($s) == 4;
10300            
10301             my $f = eval {writeFile(undef, $s)};
10302             ok fileSize($f) == 8;
10303            
10304             eval {writeBinaryFile($f, $s)};
10305             ok $@ =~ m(Binary file already exists:)s;
10306            
10307            
10308             eval {overWriteBinaryFile($f, $s)}; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
10309              
10310             ok !$@;
10311             ok fileSize($f) == 4;
10312            
10313             ok $s eq eval {readBinaryFile($f)};
10314            
10315             copyBinaryFile($f, my $F = temporaryFile);
10316             ok $s eq readBinaryFile($F);
10317             unlink $f, $F;
10318             }
10319            
10320              
10321             =head3 writeBinaryFile($file, $string)
10322              
10323             Write to a new B<$file>, after creating a path to the file with L if necessary, the binary content in B<$string>. Return the name of the $file on success else confess if the file already exists or any other error occurs.
10324              
10325             Parameter Description
10326             1 $file New file to write to or B for a temporary file
10327             2 $string String to write
10328              
10329             B
10330              
10331              
10332            
10333             my $f = writeBinaryFile(undef, 0xff x 8); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
10334              
10335            
10336             my $s = readBinaryFile($f);
10337            
10338             ok $s eq 0xff x 8;
10339            
10340             if (1)
10341             {vec(my $a, 0, 8) = 254;
10342             vec(my $b, 0, 8) = 255;
10343             ok dump($a) eq dump("FE");
10344             ok dump($b) eq dump("FF");
10345             ok length($a) == 1;
10346             ok length($b) == 1;
10347            
10348             my $s = $a.$a.$b.$b;
10349             ok length($s) == 4;
10350            
10351             my $f = eval {writeFile(undef, $s)};
10352             ok fileSize($f) == 8;
10353            
10354            
10355             eval {writeBinaryFile($f, $s)}; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
10356              
10357             ok $@ =~ m(Binary file already exists:)s;
10358            
10359             eval {overWriteBinaryFile($f, $s)};
10360             ok !$@;
10361             ok fileSize($f) == 4;
10362            
10363             ok $s eq eval {readBinaryFile($f)};
10364            
10365             copyBinaryFile($f, my $F = temporaryFile);
10366             ok $s eq readBinaryFile($F);
10367             unlink $f, $F;
10368             }
10369            
10370              
10371             =head3 dumpFile($file, $structure)
10372              
10373             Dump to a B<$file> the referenced data B<$structure>.
10374              
10375             Parameter Description
10376             1 $file File to write to or B for a temporary file
10377             2 $structure Address of data structure to write
10378              
10379             B
10380              
10381              
10382             my $d = [qw(aaa bbb ccc), [{aaa=>'AAA', bbb=>'BBB'}]];
10383            
10384             my $f = dumpFile(undef, $d); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
10385              
10386             is_deeply evalFile($f), $d;
10387             is_deeply evalFile(my $F = dumpTempFile($d)), $d;
10388             unlink $f, $F;
10389            
10390             my $j = dumpFileAsJson(undef, $d);
10391             is_deeply evalFileAsJson($j), $d;
10392             is_deeply evalFileAsJson(my $J = dumpTempFileAsJson($d)), $d;
10393             unlink $j, $J;
10394            
10395              
10396             =head3 dumpTempFile($structure)
10397              
10398             Dump a data structure to a temporary file and return the name of the file created
10399              
10400             Parameter Description
10401             1 $structure Data structure to write
10402              
10403             B
10404              
10405              
10406             my $d = [qw(aaa bbb ccc), [{aaa=>'AAA', bbb=>'BBB'}]];
10407             my $f = dumpFile(undef, $d);
10408             is_deeply evalFile($f), $d;
10409            
10410             is_deeply evalFile(my $F = dumpTempFile($d)), $d; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
10411              
10412             unlink $f, $F;
10413            
10414             my $j = dumpFileAsJson(undef, $d);
10415             is_deeply evalFileAsJson($j), $d;
10416             is_deeply evalFileAsJson(my $J = dumpTempFileAsJson($d)), $d;
10417             unlink $j, $J;
10418            
10419              
10420             =head3 dumpFileAsJson($file, $structure)
10421              
10422             Dump to a B<$file> the referenced data B<$structure> represented as L string.
10423              
10424             Parameter Description
10425             1 $file File to write to or B for a temporary file
10426             2 $structure Address of data structure to write
10427              
10428             B
10429              
10430              
10431             my $d = [qw(aaa bbb ccc), [{aaa=>'AAA', bbb=>'BBB'}]];
10432             my $f = dumpFile(undef, $d);
10433             is_deeply evalFile($f), $d;
10434             is_deeply evalFile(my $F = dumpTempFile($d)), $d;
10435             unlink $f, $F;
10436            
10437            
10438             my $j = dumpFileAsJson(undef, $d); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
10439              
10440             is_deeply evalFileAsJson($j), $d;
10441             is_deeply evalFileAsJson(my $J = dumpTempFileAsJson($d)), $d;
10442             unlink $j, $J;
10443            
10444              
10445             =head3 dumpTempFileAsJson($structure)
10446              
10447             Dump a data structure represented as L string to a temporary file and return the name of the file created.
10448              
10449             Parameter Description
10450             1 $structure Data structure to write
10451              
10452             B
10453              
10454              
10455             my $d = [qw(aaa bbb ccc), [{aaa=>'AAA', bbb=>'BBB'}]];
10456             my $f = dumpFile(undef, $d);
10457             is_deeply evalFile($f), $d;
10458             is_deeply evalFile(my $F = dumpTempFile($d)), $d;
10459             unlink $f, $F;
10460            
10461             my $j = dumpFileAsJson(undef, $d);
10462             is_deeply evalFileAsJson($j), $d;
10463            
10464             is_deeply evalFileAsJson(my $J = dumpTempFileAsJson($d)), $d; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
10465              
10466             unlink $j, $J;
10467            
10468              
10469             =head3 storeFile($file, $structure)
10470              
10471             Store into a B<$file>, after creating a path to the file with L if necessary, a data B<$structure> via L. This is much faster than L but the stored results are not easily modified.
10472              
10473             Parameter Description
10474             1 $file File to write to or B for a temporary file
10475             2 $structure Address of data structure to write
10476              
10477             B
10478              
10479              
10480            
10481             my $f = storeFile(undef, my $d = [qw(aaa bbb ccc)]); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
10482              
10483             my $s = retrieveFile($f);
10484             is_deeply $s, $d;
10485             unlink $f;
10486            
10487              
10488             =head3 writeGZipFile($file, $string)
10489              
10490             Write to a B<$file>, after creating a path to the file with L if necessary, through L a B<$string> whose content is encoded as L.
10491              
10492             Parameter Description
10493             1 $file File to write to
10494             2 $string String to write
10495              
10496             B
10497              
10498              
10499             my $s = '𝝰'x1e3;
10500            
10501             my $file = writeGZipFile(q(zzz.zip), $s); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
10502              
10503             ok -e $file;
10504             my $S = readGZipFile($file);
10505             ok $s eq $S;
10506             ok length($s) == length($S);
10507             unlink $file;
10508            
10509              
10510             =head3 dumpGZipFile($file, $structure)
10511              
10512             Write to a B<$file> a data B<$structure> through L. This technique produces files that are a lot more compact files than those produced by L, but the execution time is much longer. See also: L.
10513              
10514             Parameter Description
10515             1 $file File to write
10516             2 $structure Reference to data
10517              
10518             B
10519              
10520              
10521             my $d = [1, 2, 3=>{a=>4, b=>5}];
10522            
10523             my $file = dumpGZipFile(q(zzz.zip), $d); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
10524              
10525             ok -e $file;
10526             my $D = evalGZipFile($file);
10527             is_deeply $d, $D;
10528             unlink $file;
10529            
10530              
10531             =head3 writeFiles($hash, $old, $new)
10532              
10533             Write the values of a B<$hash> reference into files identified by the key of each value using L optionally swapping the prefix of each file from B<$old> to B<$new>.
10534              
10535             Parameter Description
10536             1 $hash Hash of key value pairs representing files and data
10537             2 $old Optional old prefix
10538             3 $new New prefix
10539              
10540             B
10541              
10542              
10543             my $d = temporaryFolder;
10544             my $a = fpd($d, q(aaa));
10545             my $b = fpd($d, q(bbb));
10546             my $c = fpd($d, q(ccc));
10547             my ($a1, $a2) = map {fpe($a, $_, q(txt))} 1..2;
10548             my ($b1, $b2) = map {fpe($b, $_, q(txt))} 1..2;
10549             my $files = {$a1 => "1111", $a2 => "2222"};
10550            
10551            
10552             writeFiles($files); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
10553              
10554             my $ra = readFiles($a);
10555             is_deeply $files, $ra;
10556             copyFolder($a, $b);
10557             my $rb = readFiles($b);
10558             is_deeply [sort values %$ra], [sort values %$rb];
10559            
10560             unlink $a2;
10561             mergeFolder($a, $b);
10562             ok -e $b1; ok -e $b2;
10563            
10564             copyFolder($a, $b);
10565             ok -e $b1; ok !-e $b2;
10566            
10567             copyFile($a1, $a2);
10568             ok readFile($a1) eq readFile($a2);
10569            
10570            
10571             writeFiles($files); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
10572              
10573             ok !moveFileNoClobber ($a1, $a2);
10574             ok moveFileWithClobber($a1, $a2);
10575             ok !-e $a1;
10576             ok readFile($a2) eq q(1111);
10577             ok moveFileNoClobber ($a2, $a1);
10578             ok !-e $a2;
10579             ok readFile($a1) eq q(1111);
10580            
10581             clearFolder(q(aaa), 11);
10582             clearFolder(q(bbb), 11);
10583            
10584              
10585             =head3 readFiles(@folders)
10586              
10587             Read all the files in the specified list of folders into a hash.
10588              
10589             Parameter Description
10590             1 @folders Folders to read
10591              
10592             B
10593              
10594              
10595             my $d = temporaryFolder;
10596             my $a = fpd($d, q(aaa));
10597             my $b = fpd($d, q(bbb));
10598             my $c = fpd($d, q(ccc));
10599             my ($a1, $a2) = map {fpe($a, $_, q(txt))} 1..2;
10600             my ($b1, $b2) = map {fpe($b, $_, q(txt))} 1..2;
10601             my $files = {$a1 => "1111", $a2 => "2222"};
10602            
10603             writeFiles($files);
10604            
10605             my $ra = readFiles($a); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
10606              
10607             is_deeply $files, $ra;
10608             copyFolder($a, $b);
10609            
10610             my $rb = readFiles($b); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
10611              
10612             is_deeply [sort values %$ra], [sort values %$rb];
10613            
10614             unlink $a2;
10615             mergeFolder($a, $b);
10616             ok -e $b1; ok -e $b2;
10617            
10618             copyFolder($a, $b);
10619             ok -e $b1; ok !-e $b2;
10620            
10621             copyFile($a1, $a2);
10622             ok readFile($a1) eq readFile($a2);
10623            
10624             writeFiles($files);
10625             ok !moveFileNoClobber ($a1, $a2);
10626             ok moveFileWithClobber($a1, $a2);
10627             ok !-e $a1;
10628             ok readFile($a2) eq q(1111);
10629             ok moveFileNoClobber ($a2, $a1);
10630             ok !-e $a2;
10631             ok readFile($a1) eq q(1111);
10632            
10633             clearFolder(q(aaa), 11);
10634             clearFolder(q(bbb), 11);
10635            
10636              
10637             =head3 appendFile($file, $string)
10638              
10639             Append to B<$file> a B<$string> of L content encoded with L, creating the $file first if necessary. Return the name of the $file on success else confess. The $file being appended to is locked before the write with L to allow multiple processes to append linearly to the same file.
10640              
10641             Parameter Description
10642             1 $file File to append to
10643             2 $string String to append
10644              
10645             B
10646              
10647              
10648             my $f = writeFile(undef, "aaa");
10649             is_deeply [readFile $f], ["aaa"];
10650            
10651            
10652             appendFile($f, "bbb"); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
10653              
10654             is_deeply [readFile $f], ["aaabbb"];
10655            
10656             my $F = writeTempFile(qw(aaa bbb));
10657             is_deeply [readFile $F], ["aaa
10658             ", "bbb
10659             "];
10660            
10661             eval {writeFile($f, q(ccc))};
10662             ok $@ =~ m(File already exists:)i;
10663            
10664             overWriteFile($F, q(ccc));
10665             ok readFile($F) eq q(ccc);
10666            
10667             unlink $f, $F;
10668            
10669              
10670             =head3 createEmptyFile($file)
10671              
10672             Create an empty file unless the file already exists and return the name of the file else confess if the file cannot be created.
10673              
10674             Parameter Description
10675             1 $file File to create or B for a temporary file
10676              
10677             B
10678              
10679              
10680             my $D = temporaryFolder;
10681             ok -d $D;
10682            
10683             my $d = fpd($D, q(ddd));
10684             ok !-d $d;
10685            
10686            
10687             my @f = map {createEmptyFile(fpe($d, $_, qw(txt)))} qw(a b c); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
10688              
10689             is_deeply [sort map {fne $_} findFiles($d, qr(txt\Z))], [qw(a.txt b.txt c.txt)];
10690            
10691             my @D = findDirs($D);
10692             my @e = ($D, $d);
10693             my @E = sort @e;
10694             is_deeply [@D], [@E];
10695            
10696             is_deeply [sort map {fne $_} searchDirectoryTreesForMatchingFiles($d)],
10697             ["a.txt", "b.txt", "c.txt"];
10698            
10699             is_deeply [sort map {fne $_} fileList(prefferedFileName "$d/*.txt")],
10700             ["a.txt", "b.txt", "c.txt"];
10701            
10702             ok -e $_ for @f;
10703            
10704             is_deeply scalar(searchDirectoryTreeForSubFolders $D), 2;
10705            
10706             my @g = fileList(qq($D/*/*.txt));
10707             ok @g == 3;
10708            
10709             clearFolder($D, 5);
10710             ok onWindows ? 1 : !-e $_ for @f;
10711             ok onWindows ? 1 : !-d $D;
10712            
10713              
10714             =head3 setPermissionsForFile($file, $permissions)
10715              
10716             Apply L to a B<$file> to set its B<$permissions>.
10717              
10718             Parameter Description
10719             1 $file File
10720             2 $permissions Permissions settings per chmod
10721              
10722             B
10723              
10724              
10725             if (1)
10726             {my $f = temporaryFile();
10727            
10728             setPermissionsForFile($f, q(ugo=r)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
10729              
10730             my $a = qx(ls -la $f);
10731             ok $a =~ m(-r--r--r--)s;
10732            
10733             setPermissionsForFile($f, q(u=rwx)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
10734              
10735             my $b = qx(ls -la $f);
10736             ok $b =~ m(-rwxr--r--)s;
10737             }
10738            
10739              
10740             =head3 numberOfLinesInFile($file)
10741              
10742             Return the number of lines in a file.
10743              
10744             Parameter Description
10745             1 $file File
10746              
10747             B
10748              
10749              
10750             my $f = writeFile(undef, "a
10751             b
10752             ");
10753            
10754            
10755             ok numberOfLinesInFile($f) == 2; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
10756              
10757            
10758              
10759             =head3 overWriteHtmlFile($file, $data)
10760              
10761             Write an L file to /var/www/html and make it readable
10762              
10763             Parameter Description
10764             1 $file Target file relative to /var/www/html
10765             2 $data Data to write
10766              
10767             B
10768              
10769              
10770            
10771             overWriteHtmlFile (q(index.html), q(

Hello

)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
10772              
10773             overWritePerlCgiFile(q(gen.pl), q(...));
10774            
10775              
10776             =head3 overWritePerlCgiFile($file, $data)
10777              
10778             Write a L file to /usr/lib/cgi-bin and make it executable after checking it for syntax errors
10779              
10780             Parameter Description
10781             1 $file Target file relative to /var/www/html
10782             2 $data Data to write
10783              
10784             B
10785              
10786              
10787             overWriteHtmlFile (q(index.html), q(

Hello

));
10788            
10789             overWritePerlCgiFile(q(gen.pl), q(...)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
10790              
10791            
10792              
10793             =head2 Copy
10794              
10795             Copy files and folders. The B<\Acopy.*Md5Normalized.*\Z> methods can be used to ensure that files have collision proof names that collapse duplicate content even when copied to another folder.
10796              
10797             =head3 copyFile($source, $target)
10798              
10799             Copy the B<$source> file encoded in utf8 to the specified B<$target> file in and return $target.
10800              
10801             Parameter Description
10802             1 $source Source file
10803             2 $target Target file
10804              
10805             B
10806              
10807              
10808             my $d = temporaryFolder;
10809             my $a = fpd($d, q(aaa));
10810             my $b = fpd($d, q(bbb));
10811             my $c = fpd($d, q(ccc));
10812             my ($a1, $a2) = map {fpe($a, $_, q(txt))} 1..2;
10813             my ($b1, $b2) = map {fpe($b, $_, q(txt))} 1..2;
10814             my $files = {$a1 => "1111", $a2 => "2222"};
10815            
10816             writeFiles($files);
10817             my $ra = readFiles($a);
10818             is_deeply $files, $ra;
10819             copyFolder($a, $b);
10820             my $rb = readFiles($b);
10821             is_deeply [sort values %$ra], [sort values %$rb];
10822            
10823             unlink $a2;
10824             mergeFolder($a, $b);
10825             ok -e $b1; ok -e $b2;
10826            
10827             copyFolder($a, $b);
10828             ok -e $b1; ok !-e $b2;
10829            
10830            
10831             copyFile($a1, $a2); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
10832              
10833             ok readFile($a1) eq readFile($a2);
10834            
10835             writeFiles($files);
10836             ok !moveFileNoClobber ($a1, $a2);
10837             ok moveFileWithClobber($a1, $a2);
10838             ok !-e $a1;
10839             ok readFile($a2) eq q(1111);
10840             ok moveFileNoClobber ($a2, $a1);
10841             ok !-e $a2;
10842             ok readFile($a1) eq q(1111);
10843            
10844             clearFolder(q(aaa), 11);
10845             clearFolder(q(bbb), 11);
10846            
10847              
10848             =head3 moveFileNoClobber($source, $target)
10849              
10850             Rename the B<$source> file, which must exist, to the B<$target> file but only if the $target file does not exist already. Returns 1 if the $source file was successfully renamed to the $target file else 0.
10851              
10852             Parameter Description
10853             1 $source Source file
10854             2 $target Target file
10855              
10856             B
10857              
10858              
10859             my $d = temporaryFolder;
10860             my $a = fpd($d, q(aaa));
10861             my $b = fpd($d, q(bbb));
10862             my $c = fpd($d, q(ccc));
10863             my ($a1, $a2) = map {fpe($a, $_, q(txt))} 1..2;
10864             my ($b1, $b2) = map {fpe($b, $_, q(txt))} 1..2;
10865             my $files = {$a1 => "1111", $a2 => "2222"};
10866            
10867             writeFiles($files);
10868             my $ra = readFiles($a);
10869             is_deeply $files, $ra;
10870             copyFolder($a, $b);
10871             my $rb = readFiles($b);
10872             is_deeply [sort values %$ra], [sort values %$rb];
10873            
10874             unlink $a2;
10875             mergeFolder($a, $b);
10876             ok -e $b1; ok -e $b2;
10877            
10878             copyFolder($a, $b);
10879             ok -e $b1; ok !-e $b2;
10880            
10881             copyFile($a1, $a2);
10882             ok readFile($a1) eq readFile($a2);
10883            
10884             writeFiles($files);
10885            
10886             ok !moveFileNoClobber ($a1, $a2); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
10887              
10888             ok moveFileWithClobber($a1, $a2);
10889             ok !-e $a1;
10890             ok readFile($a2) eq q(1111);
10891            
10892             ok moveFileNoClobber ($a2, $a1); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
10893              
10894             ok !-e $a2;
10895             ok readFile($a1) eq q(1111);
10896            
10897             clearFolder(q(aaa), 11);
10898             clearFolder(q(bbb), 11);
10899            
10900              
10901             =head3 moveFileWithClobber($source, $target)
10902              
10903             Rename the B<$source> file, which must exist, to the B<$target> file but only if the $target file does not exist already. Returns 1 if the $source file was successfully renamed to the $target file else 0.
10904              
10905             Parameter Description
10906             1 $source Source file
10907             2 $target Target file
10908              
10909             B
10910              
10911              
10912             my $d = temporaryFolder;
10913             my $a = fpd($d, q(aaa));
10914             my $b = fpd($d, q(bbb));
10915             my $c = fpd($d, q(ccc));
10916             my ($a1, $a2) = map {fpe($a, $_, q(txt))} 1..2;
10917             my ($b1, $b2) = map {fpe($b, $_, q(txt))} 1..2;
10918             my $files = {$a1 => "1111", $a2 => "2222"};
10919            
10920             writeFiles($files);
10921             my $ra = readFiles($a);
10922             is_deeply $files, $ra;
10923             copyFolder($a, $b);
10924             my $rb = readFiles($b);
10925             is_deeply [sort values %$ra], [sort values %$rb];
10926            
10927             unlink $a2;
10928             mergeFolder($a, $b);
10929             ok -e $b1; ok -e $b2;
10930            
10931             copyFolder($a, $b);
10932             ok -e $b1; ok !-e $b2;
10933            
10934             copyFile($a1, $a2);
10935             ok readFile($a1) eq readFile($a2);
10936            
10937             writeFiles($files);
10938             ok !moveFileNoClobber ($a1, $a2);
10939            
10940             ok moveFileWithClobber($a1, $a2); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
10941              
10942             ok !-e $a1;
10943             ok readFile($a2) eq q(1111);
10944             ok moveFileNoClobber ($a2, $a1);
10945             ok !-e $a2;
10946             ok readFile($a1) eq q(1111);
10947            
10948             clearFolder(q(aaa), 11);
10949             clearFolder(q(bbb), 11);
10950            
10951              
10952             =head3 copyFileToFolder($source, $targetFolder)
10953              
10954             Copy the file named in B<$source> to the specified B<$targetFolder/> or if $targetFolder/ is in fact a file into the folder containing this file and return the target file name. Confesses instead of copying if the target already exists.
10955              
10956             Parameter Description
10957             1 $source Source file
10958             2 $targetFolder Target folder
10959              
10960             B
10961              
10962              
10963             my $sd = temporaryFolder;
10964             my $td = temporaryFolder;
10965             my $sf = writeFile fpe($sd, qw(test data)), q(aaaa);
10966            
10967             my $tf = copyFileToFolder($sf, $td); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
10968              
10969             ok readFile($tf) eq q(aaaa);
10970             ok fp ($tf) eq $td;
10971             ok fne($tf) eq q(test.data);
10972            
10973              
10974             =head3 nameFromString($string, %options)
10975              
10976             Create a readable name from an arbitrary string of text.
10977              
10978             Parameter Description
10979             1 $string String
10980             2 %options Options
10981              
10982             B
10983              
10984              
10985            
10986             ok q(help) eq nameFromString(q(!@#$%^help___<>?>)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
10987              
10988            
10989             ok q(bm_The_skyscraper_analogy) eq nameFromString(<
10990              
10991            
10992             The skyscraper analogy
10993            
10994             END
10995            
10996             ok q(bm_The_skyscraper_analogy_An_exciting_tale_of_two_skyscrapers_that_meet_in_downtown_Houston)
10997            
10998             eq nameFromString(<
10999              
11000            
11001             The skyscraper analogy
11002             An exciting tale of two skyscrapers that meet in downtown Houston
11003            
11004            
11005             END
11006            
11007             ok q(bm_the_skyscraper_analogy) eq nameFromStringRestrictedToTitle(<
11008            
11009             The skyscraper analogy
11010             An exciting tale of two skyscrapers that meet in downtown Houston
11011            
11012            
11013             END
11014            
11015              
11016             =head3 nameFromStringRestrictedToTitle($string, %options)
11017              
11018             Create a readable name from a string of text that might contain a title tag - fall back to L if that is not possible.
11019              
11020             Parameter Description
11021             1 $string String
11022             2 %options Options
11023              
11024             B
11025              
11026              
11027             ok q(help) eq nameFromString(q(!@#$%^help___<>?>));
11028             ok q(bm_The_skyscraper_analogy) eq nameFromString(<
11029            
11030             The skyscraper analogy
11031            
11032             END
11033            
11034             ok q(bm_The_skyscraper_analogy_An_exciting_tale_of_two_skyscrapers_that_meet_in_downtown_Houston)
11035             eq nameFromString(<
11036            
11037             The skyscraper analogy
11038             An exciting tale of two skyscrapers that meet in downtown Houston
11039            
11040            
11041             END
11042            
11043            
11044             ok q(bm_the_skyscraper_analogy) eq nameFromStringRestrictedToTitle(<
11045              
11046            
11047             The skyscraper analogy
11048             An exciting tale of two skyscrapers that meet in downtown Houston
11049            
11050            
11051             END
11052            
11053              
11054             =head3 uniqueNameFromFile($source)
11055              
11056             Create a unique name from a file name and the md5 sum of its content
11057              
11058             Parameter Description
11059             1 $source Source file
11060              
11061             B
11062              
11063              
11064             my $f = owf(q(test.txt), join "", 1..100);
11065            
11066             ok uniqueNameFromFile($f) eq q(test_ef69caaaeea9c17120821a9eb6c7f1de.txt); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
11067              
11068             unlink $f;
11069            
11070              
11071             =head3 nameFromFolder($file)
11072              
11073             Create a name from the last folder in the path of a file name. Return undef if the file does not have a path.
11074              
11075             Parameter Description
11076             1 $file File name
11077              
11078             B
11079              
11080              
11081            
11082             ok nameFromFolder(fpe(qw( a b c d e))) eq q(c); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
11083              
11084            
11085              
11086             =head3 copyFileMd5Normalized($source, $Target)
11087              
11088             Normalize the name of the specified B<$source> file to the md5 sum of its content, retaining its current extension, while placing the original file name in a companion file if the companion file does not already exist. If no B<$target> folder is supplied the file is renamed to its normalized form in situ, otherwise it is copied to the target folder and renamed there. A companion file for the B<$source> file is created by removing the extension of the normalized file and writing the original B<$source> file name to it unless such a file already exists as we assume that it contains the 'original' original name of the B<$source> file. If the B<$source> file is copied to a new location then the companion file is copied as well to maintain the link back to the original name of the file.
11089              
11090             Parameter Description
11091             1 $source Source file
11092             2 $Target Target folder or a file in the target folder
11093              
11094             B
11095              
11096              
11097             my $dir = temporaryFolder;
11098             my $a = fpe($dir, qw(a a jpg));
11099             my $b = fpe($dir, qw(b a jpg));
11100             my $c = fpe($dir, qw(c a jpg));
11101            
11102             my $content = join '', 1..1e3;
11103            
11104             my $A = copyFileMd5NormalizedCreate($a, $content, q(jpg), $a);
11105             ok readFile($A) eq $content;
11106            
11107             ok $A eq copyFileMd5Normalized($A); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
11108              
11109            
11110            
11111             my $B = copyFileMd5Normalized($A, $b); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
11112              
11113             ok readFile($B) eq $content;
11114            
11115             ok $B eq copyFileMd5Normalized($B); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
11116              
11117            
11118            
11119             my $C = copyFileMd5Normalized($B, $c); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
11120              
11121             ok readFile($C) eq $content;
11122            
11123             ok $C eq copyFileMd5Normalized($C); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
11124              
11125            
11126             ok fne($A) eq fne($_) for $B, $C;
11127             ok readFile($_) eq $content for $A, $B, $C;
11128             ok copyFileMd5NormalizedGetCompanionContent($_) eq $a for $A, $B, $C;
11129            
11130             ok 6 == searchDirectoryTreesForMatchingFiles($dir);
11131             copyFileMd5NormalizedDelete($A);
11132             ok 4 == searchDirectoryTreesForMatchingFiles($dir);
11133             copyFileMd5NormalizedDelete($B);
11134             ok 2 == searchDirectoryTreesForMatchingFiles($dir);
11135             copyFileMd5NormalizedDelete($C);
11136             ok 0 == searchDirectoryTreesForMatchingFiles($dir);
11137            
11138             clearFolder($dir, 10);
11139             ok 0 == searchDirectoryTreesForMatchingFiles($dir);
11140            
11141              
11142             =head3 copyFileMd5NormalizedName($content, $extension, %options)
11143              
11144             Name a file using the GB Standard
11145              
11146             Parameter Description
11147             1 $content Content
11148             2 $extension Extension
11149             3 %options Options
11150              
11151             B
11152              
11153              
11154            
11155             ok copyFileMd5NormalizedName(<
11156              
11157            

HelloWorld

11158             END
11159             q(Hello_World_6ba23858c1b4811660896c324acac6fa.txt);
11160            
11161              
11162             =head3 copyFileMd5NormalizedCreate($Folder, $content, $extension, $companionContent, %options)
11163              
11164             Create a file in the specified B<$folder> whose name is constructed from the md5 sum of the specified B<$content>, whose content is B<$content>, whose extension is B<$extension> and which has a companion file with the same name minus the extension which contains the specified B<$companionContent>. Such a file can be copied multiple times by L regardless of the other files in the target folders.
11165              
11166             Parameter Description
11167             1 $Folder Target folder or a file in that folder
11168             2 $content Content of the file
11169             3 $extension File extension
11170             4 $companionContent Contents of the companion file
11171             5 %options Options.
11172              
11173             B
11174              
11175              
11176             my $dir = temporaryFolder;
11177             my $a = fpe($dir, qw(a a jpg));
11178             my $b = fpe($dir, qw(b a jpg));
11179             my $c = fpe($dir, qw(c a jpg));
11180            
11181             my $content = join '', 1..1e3;
11182            
11183            
11184             my $A = copyFileMd5NormalizedCreate($a, $content, q(jpg), $a); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
11185              
11186             ok readFile($A) eq $content;
11187             ok $A eq copyFileMd5Normalized($A);
11188            
11189             my $B = copyFileMd5Normalized($A, $b);
11190             ok readFile($B) eq $content;
11191             ok $B eq copyFileMd5Normalized($B);
11192            
11193             my $C = copyFileMd5Normalized($B, $c);
11194             ok readFile($C) eq $content;
11195             ok $C eq copyFileMd5Normalized($C);
11196            
11197             ok fne($A) eq fne($_) for $B, $C;
11198             ok readFile($_) eq $content for $A, $B, $C;
11199             ok copyFileMd5NormalizedGetCompanionContent($_) eq $a for $A, $B, $C;
11200            
11201             ok 6 == searchDirectoryTreesForMatchingFiles($dir);
11202             copyFileMd5NormalizedDelete($A);
11203             ok 4 == searchDirectoryTreesForMatchingFiles($dir);
11204             copyFileMd5NormalizedDelete($B);
11205             ok 2 == searchDirectoryTreesForMatchingFiles($dir);
11206             copyFileMd5NormalizedDelete($C);
11207             ok 0 == searchDirectoryTreesForMatchingFiles($dir);
11208            
11209             clearFolder($dir, 10);
11210             ok 0 == searchDirectoryTreesForMatchingFiles($dir);
11211            
11212              
11213             =head3 copyFileMd5NormalizedGetCompanionContent($source)
11214              
11215             Return the content of the companion file to the specified B<$source> file after it has been normalized via L or L or return B if the corresponding companion file does not exist.
11216              
11217             Parameter Description
11218             1 $source Source file.
11219              
11220             B
11221              
11222              
11223             my $dir = temporaryFolder;
11224             my $a = fpe($dir, qw(a a jpg));
11225             my $b = fpe($dir, qw(b a jpg));
11226             my $c = fpe($dir, qw(c a jpg));
11227            
11228             my $content = join '', 1..1e3;
11229            
11230             my $A = copyFileMd5NormalizedCreate($a, $content, q(jpg), $a);
11231             ok readFile($A) eq $content;
11232             ok $A eq copyFileMd5Normalized($A);
11233            
11234             my $B = copyFileMd5Normalized($A, $b);
11235             ok readFile($B) eq $content;
11236             ok $B eq copyFileMd5Normalized($B);
11237            
11238             my $C = copyFileMd5Normalized($B, $c);
11239             ok readFile($C) eq $content;
11240             ok $C eq copyFileMd5Normalized($C);
11241            
11242             ok fne($A) eq fne($_) for $B, $C;
11243             ok readFile($_) eq $content for $A, $B, $C;
11244            
11245             ok copyFileMd5NormalizedGetCompanionContent($_) eq $a for $A, $B, $C; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
11246              
11247            
11248             ok 6 == searchDirectoryTreesForMatchingFiles($dir);
11249             copyFileMd5NormalizedDelete($A);
11250             ok 4 == searchDirectoryTreesForMatchingFiles($dir);
11251             copyFileMd5NormalizedDelete($B);
11252             ok 2 == searchDirectoryTreesForMatchingFiles($dir);
11253             copyFileMd5NormalizedDelete($C);
11254             ok 0 == searchDirectoryTreesForMatchingFiles($dir);
11255            
11256             clearFolder($dir, 10);
11257             ok 0 == searchDirectoryTreesForMatchingFiles($dir);
11258            
11259              
11260             =head3 copyFileMd5NormalizedDelete($file)
11261              
11262             Delete a normalized and its companion file
11263              
11264             Parameter Description
11265             1 $file File
11266              
11267             B
11268              
11269              
11270             my $dir = temporaryFolder;
11271             my $a = fpe($dir, qw(a a jpg));
11272             my $b = fpe($dir, qw(b a jpg));
11273             my $c = fpe($dir, qw(c a jpg));
11274            
11275             my $content = join '', 1..1e3;
11276            
11277             my $A = copyFileMd5NormalizedCreate($a, $content, q(jpg), $a);
11278             ok readFile($A) eq $content;
11279             ok $A eq copyFileMd5Normalized($A);
11280            
11281             my $B = copyFileMd5Normalized($A, $b);
11282             ok readFile($B) eq $content;
11283             ok $B eq copyFileMd5Normalized($B);
11284            
11285             my $C = copyFileMd5Normalized($B, $c);
11286             ok readFile($C) eq $content;
11287             ok $C eq copyFileMd5Normalized($C);
11288            
11289             ok fne($A) eq fne($_) for $B, $C;
11290             ok readFile($_) eq $content for $A, $B, $C;
11291             ok copyFileMd5NormalizedGetCompanionContent($_) eq $a for $A, $B, $C;
11292            
11293             ok 6 == searchDirectoryTreesForMatchingFiles($dir);
11294            
11295             copyFileMd5NormalizedDelete($A); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
11296              
11297             ok 4 == searchDirectoryTreesForMatchingFiles($dir);
11298            
11299             copyFileMd5NormalizedDelete($B); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
11300              
11301             ok 2 == searchDirectoryTreesForMatchingFiles($dir);
11302            
11303             copyFileMd5NormalizedDelete($C); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
11304              
11305             ok 0 == searchDirectoryTreesForMatchingFiles($dir);
11306            
11307             clearFolder($dir, 10);
11308             ok 0 == searchDirectoryTreesForMatchingFiles($dir);
11309            
11310              
11311             =head3 copyBinaryFile($source, $target)
11312              
11313             Copy the binary file B<$source> to a file named <%target> and return the target file name,
11314              
11315             Parameter Description
11316             1 $source Source file
11317             2 $target Target file
11318              
11319             B
11320              
11321              
11322             if (1)
11323             {vec(my $a, 0, 8) = 254;
11324             vec(my $b, 0, 8) = 255;
11325             ok dump($a) eq dump("FE");
11326             ok dump($b) eq dump("FF");
11327             ok length($a) == 1;
11328             ok length($b) == 1;
11329            
11330             my $s = $a.$a.$b.$b;
11331             ok length($s) == 4;
11332            
11333             my $f = eval {writeFile(undef, $s)};
11334             ok fileSize($f) == 8;
11335            
11336             eval {writeBinaryFile($f, $s)};
11337             ok $@ =~ m(Binary file already exists:)s;
11338            
11339             eval {overWriteBinaryFile($f, $s)};
11340             ok !$@;
11341             ok fileSize($f) == 4;
11342            
11343             ok $s eq eval {readBinaryFile($f)};
11344            
11345            
11346             copyBinaryFile($f, my $F = temporaryFile); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
11347              
11348             ok $s eq readBinaryFile($F);
11349             unlink $f, $F;
11350             }
11351            
11352              
11353             =head3 copyBinaryFileMd5Normalized($source, $Target)
11354              
11355             Normalize the name of the specified B<$source> file to the md5 sum of its content, retaining its current extension, while placing the original file name in a companion file if the companion file does not already exist. If no B<$target> folder is supplied the file is renamed to its normalized form in situ, otherwise it is copied to the target folder and renamed there. A companion file for the B<$source> file is created by removing the extension of the normalized file and writing the original B<$source> file name to it unless such a file already exists as we assume that it contains the 'original' original name of the B<$source> file. If the B<$source> file is copied to a new location then the companion file is copied as well to maintain the link back to the original name of the file.
11356              
11357             Parameter Description
11358             1 $source Source file
11359             2 $Target Target folder or a file in the target folder
11360              
11361             B
11362              
11363              
11364             my $dir = temporaryFolder;
11365             my $a = fpe($dir, qw(a a jpg));
11366             my $b = fpe($dir, qw(b a jpg));
11367             my $c = fpe($dir, qw(c a jpg));
11368            
11369             my $content = join '', 1..1e3;
11370            
11371             my $A = copyBinaryFileMd5NormalizedCreate($a, $content, q(jpg), $a);
11372             ok readBinaryFile($A) eq $content;
11373            
11374             ok $A eq copyBinaryFileMd5Normalized($A); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
11375              
11376            
11377            
11378             my $B = copyBinaryFileMd5Normalized($A, $b); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
11379              
11380             ok readBinaryFile($B) eq $content;
11381            
11382             ok $B eq copyBinaryFileMd5Normalized($B); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
11383              
11384            
11385            
11386             my $C = copyBinaryFileMd5Normalized($B, $c); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
11387              
11388             ok readBinaryFile($C) eq $content;
11389            
11390             ok $C eq copyBinaryFileMd5Normalized($C); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
11391              
11392            
11393             ok fne($A) eq fne($_) for $B, $C;
11394             ok readBinaryFile($_) eq $content for $A, $B, $C;
11395             ok copyBinaryFileMd5NormalizedGetCompanionContent($_) eq $a for $A, $B, $C;
11396            
11397             ok 6 == searchDirectoryTreesForMatchingFiles($dir);
11398             clearFolder($dir, 10);
11399            
11400              
11401             =head3 copyBinaryFileMd5NormalizedCreate($Folder, $content, $extension, $companionContent)
11402              
11403             Create a file in the specified B<$folder> whose name is constructed from the md5 sum of the specified B<$content>, whose content is B<$content>, whose extension is B<$extension> and which has a companion file with the same name minus the extension which contains the specified B<$companionContent>. Such a file can be copied multiple times by L regardless of the other files in the target folders while retaining the original name information.
11404              
11405             Parameter Description
11406             1 $Folder Target folder or a file in that folder
11407             2 $content Content of the file
11408             3 $extension File extension
11409             4 $companionContent Optional content of the companion file.
11410              
11411             B
11412              
11413              
11414             my $dir = temporaryFolder;
11415             my $a = fpe($dir, qw(a a jpg));
11416             my $b = fpe($dir, qw(b a jpg));
11417             my $c = fpe($dir, qw(c a jpg));
11418            
11419             my $content = join '', 1..1e3;
11420            
11421            
11422             my $A = copyBinaryFileMd5NormalizedCreate($a, $content, q(jpg), $a); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
11423              
11424             ok readBinaryFile($A) eq $content;
11425             ok $A eq copyBinaryFileMd5Normalized($A);
11426            
11427             my $B = copyBinaryFileMd5Normalized($A, $b);
11428             ok readBinaryFile($B) eq $content;
11429             ok $B eq copyBinaryFileMd5Normalized($B);
11430            
11431             my $C = copyBinaryFileMd5Normalized($B, $c);
11432             ok readBinaryFile($C) eq $content;
11433             ok $C eq copyBinaryFileMd5Normalized($C);
11434            
11435             ok fne($A) eq fne($_) for $B, $C;
11436             ok readBinaryFile($_) eq $content for $A, $B, $C;
11437             ok copyBinaryFileMd5NormalizedGetCompanionContent($_) eq $a for $A, $B, $C;
11438            
11439             ok 6 == searchDirectoryTreesForMatchingFiles($dir);
11440             clearFolder($dir, 10);
11441            
11442              
11443             =head3 copyBinaryFileMd5NormalizedGetCompanionContent($source)
11444              
11445             Return the original name of the specified B<$source> file after it has been normalized via L or L or return B if the corresponding companion file does not exist.
11446              
11447             Parameter Description
11448             1 $source Source file.
11449              
11450             B
11451              
11452              
11453             my $dir = temporaryFolder;
11454             my $a = fpe($dir, qw(a a jpg));
11455             my $b = fpe($dir, qw(b a jpg));
11456             my $c = fpe($dir, qw(c a jpg));
11457            
11458             my $content = join '', 1..1e3;
11459            
11460             my $A = copyBinaryFileMd5NormalizedCreate($a, $content, q(jpg), $a);
11461             ok readBinaryFile($A) eq $content;
11462             ok $A eq copyBinaryFileMd5Normalized($A);
11463            
11464             my $B = copyBinaryFileMd5Normalized($A, $b);
11465             ok readBinaryFile($B) eq $content;
11466             ok $B eq copyBinaryFileMd5Normalized($B);
11467            
11468             my $C = copyBinaryFileMd5Normalized($B, $c);
11469             ok readBinaryFile($C) eq $content;
11470             ok $C eq copyBinaryFileMd5Normalized($C);
11471            
11472             ok fne($A) eq fne($_) for $B, $C;
11473             ok readBinaryFile($_) eq $content for $A, $B, $C;
11474            
11475             ok copyBinaryFileMd5NormalizedGetCompanionContent($_) eq $a for $A, $B, $C; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
11476              
11477            
11478             ok 6 == searchDirectoryTreesForMatchingFiles($dir);
11479             clearFolder($dir, 10);
11480            
11481              
11482             =head3 copyFileToRemote($file, $ip)
11483              
11484             Copy the specified local B<$file> to the server whose ip address is specified by B<$ip> or returned by L.
11485              
11486             Parameter Description
11487             1 $file Source file
11488             2 $ip Optional ip address
11489              
11490             B
11491              
11492              
11493             if (0)
11494            
11495             {copyFileToRemote (q(/home/phil/perl/cpan/aaa.txt)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
11496              
11497             copyFileFromRemote (q(/home/phil/perl/cpan/aaa.txt));
11498             copyFolderToRemote (q(/home/phil/perl/cpan/));
11499             mergeFolderFromRemote(q(/home/phil/perl/cpan/));
11500             }
11501            
11502              
11503             =head3 copyFileFromRemote($file, $ip)
11504              
11505             Copy the specified B<$file> from the server whose ip address is specified by B<$ip> or returned by L.
11506              
11507             Parameter Description
11508             1 $file Source file
11509             2 $ip Optional ip address
11510              
11511             B
11512              
11513              
11514             if (0)
11515             {copyFileToRemote (q(/home/phil/perl/cpan/aaa.txt));
11516            
11517             copyFileFromRemote (q(/home/phil/perl/cpan/aaa.txt)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
11518              
11519             copyFolderToRemote (q(/home/phil/perl/cpan/));
11520             mergeFolderFromRemote(q(/home/phil/perl/cpan/));
11521             }
11522            
11523              
11524             =head3 copyFolder($source, $target)
11525              
11526             Copy the B<$source> folder to the B<$target> folder after clearing the $target folder.
11527              
11528             Parameter Description
11529             1 $source Source file
11530             2 $target Target file
11531              
11532             B
11533              
11534              
11535             my $d = temporaryFolder;
11536             my $a = fpd($d, q(aaa));
11537             my $b = fpd($d, q(bbb));
11538             my $c = fpd($d, q(ccc));
11539             my ($a1, $a2) = map {fpe($a, $_, q(txt))} 1..2;
11540             my ($b1, $b2) = map {fpe($b, $_, q(txt))} 1..2;
11541             my $files = {$a1 => "1111", $a2 => "2222"};
11542            
11543             writeFiles($files);
11544             my $ra = readFiles($a);
11545             is_deeply $files, $ra;
11546            
11547             copyFolder($a, $b); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
11548              
11549             my $rb = readFiles($b);
11550             is_deeply [sort values %$ra], [sort values %$rb];
11551            
11552             unlink $a2;
11553             mergeFolder($a, $b);
11554             ok -e $b1; ok -e $b2;
11555            
11556            
11557             copyFolder($a, $b); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
11558              
11559             ok -e $b1; ok !-e $b2;
11560            
11561             copyFile($a1, $a2);
11562             ok readFile($a1) eq readFile($a2);
11563            
11564             writeFiles($files);
11565             ok !moveFileNoClobber ($a1, $a2);
11566             ok moveFileWithClobber($a1, $a2);
11567             ok !-e $a1;
11568             ok readFile($a2) eq q(1111);
11569             ok moveFileNoClobber ($a2, $a1);
11570             ok !-e $a2;
11571             ok readFile($a1) eq q(1111);
11572            
11573             clearFolder(q(aaa), 11);
11574             clearFolder(q(bbb), 11);
11575            
11576              
11577             =head3 mergeFolder($source, $target)
11578              
11579             Copy the B<$source> folder into the B<$target> folder retaining any existing files not replaced by copied files.
11580              
11581             Parameter Description
11582             1 $source Source file
11583             2 $target Target file
11584              
11585             B
11586              
11587              
11588             my $d = temporaryFolder;
11589             my $a = fpd($d, q(aaa));
11590             my $b = fpd($d, q(bbb));
11591             my $c = fpd($d, q(ccc));
11592             my ($a1, $a2) = map {fpe($a, $_, q(txt))} 1..2;
11593             my ($b1, $b2) = map {fpe($b, $_, q(txt))} 1..2;
11594             my $files = {$a1 => "1111", $a2 => "2222"};
11595            
11596             writeFiles($files);
11597             my $ra = readFiles($a);
11598             is_deeply $files, $ra;
11599             copyFolder($a, $b);
11600             my $rb = readFiles($b);
11601             is_deeply [sort values %$ra], [sort values %$rb];
11602            
11603             unlink $a2;
11604            
11605             mergeFolder($a, $b); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
11606              
11607             ok -e $b1; ok -e $b2;
11608            
11609             copyFolder($a, $b);
11610             ok -e $b1; ok !-e $b2;
11611            
11612             copyFile($a1, $a2);
11613             ok readFile($a1) eq readFile($a2);
11614            
11615             writeFiles($files);
11616             ok !moveFileNoClobber ($a1, $a2);
11617             ok moveFileWithClobber($a1, $a2);
11618             ok !-e $a1;
11619             ok readFile($a2) eq q(1111);
11620             ok moveFileNoClobber ($a2, $a1);
11621             ok !-e $a2;
11622             ok readFile($a1) eq q(1111);
11623            
11624             clearFolder(q(aaa), 11);
11625             clearFolder(q(bbb), 11);
11626            
11627              
11628             =head3 copyFolderToRemote($Source, $ip)
11629              
11630             Copy the specified local B<$Source> folder to the corresponding remote folder on the server whose ip address is specified by B<$ip> or returned by L. The default userid supplied by F<.ssh/config> will be used on the remote server.
11631              
11632             Parameter Description
11633             1 $Source Source file
11634             2 $ip Optional ip address of server
11635              
11636             B
11637              
11638              
11639             if (0)
11640             {copyFileToRemote (q(/home/phil/perl/cpan/aaa.txt));
11641             copyFileFromRemote (q(/home/phil/perl/cpan/aaa.txt));
11642            
11643             copyFolderToRemote (q(/home/phil/perl/cpan/)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
11644              
11645             mergeFolderFromRemote(q(/home/phil/perl/cpan/));
11646             }
11647            
11648              
11649             =head3 mergeFolderFromRemote($Source, $ip)
11650              
11651             Merge the specified B<$Source> folder from the corresponding remote folder on the server whose ip address is specified by B<$ip> or returned by L. The default userid supplied by F<.ssh/config> will be used on the remote server.
11652              
11653             Parameter Description
11654             1 $Source Source file
11655             2 $ip Optional ip address of server
11656              
11657             B
11658              
11659              
11660             if (0)
11661             {copyFileToRemote (q(/home/phil/perl/cpan/aaa.txt));
11662             copyFileFromRemote (q(/home/phil/perl/cpan/aaa.txt));
11663             copyFolderToRemote (q(/home/phil/perl/cpan/));
11664            
11665             mergeFolderFromRemote(q(/home/phil/perl/cpan/)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
11666              
11667             }
11668            
11669              
11670             =head1 Testing
11671              
11672             Methods to assist with testing
11673              
11674             =head2 removeFilePathsFromStructure($structure)
11675              
11676             Remove all file paths from a specified B<$structure> to make said $structure testable with L.
11677              
11678             Parameter Description
11679             1 $structure Data structure reference
11680              
11681             B
11682              
11683              
11684             if (1)
11685             {my $d = {"/home/aaa/bbb.txt"=>1, "ccc/ddd.txt"=>2, "eee.txt"=>3};
11686            
11687             my $D = removeFilePathsFromStructure($d); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
11688              
11689            
11690            
11691             is_deeply removeFilePathsFromStructure($d), # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
11692              
11693             {"bbb.txt"=>1, "ddd.txt"=>2, "eee.txt"=>3};
11694            
11695             ok writeStructureTest($d, q($d)) eq <<'END';
11696            
11697             is_deeply removeFilePathsFromStructure($d), # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
11698              
11699             { "bbb.txt" => 1, "ddd.txt" => 2, "eee.txt" => 3 };
11700             END
11701             }
11702            
11703              
11704             =head2 writeStructureTest($structure, $expr)
11705              
11706             Write a test for a data B<$structure> with file names in it.
11707              
11708             Parameter Description
11709             1 $structure Data structure reference
11710             2 $expr Expression
11711              
11712             B
11713              
11714              
11715             if (1)
11716             {my $d = {"/home/aaa/bbb.txt"=>1, "ccc/ddd.txt"=>2, "eee.txt"=>3};
11717             my $D = removeFilePathsFromStructure($d);
11718            
11719             is_deeply removeFilePathsFromStructure($d),
11720             {"bbb.txt"=>1, "ddd.txt"=>2, "eee.txt"=>3};
11721            
11722            
11723             ok writeStructureTest($d, q($d)) eq <<'END'; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
11724              
11725             is_deeply removeFilePathsFromStructure($d),
11726             { "bbb.txt" => 1, "ddd.txt" => 2, "eee.txt" => 3 };
11727             END
11728             }
11729            
11730              
11731             =head1 Images
11732              
11733             Image operations.
11734              
11735             =head2 imageSize($image)
11736              
11737             Return (width, height) of an B<$image>.
11738              
11739             Parameter Description
11740             1 $image File containing image
11741              
11742             B
11743              
11744              
11745            
11746             my ($width, $height) = imageSize(fpe(qw(a image jpg))); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
11747              
11748            
11749              
11750             =head2 convertDocxToFodt($inputFile, $outputFile)
11751              
11752             Convert a I B<$inputFile> file to a I B<$outputFile> using B which must not be running elsewhere at the time. L can be installed via:
11753              
11754             sudo apt install sharutils unoconv
11755              
11756             Parameters:
11757              
11758             Parameter Description
11759             1 $inputFile Input file
11760             2 $outputFile Output file
11761              
11762             B
11763              
11764              
11765            
11766             convertDocxToFodt(fpe(qw(a docx)), fpe(qw(a fodt))); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
11767              
11768            
11769              
11770             =head2 cutOutImagesInFodtFile($inputFile, $outputFolder, $imagePrefix)
11771              
11772             Cut out the images embedded in a B file, perhaps produced via L, placing them in the specified folder and replacing them in the source file with:
11773              
11774             .
11775              
11776             This conversion requires that you have both L and L installed on your system:
11777              
11778             sudo apt install sharutils imagemagick unoconv
11779              
11780             Parameters:
11781              
11782             Parameter Description
11783             1 $inputFile Input file
11784             2 $outputFolder Output folder for images
11785             3 $imagePrefix A prefix to be added to image file names
11786              
11787             B
11788              
11789              
11790            
11791             cutOutImagesInFodtFile(fpe(qw(source fodt)), fpd(qw(images)), q(image)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
11792              
11793            
11794              
11795             =head1 Encoding and Decoding
11796              
11797             Encode and decode using L and Mime.
11798              
11799             =head2 unbless($d)
11800              
11801             Remove the effects of bless from a L data B<$structure> enabling it to be converted to L or compared with L.
11802              
11803             Parameter Description
11804             1 $d Unbless a L data structure.
11805              
11806             B
11807              
11808              
11809             if (1)
11810             {my $a = {};
11811             ok ref($a) eq q(HASH);
11812             my $b = bless $a, q(aaaa);
11813             ok ref($a) eq q(aaaa);
11814            
11815             my $c = unbless $b; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
11816              
11817             ok ref($c) eq q(HASH);
11818             }
11819            
11820              
11821             =head2 encodeJson($structure)
11822              
11823             Convert a L data B<$structure> to a L string.
11824              
11825             Parameter Description
11826             1 $structure Data to encode
11827              
11828             B
11829              
11830              
11831            
11832             my $A = encodeJson(my $a = {a=>1,b=>2, c=>[1..2]}); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
11833              
11834             my $b = decodeJson($A);
11835             is_deeply $a, $b;
11836            
11837              
11838             =head2 decodeJson($string)
11839              
11840             Convert a L B<$string> to a L data structure.
11841              
11842             Parameter Description
11843             1 $string Data to decode
11844              
11845             B
11846              
11847              
11848             my $A = encodeJson(my $a = {a=>1,b=>2, c=>[1..2]});
11849            
11850             my $b = decodeJson($A); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
11851              
11852             is_deeply $a, $b;
11853            
11854              
11855             =head2 encodeBase64($string)
11856              
11857             Encode an L B<$string> in base 64.
11858              
11859             Parameter Description
11860             1 $string String to encode
11861              
11862             B
11863              
11864              
11865            
11866             my $A = encodeBase64(my $a = "Hello World" x 10); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
11867              
11868             my $b = decodeBase64($A);
11869             ok $a eq $b;
11870            
11871              
11872             =head2 decodeBase64($string)
11873              
11874             Decode an L B<$string> in base 64.
11875              
11876             Parameter Description
11877             1 $string String to decode
11878              
11879             B
11880              
11881              
11882             my $A = encodeBase64(my $a = "Hello World" x 10);
11883            
11884             my $b = decodeBase64($A); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
11885              
11886             ok $a eq $b;
11887            
11888              
11889             =head2 convertUnicodeToXml($string)
11890              
11891             Convert a B<$string> with L code points that are not directly representable in L into string that replaces these code points with their representation in L making the string usable in L documents.
11892              
11893             Parameter Description
11894             1 $string String to convert
11895              
11896             B
11897              
11898              
11899            
11900             ok convertUnicodeToXml('setenta e três') eq q(setenta e três); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
11901              
11902            
11903              
11904             =head2 asciiToHexString($ascii)
11905              
11906             Encode an L string as a string of L digits.
11907              
11908             Parameter Description
11909             1 $ascii Ascii string
11910              
11911             B
11912              
11913              
11914            
11915             ok asciiToHexString("Hello World!") eq "48656c6c6f20576f726c6421"; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
11916              
11917             ok "Hello World!" eq hexToAsciiString("48656c6c6f20576f726c6421");
11918            
11919              
11920             =head2 hexToAsciiString($hex)
11921              
11922             Decode a string of L digits as an L string.
11923              
11924             Parameter Description
11925             1 $hex Hexadecimal string
11926              
11927             B
11928              
11929              
11930             ok asciiToHexString("Hello World!") eq "48656c6c6f20576f726c6421";
11931            
11932             ok "Hello World!" eq hexToAsciiString("48656c6c6f20576f726c6421"); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
11933              
11934            
11935              
11936             =head2 wwwEncode($string)
11937              
11938             Percent encode a L per: https://en.wikipedia.org/wiki/Percent-encoding#Percent-encoding_reserved_characters
11939              
11940             Parameter Description
11941             1 $string String
11942              
11943             B
11944              
11945              
11946            
11947             ok wwwEncode(q(a {b} )) eq q(a%20%20%7bb%7d%20%3cc%3e); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
11948              
11949            
11950             ok wwwEncode(q(../)) eq q(%2e%2e/); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
11951              
11952            
11953             ok wwwDecode(wwwEncode $_) eq $_ for q(a {b} ), q(a b|c), # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
11954              
11955             q(%), q(%%), q(%%.%%);
11956            
11957            
11958             sub wwwEncode($) # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
11959              
11960             {my ($string) = @_; # String
11961             join '', map {$translatePercentEncoding{$_}//$_} split //, $string
11962             }
11963            
11964              
11965             =head2 wwwDecode($string)
11966              
11967             Percent decode a L B<$string> per: https://en.wikipedia.org/wiki/Percent-encoding#Percent-encoding_reserved_characters
11968              
11969             Parameter Description
11970             1 $string String
11971              
11972             B
11973              
11974              
11975             ok wwwEncode(q(a {b} )) eq q(a%20%20%7bb%7d%20%3cc%3e);
11976             ok wwwEncode(q(../)) eq q(%2e%2e/);
11977            
11978             ok wwwDecode(wwwEncode $_) eq $_ for q(a {b} ), q(a b|c), # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
11979              
11980             q(%), q(%%), q(%%.%%);
11981            
11982            
11983             sub wwwDecode($) # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
11984              
11985             {my ($string) = @_; # String
11986             my $r = '';
11987             my @s = split //, $string;
11988             while(@s)
11989             {my $c = shift @s;
11990             if ($c eq q(%) and @s >= 2)
11991             {$c .= shift(@s).shift(@s);
11992             $r .= $TranslatePercentEncoding{$c}//$c;
11993             }
11994             else
11995             {$r .= $c;
11996             }
11997             }
11998             $r =~ s(%0d0a) (
11999             )gs; # Awkward characters that appear in urls
12000             $r =~ s(\+) ( )gs;
12001             $r
12002             }
12003            
12004              
12005             =head1 Numbers
12006              
12007             Numeric operations,
12008              
12009             =head2 powerOfTwo($n)
12010              
12011             Test whether a number B<$n> is a power of two, return the power if it is else B.
12012              
12013             Parameter Description
12014             1 $n Number to check
12015              
12016             B
12017              
12018              
12019            
12020             ok powerOfTwo(1) == 0; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
12021              
12022            
12023            
12024             ok powerOfTwo(2) == 1; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
12025              
12026            
12027            
12028             ok !powerOfTwo(3); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
12029              
12030            
12031            
12032             ok powerOfTwo(4) == 2; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
12033              
12034            
12035              
12036             =head2 containingPowerOfTwo($n)
12037              
12038             Find log two of the lowest power of two greater than or equal to a number B<$n>.
12039              
12040             Parameter Description
12041             1 $n Number to check
12042              
12043             B
12044              
12045              
12046            
12047             ok containingPowerOfTwo(1) == 0; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
12048              
12049            
12050            
12051             ok containingPowerOfTwo(2) == 1; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
12052              
12053            
12054            
12055             ok containingPowerOfTwo(3) == 2; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
12056              
12057            
12058            
12059             ok containingPowerOfTwo(4) == 2; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
12060              
12061            
12062              
12063             =head2 Minima and Maxima
12064              
12065             Find the smallest and largest elements of arrays.
12066              
12067             =head3 min(@m)
12068              
12069             Find the minimum number in a list of numbers confessing to any ill defined values.
12070              
12071             Parameter Description
12072             1 @m Numbers
12073              
12074             B
12075              
12076              
12077             ok !max;
12078             ok max(1) == 1;
12079             ok max(1,4,2,3) == 4;
12080            
12081            
12082             ok min(1) == 1; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
12083              
12084            
12085             ok min(5,4,2,3) == 2; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
12086              
12087            
12088              
12089             =head3 indexOfMin(@m)
12090              
12091             Find the index of the minimum number in a list of numbers confessing to any ill defined values.
12092              
12093             Parameter Description
12094             1 @m Numbers
12095              
12096             B
12097              
12098              
12099            
12100             ok indexOfMin(qw(2 3 1 2)) == 2; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
12101              
12102            
12103              
12104             =head3 max(@m)
12105              
12106             Find the maximum number in a list of numbers confessing to any ill defined values.
12107              
12108             Parameter Description
12109             1 @m Numbers
12110              
12111             B
12112              
12113              
12114            
12115             ok !max; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
12116              
12117            
12118             ok max(1) == 1; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
12119              
12120            
12121             ok max(1,4,2,3) == 4; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
12122              
12123            
12124             ok min(1) == 1;
12125             ok min(5,4,2,3) == 2;
12126            
12127              
12128             =head3 indexOfMax(@m)
12129              
12130             Find the index of the maximum number in a list of numbers confessing to any ill defined values.
12131              
12132             Parameter Description
12133             1 @m Numbers
12134              
12135             B
12136              
12137              
12138            
12139             {ok indexOfMax(qw(2 3 1 2)) == 1; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
12140              
12141            
12142              
12143             =head3 arraySum(@a)
12144              
12145             Find the sum of any strings that look like numbers in an array.
12146              
12147             Parameter Description
12148             1 @a Array to sum
12149              
12150             B
12151              
12152              
12153            
12154             {ok arraySum (1..10) == 55; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
12155              
12156            
12157              
12158             =head3 arrayProduct(@a)
12159              
12160             Find the product of any strings that look like numbers in an array.
12161              
12162             Parameter Description
12163             1 @a Array to multiply
12164              
12165             B
12166              
12167              
12168            
12169             ok arrayProduct(1..5) == 120; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
12170              
12171            
12172              
12173             =head3 arrayTimes($multiplier, @a)
12174              
12175             Multiply by B<$multiplier> each element of the array B<@a> and return as the result.
12176              
12177             Parameter Description
12178             1 $multiplier Multiplier
12179             2 @a Array to multiply and return
12180              
12181             B
12182              
12183              
12184            
12185             is_deeply[arrayTimes(2, 1..5)], [qw(2 4 6 8 10)]; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
12186              
12187            
12188              
12189             =head1 Sets
12190              
12191             Set operations.
12192              
12193             =head2 mergeHashesBySummingValues(@h)
12194              
12195             Merge a list of hashes B<@h> by summing their values
12196              
12197             Parameter Description
12198             1 @h List of hashes to be summed
12199              
12200             B
12201              
12202              
12203             is_deeply +{a=>1, b=>2, c=>3},
12204            
12205             mergeHashesBySummingValues # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
12206              
12207             +{a=>1,b=>1, c=>1}, +{b=>1,c=>1}, +{c=>1};
12208            
12209              
12210             =head2 invertHashOfHashes($h)
12211              
12212             Invert a hash of hashes: given {a}{b} = c return {b}{c} = c
12213              
12214             Parameter Description
12215             1 $h Hash of hashes
12216              
12217             B
12218              
12219              
12220             my $h = {a=>{A=>q(aA), B=>q(aB)}, b=>{A=>q(bA), B=>q(bB)}};
12221             my $g = {A=>{a=>q(aA), b=>q(bA)}, B=>{a=>q(aB), b=>q(bB)}};
12222            
12223            
12224             is_deeply invertHashOfHashes($h), $g; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
12225              
12226            
12227             is_deeply invertHashOfHashes($g), $h; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
12228              
12229            
12230              
12231             =head2 unionOfHashKeys(@h)
12232              
12233             Form the union of the keys of the specified hashes B<@h> as one hash whose keys represent the union.
12234              
12235             Parameter Description
12236             1 @h List of hashes to be united
12237              
12238             B
12239              
12240              
12241             if (1)
12242            
12243             {is_deeply unionOfHashKeys # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
12244              
12245             ({a=>1,b=>2}, {b=>1,c=>1}, {c=>2}),
12246             {a=>1, b=>2, c=>2};
12247            
12248             is_deeply intersectionOfHashKeys
12249             ({a=>1,b=>2},{b=>1,c=>1},{b=>3,c=>2}),
12250             {b=>1};
12251             }
12252            
12253              
12254             =head2 intersectionOfHashKeys(@h)
12255              
12256             Form the intersection of the keys of the specified hashes B<@h> as one hash whose keys represent the intersection.
12257              
12258             Parameter Description
12259             1 @h List of hashes to be intersected
12260              
12261             B
12262              
12263              
12264             if (1)
12265             {is_deeply unionOfHashKeys
12266             ({a=>1,b=>2}, {b=>1,c=>1}, {c=>2}),
12267             {a=>1, b=>2, c=>2};
12268            
12269            
12270             is_deeply intersectionOfHashKeys # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
12271              
12272             ({a=>1,b=>2},{b=>1,c=>1},{b=>3,c=>2}),
12273             {b=>1};
12274             }
12275            
12276              
12277             =head2 unionOfHashesAsArrays(@h)
12278              
12279             Form the union of the specified hashes B<@h> as one hash whose values are a array of corresponding values from each hash
12280              
12281             Parameter Description
12282             1 @h List of hashes to be united
12283              
12284             B
12285              
12286              
12287             if (1)
12288            
12289             {is_deeply unionOfHashesAsArrays # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
12290              
12291             ({a=>1,b=>2}, {b=>1,c=>1}, {c=>2}),
12292             {a=>[1], b=>[2,1], c=>[undef,1,2]};
12293            
12294             is_deeply intersectionOfHashesAsArrays
12295             ({a=>1,b=>2},{b=>1,c=>1},{b=>3,c=>2}),
12296             {b=>[2,1,3]};
12297             }
12298            
12299              
12300             =head2 intersectionOfHashesAsArrays(@h)
12301              
12302             Form the intersection of the specified hashes B<@h> as one hash whose values are an array of corresponding values from each hash
12303              
12304             Parameter Description
12305             1 @h List of hashes to be intersected
12306              
12307             B
12308              
12309              
12310             if (1)
12311             {is_deeply unionOfHashesAsArrays
12312             ({a=>1,b=>2}, {b=>1,c=>1}, {c=>2}),
12313             {a=>[1], b=>[2,1], c=>[undef,1,2]};
12314            
12315            
12316             is_deeply intersectionOfHashesAsArrays # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
12317              
12318             ({a=>1,b=>2},{b=>1,c=>1},{b=>3,c=>2}),
12319             {b=>[2,1,3]};
12320             }
12321            
12322              
12323             =head2 setUnion(@s)
12324              
12325             Union of sets B<@s> represented as arrays of strings and/or the keys of hashes
12326              
12327             Parameter Description
12328             1 @s Array of arrays of strings and/or hashes
12329              
12330             B
12331              
12332              
12333            
12334             is_deeply [qw(a b c)], [setUnion(qw(a b c a a b b b))]; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
12335              
12336            
12337             is_deeply [qw(a b c d e)], [setUnion {a=>1, b=>2, e=>3}, [qw(c d e)], qw(e)]; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
12338              
12339            
12340              
12341             =head2 setIntersection(@s)
12342              
12343             Intersection of sets B<@s> represented as arrays of strings and/or the keys of hashes
12344              
12345             Parameter Description
12346             1 @s Array of arrays of strings and/or hashes
12347              
12348             B
12349              
12350              
12351            
12352             is_deeply [qw(a b c)], [setIntersection[qw(e f g a b c )],[qw(a A b B c C)]]; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
12353              
12354            
12355             is_deeply [qw(e)], [setIntersection {a=>1, b=>2, e=>3}, [qw(c d e)], qw(e)]; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
12356              
12357            
12358              
12359             =head2 setIntersectionOverUnion(@s)
12360              
12361             Returns the size of the intersection over the size of the union of one or more sets B<@s> represented as arrays and/or hashes
12362              
12363             Parameter Description
12364             1 @s Array of arrays of strings and/or hashes
12365              
12366             B
12367              
12368              
12369            
12370             my $f = setIntersectionOverUnion {a=>1, b=>2, e=>3}, [qw(c d e)], qw(e); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
12371              
12372             ok $f > 0.199999 && $f < 0.200001;
12373            
12374              
12375             =head2 setPartitionOnIntersectionOverUnion($confidence, @sets)
12376              
12377             Partition, at a level of B<$confidence> between 0 and 1, a set of sets B<@sets> so that within each partition the L of any two sets in the partition is never less than the specified level of I<$confidence**2>
12378              
12379             Parameter Description
12380             1 $confidence Minimum setIntersectionOverUnion
12381             2 @sets Array of arrays of strings and/or hashes representing sets
12382              
12383             B
12384              
12385              
12386            
12387             is_deeply [setPartitionOnIntersectionOverUnion # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
12388              
12389             (0.80,
12390             [qw(a A b c d e)],
12391             [qw(a A B b c d e)],
12392             [qw(a A B C b c d)],
12393             )],
12394             [[["A", "B", "a".."e"],
12395             ["A", "a".."e"]],
12396             [["A".."C", "a".."d"]],
12397             ];
12398             }
12399            
12400            
12401            
12402            
12403             if (1) {
12404             is_deeply [setPartitionOnIntersectionOverUnionOfSetsOfWords
12405             (0.80,
12406             [qw(a A b c d e)],
12407             [qw(a A B b c d e)],
12408             [qw(a A B C b c d)],
12409             )],
12410             [[["a", "A", "B", "C", "b", "c", "d"]],
12411             [["a", "A", "B", "b" .. "e"], ["a", "A", "b" .. "e"]],
12412             ];
12413            
12414              
12415             =head2 setPartitionOnIntersectionOverUnionOfSetsOfWords($confidence, @sets)
12416              
12417             Partition, at a level of B<$confidence> between 0 and 1, a set of sets B<@sets> of words so that within each partition the L of any two sets of words in the partition is never less than the specified I<$confidence**2>
12418              
12419             Parameter Description
12420             1 $confidence Minimum setIntersectionOverUnion
12421             2 @sets Array of arrays of strings and/or hashes representing sets
12422              
12423             B
12424              
12425              
12426            
12427             is_deeply [setPartitionOnIntersectionOverUnionOfSetsOfWords # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
12428              
12429             (0.80,
12430             [qw(a A b c d e)],
12431             [qw(a A B b c d e)],
12432             [qw(a A B C b c d)],
12433             )],
12434             [[["a", "A", "B", "C", "b", "c", "d"]],
12435             [["a", "A", "B", "b" .. "e"], ["a", "A", "b" .. "e"]],
12436             ];
12437            
12438              
12439             =head2 setPartitionOnIntersectionOverUnionOfStringSets($confidence, @strings)
12440              
12441             Partition, at a level of B<$confidence> between 0 and 1, a set of sets B<@strings>, each set represented by a string containing words and punctuation, each word possibly capitalized, so that within each partition the L of any two sets of words in the partition is never less than the specified I<$confidence**2>
12442              
12443             Parameter Description
12444             1 $confidence Minimum setIntersectionOverUnion
12445             2 @strings Sets represented by strings
12446              
12447             B
12448              
12449              
12450            
12451             is_deeply [setPartitionOnIntersectionOverUnionOfStringSets # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
12452              
12453             (0.80,
12454             q(The Emu are seen here sometimes.),
12455             q(The Emu, Gnu are seen here sometimes.),
12456             q(The Emu, Gnu, Colt are seen here.),
12457             )],
12458             [["The Emu, Gnu, Colt are seen here."],
12459             ["The Emu, Gnu are seen here sometimes.",
12460             "The Emu are seen here sometimes.",
12461             ]];
12462            
12463              
12464             =head2 setPartitionOnIntersectionOverUnionOfHashStringSets($confidence, $hashSet)
12465              
12466             Partition, at a level of B<$confidence> between 0 and 1, a set of sets B<$hashSet> represented by a hash, each hash value being a string containing words and punctuation, each word possibly capitalized, so that within each partition the L of any two sets of words in the partition is never less than the specified B<$confidence**2> and the partition entries are the hash keys of the string sets.
12467              
12468             Parameter Description
12469             1 $confidence Minimum setIntersectionOverUnion
12470             2 $hashSet Sets represented by the hash value strings
12471              
12472             B
12473              
12474              
12475            
12476             is_deeply [setPartitionOnIntersectionOverUnionOfHashStringSets # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
12477              
12478             (0.80,
12479             {e =>q(The Emu are seen here sometimes.),
12480             eg =>q(The Emu, Gnu are seen here sometimes.),
12481             egc=>q(The Emu, Gnu, Colt are seen here.),
12482             }
12483             )],
12484             [["e", "eg"], ["egc"]];
12485            
12486              
12487             =head2 setPartitionOnIntersectionOverUnionOfHashStringSetsInParallel($confidence, $hashSet)
12488              
12489             Partition, at a level of B<$confidence> between 0 and 1, a set of sets B<$hashSet> represented by a hash, each hash value being a string containing words and punctuation, each word possibly capitalized, so that within each partition the L of any two sets of words in the partition is never less than the specified B<$confidence**2> and the partition entries are the hash keys of the string sets. The partition is performed in square root parallel.
12490              
12491             Parameter Description
12492             1 $confidence Minimum setIntersectionOverUnion
12493             2 $hashSet Sets represented by the hash value strings
12494              
12495             B
12496              
12497              
12498             my $N = 8;
12499             my %s;
12500             for my $a('a'..'z')
12501             {my @w;
12502             for my $b('a'..'e')
12503             {for my $c('a'..'e')
12504             {push @w, qq($a$b$c);
12505             }
12506             }
12507            
12508             for my $i(1..$N)
12509             {$s{qq($a$i)} = join ' ', @w;
12510             }
12511             }
12512            
12513             my $expected =
12514             [["a1" .. "a8"],
12515             ["b1" .. "b8"],
12516             ["c1" .. "c8"],
12517             ["d1" .. "d8"],
12518             ["e1" .. "e8"],
12519             ["f1" .. "f8"],
12520             ["g1" .. "g8"],
12521             ["h1" .. "h8"],
12522             ["i1" .. "i8"],
12523             ["j1" .. "j8"],
12524             ["k1" .. "k8"],
12525             ["l1" .. "l8"],
12526             ["m1" .. "m8"],
12527             ["n1" .. "n8"],
12528             ["o1" .. "o8"],
12529             ["p1" .. "p8"],
12530             ["q1" .. "q8"],
12531             ["r1" .. "r8"],
12532             ["s1" .. "s8"],
12533             ["t1" .. "t8"],
12534             ["u1" .. "u8"],
12535             ["v1" .. "v8"],
12536             ["w1" .. "w8"],
12537             ["x1" .. "x8"],
12538             ["y1" .. "y8"],
12539             ["z1" .. "z8"],
12540             ];
12541            
12542             is_deeply $expected,
12543             [setPartitionOnIntersectionOverUnionOfHashStringSets (0.50, \%s)];
12544            
12545             my $expectedInParallel =
12546             ["a1 a2 a3 a4 a5 a6 a7 a8", # Same strings in multiple parallel processes
12547             "b1 b2 b3 b4 b5 b6 b7 b8",
12548             "b1 b2 b3 b4 b5 b6 b7 b8",
12549             "c1 c2 c3 c4 c5 c6 c7 c8",
12550             "d1 d2 d3 d4 d5 d6 d7 d8",
12551             "d1 d2 d3 d4 d5 d6 d7 d8",
12552             "e1 e2 e3 e4 e5 e6 e7 e8",
12553             "f1 f2 f3 f4 f5 f6 f7 f8",
12554             "f1 f2 f3 f4 f5 f6 f7 f8",
12555             "g1 g2 g3 g4 g5 g6 g7 g8",
12556             "h1 h2 h3 h4 h5 h6 h7 h8",
12557             "h1 h2 h3 h4 h5 h6 h7 h8",
12558             "i1 i2 i3 i4 i5 i6 i7 i8",
12559             "j1 j2 j3 j4 j5 j6 j7 j8",
12560             "j1 j2 j3 j4 j5 j6 j7 j8",
12561             "k1 k2 k3 k4 k5 k6 k7 k8",
12562             "l1 l2 l3 l4 l5 l6 l7 l8",
12563             "l1 l2 l3 l4 l5 l6 l7 l8",
12564             "m1 m2 m3 m4 m5 m6 m7 m8",
12565             "n1 n2 n3 n4 n5 n6 n7 n8",
12566             "n1 n2 n3 n4 n5 n6 n7 n8",
12567             "o1 o2 o3 o4 o5 o6 o7 o8",
12568             "p1 p2 p3 p4 p5 p6 p7 p8",
12569             "q1 q2 q3 q4 q5 q6 q7 q8",
12570             "q1 q2 q3 q4 q5 q6 q7 q8",
12571             "r1 r2 r3 r4 r5 r6 r7 r8",
12572             "s1 s2 s3 s4 s5 s6 s7 s8",
12573             "s1 s2 s3 s4 s5 s6 s7 s8",
12574             "t1 t2 t3 t4 t5 t6 t7 t8",
12575             "u1 u2 u3 u4 u5 u6 u7 u8",
12576             "u1 u2 u3 u4 u5 u6 u7 u8",
12577             "v1 v2 v3 v4 v5 v6 v7 v8",
12578             "w1 w2 w3 w4 w5 w6 w7 w8",
12579             "w1 w2 w3 w4 w5 w6 w7 w8",
12580             "x1 x2 x3 x4 x5 x6 x7 x8",
12581             "y1 y2 y3 y4 y5 y6 y7 y8",
12582             "y1 y2 y3 y4 y5 y6 y7 y8",
12583             "z1 z2 z3 z4 z5 z6 z7 z8",
12584             ];
12585            
12586             if (1)
12587            
12588             {my @p = setPartitionOnIntersectionOverUnionOfHashStringSetsInParallel # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
12589              
12590             (0.50, \%s);
12591            
12592             is_deeply $expectedInParallel, [sort map {join ' ', @$_} @p];
12593             }
12594            
12595              
12596             =head2 contains($item, @array)
12597              
12598             Returns the indices at which an B<$item> matches elements of the specified B<@array>. If the item is a regular expression then it is matched as one, else it is a number it is matched as a number, else as a string.
12599              
12600             Parameter Description
12601             1 $item Item
12602             2 @array Array
12603              
12604             B
12605              
12606              
12607            
12608             is_deeply [1], [contains(1,0..1)]; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
12609              
12610            
12611            
12612             is_deeply [1,3], [contains(1, qw(0 1 0 1 0 0))]; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
12613              
12614            
12615            
12616             is_deeply [0, 5], [contains('a', qw(a b c d e a b c d e))]; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
12617              
12618            
12619            
12620             is_deeply [0, 1, 5], [contains(qr(a+), qw(a baa c d e aa b c d e))]; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
12621              
12622            
12623              
12624             =head2 countOccurencesInString($inString, $searchFor)
12625              
12626             Returns the number of occurrences in B<$inString> of B<$searchFor>.
12627              
12628             Parameter Description
12629             1 $inString String to search in
12630             2 $searchFor String to search for.
12631              
12632             B
12633              
12634              
12635             if (1)
12636            
12637             {ok countOccurencesInString(q(acd), q()) == 3; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
12638              
12639             }
12640            
12641              
12642             =head2 partitionStringsOnPrefixBySize()
12643              
12644             Partition a hash of strings and associated sizes into partitions with either a maximum size B<$maxSize> or only one element; the hash B<%Sizes> consisting of a mapping {string=>size}; with each partition being named with the shortest string prefix that identifies just the strings in that partition. Returns a list of {prefix => size}... describing each partition.
12645              
12646              
12647             B
12648              
12649              
12650             if (1)
12651            
12652             {my $ps = \&partitionStringsOnPrefixBySize; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
12653              
12654            
12655             is_deeply {&$ps(1)}, {};
12656             is_deeply {&$ps(1, 1=>0)}, {q()=>0};
12657             is_deeply {&$ps(1, 1=>1)}, {q()=>1};
12658             is_deeply {&$ps(1, 1=>2)}, {1=>2};
12659             is_deeply {&$ps(1, 1=>1,2=>1)}, {1=>1,2=>1};
12660             is_deeply {&$ps(2, 11=>1,12=>1, 21=>1,22=>1)}, {1=>2, 2=>2};
12661             is_deeply {&$ps(2, 111=>1,112=>1,113=>1, 121=>1,122=>1,123=>1, 131=>1,132=>1,133=>1)}, { 111 => 1, 112 => 1, 113 => 1, 121 => 1, 122 => 1, 123 => 1, 131 => 1, 132 => 1, 133 => 1 };
12662            
12663             for(3..8)
12664             {is_deeply {&$ps($_, 111=>1,112=>1,113=>1, 121=>1,122=>1,123=>1, 131=>1,132=>1,133=>1)}, { 11 => 3, 12 => 3, 13 => 3 };
12665             }
12666            
12667             is_deeply {&$ps(9, 111=>1,112=>1,113=>1, 121=>1,122=>1,123=>1, 131=>1,132=>1,133=>1)}, { q()=> 9};
12668             is_deeply {&$ps(3, 111=>1,112=>1,113=>1, 121=>1,122=>1,123=>1, 131=>1,132=>1,133=>2)}, { 11 => 3, 12 => 3, 131 => 1, 132 => 1, 133 => 2 };
12669             is_deeply {&$ps(4, 111=>1,112=>1,113=>1, 121=>1,122=>1,123=>1, 131=>1,132=>1,133=>2)}, { 11 => 3, 12 => 3, 13 => 4 };
12670            
12671             }
12672            
12673              
12674             =head2 transitiveClosure($h)
12675              
12676             Transitive closure of a hash of hashes
12677              
12678             Parameter Description
12679             1 $h Hash of hashes
12680              
12681             B
12682              
12683              
12684             if (1)
12685            
12686             {is_deeply transitiveClosure({a=>{b=>1, c=>2}, b=>{d=>3}, c=>{d=>4}}), # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
12687              
12688             {end => [{ b => 1, c => 1, d => 4 }, { d => 1 }],
12689             start => { a => 0, b => 1, c => 1 },
12690             };
12691             }
12692            
12693              
12694             =head1 Format
12695              
12696             Format data structures as tables.
12697              
12698             =head2 maximumLineLength($string)
12699              
12700             Find the longest line in a B<$string>.
12701              
12702             Parameter Description
12703             1 $string String of lines of text
12704              
12705             B
12706              
12707              
12708            
12709             ok 3 == maximumLineLength(<
12710              
12711             a
12712             bb
12713             ccc
12714             END
12715            
12716              
12717             =head2 formatTableBasic($data)
12718              
12719             Tabularize an array of arrays of text.
12720              
12721             Parameter Description
12722             1 $data Reference to an array of arrays of data to be formatted as a table.
12723              
12724             B
12725              
12726              
12727             my $d = [[qw(a 1)], [qw(bb 22)], [qw(ccc 333)], [qw(dddd 4444)]];
12728            
12729             ok formatTableBasic($d) eq <
12730              
12731             a 1
12732             bb 22
12733             ccc 333
12734             dddd 4444
12735             END
12736             }
12737            
12738             if (0) {
12739             my %pids;
12740             sub{startProcess {} %pids, 1; ok 1 >= keys %pids}->() for 1..8;
12741             waitForAllStartedProcessesToFinish(%pids);
12742             ok !keys(%pids)
12743            
12744              
12745             =head2 formatTable($data, $columnTitles, @options)
12746              
12747             Format various B<$data> structures as a table with titles as specified by B<$columnTitles>: either a reference to an array of column titles or a string each line of which contains the column title as the first word with the rest of the line describing that column.
12748              
12749             Optionally create a report from the table using the report B<%options> described in L
12750              
12751             Parameter Description
12752             1 $data Data to be formatted
12753             2 $columnTitles Optional reference to an array of titles or string of column descriptions
12754             3 @options Options
12755              
12756             B
12757              
12758              
12759            
12760             ok formatTable # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
12761              
12762            
12763             ([[qw(A B C D )],
12764            
12765             [qw(AA BB CC DD )],
12766            
12767             [qw(AAA BBB CCC DDD )],
12768            
12769             [qw(AAAA BBBB CCCC DDDD)],
12770            
12771             [qw(1 22 333 4444)]], [qw(aa bb cc)]) eq <
12772             aa bb cc
12773             1 A B C D
12774             2 AA BB CC DD
12775             3 AAA BBB CCC DDD
12776             4 AAAA BBBB CCCC DDDD
12777             5 1 22 333 4444
12778             END
12779            
12780            
12781             ok formatTable # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
12782              
12783            
12784             ([[qw(1 B C)],
12785            
12786             [qw(22 BB CC)],
12787            
12788             [qw(333 BBB CCC)],
12789            
12790             [qw(4444 22 333)]], [qw(aa bb cc)]) eq <
12791             aa bb cc
12792             1 1 B C
12793             2 22 BB CC
12794             3 333 BBB CCC
12795             4 4444 22 333
12796             END
12797            
12798            
12799             ok formatTable # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
12800              
12801            
12802             ([{aa=>'A', bb=>'B', cc=>'C'},
12803            
12804             {aa=>'AA', bb=>'BB', cc=>'CC'},
12805            
12806             {aa=>'AAA', bb=>'BBB', cc=>'CCC'},
12807            
12808             {aa=>'1', bb=>'22', cc=>'333'}
12809            
12810             ]) eq <
12811             aa bb cc
12812             1 A B C
12813             2 AA BB CC
12814             3 AAA BBB CCC
12815             4 1 22 333
12816             END
12817            
12818            
12819             ok formatTable # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
12820              
12821            
12822             ({''=>[qw(aa bb cc)],
12823            
12824             1=>[qw(A B C)],
12825            
12826             22=>[qw(AA BB CC)],
12827            
12828             333=>[qw(AAA BBB CCC)],
12829            
12830             4444=>[qw(1 22 333)]}) eq <
12831             aa bb cc
12832             1 A B C
12833             22 AA BB CC
12834             333 AAA BBB CCC
12835             4444 1 22 333
12836             END
12837            
12838            
12839             ok formatTable # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
12840              
12841            
12842             ({1=>{aa=>'A', bb=>'B', cc=>'C'},
12843            
12844             22=>{aa=>'AA', bb=>'BB', cc=>'CC'},
12845            
12846             333=>{aa=>'AAA', bb=>'BBB', cc=>'CCC'},
12847            
12848             4444=>{aa=>'1', bb=>'22', cc=>'333'}}) eq <
12849             aa bb cc
12850             1 A B C
12851             22 AA BB CC
12852             333 AAA BBB CCC
12853             4444 1 22 333
12854             END
12855            
12856            
12857             ok formatTable({aa=>'A', bb=>'B', cc=>'C'}, [qw(aaaa bbbb)]) eq <
12858              
12859             aaaa bbbb
12860             aa A
12861             bb B
12862             cc C
12863             END
12864            
12865             my $d = temporaryFolder;
12866             my $f = fpe($d, qw(report txt)); # Create a report
12867            
12868             my $t = formatTable # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
12869              
12870             ([["a",undef], [undef, "b0ac"]], # Data - please replace 0a with a new line
12871             [undef, "BC"], # Column titles
12872             file=>$f, # Output file
12873             head=><
12874             Sample report.
12875            
12876             Table has NNNN rows.
12877             END
12878             ok -e $f;
12879            
12880             ok readFile($f) eq $t;
12881             is_deeply nws($t), nws(<
12882             Sample report.
12883            
12884             Table has 2 rows.
12885            
12886             This file: ${d}report.txt
12887            
12888             BC
12889             1 a
12890             2 b
12891             c
12892             END
12893             clearFolder($d, 2);
12894            
12895              
12896             =head2 formattedTablesReport(@options)
12897              
12898             Report of all the reports created. The optional parameters are the same as for L
12899              
12900             Parameter Description
12901             1 @options Options
12902              
12903             B
12904              
12905              
12906             @formatTables = ();
12907            
12908             for my $m(2..8)
12909             {formatTable([map {[$_, $_*$m]} 1..$m], [q(Single), qq(* $m)],
12910             title=>qq(Multiply by $m));
12911             }
12912            
12913            
12914             ok nws(formattedTablesReport) eq nws(<
12915              
12916             Rows Title File
12917             1 2 Multiply by 2
12918             2 3 Multiply by 3
12919             3 4 Multiply by 4
12920             4 5 Multiply by 5
12921             5 6 Multiply by 6
12922             6 7 Multiply by 7
12923             7 8 Multiply by 8
12924             END
12925            
12926              
12927             =head2 summarizeColumn($data, $column)
12928              
12929             Count the number of unique instances of each value a column in a table assumes.
12930              
12931             Parameter Description
12932             1 $data Table == array of arrays
12933             2 $column Column number to summarize.
12934              
12935             B
12936              
12937              
12938             is_deeply
12939            
12940             [summarizeColumn([map {[$_]} qw(A B D B C D C D A C D C B B D)], 0)], # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
12941              
12942             [[5, "D"], [4, "B"], [4, "C"], [2, "A"]];
12943            
12944             ok nws(formatTable
12945             ([map {[split m//, $_]} qw(AA CB CD BC DC DD CD AD AA DC CD CC BB BB BD)],
12946             [qw(Col-1 Col-2)],
12947             summarize=>1)) eq nws(<<'END');
12948            
12949             Summary_of_column - Count of unique values found in each column Use the Geany flick capability by placing your cursor on the first word
12950             Comma_Separated_Values_of_column - Comma separated list of the unique values found in each column of these lines and pressing control + down arrow to see each sub report.
12951            
12952             Col-1 Col-2
12953             1 A A
12954             2 C B
12955             3 C D
12956             4 B C
12957             5 D C
12958             6 D D
12959             7 C D
12960             8 A D
12961             9 A A
12962             10 D C
12963             11 C D
12964             12 C C
12965             13 B B
12966             14 B B
12967             15 B D
12968            
12969             Summary_of_column_Col-1
12970             Count Col-1
12971             1 5 C
12972             2 4 B
12973             3 3 A
12974             4 3 D
12975            
12976             Comma_Separated_Values_of_column_Col-1: "A","B","C","D"
12977            
12978             Summary_of_column_Col-2
12979             Count Col-2
12980             1 6 D
12981             2 4 C
12982             3 3 B
12983             4 2 A
12984            
12985             Comma_Separated_Values_of_column_Col-2: "A","B","C","D"
12986             END
12987            
12988              
12989             =head2 keyCount($maxDepth, $ref)
12990              
12991             Count keys down to the specified level.
12992              
12993             Parameter Description
12994             1 $maxDepth Maximum depth to count to
12995             2 $ref Reference to an array or a hash
12996              
12997             B
12998              
12999              
13000             my $a = [[1..3], {map{$_=>1} 1..3}];
13001            
13002             my $h = {a=>[1..3], b=>{map{$_=>1} 1..3}};
13003            
13004            
13005             ok keyCount(2, $a) == 6; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
13006              
13007            
13008            
13009             ok keyCount(2, $h) == 6; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
13010              
13011            
13012              
13013             =head2 formatHtmlTable($data, %options)
13014              
13015             Format an array of arrays of scalars as an html table using the B<%options> described in L.
13016              
13017             Parameter Description
13018             1 $data Data to be formatted
13019             2 %options Options
13020              
13021             B
13022              
13023              
13024             if (1)
13025            
13026             {my $t = formatHtmlTable # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
13027              
13028             ([
13029             [qw(1 a)],
13030             [qw(2 b)],
13031             ],
13032             title => q(Sample html table),
13033             head => q(Head NNNN rows),
13034             foot => q(Footer),
13035             columns=> <
13036             source The source number
13037             target The target letter
13038             END
13039             );
13040            
13041             my $T = <<'END';
13042            

Sample html table

13043            
13044            

Head 2 rows

13045            
13046            

13047            
13048            
sourcetarget
13049            
1a
13050            
2b
13051            

13052            
13053            

 
13054             source The source number
13055             target The target letter
13056            
13057            

13058            
13059            

Footer

13060            
13061            
13062             columns => "source The source number
13063             target The target letter
13064             ",
13065             foot => "Footer",
13066             head => "Head NNNN rows",
13067             rows => 2,
13068             title => "Sample html table",
13069             }
13070             END
13071            
13072             ok "$t
13073             " eq $T;
13074             }
13075            
13076              
13077             =head2 formatHtmlTablesIndex($reports, $title, $url, $columns)
13078              
13079             Create an index of html reports.
13080              
13081             Parameter Description
13082             1 $reports Reports folder
13083             2 $title Title of report of reports
13084             3 $url $url to get files
13085             4 $columns Number of columns - defaults to 1
13086              
13087             B
13088              
13089              
13090             if (1)
13091             {my $reports = temporaryFolder;
13092            
13093             formatHtmlAndTextTables
13094             ($reports, $reports, q(/cgi-bin/getFile.pl?), q(/a/),
13095             [[qw(1 /a/a)],
13096             [qw(2 /a/b)],
13097             ],
13098             title => q(Bad files),
13099             head => q(Head NNNN rows),
13100             foot => q(Footer),
13101             file => q(bad.html),
13102             facet => q(files), aspectColor => "red",
13103             columns => <
13104             source The source number
13105             target The target letter
13106             END
13107             );
13108            
13109             formatHtmlAndTextTables
13110             ($reports, $reports, q(/cgi-bin/getFile.pl?file=), q(/a/),
13111             [[qw(1 /a/a1)],
13112             [qw(2 /a/b2)],
13113             [qw(3 /a/b3)],
13114             ],
13115             title => q(Good files),
13116             head => q(Head NNNN rows),
13117             foot => q(Footer),
13118             file => q(good.html),
13119             facet => q(files), aspectColor => "green",
13120             columns => <
13121             source The source number
13122             target The target letter
13123             END
13124             );
13125            
13126             formatHtmlAndTextTablesWaitPids;
13127            
13128            
13129             my $result = formatHtmlTablesIndex($reports, q(TITLE), q(/cgi-bin/getFile.pl?file=)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
13130              
13131             ok $result =~ m(3.*Good files);
13132             ok $result =~ m(2.*Bad files);
13133             # ok $result =~ m(green.*>3<.*>Good files);
13134             # ok $result =~ m(red.*>2<.*>Bad files);
13135            
13136             clearFolder($reports, 11);
13137             }
13138            
13139              
13140             =head2 formatHtmlAndTextTablesWaitPids()
13141              
13142             Wait on all table formatting pids to complete
13143              
13144              
13145             B
13146              
13147              
13148             if (1)
13149             {my $reports = temporaryFolder;
13150            
13151             formatHtmlAndTextTables
13152             ($reports, $reports, q(/cgi-bin/getFile.pl?), q(/a/),
13153             [[qw(1 /a/a)],
13154             [qw(2 /a/b)],
13155             ],
13156             title => q(Bad files),
13157             head => q(Head NNNN rows),
13158             foot => q(Footer),
13159             file => q(bad.html),
13160             facet => q(files), aspectColor => "red",
13161             columns => <
13162             source The source number
13163             target The target letter
13164             END
13165             );
13166            
13167             formatHtmlAndTextTables
13168             ($reports, $reports, q(/cgi-bin/getFile.pl?file=), q(/a/),
13169             [[qw(1 /a/a1)],
13170             [qw(2 /a/b2)],
13171             [qw(3 /a/b3)],
13172             ],
13173             title => q(Good files),
13174             head => q(Head NNNN rows),
13175             foot => q(Footer),
13176             file => q(good.html),
13177             facet => q(files), aspectColor => "green",
13178             columns => <
13179             source The source number
13180             target The target letter
13181             END
13182             );
13183            
13184            
13185             formatHtmlAndTextTablesWaitPids; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
13186              
13187            
13188             my $result = formatHtmlTablesIndex($reports, q(TITLE), q(/cgi-bin/getFile.pl?file=));
13189             ok $result =~ m(3.*Good files);
13190             ok $result =~ m(2.*Bad files);
13191             # ok $result =~ m(green.*>3<.*>Good files);
13192             # ok $result =~ m(red.*>2<.*>Bad files);
13193            
13194             clearFolder($reports, 11);
13195             }
13196            
13197              
13198             =head2 formatHtmlAndTextTables($reports, $html, $getFile, $filePrefix, $data, %options)
13199              
13200             Create text and html versions of a tabular report
13201              
13202             Parameter Description
13203             1 $reports Folder to contain text reports
13204             2 $html Folder to contain html reports
13205             3 $getFile L to get files
13206             4 $filePrefix File prefix to be removed from file entries or array of file prefixes
13207             5 $data Data
13208             6 %options Options
13209              
13210             B
13211              
13212              
13213             if (1)
13214             {my $reports = temporaryFolder;
13215            
13216            
13217             formatHtmlAndTextTables # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
13218              
13219             ($reports, $reports, q(/cgi-bin/getFile.pl?), q(/a/),
13220             [[qw(1 /a/a)],
13221             [qw(2 /a/b)],
13222             ],
13223             title => q(Bad files),
13224             head => q(Head NNNN rows),
13225             foot => q(Footer),
13226             file => q(bad.html),
13227             facet => q(files), aspectColor => "red",
13228             columns => <
13229             source The source number
13230             target The target letter
13231             END
13232             );
13233            
13234            
13235             formatHtmlAndTextTables # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
13236              
13237             ($reports, $reports, q(/cgi-bin/getFile.pl?file=), q(/a/),
13238             [[qw(1 /a/a1)],
13239             [qw(2 /a/b2)],
13240             [qw(3 /a/b3)],
13241             ],
13242             title => q(Good files),
13243             head => q(Head NNNN rows),
13244             foot => q(Footer),
13245             file => q(good.html),
13246             facet => q(files), aspectColor => "green",
13247             columns => <
13248             source The source number
13249             target The target letter
13250             END
13251             );
13252            
13253             formatHtmlAndTextTablesWaitPids;
13254            
13255             my $result = formatHtmlTablesIndex($reports, q(TITLE), q(/cgi-bin/getFile.pl?file=));
13256             ok $result =~ m(3.*Good files);
13257             ok $result =~ m(2.*Bad files);
13258             # ok $result =~ m(green.*>3<.*>Good files);
13259             # ok $result =~ m(red.*>2<.*>Bad files);
13260            
13261             clearFolder($reports, 11);
13262             }
13263            
13264              
13265             =head1 Lines
13266              
13267             Load data structures from lines.
13268              
13269             =head2 loadArrayFromLines($string)
13270              
13271             Load an array from lines of text in a string.
13272              
13273             Parameter Description
13274             1 $string The string of lines from which to create an array
13275              
13276             B
13277              
13278              
13279            
13280             my $s = loadArrayFromLines <
13281              
13282             a a
13283             b b
13284             END
13285            
13286             is_deeply $s, [q(a a), q(b b)];
13287            
13288             ok formatTable($s) eq <
13289             0 a a
13290             1 b b
13291             END
13292            
13293              
13294             =head2 loadHashFromLines($string)
13295              
13296             Load a hash: first word of each line is the key and the rest is the value.
13297              
13298             Parameter Description
13299             1 $string The string of lines from which to create a hash
13300              
13301             B
13302              
13303              
13304            
13305             my $s = loadHashFromLines <
13306              
13307             a 10 11 12
13308             b 20 21 22
13309             END
13310            
13311             is_deeply $s, {a => q(10 11 12), b =>q(20 21 22)};
13312            
13313             ok formatTable($s) eq <
13314             a 10 11 12
13315             b 20 21 22
13316             END
13317            
13318              
13319             =head2 loadArrayArrayFromLines($string)
13320              
13321             Load an array of arrays from lines of text: each line is an array of words.
13322              
13323             Parameter Description
13324             1 $string The string of lines from which to create an array of arrays
13325              
13326             B
13327              
13328              
13329            
13330             my $s = loadArrayArrayFromLines <
13331              
13332             A B C
13333             AA BB CC
13334             END
13335            
13336             is_deeply $s, [[qw(A B C)], [qw(AA BB CC)]];
13337            
13338             ok formatTable($s) eq <
13339             1 A B C
13340             2 AA BB CC
13341             END
13342            
13343              
13344             =head2 loadHashArrayFromLines($string)
13345              
13346             Load a hash of arrays from lines of text: the first word of each line is the key, the remaining words are the array contents.
13347              
13348             Parameter Description
13349             1 $string The string of lines from which to create a hash of arrays
13350              
13351             B
13352              
13353              
13354            
13355             my $s = loadHashArrayFromLines <
13356              
13357             a A B C
13358             b AA BB CC
13359             END
13360            
13361             is_deeply $s, {a =>[qw(A B C)], b => [qw(AA BB CC)] };
13362            
13363             ok formatTable($s) eq <
13364             a A B C
13365             b AA BB CC
13366             END
13367            
13368              
13369             =head2 loadArrayHashFromLines($string)
13370              
13371             Load an array of hashes from lines of text: each line is a hash of words.
13372              
13373             Parameter Description
13374             1 $string The string of lines from which to create an array of arrays
13375              
13376             B
13377              
13378              
13379            
13380             my $s = loadArrayHashFromLines <
13381              
13382             A 1 B 2
13383             AA 11 BB 22
13384             END
13385            
13386             is_deeply $s, [{A=>1, B=>2}, {AA=>11, BB=>22}];
13387            
13388             ok formatTable($s) eq <
13389             A AA B BB
13390             1 1 2
13391             2 11 22
13392             END
13393            
13394              
13395             =head2 loadHashHashFromLines($string)
13396              
13397             Load a hash of hashes from lines of text: the first word of each line is the key, the remaining words are the sub hash contents.
13398              
13399             Parameter Description
13400             1 $string The string of lines from which to create a hash of arrays
13401              
13402             B
13403              
13404              
13405            
13406             my $s = loadHashHashFromLines <
13407              
13408             a A 1 B 2
13409             b AA 11 BB 22
13410             END
13411            
13412             is_deeply $s, {a=>{A=>1, B=>2}, b=>{AA=>11, BB=>22}};
13413            
13414             ok formatTable($s) eq <
13415             A AA B BB
13416             a 1 2
13417             b 11 22
13418             END
13419            
13420              
13421             =head2 checkKeys($hash, $permitted)
13422              
13423             Check the keys in a B confirm to those B<$permitted>.
13424              
13425             Parameter Description
13426             1 $hash The hash to test
13427             2 $permitted A hash of the permitted keys and their meanings
13428              
13429             B
13430              
13431              
13432            
13433             eval q{checkKeys({a=>1, b=>2, d=>3}, {a=>1, b=>2, c=>3})}; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
13434              
13435            
13436             ok nws($@) =~ m(\AInvalid options chosen: d Permitted.+?: a 1 b 2 c 3);
13437            
13438              
13439             =head1 LVALUE methods
13440              
13441             Replace $a->{B} = $b with $a->B = $b which reduces the amount of typing required, is easier to read and provides a hard check that {B} is spelled correctly.
13442              
13443             =head2 genLValueScalarMethods(@names)
13444              
13445             Generate L scalar methods in the current package, A method whose value has not yet been set will return a new scalar with value B. Suffixing B to the scalar name will confess if a value has not been set.
13446              
13447             Parameter Description
13448             1 @names List of method names
13449              
13450             B
13451              
13452              
13453             package Scalars;
13454            
13455             my $a = bless{};
13456            
13457            
13458             Data::Table::Text::genLValueScalarMethods(qw(aa bb cc)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
13459              
13460            
13461             $a->aa = 'aa';
13462            
13463             Test::More::ok $a->aa eq 'aa';
13464            
13465             Test::More::ok !$a->bb;
13466            
13467             Test::More::ok $a->bbX eq q();
13468            
13469             $a->aa = undef;
13470            
13471             Test::More::ok !$a->aa;
13472            
13473              
13474             =head2 addLValueScalarMethods(@names)
13475              
13476             Generate L scalar methods in the current package if they do not already exist. A method whose value has not yet been set will return a new scalar with value B. Suffixing B to the scalar name will confess if a value has not been set.
13477              
13478             Parameter Description
13479             1 @names List of method names
13480              
13481             B
13482              
13483              
13484             my $class = "Data::Table::Text::Test";
13485            
13486             my $a = bless{}, $class;
13487            
13488            
13489             addLValueScalarMethods(qq(${class}::$_)) for qw(aa bb aa bb); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
13490              
13491            
13492             $a->aa = 'aa';
13493            
13494             ok $a->aa eq 'aa';
13495            
13496             ok !$a->bb;
13497            
13498             ok $a->bbX eq q();
13499            
13500             $a->aa = undef;
13501            
13502             ok !$a->aa;
13503            
13504              
13505             =head2 genLValueScalarMethodsWithDefaultValues(@names)
13506              
13507             Generate L scalar methods with default values in the current package. A reference to a method whose value has not yet been set will return a scalar whose value is the name of the method.
13508              
13509             Parameter Description
13510             1 @names List of method names
13511              
13512             B
13513              
13514              
13515             package ScalarsWithDefaults;
13516            
13517             my $a = bless{};
13518            
13519            
13520             Data::Table::Text::genLValueScalarMethodsWithDefaultValues(qw(aa bb cc)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
13521              
13522            
13523             Test::More::ok $a->aa eq 'aa';
13524            
13525              
13526             =head2 genLValueArrayMethods(@names)
13527              
13528             Generate L array methods in the current package. A reference to a method that has no yet been set will return a reference to an empty array.
13529              
13530             Parameter Description
13531             1 @names List of method names
13532              
13533             B
13534              
13535              
13536             package Arrays;
13537            
13538             my $a = bless{};
13539            
13540            
13541             Data::Table::Text::genLValueArrayMethods(qw(aa bb cc)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
13542              
13543            
13544             $a->aa->[1] = 'aa';
13545            
13546             Test::More::ok $a->aa->[1] eq 'aa';
13547            
13548              
13549             =head2 genLValueHashMethods(@names)
13550              
13551             Generate L hash methods in the current package. A reference to a method that has no yet been set will return a reference to an empty hash.
13552              
13553             Parameter Description
13554             1 @names Method names
13555              
13556             B
13557              
13558              
13559             package Hashes;
13560            
13561             my $a = bless{};
13562            
13563            
13564             Data::Table::Text::genLValueHashMethods(qw(aa bb cc)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
13565              
13566            
13567             $a->aa->{a} = 'aa';
13568            
13569             Test::More::ok $a->aa->{a} eq 'aa';
13570            
13571              
13572             =head2 genHash($bless, %attributes)
13573              
13574             Return a B<$bless>ed hash with the specified B<$attributes> accessible via L method calls. L will generate documentation at L for the hash defined by the call to L if the call is laid out as in the example below.
13575              
13576             Parameter Description
13577             1 $bless Package name
13578             2 %attributes Hash of attribute names and values
13579              
13580             B
13581              
13582              
13583            
13584             my $o = genHash(q(TestHash), # Definition of a blessed hash. # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
13585              
13586             a=>q(aa), # Definition of attribute aa.
13587             b=>q(bb), # Definition of attribute bb.
13588             );
13589             ok $o->a eq q(aa);
13590             is_deeply $o, {a=>"aa", b=>"bb"};
13591            
13592             my $p = genHash(q(TestHash), # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
13593              
13594             c=>q(cc), # Definition of attribute cc.
13595             );
13596             ok $p->c eq q(cc);
13597             ok $p->a = q(aa);
13598             ok $p->a eq q(aa);
13599             is_deeply $p, {a=>"aa", c=>"cc"};
13600            
13601             loadHash($p, a=>11, b=>22); # Load the hash
13602             is_deeply $p, {a=>11, b=>22, c=>"cc"};
13603            
13604             my $r = eval {loadHash($p, d=>44)}; # Try to load the hash
13605             ok $@ =~ m(Cannot load attribute: d);
13606            
13607              
13608             =head2 loadHash($hash, %attributes)
13609              
13610             Load the specified blessed B<$hash> generated with L with B<%attributes>. Confess to any unknown attribute names.
13611              
13612             Parameter Description
13613             1 $hash Hash
13614             2 %attributes Hash of attribute names and values to be loaded
13615              
13616             B
13617              
13618              
13619             my $o = genHash(q(TestHash), # Definition of a blessed hash.
13620             a=>q(aa), # Definition of attribute aa.
13621             b=>q(bb), # Definition of attribute bb.
13622             );
13623             ok $o->a eq q(aa);
13624             is_deeply $o, {a=>"aa", b=>"bb"};
13625             my $p = genHash(q(TestHash),
13626             c=>q(cc), # Definition of attribute cc.
13627             );
13628             ok $p->c eq q(cc);
13629             ok $p->a = q(aa);
13630             ok $p->a eq q(aa);
13631             is_deeply $p, {a=>"aa", c=>"cc"};
13632            
13633            
13634             loadHash($p, a=>11, b=>22); # Load the hash # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
13635              
13636             is_deeply $p, {a=>11, b=>22, c=>"cc"};
13637            
13638            
13639             my $r = eval {loadHash($p, d=>44)}; # Try to load the hash # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
13640              
13641             ok $@ =~ m(Cannot load attribute: d);
13642            
13643              
13644             =head2 reloadHashes($d)
13645              
13646             Ensures that all the hashes within a tower of data structures have LValue methods to get and set their current keys.
13647              
13648             Parameter Description
13649             1 $d Data structure
13650              
13651             B
13652              
13653              
13654             if (1)
13655             {my $a = bless [bless {aaa=>42}, "AAAA"], "BBBB";
13656             eval {$a->[0]->aaa};
13657             ok $@ =~ m(\ACan.t locate object method .aaa. via package .AAAA.);
13658            
13659             reloadHashes($a); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
13660              
13661             ok $a->[0]->aaa == 42;
13662             }
13663            
13664             if (1)
13665             {my $a = bless [bless {ccc=>42}, "CCCC"], "DDDD";
13666             eval {$a->[0]->ccc};
13667             ok $@ =~ m(\ACan.t locate object method .ccc. via package .CCCC.);
13668            
13669             reloadHashes($a); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
13670              
13671             ok $a->[0]->ccc == 42;
13672             }
13673            
13674              
13675             =head2 setPackageSearchOrder($set, @search)
13676              
13677             Set a package search order for methods requested in the current package via AUTOLOAD.
13678              
13679             Parameter Description
13680             1 $set Package to set
13681             2 @search Package names in search order.
13682              
13683             B
13684              
13685              
13686             if (1)
13687             {if (1)
13688             {package AAAA;
13689            
13690             sub aaaa{q(AAAAaaaa)}
13691             sub bbbb{q(AAAAbbbb)}
13692             sub cccc{q(AAAAcccc)}
13693             }
13694             if (1)
13695             {package BBBB;
13696            
13697             sub aaaa{q(BBBBaaaa)}
13698             sub bbbb{q(BBBBbbbb)}
13699             sub dddd{q(BBBBdddd)}
13700             }
13701             if (1)
13702             {package CCCC;
13703            
13704             sub aaaa{q(CCCCaaaa)}
13705             sub dddd{q(CCCCdddd)}
13706             sub eeee{q(CCCCeeee)}
13707             }
13708            
13709            
13710             setPackageSearchOrder(__PACKAGE__, qw(CCCC BBBB AAAA)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
13711              
13712            
13713             ok &aaaa eq q(CCCCaaaa);
13714             ok &bbbb eq q(BBBBbbbb);
13715             ok &cccc eq q(AAAAcccc);
13716            
13717             ok &aaaa eq q(CCCCaaaa);
13718             ok &bbbb eq q(BBBBbbbb);
13719             ok &cccc eq q(AAAAcccc);
13720            
13721             ok &dddd eq q(CCCCdddd);
13722             ok &eeee eq q(CCCCeeee);
13723            
13724            
13725             setPackageSearchOrder(__PACKAGE__, qw(AAAA BBBB CCCC)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
13726              
13727            
13728             ok &aaaa eq q(AAAAaaaa);
13729             ok &bbbb eq q(AAAAbbbb);
13730             ok &cccc eq q(AAAAcccc);
13731            
13732             ok &aaaa eq q(AAAAaaaa);
13733             ok &bbbb eq q(AAAAbbbb);
13734             ok &cccc eq q(AAAAcccc);
13735            
13736             ok &dddd eq q(BBBBdddd);
13737             ok &eeee eq q(CCCCeeee);
13738             }
13739            
13740              
13741             =head2 isSubInPackage($package, $sub)
13742              
13743             Test whether the specified B<$package> contains the subroutine <$sub>.
13744              
13745             Parameter Description
13746             1 $package Package name
13747             2 $sub Subroutine name
13748              
13749             B
13750              
13751              
13752             if (1)
13753             {sub AAAA::Call {q(AAAA)}
13754            
13755             sub BBBB::Call {q(BBBB)}
13756             sub BBBB::call {q(bbbb)}
13757            
13758             if (1)
13759             {package BBBB;
13760             use Test::More;
13761             *ok = *Test::More::ok;
13762            
13763             *isSubInPackage = *Data::Table::Text::isSubInPackage; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
13764              
13765            
13766             ok isSubInPackage(q(AAAA), q(Call)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
13767              
13768            
13769             ok !isSubInPackage(q(AAAA), q(call)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
13770              
13771            
13772             ok isSubInPackage(q(BBBB), q(Call)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
13773              
13774            
13775             ok isSubInPackage(q(BBBB), q(call)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
13776              
13777             ok Call eq q(BBBB);
13778             ok call eq q(bbbb);
13779             &Data::Table::Text::overrideMethods(qw(AAAA BBBB Call call));
13780            
13781             *isSubInPackage = *Data::Table::Text::isSubInPackage; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
13782              
13783            
13784             ok isSubInPackage(q(AAAA), q(Call)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
13785              
13786            
13787             ok isSubInPackage(q(AAAA), q(call)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
13788              
13789            
13790             ok isSubInPackage(q(BBBB), q(Call)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
13791              
13792            
13793             ok isSubInPackage(q(BBBB), q(call)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
13794              
13795             ok Call eq q(AAAA);
13796             ok call eq q(bbbb);
13797             package AAAA;
13798             use Test::More;
13799             *ok = *Test::More::ok;
13800             ok Call eq q(AAAA);
13801             ok &call eq q(bbbb);
13802             }
13803             }
13804            
13805              
13806             =head2 overrideMethods($from, $to, @methods)
13807              
13808             For each method, if it exists in package B<$from> then export it to package B<$to> replacing any existing method in B<$to>, otherwise export the method from package B<$to> to package B<$from> in order to merge the behavior of the B<$from> and B<$to> packages with respect to the named methods with duplicates resolved if favour of package B<$from>.
13809              
13810             Parameter Description
13811             1 $from Name of package from which to import methods
13812             2 $to Package into which to import the methods
13813             3 @methods List of methods to try importing.
13814              
13815             B
13816              
13817              
13818             if (1)
13819             {sub AAAA::Call {q(AAAA)}
13820            
13821             sub BBBB::Call {q(BBBB)}
13822             sub BBBB::call {q(bbbb)}
13823            
13824             if (1)
13825             {package BBBB;
13826             use Test::More;
13827             *ok = *Test::More::ok;
13828             *isSubInPackage = *Data::Table::Text::isSubInPackage;
13829             ok isSubInPackage(q(AAAA), q(Call));
13830             ok !isSubInPackage(q(AAAA), q(call));
13831             ok isSubInPackage(q(BBBB), q(Call));
13832             ok isSubInPackage(q(BBBB), q(call));
13833             ok Call eq q(BBBB);
13834             ok call eq q(bbbb);
13835            
13836             &Data::Table::Text::overrideMethods(qw(AAAA BBBB Call call)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
13837              
13838             *isSubInPackage = *Data::Table::Text::isSubInPackage;
13839             ok isSubInPackage(q(AAAA), q(Call));
13840             ok isSubInPackage(q(AAAA), q(call));
13841             ok isSubInPackage(q(BBBB), q(Call));
13842             ok isSubInPackage(q(BBBB), q(call));
13843             ok Call eq q(AAAA);
13844             ok call eq q(bbbb);
13845             package AAAA;
13846             use Test::More;
13847             *ok = *Test::More::ok;
13848             ok Call eq q(AAAA);
13849             ok &call eq q(bbbb);
13850             }
13851             }
13852            
13853              
13854             This is a static method and so should either be imported or invoked as:
13855              
13856             Data::Table::Text::overrideMethods
13857              
13858              
13859             =head2 overrideAndReabsorbMethods(@packages)
13860              
13861             Override methods down the list of B<@packages> then reabsorb any unused methods back up the list of packages so that all the packages have the same methods as the last package with methods from packages mentioned earlier overriding methods from packages mentioned later. The methods to override and reabsorb are listed by the sub B in the last package in the packages list. Confess to any errors.
13862              
13863             Parameter Description
13864             1 @packages List of packages
13865              
13866             B
13867              
13868              
13869            
13870             ok overrideAndReabsorbMethods(qw(main Edit::Xml Data::Edit::Xml)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
13871              
13872            
13873              
13874             This is a static method and so should either be imported or invoked as:
13875              
13876             Data::Table::Text::overrideAndReabsorbMethods
13877              
13878              
13879             =head2 assertPackageRefs($package, @refs)
13880              
13881             Confirm that the specified references are to the specified package
13882              
13883             Parameter Description
13884             1 $package Package
13885             2 @refs References
13886              
13887             B
13888              
13889              
13890            
13891             eval q{assertPackageRefs(q(bbb), bless {}, q(aaa))}; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
13892              
13893             ok $@ =~ m(\AWanted reference to bbb, but got aaa);
13894            
13895              
13896             =head2 assertRef(@refs)
13897              
13898             Confirm that the specified references are to the package into which this routine has been exported.
13899              
13900             Parameter Description
13901             1 @refs References
13902              
13903             B
13904              
13905              
13906            
13907             eval q{assertRef(bless {}, q(aaa))}; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
13908              
13909             ok $@ =~ m(\AWanted reference to Data::Table::Text, but got aaa);
13910            
13911              
13912             =head2 arrayToHash(@array)
13913              
13914             Create a hash reference from an array
13915              
13916             Parameter Description
13917             1 @array Array
13918              
13919             B
13920              
13921              
13922            
13923             is_deeply arrayToHash(qw(a b c)), {a=>1, b=>1, c=>1}; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
13924              
13925            
13926              
13927             =head2 flattenArrayAndHashValues(@array)
13928              
13929             Flatten an array of scalars, array and hash references to make an array of scalars by flattening the array references and hash values.
13930              
13931             Parameter Description
13932             1 @array Array to flatten
13933              
13934             B
13935              
13936              
13937            
13938             is_deeply [1..5], [flattenArrayAndHashValues([1], [[2]], {a=>3, b=>[4, [5]]})], 'ggg'; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
13939              
13940            
13941              
13942             =head2 getSubName($sub)
13943              
13944             Returns the (package, name, file, line) of a perl B<$sub> reference.
13945              
13946             Parameter Description
13947             1 $sub Reference to a sub with a name.
13948              
13949             B
13950              
13951              
13952            
13953             is_deeply [(getSubName(\&dateTime))[0,1]], ["Data::Table::Text", "dateTime"]; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
13954              
13955            
13956              
13957             =head1 Strings
13958              
13959             Actions on strings.
13960              
13961             =head2 stringMd5Sum($string)
13962              
13963             Get the Md5 sum of a B<$string> that might contain L code points.
13964              
13965             Parameter Description
13966             1 $string String
13967              
13968             B
13969              
13970              
13971             my $s = join '', 1..100;
13972             my $m = q(ef69caaaeea9c17120821a9eb6c7f1de);
13973            
13974            
13975             ok stringMd5Sum($s) eq $m; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
13976              
13977            
13978             my $f = writeFile(undef, $s);
13979             ok fileMd5Sum($f) eq $m;
13980             unlink $f;
13981            
13982             ok guidFromString(join '', 1..100) eq
13983             q(GUID-ef69caaa-eea9-c171-2082-1a9eb6c7f1de);
13984            
13985            
13986             ok guidFromMd5(stringMd5Sum(join('', 1..100))) eq # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
13987              
13988             q(GUID-ef69caaa-eea9-c171-2082-1a9eb6c7f1de);
13989            
13990             ok md5FromGuid(q(GUID-ef69caaa-eea9-c171-2082-1a9eb6c7f1de)) eq
13991             q(ef69caaaeea9c17120821a9eb6c7f1de);
13992            
13993            
13994             ok stringMd5Sum(q(𝝰 𝝱 𝝲)) eq q(3c2b7c31b1011998bd7e1f66fb7c024d); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
13995              
13996             }
13997            
13998             if (1)
13999             {ok arraySum (1..10) == 55;
14000             ok arrayProduct(1..5) == 120;
14001             is_deeply[arrayTimes(2, 1..5)], [qw(2 4 6 8 10)];
14002            
14003              
14004             =head2 indentString($string, $indent)
14005              
14006             Indent lines contained in a string or formatted table by the specified string.
14007              
14008             Parameter Description
14009             1 $string The string of lines to indent
14010             2 $indent The indenting string
14011              
14012             B
14013              
14014              
14015             my $t = [qw(aa bb cc)];
14016             my $d = [[qw(A B C)], [qw(AA BB CC)], [qw(AAA BBB CCC)], [qw(1 22 333)]];
14017            
14018             my $s = indentString(formatTable($d), ' ')."
14019             "; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14020              
14021            
14022             ok $s eq <
14023             1 A B C
14024             2 AA BB CC
14025             3 AAA BBB CCC
14026             4 1 22 333
14027             END
14028            
14029              
14030             =head2 replaceStringWithString($string, $source, $target)
14031              
14032             Replace all instances in B<$string> of B<$source> with B<$target>
14033              
14034             Parameter Description
14035             1 $string String in which to replace substrings
14036             2 $source The string to be replaced
14037             3 $target The replacement string
14038              
14039             B
14040              
14041              
14042            
14043             ok replaceStringWithString(q(abababZ), q(ab), q(c)) eq q(cccZ), 'eee'; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14044              
14045            
14046              
14047             =head2 formatString($string, $width)
14048              
14049             Format the specified B<$string> so it can be displayed in B<$width> columns.
14050              
14051             Parameter Description
14052             1 $string The string of text to format
14053             2 $width The formatted width.
14054              
14055             B
14056              
14057              
14058            
14059             ok formatString(<
14060              
14061             Now is the time for all
14062             good men to come to the rescue
14063             of the ailing B.
14064             END
14065            
14066              
14067             =head2 isBlank($string)
14068              
14069             Test whether a string is blank.
14070              
14071             Parameter Description
14072             1 $string String
14073              
14074             B
14075              
14076              
14077            
14078             ok isBlank(""); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14079              
14080            
14081            
14082             ok isBlank("
14083             "); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14084              
14085            
14086              
14087             =head2 trim($string)
14088              
14089             Remove any white space from the front and end of a string.
14090              
14091             Parameter Description
14092             1 $string String
14093              
14094             B
14095              
14096              
14097            
14098             ok trim(" a b ") eq join ' ', qw(a b); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14099              
14100            
14101              
14102             =head2 pad($string, $length, $padding)
14103              
14104             Pad the specified B<$string> to a multiple of the specified B<$length> with blanks or the specified padding character to a multiple of a specified length.
14105              
14106             Parameter Description
14107             1 $string String
14108             2 $length Tab width
14109             3 $padding Padding string
14110              
14111             B
14112              
14113              
14114            
14115             is_deeply pad('abc ', 2).'=' , "abc ="; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14116              
14117            
14118             is_deeply pad('abc ', 3).'=' , "abc="; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14119              
14120            
14121             is_deeply pad('abc ', 4, q(.)).'=' , "abc.="; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14122              
14123            
14124             is_deeply pad('abc ', 5).'=' , "abc ="; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14125              
14126            
14127             is_deeply pad('abc ', 6).'=' , "abc ="; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14128              
14129            
14130             is_deeply ppp(2, 'abc ').'=' , "abc =";
14131             is_deeply ppp(3, 'abc ').'=' , "abc=";
14132             is_deeply ppp(4, 'abc ', q(.)).'=' , "abc.=";
14133             is_deeply ppp(5, 'abc ').'=' , "abc =";
14134             is_deeply ppp(6, 'abc ').'=' , "abc =";
14135            
14136             is_deeply lpad('abc ', 2).'=' , " abc=";
14137             is_deeply lpad('abc ', 3).'=' , "abc=";
14138             is_deeply lpad('abc ', 4, q(.)).'=' , ".abc=";
14139             is_deeply lpad('abc ', 5).'=' , " abc=";
14140             is_deeply lpad('abc ', 6).'=' , " abc=";
14141            
14142              
14143             =head2 lpad($string, $length, $padding)
14144              
14145             Left Pad the specified B<$string> to a multiple of the specified B<$length> with blanks or the specified padding character to a multiple of a specified length.
14146              
14147             Parameter Description
14148             1 $string String
14149             2 $length Tab width
14150             3 $padding Padding string
14151              
14152             B
14153              
14154              
14155             is_deeply pad('abc ', 2).'=' , "abc =";
14156             is_deeply pad('abc ', 3).'=' , "abc=";
14157             is_deeply pad('abc ', 4, q(.)).'=' , "abc.=";
14158             is_deeply pad('abc ', 5).'=' , "abc =";
14159             is_deeply pad('abc ', 6).'=' , "abc =";
14160            
14161             is_deeply ppp(2, 'abc ').'=' , "abc =";
14162             is_deeply ppp(3, 'abc ').'=' , "abc=";
14163             is_deeply ppp(4, 'abc ', q(.)).'=' , "abc.=";
14164             is_deeply ppp(5, 'abc ').'=' , "abc =";
14165             is_deeply ppp(6, 'abc ').'=' , "abc =";
14166            
14167            
14168             is_deeply lpad('abc ', 2).'=' , " abc="; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14169              
14170            
14171             is_deeply lpad('abc ', 3).'=' , "abc="; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14172              
14173            
14174             is_deeply lpad('abc ', 4, q(.)).'=' , ".abc="; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14175              
14176            
14177             is_deeply lpad('abc ', 5).'=' , " abc="; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14178              
14179            
14180             is_deeply lpad('abc ', 6).'=' , " abc="; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14181              
14182            
14183              
14184             =head2 ppp($length, $string, $padding)
14185              
14186             Pad the specified B<$string> to a multiple of the specified B<$length> with blanks or the specified padding character to a multiple of a specified length.
14187              
14188             Parameter Description
14189             1 $length Tab width
14190             2 $string String
14191             3 $padding Padding string
14192              
14193             B
14194              
14195              
14196             is_deeply pad('abc ', 2).'=' , "abc =";
14197             is_deeply pad('abc ', 3).'=' , "abc=";
14198             is_deeply pad('abc ', 4, q(.)).'=' , "abc.=";
14199             is_deeply pad('abc ', 5).'=' , "abc =";
14200             is_deeply pad('abc ', 6).'=' , "abc =";
14201            
14202            
14203             is_deeply ppp(2, 'abc ').'=' , "abc ="; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14204              
14205            
14206             is_deeply ppp(3, 'abc ').'=' , "abc="; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14207              
14208            
14209             is_deeply ppp(4, 'abc ', q(.)).'=' , "abc.="; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14210              
14211            
14212             is_deeply ppp(5, 'abc ').'=' , "abc ="; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14213              
14214            
14215             is_deeply ppp(6, 'abc ').'=' , "abc ="; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14216              
14217            
14218             is_deeply lpad('abc ', 2).'=' , " abc=";
14219             is_deeply lpad('abc ', 3).'=' , "abc=";
14220             is_deeply lpad('abc ', 4, q(.)).'=' , ".abc=";
14221             is_deeply lpad('abc ', 5).'=' , " abc=";
14222             is_deeply lpad('abc ', 6).'=' , " abc=";
14223            
14224              
14225             =head2 firstNChars($string, $length)
14226              
14227             First N characters of a string.
14228              
14229             Parameter Description
14230             1 $string String
14231             2 $length Length
14232              
14233             B
14234              
14235              
14236            
14237             ok firstNChars(q(abc), 2) eq q(ab); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14238              
14239            
14240            
14241             ok firstNChars(q(abc), 4) eq q(abc); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14242              
14243            
14244              
14245             =head2 nws($string, $length)
14246              
14247             Normalize white space in a string to make comparisons easier. Leading and trailing white space is removed; blocks of white space in the interior are reduced to a single space. In effect: this puts everything on one long line with never more than one space at a time. Optionally a maximum length is applied to the normalized string.
14248              
14249             Parameter Description
14250             1 $string String to normalize
14251             2 $length Maximum length of result
14252              
14253             B
14254              
14255              
14256            
14257             ok nws(qq(a b c)) eq q(a b c); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14258              
14259            
14260              
14261             =head2 deduplicateSequentialWordsInString($s)
14262              
14263             Remove sequentially duplicate words in a string
14264              
14265             Parameter Description
14266             1 $s String to deduplicate
14267              
14268             B
14269              
14270              
14271            
14272             ok deduplicateSequentialWordsInString(<
14273             ); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14274              
14275             (aa [bb bb -cc cc dd dd dd dd ee ee ee ee
14276             END
14277            
14278              
14279             =head2 detagString($string)
14280              
14281             Remove L or L tags from a string
14282              
14283             Parameter Description
14284             1 $string String to detag
14285              
14286             B
14287              
14288              
14289            
14290             ok detagString(q(a b c)) eq q(a b c), 'hhh'; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14291              
14292            
14293              
14294             =head2 parseIntoWordsAndStrings($string)
14295              
14296             Parse a B<$string> into words and quoted strings. A quote following a space introduces a string, else a quote is just part of the containing word.
14297              
14298             Parameter Description
14299             1 $string String to parse
14300              
14301             B
14302              
14303              
14304             if (1)
14305             {is_deeply
14306            
14307             [parseIntoWordsAndStrings( # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14308              
14309             q( aa12! a'b "aa !! ++ bb" ' ', '"' "'" "" ''.)) ],
14310             ["aa12!", "a'b", "aa !! ++ bb", " ", ",", '"', "'", "", "", '.'];
14311             }
14312            
14313              
14314             =head2 stringsAreNotEqual($a, $b)
14315              
14316             Return the common start followed by the two non equal tails of two non equal strings or an empty list if the strings are equal.
14317              
14318             Parameter Description
14319             1 $a First string
14320             2 $b Second string
14321              
14322             B
14323              
14324              
14325            
14326             ok !stringsAreNotEqual(q(abc), q(abc)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14327              
14328            
14329             ok stringsAreNotEqual(q(abc), q(abd)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14330              
14331            
14332             is_deeply [stringsAreNotEqual(q(abc), q(abd))], [qw(ab c d)]; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14333              
14334            
14335             is_deeply [stringsAreNotEqual(q(ab), q(abd))], [q(ab), '', q(d)]; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14336              
14337             is_deeply showGotVersusWanted("aaaa
14338             bbbb
14339             cccc
14340             dddd
14341             ",
14342             "aaaa
14343             bbbb
14344             ccee
14345             ffff
14346             "), <
14347             Comparing wanted with got failed at line: 3, character: 3
14348             Start:
14349             aaaa
14350             bbbb
14351             cc
14352             Want ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
14353            
14354             ee
14355             ffff
14356            
14357             Got ________________________________________________________________________________
14358            
14359             cc
14360             dddd
14361             END
14362            
14363              
14364             =head2 showGotVersusWanted($g, $e)
14365              
14366             Show the difference between the wanted string and the wanted string
14367              
14368             Parameter Description
14369             1 $g First string
14370             2 $e Second string
14371              
14372             B
14373              
14374              
14375             ok !stringsAreNotEqual(q(abc), q(abc));
14376             ok stringsAreNotEqual(q(abc), q(abd));
14377             is_deeply [stringsAreNotEqual(q(abc), q(abd))], [qw(ab c d)];
14378             is_deeply [stringsAreNotEqual(q(ab), q(abd))], [q(ab), '', q(d)];
14379            
14380             is_deeply showGotVersusWanted("aaaa
14381             bbbb
14382             cccc
14383             dddd
14384             ", # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14385              
14386             "aaaa
14387             bbbb
14388             ccee
14389             ffff
14390             "), <
14391             Comparing wanted with got failed at line: 3, character: 3
14392             Start:
14393             aaaa
14394             bbbb
14395             cc
14396             Want ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
14397            
14398             ee
14399             ffff
14400            
14401             Got ________________________________________________________________________________
14402            
14403             cc
14404             dddd
14405             END
14406            
14407              
14408             =head2 printQw(@words)
14409              
14410             Print an array of words in qw() format.
14411              
14412             Parameter Description
14413             1 @words Array of words
14414              
14415             B
14416              
14417              
14418            
14419             is_deeply printQw(qw(a b c)), q(qw(a b c)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14420              
14421            
14422              
14423             =head2 numberOfLinesInString($string)
14424              
14425             The number of lines in a string.
14426              
14427             Parameter Description
14428             1 $string String
14429              
14430             B
14431              
14432              
14433            
14434             ok numberOfLinesInString("a
14435             b
14436             ") == 2; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14437              
14438            
14439              
14440             =head2 javaPackage($java)
14441              
14442             Extract the package name from a java string or file.
14443              
14444             Parameter Description
14445             1 $java Java file if it exists else the string of java
14446              
14447             B
14448              
14449              
14450             my $j = writeFile(undef, <
14451             // Test
14452             package com.xyz;
14453             END
14454            
14455             ok javaPackage($j) eq "com.xyz"; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14456              
14457             ok javaPackageAsFileName($j) eq "com/xyz";
14458             unlink $j;
14459            
14460             my $p = writeFile(undef, <
14461             package a::b;
14462             END
14463             ok perlPackage($p) eq "a::b";
14464             unlink $p;
14465            
14466              
14467             =head2 javaPackageAsFileName($java)
14468              
14469             Extract the package name from a java string or file and convert it to a file name.
14470              
14471             Parameter Description
14472             1 $java Java file if it exists else the string of java
14473              
14474             B
14475              
14476              
14477             my $j = writeFile(undef, <
14478             // Test
14479             package com.xyz;
14480             END
14481             ok javaPackage($j) eq "com.xyz";
14482            
14483             ok javaPackageAsFileName($j) eq "com/xyz"; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14484              
14485             unlink $j;
14486            
14487             my $p = writeFile(undef, <
14488             package a::b;
14489             END
14490             ok perlPackage($p) eq "a::b";
14491             unlink $p;
14492            
14493              
14494             =head2 perlPackage($perl)
14495              
14496             Extract the package name from a perl string or file.
14497              
14498             Parameter Description
14499             1 $perl Perl file if it exists else the string of perl
14500              
14501             B
14502              
14503              
14504             my $j = writeFile(undef, <
14505             // Test
14506             package com.xyz;
14507             END
14508             ok javaPackage($j) eq "com.xyz";
14509             ok javaPackageAsFileName($j) eq "com/xyz";
14510             unlink $j;
14511            
14512             my $p = writeFile(undef, <
14513             package a::b;
14514             END
14515            
14516             ok perlPackage($p) eq "a::b"; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14517              
14518             unlink $p;
14519            
14520             my $p = writeFile(undef, <
14521             package a::b;
14522             END
14523            
14524            
14525             ok perlPackage($p) eq "a::b"; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14526              
14527            
14528              
14529             =head2 javaScriptExports($fileOrString)
14530              
14531             Extract the Javascript functions marked for export in a file or string. Functions are marked for export by placing function in column 1 followed by //E on the same line. The end of the exported function is located by
14532             }
14533              
14534             Parameter Description
14535             1 $fileOrString File or string
14536              
14537             B
14538              
14539              
14540            
14541             ok javaScriptExports(<
14542              
14543             function aaa() //E
14544             {console.log('aaa');
14545            
14546              
14547             =head2 chooseStringAtRandom(@strings)
14548              
14549             Choose a string at random from the list of B<@strings> supplied.
14550              
14551             Parameter Description
14552             1 @strings Strings to chose from
14553              
14554             B
14555              
14556              
14557            
14558             ok q(a) eq chooseStringAtRandom(qw(a a a a)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14559              
14560            
14561              
14562             =head2 randomizeArray(@a)
14563              
14564             Randomize an array
14565              
14566             Parameter Description
14567             1 @a Array to randomize
14568              
14569             B
14570              
14571              
14572            
14573             is_deeply [randomizeArray(qw(a a a a))], [qw(a a a a)]; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14574              
14575            
14576              
14577             =head1 Arrays and Hashes
14578              
14579             Operations on arrays and hashes and array of of hashesh and ghashes of arrays and so on a infinitum.
14580              
14581             =head2 lengthOfLongestSubArray($a)
14582              
14583             Given an array of arrays find the length of the longest sub array.
14584              
14585             Parameter Description
14586             1 $a Array reference
14587              
14588             B
14589              
14590              
14591             if (1)
14592            
14593             {ok 3 == lengthOfLongestSubArray [[1..2], [1..3], [1..3], []]; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14594              
14595             }
14596            
14597              
14598             =head2 cmpArrays($a, $b)
14599              
14600             Compare two arrays of strings
14601              
14602             Parameter Description
14603             1 $a Array A
14604             2 $b Array B
14605              
14606             B
14607              
14608              
14609            
14610             ok cmpArrays([qw(a b)], [qw(a a)]) == +1; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14611              
14612            
14613             ok cmpArrays([qw(a b)], [qw(a c)]) == -1; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14614              
14615            
14616             ok cmpArrays([qw(a b)], [qw(a b a)]) == -1; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14617              
14618            
14619             ok cmpArrays([qw(a b a)], [qw(a b)]) == +1; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14620              
14621            
14622             ok cmpArrays([qw(a b)], [qw(a b)]) == 0; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14623              
14624            
14625              
14626             =head2 forEachKeyAndValue($body, %hash)
14627              
14628             Iterate over a hash for each key and value
14629              
14630             Parameter Description
14631             1 $body Body to be executed
14632             2 %hash Hash to be iterated
14633              
14634             B
14635              
14636              
14637            
14638             forEachKeyAndValue # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14639              
14640             {my ($letter, $number) = @_;
14641             push @t, "Letter=$letter, number=$number";
14642             } %h;
14643            
14644             is_deeply join("
14645             ", @t, ''), <
14646             Letter=a, number=1
14647             Letter=b, number=2
14648             Letter=c, number=3
14649             END
14650             }
14651            
14652             if (1) {
14653             is_deeply convertUtf8ToUtf32(0x24), 0x24;
14654             is_deeply convertUtf8ToUtf32(0xc2a2), 0xa2;
14655             is_deeply convertUtf8ToUtf32(0xe0a4b9), 0x939;
14656             is_deeply convertUtf8ToUtf32(0xe282ac), 0x20ac;
14657             is_deeply convertUtf8ToUtf32(0xed959c), 0xd55c;
14658             is_deeply convertUtf8ToUtf32(0xf0908d88), 0x10348;
14659            
14660             is_deeply convertUtf32ToUtf8(0x24), 0x24;
14661             is_deeply convertUtf32ToUtf8(0xa2), 0xc2a2;
14662             is_deeply convertUtf32ToUtf8(0x939), 0xe0a4b9;
14663             is_deeply convertUtf32ToUtf8(0x20ac), 0xe282ac;
14664             is_deeply convertUtf32ToUtf8(0xd55c), 0xed959c;
14665             is_deeply convertUtf32ToUtf8(0x10348), 0xf0908d88;
14666             };
14667            
14668             if ($localTest)
14669             {say STDERR "DTT finished in ", (time() - $timeStart), " seconds";
14670            
14671              
14672             =head1 Unicode
14673              
14674             Translate L alphanumerics in strings to various L blocks.
14675              
14676             =head2 mathematicalItalicString($string)
14677              
14678             Convert alphanumerics in a string to L Mathematical Italic.
14679              
14680             Parameter Description
14681             1 $string String to convert
14682              
14683             B
14684              
14685              
14686            
14687             ok mathematicalItalicString (q(APPLES and ORANGES)) eq q(𝐴𝑃𝑃𝐿𝐸𝑆 𝑎𝑛𝑑 𝑂𝑅𝐴𝑁𝐺𝐸𝑆); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14688              
14689            
14690              
14691             =head2 mathematicalBoldString($string)
14692              
14693             Convert alphanumerics in a string to L Mathematical Bold.
14694              
14695             Parameter Description
14696             1 $string String to convert
14697              
14698             B
14699              
14700              
14701            
14702             ok mathematicalBoldString (q(APPLES and ORANGES)) eq q(𝐀𝐏𝐏𝐋𝐄𝐒 𝐚𝐧𝐝 𝐎𝐑𝐀𝐍𝐆𝐄𝐒); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14703              
14704            
14705              
14706             =head2 mathematicalBoldStringUndo($string)
14707              
14708             Undo alphanumerics in a string to L Mathematical Bold..
14709              
14710             Parameter Description
14711             1 $string String to convert
14712              
14713             B
14714              
14715              
14716            
14717             ok mathematicalBoldStringUndo (q(𝐀𝐏𝐏𝐋𝐄𝐒 𝐚𝐧𝐝 𝐎𝐑𝐀𝐍𝐆𝐄𝐒)) eq q(APPLES and ORANGES); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14718              
14719            
14720              
14721             =head2 mathematicalBoldItalicString($string)
14722              
14723             Convert alphanumerics in a string to L Mathematical Bold Italic.
14724              
14725             Parameter Description
14726             1 $string String to convert
14727              
14728             B
14729              
14730              
14731            
14732             ok mathematicalBoldItalicString (q(APPLES and ORANGES)) eq q(𝑨𝑷𝑷𝑳𝑬𝑺 𝒂𝒏𝒅 𝑶𝑹𝑨𝑵𝑮𝑬𝑺); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14733              
14734            
14735              
14736             =head2 mathematicalBoldItalicStringUndo($string)
14737              
14738             Undo alphanumerics in a string to L Mathematical Bold Italic.
14739              
14740             Parameter Description
14741             1 $string String to convert
14742              
14743             B
14744              
14745              
14746            
14747             ok mathematicalBoldItalicStringUndo (q(𝑨𝑷𝑷𝑳𝑬𝑺 𝒂𝒏𝒅 𝑶𝑹𝑨𝑵𝑮𝑬𝑺)) eq q(APPLES and ORANGES); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14748              
14749            
14750              
14751             =head2 mathematicalSansSerifString($string)
14752              
14753             Convert alphanumerics in a string to L Mathematical Sans Serif.
14754              
14755             Parameter Description
14756             1 $string String to convert
14757              
14758             B
14759              
14760              
14761            
14762             ok mathematicalSansSerifString (q(APPLES and ORANGES)) eq q(𝖠𝖯𝖯𝖫𝖤𝖲 𝖺𝗇𝖽 𝖮𝖱𝖠𝖭𝖦𝖤𝖲); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14763              
14764            
14765              
14766             =head2 mathematicalSansSerifStringUndo($string)
14767              
14768             Undo alphanumerics in a string to L Mathematical Sans Serif.
14769              
14770             Parameter Description
14771             1 $string String to convert
14772              
14773             B
14774              
14775              
14776            
14777             ok mathematicalSansSerifStringUndo (q(𝖠𝖯𝖯𝖫𝖤𝖲 𝖺𝗇𝖽 𝖮𝖱𝖠𝖭𝖦𝖤𝖲)) eq q(APPLES and ORANGES); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14778              
14779            
14780              
14781             =head2 mathematicalSansSerifBoldString($string)
14782              
14783             Convert alphanumerics in a string to L Mathematical Sans Serif Bold.
14784              
14785             Parameter Description
14786             1 $string String to convert
14787              
14788             B
14789              
14790              
14791            
14792             ok mathematicalSansSerifBoldString (q(APPLES and ORANGES)) eq q(𝗔𝗣𝗣𝗟𝗘𝗦 𝗮𝗻𝗱 𝗢𝗥𝗔𝗡𝗚𝗘𝗦); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14793              
14794            
14795              
14796             =head2 mathematicalSansSerifBoldStringUndo($string)
14797              
14798             Undo alphanumerics in a string to L Mathematical Sans Serif Bold.
14799              
14800             Parameter Description
14801             1 $string String to convert
14802              
14803             B
14804              
14805              
14806            
14807             ok mathematicalSansSerifBoldStringUndo (q(𝗔𝗣𝗣𝗟𝗘𝗦 𝗮𝗻𝗱 𝗢𝗥𝗔𝗡𝗚𝗘𝗦)) eq q(APPLES and ORANGES); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14808              
14809            
14810              
14811             =head2 mathematicalSansSerifItalicString($string)
14812              
14813             Convert alphanumerics in a string to L Mathematical Sans Serif Italic.
14814              
14815             Parameter Description
14816             1 $string String to convert
14817              
14818             B
14819              
14820              
14821            
14822             ok mathematicalSansSerifItalicString (q(APPLES and ORANGES)) eq q(𝘈𝘗𝘗𝘓𝘌𝘚 𝘢𝘯𝘥 𝘖𝘙𝘈𝘕𝘎𝘌𝘚); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14823              
14824            
14825              
14826             =head2 mathematicalSansSerifItalicStringUndo($string)
14827              
14828             Undo alphanumerics in a string to L Mathematical Sans Serif Italic.
14829              
14830             Parameter Description
14831             1 $string String to convert
14832              
14833             B
14834              
14835              
14836            
14837             ok mathematicalSansSerifItalicStringUndo (q(𝘈𝘗𝘗𝘓𝘌𝘚 𝘢𝘯𝘥 𝘖𝘙𝘈𝘕𝘎𝘌𝘚)) eq q(APPLES and ORANGES); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14838              
14839            
14840              
14841             =head2 mathematicalSansSerifBoldItalicString($string)
14842              
14843             Convert alphanumerics in a string to L Mathematical Sans Serif Bold Italic.
14844              
14845             Parameter Description
14846             1 $string String to convert
14847              
14848             B
14849              
14850              
14851            
14852             ok mathematicalSansSerifBoldItalicString (q(APPLES and ORANGES)) eq q(𝘼𝙋𝙋𝙇𝙀𝙎 𝙖𝙣𝙙 𝙊𝙍𝘼𝙉𝙂𝙀𝙎); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14853              
14854            
14855              
14856             =head2 mathematicalSansSerifBoldItalicStringUndo($string)
14857              
14858             Undo alphanumerics in a string to L Mathematical Sans Serif Bold Italic.
14859              
14860             Parameter Description
14861             1 $string String to convert
14862              
14863             B
14864              
14865              
14866            
14867             ok mathematicalSansSerifBoldItalicStringUndo(q(𝘼𝙋𝙋𝙇𝙀𝙎 𝙖𝙣𝙙 𝙊𝙍𝘼𝙉𝙂𝙀𝙎)) eq q(APPLES and ORANGES); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14868              
14869            
14870              
14871             =head2 mathematicalMonoSpaceString($string)
14872              
14873             Convert alphanumerics in a string to L Mathematical MonoSpace.
14874              
14875             Parameter Description
14876             1 $string String to convert
14877              
14878             B
14879              
14880              
14881            
14882             ok mathematicalMonoSpaceString (q(APPLES and ORANGES)) eq q(𝙰𝙿𝙿𝙻𝙴𝚂 𝚊𝚗𝚍 𝙾𝚁𝙰𝙽𝙶𝙴𝚂); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14883              
14884            
14885              
14886             =head2 mathematicalMonoSpaceStringUndo($string)
14887              
14888             Undo alphanumerics in a string to L Mathematical MonoSpace.
14889              
14890             Parameter Description
14891             1 $string String to convert
14892              
14893             B
14894              
14895              
14896            
14897             ok mathematicalMonoSpaceStringUndo (q(𝙰𝙿𝙿𝙻𝙴𝚂 𝚊𝚗𝚍 𝙾𝚁𝙰𝙽𝙶𝙴𝚂)) eq q(APPLES and ORANGES); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14898              
14899            
14900              
14901             =head2 boldString($string)
14902              
14903             Convert alphanumerics in a string to bold.
14904              
14905             Parameter Description
14906             1 $string String to convert
14907              
14908             B
14909              
14910              
14911            
14912             ok boldString(q(zZ)) eq q(𝘇𝗭); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14913              
14914            
14915              
14916             =head2 boldStringUndo($string)
14917              
14918             Undo alphanumerics in a string to bold.
14919              
14920             Parameter Description
14921             1 $string String to convert
14922              
14923             B
14924              
14925              
14926             if (1)
14927             {my $n = 1234567890;
14928            
14929             ok boldStringUndo (boldString($n)) == $n; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14930              
14931             ok enclosedStringUndo (enclosedString($n)) == $n;
14932             ok enclosedReversedStringUndo(enclosedReversedString($n)) == $n;
14933             ok superScriptStringUndo (superScriptString($n)) == $n;
14934             ok subScriptStringUndo (subScriptString($n)) == $n;
14935             }
14936            
14937              
14938             =head2 enclosedString($string)
14939              
14940             Convert alphanumerics in a string to enclosed alphanumerics.
14941              
14942             Parameter Description
14943             1 $string String to convert
14944              
14945             B
14946              
14947              
14948            
14949             ok enclosedString(q(hello world 1234)) eq q(ⓗⓔⓛⓛⓞ ⓦⓞⓡⓛⓓ ①②③④); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14950              
14951            
14952              
14953             =head2 enclosedStringUndo($string)
14954              
14955             Undo alphanumerics in a string to enclosed alphanumerics.
14956              
14957             Parameter Description
14958             1 $string String to convert
14959              
14960             B
14961              
14962              
14963             if (1)
14964             {my $n = 1234567890;
14965             ok boldStringUndo (boldString($n)) == $n;
14966            
14967             ok enclosedStringUndo (enclosedString($n)) == $n; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14968              
14969             ok enclosedReversedStringUndo(enclosedReversedString($n)) == $n;
14970             ok superScriptStringUndo (superScriptString($n)) == $n;
14971             ok subScriptStringUndo (subScriptString($n)) == $n;
14972             }
14973            
14974              
14975             =head2 enclosedReversedString($string)
14976              
14977             Convert alphanumerics in a string to enclosed reversed alphanumerics.
14978              
14979             Parameter Description
14980             1 $string String to convert
14981              
14982             B
14983              
14984              
14985            
14986             ok enclosedReversedString(q(hello world 1234)) eq q(🅗🅔🅛🅛🅞 🅦🅞🅡🅛🅓 ➊➋➌➍); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
14987              
14988            
14989              
14990             =head2 enclosedReversedStringUndo($string)
14991              
14992             Undo alphanumerics in a string to enclosed reversed alphanumerics.
14993              
14994             Parameter Description
14995             1 $string String to convert
14996              
14997             B
14998              
14999              
15000             if (1)
15001             {my $n = 1234567890;
15002             ok boldStringUndo (boldString($n)) == $n;
15003             ok enclosedStringUndo (enclosedString($n)) == $n;
15004            
15005             ok enclosedReversedStringUndo(enclosedReversedString($n)) == $n; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
15006              
15007             ok superScriptStringUndo (superScriptString($n)) == $n;
15008             ok subScriptStringUndo (subScriptString($n)) == $n;
15009             }
15010            
15011              
15012             =head2 superScriptString($string)
15013              
15014             Convert alphanumerics in a string to super scripts
15015              
15016             Parameter Description
15017             1 $string String to convert
15018              
15019             B
15020              
15021              
15022            
15023             ok superScriptString(1234567890) eq q(¹²³⁴⁵⁶⁷⁸⁹⁰); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
15024              
15025            
15026              
15027             =head2 superScriptStringUndo($string)
15028              
15029             Undo alphanumerics in a string to super scripts
15030              
15031             Parameter Description
15032             1 $string String to convert
15033              
15034             B
15035              
15036              
15037             if (1)
15038             {my $n = 1234567890;
15039             ok boldStringUndo (boldString($n)) == $n;
15040             ok enclosedStringUndo (enclosedString($n)) == $n;
15041             ok enclosedReversedStringUndo(enclosedReversedString($n)) == $n;
15042            
15043             ok superScriptStringUndo (superScriptString($n)) == $n; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
15044              
15045             ok subScriptStringUndo (subScriptString($n)) == $n;
15046             }
15047            
15048              
15049             =head2 subScriptString($string)
15050              
15051             Convert alphanumerics in a string to sub scripts
15052              
15053             Parameter Description
15054             1 $string String to convert
15055              
15056             B
15057              
15058              
15059            
15060             ok subScriptString(1234567890) eq q(₁₂₃₄₅₆₇₈₉₀); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
15061              
15062            
15063              
15064             =head2 subScriptStringUndo($string)
15065              
15066             Undo alphanumerics in a string to sub scripts
15067              
15068             Parameter Description
15069             1 $string String to convert
15070              
15071             B
15072              
15073              
15074             if (1)
15075             {my $n = 1234567890;
15076             ok boldStringUndo (boldString($n)) == $n;
15077             ok enclosedStringUndo (enclosedString($n)) == $n;
15078             ok enclosedReversedStringUndo(enclosedReversedString($n)) == $n;
15079             ok superScriptStringUndo (superScriptString($n)) == $n;
15080            
15081             ok subScriptStringUndo (subScriptString($n)) == $n; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
15082              
15083             }
15084            
15085              
15086             =head2 isFileUtf8($file)
15087              
15088             Return the file name quoted if its contents are in utf8 else return undef
15089              
15090             Parameter Description
15091             1 $file File to test
15092              
15093             B
15094              
15095              
15096             my $f = writeFile(undef, "aaa");
15097            
15098             ok isFileUtf8 $f; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
15099              
15100            
15101              
15102             =head2 convertUtf8ToUtf32($c)
15103              
15104             Convert a number representing a single unicode point coded in utf8 to utf32.
15105              
15106             Parameter Description
15107             1 $c Unicode point encoded as utf8
15108              
15109             B
15110              
15111              
15112            
15113             is_deeply convertUtf8ToUtf32(0x24), 0x24; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
15114              
15115            
15116             is_deeply convertUtf8ToUtf32(0xc2a2), 0xa2; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
15117              
15118            
15119             is_deeply convertUtf8ToUtf32(0xe0a4b9), 0x939; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
15120              
15121            
15122             is_deeply convertUtf8ToUtf32(0xe282ac), 0x20ac; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
15123              
15124            
15125             is_deeply convertUtf8ToUtf32(0xed959c), 0xd55c; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
15126              
15127            
15128             is_deeply convertUtf8ToUtf32(0xf0908d88), 0x10348; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
15129              
15130            
15131             is_deeply convertUtf32ToUtf8(0x24), 0x24;
15132             is_deeply convertUtf32ToUtf8(0xa2), 0xc2a2;
15133             is_deeply convertUtf32ToUtf8(0x939), 0xe0a4b9;
15134             is_deeply convertUtf32ToUtf8(0x20ac), 0xe282ac;
15135             is_deeply convertUtf32ToUtf8(0xd55c), 0xed959c;
15136             is_deeply convertUtf32ToUtf8(0x10348), 0xf0908d88;
15137             };
15138            
15139             if ($localTest)
15140             {say STDERR "DTT finished in ", (time() - $timeStart), " seconds";
15141            
15142              
15143             =head2 convertUtf32ToUtf8($c)
15144              
15145             Convert a number representing a single unicode point coded in utf32 to utf8.
15146              
15147             Parameter Description
15148             1 $c Unicode point encoded as utf32
15149              
15150             B
15151              
15152              
15153            
15154             is_deeply convertUtf32ToUtf8(0x24), 0x24; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
15155              
15156            
15157              
15158             =head1 Unix domain communications
15159              
15160             Send messages between processes via a unix domain socket.
15161              
15162             =head2 newUdsrServer(@parms)
15163              
15164             Create a communications server - a means to communicate between processes on the same machine via L and L.
15165              
15166             Parameter Description
15167             1 @parms Attributes per L
15168              
15169             B
15170              
15171              
15172             my $N = 20;
15173            
15174             my $s = newUdsrServer(serverAction=>sub # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
15175              
15176             {my ($u) = @_;
15177             my $r = $u->read;
15178             $u->write(qq(Hello from server $r));
15179             });
15180            
15181             my $p = newProcessStarter(min(100, $N)); # Run some clients
15182             for my $i(1..$N)
15183             {$p->start(sub
15184             {my $count = 0;
15185             for my $j(1..$N)
15186             {my $c = newUdsrClient;
15187             my $m = qq(Hello from client $i x $j);
15188             $c->write($m);
15189             my $r = $c->read;
15190             ++$count if $r eq qq(Hello from server $m);
15191             }
15192             [$count]
15193             });
15194             }
15195            
15196             my $count;
15197             for my $r($p->finish) # Consolidate results
15198             {my ($c) = @$r;
15199             $count += $c;
15200             }
15201            
15202             ok $count == $N*$N; # Check results and kill
15203             $s->kill;
15204            
15205              
15206             =head2 newUdsrClient(@parms)
15207              
15208             Create a new communications client - a means to communicate between processes on the same machine via L and L.
15209              
15210             Parameter Description
15211             1 @parms Attributes per L
15212              
15213             B
15214              
15215              
15216             my $N = 20;
15217             my $s = newUdsrServer(serverAction=>sub
15218             {my ($u) = @_;
15219             my $r = $u->read;
15220             $u->write(qq(Hello from server $r));
15221             });
15222            
15223             my $p = newProcessStarter(min(100, $N)); # Run some clients
15224             for my $i(1..$N)
15225             {$p->start(sub
15226             {my $count = 0;
15227             for my $j(1..$N)
15228            
15229             {my $c = newUdsrClient; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
15230              
15231             my $m = qq(Hello from client $i x $j);
15232             $c->write($m);
15233             my $r = $c->read;
15234             ++$count if $r eq qq(Hello from server $m);
15235             }
15236             [$count]
15237             });
15238             }
15239            
15240             my $count;
15241             for my $r($p->finish) # Consolidate results
15242             {my ($c) = @$r;
15243             $count += $c;
15244             }
15245            
15246             ok $count == $N*$N; # Check results and kill
15247             $s->kill;
15248            
15249              
15250             =head2 Udsr::write($u, $msg)
15251              
15252             Write a communications message to the L or the L.
15253              
15254             Parameter Description
15255             1 $u Communicator
15256             2 $msg Message
15257              
15258             B
15259              
15260              
15261             my $N = 20;
15262             my $s = newUdsrServer(serverAction=>sub
15263             {my ($u) = @_;
15264             my $r = $u->read;
15265             $u->write(qq(Hello from server $r));
15266             });
15267            
15268             my $p = newProcessStarter(min(100, $N)); # Run some clients
15269             for my $i(1..$N)
15270             {$p->start(sub
15271             {my $count = 0;
15272             for my $j(1..$N)
15273             {my $c = newUdsrClient;
15274             my $m = qq(Hello from client $i x $j);
15275             $c->write($m);
15276             my $r = $c->read;
15277             ++$count if $r eq qq(Hello from server $m);
15278             }
15279             [$count]
15280             });
15281             }
15282            
15283             my $count;
15284             for my $r($p->finish) # Consolidate results
15285             {my ($c) = @$r;
15286             $count += $c;
15287             }
15288            
15289             ok $count == $N*$N; # Check results and kill
15290             $s->kill;
15291            
15292              
15293             =head2 Udsr::read($u)
15294              
15295             Read a message from the L or the L.
15296              
15297             Parameter Description
15298             1 $u Communicator
15299              
15300             B
15301              
15302              
15303             my $N = 20;
15304             my $s = newUdsrServer(serverAction=>sub
15305             {my ($u) = @_;
15306             my $r = $u->read;
15307             $u->write(qq(Hello from server $r));
15308             });
15309            
15310             my $p = newProcessStarter(min(100, $N)); # Run some clients
15311             for my $i(1..$N)
15312             {$p->start(sub
15313             {my $count = 0;
15314             for my $j(1..$N)
15315             {my $c = newUdsrClient;
15316             my $m = qq(Hello from client $i x $j);
15317             $c->write($m);
15318             my $r = $c->read;
15319             ++$count if $r eq qq(Hello from server $m);
15320             }
15321             [$count]
15322             });
15323             }
15324            
15325             my $count;
15326             for my $r($p->finish) # Consolidate results
15327             {my ($c) = @$r;
15328             $count += $c;
15329             }
15330            
15331             ok $count == $N*$N; # Check results and kill
15332             $s->kill;
15333            
15334              
15335             =head2 Udsr::kill($u)
15336              
15337             Kill a communications server.
15338              
15339             Parameter Description
15340             1 $u Communicator
15341              
15342             B
15343              
15344              
15345             my $N = 20;
15346             my $s = newUdsrServer(serverAction=>sub
15347             {my ($u) = @_;
15348             my $r = $u->read;
15349             $u->write(qq(Hello from server $r));
15350             });
15351            
15352             my $p = newProcessStarter(min(100, $N)); # Run some clients
15353             for my $i(1..$N)
15354             {$p->start(sub
15355             {my $count = 0;
15356             for my $j(1..$N)
15357             {my $c = newUdsrClient;
15358             my $m = qq(Hello from client $i x $j);
15359             $c->write($m);
15360             my $r = $c->read;
15361             ++$count if $r eq qq(Hello from server $m);
15362             }
15363             [$count]
15364             });
15365             }
15366            
15367             my $count;
15368             for my $r($p->finish) # Consolidate results
15369             {my ($c) = @$r;
15370             $count += $c;
15371             }
15372            
15373             ok $count == $N*$N; # Check results and kill
15374             $s->kill;
15375            
15376              
15377             =head2 Udsr::webUser($u, $folder)
15378              
15379             Create a systemd installed server that processes http requests using a specified userid. The systemd and CGI files plus an installation script are written to the specified folder after it has been cleared. The L attribute contains the code to be executed by the server: it should contain a L B which will be called with a hash of the CGI variables. This L should return the response to be sent back to the client. Returns the installation script file name.
15380              
15381             Parameter Description
15382             1 $u Communicator
15383             2 $folder Folder to contain server code
15384              
15385             B
15386              
15387              
15388             if (0)
15389             {my $fold = fpd(qw(/home phil zzz)); # Folder to contain server code
15390             my $name = q(test); # Service
15391             my $user = q(phil); # User
15392            
15393             my $udsr = newUdsr # Create a Udsr parameter list
15394             (serviceName => $name,
15395             serviceUser => $user,
15396             socketPath => qq(/home/phil/$name.socket),
15397             serverAction=> <<'END'
15398             my $user = userId;
15399             my $list = qx(ls -l);
15400             my $dtts = dateTimeStamp;
15401             return <
15402             Content-type: text/html
15403            
15404            

Hello World to you $user on $dtts!

15405            
15406            
 
15407             $list
15408            
15409             END2
15410             END
15411             );
15412            
15413            
15414             Udsr::webUser($udsr, $fold); # Create and install web service interface # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
15415              
15416             my $ip = awsIp;
15417             say STDERR qx(curl http://$ip/cgi-bin/$name/client.pl); # Enable port 80 on AWS first
15418             }
15419            
15420              
15421             =head2 www
15422              
15423             Web processing
15424              
15425             =head3 wwwGitHubAuth($saveUserDetails, $clientId, $clientSecret, $code, $state)
15426              
15427             Logon as a L L app per: L. If no L code is supplied then a web page is printed that allows the user to request that such a code be sent to the server. If a valid code is received, by the server then it is converted to a L token which is handed to L L.
15428              
15429             Parameter Description
15430             1 $saveUserDetails Process user token once obtained from GitHub
15431             2 $clientId Client id
15432             3 $clientSecret Client secret
15433             4 $code Authorization code
15434             5 $state Random string
15435              
15436             B
15437              
15438              
15439             wwwHeader;
15440            
15441            
15442             wwwGitHubAuth # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
15443              
15444             {my ($user, $state, $token, $scope, $type) = @_;
15445             }
15446             q(12345678901234567890), q(1234567890123456789012345678901234567890),
15447             q(12345678901234567890123456789012), q(12345678901234567890);
15448            
15449              
15450             =head1 Cloud Cover
15451              
15452             Useful for operating across the cloud.
15453              
15454             =head2 makeDieConfess()
15455              
15456             Force die to confess where the death occurred
15457              
15458              
15459             B
15460              
15461              
15462            
15463             makeDieConfess # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
15464              
15465            
15466              
15467             =head2 ipAddressOfHost($host)
15468              
15469             Get the first ip address of the specified host via Domain Name Services
15470              
15471             Parameter Description
15472             1 $host Host name
15473              
15474             B
15475              
15476              
15477             ok saveAwsIp(q(0.0.0.0)) eq awsIp;
15478             ok saveAwsIp(q(example.org));
15479             ok saveAwsDomain(q(example.org));
15480             ok awsR53a (q(XXXXX), q(www.example.org), q(22.12.232.1));
15481             ok awsR53aaaa(q(XXXXX), q(www.example.org), q([1232:1232:1232:1232:1232:1232:1232:1232:]));
15482            
15483              
15484             =head2 awsIp()
15485              
15486             Get ip address of server at L.
15487              
15488              
15489             B
15490              
15491              
15492            
15493             ok saveAwsIp(q(0.0.0.0)) eq awsIp; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
15494              
15495             ok saveAwsIp(q(example.org));
15496             ok saveAwsDomain(q(example.org));
15497             ok awsR53a (q(XXXXX), q(www.example.org), q(22.12.232.1));
15498             ok awsR53aaaa(q(XXXXX), q(www.example.org), q([1232:1232:1232:1232:1232:1232:1232:1232:]));
15499            
15500              
15501             =head2 saveAwsIp()
15502              
15503             Make the server at L with the given IP address the default primary server as used by all the methods whose names end in B or B. Returns the given IP address.
15504              
15505              
15506             B
15507              
15508              
15509            
15510             ok saveAwsIp(q(0.0.0.0)) eq awsIp; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
15511              
15512            
15513             ok saveAwsIp(q(example.org)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
15514              
15515             ok saveAwsDomain(q(example.org));
15516             ok awsR53a (q(XXXXX), q(www.example.org), q(22.12.232.1));
15517             ok awsR53aaaa(q(XXXXX), q(www.example.org), q([1232:1232:1232:1232:1232:1232:1232:1232:]));
15518            
15519              
15520             =head2 saveAwsDomain()
15521              
15522             Make the server at L with the given domain name the default primary server as used by all the methods whose names end in B or B. Returns the given IP address.
15523              
15524              
15525             B
15526              
15527              
15528             ok saveAwsIp(q(0.0.0.0)) eq awsIp;
15529             ok saveAwsIp(q(example.org));
15530            
15531             ok saveAwsDomain(q(example.org)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
15532              
15533             ok awsR53a (q(XXXXX), q(www.example.org), q(22.12.232.1));
15534             ok awsR53aaaa(q(XXXXX), q(www.example.org), q([1232:1232:1232:1232:1232:1232:1232:1232:]));
15535            
15536              
15537             =head2 awsMetaData($item)
15538              
15539             Get an item of meta data for the L server we are currently running on if we are running on an L server else return a blank string.
15540              
15541             Parameter Description
15542             1 $item Meta data field
15543              
15544             B
15545              
15546              
15547            
15548             ok awsMetaData(q(instance-id)) eq q(i-06a4b221b30bf7a37); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
15549              
15550            
15551              
15552             =head2 awsCurrentIp()
15553              
15554             Get the ip address of the AWS server we are currently running on if we are running on an L server else return a blank string.
15555              
15556              
15557             B
15558              
15559              
15560            
15561             awsCurrentIp; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
15562              
15563             confirmHasCommandLineCommand(q(find));
15564            
15565            
15566             ok awsCurrentIp eq q(31.41.59.26); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
15567              
15568            
15569              
15570             =head2 awsCurrentInstanceId()
15571              
15572             Get the instance id of the L server we are currently running on if we are running on an L server else return a blank string.
15573              
15574              
15575             B
15576              
15577              
15578            
15579             ok awsCurrentInstanceId eq q(i-06a4b221b30bf7a37); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
15580              
15581            
15582              
15583             =head2 awsCurrentAvailabilityZone()
15584              
15585             Get the availability zone of the L server we are currently running on if we are running on an L server else return a blank string.
15586              
15587              
15588             B
15589              
15590              
15591            
15592             ok awsCurrentAvailabilityZone eq q(us-east-2a); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
15593              
15594            
15595              
15596             =head2 awsCurrentRegion()
15597              
15598             Get the region of the L server we are currently running on if we are running on an L server else return a blank string.
15599              
15600              
15601             B
15602              
15603              
15604            
15605             ok awsCurrentRegion eq q(us-east-2); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
15606              
15607            
15608              
15609             =head2 awsCurrentInstanceType()
15610              
15611             Get the instance type of the L server if we are running on an L server else return a blank string.
15612              
15613              
15614             B
15615              
15616              
15617            
15618             ok awsCurrentInstanceType eq q(r4.4xlarge); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
15619              
15620            
15621              
15622             =head2 awsExecCli($command, %options)
15623              
15624             Execute an AWs command and return its response
15625              
15626             Parameter Description
15627             1 $command Command to execute
15628             2 %options Aws cli options
15629              
15630             B
15631              
15632              
15633            
15634             ok awsExecCli(q(aws s3 ls)) =~ m(ryffine)i; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
15635              
15636             my $p = awsExecCliJson(q(aws ec2 describe-vpcs), region=>q(us-east-1));
15637             ok $p->Vpcs->[0]->VpcId =~ m(\Avpc-)i;
15638            
15639              
15640             =head2 awsExecCliJson($command, %options)
15641              
15642             Execute an AWs command and decode the json so produced
15643              
15644             Parameter Description
15645             1 $command Command to execute
15646             2 %options Aws cli options
15647              
15648             B
15649              
15650              
15651             ok awsExecCli(q(aws s3 ls)) =~ m(ryffine)i;
15652            
15653             my $p = awsExecCliJson(q(aws ec2 describe-vpcs), region=>q(us-east-1)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
15654              
15655             ok $p->Vpcs->[0]->VpcId =~ m(\Avpc-)i;
15656            
15657              
15658             =head2 awsEc2DescribeInstances(%options)
15659              
15660             Describe the L instances running in a B<$region>.
15661              
15662             Parameter Description
15663             1 %options Options
15664              
15665             B
15666              
15667              
15668             my %options = (region => q(us-east-2), profile=>q(fmc));
15669            
15670             my $r = awsEc2DescribeInstances (%options); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
15671              
15672             my %i = awsEc2DescribeInstancesGetIPAddresses(%options);
15673             is_deeply \%i, { "i-068a7176ba9140057" => { "18.221.162.39" => 1 } };
15674            
15675              
15676             =head2 awsEc2DescribeInstancesGetIPAddresses(%options)
15677              
15678             Return a hash of {instanceId => public ip address} for all running instances on L with ip addresses.
15679              
15680             Parameter Description
15681             1 %options Options
15682              
15683             B
15684              
15685              
15686             my %options = (region => q(us-east-2), profile=>q(fmc));
15687             my $r = awsEc2DescribeInstances (%options);
15688            
15689             my %i = awsEc2DescribeInstancesGetIPAddresses(%options); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
15690              
15691             is_deeply \%i, { "i-068a7176ba9140057" => { "18.221.162.39" => 1 } };
15692            
15693              
15694             =head2 awsEc2InstanceIpAddress($instanceId, %options)
15695              
15696             Return the IP address of a named instance on L else return B.
15697              
15698             Parameter Description
15699             1 $instanceId Instance id
15700             2 %options Options
15701              
15702             B
15703              
15704              
15705            
15706             ok q(3.33.133.233) eq awsEc2InstanceIpAddress # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
15707              
15708             ("i-xxx", region => q(us-east-2), profile=>q(fmc));
15709            
15710              
15711             =head2 awsEc2CreateImage($name, %options)
15712              
15713             Create an image snap shot with the specified B<$name> of the AWS server we are currently running on if we are running on an AWS server else return false. It is safe to shut down the instance immediately after initiating the snap shot - the snap continues even though the instance has terminated.
15714              
15715             Parameter Description
15716             1 $name Image name
15717             2 %options Options
15718              
15719             B
15720              
15721              
15722            
15723             awsEc2CreateImage(q(099 Gold)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
15724              
15725            
15726              
15727             =head2 awsEc2FindImagesWithTagValue($value, %options)
15728              
15729             Find images with a tag that matches the specified regular expression B<$value>.
15730              
15731             Parameter Description
15732             1 $value Regular expression
15733             2 %options Options
15734              
15735             B
15736              
15737              
15738             is_deeply
15739            
15740             [awsEc2FindImagesWithTagValue(qr(boot)i, region=>'us-east-2', # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
15741              
15742             profile=>'fmc')],
15743             ["ami-011b4273c6123ae76"];
15744            
15745              
15746             =head2 awsEc2DescribeImages(%options)
15747              
15748             Describe images available.
15749              
15750             Parameter Description
15751             1 %options Options
15752              
15753             B
15754              
15755              
15756            
15757             awsEc2DescribeImages(region => q(us-east-2), profile=>q(fmc)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
15758              
15759            
15760              
15761             =head2 awsCurrentLinuxSpotPrices(%options)
15762              
15763             Return {instance type} = cheapest spot price in dollars per hour for the given region
15764              
15765             Parameter Description
15766             1 %options Options
15767              
15768             B
15769              
15770              
15771            
15772             awsCurrentLinuxSpotPrices(region => q(us-east-2), profile=>q(fmc)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
15773              
15774            
15775              
15776             =head2 awsEc2DescribeInstanceType($instanceType, %options)
15777              
15778             Return details of the specified instance type.
15779              
15780             Parameter Description
15781             1 $instanceType Instance type name
15782             2 %options Options
15783              
15784             B
15785              
15786              
15787            
15788             my $i = awsEc2DescribeInstanceType # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
15789              
15790             ("m4.large", region=>'us-east-2', profile=>'fmc');
15791            
15792             is_deeply $i->{VCpuInfo},
15793             {DefaultCores => 1,
15794             DefaultThreadsPerCore => 2,
15795             DefaultVCpus => 2,
15796             ValidCores => [1],
15797             ValidThreadsPerCore => [1, 2],
15798             };
15799            
15800              
15801             =head2 awsEc2ReportSpotInstancePrices($instanceTypeRe, %options)
15802              
15803             Report the prices of all the spot instances whose type matches a regular expression B<$instanceTypeRe>. The report is sorted by price in millidollars per cpu ascending.
15804              
15805             Parameter Description
15806             1 $instanceTypeRe Regular expression for instance type name
15807             2 %options Options
15808              
15809             B
15810              
15811              
15812            
15813             my $a = awsEc2ReportSpotInstancePrices # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
15814              
15815             (qr(\.metal), region=>'us-east-2', profile=>'fmc');
15816             ok $a->report eq <
15817             CPUs by price
15818            
15819             10 instances types found on 2019-12-24 at 22:53:26
15820            
15821             Cheapest Instance Type: m5.metal
15822             Price Per Cpu hour : 6.65 in millidollars per hour
15823            
15824             Column Description
15825             1 Instance_Type Instance type name
15826             2 Price Price in millidollars per hour
15827             3 CPUs Number of Cpus
15828             4 Price_per_CPU The price per CPU in millidollars per hour
15829            
15830             Instance_Type Price CPUs Price_per_CPU
15831             1 m5.metal 638 96 6.65
15832             2 r5.metal 668 96 6.97
15833             3 r5d.metal 668 96 6.97
15834             4 m5d.metal 826 96 8.61
15835             5 c5d.metal 912 96 9.50
15836             6 c5.metal 1037 96 10.81
15837             7 c5n.metal 912 72 12.67
15838             8 i3.metal 1497 72 20.80
15839             9 z1d.metal 1339 48 27.90
15840             10 i3en.metal 3254 96 33.90
15841             END
15842            
15843              
15844             =head2 awsEc2RequestSpotInstances($count, $instanceType, $ami, $price, $securityGroup, $key, %options)
15845              
15846             Request spot instances as long as they can be started within the next minute. Return a list of spot instance request ids one for each instance requested.
15847              
15848             Parameter Description
15849             1 $count Number of instances
15850             2 $instanceType Instance type
15851             3 $ami AMI
15852             4 $price Price in dollars per hour
15853             5 $securityGroup Security group
15854             6 $key Key name
15855             7 %options Options.
15856              
15857             B
15858              
15859              
15860            
15861             my $r = awsEc2RequestSpotInstances # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
15862              
15863             (2, q(t2.micro), "ami-xxx", 0.01, q(xxx), q(yyy),
15864             region=>'us-east-2', profile=>'fmc');
15865            
15866              
15867             =head2 awsEc2DescribeSpotInstances(%options)
15868              
15869             Return a hash {spot instance request => spot instance details} describing the status of active spot instances.
15870              
15871             Parameter Description
15872             1 %options Options.
15873              
15874             B
15875              
15876              
15877            
15878             my $r = awsEc2DescribeSpotInstances(region => q(us-east-2), profile=>q(fmc)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
15879              
15880            
15881              
15882             =head2 awsR53a($zone, $server, $ip, %options)
15883              
15884             Create/Update a B L record for the specified server.
15885              
15886             Parameter Description
15887             1 $zone Zone id from R53
15888             2 $server Fully qualified domain name
15889             3 $ip Ip address
15890             4 %options AWS CLI global options
15891              
15892             B
15893              
15894              
15895             ok saveAwsIp(q(0.0.0.0)) eq awsIp;
15896             ok saveAwsIp(q(example.org));
15897             ok saveAwsDomain(q(example.org));
15898            
15899             ok awsR53a (q(XXXXX), q(www.example.org), q(22.12.232.1)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
15900              
15901             ok awsR53aaaa(q(XXXXX), q(www.example.org), q([1232:1232:1232:1232:1232:1232:1232:1232:]));
15902            
15903              
15904             =head2 awsR53aaaa($zone, $server, $ip, %options)
15905              
15906             Create/Update a B L record for the specified server.
15907              
15908             Parameter Description
15909             1 $zone Zone id from R53
15910             2 $server Fully qualified domain name
15911             3 $ip Ip6 address
15912             4 %options AWS CLI global options
15913              
15914             B
15915              
15916              
15917             ok saveAwsIp(q(0.0.0.0)) eq awsIp;
15918             ok saveAwsIp(q(example.org));
15919             ok saveAwsDomain(q(example.org));
15920             ok awsR53a (q(XXXXX), q(www.example.org), q(22.12.232.1));
15921            
15922             ok awsR53aaaa(q(XXXXX), q(www.example.org), q([1232:1232:1232:1232:1232:1232:1232:1232:])); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
15923              
15924            
15925              
15926             =head2 awsEc2Tag($resource, $name, $value, %options)
15927              
15928             Tag an elastic compute resource with the supplied tags.
15929              
15930             Parameter Description
15931             1 $resource Resource
15932             2 $name Tag name
15933             3 $value Tag value
15934             4 %options Options.
15935              
15936             B
15937              
15938              
15939            
15940             awsEc2Tag # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
15941              
15942             ("i-xxxx", Name=>q(Conversion), region => q(us-east-2), profile=>q(fmc));
15943            
15944              
15945             =head2 confirmHasCommandLineCommand($cmd)
15946              
15947             Check that the specified b<$cmd> is present on the current system. Use $ENV{PATH} to add folders containing commands as necessary.
15948              
15949             Parameter Description
15950             1 $cmd Command to check for
15951              
15952             B
15953              
15954              
15955             awsCurrentIp;
15956            
15957             confirmHasCommandLineCommand(q(find)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
15958              
15959            
15960              
15961             =head2 numberOfCpus($scale)
15962              
15963             Number of cpus scaled by an optional factor - but only if you have nproc. If you do not have nproc but do have a convenient way for determining the number of cpus on your system please let me know.
15964              
15965             Parameter Description
15966             1 $scale Scale factor
15967              
15968             B
15969              
15970              
15971            
15972             ok numberOfCpus(8) >= 8, 'ddd'; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
15973              
15974            
15975              
15976             =head2 ipAddressViaArp($hostName)
15977              
15978             Get the ip address of a server on the local network by hostname via arp
15979              
15980             Parameter Description
15981             1 $hostName Host name
15982              
15983             B
15984              
15985              
15986            
15987             ipAddressViaArp(q(secarias)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
15988              
15989            
15990              
15991             =head2 parseS3BucketAndFolderName($name)
15992              
15993             Parse an L bucket/folder name into a bucket and a folder name removing any initial s3://.
15994              
15995             Parameter Description
15996             1 $name Bucket/folder name
15997              
15998             B
15999              
16000              
16001             if (1)
16002            
16003             {is_deeply [parseS3BucketAndFolderName(q(s3://bbbb/ffff/dddd/))], [qw(bbbb ffff/dddd/)], q(iii); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
16004              
16005            
16006             is_deeply [parseS3BucketAndFolderName(q(s3://bbbb/))], [qw(bbbb), q()]; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
16007              
16008            
16009             is_deeply [parseS3BucketAndFolderName(q( bbbb/))], [qw(bbbb), q()]; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
16010              
16011            
16012             is_deeply [parseS3BucketAndFolderName(q( bbbb))], [qw(bbbb), q()]; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
16013              
16014             }
16015            
16016              
16017             =head2 saveCodeToS3($saveCodeEvery, $folder, $zipFileName, $bucket, $S3Parms)
16018              
16019             Save source code every B<$saveCodeEvery> seconds by zipping folder B<$folder> to zip file B<$zipFileName> then saving this zip file in the specified L B<$bucket> using any additional L parameters in B<$S3Parms>.
16020              
16021             Parameter Description
16022             1 $saveCodeEvery Save every seconds
16023             2 $folder Folder to save
16024             3 $zipFileName Zip file name
16025             4 $bucket Bucket/key
16026             5 $S3Parms Additional S3 parameters like profile or region as a string
16027              
16028             B
16029              
16030              
16031            
16032             saveCodeToS3(1200, q(.), q(projectName), q(bucket/folder), q(--quiet)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
16033              
16034            
16035              
16036             =head2 addCertificate($file)
16037              
16038             Add a certificate to the current ssh session.
16039              
16040             Parameter Description
16041             1 $file File containing certificate
16042              
16043             B
16044              
16045              
16046            
16047             addCertificate(fpf(qw(.ssh cert))); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
16048              
16049            
16050              
16051             =head2 hostName()
16052              
16053             The name of the host we are running on.
16054              
16055              
16056             B
16057              
16058              
16059            
16060             hostName; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
16061              
16062            
16063              
16064             =head2 userId($user)
16065              
16066             Get or confirm the userid we are currently running under.
16067              
16068             Parameter Description
16069             1 $user Userid to confirm
16070              
16071             B
16072              
16073              
16074            
16075             userId; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
16076              
16077            
16078              
16079             =head2 awsTranslateText($string, $language, $cacheFolder, $Options)
16080              
16081             Translate B<$text> from English to a specified B<$language> using AWS Translate with the specified global B<$options> and return the translated string. Translations are cached in the specified B<$cacheFolder> for reuse where feasible.
16082              
16083             Parameter Description
16084             1 $string String to translate
16085             2 $language Language code
16086             3 $cacheFolder Cache folder
16087             4 $Options Aws global options string
16088              
16089             B
16090              
16091              
16092            
16093             ok awsTranslateText("Hello", "it", ".translations/") eq q(Ciao); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
16094              
16095            
16096              
16097             =head1 AWS parallel
16098              
16099             Parallel computing across multiple instances running on L.
16100              
16101             =head2 onAws()
16102              
16103             Returns 1 if we are on AWS else return 0.
16104              
16105              
16106             B
16107              
16108              
16109            
16110             ok onAws; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
16111              
16112             ok !onAwsSecondary;
16113             ok onAwsPrimary;
16114            
16115              
16116             =head2 onAwsPrimary()
16117              
16118             Return 1 if we are on L and we are on the primary session instance as defined by L, return 0 if we are on a secondary session instance, else return B if we are not on L.
16119              
16120              
16121             B
16122              
16123              
16124             ok onAws;
16125             ok !onAwsSecondary;
16126            
16127             ok onAwsPrimary; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
16128              
16129            
16130              
16131             =head2 onAwsSecondary()
16132              
16133             Return 1 if we are on L but we are not on the primary session instance as defined by L, return 0 if we are on the primary session instance, else return B if we are not on L.
16134              
16135              
16136             B
16137              
16138              
16139             ok onAws;
16140            
16141             ok !onAwsSecondary; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
16142              
16143             ok onAwsPrimary;
16144            
16145              
16146             =head2 awsParallelPrimaryInstanceId(%options)
16147              
16148             Return the instance id of the primary instance. The primary instance is the instance at L that we communicate with - it controls all the secondary instances that form part of the parallel session. The primary instance is located by finding the first running instance in instance Id order whose Name tag contains the word I. If no running instance has been identified as the primary instance, then the first viable instance is made the primary. The ip address of the primary is recorded in F so that it can be quickly reused by L, L, L etc. Returns the instanceId of the primary instance or B if no suitable instance exists.
16149              
16150             Parameter Description
16151             1 %options Options
16152              
16153             B
16154              
16155              
16156            
16157             ok "i-xxx" eq awsParallelPrimaryInstanceId # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
16158              
16159             (region => q(us-east-2), profile=>q(fmc));
16160            
16161              
16162             =head2 awsParallelSpreadFolder($folder, %options)
16163              
16164             On L: copies a specified B<$folder> from the primary instance, see: L, in parallel, to all the secondary instances in the session. If running locally: copies the specified folder to all L session instances both primary and secondary.
16165              
16166             Parameter Description
16167             1 $folder Fully qualified folder name
16168             2 %options Options
16169              
16170             B
16171              
16172              
16173             my $d = temporaryFolder;
16174             my ($f1, $f2) = map {fpe($d, $_, q(txt))} 1..2;
16175             my $files = {$f1 => "1111", $f2 => "2222"};
16176            
16177             writeFiles($files);
16178            
16179             awsParallelSpreadFolder($d); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
16180              
16181             clearFolder($d, 3);
16182            
16183             awsParallelGatherFolder($d);
16184             my $r = readFiles($d);
16185             is_deeply $files, $r;
16186             clearFolder($d, 3);
16187            
16188              
16189             =head2 awsParallelGatherFolder($folder, %options)
16190              
16191             On L: merges all the files in the specified B<$folder> on each secondary instance to the corresponding folder on the primary instance in parallel. If running locally: merges all the files in the specified folder on each L session instance (primary and secondary) to the corresponding folder on the local machine. The folder merges are done in parallel which makes it impossible to rely on the order of the merges.
16192              
16193             Parameter Description
16194             1 $folder Fully qualified folder name
16195             2 %options Options
16196              
16197             B
16198              
16199              
16200             my $d = temporaryFolder;
16201             my ($f1, $f2) = map {fpe($d, $_, q(txt))} 1..2;
16202             my $files = {$f1 => "1111", $f2 => "2222"};
16203            
16204             writeFiles($files);
16205             awsParallelSpreadFolder($d);
16206             clearFolder($d, 3);
16207            
16208            
16209             awsParallelGatherFolder($d); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
16210              
16211             my $r = readFiles($d);
16212             is_deeply $files, $r;
16213             clearFolder($d, 3);
16214            
16215              
16216             =head2 awsParallelPrimaryIpAddress(%options)
16217              
16218             Return the IP addresses of any primary instance on L.
16219              
16220             Parameter Description
16221             1 %options Options
16222              
16223             B
16224              
16225              
16226            
16227             ok awsParallelPrimaryIpAddress eq q(3.1.4.4); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
16228              
16229            
16230             is_deeply [awsParallelSecondaryIpAddresses], [qw(3.1.4.5 3.1.4.6)];
16231            
16232             is_deeply [awsParallelIpAddresses], [qw(3.1.4.4 3.1.4.5 3.1.4.6)];
16233            
16234              
16235             =head2 awsParallelSecondaryIpAddresses(%options)
16236              
16237             Return a list containing the IP addresses of any secondary instances on L.
16238              
16239             Parameter Description
16240             1 %options Options
16241              
16242             B
16243              
16244              
16245             ok awsParallelPrimaryIpAddress eq q(3.1.4.4);
16246            
16247            
16248             is_deeply [awsParallelSecondaryIpAddresses], [qw(3.1.4.5 3.1.4.6)]; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
16249              
16250            
16251             is_deeply [awsParallelIpAddresses], [qw(3.1.4.4 3.1.4.5 3.1.4.6)];
16252            
16253              
16254             =head2 awsParallelIpAddresses(%options)
16255              
16256             Return the IP addresses of all the L session instances.
16257              
16258             Parameter Description
16259             1 %options Options
16260              
16261             B
16262              
16263              
16264             ok awsParallelPrimaryIpAddress eq q(3.1.4.4);
16265            
16266             is_deeply [awsParallelSecondaryIpAddresses], [qw(3.1.4.5 3.1.4.6)];
16267            
16268            
16269             is_deeply [awsParallelIpAddresses], [qw(3.1.4.4 3.1.4.5 3.1.4.6)]; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
16270              
16271            
16272              
16273             =head2 getCodeContext($sub)
16274              
16275             Recreate the code context for a referenced sub
16276              
16277             Parameter Description
16278             1 $sub Sub reference
16279              
16280             B
16281              
16282              
16283            
16284             ok getCodeContext(\&getCodeContext) =~ m(use strict)ims; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
16285              
16286            
16287              
16288             =head2 awsParallelProcessFiles($userData, $parallel, $results, $files, %options)
16289              
16290             Process files in parallel across multiple L instances if available or in series if not. The data located by B<$userData> is transferred from the primary instance, as determined by L, to all the secondary instances. B<$parallel> contains a reference to a sub, parameterized by array @_ = (a copy of the user data, the name of the file to process), which will be executed upon each session instance including the primary instance to update $userData. B<$results> contains a reference to a sub, parameterized by array @_ = (the user data, an array of results returned by each execution of $parallel), that will be called on the primary instance to process the results folders from each instance once their results folders have been copied back and merged into the results folder of the primary instance. $results should update its copy of $userData with the information received from each instance. B<$files> is a reference to an array of the files to be processed: each file will be copied from the primary instance to each of the secondary instances before parallel processing starts. B<%options> contains any parameters needed to interact with L via the L. The returned result is that returned by sub $results.
16291              
16292             Parameter Description
16293             1 $userData User data or undef
16294             2 $parallel Parallel sub reference
16295             3 $results Series sub reference
16296             4 $files [files to process]
16297             5 %options Aws cli options.
16298              
16299             B
16300              
16301              
16302             my $N = 2001; # Number of files to process
16303             my $options = q(region => q(us-east-2), profile=>q(fmc)); # Aws cli options
16304             my %options = eval "($options)";
16305            
16306             for my $dir(q(/home/phil/perl/cpan/DataTableText/lib/Data/Table/), # Folders we will need on aws
16307             q(/home/phil/.aws/))
16308             {awsParallelSpreadFolder($dir, %options);
16309             }
16310            
16311             my $d = temporaryFolder; # Create a temporary folder
16312             my $resultsFile = fpe($d, qw(results data)); # Save results in this temporary file
16313            
16314             if (my $r = execPerlOnRemote(join "
16315             ", # Execute some code on a server
16316             getCodeContext(\&awsParallelProcessFilesTestParallel), # Get code context of the sub we want to call.
16317             <
16318             use Data::Table::Text qw(:all);
16319            
16320            
16321             my \$r = awsParallelProcessFiles # Process files on multiple L instances in parallel # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
16322              
16323             ({file=>4, time=>timeStamp}, # User data
16324             \\\&Data::Table::Text::awsParallelProcessFilesTestParallel, # Reference to code to execute in parallel on each session instance
16325             \\\&Data::Table::Text::awsParallelProcessFilesTestResults, # Reference to code to execute in series to merge the results of each parallel computation
16326             [map {writeFile(fpe(q($d), \$_, qw(txt)), \$_)} 1..$N], # Files to process
16327             $options); # Aws cli options as we will be running on Aws
16328            
16329             storeFile(q($resultsFile), \$r); # Save results in a file
16330            
16331             SESSIONLEADER
16332            
16333             {copyFileFromRemote($resultsFile); # Retrieve user data
16334            
16335             my $userData = retrieveFile($resultsFile); # Recover user data
16336             my @i = awsParallelSecondaryIpAddresses(%options); # Ip addresses of secondary instances
16337             my @I = keys $userData->{ip}->%*;
16338             is_deeply [sort @i], [sort @I]; # Each secondary ip address was used
16339            
16340             ok $userData->{file} == 4; # Prove we can pass data in and get it back
16341             ok $userData->{merge} == 1 + @i, 'ii'; # Number of merges
16342            
16343             my %f; my %i; # Files processed on each ip
16344             for my $i(sort keys $userData->{ipFile}->%*) # Ip
16345             {for my $f(sort keys $userData->{ipFile}{$i}->%*) # File
16346             {$f{fn($f)}++; # Files processed
16347             $i{$i}++; # Count files on each ip
16348             }
16349             }
16350            
16351             is_deeply \%f, {map {$_=>1} 1..$N}; # Check each file was processed
16352            
16353             if (1)
16354             {my @rc; my @ra; # Range of number of files processed on each ip - computed, actually counted
16355             my $l = $N/@i-1; # Lower limit of number of files per IP address
16356             my $h = $N/@i+1; # Upper limit of number of files per IP address
16357             for my $i(keys %i)
16358             {my $nc = $i{$i}; # Number of files processed on this ip - computed
16359             my $na = $userData->{ip}{$i}; # Number of files processed on this ip - actually counted
16360             push @rc, ($nc >= $l and $nc <= $h) ? 1 : 0; # 1 - in range, 0 - out of range
16361             push @ra, ($na >= $l and $na <= $h) ? 1 : 0; # 1 - in range, 0 - out of range
16362             }
16363             ok @i == grep {$_} @ra; # Check each ip processed the expected number of files
16364             ok @i == grep {$_} @rc;
16365             }
16366            
16367             ok $userData->{files}{&fpe($d, qw(4 txt))} eq # Check the computed MD5 sum for the specified file
16368             q(a87ff679a2f3e71d9181a67b7542122c);
16369             }
16370            
16371             if (0) # Process files in series on local machine
16372             {my $N = 42;
16373             my $d = temporaryFolder;
16374            
16375            
16376             my $r = awsParallelProcessFiles # Process files in series on local machine # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
16377              
16378             ({file => 4}, # User data
16379             \&Data::Table::Text::awsParallelProcessFilesTestParallel, # Code to execute on each session instance including the session leader written as a string because it has to be shipped to each instance
16380             \&Data::Table::Text::awsParallelProcessFilesTestResults, # Code to execute in series on the session leader to analyze the results of the parallel runs
16381             [map {writeFile(fpe($d, $_, qw(txt)), $_)} 1..$N], # Files to process
16382             ()); # No Aws cli options as we are running locally
16383            
16384             ok $r->{file} == 4, 'aaa'; # Prove we can pass data in and get it back
16385             ok $r->{merge} == 1, 'bbb'; # Only one merge as we are running locally
16386            
16387             ok $r->{ip}{localHost} == $N, 'ccc'; # Number of files processed locally
16388             ok keys($r->{files}->%*) == $N; # Number of files processed
16389             ok $r->{files}{fpe($d, qw(4 txt))} eq q(a87ff679a2f3e71d9181a67b7542122c); # Check the computed MD5 sum for the specified file
16390            
16391             clearFolder($d, $N+2);
16392             }
16393            
16394              
16395             =head1 S3
16396              
16397             Work with S3 as if it were a file system.
16398              
16399             =head2 s3ListFilesAndSizes($folderOrFile, %options)
16400              
16401             Return {file=>size} for all the files in a specified B<$folderOrFile> on S3 using the specified B<%options> if any.
16402              
16403             Parameter Description
16404             1 $folderOrFile Source on S3 - which will be truncated to a folder name
16405             2 %options Options
16406              
16407             B
16408              
16409              
16410             my %options = (profile => q(fmc));
16411            
16412             s3DownloadFolder
16413             (q(s3://bucket/folder/), q(home/phil/s3/folder/), %options, delete=>1);
16414            
16415             s3ZipFolder ( q(home/phil/s3/folder/) => q(s3://bucket/folder/), %options);
16416            
16417             s3ZipFolders({q(home/phil/s3/folder/) => q(s3://bucket/folder/)}, %options);
16418            
16419             is_deeply
16420            
16421             {s3ListFilesAndSizes(q(s3://salesforce.dita/originals4/images), %options) # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
16422              
16423             },
16424             {"s3://salesforce.dita/originals4/images/business_plan_sections.png" =>
16425             ["originals4/images/business_plan_sections.png",
16426             112525,
16427             "2019-08-13",
16428             "20:01:10",
16429             ],
16430             "s3://salesforce.dita/originals4/images/non-referenced.png" =>
16431             ["originals4/images/non-referenced.png",
16432             19076,
16433             "2019-08-20",
16434             "01:25:04",
16435             ],
16436             };
16437            
16438             my $data = q(0123456789);
16439             my $file = q(s3://salesforce.dita/zzz/111.txt);
16440            
16441             if (1)
16442             { s3WriteString($file, $data, %options);
16443             my $r = s3ReadString($file, %options);
16444             ok $r eq $data;
16445             }
16446            
16447             if (1)
16448             {my @r = s3FileExists($file, %options);
16449             ok $r[0] eq "zzz/111.txt";
16450             ok $r[1] == 10;
16451             }
16452            
16453             if (1)
16454             {my $d = $data x 2;
16455             my $f = writeFile(undef, $d);
16456            
16457             s3WriteFile($file, $f, %options);
16458             unlink $f;
16459             s3ReadFile ($file, $f, %options);
16460             ok readFile($f) eq $d;
16461             unlink $f;
16462             }
16463            
16464              
16465             =head2 s3FileExists($file, %options)
16466              
16467             Return (name, size, date, time) for a B<$file> that exists on S3 else () using the specified B<%options> if any.
16468              
16469             Parameter Description
16470             1 $file File on S3 - which will be truncated to a folder name
16471             2 %options Options
16472              
16473             B
16474              
16475              
16476             my %options = (profile => q(fmc));
16477            
16478             s3DownloadFolder
16479             (q(s3://bucket/folder/), q(home/phil/s3/folder/), %options, delete=>1);
16480            
16481             s3ZipFolder ( q(home/phil/s3/folder/) => q(s3://bucket/folder/), %options);
16482            
16483             s3ZipFolders({q(home/phil/s3/folder/) => q(s3://bucket/folder/)}, %options);
16484            
16485             is_deeply
16486             {s3ListFilesAndSizes(q(s3://salesforce.dita/originals4/images), %options)
16487             },
16488             {"s3://salesforce.dita/originals4/images/business_plan_sections.png" =>
16489             ["originals4/images/business_plan_sections.png",
16490             112525,
16491             "2019-08-13",
16492             "20:01:10",
16493             ],
16494             "s3://salesforce.dita/originals4/images/non-referenced.png" =>
16495             ["originals4/images/non-referenced.png",
16496             19076,
16497             "2019-08-20",
16498             "01:25:04",
16499             ],
16500             };
16501            
16502             my $data = q(0123456789);
16503             my $file = q(s3://salesforce.dita/zzz/111.txt);
16504            
16505             if (1)
16506             { s3WriteString($file, $data, %options);
16507             my $r = s3ReadString($file, %options);
16508             ok $r eq $data;
16509             }
16510            
16511             if (1)
16512            
16513             {my @r = s3FileExists($file, %options); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
16514              
16515             ok $r[0] eq "zzz/111.txt";
16516             ok $r[1] == 10;
16517             }
16518            
16519             if (1)
16520             {my $d = $data x 2;
16521             my $f = writeFile(undef, $d);
16522            
16523             s3WriteFile($file, $f, %options);
16524             unlink $f;
16525             s3ReadFile ($file, $f, %options);
16526             ok readFile($f) eq $d;
16527             unlink $f;
16528             }
16529            
16530              
16531             =head2 s3WriteFile($fileS3, $fileLocal, %options)
16532              
16533             Write to a file B<$fileS3> on S3 the contents of a local file B<$fileLocal> using the specified B<%options> if any. $fileLocal will be removed if %options contains a key cleanUp with a true value.
16534              
16535             Parameter Description
16536             1 $fileS3 File to write to on S3
16537             2 $fileLocal String to write into file
16538             3 %options Options
16539              
16540             B
16541              
16542              
16543             my %options = (profile => q(fmc));
16544            
16545             s3DownloadFolder
16546             (q(s3://bucket/folder/), q(home/phil/s3/folder/), %options, delete=>1);
16547            
16548             s3ZipFolder ( q(home/phil/s3/folder/) => q(s3://bucket/folder/), %options);
16549            
16550             s3ZipFolders({q(home/phil/s3/folder/) => q(s3://bucket/folder/)}, %options);
16551            
16552             is_deeply
16553             {s3ListFilesAndSizes(q(s3://salesforce.dita/originals4/images), %options)
16554             },
16555             {"s3://salesforce.dita/originals4/images/business_plan_sections.png" =>
16556             ["originals4/images/business_plan_sections.png",
16557             112525,
16558             "2019-08-13",
16559             "20:01:10",
16560             ],
16561             "s3://salesforce.dita/originals4/images/non-referenced.png" =>
16562             ["originals4/images/non-referenced.png",
16563             19076,
16564             "2019-08-20",
16565             "01:25:04",
16566             ],
16567             };
16568            
16569             my $data = q(0123456789);
16570             my $file = q(s3://salesforce.dita/zzz/111.txt);
16571            
16572             if (1)
16573             { s3WriteString($file, $data, %options);
16574             my $r = s3ReadString($file, %options);
16575             ok $r eq $data;
16576             }
16577            
16578             if (1)
16579             {my @r = s3FileExists($file, %options);
16580             ok $r[0] eq "zzz/111.txt";
16581             ok $r[1] == 10;
16582             }
16583            
16584             if (1)
16585             {my $d = $data x 2;
16586             my $f = writeFile(undef, $d);
16587            
16588            
16589             s3WriteFile($file, $f, %options); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
16590              
16591             unlink $f;
16592             s3ReadFile ($file, $f, %options);
16593             ok readFile($f) eq $d;
16594             unlink $f;
16595             }
16596            
16597              
16598             =head2 s3WriteString($file, $string, %options)
16599              
16600             Write to a B<$file> on S3 the contents of B<$string> using the specified B<%options> if any.
16601              
16602             Parameter Description
16603             1 $file File to write to on S3
16604             2 $string String to write into file
16605             3 %options Options
16606              
16607             B
16608              
16609              
16610             my %options = (profile => q(fmc));
16611            
16612             s3DownloadFolder
16613             (q(s3://bucket/folder/), q(home/phil/s3/folder/), %options, delete=>1);
16614            
16615             s3ZipFolder ( q(home/phil/s3/folder/) => q(s3://bucket/folder/), %options);
16616            
16617             s3ZipFolders({q(home/phil/s3/folder/) => q(s3://bucket/folder/)}, %options);
16618            
16619             is_deeply
16620             {s3ListFilesAndSizes(q(s3://salesforce.dita/originals4/images), %options)
16621             },
16622             {"s3://salesforce.dita/originals4/images/business_plan_sections.png" =>
16623             ["originals4/images/business_plan_sections.png",
16624             112525,
16625             "2019-08-13",
16626             "20:01:10",
16627             ],
16628             "s3://salesforce.dita/originals4/images/non-referenced.png" =>
16629             ["originals4/images/non-referenced.png",
16630             19076,
16631             "2019-08-20",
16632             "01:25:04",
16633             ],
16634             };
16635            
16636             my $data = q(0123456789);
16637             my $file = q(s3://salesforce.dita/zzz/111.txt);
16638            
16639             if (1)
16640            
16641             { s3WriteString($file, $data, %options); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
16642              
16643             my $r = s3ReadString($file, %options);
16644             ok $r eq $data;
16645             }
16646            
16647             if (1)
16648             {my @r = s3FileExists($file, %options);
16649             ok $r[0] eq "zzz/111.txt";
16650             ok $r[1] == 10;
16651             }
16652            
16653             if (1)
16654             {my $d = $data x 2;
16655             my $f = writeFile(undef, $d);
16656            
16657             s3WriteFile($file, $f, %options);
16658             unlink $f;
16659             s3ReadFile ($file, $f, %options);
16660             ok readFile($f) eq $d;
16661             unlink $f;
16662             }
16663            
16664              
16665             =head2 s3ReadFile($file, $local, %options)
16666              
16667             Read from a B<$file> on S3 and write the contents to a local file B<$local> using the specified B<%options> if any. Any pre existing version of the local file $local will be deleted. Returns whether the local file exists after completion of the download.
16668              
16669             Parameter Description
16670             1 $file File to read from on S3
16671             2 $local Local file to write to
16672             3 %options Options
16673              
16674             B
16675              
16676              
16677             my %options = (profile => q(fmc));
16678            
16679             s3DownloadFolder
16680             (q(s3://bucket/folder/), q(home/phil/s3/folder/), %options, delete=>1);
16681            
16682             s3ZipFolder ( q(home/phil/s3/folder/) => q(s3://bucket/folder/), %options);
16683            
16684             s3ZipFolders({q(home/phil/s3/folder/) => q(s3://bucket/folder/)}, %options);
16685            
16686             is_deeply
16687             {s3ListFilesAndSizes(q(s3://salesforce.dita/originals4/images), %options)
16688             },
16689             {"s3://salesforce.dita/originals4/images/business_plan_sections.png" =>
16690             ["originals4/images/business_plan_sections.png",
16691             112525,
16692             "2019-08-13",
16693             "20:01:10",
16694             ],
16695             "s3://salesforce.dita/originals4/images/non-referenced.png" =>
16696             ["originals4/images/non-referenced.png",
16697             19076,
16698             "2019-08-20",
16699             "01:25:04",
16700             ],
16701             };
16702            
16703             my $data = q(0123456789);
16704             my $file = q(s3://salesforce.dita/zzz/111.txt);
16705            
16706             if (1)
16707             { s3WriteString($file, $data, %options);
16708             my $r = s3ReadString($file, %options);
16709             ok $r eq $data;
16710             }
16711            
16712             if (1)
16713             {my @r = s3FileExists($file, %options);
16714             ok $r[0] eq "zzz/111.txt";
16715             ok $r[1] == 10;
16716             }
16717            
16718             if (1)
16719             {my $d = $data x 2;
16720             my $f = writeFile(undef, $d);
16721            
16722             s3WriteFile($file, $f, %options);
16723             unlink $f;
16724            
16725             s3ReadFile ($file, $f, %options); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
16726              
16727             ok readFile($f) eq $d;
16728             unlink $f;
16729             }
16730            
16731              
16732             =head2 s3ReadString($file, %options)
16733              
16734             Read from a B<$file> on S3 and return the contents as a string using specified B<%options> if any. Any pre existing version of $local will be deleted. Returns whether the local file exists after completion of the download.
16735              
16736             Parameter Description
16737             1 $file File to read from on S3
16738             2 %options Options
16739              
16740             B
16741              
16742              
16743             my %options = (profile => q(fmc));
16744            
16745             s3DownloadFolder
16746             (q(s3://bucket/folder/), q(home/phil/s3/folder/), %options, delete=>1);
16747            
16748             s3ZipFolder ( q(home/phil/s3/folder/) => q(s3://bucket/folder/), %options);
16749            
16750             s3ZipFolders({q(home/phil/s3/folder/) => q(s3://bucket/folder/)}, %options);
16751            
16752             is_deeply
16753             {s3ListFilesAndSizes(q(s3://salesforce.dita/originals4/images), %options)
16754             },
16755             {"s3://salesforce.dita/originals4/images/business_plan_sections.png" =>
16756             ["originals4/images/business_plan_sections.png",
16757             112525,
16758             "2019-08-13",
16759             "20:01:10",
16760             ],
16761             "s3://salesforce.dita/originals4/images/non-referenced.png" =>
16762             ["originals4/images/non-referenced.png",
16763             19076,
16764             "2019-08-20",
16765             "01:25:04",
16766             ],
16767             };
16768            
16769             my $data = q(0123456789);
16770             my $file = q(s3://salesforce.dita/zzz/111.txt);
16771            
16772             if (1)
16773             { s3WriteString($file, $data, %options);
16774            
16775             my $r = s3ReadString($file, %options); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
16776              
16777             ok $r eq $data;
16778             }
16779            
16780             if (1)
16781             {my @r = s3FileExists($file, %options);
16782             ok $r[0] eq "zzz/111.txt";
16783             ok $r[1] == 10;
16784             }
16785            
16786             if (1)
16787             {my $d = $data x 2;
16788             my $f = writeFile(undef, $d);
16789            
16790             s3WriteFile($file, $f, %options);
16791             unlink $f;
16792             s3ReadFile ($file, $f, %options);
16793             ok readFile($f) eq $d;
16794             unlink $f;
16795             }
16796            
16797              
16798             =head2 s3DownloadFolder($folder, $local, %options)
16799              
16800             Download a specified B<$folder> on S3 to a B<$local> folder using the specified B<%options> if any. Any existing data in the $local folder will be will be deleted if delete=>1 is specified as an option. Returns B else the name of the B<$local> on success.
16801              
16802             Parameter Description
16803             1 $folder Folder to read from on S3
16804             2 $local Local folder to write to
16805             3 %options Options
16806              
16807             B
16808              
16809              
16810             my %options = (profile => q(fmc));
16811            
16812            
16813             s3DownloadFolder # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
16814              
16815             (q(s3://bucket/folder/), q(home/phil/s3/folder/), %options, delete=>1);
16816            
16817             s3ZipFolder ( q(home/phil/s3/folder/) => q(s3://bucket/folder/), %options);
16818            
16819             s3ZipFolders({q(home/phil/s3/folder/) => q(s3://bucket/folder/)}, %options);
16820            
16821             is_deeply
16822             {s3ListFilesAndSizes(q(s3://salesforce.dita/originals4/images), %options)
16823             },
16824             {"s3://salesforce.dita/originals4/images/business_plan_sections.png" =>
16825             ["originals4/images/business_plan_sections.png",
16826             112525,
16827             "2019-08-13",
16828             "20:01:10",
16829             ],
16830             "s3://salesforce.dita/originals4/images/non-referenced.png" =>
16831             ["originals4/images/non-referenced.png",
16832             19076,
16833             "2019-08-20",
16834             "01:25:04",
16835             ],
16836             };
16837            
16838             my $data = q(0123456789);
16839             my $file = q(s3://salesforce.dita/zzz/111.txt);
16840            
16841             if (1)
16842             { s3WriteString($file, $data, %options);
16843             my $r = s3ReadString($file, %options);
16844             ok $r eq $data;
16845             }
16846            
16847             if (1)
16848             {my @r = s3FileExists($file, %options);
16849             ok $r[0] eq "zzz/111.txt";
16850             ok $r[1] == 10;
16851             }
16852            
16853             if (1)
16854             {my $d = $data x 2;
16855             my $f = writeFile(undef, $d);
16856            
16857             s3WriteFile($file, $f, %options);
16858             unlink $f;
16859             s3ReadFile ($file, $f, %options);
16860             ok readFile($f) eq $d;
16861             unlink $f;
16862             }
16863            
16864              
16865             =head2 s3ZipFolder($source, $target, %options)
16866              
16867             Zip the specified B<$source> folder and write it to the named B<$target> file on S3.
16868              
16869             Parameter Description
16870             1 $source Source folder
16871             2 $target Target file on S3
16872             3 %options S3 options
16873              
16874             B
16875              
16876              
16877            
16878             s3ZipFolder(q(home/phil/r/), q(s3://bucket/r.zip)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
16879              
16880            
16881             my %options = (profile => q(fmc));
16882            
16883             s3DownloadFolder
16884             (q(s3://bucket/folder/), q(home/phil/s3/folder/), %options, delete=>1);
16885            
16886            
16887             s3ZipFolder ( q(home/phil/s3/folder/) => q(s3://bucket/folder/), %options); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
16888              
16889            
16890             s3ZipFolders({q(home/phil/s3/folder/) => q(s3://bucket/folder/)}, %options);
16891            
16892             is_deeply
16893             {s3ListFilesAndSizes(q(s3://salesforce.dita/originals4/images), %options)
16894             },
16895             {"s3://salesforce.dita/originals4/images/business_plan_sections.png" =>
16896             ["originals4/images/business_plan_sections.png",
16897             112525,
16898             "2019-08-13",
16899             "20:01:10",
16900             ],
16901             "s3://salesforce.dita/originals4/images/non-referenced.png" =>
16902             ["originals4/images/non-referenced.png",
16903             19076,
16904             "2019-08-20",
16905             "01:25:04",
16906             ],
16907             };
16908            
16909             my $data = q(0123456789);
16910             my $file = q(s3://salesforce.dita/zzz/111.txt);
16911            
16912             if (1)
16913             { s3WriteString($file, $data, %options);
16914             my $r = s3ReadString($file, %options);
16915             ok $r eq $data;
16916             }
16917            
16918             if (1)
16919             {my @r = s3FileExists($file, %options);
16920             ok $r[0] eq "zzz/111.txt";
16921             ok $r[1] == 10;
16922             }
16923            
16924             if (1)
16925             {my $d = $data x 2;
16926             my $f = writeFile(undef, $d);
16927            
16928             s3WriteFile($file, $f, %options);
16929             unlink $f;
16930             s3ReadFile ($file, $f, %options);
16931             ok readFile($f) eq $d;
16932             unlink $f;
16933             }
16934            
16935              
16936             =head2 s3ZipFolders($map, %options)
16937              
16938             Zip local folders and upload them to S3 in parallel. B<$map> maps source folder names on the local machine to target folders on S3. B<%options> contains any additional L cli options.
16939              
16940             Parameter Description
16941             1 $map Source folder to S3 mapping
16942             2 %options S3 options
16943              
16944             B
16945              
16946              
16947             my %options = (profile => q(fmc));
16948            
16949             s3DownloadFolder
16950             (q(s3://bucket/folder/), q(home/phil/s3/folder/), %options, delete=>1);
16951            
16952             s3ZipFolder ( q(home/phil/s3/folder/) => q(s3://bucket/folder/), %options);
16953            
16954            
16955             s3ZipFolders({q(home/phil/s3/folder/) => q(s3://bucket/folder/)}, %options); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
16956              
16957            
16958             is_deeply
16959             {s3ListFilesAndSizes(q(s3://salesforce.dita/originals4/images), %options)
16960             },
16961             {"s3://salesforce.dita/originals4/images/business_plan_sections.png" =>
16962             ["originals4/images/business_plan_sections.png",
16963             112525,
16964             "2019-08-13",
16965             "20:01:10",
16966             ],
16967             "s3://salesforce.dita/originals4/images/non-referenced.png" =>
16968             ["originals4/images/non-referenced.png",
16969             19076,
16970             "2019-08-20",
16971             "01:25:04",
16972             ],
16973             };
16974            
16975             my $data = q(0123456789);
16976             my $file = q(s3://salesforce.dita/zzz/111.txt);
16977            
16978             if (1)
16979             { s3WriteString($file, $data, %options);
16980             my $r = s3ReadString($file, %options);
16981             ok $r eq $data;
16982             }
16983            
16984             if (1)
16985             {my @r = s3FileExists($file, %options);
16986             ok $r[0] eq "zzz/111.txt";
16987             ok $r[1] == 10;
16988             }
16989            
16990             if (1)
16991             {my $d = $data x 2;
16992             my $f = writeFile(undef, $d);
16993            
16994             s3WriteFile($file, $f, %options);
16995             unlink $f;
16996             s3ReadFile ($file, $f, %options);
16997             ok readFile($f) eq $d;
16998             unlink $f;
16999             }
17000            
17001              
17002             =head1 GitHub
17003              
17004             Simple interactions with L - for more complex interactions please use L.
17005              
17006             =head2 downloadGitHubPublicRepo($user, $repo)
17007              
17008             Get the contents of a public repo on GitHub and place them in a temporary folder whose name is returned to the caller or confess if no such repo exists.
17009              
17010             Parameter Description
17011             1 $user GitHub user
17012             2 $repo GitHub repo
17013              
17014             B
17015              
17016              
17017            
17018             downloadGitHubPublicRepo(q(philiprbrenan), q(psr)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
17019              
17020            
17021              
17022             =head2 downloadGitHubPublicRepoFile($user, $repo, $file)
17023              
17024             Get the contents of a B<$user> B<$repo> B<$file> from a public repo on GitHub and return them as a string.
17025              
17026             Parameter Description
17027             1 $user GitHub user
17028             2 $repo GitHub repository
17029             3 $file File name in repository
17030              
17031             B
17032              
17033              
17034            
17035             ok &downloadGitHubPublicRepoFile(qw(philiprbrenan pleaseChangeDita index.html)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
17036              
17037            
17038              
17039             =head1 Processes
17040              
17041             Start processes, wait for them to terminate and retrieve their results
17042              
17043             =head2 startProcess($sub, $pids, $maximum)
17044              
17045             Start new processes while the number of child processes recorded in B<%$pids> is less than the specified B<$maximum>. Use L to wait for all these processes to finish.
17046              
17047             Parameter Description
17048             1 $sub Sub to start
17049             2 $pids Hash in which to record the process ids
17050             3 $maximum Maximum number of processes to run at a time
17051              
17052             B
17053              
17054              
17055             my %pids;
17056            
17057             sub{startProcess {} %pids, 1; ok 1 >= keys %pids}->() for 1..8; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
17058              
17059             waitForAllStartedProcessesToFinish(%pids);
17060             ok !keys(%pids)
17061            
17062              
17063             =head2 waitForAllStartedProcessesToFinish($pids)
17064              
17065             Wait until all the processes started by L have finished.
17066              
17067             Parameter Description
17068             1 $pids Hash of started process ids
17069              
17070             B
17071              
17072              
17073             my %pids;
17074             sub{startProcess {} %pids, 1; ok 1 >= keys %pids}->() for 1..8;
17075            
17076             waitForAllStartedProcessesToFinish(%pids); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
17077              
17078             ok !keys(%pids)
17079            
17080              
17081             =head2 newProcessStarter($maximumNumberOfProcesses, %options)
17082              
17083             Create a new L with which to start parallel processes up to a specified B<$maximumNumberOfProcesses> maximum number of parallel processes at a time, wait for all the started processes to finish and then optionally retrieve their saved results as an array from the folder named by B<$transferArea>.
17084              
17085             Parameter Description
17086             1 $maximumNumberOfProcesses Maximum number of processes to start
17087             2 %options Options
17088              
17089             B
17090              
17091              
17092             if (1)
17093             {my $N = 100;
17094             my $l = q(logFile.txt);
17095             unlink $l;
17096            
17097             my $s = newProcessStarter(4); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
17098              
17099             $s->processingTitle = q(Test processes);
17100             $s->totalToBeStarted = $N;
17101             $s->processingLogFile = $l;
17102            
17103             for my $i(1..$N)
17104             {Data::Table::Text::Starter::start($s, sub{$i*$i});
17105             }
17106            
17107             is_deeply
17108             [sort {$a <=> $b} Data::Table::Text::Starter::finish($s)],
17109             [map {$_**2} 1..$N];
17110            
17111             ok readFile($l) =~ m(Finished $N processes for: Test processes)s;
17112             clearFolder($s->transferArea, 1e3);
17113             unlink $l;
17114             }
17115            
17116              
17117             =head2 Data::Table::Text::Starter::start($starter, $sub)
17118              
17119             Start a new process to run the specified B<$sub>.
17120              
17121             Parameter Description
17122             1 $starter Starter
17123             2 $sub Sub to be run.
17124              
17125             B
17126              
17127              
17128             if (1)
17129             {my $N = 100;
17130             my $l = q(logFile.txt);
17131             unlink $l;
17132             my $s = newProcessStarter(4);
17133             $s->processingTitle = q(Test processes);
17134             $s->totalToBeStarted = $N;
17135             $s->processingLogFile = $l;
17136            
17137             for my $i(1..$N)
17138            
17139             {Data::Table::Text::Starter::start($s, sub{$i*$i}); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
17140              
17141             }
17142            
17143             is_deeply
17144             [sort {$a <=> $b} Data::Table::Text::Starter::finish($s)],
17145             [map {$_**2} 1..$N];
17146            
17147             ok readFile($l) =~ m(Finished $N processes for: Test processes)s;
17148             clearFolder($s->transferArea, 1e3);
17149             unlink $l;
17150             }
17151            
17152              
17153             =head2 Data::Table::Text::Starter::finish($starter)
17154              
17155             Wait for all started processes to finish and return their results as an array.
17156              
17157             Parameter Description
17158             1 $starter Starter
17159              
17160             B
17161              
17162              
17163             if (1)
17164             {my $N = 100;
17165             my $l = q(logFile.txt);
17166             unlink $l;
17167             my $s = newProcessStarter(4);
17168             $s->processingTitle = q(Test processes);
17169             $s->totalToBeStarted = $N;
17170             $s->processingLogFile = $l;
17171            
17172             for my $i(1..$N)
17173             {Data::Table::Text::Starter::start($s, sub{$i*$i});
17174             }
17175            
17176             is_deeply
17177            
17178             [sort {$a <=> $b} Data::Table::Text::Starter::finish($s)], # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
17179              
17180             [map {$_**2} 1..$N];
17181            
17182             ok readFile($l) =~ m(Finished $N processes for: Test processes)s;
17183             clearFolder($s->transferArea, 1e3);
17184             unlink $l;
17185             }
17186            
17187              
17188             =head2 squareArray(@array)
17189              
17190             Create a two dimensional square array from a one dimensional linear array.
17191              
17192             Parameter Description
17193             1 @array Array
17194              
17195             B
17196              
17197              
17198            
17199             is_deeply [squareArray @{[1..4]} ], [[1, 2], [3, 4]]; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
17200              
17201            
17202             is_deeply [squareArray @{[1..22]}], # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
17203              
17204             [[1 .. 5], [6 .. 10], [11 .. 15], [16 .. 20], [21, 22]];
17205            
17206            
17207             is_deeply [1..$_], [deSquareArray squareArray @{[1..$_]}] for 1..22; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
17208              
17209            
17210             ok $_ == countSquareArray squareArray @{[1..$_]} for 222; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
17211              
17212            
17213             is_deeply [rectangularArray(3, 1..11)],
17214             [[1, 4, 7, 10],
17215             [2, 5, 8, 11],
17216             [3, 6, 9]];
17217            
17218             is_deeply [rectangularArray(3, 1..12)],
17219             [[1, 4, 7, 10],
17220             [2, 5, 8, 11],
17221             [3, 6, 9, 12]];
17222            
17223             is_deeply [rectangularArray(3, 1..13)],
17224             [[1, 4, 7, 10, 13],
17225             [2, 5, 8, 11],
17226             [3, 6, 9, 12]];
17227            
17228             is_deeply [rectangularArray2(3, 1..5)],
17229             [[1, 2, 3],
17230             [4, 5]];
17231            
17232             is_deeply [rectangularArray2(3, 1..6)],
17233             [[1, 2, 3],
17234             [4, 5, 6]];
17235            
17236             is_deeply [rectangularArray2(3, 1..7)],
17237             [[1, 2, 3],
17238             [4, 5, 6],
17239             [7]];
17240            
17241              
17242             =head2 deSquareArray(@square)
17243              
17244             Create a one dimensional array from a two dimensional array of arrays
17245              
17246             Parameter Description
17247             1 @square Array of arrays
17248              
17249             B
17250              
17251              
17252             is_deeply [squareArray @{[1..4]} ], [[1, 2], [3, 4]];
17253             is_deeply [squareArray @{[1..22]}],
17254             [[1 .. 5], [6 .. 10], [11 .. 15], [16 .. 20], [21, 22]];
17255            
17256            
17257             is_deeply [1..$_], [deSquareArray squareArray @{[1..$_]}] for 1..22; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
17258              
17259             ok $_ == countSquareArray squareArray @{[1..$_]} for 222;
17260            
17261             is_deeply [rectangularArray(3, 1..11)],
17262             [[1, 4, 7, 10],
17263             [2, 5, 8, 11],
17264             [3, 6, 9]];
17265            
17266             is_deeply [rectangularArray(3, 1..12)],
17267             [[1, 4, 7, 10],
17268             [2, 5, 8, 11],
17269             [3, 6, 9, 12]];
17270            
17271             is_deeply [rectangularArray(3, 1..13)],
17272             [[1, 4, 7, 10, 13],
17273             [2, 5, 8, 11],
17274             [3, 6, 9, 12]];
17275            
17276             is_deeply [rectangularArray2(3, 1..5)],
17277             [[1, 2, 3],
17278             [4, 5]];
17279            
17280             is_deeply [rectangularArray2(3, 1..6)],
17281             [[1, 2, 3],
17282             [4, 5, 6]];
17283            
17284             is_deeply [rectangularArray2(3, 1..7)],
17285             [[1, 2, 3],
17286             [4, 5, 6],
17287             [7]];
17288            
17289              
17290             =head2 rectangularArray($first, @array)
17291              
17292             Create a two dimensional rectangular array whose first dimension is B<$first> from a one dimensional linear array.
17293              
17294             Parameter Description
17295             1 $first First dimension size
17296             2 @array Array
17297              
17298             B
17299              
17300              
17301             is_deeply [squareArray @{[1..4]} ], [[1, 2], [3, 4]];
17302             is_deeply [squareArray @{[1..22]}],
17303             [[1 .. 5], [6 .. 10], [11 .. 15], [16 .. 20], [21, 22]];
17304            
17305             is_deeply [1..$_], [deSquareArray squareArray @{[1..$_]}] for 1..22;
17306             ok $_ == countSquareArray squareArray @{[1..$_]} for 222;
17307            
17308            
17309             is_deeply [rectangularArray(3, 1..11)], # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
17310              
17311             [[1, 4, 7, 10],
17312             [2, 5, 8, 11],
17313             [3, 6, 9]];
17314            
17315            
17316             is_deeply [rectangularArray(3, 1..12)], # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
17317              
17318             [[1, 4, 7, 10],
17319             [2, 5, 8, 11],
17320             [3, 6, 9, 12]];
17321            
17322            
17323             is_deeply [rectangularArray(3, 1..13)], # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
17324              
17325             [[1, 4, 7, 10, 13],
17326             [2, 5, 8, 11],
17327             [3, 6, 9, 12]];
17328            
17329             is_deeply [rectangularArray2(3, 1..5)],
17330             [[1, 2, 3],
17331             [4, 5]];
17332            
17333             is_deeply [rectangularArray2(3, 1..6)],
17334             [[1, 2, 3],
17335             [4, 5, 6]];
17336            
17337             is_deeply [rectangularArray2(3, 1..7)],
17338             [[1, 2, 3],
17339             [4, 5, 6],
17340             [7]];
17341            
17342              
17343             =head2 rectangularArray2($second, @array)
17344              
17345             Create a two dimensional rectangular array whose second dimension is B<$second> from a one dimensional linear array.
17346              
17347             Parameter Description
17348             1 $second Second dimension size
17349             2 @array Array
17350              
17351             B
17352              
17353              
17354             is_deeply [squareArray @{[1..4]} ], [[1, 2], [3, 4]];
17355             is_deeply [squareArray @{[1..22]}],
17356             [[1 .. 5], [6 .. 10], [11 .. 15], [16 .. 20], [21, 22]];
17357            
17358             is_deeply [1..$_], [deSquareArray squareArray @{[1..$_]}] for 1..22;
17359             ok $_ == countSquareArray squareArray @{[1..$_]} for 222;
17360            
17361             is_deeply [rectangularArray(3, 1..11)],
17362             [[1, 4, 7, 10],
17363             [2, 5, 8, 11],
17364             [3, 6, 9]];
17365            
17366             is_deeply [rectangularArray(3, 1..12)],
17367             [[1, 4, 7, 10],
17368             [2, 5, 8, 11],
17369             [3, 6, 9, 12]];
17370            
17371             is_deeply [rectangularArray(3, 1..13)],
17372             [[1, 4, 7, 10, 13],
17373             [2, 5, 8, 11],
17374             [3, 6, 9, 12]];
17375            
17376            
17377             is_deeply [rectangularArray2(3, 1..5)], # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
17378              
17379             [[1, 2, 3],
17380             [4, 5]];
17381            
17382            
17383             is_deeply [rectangularArray2(3, 1..6)], # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
17384              
17385             [[1, 2, 3],
17386             [4, 5, 6]];
17387            
17388            
17389             is_deeply [rectangularArray2(3, 1..7)], # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
17390              
17391             [[1, 2, 3],
17392             [4, 5, 6],
17393             [7]];
17394            
17395              
17396             =head2 callSubInParallel($sub)
17397              
17398             Call a sub reference in parallel to avoid memory fragmentation and return its results.
17399              
17400             Parameter Description
17401             1 $sub Sub reference
17402              
17403             B
17404              
17405              
17406             my %a = (a=>1, b=>2);
17407            
17408             my %b = callSubInParallel {return %a}; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
17409              
17410             is_deeply \%a, \%b;
17411            
17412             my $f = temporaryFile;
17413             ok -e $f;
17414            
17415             my $a = callSubInOverlappedParallel
17416             sub {$a{a}++; owf($f, "Hello World")},
17417             sub {q(aaaa)};
17418            
17419             ok $a =~ m(aaaa)i;
17420             ok $a{a} == 1;
17421             ok readFile($f) =~ m(Hello World)i;
17422            
17423              
17424             =head2 callSubInOverlappedParallel($child, $parent)
17425              
17426             Call the B<$child> sub reference in parallel in a separate child process and ignore its results while calling the B<$parent> sub reference in the parent process and returning its results.
17427              
17428             Parameter Description
17429             1 $child Sub reference to call in child process
17430             2 $parent Sub reference to call in parent process
17431              
17432             B
17433              
17434              
17435             my %a = (a=>1, b=>2);
17436             my %b = callSubInParallel {return %a};
17437             is_deeply \%a, \%b;
17438            
17439             my $f = temporaryFile;
17440             ok -e $f;
17441            
17442            
17443             my $a = callSubInOverlappedParallel # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
17444              
17445             sub {$a{a}++; owf($f, "Hello World")},
17446             sub {q(aaaa)};
17447            
17448             ok $a =~ m(aaaa)i;
17449             ok $a{a} == 1;
17450             ok readFile($f) =~ m(Hello World)i;
17451            
17452              
17453             =head2 runInParallel($maximumNumberOfProcesses, $parallel, $results, @array)
17454              
17455             Process the elements of an array in parallel using a maximum of B<$maximumNumberOfProcesses> processes. sub B<&$parallel> is forked to process each array element in parallel. The results returned by the forked copies of &$parallel are presented as a single array to sub B<&$results> which is run in series. B<@array> contains the elements to be processed. Returns the result returned by &$results.
17456              
17457             Parameter Description
17458             1 $maximumNumberOfProcesses Maximum number of processes
17459             2 $parallel Parallel sub
17460             3 $results Results sub
17461             4 @array Array of items to process
17462              
17463             B
17464              
17465              
17466             my @N = 1..100;
17467             my $N = 100;
17468             my $R = 0; $R += $_*$_ for 1..$N;
17469            
17470             ok 338350 == $R;
17471            
17472             ok $R == runInSquareRootParallel
17473             (4,
17474             sub {my ($p) = @_; $p * $p},
17475             sub {my $p = 0; $p += $_ for @_; $p},
17476             @{[1..$N]}
17477             );
17478            
17479            
17480             ok $R == runInParallel # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
17481              
17482             (4,
17483             sub {my ($p) = @_; $p * $p},
17484             sub {my $p = 0; $p += $_ for @_; $p},
17485             @{[1..$N]}
17486             );
17487            
17488              
17489             =head2 runInSquareRootParallel($maximumNumberOfProcesses, $parallel, $results, @array)
17490              
17491             Process the elements of an array in square root parallel using a maximum of B<$maximumNumberOfProcesses> processes. sub B<&$parallel> is forked to process each block of array elements in parallel. The results returned by the forked copies of &$parallel are presented as a single array to sub B<&$results> which is run in series. B<@array> contains the elements to be processed. Returns the result returned by &$results..
17492              
17493             Parameter Description
17494             1 $maximumNumberOfProcesses Maximum number of processes
17495             2 $parallel Parallel sub
17496             3 $results Results sub
17497             4 @array Array of items to process
17498              
17499             B
17500              
17501              
17502             my @N = 1..100;
17503             my $N = 100;
17504             my $R = 0; $R += $_*$_ for 1..$N;
17505            
17506             ok 338350 == $R;
17507            
17508            
17509             ok $R == runInSquareRootParallel # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
17510              
17511             (4,
17512             sub {my ($p) = @_; $p * $p},
17513             sub {my $p = 0; $p += $_ for @_; $p},
17514             @{[1..$N]}
17515             );
17516            
17517             ok $R == runInParallel
17518             (4,
17519             sub {my ($p) = @_; $p * $p},
17520             sub {my $p = 0; $p += $_ for @_; $p},
17521             @{[1..$N]}
17522             );
17523            
17524              
17525             =head2 packBySize($N, @sizes)
17526              
17527             Given B<$N> buckets and a list B<@sizes> of ([size of file, name of file]...) pack the file names into buckets so that each bucket contains approximately the same number of bytes. In general this is an NP problem. Packing largest first into emptiest bucket produces an N**2 heuristic if the buckets are scanned linearly, or N*log(N) if a binary tree is used. This solution is a compromise at N**3/2 which has the benefits of simple code yet good performance. Returns ([file names ...]).
17528              
17529             Parameter Description
17530             1 $N Number of buckets
17531             2 @sizes Sizes
17532              
17533             B
17534              
17535              
17536             my $M = 7;
17537             my $N = 15;
17538            
17539             my @b = packBySize($M, map {[$_, $_]} 1..$N); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
17540              
17541             my @B; my $B = 0;
17542             for my $b(@b)
17543             {my $n = 0;
17544             for(@$b)
17545             {$n += $_;
17546             $B += $_;
17547             }
17548             push @B, $n;
17549             }
17550             ok $B == $N * ($N + 1) / 2;
17551             is_deeply [@B], [16, 20, 16, 18, 16, 18, 16];
17552            
17553              
17554             =head2 processSizesInParallel($parallel, $results, @sizes)
17555              
17556             Process items of known size in parallel using (8 * the number of CPUs) processes with the process each item is assigned to depending on the size of the item so that each process is loaded with approximately the same number of bytes of data in total from the items it processes.
17557              
17558             Each item is processed by sub B<$parallel> and the results of processing all items is processed by B<$results> where the items are taken from B<@sizes>. Each &$parallel() receives an item from @files. &$results() receives an array of all the results returned by &$parallel().
17559              
17560             Parameter Description
17561             1 $parallel Parallel sub
17562             2 $results Results sub
17563             3 @sizes Array of [size; item] to process by size
17564              
17565             B
17566              
17567              
17568             my $d = temporaryFolder;
17569             my @f = map {owf(fpe($d, $_, q(txt)), 'X' x ($_ ** 2 % 11))} 1..9;
17570            
17571             my $f = fileLargestSize(@f);
17572             ok fn($f) eq '3', 'aaa';
17573            
17574             # my $b = folderSize($d); # Needs du
17575             # ok $b > 0, 'bbb';
17576            
17577             my $c = processFilesInParallel(
17578             sub
17579             {my ($file) = @_;
17580             [&fileSize($file), $file]
17581             },
17582             sub
17583             {scalar @_;
17584             }, (@f) x 12);
17585            
17586             ok 108 == $c, 'cc11';
17587            
17588            
17589             my $C = processSizesInParallel # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
17590              
17591             sub
17592             {my ($file) = @_;
17593             [&fileSize($file), $file]
17594             },
17595             sub
17596             {scalar @_;
17597             }, map {[fileSize($_), $_]} (@f) x 12;
17598            
17599             ok 108 == $C, 'cc2';
17600            
17601             my $J = processJavaFilesInParallel
17602             sub
17603             {my ($file) = @_;
17604             [&fileSize($file), $file]
17605             },
17606             sub
17607             {scalar @_;
17608             }, (@f) x 12;
17609            
17610             ok 108 == $J, 'cc3';
17611            
17612             clearFolder($d, 12);
17613            
17614              
17615             =head2 processFilesInParallel($parallel, $results, @files)
17616              
17617             Process files in parallel using (8 * the number of CPUs) processes with the process each file is assigned to depending on the size of the file so that each process is loaded with approximately the same number of bytes of data in total from the files it processes.
17618              
17619             Each file is processed by sub B<$parallel> and the results of processing all files is processed by B<$results> where the files are taken from B<@files>. Each B<&$parallel> receives a file from B<@files>. B<&$results> receives an array of all the results returned by B<&$parallel>.
17620              
17621             Parameter Description
17622             1 $parallel Parallel sub
17623             2 $results Results sub
17624             3 @files Array of files to process by size
17625              
17626             B
17627              
17628              
17629             my $d = temporaryFolder;
17630             my @f = map {owf(fpe($d, $_, q(txt)), 'X' x ($_ ** 2 % 11))} 1..9;
17631            
17632             my $f = fileLargestSize(@f);
17633             ok fn($f) eq '3', 'aaa';
17634            
17635             # my $b = folderSize($d); # Needs du
17636             # ok $b > 0, 'bbb';
17637            
17638            
17639             my $c = processFilesInParallel( # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
17640              
17641             sub
17642             {my ($file) = @_;
17643             [&fileSize($file), $file]
17644             },
17645             sub
17646             {scalar @_;
17647             }, (@f) x 12);
17648            
17649             ok 108 == $c, 'cc11';
17650            
17651             my $C = processSizesInParallel
17652             sub
17653             {my ($file) = @_;
17654             [&fileSize($file), $file]
17655             },
17656             sub
17657             {scalar @_;
17658             }, map {[fileSize($_), $_]} (@f) x 12;
17659            
17660             ok 108 == $C, 'cc2';
17661            
17662             my $J = processJavaFilesInParallel
17663             sub
17664             {my ($file) = @_;
17665             [&fileSize($file), $file]
17666             },
17667             sub
17668             {scalar @_;
17669             }, (@f) x 12;
17670            
17671             ok 108 == $J, 'cc3';
17672            
17673             clearFolder($d, 12);
17674            
17675              
17676             =head2 processJavaFilesInParallel($parallel, $results, @files)
17677              
17678             Process java files of known size in parallel using (the number of CPUs) processes with the process each item is assigned to depending on the size of the java item so that each process is loaded with approximately the same number of bytes of data in total from the java files it processes.
17679              
17680             Each java item is processed by sub B<$parallel> and the results of processing all java files is processed by B<$results> where the java files are taken from B<@sizes>. Each &$parallel() receives a java item from @files. &$results() receives an array of all the results returned by &$parallel().
17681              
17682             Parameter Description
17683             1 $parallel Parallel sub
17684             2 $results Results sub
17685             3 @files Array of [size; java item] to process by size
17686              
17687             B
17688              
17689              
17690             my $d = temporaryFolder;
17691             my @f = map {owf(fpe($d, $_, q(txt)), 'X' x ($_ ** 2 % 11))} 1..9;
17692            
17693             my $f = fileLargestSize(@f);
17694             ok fn($f) eq '3', 'aaa';
17695            
17696             # my $b = folderSize($d); # Needs du
17697             # ok $b > 0, 'bbb';
17698            
17699             my $c = processFilesInParallel(
17700             sub
17701             {my ($file) = @_;
17702             [&fileSize($file), $file]
17703             },
17704             sub
17705             {scalar @_;
17706             }, (@f) x 12);
17707            
17708             ok 108 == $c, 'cc11';
17709            
17710             my $C = processSizesInParallel
17711             sub
17712             {my ($file) = @_;
17713             [&fileSize($file), $file]
17714             },
17715             sub
17716             {scalar @_;
17717             }, map {[fileSize($_), $_]} (@f) x 12;
17718            
17719             ok 108 == $C, 'cc2';
17720            
17721            
17722             my $J = processJavaFilesInParallel # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
17723              
17724             sub
17725             {my ($file) = @_;
17726             [&fileSize($file), $file]
17727             },
17728             sub
17729             {scalar @_;
17730             }, (@f) x 12;
17731            
17732             ok 108 == $J, 'cc3';
17733            
17734             clearFolder($d, 12);
17735            
17736              
17737             =head2 syncFromS3InParallel($maxSize, $source, $target, $Profile, $options)
17738              
17739             Download from L by using "aws s3 sync --exclude '*' --include '...'" in parallel to sync collections of two or more files no greater then B<$maxSize> or single files greater than $maxSize from the B<$source> folder on L to the local folder B<$target> using the specified B<$Profile> and B<$options> - then execute the entire command again without the --exclude and --include options in series which might now run faster due to the prior downloads.
17740              
17741             Parameter Description
17742             1 $maxSize The maximum collection size
17743             2 $source The source folder on S3
17744             3 $target The target folder locally
17745             4 $Profile Aws cli profile
17746             5 $options Aws cli options
17747              
17748             B
17749              
17750              
17751             if (0)
17752            
17753             {syncFromS3InParallel 1e5, # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
17754              
17755             q(xxx/originals3/),
17756             q(/home/phil/xxx/),
17757             q(phil), q(--quiet);
17758            
17759             syncToS3InParallel 1e5,
17760             q(/home/phil/xxx/),
17761             q(xxx/originals3/),
17762             q(phil), q(--quiet);
17763             }
17764            
17765              
17766             =head2 syncToS3InParallel($maxSize, $source, $target, $Profile, $options)
17767              
17768             Upload to L by using "aws s3 sync --exclude '*' --include '...'" in parallel to sync collections of two or more files no greater then B<$maxSize> or single files greater than $maxSize from the B<$source> folder locally to the target folder B<$target> on L using the specified B<$Profile> and B<$options> - then execute the entire command again without the --exclude and --include options in series which might now run faster due to the prior uploads.
17769              
17770             Parameter Description
17771             1 $maxSize The maximum collection size
17772             2 $source The target folder locally
17773             3 $target The source folder on S3
17774             4 $Profile Aws cli profile
17775             5 $options Aws cli options
17776              
17777             B
17778              
17779              
17780             if (0)
17781             {syncFromS3InParallel 1e5,
17782             q(xxx/originals3/),
17783             q(/home/phil/xxx/),
17784             q(phil), q(--quiet);
17785            
17786            
17787             syncToS3InParallel 1e5, # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
17788              
17789             q(/home/phil/xxx/),
17790             q(xxx/originals3/),
17791             q(phil), q(--quiet);
17792             }
17793            
17794              
17795             =head2 childPids($p)
17796              
17797             Recursively find the pids of all the sub processes of a B<$process> and all their sub processes and so on returning the specified pid and all its child pids as a list.
17798              
17799             Parameter Description
17800             1 $p Process
17801              
17802             B
17803              
17804              
17805            
17806             is_deeply [childPids(2702)], [2702..2705]; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
17807              
17808            
17809              
17810             =head2 newServiceIncarnation($service, $file)
17811              
17812             Create a new service incarnation to record the start up of a new instance of a service and return the description as a L.
17813              
17814             Parameter Description
17815             1 $service Service name
17816             2 $file Optional details file
17817              
17818             B
17819              
17820              
17821             if (1)
17822            
17823             {my $s = newServiceIncarnation("aaa", q(bbb.txt)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
17824              
17825             is_deeply $s->check, $s;
17826            
17827             my $t = newServiceIncarnation("aaa", q(bbb.txt)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
17828              
17829             is_deeply $t->check, $t;
17830             ok $t->start >= $s->start+1;
17831             ok !$s->check(1);
17832             unlink q(bbb.txt);
17833             }
17834            
17835              
17836             =head2 Data::Exchange::Service::check($service, $continue)
17837              
17838             Check that we are the current incarnation of the named service with details obtained from L. If the optional B<$continue> flag has been set then return the service details if this is the current service incarnation else B. Otherwise if the B<$continue> flag is false confess unless this is the current service incarnation thus bringing the earlier version of this service to an abrupt end.
17839              
17840             Parameter Description
17841             1 $service Current service details
17842             2 $continue Return result if B<$continue> is true else confess if the service has been replaced
17843              
17844             B
17845              
17846              
17847             if (1)
17848             {my $s = newServiceIncarnation("aaa", q(bbb.txt));
17849             is_deeply $s->check, $s;
17850             my $t = newServiceIncarnation("aaa", q(bbb.txt));
17851             is_deeply $t->check, $t;
17852             ok $t->start >= $s->start+1;
17853             ok !$s->check(1);
17854             unlink q(bbb.txt);
17855             }
17856            
17857              
17858             =head1 Conversions
17859              
17860             Perform various conversions from STDIN to STDOUT
17861              
17862             =head2 convertPerlToJavaScript($in, $out)
17863              
17864             Convert Perl to Javascript
17865              
17866             Parameter Description
17867             1 $in Input file name or STDIN if undef
17868             2 $out Output file name or STDOUT if undefined
17869              
17870             B
17871              
17872              
17873             if (1)
17874             {my $i = writeTempFile(<<'END');
17875             sub test($$) #P A test method
17876             {my ($file, $data) = @_; # Parameter 1, parameter 2
17877             if (fullyQualifiedFile($file)) {return qq($data)} # File is already fully qualified
17878             } # test
17879            
17880              
17881             =head1 Documentation
17882              
17883             Extract, format and update documentation for a perl module.
17884              
17885             =head2 parseDitaRef($ref, $File, $TopicId)
17886              
17887             Parse a dita reference B<$ref> into its components (file name, topic id, id) . Optionally supply a base file name B<$File>> to make the the file component absolute and/or a a default the topic id B<$TopicId> to use if the topic id is not present in the reference.
17888              
17889             Parameter Description
17890             1 $ref Reference to parse
17891             2 $File Default absolute file
17892             3 $TopicId Default topic id
17893              
17894             B
17895              
17896              
17897            
17898             is_deeply [parseDitaRef(q(a#b/c))], [qw(a b c)]; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
17899              
17900            
17901             is_deeply [parseDitaRef(q(a#./c))], [q(a), q(), q(c)]; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
17902              
17903            
17904             is_deeply [parseDitaRef(q(a#/c))], [q(a), q(), q(c)]; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
17905              
17906            
17907             is_deeply [parseDitaRef(q(a#c))], [q(a), q(), q(c)]; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
17908              
17909            
17910             is_deeply [parseDitaRef(q(#b/c))], [q(), qw(b c)]; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
17911              
17912            
17913             is_deeply [parseDitaRef(q(#b))], [q(), q(), q(b)]; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
17914              
17915            
17916             is_deeply [parseDitaRef(q(#./c))], [q(), q(), q(c)]; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
17917              
17918            
17919             is_deeply [parseDitaRef(q(#/c))], [q(), q(), q(c)]; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
17920              
17921            
17922             is_deeply [parseDitaRef(q(#c))], [q(), q(), q(c)]; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
17923              
17924            
17925              
17926             =head2 parseXmlDocType($string)
17927              
17928             Parse an L DOCTYPE and return a hash indicating its components
17929              
17930             Parameter Description
17931             1 $string String containing a DOCTYPE
17932              
17933             B
17934              
17935              
17936             if (1)
17937            
17938             {is_deeply parseXmlDocType(<
17939              
17940            
17941             ...
17942             END
17943             {localDtd => "reference.dtd",
17944             public => 1,
17945             publicId => "-//OASIS//DTD DITA Reference//EN",
17946             root => "reference",
17947             };
17948            
17949            
17950             is_deeply parseXmlDocType(<
17951              
17952             ...
17953            
17954             ...
17955             )),
17956             END
17957             {localDtd => "concept.dtd",
17958             public => 1,
17959             publicId => "-//OASIS//DTD DITA Task//EN",
17960             root => "concept",
17961             };
17962             }
17963            
17964              
17965             =head2 reportSettings($sourceFile, $reportFile)
17966              
17967             Report the current values of parameterless subs.
17968              
17969             Parameter Description
17970             1 $sourceFile Source file
17971             2 $reportFile Optional report file
17972              
17973             B
17974              
17975              
17976            
17977             reportSettings($0); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
17978              
17979            
17980              
17981             =head2 reportAttributes($sourceFile)
17982              
17983             Report the attributes present in a B<$sourceFile>
17984              
17985             Parameter Description
17986             1 $sourceFile Source file
17987              
17988             B
17989              
17990              
17991             my $d = temporaryFile;
17992            
17993             my $f = writeFile(undef, <<'END'.<
17994             #!perl -I/home/phil/perl/cpan/DataTableText/lib/
17995             use Data::Table::Text qw(reportAttributeSettings);
17996             sub attribute {1} # An attribute
17997             sub replaceable($) #r A replaceable method
17998             {
17999            
18000              
18001             =head2 reportAttributeSettings($reportFile)
18002              
18003             Report the current values of the attribute methods in the calling file and optionally write the report to B<$reportFile>. Return the text of the report.
18004              
18005             Parameter Description
18006             1 $reportFile Optional report file
18007              
18008             B
18009              
18010              
18011             my $d = temporaryFile;
18012            
18013             my $f = writeFile(undef, <<'END'.<
18014             #!perl -I/home/phil/perl/cpan/DataTableText/lib/
18015            
18016             use Data::Table::Text qw(reportAttributeSettings); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
18017              
18018             sub attribute {1} # An attribute
18019             sub replaceable($) #r A replaceable method
18020             {
18021            
18022              
18023             =head2 reportReplacableMethods($sourceFile)
18024              
18025             Report the replaceable methods marked with #r in a B<$sourceFile>
18026              
18027             Parameter Description
18028             1 $sourceFile Source file
18029              
18030             B
18031              
18032              
18033             my $d = temporaryFile;
18034            
18035             my $f = writeFile(undef, <<'END'.<
18036             #!perl -I/home/phil/perl/cpan/DataTableText/lib/
18037             use Data::Table::Text qw(reportAttributeSettings);
18038             sub attribute {1} # An attribute
18039             sub replaceable($) #r A replaceable method
18040             {
18041            
18042            
18043             sub reportReplacableMethods($) # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
18044              
18045             {my ($sourceFile) = @_; # Source file
18046             my $s = readFile($sourceFile);
18047             my %s;
18048             for my $l(split /
18049             /, $s) # Find the attribute subs
18050             {if ($l =~ m(\Asub\s*(\w+).*?#\w*r\w*\s+(.*)\Z))
18051             {$s{$1} = $2;
18052             }
18053             }
18054             \%s
18055             }
18056            
18057              
18058             =head2 reportExportableMethods($sourceFile)
18059              
18060             Report the exportable methods marked with #e in a B<$sourceFile>
18061              
18062             Parameter Description
18063             1 $sourceFile Source file
18064              
18065             B
18066              
18067              
18068             my $d = temporaryFile;
18069            
18070             my $f = writeFile(undef, <<'END'.<
18071             #!perl -I/home/phil/perl/cpan/DataTableText/lib/
18072             use Data::Table::Text qw(reportAttributeSettings);
18073             sub attribute {1} # An attribute
18074             sub replaceable($) #r A replaceable method
18075             {
18076            
18077              
18078             =head2 htmlToc($replace, $html)
18079              
18080             Generate a table of contents for some html.
18081              
18082             Parameter Description
18083             1 $replace Sub-string within the html to be replaced with the toc
18084             2 $html String of html
18085              
18086             B
18087              
18088              
18089            
18090             ok nws(htmlToc("XXXX", <
18091              
18092            

Chapter 1

18093            

Section 1

18094            

Chapter 2

18095             XXXX
18096             END
18097            
18098             eq nws(<
18099            

Chapter 1

18100            

Section 1

18101            

Chapter 2

18102            
18103            
 
18104            
1    Chapter 1
18105            
2        Section 1
18106            
 
18107            
3    Chapter 2
18108            
18109             END
18110            
18111              
18112             =head2 expandWellKnownWordsAsUrlsInHtmlFormat($string)
18113              
18114             Expand words found in a string using the html B tag to supply a definition of that word.
18115              
18116             Parameter Description
18117             1 $string String containing url names to expand
18118              
18119             B
18120              
18121              
18122             ok expandWellKnownUrlsInDitaFormat(q(L[github])) eq
18123             q(GitHub);
18124            
18125             ok expandWellKnownUrlsInHtmlFormat(q(L[github])) eq
18126             q(GitHub);
18127            
18128             ok expandWellKnownUrlsInPerlFormat(q(L)) eq
18129             q(L);
18130            
18131             ok expandWellKnownUrlsInPerlFormat(q(github)) eq q(github);
18132            
18133             ok expandWellKnownUrlsInHtmlFromPerl(q(L)) eq
18134             q(GitHub);
18135            
18136            
18137             is_deeply expandWellKnownWordsAsUrlsInHtmlFormat(q(go to gitHub and press w[enter].)), # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
18138              
18139             q(go to GitHub and press enter.), 'ex1';
18140            
18141             is_deeply expandWellKnownWordsAsUrlsInMdFormat(q(go to gitHub and press w[enter].)),
18142             q(go to [GitHub](https://github.com/philiprbrenan) and press enter.), 'ex2';
18143            
18144             ok expandWellKnownUrlsInPod2Html(<
18145              
18146             =begin HTML
18147              
18148             GitHub
18149              
18150             =end HTML
18151              
18152              
18153             bbb
18154             "';
18155             aaa L bbb
18156             END
18157            
18158              
18159             =head2 expandWellKnownWordsAsUrlsInMdFormat($string)
18160              
18161             Expand words found in a string using the md url to supply a definition of that word.
18162              
18163             Parameter Description
18164             1 $string String containing url names to expand
18165              
18166             B
18167              
18168              
18169             ok expandWellKnownUrlsInDitaFormat(q(L[github])) eq
18170             q(GitHub);
18171            
18172             ok expandWellKnownUrlsInHtmlFormat(q(L[github])) eq
18173             q(GitHub);
18174            
18175             ok expandWellKnownUrlsInPerlFormat(q(L)) eq
18176             q(L);
18177            
18178             ok expandWellKnownUrlsInPerlFormat(q(github)) eq q(github);
18179            
18180             ok expandWellKnownUrlsInHtmlFromPerl(q(L)) eq
18181             q(GitHub);
18182            
18183             is_deeply expandWellKnownWordsAsUrlsInHtmlFormat(q(go to gitHub and press w[enter].)),
18184             q(go to GitHub and press enter.), 'ex1';
18185            
18186            
18187             is_deeply expandWellKnownWordsAsUrlsInMdFormat(q(go to gitHub and press w[enter].)), # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
18188              
18189             q(go to [GitHub](https://github.com/philiprbrenan) and press enter.), 'ex2';
18190            
18191             ok expandWellKnownUrlsInPod2Html(<
18192              
18193             =begin HTML
18194              
18195             GitHub
18196              
18197             =end HTML
18198              
18199              
18200             bbb
18201             "';
18202             aaa L bbb
18203             END
18204            
18205              
18206             =head2 expandWellKnownUrlsInPerlFormat($string)
18207              
18208             Expand short L names found in a string in the format LEurl-nameE using the Perl POD syntax
18209              
18210             Parameter Description
18211             1 $string String containing url names to expand
18212              
18213             B
18214              
18215              
18216             ok expandWellKnownUrlsInDitaFormat(q(L[github])) eq
18217             q(GitHub);
18218            
18219             ok expandWellKnownUrlsInHtmlFormat(q(L[github])) eq
18220             q(GitHub);
18221            
18222            
18223             ok expandWellKnownUrlsInPerlFormat(q(L)) eq # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
18224              
18225             q(L);
18226            
18227            
18228             ok expandWellKnownUrlsInPerlFormat(q(github)) eq q(github); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
18229              
18230            
18231             ok expandWellKnownUrlsInHtmlFromPerl(q(L)) eq
18232             q(GitHub);
18233            
18234             is_deeply expandWellKnownWordsAsUrlsInHtmlFormat(q(go to gitHub and press w[enter].)),
18235             q(go to GitHub and press enter.), 'ex1';
18236            
18237             is_deeply expandWellKnownWordsAsUrlsInMdFormat(q(go to gitHub and press w[enter].)),
18238             q(go to [GitHub](https://github.com/philiprbrenan) and press enter.), 'ex2';
18239            
18240             ok expandWellKnownUrlsInPod2Html(<
18241              
18242             =begin HTML
18243              
18244             GitHub
18245              
18246             =end HTML
18247              
18248              
18249             bbb
18250             "';
18251             aaa L bbb
18252             END
18253            
18254              
18255             =head2 expandWellKnownUrlsInHtmlFormat($string)
18256              
18257             Expand short L names found in a string in the format L[url-name] using the html B tag.
18258              
18259             Parameter Description
18260             1 $string String containing url names to expand
18261              
18262             B
18263              
18264              
18265             ok expandWellKnownUrlsInDitaFormat(q(L[github])) eq
18266             q(GitHub);
18267            
18268            
18269             ok expandWellKnownUrlsInHtmlFormat(q(L[github])) eq # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
18270              
18271             q(GitHub);
18272            
18273             ok expandWellKnownUrlsInPerlFormat(q(L)) eq
18274             q(L);
18275            
18276             ok expandWellKnownUrlsInPerlFormat(q(github)) eq q(github);
18277            
18278             ok expandWellKnownUrlsInHtmlFromPerl(q(L)) eq
18279             q(GitHub);
18280            
18281             is_deeply expandWellKnownWordsAsUrlsInHtmlFormat(q(go to gitHub and press w[enter].)),
18282             q(go to GitHub and press enter.), 'ex1';
18283            
18284             is_deeply expandWellKnownWordsAsUrlsInMdFormat(q(go to gitHub and press w[enter].)),
18285             q(go to [GitHub](https://github.com/philiprbrenan) and press enter.), 'ex2';
18286            
18287             ok expandWellKnownUrlsInPod2Html(<
18288              
18289             =begin HTML
18290              
18291             GitHub
18292              
18293             =end HTML
18294              
18295              
18296             bbb
18297             "';
18298             aaa L bbb
18299             END
18300            
18301              
18302             =head2 expandWellKnownUrlsInHtmlFromPerl($string)
18303              
18304             Expand short L names found in a string in the format L[url-name] using the html B tag.
18305              
18306             Parameter Description
18307             1 $string String containing url names to expand
18308              
18309             B
18310              
18311              
18312             ok expandWellKnownUrlsInDitaFormat(q(L[github])) eq
18313             q(GitHub);
18314            
18315             ok expandWellKnownUrlsInHtmlFormat(q(L[github])) eq
18316             q(GitHub);
18317            
18318             ok expandWellKnownUrlsInPerlFormat(q(L)) eq
18319             q(L);
18320            
18321             ok expandWellKnownUrlsInPerlFormat(q(github)) eq q(github);
18322            
18323            
18324             ok expandWellKnownUrlsInHtmlFromPerl(q(L)) eq # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
18325              
18326             q(GitHub);
18327            
18328             is_deeply expandWellKnownWordsAsUrlsInHtmlFormat(q(go to gitHub and press w[enter].)),
18329             q(go to GitHub and press enter.), 'ex1';
18330            
18331             is_deeply expandWellKnownWordsAsUrlsInMdFormat(q(go to gitHub and press w[enter].)),
18332             q(go to [GitHub](https://github.com/philiprbrenan) and press enter.), 'ex2';
18333            
18334             ok expandWellKnownUrlsInPod2Html(<
18335              
18336             =begin HTML
18337              
18338             GitHub
18339              
18340             =end HTML
18341              
18342              
18343             bbb
18344             "';
18345             aaa L bbb
18346             END
18347            
18348              
18349             =head2 expandWellKnownUrlsInPod2Html($string)
18350              
18351             Expand short L names found in a string in the format =begin html format
18352              
18353             Parameter Description
18354             1 $string String containing url names to expand
18355              
18356             B
18357              
18358              
18359             ok expandWellKnownUrlsInDitaFormat(q(L[github])) eq
18360             q(GitHub);
18361            
18362             ok expandWellKnownUrlsInHtmlFormat(q(L[github])) eq
18363             q(GitHub);
18364            
18365             ok expandWellKnownUrlsInPerlFormat(q(L)) eq
18366             q(L);
18367            
18368             ok expandWellKnownUrlsInPerlFormat(q(github)) eq q(github);
18369            
18370             ok expandWellKnownUrlsInHtmlFromPerl(q(L)) eq
18371             q(GitHub);
18372            
18373             is_deeply expandWellKnownWordsAsUrlsInHtmlFormat(q(go to gitHub and press w[enter].)),
18374             q(go to GitHub and press enter.), 'ex1';
18375            
18376             is_deeply expandWellKnownWordsAsUrlsInMdFormat(q(go to gitHub and press w[enter].)),
18377             q(go to [GitHub](https://github.com/philiprbrenan) and press enter.), 'ex2';
18378            
18379            
18380             ok expandWellKnownUrlsInPod2Html(<
18381              
18382             =begin HTML
18383              
18384             GitHub
18385              
18386             =end HTML
18387              
18388              
18389             bbb
18390             "'; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
18391              
18392             aaa L bbb
18393             END
18394            
18395              
18396             =head2 expandWellKnownUrlsInDitaFormat($string)
18397              
18398             Expand short L names found in a string in the format L[url-name] in the L[Dita] Bformat.
18399              
18400             Parameter Description
18401             1 $string String containing url names to expand
18402              
18403             B
18404              
18405              
18406            
18407             ok expandWellKnownUrlsInDitaFormat(q(L[github])) eq # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
18408              
18409             q(GitHub);
18410            
18411             ok expandWellKnownUrlsInHtmlFormat(q(L[github])) eq
18412             q(GitHub);
18413            
18414             ok expandWellKnownUrlsInPerlFormat(q(L)) eq
18415             q(L);
18416            
18417             ok expandWellKnownUrlsInPerlFormat(q(github)) eq q(github);
18418            
18419             ok expandWellKnownUrlsInHtmlFromPerl(q(L)) eq
18420             q(GitHub);
18421            
18422             is_deeply expandWellKnownWordsAsUrlsInHtmlFormat(q(go to gitHub and press w[enter].)),
18423             q(go to GitHub and press enter.), 'ex1';
18424            
18425             is_deeply expandWellKnownWordsAsUrlsInMdFormat(q(go to gitHub and press w[enter].)),
18426             q(go to [GitHub](https://github.com/philiprbrenan) and press enter.), 'ex2';
18427            
18428             ok expandWellKnownUrlsInPod2Html(<
18429              
18430             =begin HTML
18431              
18432             GitHub
18433              
18434             =end HTML
18435              
18436              
18437             bbb
18438             "';
18439             aaa L bbb
18440             END
18441            
18442              
18443             =head2 expandNewLinesInDocumentation($s)
18444              
18445             Expand new lines in documentation, specifically
18446             for new line and
18447              
18448             for two new lines.
18449              
18450             Parameter Description
18451             1 $s String to be expanded
18452              
18453             B
18454              
18455              
18456            
18457             ok expandNewLinesInDocumentation(q(a
18458              
18459             b
18460             c
18461             )) eq <
18462              
18463             a
18464            
18465             b
18466             c
18467             END
18468            
18469              
18470             =head2 extractCodeBlock($comment, $file)
18471              
18472             Extract the block of code delimited by B<$comment>, starting at qq($comment-begin), ending at qq($comment-end) from the named B<$file> else the current Perl program $0 and return it as a string or confess if this is not possible.
18473              
18474             Parameter Description
18475             1 $comment Comment delimiting the block of code
18476             2 $file File to read from if not $0
18477              
18478             B
18479              
18480              
18481            
18482             ok extractCodeBlock(q(#CODEBLOCK), $INC{"Data/Table/Text.pm"}) eq <<'END'; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
18483              
18484             my $a = 1;
18485             my $b = 2;
18486             END
18487            
18488              
18489             =head2 updateDocumentation($perlModule)
18490              
18491             Update the documentation for a Perl module from the comments in its source code. Comments between the lines marked with:
18492              
18493             #Dn title # description
18494              
18495             and:
18496              
18497             #D
18498              
18499             where n is either 1, 2 or 3 indicating the heading level of the section and the # is in column 1.
18500              
18501             Methods are formatted as:
18502              
18503             sub name(signature) #FLAGS comment describing method
18504             {my ($parameters) = @_; # comments for each parameter separated by commas.
18505              
18506             FLAGS can be chosen from:
18507              
18508             =over
18509              
18510             =item I
18511              
18512             method of interest to new users
18513              
18514             =item P
18515              
18516             private method
18517              
18518             =item r
18519              
18520             optionally replaceable method
18521              
18522             =item R
18523              
18524             required replaceable method
18525              
18526             =item S
18527              
18528             static method
18529              
18530             =item X
18531              
18532             die rather than received a returned B result
18533              
18534             =back
18535              
18536             Other flags will be handed to the method extractDocumentationFlags(flags to process, method name) found in the file being documented, this method should return [the additional documentation for the method, the code to implement the flag].
18537              
18538             Text following 'Example:' in the comment (if present) will be placed after the parameters list as an example. Lines containing comments consisting of '#T'.methodName will also be aggregated and displayed as examples for that method.
18539              
18540             Lines formatted as:
18541              
18542             BEGIN{*source=*target}
18543              
18544             starting in column 1 will define a synonym for a method.
18545              
18546             Lines formatted as:
18547              
18548             #C emailAddress text
18549              
18550             will be aggregated in the acknowledgments section at the end of the documentation.
18551              
18552             The character sequence B<\n> in the comment will be expanded to one new line, B<\m> to two new lines and BB<<$_>>,BB<>,BB<>,BB<>,BB<> to links to the perl documentation.
18553              
18554             Search for '#D1': in L to see more examples of such documentation in action - although it is quite difficult to see as it looks just like normal comments placed in the code.
18555              
18556             Parameters:
18557              
18558              
18559             Parameter Description
18560             1 $perlModule Optional file name with caller's file being the default
18561              
18562             B
18563              
18564              
18565            
18566             {my $s = updateDocumentation(<<'END' =~ s(#) (#)gsr =~ s(~) ()gsr); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
18567              
18568             package Sample::Module;
18569            
18570             #D1 Samples # Sample methods.
18571            
18572             sub sample($@) #R Documentation for the: sample() method. See also L. #Tsample .
18573             {my ($node, @context) = @_; # Node, optional context
18574             1
18575             }
18576            
18577             ~BEGIN{*smpl=*sample}
18578            
18579             sub Data::Table::Text::sample2(\&@) #PS Documentation for the sample2() method.
18580             {my ($sub, @context) = @_; # Sub to call, context.
18581             1
18582             }
18583            
18584             ok sample(undef, qw(a b c)) == 1; #Tsample
18585            
18586             if (1) #Tsample
18587             {ok sample(q(a), qw(a b c)) == 2;
18588             ok sample(undef, qw(a b c)) == 1;
18589             }
18590            
18591             ok sample(<
18592             sample data
18593             END2
18594            
18595             ok $s =~ m/=head2 Data::Table::Text::sample2.\$sub, \@context/;
18596            
18597              
18598              
18599             =head1 Hash Definitions
18600              
18601              
18602              
18603              
18604             =head2 Data::Exchange::Service Definition
18605              
18606              
18607             Service details.
18608              
18609              
18610              
18611              
18612             =head3 Output fields
18613              
18614              
18615             =head4 file
18616              
18617             The file in which the service start details is being recorded.
18618              
18619             =head4 service
18620              
18621             The name of the service.
18622              
18623             =head4 start
18624              
18625             The time this service was started time plus a minor hack to simplify testing.
18626              
18627              
18628              
18629             =head2 Data::Table::Text::AwsEc2Price Definition
18630              
18631              
18632             Prices of selected aws elastic compute instance types
18633              
18634              
18635              
18636              
18637             =head3 Output fields
18638              
18639              
18640             =head4 cheapestInstance
18641              
18642             The instance type that has the lowest CPU cost
18643              
18644             =head4 pricePerCpu
18645              
18646             The cost of the cheapest CPU In millidollars per hour
18647              
18648             =head4 report
18649              
18650             Report showing the cost of other selected instances
18651              
18652              
18653              
18654             =head2 Data::Table::Text::Python::Documentation Definition
18655              
18656              
18657             Documentation extracted from Python source files
18658              
18659              
18660              
18661              
18662             =head3 Output fields
18663              
18664              
18665             =head4 classDefinitions
18666              
18667             Class definitions
18668              
18669             =head4 classFiles
18670              
18671             Class files
18672              
18673             =head4 comments
18674              
18675             Comments for each def
18676              
18677             =head4 errors
18678              
18679             Errors encountered
18680              
18681             =head4 parameters
18682              
18683             Parameters for each def
18684              
18685             =head4 tests
18686              
18687             Tests for each def
18688              
18689             =head4 testsCommon
18690              
18691             Common line for tests
18692              
18693              
18694              
18695             =head2 Data::Table::Text::Starter Definition
18696              
18697              
18698             Process starter definition.
18699              
18700              
18701              
18702              
18703             =head3 Input fields
18704              
18705              
18706             =head4 processingLogFile
18707              
18708             Optional: name of a file to which process start and end information should be appended
18709              
18710             =head4 processingTitle
18711              
18712             Optional: title describing the processing being performed.
18713              
18714             =head4 totalToBeStarted
18715              
18716             Optionally: the total number of processes to be started - if this is supplied then an estimate of the finish time for this processing is printed to the log file every time a process starts or finishes.
18717              
18718              
18719              
18720             =head3 Output fields
18721              
18722              
18723             =head4 autoRemoveTransferArea
18724              
18725             If true then automatically clear the transfer area at the end of processing.
18726              
18727             =head4 maximumNumberOfProcesses
18728              
18729             The maximum number of processes to start in parallel at one time. If this limit is exceeded, the start of subsequent processes will be delayed until processes started earlier have finished.
18730              
18731             =head4 pids
18732              
18733             A hash of pids representing processes started but not yet completed.
18734              
18735             =head4 processFinishTime
18736              
18737             {pid} == time the process finished.
18738              
18739             =head4 processStartTime
18740              
18741             {pid} == time the process was started.
18742              
18743             =head4 processingLogFileHandle
18744              
18745             Handle for log file if a log file was supplied
18746              
18747             =head4 resultsArray
18748              
18749             Consolidated array of results.
18750              
18751             =head4 startTime
18752              
18753             Start time
18754              
18755             =head4 transferArea
18756              
18757             The name of the folder in which files transferring results from the child to the parent process will be stored.
18758              
18759              
18760              
18761             =head2 TestHash Definition
18762              
18763              
18764             Definition of a blessed hash.
18765              
18766              
18767              
18768              
18769             =head3 Output fields
18770              
18771              
18772             =head4 a
18773              
18774             Definition of attribute aa.
18775              
18776             =head4 b
18777              
18778             Definition of attribute bb.
18779              
18780             =head4 c
18781              
18782             Definition of attribute cc.
18783              
18784              
18785              
18786             =head2 Udsr Definition
18787              
18788              
18789             Package name
18790              
18791              
18792              
18793              
18794             =head3 Input fields
18795              
18796              
18797             =head4 headerLength
18798              
18799             Length of fixed header which carries the length of the following message
18800              
18801             =head4 serverAction
18802              
18803             Server action sub, which receives a communicator every time a client creates a new connection. If this server is going to be started by systemd as a service with the specified L then this is the a actual text of the code that will be installed as a CGI script and run in response to an incoming transaction in a separate process with the userid set to L. It receives the text of the http request from the browser as parameter 1 and should return the text to be sent back to the browser.
18804              
18805             =head4 serviceName
18806              
18807             Service name for install by systemd
18808              
18809             =head4 serviceUser
18810              
18811             Userid for service
18812              
18813             =head4 socketPath
18814              
18815             Socket file
18816              
18817              
18818              
18819             =head3 Output fields
18820              
18821              
18822             =head4 client
18823              
18824             Client socket and connection socket
18825              
18826             =head4 serverPid
18827              
18828             Server pid which can be used to kill the server via kill q(kill), $pid
18829              
18830              
18831              
18832             =head1 Attributes
18833              
18834              
18835             The following is a list of all the attributes in this package. A method coded
18836             with the same name in your package will over ride the method of the same name
18837             in this package and thus provide your value for the attribute in place of the
18838             default value supplied for this attribute by this package.
18839              
18840             =head2 Replaceable Attribute List
18841              
18842              
18843             awsEc2DescribeInstancesCache awsIpFile nameFromStringMaximumLength wwwHeader
18844              
18845              
18846             =head2 awsEc2DescribeInstancesCache
18847              
18848             File in which to cache latest results from describe instances to avoid being throttled
18849              
18850              
18851             =head2 awsIpFile
18852              
18853             File in which to save IP address of primary instance on Aws
18854              
18855              
18856             =head2 nameFromStringMaximumLength
18857              
18858             Maximum length of a name generated from a string
18859              
18860              
18861             =head2 wwwHeader
18862              
18863             Html header
18864              
18865              
18866              
18867              
18868             =head1 Private Methods
18869              
18870             =head2 onWindows()
18871              
18872             Are we on windows
18873              
18874              
18875             =head2 onMac()
18876              
18877             Are we on mac
18878              
18879              
18880             =head2 filePathSeparatorChar()
18881              
18882             File path separator
18883              
18884              
18885             =head2 denormalizeFolderName($name)
18886              
18887             Remove any trailing folder separator from a folder name.
18888              
18889             Parameter Description
18890             1 $name Folder name
18891              
18892             =head2 renormalizeFolderName($name)
18893              
18894             Normalize a folder name by ensuring it has a single trailing directory separator.
18895              
18896             Parameter Description
18897             1 $name Name
18898              
18899             =head2 prefferedFileName($name)
18900              
18901             Normalize a file name
18902              
18903             Parameter Description
18904             1 $name Name
18905              
18906             =head2 findAllFilesAndFolders($folder, $dirs)
18907              
18908             Find all the files and folders under a folder.
18909              
18910             Parameter Description
18911             1 $folder Folder to start the search with
18912             2 $dirs True if only folders are required
18913              
18914             =head2 readUtf16File($file)
18915              
18916             Read a file containing L encoded in utf-16.
18917              
18918             Parameter Description
18919             1 $file Name of file to read
18920              
18921             =head2 binModeAllUtf8()
18922              
18923             Set STDOUT and STDERR to accept utf8 without complaint.
18924              
18925              
18926             B
18927              
18928              
18929            
18930             binModeAllUtf8; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
18931              
18932            
18933              
18934             =head2 convertImageToJpx690($Source, $target, $Size, $Tiles)
18935              
18936             Convert a B<$source> image to a B<$target> image in jpx format using versions of L version 6.9.0 and above. The size in pixels of each jpx tile may be specified by the optional B<$Size> parameter which defaults to B<256>. B<$Tiles> optionally provides an upper limit on the number of each tiles in each dimension.
18937              
18938             Parameter Description
18939             1 $Source Source file
18940             2 $target Target folder (as multiple files will be created)
18941             3 $Size Optional size of each tile - defaults to 256
18942             4 $Tiles Optional limit on the number of tiles in either dimension
18943              
18944             =head2 convertImageToJpx($Source, $target, $Size, $Tiles)
18945              
18946             Convert a B<$source> image to a B<$target> image in jpx format. The size in pixels of each jpx tile may be specified by the optional B<$Size> parameter which defaults to B<256>. B<$Tiles> optionally provides an upper limit on the number of each tiles in each dimension.
18947              
18948             Parameter Description
18949             1 $Source Source file
18950             2 $target Target folder (as multiple files will be created)
18951             3 $Size Optional size of each tile - defaults to 256
18952             4 $Tiles Optional limit in either direction on the number of tiles
18953              
18954             B
18955              
18956              
18957            
18958             convertImageToJpx(fpe(qw(a image jpg)), fpe(qw(a image jpg)), 256); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
18959              
18960            
18961              
18962             =head2 setCombination(@s)
18963              
18964             Count the elements in sets B<@s> represented as arrays of strings and/or the keys of hashes
18965              
18966             Parameter Description
18967             1 @s Array of arrays of strings and/or hashes
18968              
18969             =head2 formatTableMultiLine($data, $separator)
18970              
18971             Tabularize text that has new lines in it.
18972              
18973             Parameter Description
18974             1 $data Reference to an array of arrays of data to be formatted as a table
18975             2 $separator Optional line separator to use instead of new line for each row.
18976              
18977             =head2 formatTableClearUpLeft($data)
18978              
18979             Blank identical column values up and left
18980              
18981             Parameter Description
18982             1 $data Array of arrays
18983              
18984             =head2 formatTableAA($data, $title, %options)
18985              
18986             Tabularize an array of arrays.
18987              
18988             Parameter Description
18989             1 $data Data to be formatted
18990             2 $title Reference to an array of titles
18991             3 %options Options
18992              
18993             B
18994              
18995              
18996             ok formatTable
18997             ([[1,1,1],[1,1,2],[1,2,2],[1,2,3]], [], clearUpLeft=>1) eq <
18998            
18999             1 1 1 1
19000             2 2
19001             3 2 2
19002             4 3
19003             END
19004            
19005              
19006             =head2 formatTableHA($data, $title)
19007              
19008             Tabularize a hash of arrays.
19009              
19010             Parameter Description
19011             1 $data Data to be formatted
19012             2 $title Optional titles
19013              
19014             =head2 formatTableAH($data)
19015              
19016             Tabularize an array of hashes.
19017              
19018             Parameter Description
19019             1 $data Data to be formatted
19020              
19021             =head2 formatTableHH($data)
19022              
19023             Tabularize a hash of hashes.
19024              
19025             Parameter Description
19026             1 $data Data to be formatted
19027              
19028             =head2 formatTableA($data, $title)
19029              
19030             Tabularize an array.
19031              
19032             Parameter Description
19033             1 $data Data to be formatted
19034             2 $title Optional title
19035              
19036             =head2 formatTableH($data, $title)
19037              
19038             Tabularize a hash.
19039              
19040             Parameter Description
19041             1 $data Data to be formatted
19042             2 $title Optional title
19043              
19044             =head2 formatTableCheckKeys()
19045              
19046             Options available for formatting tables
19047              
19048              
19049             =head2 reloadHashes2($d, $progress)
19050              
19051             Ensures that all the hashes within a tower of data structures have LValue methods to get and set their current keys.
19052              
19053             Parameter Description
19054             1 $d Data structure
19055             2 $progress Progress
19056              
19057             =head2 showHashes2($d, $keys, $progress)
19058              
19059             Create a map of all the keys within all the hashes within a tower of data structures.
19060              
19061             Parameter Description
19062             1 $d Data structure
19063             2 $keys Keys found
19064             3 $progress Progress
19065              
19066             =head2 showHashes($d)
19067              
19068             Create a map of all the keys within all the hashes within a tower of data structures.
19069              
19070             Parameter Description
19071             1 $d Data structure
19072              
19073             =head2 newUdsr(@parms)
19074              
19075             Create a communicator - a means to communicate between processes on the same machine via L and L.
19076              
19077             Parameter Description
19078             1 @parms Attributes per L
19079              
19080             =head2 awsInstanceId(%options)
19081              
19082             Create an instance-id from the specified B<%options>
19083              
19084             Parameter Description
19085             1 %options Options
19086              
19087             =head2 awsProfile(%options)
19088              
19089             Create a profile keyword from the specified B<%options>
19090              
19091             Parameter Description
19092             1 %options Options
19093              
19094             =head2 awsRegion(%options)
19095              
19096             Create a region keyword from the specified B<%options>
19097              
19098             Parameter Description
19099             1 %options Options
19100              
19101             =head2 getNumberOfCpus()
19102              
19103             Number of cpus
19104              
19105              
19106             =head2 saveSourceToS3($aws, $saveIntervalInSeconds)
19107              
19108             Save source code.
19109              
19110             Parameter Description
19111             1 $aws Aws target file and keywords
19112             2 $saveIntervalInSeconds Save internal
19113              
19114             =head2 awsParallelProcessFilesTestParallel($userData, $file)
19115              
19116             Test running on L in parallel.
19117              
19118             Parameter Description
19119             1 $userData User data
19120             2 $file File to process.
19121              
19122             B
19123              
19124              
19125             my $N = 2001; # Number of files to process
19126             my $options = q(region => q(us-east-2), profile=>q(fmc)); # Aws cli options
19127             my %options = eval "($options)";
19128            
19129             for my $dir(q(/home/phil/perl/cpan/DataTableText/lib/Data/Table/), # Folders we will need on aws
19130             q(/home/phil/.aws/))
19131             {awsParallelSpreadFolder($dir, %options);
19132             }
19133            
19134             my $d = temporaryFolder; # Create a temporary folder
19135             my $resultsFile = fpe($d, qw(results data)); # Save results in this temporary file
19136            
19137             if (my $r = execPerlOnRemote(join "
19138             ", # Execute some code on a server
19139            
19140             getCodeContext(\&awsParallelProcessFilesTestParallel), # Get code context of the sub we want to call. # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
19141              
19142             <
19143             use Data::Table::Text qw(:all);
19144            
19145             my \$r = awsParallelProcessFiles # Process files on multiple L instances in parallel
19146             ({file=>4, time=>timeStamp}, # User data
19147            
19148             \\\&Data::Table::Text::awsParallelProcessFilesTestParallel, # Reference to code to execute in parallel on each session instance # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
19149              
19150             \\\&Data::Table::Text::awsParallelProcessFilesTestResults, # Reference to code to execute in series to merge the results of each parallel computation
19151             [map {writeFile(fpe(q($d), \$_, qw(txt)), \$_)} 1..$N], # Files to process
19152             $options); # Aws cli options as we will be running on Aws
19153            
19154             storeFile(q($resultsFile), \$r); # Save results in a file
19155            
19156             SESSIONLEADER
19157            
19158             {copyFileFromRemote($resultsFile); # Retrieve user data
19159            
19160             my $userData = retrieveFile($resultsFile); # Recover user data
19161             my @i = awsParallelSecondaryIpAddresses(%options); # Ip addresses of secondary instances
19162             my @I = keys $userData->{ip}->%*;
19163             is_deeply [sort @i], [sort @I]; # Each secondary ip address was used
19164            
19165             ok $userData->{file} == 4; # Prove we can pass data in and get it back
19166             ok $userData->{merge} == 1 + @i, 'ii'; # Number of merges
19167            
19168             my %f; my %i; # Files processed on each ip
19169             for my $i(sort keys $userData->{ipFile}->%*) # Ip
19170             {for my $f(sort keys $userData->{ipFile}{$i}->%*) # File
19171             {$f{fn($f)}++; # Files processed
19172             $i{$i}++; # Count files on each ip
19173             }
19174             }
19175            
19176             is_deeply \%f, {map {$_=>1} 1..$N}; # Check each file was processed
19177            
19178             if (1)
19179             {my @rc; my @ra; # Range of number of files processed on each ip - computed, actually counted
19180             my $l = $N/@i-1; # Lower limit of number of files per IP address
19181             my $h = $N/@i+1; # Upper limit of number of files per IP address
19182             for my $i(keys %i)
19183             {my $nc = $i{$i}; # Number of files processed on this ip - computed
19184             my $na = $userData->{ip}{$i}; # Number of files processed on this ip - actually counted
19185             push @rc, ($nc >= $l and $nc <= $h) ? 1 : 0; # 1 - in range, 0 - out of range
19186             push @ra, ($na >= $l and $na <= $h) ? 1 : 0; # 1 - in range, 0 - out of range
19187             }
19188             ok @i == grep {$_} @ra; # Check each ip processed the expected number of files
19189             ok @i == grep {$_} @rc;
19190             }
19191            
19192             ok $userData->{files}{&fpe($d, qw(4 txt))} eq # Check the computed MD5 sum for the specified file
19193             q(a87ff679a2f3e71d9181a67b7542122c);
19194             }
19195            
19196              
19197             =head2 awsParallelProcessFilesTestResults($userData, @results)
19198              
19199             Test results of running on L in parallel.
19200              
19201             Parameter Description
19202             1 $userData User data from primary instance instance or process
19203             2 @results Results from each parallel instance or process
19204              
19205             B
19206              
19207              
19208             my $N = 2001; # Number of files to process
19209             my $options = q(region => q(us-east-2), profile=>q(fmc)); # Aws cli options
19210             my %options = eval "($options)";
19211            
19212             for my $dir(q(/home/phil/perl/cpan/DataTableText/lib/Data/Table/), # Folders we will need on aws
19213             q(/home/phil/.aws/))
19214             {awsParallelSpreadFolder($dir, %options);
19215             }
19216            
19217             my $d = temporaryFolder; # Create a temporary folder
19218             my $resultsFile = fpe($d, qw(results data)); # Save results in this temporary file
19219            
19220             if (my $r = execPerlOnRemote(join "
19221             ", # Execute some code on a server
19222             getCodeContext(\&awsParallelProcessFilesTestParallel), # Get code context of the sub we want to call.
19223             <
19224             use Data::Table::Text qw(:all);
19225            
19226             my \$r = awsParallelProcessFiles # Process files on multiple L instances in parallel
19227             ({file=>4, time=>timeStamp}, # User data
19228             \\\&Data::Table::Text::awsParallelProcessFilesTestParallel, # Reference to code to execute in parallel on each session instance
19229            
19230             \\\&Data::Table::Text::awsParallelProcessFilesTestResults, # Reference to code to execute in series to merge the results of each parallel computation # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
19231              
19232             [map {writeFile(fpe(q($d), \$_, qw(txt)), \$_)} 1..$N], # Files to process
19233             $options); # Aws cli options as we will be running on Aws
19234            
19235             storeFile(q($resultsFile), \$r); # Save results in a file
19236            
19237             SESSIONLEADER
19238            
19239             {copyFileFromRemote($resultsFile); # Retrieve user data
19240            
19241             my $userData = retrieveFile($resultsFile); # Recover user data
19242             my @i = awsParallelSecondaryIpAddresses(%options); # Ip addresses of secondary instances
19243             my @I = keys $userData->{ip}->%*;
19244             is_deeply [sort @i], [sort @I]; # Each secondary ip address was used
19245            
19246             ok $userData->{file} == 4; # Prove we can pass data in and get it back
19247             ok $userData->{merge} == 1 + @i, 'ii'; # Number of merges
19248            
19249             my %f; my %i; # Files processed on each ip
19250             for my $i(sort keys $userData->{ipFile}->%*) # Ip
19251             {for my $f(sort keys $userData->{ipFile}{$i}->%*) # File
19252             {$f{fn($f)}++; # Files processed
19253             $i{$i}++; # Count files on each ip
19254             }
19255             }
19256            
19257             is_deeply \%f, {map {$_=>1} 1..$N}; # Check each file was processed
19258            
19259             if (1)
19260             {my @rc; my @ra; # Range of number of files processed on each ip - computed, actually counted
19261             my $l = $N/@i-1; # Lower limit of number of files per IP address
19262             my $h = $N/@i+1; # Upper limit of number of files per IP address
19263             for my $i(keys %i)
19264             {my $nc = $i{$i}; # Number of files processed on this ip - computed
19265             my $na = $userData->{ip}{$i}; # Number of files processed on this ip - actually counted
19266             push @rc, ($nc >= $l and $nc <= $h) ? 1 : 0; # 1 - in range, 0 - out of range
19267             push @ra, ($na >= $l and $na <= $h) ? 1 : 0; # 1 - in range, 0 - out of range
19268             }
19269             ok @i == grep {$_} @ra; # Check each ip processed the expected number of files
19270             ok @i == grep {$_} @rc;
19271             }
19272            
19273             ok $userData->{files}{&fpe($d, qw(4 txt))} eq # Check the computed MD5 sum for the specified file
19274             q(a87ff679a2f3e71d9181a67b7542122c);
19275             }
19276            
19277              
19278             =head2 s3Profile(%options)
19279              
19280             Return an S3 profile keyword from an S3 option set
19281              
19282             Parameter Description
19283             1 %options Options
19284              
19285             =head2 s3Delete(%options)
19286              
19287             Return an S3 --delete keyword from an S3 option set
19288              
19289             Parameter Description
19290             1 %options Options
19291              
19292             =head2 Data::Table::Text::Starter::logEntry($starter, $finish)
19293              
19294             Create a log entry showing progress and eta.
19295              
19296             Parameter Description
19297             1 $starter Starter
19298             2 $finish 0 - start; 1 - finish
19299              
19300             =head2 Data::Table::Text::Starter::averageProcessTime($starter)
19301              
19302             Average elapsed time spent by each process
19303              
19304             Parameter Description
19305             1 $starter Starter
19306              
19307             =head2 Data::Table::Text::Starter::say($starter, @message)
19308              
19309             Write to the log file if it is available.
19310              
19311             Parameter Description
19312             1 $starter Starter
19313             2 @message Text to write to log file.
19314              
19315             =head2 Data::Table::Text::Starter::waitOne($starter)
19316              
19317             Wait for at least one process to finish and consolidate its results.
19318              
19319             Parameter Description
19320             1 $starter Starter
19321              
19322             =head2 countSquareArray(@square)
19323              
19324             Count the number of elements in a square array
19325              
19326             Parameter Description
19327             1 @square Array of arrays
19328              
19329             =head2 processSizesInParallelN($N, $parallel, $results, @sizes)
19330              
19331             Process items of known size in parallel using the specified number B<$N> processes with the process each file is assigned to depending on the size of the file so that each process is loaded with approximately the same number of bytes of data in total from the files it processes.
19332              
19333             Each file is processed by sub B<$parallel> and the results of processing all files is processed by B<$results> where the files are taken from B<@files>. Each B<&$parallel> receives a file from B<@files>. B<&$results> receives an array of all the results returned by B<&$parallel>.
19334              
19335             Parameter Description
19336             1 $N Number of processes
19337             2 $parallel Parallel sub
19338             3 $results Results sub
19339             4 @sizes Array of [size; item] to process by size
19340              
19341             =head2 wellKnownUrls()
19342              
19343             Short names for some well known urls
19344              
19345              
19346             =head2 reinstateWellKnown($string)
19347              
19348             Contract references to well known Urls to their abbreviated form
19349              
19350             Parameter Description
19351             1 $string Source string
19352              
19353             =head2 formatSourcePodAsHtml()
19354              
19355             Format the L in the current source file as L.
19356              
19357              
19358             =head2 extractTest($string)
19359              
19360             Remove example markers from test code.
19361              
19362             Parameter Description
19363             1 $string String containing test line
19364              
19365             =head2 docUserFlags($flags, $perlModule, $package, $name)
19366              
19367             Generate documentation for a method by calling the extractDocumentationFlags method in the package being documented, passing it the flags for a method and the name of the method. The called method should return the documentation to be inserted for the named method.
19368              
19369             Parameter Description
19370             1 $flags Flags
19371             2 $perlModule File containing documentation
19372             3 $package Package containing documentation
19373             4 $name Name of method to be processed
19374              
19375             =head2 updatePerlModuleDocumentation($perlModule)
19376              
19377             Update the documentation in a B<$perlModule> and display said documentation in a web browser.
19378              
19379             Parameter Description
19380             1 $perlModule File containing the code of the perl module
19381              
19382             =head2 extractPythonDocumentationFromFiles(@sources)
19383              
19384             Extract python documentation from the specified files
19385              
19386             Parameter Description
19387             1 @sources Python source files
19388              
19389              
19390             =head1 Synonyms
19391              
19392             B is a synonym for L - Create a folder name from a list of names.
19393              
19394             B is a synonym for L - Create a file name from a list of names the last of which is assumed to be the extension of the file name.
19395              
19396             B is a synonym for L - Create a file name from a list of names.
19397              
19398             B is a synonym for L - Write to a B<$file>, after creating a path to the $file with L if necessary, a B<$string> of L content encoded as L.
19399              
19400             B is a synonym for L - Create a new, empty, temporary folder.
19401              
19402              
19403              
19404             =head1 Index
19405              
19406              
19407             1 L - Return the name of the given file if it a fully qualified file name else returns B.
19408              
19409             2 L - Absolute file from an absolute file B<$a> plus a relative file B<$r>.
19410              
19411             3 L - Add a certificate to the current ssh session.
19412              
19413             4 L - Generate L scalar methods in the current package if they do not already exist.
19414              
19415             5 L - Append to B<$file> a B<$string> of L content encoded with L, creating the $file first if necessary.
19416              
19417             6 L - Find the product of any strings that look like numbers in an array.
19418              
19419             7 L - Find the sum of any strings that look like numbers in an array.
19420              
19421             8 L - Multiply by B<$multiplier> each element of the array B<@a> and return as the result.
19422              
19423             9 L - Create a hash reference from an array
19424              
19425             10 L - Encode an L string as a string of L digits.
19426              
19427             11 L - Confirm that the specified references are to the specified package
19428              
19429             12 L - Confirm that the specified references are to the package into which this routine has been exported.
19430              
19431             13 L - Get the availability zone of the L server we are currently running on if we are running on an L server else return a blank string.
19432              
19433             14 L - Get the instance id of the L server we are currently running on if we are running on an L server else return a blank string.
19434              
19435             15 L - Get the instance type of the L server if we are running on an L server else return a blank string.
19436              
19437             16 L - Get the ip address of the AWS server we are currently running on if we are running on an L server else return a blank string.
19438              
19439             17 L - Return {instance type} = cheapest spot price in dollars per hour for the given region
19440              
19441             18 L - Get the region of the L server we are currently running on if we are running on an L server else return a blank string.
19442              
19443             19 L - Create an image snap shot with the specified B<$name> of the AWS server we are currently running on if we are running on an AWS server else return false.
19444              
19445             20 L - Describe images available.
19446              
19447             21 L - Describe the L instances running in a B<$region>.
19448              
19449             22 L - Return a hash of {instanceId => public ip address} for all running instances on L with ip addresses.
19450              
19451             23 L - Return details of the specified instance type.
19452              
19453             24 L - Return a hash {spot instance request => spot instance details} describing the status of active spot instances.
19454              
19455             25 L - Find images with a tag that matches the specified regular expression B<$value>.
19456              
19457             26 L - Return the IP address of a named instance on L else return B.
19458              
19459             27 L - Report the prices of all the spot instances whose type matches a regular expression B<$instanceTypeRe>.
19460              
19461             28 L - Request spot instances as long as they can be started within the next minute.
19462              
19463             29 L - Tag an elastic compute resource with the supplied tags.
19464              
19465             30 L - Execute an AWs command and return its response
19466              
19467             31 L - Execute an AWs command and decode the json so produced
19468              
19469             32 L - Create an instance-id from the specified B<%options>
19470              
19471             33 L - Get ip address of server at L.
19472              
19473             34 L - Get an item of meta data for the L server we are currently running on if we are running on an L server else return a blank string.
19474              
19475             35 L - On L: merges all the files in the specified B<$folder> on each secondary instance to the corresponding folder on the primary instance in parallel.
19476              
19477             36 L - Return the IP addresses of all the L session instances.
19478              
19479             37 L - Return the instance id of the primary instance.
19480              
19481             38 L - Return the IP addresses of any primary instance on L.
19482              
19483             39 L - Process files in parallel across multiple L instances if available or in series if not.
19484              
19485             40 L - Test running on L in parallel.
19486              
19487             41 L - Test results of running on L in parallel.
19488              
19489             42 L - Return a list containing the IP addresses of any secondary instances on L.
19490              
19491             43 L - On L: copies a specified B<$folder> from the primary instance, see: L, in parallel, to all the secondary instances in the session.
19492              
19493             44 L - Create a profile keyword from the specified B<%options>
19494              
19495             45 L - Create/Update a B L record for the specified server.
19496              
19497             46 L - Create/Update a B L record for the specified server.
19498              
19499             47 L - Create a region keyword from the specified B<%options>
19500              
19501             48 L - Translate B<$text> from English to a specified B<$language> using AWS Translate with the specified global B<$options> and return the translated string.
19502              
19503             49 L - Set STDOUT and STDERR to accept utf8 without complaint.
19504              
19505             50 L - Convert alphanumerics in a string to bold.
19506              
19507             51 L - Undo alphanumerics in a string to bold.
19508              
19509             52 L - Call the specified B<$sub> in a separate child process, wait for it to complete, then copy back the named B<@our> variables from the child process to the calling parent process effectively freeing any memory used during the call.
19510              
19511             53 L - Call the B<$child> sub reference in parallel in a separate child process and ignore its results while calling the B<$parent> sub reference in the parent process and returning its results.
19512              
19513             54 L - Call a sub reference in parallel to avoid memory fragmentation and return its results.
19514              
19515             55 L - Return the name of the specified file if it exists, else confess the maximum extent of the path that does exist.
19516              
19517             56 L - Check the keys in a B confirm to those B<$permitted>.
19518              
19519             57 L - Recursively find the pids of all the sub processes of a B<$process> and all their sub processes and so on returning the specified pid and all its child pids as a list.
19520              
19521             58 L - Choose a string at random from the list of B<@strings> supplied.
19522              
19523             59 L - Remove all the files and folders under and including the specified B<$folder> as long as the number of files to be removed is less than the specified B<$limitCount>.
19524              
19525             60 L - Compare two arrays of strings
19526              
19527             61 L - Check that the specified b<$cmd> is present on the current system.
19528              
19529             62 L - The name of a folder containing a file
19530              
19531             63 L - Find log two of the lowest power of two greater than or equal to a number B<$n>.
19532              
19533             64 L - Returns the indices at which an B<$item> matches elements of the specified B<@array>.
19534              
19535             65 L - Convert a I B<$inputFile> file to a I B<$outputFile> using B which must not be running elsewhere at the time.
19536              
19537             66 L - Convert a B<$source> image to a B<$target> image in jpx format.
19538              
19539             67 L - Convert a B<$source> image to a B<$target> image in jpx format using versions of L version 6.
19540              
19541             68 L - Convert Perl to Javascript
19542              
19543             69 L - Convert a B<$string> with L code points that are not directly representable in L into string that replaces these code points with their representation in L making the string usable in L documents.
19544              
19545             70 L - Convert a number representing a single unicode point coded in utf32 to utf8.
19546              
19547             71 L - Convert a number representing a single unicode point coded in utf8 to utf32.
19548              
19549             72 L - Copy the binary file B<$source> to a file named <%target> and return the target file name,
19550              
19551             73 L - Normalize the name of the specified B<$source> file to the md5 sum of its content, retaining its current extension, while placing the original file name in a companion file if the companion file does not already exist.
19552              
19553             74 L - Create a file in the specified B<$folder> whose name is constructed from the md5 sum of the specified B<$content>, whose content is B<$content>, whose extension is B<$extension> and which has a companion file with the same name minus the extension which contains the specified B<$companionContent>.
19554              
19555             75 L - Return the original name of the specified B<$source> file after it has been normalized via L or L or return B if the corresponding companion file does not exist.
19556              
19557             76 L - Copy the B<$source> file encoded in utf8 to the specified B<$target> file in and return $target.
19558              
19559             77 L - Copy the specified B<$file> from the server whose ip address is specified by B<$ip> or returned by L.
19560              
19561             78 L - Normalize the name of the specified B<$source> file to the md5 sum of its content, retaining its current extension, while placing the original file name in a companion file if the companion file does not already exist.
19562              
19563             79 L - Create a file in the specified B<$folder> whose name is constructed from the md5 sum of the specified B<$content>, whose content is B<$content>, whose extension is B<$extension> and which has a companion file with the same name minus the extension which contains the specified B<$companionContent>.
19564              
19565             80 L - Delete a normalized and its companion file
19566              
19567             81 L - Return the content of the companion file to the specified B<$source> file after it has been normalized via L or L or return B if the corresponding companion file does not exist.
19568              
19569             82 L - Name a file using the GB Standard
19570              
19571             83 L - Copy the file named in B<$source> to the specified B<$targetFolder/> or if $targetFolder/ is in fact a file into the folder containing this file and return the target file name.
19572              
19573             84 L - Copy the specified local B<$file> to the server whose ip address is specified by B<$ip> or returned by L.
19574              
19575             85 L - Copy the B<$source> folder to the B<$target> folder after clearing the $target folder.
19576              
19577             86 L - Copy the specified local B<$Source> folder to the corresponding remote folder on the server whose ip address is specified by B<$ip> or returned by L.
19578              
19579             87 L - Return a hash which counts the file extensions in and below the folders in the specified list.
19580              
19581             88 L - Return a hash which counts, in parallel with a maximum number of processes: B<$maximumNumberOfProcesses>, the results of applying the B command to each file in and under the specified B<@folders>.
19582              
19583             89 L - Returns the number of occurrences in B<$inString> of B<$searchFor>.
19584              
19585             90 L - Count the number of elements in a square array
19586              
19587             91 L - Create an empty file unless the file already exists and return the name of the file else confess if the file cannot be created.
19588              
19589             92 L - Get the current working directory.
19590              
19591             93 L - Get the path to the folder above the current working folder.
19592              
19593             94 L - Cut out the images embedded in a B file, perhaps produced via L, placing them in the specified folder and replacing them in the source file with:
19594              
19595             .
19596              
19597             95 L - Check that we are the current incarnation of the named service with details obtained from L.
19598              
19599             96 L - Average elapsed time spent by each process
19600              
19601             97 L - Wait for all started processes to finish and return their results as an array.
19602              
19603             98 L - Create a log entry showing progress and eta.
19604              
19605             99 L - Write to the log file if it is available.
19606              
19607             100 L - Start a new process to run the specified B<$sub>.
19608              
19609             101 L - Wait for at least one process to finish and consolidate its results.
19610              
19611             102 L - Year-monthName-day
19612              
19613             103 L - Year-monthNumber-day at hours:minute:seconds
19614              
19615             104 L - Date time stamp without white space.
19616              
19617             105 L - Dump data
19618              
19619             106 L - Decode an L B<$string> in base 64.
19620              
19621             107 L - Convert a L B<$string> to a L data structure.
19622              
19623             108 L - Remove sequentially duplicate words in a string
19624              
19625             109 L - Remove any trailing folder separator from a folder name.
19626              
19627             110 L - Create a one dimensional array from a two dimensional array of arrays
19628              
19629             111 L - Remove L or L tags from a string
19630              
19631             112 L - Generate documentation for a method by calling the extractDocumentationFlags method in the package being documented, passing it the flags for a method and the name of the method.
19632              
19633             113 L - Get the contents of a public repo on GitHub and place them in a temporary folder whose name is returned to the caller or confess if no such repo exists.
19634              
19635             114 L - Get the contents of a B<$user> B<$repo> B<$file> from a public repo on GitHub and return them as a string.
19636              
19637             115 L - Dump to a B<$file> the referenced data B<$structure>.
19638              
19639             116 L - Dump to a B<$file> the referenced data B<$structure> represented as L string.
19640              
19641             117 L - Write to a B<$file> a data B<$structure> through L.
19642              
19643             118 L - Dump a data structure to a temporary file and return the name of the file created
19644              
19645             119 L - Dump a data structure represented as L string to a temporary file and return the name of the file created.
19646              
19647             120 L - Convert alphanumerics in a string to enclosed reversed alphanumerics.
19648              
19649             121 L - Undo alphanumerics in a string to enclosed reversed alphanumerics.
19650              
19651             122 L - Convert alphanumerics in a string to enclosed alphanumerics.
19652              
19653             123 L - Undo alphanumerics in a string to enclosed alphanumerics.
19654              
19655             124 L - Encode an L B<$string> in base 64.
19656              
19657             125 L - Convert a L data B<$structure> to a L string.
19658              
19659             126 L - Read a file containing L content represented as L, L the content, confess to any errors and then return any result with L methods to access each hash element.
19660              
19661             127 L - Read a B<$file> containing L and return the corresponding L data structure.
19662              
19663             128 L - Read a file compressed with L containing L content represented as L, L the content, confess to any errors and then return any result with L methods to access each hash element.
19664              
19665             129 L - Execute some Perl B<$code> on the server whose ip address is specified by B<$ip> or returned by L.
19666              
19667             130 L - Expand new lines in documentation, specifically
19668             for new line and
19669              
19670             for two new lines.
19671              
19672             131 L - Expand short L names found in a string in the format L[url-name] in the L[Dita] Bformat.
19673              
19674             132 L - Expand short L names found in a string in the format L[url-name] using the html B tag.
19675              
19676             133 L - Expand short L names found in a string in the format L[url-name] using the html B tag.
19677              
19678             134 L - Expand short L names found in a string in the format LEurl-nameE using the Perl POD syntax
19679              
19680             135 L - Expand short L names found in a string in the format =begin html format
19681              
19682             136 L - Expand words found in a string using the html B tag to supply a definition of that word.
19683              
19684             137 L - Expand words found in a string using the md url to supply a definition of that word.
19685              
19686             138 L - Extract the block of code delimited by B<$comment>, starting at qq($comment-begin), ending at qq($comment-end) from the named B<$file> else the current Perl program $0 and return it as a string or confess if this is not possible.
19687              
19688             139 L - Extract python documentation from the specified files
19689              
19690             140 L - Remove example markers from test code.
19691              
19692             141 L - Get the extension of a file name.
19693              
19694             142 L - Confess a message with a line position and a file that Geany will jump to if clicked on.
19695              
19696             143 L - Convert a unix B<$file> name to windows format
19697              
19698             144 L - Return the largest B<$file>.
19699              
19700             145 L - Files that match a given search pattern interpreted by L.
19701              
19702             146 L - Get the Md5 sum of the content of a B<$file>.
19703              
19704             147 L - Get the modified time of a B<$file> as seconds since the epoch.
19705              
19706             148 L - Calls the specified sub B<$make> for each source file that is missing and then again against the B<$target> file if any of the B<@source> files were missing or the $target file is older than any of the @source files or if the target does not exist.
19707              
19708             149 L - Create a file name from a list of names.
19709              
19710             150 L - Create a folder name from a list of names.
19711              
19712             151 L - Create a file name from a list of names the last of which is assumed to be the extension of the file name.
19713              
19714             152 L - File path separator
19715              
19716             153 L - Get the size of a B<$file> in bytes.
19717              
19718             154 L - Find all the files and folders under a folder.
19719              
19720             155 L - Find all the folders under a B<$folder> and optionally B<$filter> the selected folders with a regular expression.
19721              
19722             156 L - Find all the files under a B<$folder> and optionally B<$filter> the selected files with a regular expression.
19723              
19724             157 L - Find the first file that exists with a path and name of B<$file> and an extension drawn from <@ext>.
19725              
19726             158 L - Returns the name of the first file from B<@files> that exists or B if none of the named @files exist.
19727              
19728             159 L - First N characters of a string.
19729              
19730             160 L - Flatten an array of scalars, array and hash references to make an array of scalars by flattening the array references and hash values.
19731              
19732             161 L - Remove the path and extension from a file name.
19733              
19734             162 L - Remove the path from a file name.
19735              
19736             163 L - Get the size of a B<$folder> in bytes.
19737              
19738             164 L - Iterate over a hash for each key and value
19739              
19740             165 L - Create text and html versions of a tabular report
19741              
19742             166 L - Wait on all table formatting pids to complete
19743              
19744             167 L - Format an array of arrays of scalars as an html table using the B<%options> described in L.
19745              
19746             168 L - Create an index of html reports.
19747              
19748             169 L - Format the L in the current source file as L.
19749              
19750             170 L - Format the specified B<$string> so it can be displayed in B<$width> columns.
19751              
19752             171 L - Format various B<$data> structures as a table with titles as specified by B<$columnTitles>: either a reference to an array of column titles or a string each line of which contains the column title as the first word with the rest of the line describing that column.
19753              
19754             172 L - Tabularize an array.
19755              
19756             173 L - Tabularize an array of arrays.
19757              
19758             174 L - Tabularize an array of hashes.
19759              
19760             175 L - Tabularize an array of arrays of text.
19761              
19762             176 L - Options available for formatting tables
19763              
19764             177 L - Blank identical column values up and left
19765              
19766             178 L - Tabularize a hash.
19767              
19768             179 L - Tabularize a hash of arrays.
19769              
19770             180 L - Tabularize a hash of hashes.
19771              
19772             181 L - Tabularize text that has new lines in it.
19773              
19774             182 L - Report of all the reports created.
19775              
19776             183 L - Get the path from a file name.
19777              
19778             184 L - Remove the extension from a file name.
19779              
19780             185 L - Full name of a file.
19781              
19782             186 L - Check whether a B<$file> name is fully qualified or not and, optionally, whether it is fully qualified with a specified B<$prefix> or not.
19783              
19784             187 L - Return the fully qualified name of a file.
19785              
19786             188 L - Return a B<$bless>ed hash with the specified B<$attributes> accessible via L method calls.
19787              
19788             189 L - Generate L array methods in the current package.
19789              
19790             190 L - Generate L hash methods in the current package.
19791              
19792             191 L - Generate L scalar methods in the current package, A method whose value has not yet been set will return a new scalar with value B.
19793              
19794             192 L - Generate L scalar methods with default values in the current package.
19795              
19796             193 L - Recreate the code context for a referenced sub
19797              
19798             194 L - Number of cpus
19799              
19800             195 L - Returns the (package, name, file, line) of a perl B<$sub> reference.
19801              
19802             196 L - Create a guid from an md5 hash.
19803              
19804             197 L - Create a guid representation of the L of the content of a string.
19805              
19806             198 L - Hashify a list of file names to get the corresponding folder structure.
19807              
19808             199 L - Decode a string of L digits as an L string.
19809              
19810             200 L - The name of the host we are running on.
19811              
19812             201 L - Generate a table of contents for some html.
19813              
19814             202 L - Return (width, height) of an B<$image>.
19815              
19816             203 L - Indent lines contained in a string or formatted table by the specified string.
19817              
19818             204 L - Find the index of the maximum number in a list of numbers confessing to any ill defined values.
19819              
19820             205 L - Find the index of the minimum number in a list of numbers confessing to any ill defined values.
19821              
19822             206 L - Form the intersection of the specified hashes B<@h> as one hash whose values are an array of corresponding values from each hash
19823              
19824             207 L - Form the intersection of the keys of the specified hashes B<@h> as one hash whose keys represent the intersection.
19825              
19826             208 L - Invert a hash of hashes: given {a}{b} = c return {b}{c} = c
19827              
19828             209 L - Get the first ip address of the specified host via Domain Name Services
19829              
19830             210 L - Get the ip address of a server on the local network by hostname via arp
19831              
19832             211 L - Test whether a string is blank.
19833              
19834             212 L - Return the file name quoted if its contents are in utf8 else return undef
19835              
19836             213 L - Test whether the specified B<$package> contains the subroutine <$sub>.
19837              
19838             214 L - Extract the package name from a java string or file.
19839              
19840             215 L - Extract the package name from a java string or file and convert it to a file name.
19841              
19842             216 L - Extract the Javascript functions marked for export in a file or string.
19843              
19844             217 L - Count keys down to the specified level.
19845              
19846             218 L - Given an array of arrays find the length of the longest sub array.
19847              
19848             219 L - Log messages with a time stamp and originating file and line number.
19849              
19850             220 L - Load an array of arrays from lines of text: each line is an array of words.
19851              
19852             221 L - Load an array from lines of text in a string.
19853              
19854             222 L - Load an array of hashes from lines of text: each line is a hash of words.
19855              
19856             223 L - Load the specified blessed B<$hash> generated with L with B<%attributes>.
19857              
19858             224 L - Load a hash of arrays from lines of text: the first word of each line is the key, the remaining words are the array contents.
19859              
19860             225 L - Load a hash: first word of each line is the key and the rest is the value.
19861              
19862             226 L - Load a hash of hashes from lines of text: the first word of each line is the key, the remaining words are the sub hash contents.
19863              
19864             227 L - Left Pad the specified B<$string> to a multiple of the specified B<$length> with blanks or the specified padding character to a multiple of a specified length.
19865              
19866             228 L - Force die to confess where the death occurred
19867              
19868             229 L - Make the path for the specified file name or folder on the local machine.
19869              
19870             230 L - Make the path for the specified B<$file> or folder on the L instance whose ip address is specified by B<$ip> or returned by L.
19871              
19872             231 L - Return the deepest folder that exists along a given file name path.
19873              
19874             232 L - Convert alphanumerics in a string to L Mathematical Bold Italic.
19875              
19876             233 L - Undo alphanumerics in a string to L Mathematical Bold Italic.
19877              
19878             234 L - Convert alphanumerics in a string to L Mathematical Bold.
19879              
19880             235 L - Undo alphanumerics in a string to L Mathematical Bold.
19881              
19882             236 L - Convert alphanumerics in a string to L Mathematical Italic.
19883              
19884             237 L - Convert alphanumerics in a string to L Mathematical MonoSpace.
19885              
19886             238 L - Undo alphanumerics in a string to L Mathematical MonoSpace.
19887              
19888             239 L - Convert alphanumerics in a string to L Mathematical Sans Serif Bold Italic.
19889              
19890             240 L - Undo alphanumerics in a string to L Mathematical Sans Serif Bold Italic.
19891              
19892             241 L - Convert alphanumerics in a string to L Mathematical Sans Serif Bold.
19893              
19894             242 L - Undo alphanumerics in a string to L Mathematical Sans Serif Bold.
19895              
19896             243 L - Convert alphanumerics in a string to L Mathematical Sans Serif Italic.
19897              
19898             244 L - Undo alphanumerics in a string to L Mathematical Sans Serif Italic.
19899              
19900             245 L - Convert alphanumerics in a string to L Mathematical Sans Serif.
19901              
19902             246 L - Undo alphanumerics in a string to L Mathematical Sans Serif.
19903              
19904             247 L - Find the maximum number in a list of numbers confessing to any ill defined values.
19905              
19906             248 L - Find the longest line in a B<$string>.
19907              
19908             249 L - Recover an md5 sum from a guid.
19909              
19910             250 L - Copy the B<$source> folder into the B<$target> folder retaining any existing files not replaced by copied files.
19911              
19912             251 L - Merge the specified B<$Source> folder from the corresponding remote folder on the server whose ip address is specified by B<$ip> or returned by L.
19913              
19914             252 L - Merge a list of hashes B<@h> by summing their values
19915              
19916             253 L - Micro seconds since unix epoch.
19917              
19918             254 L - Find the minimum number in a list of numbers confessing to any ill defined values.
19919              
19920             255 L - Log messages with a differential time in milliseconds and originating file and line number.
19921              
19922             256 L - Rename the B<$source> file, which must exist, to the B<$target> file but only if the $target file does not exist already.
19923              
19924             257 L - Rename the B<$source> file, which must exist, to the B<$target> file but only if the $target file does not exist already.
19925              
19926             258 L - Create a name from the last folder in the path of a file name.
19927              
19928             259 L - Create a readable name from an arbitrary string of text.
19929              
19930             260 L - Create a readable name from a string of text that might contain a title tag - fall back to L if that is not possible.
19931              
19932             261 L - Create a new L with which to start parallel processes up to a specified B<$maximumNumberOfProcesses> maximum number of parallel processes at a time, wait for all the started processes to finish and then optionally retrieve their saved results as an array from the folder named by B<$transferArea>.
19933              
19934             262 L - Create a new service incarnation to record the start up of a new instance of a service and return the description as a L.
19935              
19936             263 L - Create a communicator - a means to communicate between processes on the same machine via L and L.
19937              
19938             264 L - Create a new communications client - a means to communicate between processes on the same machine via L and L.
19939              
19940             265 L - Create a communications server - a means to communicate between processes on the same machine via L and L.
19941              
19942             266 L - Number of cpus scaled by an optional factor - but only if you have nproc.
19943              
19944             267 L - Return the number of lines in a file.
19945              
19946             268 L - The number of lines in a string.
19947              
19948             269 L - Normalize white space in a string to make comparisons easier.
19949              
19950             270 L - Returns 1 if we are on AWS else return 0.
19951              
19952             271 L - Return 1 if we are on L and we are on the primary session instance as defined by L, return 0 if we are on a secondary session instance, else return B if we are not on L.
19953              
19954             272 L - Return 1 if we are on L but we are not on the primary session instance as defined by L, return 0 if we are on the primary session instance, else return B if we are not on L.
19955              
19956             273 L - Are we on mac
19957              
19958             274 L - Are we on windows
19959              
19960             275 L - Override methods down the list of B<@packages> then reabsorb any unused methods back up the list of packages so that all the packages have the same methods as the last package with methods from packages mentioned earlier overriding methods from packages mentioned later.
19961              
19962             276 L - For each method, if it exists in package B<$from> then export it to package B<$to> replacing any existing method in B<$to>, otherwise export the method from package B<$to> to package B<$from> in order to merge the behavior of the B<$from> and B<$to> packages with respect to the named methods with duplicates resolved if favour of package B<$from>.
19963              
19964             277 L - Write to B<$file>, after creating a path to the file with L if necessary, the binary content in B<$string>.
19965              
19966             278 L - Write to a B<$file>, after creating a path to the $file with L if necessary, a B<$string> of L content encoded as L.
19967              
19968             279 L - Write an L file to /var/www/html and make it readable
19969              
19970             280 L - Write a L file to /usr/lib/cgi-bin and make it executable after checking it for syntax errors
19971              
19972             281 L - Given B<$N> buckets and a list B<@sizes> of ([size of file, name of file].
19973              
19974             282 L - Pad the specified B<$string> to a multiple of the specified B<$length> with blanks or the specified padding character to a multiple of a specified length.
19975              
19976             283 L - Call the specified B<$sub> after classifying the specified array of [arguments] in B<$args> into positional and keyword parameters.
19977              
19978             284 L - Parse a dita reference B<$ref> into its components (file name, topic id, id) .
19979              
19980             285 L - Parse a file name into (path, name, extension) considering .
19981              
19982             286 L - Parse a B<$string> into words and quoted strings.
19983              
19984             287 L - Parse an L bucket/folder name into a bucket and a folder name removing any initial s3://.
19985              
19986             288 L - Parse an L DOCTYPE and return a hash indicating its components
19987              
19988             289 L - Partition a hash of strings and associated sizes into partitions with either a maximum size B<$maxSize> or only one element; the hash B<%Sizes> consisting of a mapping {string=>size}; with each partition being named with the shortest string prefix that identifies just the strings in that partition.
19989              
19990             290 L - Extract the package name from a perl string or file.
19991              
19992             291 L - Test whether a number B<$n> is a power of two, return the power if it is else B.
19993              
19994             292 L - Pad the specified B<$string> to a multiple of the specified B<$length> with blanks or the specified padding character to a multiple of a specified length.
19995              
19996             293 L - Normalize a file name
19997              
19998             294 L - Print an array of words in qw() format.
19999              
20000             295 L - Process files in parallel using (8 * the number of CPUs) processes with the process each file is assigned to depending on the size of the file so that each process is loaded with approximately the same number of bytes of data in total from the files it processes.
20001              
20002             296 L - Process java files of known size in parallel using (the number of CPUs) processes with the process each item is assigned to depending on the size of the java item so that each process is loaded with approximately the same number of bytes of data in total from the java files it processes.
20003              
20004             297 L - Process items of known size in parallel using (8 * the number of CPUs) processes with the process each item is assigned to depending on the size of the item so that each process is loaded with approximately the same number of bytes of data in total from the items it processes.
20005              
20006             298 L - Process items of known size in parallel using the specified number B<$N> processes with the process each file is assigned to depending on the size of the file so that each process is loaded with approximately the same number of bytes of data in total from the files it processes.
20007              
20008             299 L - Quote a file name.
20009              
20010             300 L - Randomize an array
20011              
20012             301 L - Read a binary file on the local machine.
20013              
20014             302 L - Return the content of a file residing on the local machine interpreting the content of the file as L.
20015              
20016             303 L - Copy and read a B<$file> from the remote machine whose ip address is specified by B<$ip> or returned by L and return the content of $file interpreted as utf8 .
20017              
20018             304 L - Read all the files in the specified list of folders into a hash.
20019              
20020             305 L - Read the specified file containing compressed L content represented as L through L.
20021              
20022             306 L - Return the contents of STDIN and return the results as either an array or a string.
20023              
20024             307 L - Read a file containing L encoded in utf-16.
20025              
20026             308 L - Create a two dimensional rectangular array whose first dimension is B<$first> from a one dimensional linear array.
20027              
20028             309 L - Create a two dimensional rectangular array whose second dimension is B<$second> from a one dimensional linear array.
20029              
20030             310 L - Contract references to well known Urls to their abbreviated form
20031              
20032             311 L - Relative file from one absolute file B<$a> against another B<$b>.
20033              
20034             312 L - Ensures that all the hashes within a tower of data structures have LValue methods to get and set their current keys.
20035              
20036             313 L - Ensures that all the hashes within a tower of data structures have LValue methods to get and set their current keys.
20037              
20038             314 L - Remove duplicated leading directory names from a file name.
20039              
20040             315 L - Remove all file paths from a specified B<$structure> to make said $structure testable with L.
20041              
20042             316 L - Removes a file B<$prefix> from an array of B<@files>.
20043              
20044             317 L - Normalize a folder name by ensuring it has a single trailing directory separator.
20045              
20046             318 L - Replace all instances in B<$string> of B<$source> with B<$target>
20047              
20048             319 L - Report the attributes present in a B<$sourceFile>
20049              
20050             320 L - Report the current values of the attribute methods in the calling file and optionally write the report to B<$reportFile>.
20051              
20052             321 L - Report the exportable methods marked with #e in a B<$sourceFile>
20053              
20054             322 L - Report the replaceable methods marked with #r in a B<$sourceFile>
20055              
20056             323 L - Report the current values of parameterless subs.
20057              
20058             324 L - Retrieve a B<$file> created via L.
20059              
20060             325 L - Process the elements of an array in parallel using a maximum of B<$maximumNumberOfProcesses> processes.
20061              
20062             326 L - Process the elements of an array in square root parallel using a maximum of B<$maximumNumberOfProcesses> processes.
20063              
20064             327 L - Return an S3 --delete keyword from an S3 option set
20065              
20066             328 L - Download a specified B<$folder> on S3 to a B<$local> folder using the specified B<%options> if any.
20067              
20068             329 L - Return (name, size, date, time) for a B<$file> that exists on S3 else () using the specified B<%options> if any.
20069              
20070             330 L - Return {file=>size} for all the files in a specified B<$folderOrFile> on S3 using the specified B<%options> if any.
20071              
20072             331 L - Return an S3 profile keyword from an S3 option set
20073              
20074             332 L - Read from a B<$file> on S3 and write the contents to a local file B<$local> using the specified B<%options> if any.
20075              
20076             333 L - Read from a B<$file> on S3 and return the contents as a string using specified B<%options> if any.
20077              
20078             334 L - Write to a file B<$fileS3> on S3 the contents of a local file B<$fileLocal> using the specified B<%options> if any.
20079              
20080             335 L - Write to a B<$file> on S3 the contents of B<$string> using the specified B<%options> if any.
20081              
20082             336 L - Zip the specified B<$source> folder and write it to the named B<$target> file on S3.
20083              
20084             337 L - Zip local folders and upload them to S3 in parallel.
20085              
20086             338 L - Make the server at L with the given domain name the default primary server as used by all the methods whose names end in B or B.
20087              
20088             339 L - Make the server at L with the given IP address the default primary server as used by all the methods whose names end in B or B.
20089              
20090             340 L - Save source code every B<$saveCodeEvery> seconds by zipping folder B<$folder> to zip file B<$zipFileName> then saving this zip file in the specified L B<$bucket> using any additional L parameters in B<$S3Parms>.
20091              
20092             341 L - Save source code.
20093              
20094             342 L - Search the specified directory under the specified folder for sub folders
20095              
20096             343 L - Search the specified directory trees for the files (not folders) that match the specified extensions.
20097              
20098             344 L - Count the elements in sets B<@s> represented as arrays of strings and/or the keys of hashes
20099              
20100             345 L - Given a B<$file>, change its extension to B<$extension>.
20101              
20102             346 L - Intersection of sets B<@s> represented as arrays of strings and/or the keys of hashes
20103              
20104             347 L - Returns the size of the intersection over the size of the union of one or more sets B<@s> represented as arrays and/or hashes
20105              
20106             348 L - Set a package search order for methods requested in the current package via AUTOLOAD.
20107              
20108             349 L - Partition, at a level of B<$confidence> between 0 and 1, a set of sets B<@sets> so that within each partition the L of any two sets in the partition is never less than the specified level of I<$confidence**2>
20109              
20110             350 L - Partition, at a level of B<$confidence> between 0 and 1, a set of sets B<$hashSet> represented by a hash, each hash value being a string containing words and punctuation, each word possibly capitalized, so that within each partition the L of any two sets of words in the partition is never less than the specified B<$confidence**2> and the partition entries are the hash keys of the string sets.
20111              
20112             351 L - Partition, at a level of B<$confidence> between 0 and 1, a set of sets B<$hashSet> represented by a hash, each hash value being a string containing words and punctuation, each word possibly capitalized, so that within each partition the L of any two sets of words in the partition is never less than the specified B<$confidence**2> and the partition entries are the hash keys of the string sets.
20113              
20114             352 L - Partition, at a level of B<$confidence> between 0 and 1, a set of sets B<@sets> of words so that within each partition the L of any two sets of words in the partition is never less than the specified I<$confidence**2>
20115              
20116             353 L - Partition, at a level of B<$confidence> between 0 and 1, a set of sets B<@strings>, each set represented by a string containing words and punctuation, each word possibly capitalized, so that within each partition the L of any two sets of words in the partition is never less than the specified I<$confidence**2>
20117              
20118             354 L - Apply L to a B<$file> to set its B<$permissions>.
20119              
20120             355 L - Union of sets B<@s> represented as arrays of strings and/or the keys of hashes
20121              
20122             356 L - Show the difference between the wanted string and the wanted string
20123              
20124             357 L - Create a map of all the keys within all the hashes within a tower of data structures.
20125              
20126             358 L - Create a map of all the keys within all the hashes within a tower of data structures.
20127              
20128             359 L - Create a two dimensional square array from a one dimensional linear array.
20129              
20130             360 L - Start new processes while the number of child processes recorded in B<%$pids> is less than the specified B<$maximum>.
20131              
20132             361 L - Store into a B<$file>, after creating a path to the file with L if necessary, a data B<$structure> via L.
20133              
20134             362 L - Get the Md5 sum of a B<$string> that might contain L code points.
20135              
20136             363 L - Return the common start followed by the two non equal tails of two non equal strings or an empty list if the strings are equal.
20137              
20138             364 L - Convert alphanumerics in a string to sub scripts
20139              
20140             365 L - Undo alphanumerics in a string to sub scripts
20141              
20142             366 L - Combine zero or more absolute and relative names of B<@files> starting at the current working folder to get an absolute file name.
20143              
20144             367 L - Count the number of unique instances of each value a column in a table assumes.
20145              
20146             368 L - Convert alphanumerics in a string to super scripts
20147              
20148             369 L - Undo alphanumerics in a string to super scripts
20149              
20150             370 L - Swaps the start of a B<$file> name from a B<$known> name to a B<$new> one if the file does in fact start with the $known name otherwise returns the original file name as it is.
20151              
20152             371 L - Given a B<$file>, swap the folder name of the $file from B<$known> to B<$new> if the file $file starts with the $known folder name else return the $file as it is.
20153              
20154             372 L - Download from L by using "aws s3 sync --exclude '*' --include '.
20155              
20156             373 L - Upload to L by using "aws s3 sync --exclude '*' --include '.
20157              
20158             374 L - Create a new, empty, temporary file.
20159              
20160             375 L - Create a new, empty, temporary folder.
20161              
20162             376 L - hours:minute:seconds
20163              
20164             377 L - Transitive closure of a hash of hashes
20165              
20166             378 L - Remove any white space from the front and end of a string.
20167              
20168             379 L - Kill a communications server.
20169              
20170             380 L - Read a message from the L or the L.
20171              
20172             381 L - Create a systemd installed server that processes http requests using a specified userid.
20173              
20174             382 L - Write a communications message to the L or the L.
20175              
20176             383 L - Remove the effects of bless from a L data B<$structure> enabling it to be converted to L or compared with L.
20177              
20178             384 L - Form the union of the specified hashes B<@h> as one hash whose values are a array of corresponding values from each hash
20179              
20180             385 L - Form the union of the keys of the specified hashes B<@h> as one hash whose keys represent the union.
20181              
20182             386 L - Create a unique name from a file name and the md5 sum of its content
20183              
20184             387 L - Update the documentation for a Perl module from the comments in its source code.
20185              
20186             388 L - Update the documentation in a B<$perlModule> and display said documentation in a web browser.
20187              
20188             389 L - Get or confirm the userid we are currently running under.
20189              
20190             390 L - YYYYmmdd-HHMMSS
20191              
20192             391 L - YYYY-mm-dd-HH:MM:SS
20193              
20194             392 L - Wait until all the processes started by L have finished.
20195              
20196             393 L - Short names for some well known urls
20197              
20198             394 L - Write to a new B<$file>, after creating a path to the file with L if necessary, the binary content in B<$string>.
20199              
20200             395 L - Write to a new B<$file>, after creating a path to the $file with L if necessary, a B<$string> of L content encoded as L.
20201              
20202             396 L - Write the values of a B<$hash> reference into files identified by the key of each value using L optionally swapping the prefix of each file from B<$old> to B<$new>.
20203              
20204             397 L - Write to a new B<$file>, after creating a path to the file with L if necessary, a B<$string> of L content encoded as L then copy the $file to the remote server whose ip address is specified by B<$ip> or returned by L.
20205              
20206             398 L - Write to a B<$file>, after creating a path to the file with L if necessary, through L a B<$string> whose content is encoded as L.
20207              
20208             399 L - Write a test for a data B<$structure> with file names in it.
20209              
20210             400 L - Write an array of strings as lines to a temporary file and return the file name.
20211              
20212             401 L - Percent decode a L B<$string> per: https://en.
20213              
20214             402 L - Percent encode a L per: https://en.
20215              
20216             403 L - Logon as a L L app per: L
20217              
20218             404 L - Execute a shell command optionally checking its response.
20219              
20220             405 L - Execute a command B<$cmd> via bash on the server whose ip address is specified by B<$ip> or returned by L.
20221              
20222             406 L - Execute a block of shell commands line by line after removing comments - stop if there is a non zero return code from any command.
20223              
20224             407 L - Execute lines of commands after replacing new lines with && then check that the pipeline execution results in a return code of zero and that the execution results match the optional regular expression if one has been supplied; confess() to an error if either check fails.
20225              
20226             =head1 Installation
20227              
20228             This module is written in 100% Pure Perl and, thus, it is easy to read,
20229             comprehend, use, modify and install via B:
20230              
20231             sudo cpan install Data::Table::Text
20232              
20233             =head1 Author
20234              
20235             L
20236              
20237             L
20238              
20239             =head1 Copyright
20240              
20241             Copyright (c) 2016-2021 Philip R Brenan.
20242              
20243             This module is free software. It may be used, redistributed and/or modified
20244             under the same terms as Perl itself.
20245              
20246              
20247             =head1 Acknowledgements
20248              
20249             Thanks to the following people for their help with this module:
20250              
20251             =over
20252              
20253              
20254             =item L
20255              
20256             Testing on windows
20257              
20258              
20259             =back
20260              
20261              
20262             =cut
20263              
20264              
20265              
20266             # Tests and documentation
20267              
20268 371     371 0 4823 sub test
20269 371         3339 {my $p = __PACKAGE__;
20270 371 50       23744 binmode($_, ":utf8") for *STDOUT, *STDERR;
20271 371         18921 return if eval "eof(${p}::DATA)";
20272 371 50       26712 my $s = eval "join('', <${p}::DATA>)";
20273 371     371   235956 $@ and die $@;
  371     371   24460030  
  371     371   3710  
  371     371   437409  
  371     371   1484  
  371     536   3339  
  371     0   3229555  
  371     328   1113  
  371     416   2597  
  371     328   2332848  
  371     536   1113  
  371     536   3339  
  371     804   226681  
  371     1072   742  
  371     268   1855  
  371     268   83104  
  536     536   2680  
  0         0  
  328         1716  
  416         2194  
  328         1718  
  536         4750  
  536         4630  
  804         3752  
  1072         10720  
  268         3888  
  268         3884  
  536         4288  
20274 1 50       25 eval $s;
20275 1         232 $@ and die $@;
20276             1
20277             }
20278              
20279             test unless caller;
20280              
20281             1;
20282             # podDocumentation
20283             __DATA__