File Coverage

blib/lib/Data/Table/Text.pm
Criterion Covered Total %
statement 2597 3944 65.8
branch 786 1638 47.9
condition 255 1776 14.3
subroutine 379 987 38.4
pod 396 397 99.7
total 4413 8742 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 337     337   1185903 use v5.26;
  337         4718  
18             our $VERSION = 20210825; # Version
19 337     337   2022 use warnings FATAL => qw(all);
  337         337  
  337         14828  
20 337     337   1348 use strict;
  337         337  
  337         13480  
21 337     337   1685 use Carp qw(confess carp cluck);
  337         674  
  337         34374  
22 337     337   2022 use Cwd;
  337         674  
  337         28645  
23 337     337   1685 use Digest::MD5 qw(md5_hex);
  337         337  
  337         20557  
24 337     337   2022 use File::Path qw(make_path);
  337         674  
  337         21905  
25 337     337   1685 use File::Glob qw(:bsd_glob);
  337         2696  
  337         81891  
26 337     337   269937 use File::Temp qw(tempfile tempdir);
  337         7631365  
  337         23927  
27 337     337   150976 use POSIX qw(:sys_wait_h strftime); # Http://www.cplusplus.com/reference/ctime/strftime/
  337         2048960  
  337         1685  
28 337     337   713766 use Data::Dump qw(dump);
  337         1588618  
  337         25949  
29 337     337   205570 use IO::Socket::UNIX;
  337         4020410  
  337         2022  
30 337     337   379462 use JSON;
  337         3253735  
  337         2359  
31 337     337   249380 use MIME::Base64;
  337         206244  
  337         27971  
32 337     337   2696 use Scalar::Util qw(blessed reftype looks_like_number);
  337         674  
  337         17861  
33 337     337   197482 use Storable qw(store retrieve dclone);
  337         934501  
  337         28645  
34 337     337   199167 use Time::HiRes qw(time gettimeofday);
  337         431023  
  337         1348  
35 337     337   70096 use B;
  337         674  
  337         14828  
36 337     337   223768 use utf8;
  337         4381  
  337         1685  
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 3300     3300 1 212062 {strftime('%Y-%m-%d at %H:%M:%S', localtime)
42             }
43              
44             sub dateTimeStampName # Date time stamp without white space.
45 334     334 1 19706 {strftime('_on_%Y_%m_%d_at_%H_%M_%S', localtime)
46             }
47              
48             sub dateStamp # Year-monthName-day.
49 334     334 1 74482 {strftime('%Y-%b-%d', localtime)
50             }
51              
52             sub versionCode # YYYYmmdd-HHMMSS.
53 334     334 1 17368 {strftime('%Y%m%d-%H%M%S', localtime)
54             }
55              
56             sub versionCodeDashed # YYYY-mm-dd-HH:MM:SS.
57 334     334 1 19372 {strftime('%Y-%m-%d-%H:%M:%S', localtime)
58             }
59              
60             sub timeStamp # Hours:minute:seconds.
61 57307     57307 1 2954047 {strftime('%H:%M:%S', localtime)
62             }
63              
64             sub microSecondsSinceEpoch # Micro seconds since unix epoch.
65 334     334 1 2004 {my ($s, $u) = gettimeofday();
66 334         5010 $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 cluck "$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 702     702 1 3978 {my (@cmd) = @_; # Command to execute followed by an optional regular expression to test the results
151 702 50       7722 @cmd or confess "No command\n"; # Check that there is a command to execute
152 702   33     10764 $_ or confess "Missing command component\n" for @cmd; # Check that there are no undefined command components
153 702         5382 my $success = $cmd[-1]; # Error check if present
154 702         13338 my $check = ref($success) =~ /RegExp/i; # Check for error check
155 702 50       9126 pop @cmd if $check; # Remove check from command
156 702         5850 my $cmd = join ' ', @cmd; # Command to execute
157 702 50       7722 say STDERR $cmd unless $check; # Print the command unless there is a check in place
158 702   50     2808 my $response = eval {qx($cmd 2>&1)} // "No such command"; # Execute command
  702         28217124  
159 702         19656 $response =~ s/\s+\Z//s; # Remove trailing white space from response
160 702 50 33     12168 say STDERR $response if $response and !$check; # Print non blank error message
161 702 0 33     7956 confess $response if $response and $check and $response !~ m/$success/; # Error check if an error checking regular expression has been supplied
      33        
162 702 50 33     28080 confess $response if $response and $response =~ m/Syntax error:.*unexpected/; # Check for a particularly annoying error
163 702         122616 $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 674     674 1 5729 {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 674 50       7077 $cmd or confess "No command\n"; # Check that there is a command to execute
191 674         2696 my @c; # Commands
192 674         4718 for(split /\n/, $cmd) # Split commands on new lines
193 1348         5392 {s(#.*\Z)()gs; # Remove comments
194 1348 50       7077 next unless m(\S); # Skip blank lines
195 1348         4044 push @c, $_; # Save command
196             }
197 674         3370 my $c = join ' && ', @c; # Command string to execute
198 674         2808895 my $r = qx($c 2>&1); # Execute command
199 674         20557 my $R = $?;
200 674         27971 $r =~ s/\s+\Z//s; # Remove trailing white space from response
201              
202 674 50 0     1261391 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 337         25275 $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 1011     1011 1 5055 {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 1011 100   1011   5729 {return () unless $valid; # No keywords definitions
224 674 50       6066 return map {lc($_)=>0} @$valid if ref($valid) =~ m(array)is; # Keyword names as an array but with no explanation
  2022         9099  
225 0         0 %$valid # Hash of keyword name=>explanation
226 1011         12132 }->();
227              
228 1011         8425 my %keywords;
229             my @positionals;
230 1011         7077 for my $arg(@$args) # Each arg
231 5055 100       30667 {if ($arg =~ m/\A-+(\S+?)\s*(=\s*(.+)\s*)?\Z/) # Keyword parameters with leading and trailing blanks removed
232 3370 100 100     16850 {if ($valid and !defined($valid{lc($1)})) # Validate keyword name
233 337         2696 {my @s;
234 337         2696 for my $k(sort keys %valid) # Create a table of valid keywords
235 1011 50       2359 {if (my $v = $valid{$k})
236 0         0 {push @s, [$k, $v];
237             }
238             else
239 1011         2359 {push @s, [$k];
240             }
241             }
242 337 50       6066 if (@s) # Format error message
243 337         9773 {my $s = formatTable(\@s, [qw(Keyword Description)]);
244 337         80206 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 3033         20220 $keywords{lc($1)} = $3; # Save valid keyword parameter
251             }
252             else # Positional parameter
253 1685         3370 {push @positionals, $arg;
254             }
255             }
256 674         20557 $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 1008     1008 1 4704 {my ($sub, @our) = @_; # Sub to call, names of our variable names with preceding sigils to copy back
261 1008         4369 my ($package) = caller; # Caller's package
262 1008         6717 my $folder = &temporaryFolder; # Folder for returned data files
263 1008         1148888 my $pid = fork; # Fork
264 1008 50       66052 if (!defined($pid)) # Fork failed
    100          
265 0         0 {confess "Unable to fork!\n";
266             }
267             elsif ($pid == 0) # Fork - child
268 3         1107 {&$sub; # Execute the sub
269 3         67 my @save = ''; # Code to copy back our variables
270 3         93 for my $o(@our) # Each variable
271 9         171 {my ($sigil, $var) = $o =~ m(\A(.)(.+)\Z)s; # Sigil, variable name
272 9         72 my $our = $sigil.$package.q(::).$var; # Add caller's package to variable name
273 9         67 my $char = ord($sigil); # Differentiate between variables with the same type but different sigils
274 9         203 my $file = fpe($folder, qq(${$}$var$char), q(data)); # File for this variable
275 9         95 push @save, <
276             store \\$our, q($file);
277             END
278             }
279 3         74 my $save = join "\n", @save; # Perl code to store our variables
280 3         919 eval $save; # Evaluate code to store our variables
281 3 50       546 confess $@ if $@; # Confess any errors
282 3         16183 exit; # End of child process
283             }
284             else # Fork - parent
285 1005         3784511083 {waitpid $pid,0; # Wait for child
286 1005         35838 my @save = ''; # Code to retrieve our variables
287 1005         5367 my @file; # Transfer files
288 1005         16091 for my $o(@our)
289 3015         61962 {my ($sigil, $var) = $o =~ m(\A(.)(.+)\Z)s; # Sigil, variable name
290 3015         10052 my $our = $sigil.$package.q(::).$var; # Add caller's package to variable name
291 3015         8716 my $char = ord($sigil); # Differentiate between variables with the same type but different sigils
292 3015         31843 my $file = fpe($folder, qq($pid$var$char), q(data)); # Save file
293 3015         19423 push @save, <
294             $our = ${sigil}{retrieve q($file)};
295             END
296 3015         9719 push @file, $file; # Remove transfer files
297             }
298 1005         6711 my $save = join "\n", @save;
299 1005         302806 eval $save; # Evaluate perl code
300 1005         96475 my $r = $@; # Save result
301 1005         23124 clearFolder($folder, scalar(@our)+1); # Remove transfer files
302 1005 50       149082 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 82067     82067 1 18062306 {my ($file) = @_; # File name
311 82067 50       84743123 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 124     124 1 1240 {my (@files) = @_; # File names
317 1116         4712 my ($l) = map {$$_[1]} sort {$$b[0] <=> $$a[0]} # Largest file
  2356         7688  
318 124   50     620 map {[fileSize($_)//0, $_]} @files;
  1116         5580  
319 124         7688 $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 # Folder nnnn
328             }
329              
330             sub fileMd5Sum($) # Get the Md5 sum of the content of a B<$file>.
331 468     468 1 1404 {my ($file) = @_; # File or string
332 468 50 33     11700 if ($file !~ m(\0|\n|\A\.|\A\/\Z)s and -e $file) # From file - this is not entirely satisfactory.
333 468         1872 {my $s = readBinaryFile($file);
334 468         4680 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 468     468 1 1638 {my ($m) = @_; # Md5 hash
344 468 50 0     1872 length($m) == 32 or confess "Not an md5 hash: ". ($m//"undef");
345 468         4680 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 234     234 1 702 {my ($G) = @_; # Guid
351 234 50       2808 length($G) >= 41 or confess "Incorrect length for guid: $G"; # Check guid
352 234         702 my $g = substr($G, 0, 41);
353 234 50       7722 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 234     234 1 936 {my ($string) = @_; # String
359 234         936 guidFromMd5 &stringMd5Sum($string);
360             }
361              
362             sub fileModTime($) # Get the modified time of a B<$file> as seconds since the epoch.
363 334     334 1 2672 {my ($file) = @_; # File name
364 334   50     16700 (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 337     337 1 1348 {my (@files) = @_; # Files to check
390 337         1348 for(@files)
391 674 100       44821 {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 234     234 1 702 {my ($file) = @_; # File
398 234         3042 $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 160935     160935 1 235178255 {$^O =~ m(MSWin32)
407             }
408              
409             sub onMac #P Are we on mac.
410 571     571 1 14097 {$^O =~ m(darwin)
411             }
412              
413             sub filePathSeparatorChar #P File path separator.
414 33215 50   33215 1 106190 {onWindows ? '\\' : '/';
415             }
416              
417             sub denormalizeFolderName($) #P Remove any trailing folder separator from a folder name.
418 165719     165719 1 24792842 {my ($name) = @_; # Folder name
419 165719         30269506 $name =~ s([\/\\]+\Z) ()gsr;
420             }
421              
422             sub renormalizeFolderName($) #P Normalize a folder name by ensuring it has a single trailing directory separator.
423 11751     11751 1 33899 {my ($name) = @_; # Name
424 11751         3253812 ($name =~ s([\/\\]+\Z) ()gsr).filePathSeparatorChar; # Put a trailing / on the folder name
425             }
426              
427             sub prefferedFileName($) #P Normalize a file name.
428 97326     97326 1 37464169 {my ($name) = @_; # Name
429 97326 50       15886369 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 80873     80873 1 29095340 {my (@file) = @_; # File name components
435 80873   50     23636696 defined($_) or confess "Missing file component\n" for @file; # Check that there are no undefined file components
436 80873         13153244 my @components = grep {$_} map {denormalizeFolderName($_)} @file; # Skip blank components
  165719         89387012  
  165719         75498371  
437 80873 100       13435519 return '' unless @components; # No components resolves to '' rather than '/'
438 80536         65684821 prefferedFileName join '/', @components; # Join separate components
439             }
440              
441             sub filePathDir(@) # Create a folder name from a list of names. Identical to L.
442 11751     11751 1 76494 {my (@file) = @_; # Directory name components
443 11751         65727 my $file = filePath(@_);
444 11751 100       3271112 return '' unless $file; # No components resolves to '' rather than '/'
445 11414         4670853 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 68661     68661 1 118868034 {my (@File) = @_; # File name components and extension
450 68661 50       13072395 my @file = grep{defined and /\S/} @_; # Remove undefined and blank components
  210246         152603122  
451 68661 50       9694783 @file > 1 or confess "At least two non blank file name components required\n";
452 68661         178746 my $x = pop @file;
453 68661         11747386 my $n = pop @file;
454 68661         35194914 my $f = "$n.$x";
455 68661 100       17297791 return $f unless @file;
456 67079         34198747 filePath(@file, $f)
457             }
458              
459 337     337   1408997 BEGIN{*fpd=*filePathDir}
460 337     337   6066 BEGIN{*fpe=*filePathExt}
461 337     337   826661 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 2278     2278 1 10401 {my ($file) = @_; # File name
467 2278 50       7142 $file or confess "File required";
468 2278 50       5315 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 2278 50       11938 {return '' unless $file =~ m(/); # Must have a / in it else no path
474 2278         24039 $file =~ s([^/]*\Z) ()gsr
475             }
476             }
477              
478             sub fpn($) # Remove the extension from a file name.
479 674     674 1 1685 {my ($file) = @_; # File name
480 674 50       2696 $file or confess "File required";
481 674 50       1685 if (onWindows)
482 0 0       0 {return '' unless $file =~ m(\\); # Must have a \ in it else no path
483             }
484             else
485 674 50       5055 {return '' unless $file =~ m(/); # Must have a / in it else no path
486             }
487 674         5729 $file =~ s(\.[^.]+?\Z) ()gsr
488             }
489              
490             sub fn($) #I Remove the path and extension from a file name.
491 2368     2368 1 11284 {my ($file) = @_; # File name
492 2368 50       6935 $file or confess "File required";
493 2368 50       5782 if (onWindows)
494 0         0 {$file =~ s(\A.*\\) ()gsr =~ s(\.[^.]+?\Z) ()gsr
495             }
496             else
497 2368         39908 {$file =~ s(\A.*/) ()gsr =~ s(\.[^.]+?\Z) ()gsr
498             }
499             }
500              
501             sub fne($) # Remove the path from a file name.
502 3714     3714 1 15187 {my ($file) = @_; # File name
503 3714 50       11131 $file or confess "File required";
504 3714 50       12778 if (onWindows)
505 0         0 {$file =~ s(\A.*\\) ()gsr;
506             }
507             else
508 3714         91016 {$file =~ s(\A.*/) ()gsr;
509             }
510             }
511              
512             sub fe($) # Get the extension of a file name.
513 2578     2578 1 7066 {my ($file) = @_; # File name
514 2578 50       7640 $file or confess "File required";
515 2578 50       13978 return '' unless $file =~ m(\.)s; # Must have a period
516 2578         16219 my $f = $file =~ s(\.[^.]*?\Z) ()gsr;
517 2578         20909 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 1345     1345 1 8738 {my ($file) = @_; # File to check
522 1345 100       27592 unless(-e $file)
523 671         5025 {confess "Can only find the prefix (below) of the file (further below):\n".
524             matchPath($file)."\n$file\n";
525             }
526             $file
527 674         4381 }
528              
529             sub quoteFile($) # Quote a file name.
530 334     334 1 3674 {my ($file) = @_; # File name
531 334 50       3674 $file or confess "Undefined file to quote";
532 334         3006 $file =~ s(") (\\\")gs;
533 334         1336 $file =~ s(\$) (\\\$)gs;
534 334         2338 qq(\"$file\")
535             }
536              
537             sub removeFilePrefix($@) # Removes a file B<$prefix> from an array of B<@files>.
538 674     674 1 2022 {my ($prefix, @files) = @_; # File prefix, array of file names
539 674         2022 my @f = map {s(\A$prefix) ()r} @files;
  1011         15165  
540 674 50 66     4044 return $f[0] if @f == 1 and !wantarray; # Special case of wanting one file in scalar context
541             @f
542 674         54257 }
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 473     473 1 3180 {my ($file, $known, $new) = @_; # File name, existing prefix, optional new prefix defaults to q()
546 473         1183 my $L = length($file);
547 473         1199 my $l = length($known);
548 473 50       2123 if ($L >= $l)
549 473 50       3365 {if (substr($file, 0, $l) eq $known)
550 473   100     9546 {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 1045     1045 1 6233 {my ($file, $extension) = @_; # File name, optional new extension
559 1045 50       3586 return $file =~ s(\.\w+\Z) ()sr unless defined $extension; # Remove extension
560 1045         4081 my $ext = $extension =~ s(\A\.+) ()gsr; # Remove leading dots
561 1045 50       4119 return $file unless $ext; # No extension after dot removal
562 1045         12174 ($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 234     234 1 936 {my ($file, $known, $new) = @_; # File name, existing prefix, new prefix
567 234         702 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 936     936 1 2574 {my ($file, $prefix) = @_; # File name to test, file name prefix
572 936 100       5616 return $file =~ m(\A/)s unless $prefix; # Check against /
573 468         5148 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 1011     1011 1 2696 {my ($file) = @_; # File name
584 1011 100       7414 return $file unless $file =~ m(/)s; # No path to deduplicate
585 674 50       3033 return $file if $file =~ m(\A[/.]); # Later
586 674         2359 my ($p, @p) = split m(/), $file;
587 674   66     4381 shift @p while @p && $p[0] eq $p;
588 674         4044 join "/", $p, @p;
589             } # removeDuplicatePrefixes
590              
591             sub containingFolderName($) # The name of a folder containing a file.
592 35     35 1 304 {my ($file) = @_; # File name
593 35         226 my @p = split m(/), $file;
594 35 50       336 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 337     337 1 7077 {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 4278     4278 1 9960 {my ($file) = @_; # File name to parse
615 4278 50       10035 defined($file) or confess "File required";
616 4278 100 66     29880 return ($file) if $file =~ m{\/\Z}s or $file =~ m/\.\.\Z/s; # Its a folder
617 3267 100       14266 if ($file =~ m/\.[^\/]+?\Z/s) # The file name has an extension
618 2256 100       9080 {if ($file =~ m/\A.+[\/]/s) # The file name has a preceding path
619 1919         8275 {my @f = $file =~ m/(\A.+[\/])([^\/]*)\.([^\/]+?)\Z/s; # File components
620 1919         13929 return @f;
621             }
622             else # There is no preceding path
623 337         2022 {my @f = $file =~ m/(\A.+)\.([^\/]+?)\Z/s; # File components
624 337         2696 return (undef, @f)
625             }
626             }
627             else # The file name has no extension
628 1011 100       4718 {if ($file =~ m/\A.+[\/]/s) # The file name has a preceding path
    50          
    0          
629 674         3033 {my @f = $file =~ m/(\A.+\/)([^\/]+?)\Z/s; # File components
630 674         4381 return @f;
631             }
632             elsif ($file =~ m/\A[\/]./s) # The file name has a single preceding /
633 337         2696 {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 15062     15062 1 56363 {my ($a, $b) = @_; # Absolute file to be made relative, against this absolute file.
652              
653 15062 100       44493 my $m = length($a) < length($b) ? length($a) : length($b); # Shortest length
654              
655 15062 50       53901 $a =~ m(\A/) or confess "$a is not absolute"; # Require absolute file names
656 15062 50       42958 $b =~ m(\A/) or confess "$b is not absolute";
657 15062         65930 $b =~ s([^/]+\Z) (); # Make the against file into a folder
658              
659 15062         25069 my $s = 0; # Position of last matching /
660              
661 15062         39747 for my $i(1..$m-1) # Locate first non matching character - the first character of both file names is / which matches
662 124109 100       270076 {if (substr($a, $i, 1) ne substr($b, $i, 1)) # First mismatch
    100          
663 12029         15296 {my $u = 0; # Number of jumps up from $b
664 12029         16644 my $p = $s; # Last /
665 12029         38343 ++$u while(($p = index($b, q(/), $p+1)) > -1); # Number of / to jump up
666 12029         92665 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 24760         34692 {$s = $i;
670             }
671             }
672 3033         5392 my $u = 0; # Number of jumps up from $b
673 3033         4044 my $p = $s; # Last /
674 3033         10784 ++$u while(($p = index($b, q(/), $p+1)) > -1); # Number of / to jump up
675 3033         20557 ((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 15839     15839 1 53583 {my ($a, $r) = @_; # Absolute file, relative file
680              
681 15839 50       41451 return $r if $r =~ m(\A/); # Return absolute file if such is supplied
682 15839 50       48191 $a =~ m(\A/) or confess "$a is not absolute"; # Require absolute file name
683 15839         71781 $a =~ s([^/]+\Z) (); # Make the absolute file into a folder
684 15839         43136 $r =~ s(\A\./) (); # Remove any leading ./ from relative file
685 15839         23590 $r =~ s(\.\.\Z) (../); # Make trailing .. into a folder
686              
687 15839         31004 my $R = qq($a$r); # Combine and .
688 15839         93686 undef while $R =~ s([^/]+/\.\./) (); # Squeeze out jumps
689              
690 15839         74477 $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 1348     1348 1 3370 {my ($file) = @_; # File to test
695 1348 100       6066 return $file if $file =~ m(\A/);
696             undef
697 674         3033 }
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 337     337 1 1348 {my (@files) = @_; # Absolute and relative file names
701 337         5392 unshift @files, currentDirectory;
702 337         2359 while(@files > 1)
703 1011         2359 {my $a = shift @files;
704 1011         2022 my $b = shift @files;
705 1011 100       2022 unshift @files, absFile($b) ? $b : absFromAbsPlusRel($a, $b);
706             }
707 337         2359 $files[0]
708             } # sumAbsAndRel
709              
710             #D2 Temporary # Temporary files and folders
711              
712             sub temporaryFile # Create a new, empty, temporary file.
713 5894     5894 1 49688 {my ($fh, $filename) = tempfile;
714 5894         2689771 $filename
715             }# temporaryFile
716              
717             sub temporaryFolder # Create a new, empty, temporary folder.
718 5485     5485 1 8200713 {my $d = tempdir();
719 5485         93848605 $d =~ s/[\/\\]+\Z//s;
720 5485         39549 $d.filePathSeparatorChar;
721             } # temporaryFolder
722              
723 337     337   1054473 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 9028     9028 1 3118379 {my ($folder, $dirs) = @_; # Folder to start the search with, true if only folders are required
729 9028         57964 my @files; # Files
730              
731 9028 50       2827718 if (onWindows)
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 9028 50       5861472 return undef unless confirmHasCommandLineCommand(q(find)); # Confirm we have find
743 9028 100       77844 my $c = qq(find "$folder" -print0 -type ).($dirs ? 'd' : 'f'); # Use find command to find files
744 9028         73736125 my $res = qx($c); # Execute find command
745 9028 50       158263 defined($res) or confess "No result from find command below\n$c\n"; # Find failed for some reason
746 9028         142351 utf8::decode($res); # Decode unicode file names
747 9028         653969 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 4443     4443 1 36390 {my ($folder, $filter) = @_; # Folder to start the search with, optional regular expression to filter files
752 4443         3006650 my @files; # Files
753 4443         50599 for(findAllFilesAndFolders($folder, 0)) # All files and folders
754 63648 100       766125 {next if -d $_; # Do not include folder names
755 55501 50 66     220549 next if $filter and $filter and !m($filter)s; # Filter out files that do not match the regular expression
      66        
756 55501         138214 push @files, $_;
757             }
758             @files
759 4443         168682 } # 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 3432     3432 1 54450 {my ($folder, $filter) = @_; # Folder to start the search with, optional regular expression to filter files
763 3432 50       62271 return findAllFilesAndFolders($folder, 1) if onWindows; # All folders if on windows
764              
765 3432         10543 my @dir; # Directories
766 3432         33623 for(findAllFilesAndFolders($folder, 1)) # All files and folders
767 57579 100       717867 {next unless -d $_; # Include only folders
768 5111 0 33     25596 next if $filter and $filter and !m($filter)s; # Filter out directories that do not match the regular expression
      33        
769 5111         66288 push @dir, fpd($_);
770             }
771             @dir
772 3432         88833 } # findDirs
773              
774             sub fileList($) # Files that match a given search pattern interpreted by L.
775 668     668 1 5678 {my ($pattern) = @_; # Search pattern
776 668         116566 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 819     819 1 9130 {my (@FoldersandExtensions) = @_; # Mixture of folder names and extensions
781 819 50       6735 my (@foldersandExtensions) = map {ref($_) ? @$_ : $_} @_;
  836         11249  
782              
783 819 100 66     6048 my @extensions = grep {$_ and !-d $_ and !m([\/])} @_; # Extensions are not directories
  836         32197  
784 819         4361 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       374 {$_ = qq(\.$_) unless m(\A\.)s
786             }
787              
788 819 100       5731 my $ext = @extensions ? join '|', @extensions : undef; # Extensions
789 819         4044 my @file; # Files
790              
791 819         6852 for my $dir(@_) # Directories
792 836 100 66     16533 {next unless $dir && -d $dir; # Do not include folder names
793              
794 819         10445 for my $d(findAllFilesAndFolders($dir, 0)) # All files and folders beneath each folder
795 2859 100       57413 {next if -d $d; # Do not include folder names
796 2040 100 100     27149 push @file, $d if !$ext or $d =~ m(($ext)\Z)is; # Filter by extension if requested.
797             }
798             }
799             @file # Return files
800 819         51165 } # searchDirectoryTreesForMatchingFiles
801              
802             sub searchDirectoryTreeForSubFolders($) #I Search the specified directory under the specified folder for sub folders.
803 334     334 1 2672 {my ($folder) = @_; # The folder at which to start the search
804 334         2338 my @f; # Folders found
805 334         5344 for my $d(findAllFilesAndFolders($folder, 0)) # All files and folders beneath the start folder
806 1670 100       40748 {push @f, $d if -d $d; # Do not include file names
807             }
808             @f # Return folder names
809 334         18036 } # searchDirectoryTreeForSubFolders
810              
811             sub hashifyFolderStructure(@) # Hashify a list of file names to get the corresponding folder structure.
812 17     17 1 238 {my (@files) = @_; # File names
813 17         170 my %h;
814 17         153 for my $f(@files) # Map each file
815 68         357 {my @f = split m(/), $f;
816 68         221 my $s = join '', map {q({).dump($_).q(})} @f; # Hashify directory structure
  272         20281  
817 68         5746 my $c = "\$h$s = ".dump($f); # Load targets
818 68         10064 eval $c;
819 68 50       561 confess $@ if $@;
820             }
821 17         187 \%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 1008     1008 1 2350 {my ($file) = @_; # File name
869 1008 100       14461 return $file if -e $file; # File exists so nothing more to match
870 671         11416 my @path = split /[\/\\]/, $file; # Split path into components
871 671         3021 while(@path) # Remove components one by one
872 1005         2344 {pop @path; # Remove deepest component and try again
873 1005         4354 my $path = join filePathSeparatorChar, @path, ''; # Containing folder
874 1005 100       2146618 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 337     337 1 2022 {my ($file, @ext) = @_; # File name minus extensions, possible extensions
881 337         2696 for my $ext(@ext) # Each extension
882 1011         3033 {my $f = fpe($file, $ext); # Possible file
883 1011 100       23253 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 4137     4137 1 3101112 {my ($folder, $limitCount, $noMsg) = @_; # Folder, maximum number of files to remove to limit damage, no message if the folder cannot be completely removed.
890 4137 100       3290535 return unless -d $folder; # Only works on a folder that exists
891 3435         6734220 my @files = findFiles($folder); # Find files to be removed
892 3435 100       5335839 if (@files > $limitCount) # Limit the number of files that can be deleted to limit potential opportunity for damage
893 337         2696 {my $f = @files;
894 337         1234768 confess "Limit is $limitCount, but $f files under folder:\n$folder\n";
895             }
896 3098         40587 my @dirs = findDirs($folder); # These directories should be empty and thus removable after removing the files
897 3098         1985420 unlink $_ for @files; # Remove files
898 3098         4496288 rmdir $_ for reverse @dirs; # Remove empty folders
899 3098 50 33     55382 unless($noMsg or onWindows)
900 3098 50       333046 {-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 8445     8445 1 38073 {my ($file) = @_; # Name of file to read
908 8445 50       29816 defined($file) or
909             confess "Cannot read undefined file\n";
910 8445 50       57917 $file =~ m(\n|\r) and
911             confess "File name contains a new line:\n=$file=\n";
912 8445 50       128301 -e $file or
913             confess "Cannot read file because it does not exist, file:\n$file\n";
914 337 50   337   3370 open(my $F, "<:encoding(UTF-8)", $file) or
  337         674  
  337         2359  
  8445         377832  
915             confess "Cannot open file for unicode input, file:\n$file\n$!\n";
916 8445 100       5141394 if (wantarray) # Read as an array
917 1006         2705 {my @string = eval {<$F>};
  1006         21824  
918 1006 50       24201 $@ and confess "$@ reading file:\n$file\n";
919 1006         27481 return @string;
920             }
921             else # Read as a string
922 7439         60396 {local $/ = undef;
923 7439         19066 my $string = eval {<$F>};
  7439         269985  
924 7439 50       176383 $@ and confess "$@ reading file:\n$file\n";
925 7439         203307 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 15 {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         7 {local $/ = undef;
937 1         3 my $string = eval {};
  1         24  
938 1 50       5 $@ 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 1670     1670 1 4676 {my ($file) = @_; # File to read
958 1670         7348 my $string = readFile($file);
959 1670         103540 my $res = eval $string;
960 1670 50       8016 $@ and confess "$@\nin file:\n$file\n";
961 1670         11356 reloadHashes($res);
962 1670         7682 $res
963             } # evalFile
964              
965             sub evalFileAsJson($) # Read a B<$file> containing L and return the corresponding L data structure.
966 668     668 1 2338 {my ($file) = @_; # File to read
967 668         1670 my $string = readFile($file);
968 668         4676 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 334     334 1 4676 {my ($file) = @_; # File to read
973 334         8350 my $string = readGZipFile($file);
974 334         68804 my $res = eval $string;
975 334 50       8016 $@ and confess "$@\n";
976 334         9686 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 336     336 1 1386 {my ($file) = @_; # File to read
981 336 50       5112 -e $file or confess "No such file: $file\n"; # Check file exists
982 336         2068 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 336         34190 $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 2777     2777 1 8400 {my ($file) = @_; # File to read
1005 2777 50       32145 -e $file or
1006             confess "Cannot read binary file because it does not exist:\n$file\n";
1007 2777 50       80849 open my $F, "<$file" or
1008             confess "Cannot open binary file for input:\n$file\n$!\n";
1009 2777         12044 binmode $F;
1010 2777         14400 local $/ = undef;
1011 2777         102478 <$F>;
1012             } # readBinaryFile
1013              
1014             sub readGZipFile($) # Read the specified file containing compressed L content represented as L through L.
1015 668     668 1 7014 {my ($file) = @_; # File to read.
1016 668 50       6680 defined($file) or
1017             confess "Cannot read undefined file\n";
1018 668 50       11690 $file =~ m(\n|\r) and
1019             confess "File name contains a new line:\n=$file=\n";
1020 668 50       13694 -e $file or
1021             confess "Cannot read file because it does not exist, file:\n$file\n";
1022 668 50       21710 return undef unless confirmHasCommandLineCommand(q(gunzip)); # Confirm we have gunzip
1023 668 50       2439202 open(my $F, "gunzip < $file|") or # Unzip input file
1024             confess "Cannot open file for input, file:\n$file\n$!\n$?\n";
1025 668         60120 binmode($F, "encoding(UTF-8)");
1026 668         163326 local $/ = undef;
1027 668         4317952 my $string = <$F>;
1028 668         283232 $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 17449     17449 1 67672 {my ($file) = @_; # File or folder name
1033 17449         149214 my @path = split /[\\\/]+/, $file;
1034 17449 100       62873 return undef unless @path > 1; # Its just a file
1035 14974 100       79313 pop @path unless $file =~ /[\\\/]\Z/; # Remove file component allowing us to present files as well as folders
1036 14974         47951 my $path = join filePathSeparatorChar, @path;
1037 14974 100       292073 return undef if -d $path;
1038 3152         9456 eval {make_path($path)};
  3152         1021863  
1039 3152 50       63276 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 11700     11700 1 2045168 {my ($file, $string) = @_; # File to write to or B for a temporary file, unicode string to write
1059 11700   66     349835 $file //= temporaryFile;
1060 11700 50       89158 $file =~ m(\n|\r)s and confess "File name contains a new line:\n=$file=\n";
1061 11700 50       41033 defined($string) or cluck "No string for file:\n$file\n";
1062 11700         43829 makePath($file);
1063 11700 50       931102 open my $F, ">$file" or
1064             confess "Cannot open file for write because:\n$file\n$!\n";
1065 11700         98221 binmode($F, ":utf8");
1066 11700         22977 print {$F} $string;
  11700         160084  
1067 11700 50       762127 close ($F) or confess "Could not close file:\n$file\n$!\n";;
1068 11700 50       218543 -e $file or confess "Failed to write to file:\n$file\n";
1069 11700         287434 $file
1070             } # overWriteFile
1071              
1072 337     337   16907290 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 4793     4793 1 190803 {my ($file, $string) = @_; # New file to write to or B for a temporary file, string to write
1076 4793 100       28196 if (defined $file)
1077 1379 100       1172188 {-e $file and confess "File already exists:\n$file\n";
1078             }
1079 4459         27744 &overWriteFile(@_);
1080             } # writeFile
1081              
1082             sub writeTempFile(@) # Write an array of strings as lines to a temporary file and return the file name.
1083 336     336 1 3356 {my (@strings) = @_; # Array of lines
1084 336         3013 overWriteFile(undef, join '', map{"$_\n"} @strings);
  670         6380  
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 1139     1139 1 2849 {my ($file, $string) = @_; # File to write to or B for a temporary file, L string to write
1096 1139   66     12025 $file //= temporaryFile;
1097 1139 50       9936 $file =~ m(\n|\r)s and confess "File name contains a new line:\n=$file=\n";
1098 1139 50       3520 $string or carp "No string for binary write to file:\n$file\n";
1099 1139         5873 makePath($file);
1100 1139 50       90918 open my $F, ">$file" or
1101             confess "Cannot open file for binary write because:\n$file\n$!\n";
1102 1139         7037 binmode($F);
1103 1139         3317 print {$F} $string;
  1139         12769  
1104 1139         85294 close ($F);
1105 1139 50       19981 -e $file or confess "Failed to write in binary to file:\n=$file=\n$!\n";
1106 1139         32833 $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 905     905 1 6226 {my ($file, $string) = @_; # New file to write to or B for a temporary file, string to write
1111 905 100       7920 if (defined $file)
1112 571 100       716050 {-e $file and confess "Binary file already exists:\n$file\n";
1113             }
1114 671         4703 &overWriteBinaryFile(@_);
1115             }
1116              
1117             sub dumpFile($$) # Dump to a B<$file> the referenced data B<$structure>.
1118 1336     1336 1 5010 {my ($file, $structure) = @_; # File to write to or B for a temporary file, address of data structure to write
1119 1336         14362 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 334     334 1 1336 {my ($structure) = @_; # Data structure to write
1124 334         1670 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 334     334 1 1336 {my ($file, $structure) = @_; # File to write to or B for a temporary file, address of data structure to write
1129 334         5678 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 334     334 1 1336 {my ($structure) = @_; # Data structure to write
1134 334         1336 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 335     335 1 1358 {my ($file, $structure) = @_; # File to write to or B for a temporary file, address of data structure to write
1139 335 100       1360 if (!$file) # Use a temporary file or create a path to the named file
1140 334   33     4008 {$file //= temporaryFile;
1141             }
1142             else
1143 1         44 {makePath($file);
1144             }
1145 335 50       2036 ref($structure) or confess "Reference required for structure parameter";
1146 335         6057 store $structure, $file;
1147 335         70033 $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 668     668 1 288910 {my ($file, $string) = @_; # File to write to, string to write
1152 668         4342 makePath($file);
1153 668 50       3116554 open my $F, "| gzip>$file" or # Compress via gzip
1154             confess "Cannot open file for write because:\n$file\n$!\n";
1155 668         30394 binmode($F, ":utf8"); # Input to gzip encoded as utf8
1156 668         11690 print {$F} $string;
  668         42084  
1157 668         4425834 close ($F);
1158 668 50       30394 -e $file or confess "Failed to write to file:\n$file\n";
1159 668         114228 $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 334     334 1 5678 {my ($file, $structure) = @_; # File to write, reference to data
1164 334 50       4342 ref($structure) or confess "\$structure must contain a reference to data, not a scalar";
1165 334         9686 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 468     468 1 3744 {my ($hash, $old, $new) = @_; # Hash of key value pairs representing files and data, optional old prefix, new prefix
1170 468         8190 for my $file(sort keys %$hash) # Write file data for each hash key
1171 936 50 33     8424 {my $target = $old && $new ? swapFilePrefix($file, $old, $new) : $file; # Optionally swap file prefix
1172 936         6786 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 468     468 1 3510 {my (@folders) = @_; # Folders to read
1178 468         4212 my %h;
1179 468         6552 for my $file(searchDirectoryTreesForMatchingFiles(@folders)) # Files
1180 936         11700 {eval {$h{$file} = readFile($file)};
  936         15678  
1181             }
1182 468         51714 \%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 334     334 1 3006 {my ($file, $string) = @_; # File to append to, string to append
1187 334 50       6680 $file or confess "No file name supplied\n";
1188 334 50       4008 $string or carp "No string for file:\n$file\n";
1189 334         3674 makePath($file);
1190 334 50       15030 open my $F, ">>$file" or
1191             confess "Cannot open file for write file:\n$file\n$!\n";
1192 334         2672 binmode($F, ":utf8");
1193 334         6012 flock($F, 2);
1194 334         1002 print {$F} $string;
  334         6012  
1195 334         9352 close ($F);
1196 334 50       6680 -e $file or confess "Failed to write to file:\n$file\n";
1197 334         12692 $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 1910     1910 1 11110 {my ($file) = @_; # File to create or B for a temporary file
1202 1910   33     6641 $file //= temporaryFile;
1203 1910 50       38777 return $file if -e $file; # Return file name as proxy for success if file already exists
1204 1910         35830 makePath($file);
1205 1910 50       137777 open my $F, ">$file" or confess "Cannot create empty file:\n$file\n$!\n";
1206 1910         11132 binmode($F);
1207 1910         7400 print {$F} '';
  1910         9921  
1208 1910         21933 close ($F);
1209 1910 50       23603 -e $file or confess "Failed to create empty file:\n$file\n";
1210 1910         60755 $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 468     468 1 2808 {my ($file, $permissions) = @_; # File, permissions settings per chmod
1219 468 50       7956 return undef unless confirmHasCommandLineCommand(q(chmod)); # Confirm we have chmod
1220 468         2051244 qx(chmod $permissions $file); # Use chmod to set permissions
1221             }
1222              
1223             sub numberOfLinesInFile($) # Return the number of lines in a file.
1224 334     334 1 4008 {my ($file) = @_; # File
1225 334         7014 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 234     234 1 4446 {my ($source, $target) = @_; # Source file, target file
1256 234         7488 owf($target, readFile($source));
1257 234         4914 my $s = fileSize($source);
1258 234         3978 my $t = fileSize($target);
1259 234 50       7488 $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 234         10296 $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 468     468 1 3042 {my ($source, $target) = @_; # Source file, target file
1270 468 100 66     40014 if (-e $source and !-e $target) # Rename possible
1271 234         7488 {rename $source, $target;
1272 234         1872 return 1;
1273             }
1274             0 # Rename not possible
1275 234         4680 }
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 234     234 1 3042 {my ($source, $target) = @_; # Source file, target file
1279 234 50       5616 if (-e $source) # Source file exists so rename
1280 234         217386 {unlink $target;
1281 234         11700 rename $source, $target;
1282 234         5616 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 85 {my ($source, $targetFolder) = @_; # Source file, target folder
1289 17         442 writeFile fpf(fp($targetFolder), fne($source)), readFile $source;
1290             }
1291              
1292 1170     1170 1 3744 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 936     936 1 2574 {my ($string, %options) = @_; # String, options
1296              
1297 936         1638 my @name;
1298 936 100       7722 if ($string =~ m(<(bookmap))s) # The ghastly compromise
    50          
1299 468         1638 {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 936         4914 $string =~ s(<[^>]*>) (_)gs; # Remove xml/html tags
1306 936         5382 $string =~ s([^a-z0-9_])(_)gis; # Reduce character set to produce a readable name
1307 936         3042 push @name, $string;
1308              
1309 936         2808 my $name = join q(_), @name;
1310 936         4212 $name =~ s(_+)(_)gs; # Remove runs of underscores
1311 936         7254 $name =~ s((\A_+|_+\Z)) ()gs; # Remove leading and trailing underscores
1312              
1313 936   33     3978 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 234     234 1 702 {my ($string, %options) = @_; # String, options
1318 234         702 my @name;
1319 234 50       3510 if ($string =~ m(<(bookmap))s) # The ghastly compromise
    0          
1320 234         936 {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 234         1638 for my $t(qw(title mainbooktitle booktitlealt )) # Various title tags
1327 702 100       17316 {if ($string =~ m(<$t[^>]*>([^<]*))is)
1328 234         1170 {push @name, $1;
1329             }
1330             }
1331              
1332 234         1404 my $name = lc join '_', @name; # Mim believes in lc
1333 234         936 $name =~ s(<[^>]*>) (_)gs; # Remove xml/html tags
1334 234         1170 $name =~ s([^a-z0-9_])(_)gis; # Reduce character set to produce a readable name
1335 234         1170 $name =~ s(_+)(_)gs; # Remove runs of underscores
1336 234         2574 $name =~ s((\A_+|_+\Z)) ()gs; # Remove leading and trailing underscores
1337              
1338 234   33     4212 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 234     234 1 702 {my ($source) = @_; # Source file
1343 234         3276 my $sourceFile = fn $source; # File name component
1344 234 50       1404 return fne($source) if $sourceFile =~ m([0-9a-z]{32}\Z)is; # Name already normalized
1345 234         936 my $sourceFileLimited = nameFromString($sourceFile); # File name with limited character set
1346 234         702 my $md5 = fileMd5Sum($source); # Normalizing Md5 sum
1347 234         2574 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 234     234 1 2340 {my ($file) = @_; # File name
1352 234         2106 my $p = fp $file;
1353 234 50       702 my @p = onWindows ? split m(\\), $p : split m(/), $p;
1354 234 50       1638 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 234     234 1 936 {my ($source, $target) = @_; # Source file, target file
1444 234         1170 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 234         6318 $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 468     468 1 5850 {my ($source, $target) = @_; # Source file, target file
1528 468 50       15210 -d $source or confess "No such folder:\n$source\n";
1529 468         14742 my $s = fpd($source);
1530 468         6318 my $t = fpd($target);
1531 468         10998 makePath($t);
1532 468         11232 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 468         10296 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 234     234 1 3978 {my ($source, $target) = @_; # Source file, target file
1539 234 50       7488 -d $source or confess "No such folder:\n$source\n";
1540 234         6318 my $s = fpd($source);
1541 234         3744 my $t = fpd($target);
1542 234         4914 makePath($t);
1543 234         1170 my $c = qq(rsync -r $s $t);
1544             #lll $c;
1545 234         7488 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 252 {my ($structure) = @_; # Data structure reference
1577 42         1701 my $s = dump($structure); # Dump structure
1578 42         39711 $s =~ s("[^"]*/) (")gs; # Remove file prefixes in strings
1579 42         3423 my $r = eval $s; # New version of structure
1580 42 50       399 confess "Unable to remove file prefixes from structure\n$@\n$s\n" if $@; # Complain if removal fails
1581 42         798 $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 210 {my ($structure, $expr) = @_; # Data structure reference, expression
1586 21         252 my $s = nws(dump($structure)); # Dump structure
1587 21         315 $s =~ s("[^"]*/) (")gs; # Remove file prefixes in strings
1588 21         63 $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 7367     7367 1 11060 {my ($d) = @_; # Unbless a L data structure.
1787 7367 100       48005 return $d unless ref $d;
1788 2679         7742 my $r = reftype $d;
1789 2679 100       8383 return [map { __SUB__->( $_ )} @$d] if $r eq q(ARRAY);
  4014         7369  
1790 1006 50       4722 return {map {$_ => __SUB__->($$d{$_})} keys %$d} if $r eq q(HASH);
  2347         10751  
1791 0         0 confess "Unknown container: $r\n";
1792             }
1793              
1794             sub encodeJson($) # Convert a L data B<$structure> to a L string.
1795 1005     1005 1 3689 {my ($structure) = @_; # Data to encode
1796 1005         50114 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 1005     1005 1 4688 {my ($string) = @_; # Data to decode
1801 1005         26491 JSON->new->utf8->pretty->canonical->decode($string)
1802             }
1803              
1804             sub encodeBase64($) # Encode an L B<$string> in base 64.
1805 337     337 1 1011 {my ($string) = @_; # String to encode
1806 337         674 my $s = eval {encode_base64($string, '')};
  337         2359  
1807 337 50       1348 confess $@ if $@; # So we get a trace back
1808 337         5729 $s
1809             }
1810              
1811             sub decodeBase64($) # Decode an L B<$string> in base 64.
1812 337     337 1 1348 {my ($string) = @_; # String to decode
1813 337         674 my $s = eval {decode_base64($string)};
  337         4381  
1814 337 50       1348 confess $@ if $@; # So we get a trace back
1815 337         6403 $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 337     337 1 3033 {my ($string) = @_; # String to convert
1820 337         2696 my $t = '';
1821 337         3370 for(split //, $string) # Each letter in the source
1822 4718         6066 {my $n = ord($_);
1823 4718 100       7751 my $c = $n > 127 ? "&#$n;" : $_; # Use xml representation beyond u+127
1824 4718         6403 $t .= $c;
1825             }
1826             $t # Return resulting string
1827 337         2696 }
1828              
1829             sub asciiToHexString($) # Encode an L string as a string of L digits.
1830 234     234 1 936 {my ($ascii) = @_; # Ascii string
1831 234         1404 my $c = ''; # Result
1832 234         2574 for my $a(split //, $ascii) # Each ascii character
1833 2808         5382 {$c .= sprintf("%x", ord $a) # Format as hex
1834             }
1835             $c # Return string of hexadecimal digits
1836 234         1638 }
1837              
1838             sub hexToAsciiString($) # Decode a string of L digits as an L string.
1839 234     234 1 1170 {my ($hex) = @_; # Hexadecimal string
1840 234         1872 my @c = grep {m/[0-9a-f]/i} split //, $hex; # Each hexadecimal digit
  5616         11934  
1841 234         702 my $c = ''; # Result
1842 234         2340 for my $i(keys @c) # Index of each hexadecimal digit
1843 5616 100       9126 {if ($i % 2 == 1) # End of latest pair
1844 2808         11232 {$c .= chr hex $c[$i-1].$c[$i]; # Convert to character
1845             }
1846             }
1847             $c # Return result
1848 234         2808 }
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 2338     2338 1 359718 {my ($string) = @_; # String
1874 2338   66     8684 join '', map {$translatePercentEncoding{$_}//$_} split //, $string
  12358         48430  
1875             }
1876              
1877             sub wwwDecode($) # Percent decode a L B<$string> per: https://en.wikipedia.org/wiki/Percent-encoding#Percent-encoding_reserved_characters.
1878 1670     1670 1 5344 {my ($string) = @_; # String
1879 1670         4008 my $r = '';
1880 1670         5678 my @s = split //, $string;
1881 1670         5678 while(@s)
1882 8016         11356 {my $c = shift @s;
1883 8016 100 66     24382 if ($c eq q(%) and @s >= 2)
1884 6012         10020 {$c .= shift(@s).shift(@s);
1885 6012   33     16366 $r .= $TranslatePercentEncoding{$c}//$c;
1886             }
1887             else
1888 2004         4342 {$r .= $c;
1889             }
1890             }
1891 1670         5344 $r =~ s(%0d0a) (\n)gs; # Awkward characters that appear in urls
1892 1670         2672 $r =~ s(\+) ( )gs;
1893 1670         5344 $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 1348     1348 1 3370 {my ($n) = @_; # Number to check
1900 1348         3370 for(0..128)
1901 3033 100       8762 {return $_ if 1<<$_ == $n;
1902 2022 100       4718 last if 1<<$_ > $n;
1903             }
1904             undef
1905 337         1685 }
1906              
1907             sub containingPowerOfTwo($) # Find log two of the lowest power of two greater than or equal to a number B<$n>.
1908 2022     2022 1 4381 {my ($n) = @_; # Number to check
1909 2022         4718 for(0..128)
1910 5729 100       15165 {return $_ if $n <= 1<<$_;
1911             }
1912             undef
1913 0         0 }
1914              
1915             sub numberWithCommas($) # Place commas in a number.
1916 674     674 1 2022 {my ($n) = @_; # Number to add commas to
1917 674         7751 scalar reverse join ',', unpack("(A3)*", reverse $n);
1918             }
1919              
1920             #D2 Minima and Maxima # Find the smallest and largest elements of arrays.
1921              
1922             sub min(@) # Find the minimum number in a list of numbers confessing to any ill defined values.
1923 674     674 1 1685 {my (@m) = @_; # Numbers
1924 674 50       1685 my @n = grep {defined($_) and looks_like_number($_)} @_;
  1685         10784  
1925 674 50       2359 @_ == @n or confess q(Undefined or non numeric parameters present);
1926 674 50       2022 return undef unless @n;
1927 674         1011 my $M = shift;
1928 674         1348 for(@n)
1929 1685 100       4044 {$M = $_ if $_ < $M;
1930             }
1931             $M
1932 674         2696 }
1933              
1934             sub indexOfMin(@) # Find the index of the minimum number in a list of numbers confessing to any ill defined values.
1935 234     234 1 1170 {my (@m) = @_; # Numbers
1936 234 50       702 my @n = grep {defined($_) and looks_like_number($_)} @_;
  936         5850  
1937 234 50       1170 @_ == @n or confess q(Undefined or non numeric parameters present);
1938 234 50       936 return undef unless @n;
1939 234         702 my $M = 0;
1940 234         1638 for my $i(keys @n)
1941 936         1638 {my $n = $n[$i];
1942 936 100       2808 $M = $i if $n < $n[$M];
1943             }
1944             $M
1945 234         1404 }
1946              
1947             sub max(@) # Find the maximum number in a list of numbers confessing to any ill defined values.
1948 20384     20384 1 59821 {my (@m) = @_; # Numbers
1949 20384 50       30756 my @n = grep {defined($_) and looks_like_number($_)} @_;
  33419         125058  
1950 20384 50       42783 @_ == @n or confess q(Undefined or non numeric parameters present);
1951 20384 100       75186 return undef unless @n;
1952 15371         24403 my $M = shift;
1953 15371         22391 for(@n)
1954 33419 100       54824 {$M = $_ if $_ > $M;
1955             }
1956             $M
1957 15371         62489 }
1958              
1959             sub indexOfMax(@) # Find the index of the maximum number in a list of numbers confessing to any ill defined values.
1960 234     234 1 936 {my (@m) = @_; # Numbers
1961 234 50       702 my @n = grep {defined($_) and looks_like_number($_)} @_;
  936         6084  
1962 234 50       1170 @_ == @n or confess q(Undefined or non numeric parameters present);
1963 234 50       702 return undef unless @n;
1964 234         702 my $M = 0;
1965 234         1872 for my $i(keys @n)
1966 936         1638 {my $n = $n[$i];
1967 936 100       2574 $M = $i if $n > $n[$M];
1968             }
1969             $M
1970 234         1638 }
1971              
1972             sub arraySum(@) # Find the sum of any strings that look like numbers in an array.
1973 234     234 1 936 {my (@a) = @_; # Array to sum
1974 234 50       702 my @n = grep {defined($_) and looks_like_number($_)} @_;
  2340         7488  
1975 234 50       1404 @_ == @n or confess q(Undefined or non numeric parameters present);
1976 234         468 my $sum = 0; $sum += $_ for @n;
  234         1170  
1977 234         1170 $sum
1978             }
1979              
1980             sub arrayProduct(@) # Find the product of any strings that look like numbers in an array.
1981 234     234 1 936 {my (@a) = @_; # Array to multiply
1982 234 50       702 my @n = grep {defined($_) and looks_like_number($_)} @_;
  1170         6084  
1983 234 50       1404 @_ == @n or confess q(Undefined or non numeric parameters present);
1984 234         702 my $product = 1; $product *= $_ for @n;
  234         936  
1985 234         1404 $product
1986             }
1987              
1988             sub arrayTimes($@) # Multiply by B<$multiplier> each element of the array B<@a> and return as the result.
1989 468     468 1 1404 {my ($multiplier, @a) = @_; # Multiplier, array to multiply and return
1990 468         1170 map {$multiplier * $_} @a
  1872         7020  
1991             }
1992              
1993             #D1 Sets # Set operations.
1994              
1995             sub mergeHashesBySummingValues(@) # Merge a list of hashes B<@h> by summing their values.
1996 234     234 1 1170 {my (@h) = @_; # List of hashes to be summed
1997 234         702 my %h;
1998 234         2106 for my $h(@h)
1999 702         3744 {$h{$_} += $$h{$_} for sort keys %$h;
2000             }
2001 234         1404 \%h
2002             }
2003              
2004             sub invertHashOfHashes(@) # Invert a hash of hashes: given {a}{b} = c return {b}{c} = c.
2005 34     34 1 119 {my ($h) = @_; # Hash of hashes
2006 34         119 my %i; # Resulting inverted hash of hashes
2007 34         204 for my $a(keys $h->%*)
2008 68         204 {for my $b(keys $$h{$a}->%*)
2009 136         459 {$i{$b}{$a} = $$h{$a}{$b};
2010             }
2011             }
2012              
2013 34         238 \%i # Inverted hashes
2014             }
2015              
2016             sub unionOfHashKeys(@) # Form the union of the keys of the specified hashes B<@h> as one hash whose keys represent the union.
2017 42     42 1 126 {my (@h) = @_; # List of hashes to be united
2018 42 50       126 return {} unless @h;
2019 42 50       147 return $h[0] if @h == 1;
2020 42         63 my %u; # Union
2021 42         294 for my $h(@h) # Each hash to be united
2022 126         273 {for my $k(keys %$h) # Keys in current hash
2023 231         399 {$u{$k}++; # Add value to union array
2024             }
2025             }
2026              
2027 42         441 \%u # Union of all hashes
2028             }
2029              
2030             sub intersectionOfHashKeys(@) # Form the intersection of the keys of the specified hashes B<@h> as one hash whose keys represent the intersection.
2031 21     21 1 63 {my (@h) = @_; # List of hashes to be intersected
2032 21 50       84 return {} unless @h;
2033 21 50       378 return $h[0] if @h == 1;
2034              
2035 21         315 my $u = unionOfHashKeys(@h); # Union
2036 21         42 my $N = @h; # Number of hashes
2037 21         42 my %i; # Intersection
2038 21         294 for my $k(keys %$u) # Each key
2039 63 100       357 {if ($$u{$k} == $N) # Key present in all hashes
2040 21         210 {$i{$k}++ # Add hash value to intersection
2041             }
2042             }
2043              
2044 21         273 \%i # Intersection of all hashes
2045             }
2046              
2047             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.
2048 21     21 1 84 {my (@h) = @_; # List of hashes to be united
2049 21         147 my %u; # Union
2050 21         210 for my $i(keys @h) # Each hash to be united
2051 63         147 {my $h = $h[$i]; # Current hash
2052 63         462 for my $k(keys %$h) # Keys in current hash
2053 105 50       294 {if (defined(my $v = $$h{$k})) # Value defined at current key
2054 105         210 {$u{$k}[$i] = $v; # Add value to union array
2055             }
2056             }
2057             }
2058 21         210 \%u # Union of all hashes
2059             }
2060              
2061             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.
2062 21     21 1 126 {my (@h) = @_; # List of hashes to be intersected
2063 21         63 my $N = @h; # Number of hashes
2064 21         42 my %n; # Count of number of hashes that have each key
2065 21         147 for my $h(@h) # Each hash
2066 63 50       462 {defined($$h{$_}) ? ++$n{$_} : undef for keys %$h # Count the number of hashes that have this key
2067             }
2068              
2069 21         42 my %i; # Intersection
2070 21         63 for my $k(keys %n) # Each key
2071 63 100       399 {if ($n{$k} == $N) # Key present in all hashes
2072 21         126 {$i{$k}[$_] = $h[$_]{$k} for keys @h # Add hash value to intersection array
2073             }
2074             }
2075              
2076 21         441 \%i # Intersection of all hashes
2077             }
2078              
2079             sub setCombination(@) #P Count the elements in sets B<@s> represented as arrays of strings and/or the keys of hashes.
2080 19222     19222 1 26912 {my (@s) = @_; # Array of arrays of strings and/or hashes
2081 19222         20833 my %e;
2082 19222         29375 for my $s(@s) # Intersect each set
2083 33440         59547 {my $t = reftype($s);
2084 33440 100       155303 if (!defined $t) # Scalar as a set of one
    100          
    50          
2085 4175         9782 {$e{$s}++
2086             }
2087             elsif ($t =~ m(array)is) # Intersect array of strings
2088 28254         36751 {for my $e(@$s) # Count instances of each string
2089 405746         654905 {$e{$e}++
2090             }
2091             }
2092             elsif ($t =~ m(hash)is) # Intersect keys of hash
2093 1011         5392 {for my $e(keys %$s) # Count instances of each key
2094 3033         5729 {$e{$e}++
2095             }
2096             }
2097             else # Unknown set type
2098 0         0 {confess "Unknown set type: $t";
2099             }
2100             }
2101 19222         34008 \%e # Count of each set member
2102             }
2103              
2104             sub setUnion(@) # Union of sets B<@s> represented as arrays of strings and/or the keys of hashes.
2105 8945     8945 1 18787 {my (@s) = @_; # Array of arrays of strings and/or hashes
2106 8945         18775 my $e = setCombination(@_);
2107 8945         80787 sort keys %$e # Return words in union
2108             }
2109              
2110             sub setIntersection(@) # Intersection of sets B<@s> represented as arrays of strings and/or the keys of hashes.
2111 674     674 1 2696 {my (@s) = @_; # Array of arrays of strings and/or hashes
2112 674         4718 my $e = setCombination(@_);
2113 674         1348 my $S = @s; # Set count
2114 674         3707 grep {$e->{$_} == $S} sort keys %$e # Return words that appear in all the sets
  4718         11121  
2115             }
2116              
2117             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.
2118 9603     9603 1 17054 {my (@s) = @_; # Array of arrays of strings and/or hashes
2119 9603         14216 my $e = setCombination(@_); # Set element count
2120 9603         17854 my $u = keys %$e; # Union size
2121 9603 50       15388 $u == 0 and confess "Empty union"; # 0/0 can be anything
2122 9603         15330 my $S = @s; # Set count
2123 9603         35289 my $i = grep {$e->{$_} == $S} keys %$e; # Intersection size
  307943         602312  
2124 9603         112781 $i/$u # Return ratio
2125             }
2126              
2127             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>.
2128 1379     1379 1 3746 {my ($confidence, @sets) = @_; # Minimum setIntersectionOverUnion, array of arrays of strings and/or hashes representing sets
2129 1379         3221 my @s = sort {scalar(@$b) <=> scalar(@$a)} map {[setUnion($_)]} @sets; # Input sets as arrays in descending order of length
  4503         8507  
  4524         7319  
2130              
2131 1379         2118 my @partition;
2132 1379         4147 while(@s) # The proposed partition
2133 4524         6421 {my $base = shift @s; # Each set starting with the largest
2134 4524 100       11620 next unless defined $base; # No longer present
2135 3176         6541 my @base = ($base); # Create set of elements congruent with the base set
2136 3176         13396 for my $i(keys @s) # Each remaining set
2137 9603         14604 {my $s = $s[$i]; # Current set to compare with base set
2138 9603 100       15881 next unless defined $s; # Current set has already been classified
2139 9266 50       19485 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
2140 9266         13964 my $o = setIntersectionOverUnion($base, $s); # Overlap
2141 9266 100       20451 if ($o > $confidence) # Overlap is better than confidence
2142 1348         2359 {push @base, $s; # Include in partition
2143 1348         3033 $s[$i] = undef; # Remove from further consideration
2144             }
2145             }
2146 3176         8523 push @partition, \@base; # Save partition
2147             }
2148 1379         7151 @partition; # Return partitions
2149             }
2150              
2151             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>.
2152 1042     1042 1 3594 {my ($confidence, @sets) = @_; # Minimum setIntersectionOverUnion, array of arrays of strings and/or hashes representing sets
2153              
2154 1042         1760 my %u; # Normalized set to input sets with this normalization
2155 1042         9998 for my $s(@sets) # Each set
2156 3513         7204 {push @{$u{join ' ', setUnion($s)}}, $s; # Normalized set back to each input set of words
  3513         7275  
2157             }
2158             my @partition = setPartitionOnIntersectionOverUnion($confidence, # Partition normalized sets
2159 1042         3284 map {[split /\s+/, $_]} sort keys %u);
  3513         25933  
2160              
2161 1042         4537 my @P;
2162 1042         2436 for my $partition(@partition) # Each partition
2163 2502         3182 {my @p;
2164 2502         3670 for my $set(@$partition) # Each set in the current partition
2165 3513         3858 {push @p, @{$u{join ' ', @$set}};
  3513         41128  
2166             }
2167              
2168 2502         3851 push @P, \@p;
2169             }
2170             @P
2171 1042         11812 }
2172              
2173             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>.
2174 705     705 1 6140 {my ($confidence, @strings) = @_; # Minimum setIntersectionOverUnion, sets represented by strings
2175              
2176 705         1107 my %u; # Normalized set to input sets with this normalization
2177 705         3712 for my $s(@strings) # Each set
2178 5766         22577 {my $n = nws($s =~ s([^a-z ]) ()girs);
2179 5766         7603 push @{$u{$n}}, $s; # Normalized set back to each input set of words
  5766         13219  
2180             }
2181              
2182             my @partition = setPartitionOnIntersectionOverUnionOfSetsOfWords($confidence, # Partition normalized strings
2183 705         4970 map {[split /\s+/, $_]} sort {length($a) <=> length($b)} sort keys %u);
  2502         25273  
  2481         3920  
2184              
2185 705         1624 my @P; # Partition of strings
2186 705         1766 for my $partition(@partition) # Each partition
2187 1828         5209 {my @p;
2188 1828         3275 for my $set(@$partition) # Each set in the current partition
2189 2502         2968 {push @p, @{$u{join ' ', @$set}};
  2502         9013  
2190             }
2191              
2192 1828         3908 push @P, \@p;
2193             }
2194             @P
2195 705         5677 }
2196              
2197             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.
2198 354     354 1 1569 {my ($confidence, $hashSet) = @_; # Minimum setIntersectionOverUnion, sets represented by the hash value strings
2199 354 50       5177 reftype($hashSet) =~ m(hash)is or confess "Second parameter must be a hash";
2200              
2201 354         2815 my %u; # Invert the hash so we can present the partitions by hash key
2202 354         5186 for my $s(sort keys %$hashSet) # Each set
2203 4547         5357 {push @{$u{$$hashSet{$s}}}, $s; # Invert
  4547         10527  
2204             }
2205              
2206             my @partition = setPartitionOnIntersectionOverUnionOfStringSets($confidence, # Partition strings
2207 354         3382 sort {length($a) <=> length($b)} sort values %$hashSet);
  4193         5830  
2208              
2209 354         844 my @P; # Partition of strings
2210 354         1416 for my $partition(@partition) # Each partition
2211 1116         1960 {my @p;
2212             my %p; # If n sets are identical we get n repetitions - this hash prevents that.
2213 1116         4588 for my $set(@$partition) # Each set in the current partition
2214 4547 50       7765 {if (my $u = $u{$set})
2215 4547         6303 {for my $U(@$u)
2216 29299 100       43122 {push @p, $U unless $p{$U}++;
2217             }
2218             }
2219             }
2220              
2221 1116         4348 push @P, [sort @p];
2222             }
2223 354         2124 sort {scalar(@$b) <=> scalar(@$a)} @P
  762         5990  
2224             }
2225              
2226             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.
2227 17     17 1 153 {my ($confidence, $hashSet) = @_; # Minimum setIntersectionOverUnion, sets represented by the hash value strings
2228 17 50       255 reftype($hashSet) =~ m(hash)is or confess "Second parameter must be a hash";
2229              
2230 17         136 my %u; # Invert the hash so we can present the partitions by hash key
2231 17         2465 for my $s(sort keys %$hashSet) # Each set
2232 3536         3978 {push @{$u{$$hashSet{$s}}}, $s; # Invert
  3536         5848  
2233             }
2234              
2235 17         1173 my @strings = sort {length($a) <=> length($b)} sort values %$hashSet; # Strings in length order
  3519         3757  
2236 17         408 my @square = squareArray(@strings);
2237              
2238 17         170 my @partition;
2239              
2240             &runInParallel(&numberOfCpus(8), # Partition strings in square root blocks in parallel
2241             sub
2242 14     14   1255 {[setPartitionOnIntersectionOverUnionOfStringSets($confidence, $_[0]->@*)]; # Partition strings
2243             },
2244             sub # Consolidate partitions
2245 3     3   117 {for my $p(@_)
2246 42         753 {push @partition, @$p;
2247             }
2248 17         272 }, @square);
2249              
2250 3         159 my @P; # Partition of strings
2251 3         42 for my $partition(@partition) # Each partition
2252 114         156 {my @p;
2253             my %p; # If n sets are identical we get n repetitions - this hash prevents that.
2254 114         174 for my $set(@$partition) # Each set in the current partition
2255 624 50       1044 {if (my $u = $u{$set})
2256 624         759 {for my $U(@$u)
2257 4992 100       8673 {push @p, $U unless $p{$U}++;
2258             }
2259             }
2260             }
2261              
2262 114         543 push @P, [sort @p];
2263             }
2264 3         81 sort {scalar(@$b) <=> scalar(@$a)} @P
  111         1128  
2265             }
2266              
2267             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.
2268 1348     1348 1 7751 {my ($item, @array) = @_; # Item, array
2269 1348         2359 my @r;
2270 1348 100       9436 if (ref($item) =~ m(Regexp)) # Match via a regular expression
    100          
2271 337         2022 {for(keys @array)
2272 3370 100       20894 {push @r, $_ if $array[$_] =~ m($item)s;
2273             }
2274             }
2275             elsif (looks_like_number($item)) # Match as a number
2276 674         2359 {for(keys @array)
2277 2696 100       6066 {push @r, $_ if $array[$_]+0 == $item;
2278             }
2279             }
2280             else # Match as a string
2281 337         1685 {for(keys @array)
2282 3370 100       6066 {push @r, $_ if $array[$_] eq $item;
2283             }
2284             }
2285             @r
2286 1348         8425 }
2287              
2288             sub countOccurencesInString($$) # Returns the number of occurrences in B<$inString> of B<$searchFor>.
2289 234     234 1 936 {my ($inString, $searchFor) = @_; # String to search in, string to search for.
2290 234         468 my $n = 0;
2291 234 50       1170 length($inString) >= length($searchFor) or
2292             confess "String to search must be longer than string to look for";
2293 234         702 my $p = -1;
2294 234         29484 ++$n while(($p = index($inString, $searchFor, $p+1)) > -1);
2295 234         1638 $n
2296             }
2297              
2298             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.
2299 336     336 1 2877 {my ($maxSize, %Sizes) = @_; # Maximum size of a partition, {string=>size}... hash to be partitioned
2300              
2301 336         903 my %paths; # Path to each character in each string
2302             my %sizes; # Size associate with each path
2303 336         2037 for my $string(sort keys %Sizes) # Create a path of hashes with single character keys
2304 2079         3318 {my $size = $Sizes{$string}; # Size associated with the string
2305 2079         3087 my $paths = '';
2306 2079         5250 my @s = split m(), $string; # String as single characters
2307 2079         4746 while(@s) # Shorten path
2308 5943         8421 {my $k = join '', map {qq({'$_'})} @s; # Path of hashes with single character keys
  11697         24192  
2309 5943         9093 $paths .= qq(\$paths$k //= {};\n); # Auto vivify
2310 5943         7644 my $d = join '', @s; # Path name
2311 5943         8778 $sizes{$d} += $size; # Aggregate size
2312 5943         11088 pop @s; # Move up one level
2313             }
2314 2079         3003 $sizes{q()} += $size; # Total size
2315 2079         145719 eval $paths; # Create paths - this level of aggregation seems to give the fastest overall response
2316 2079 50       8295 confess "$paths\n$@\n" if $@; # Unable to create path
2317             }
2318              
2319 336         1071 my %partition; # Partition the paths
2320              
2321             my $partition; $partition = sub # Partition paths at the current level
2322 1428     1428   2625 {my ($paths, @path) = @_; # Path at this level, path to this level
2323              
2324 1428         2394 my $p = join '', @path; # Path name
2325 1428         2163 my $s = $sizes{$p}; # Size of path
2326              
2327 1428 100 100     4536 if ($s <= $maxSize or !keys %$paths) # Small enough or complete path
2328 903         2058 {$partition{$p} = $s; # Path => size
2329             }
2330             else # Still too big
2331 525         1764 {for my $d(sort keys %$paths) # Next level
2332 1113         2751 {&$partition($$paths{$d}, @path, $d); # Try at the next level
2333             }
2334             }
2335 336         2352 };
2336              
2337 336 100       1533 &$partition(\%paths) if keys %paths; # Partition from the top
2338              
2339 336         5376 %partition
2340             }
2341              
2342             sub transitiveClosure($) # Transitive closure of a hash of hashes.
2343 1     1 1 5 {my ($h) = @_; # Hash of hashes
2344              
2345 1         17 my %keys = arrayToHash(keys %$h)->%*; # Find all the keys to consider
2346 1         14 for my $i(keys %$h)
2347 3         12 {my $value = $$h{$i};
2348 3 50       36 if (reftype($value) =~ m(hash)i)
2349 3         16 {%keys = (%keys, arrayToHash(keys %$value)->%*); # Just the sub keys
2350             }
2351             }
2352              
2353 1         9 my %t; # Transitive closure
2354 1         5 for my $a(keys %keys)
2355 4         12 {my $i = $$h{$a};
2356 4 100 66     37 if ($i and reftype($i) =~ m(hash)i)
2357 3         15 {for my $b(keys %keys)
2358 12 100       26 {$t{$a}{$b} = 1 if $$i{$b}
2359             }
2360             }
2361             }
2362              
2363 1         4 for(1..100)
2364 2         9 {my $changes = 0;
2365 2         15 for my $a(keys %keys)
2366 8         17 {for my $b(keys %keys)
2367 32 100       57 {if ($t{$a}{$b})
2368 10   100     59 {$t{$b}{$_} and !$t{$a}{$_}++ and ++$changes for keys %keys # From a=>b and b=>c infer a=>c
      66        
2369             }
2370             }
2371             }
2372 2 100       17 last unless $changes;
2373             }
2374              
2375 1         9 for my $s(keys %t) # Remove empty hashes
2376 4 100       21 {delete $t{$s} unless keys $t{$s}->%*;
2377             }
2378              
2379 1         9 my %s;
2380             my @s;
2381 1         14 for my $s(sort keys %t) # Compress by creating soft pointers to common key sequences
2382 3         12 {my $k = join ' ', sort keys $t{$s}->%*;
2383 3 100       20 if (defined(my $i = $s{$k})) # Reuse a matching entry indexed from zero
2384 1         10 {$t{$s} = $i
2385             }
2386             else # Create a new entry
2387 2         5 {push @s, $t{$s}; $t{$s} = $s{$k} = @s - 1;
  2         12  
2388             }
2389             }
2390              
2391 1         12 genHash(q(Data::Table::Text::TransitiveClosure),
2392             start => \%t,
2393             end => \@s,
2394             )
2395             } # transitiveClosure
2396              
2397             #D1 Format # Format data structures as tables.
2398              
2399             sub maximumLineLength($) # Find the longest line in a B<$string>.
2400 14696     14696 1 24048 {my ($string) = @_; # String of lines of text
2401 14696   100     62792 max(map {length($_)} split /\n/, ($string//'')) // 0 # Length of longest line
  19372   100     38076  
2402             }
2403              
2404             sub formatTableMultiLine($;$) #P Tabularize text that has new lines in it.
2405 1670     1670 1 6680 {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.
2406 1670 50       16366 ref($data) =~ /array/i or
2407             confess "Array reference required not:\n".dump($data)."\n";
2408              
2409 1670         6680 my @width; # Maximum width of each column
2410 1670         6346 for my $row(@$data) # Find maximum width of each column
2411 4676 50       24048 {ref($row) =~ /array/i or
2412             confess "Array reference required not:\n".dump($row)."\n";
2413 4676         12358 for my $col(0..$#$row) # Each column index
2414 14362   100     37742 {my $a = $width[$col] // 0; # Maximum length of data so far
2415 14362         27722 my $b = maximumLineLength($row->[$col]); # Length of longest line in current item
2416 14362 100       32732 $width[$col] = ($a > $b ? $a : $b); # Update maximum length
2417             }
2418             }
2419              
2420 1670         5010 my @text; # Formatted data
2421 1670         3006 for my $row(@$data) # Each row
2422 4676         7014 {my @row; # Laid out text
2423 4676         13694 for my $col(0..$#$row) # Each column
2424 14362         20708 {my $m = $width[$col]; # Maximum width
2425 14362   100     43754 for my $i(split /\n/, $row->[$col]//'') # Each line of item
2426 18370 100       73814 {if ($i !~ /\A\s*[-+]?\s*(\d|[,])+(\.\d+)?([Ee]\s*[-+]?\s*\d+)?\s*\Z/) # Not a number - left justify
2427 13026         16366 {push @{$row[$col]}, substr($i.(' 'x$m), 0, $m);
  13026         47094  
2428             }
2429             else # Number - right justify
2430 5344         8350 {push @{$row[$col]}, substr((' 'x$m).$i, -$m);
  5344         22044  
2431             }
2432             }
2433             }
2434              
2435 4676   100     11022 my $n = max(map {scalar @{$_//[]}} @row)//0; # Maximum number of rows
  12358   100     13360  
  12358         35070  
2436              
2437 4676         13026 for my $r(1..$n) # Each row of the items
2438 10688         15030 {my $text = '';
2439 10688         17034 for my $col(0..$#$row) # Each item
2440 33400   66     86172 {$text .= ($row[$col][$r-1] // (q( ) x $width[$col])).q( );
2441             }
2442 10688         61456 $text =~ s(\s*\Z) ()s; # Strip trailing blanks as they are not needed for padding
2443 10688         27054 push @text, $text;
2444             }
2445             }
2446              
2447 1670   50     14362 my $s = $separator//"\n";
2448 1670         57114 join($s, @text).$s
2449             }
2450              
2451             sub formatTableBasic($) # Tabularize an array of arrays of text.
2452 13526     13526 1 26369 {my ($data) = @_; # Reference to an array of arrays of data to be formatted as a table.
2453 13526 50       73015 ref($data) =~ /array/i or # Must be an array
2454             confess "Array reference required not:\n".dump($data)."\n";
2455 13526         20913 my @width; # Maximum width of each column
2456              
2457 13526         34269 for my $row(@$data) # Each row
2458 58986         100617 {for my $col(0..$#$row) # Each column index
2459 193857   100     309350 {my $text = $row->[$col] // ''; # Text of current line
2460 193857 100       342248 return &formatTableMultiLine(@_) if $text =~ m(\n); # Element has a new line in it
2461 192187   100     327122 my $a = $width[$col] // 0; # Maximum length of data so far
2462 192187         200222 my $b = length($text); # Length of longest line in current item
2463 192187 100       368704 $width[$col] = ($a > $b ? $a : $b); # Update maximum length
2464             }
2465             }
2466              
2467 11856         16754 my @text; # Formatted data
2468 11856         18800 for my $row(@$data)
2469 54978         76309 {my $text = ''; # Formatted text
2470 54978         85333 for my $col(0..$#$row)
2471 182501         220366 {my $m = $width[$col]; # Maximum width
2472 182501   100     280876 my $i = $row->[$col]//''; # Current item
2473 182501 100       493636 if ($i !~ /\A\s*[-+]?\s*(\d|[,])+(\.\d+)?([Ee]\s*[-+]?\s*\d+)?\s*\Z/) # Not a number - left justify
2474 102382         248110 {$text .= substr($i.(' 'x$m), 0, $m)." ";
2475             }
2476             else # Number - right justify
2477 80119         181156 {$text .= substr((' 'x$m).$i, -$m)." ";
2478             }
2479             }
2480 54978         236098 $text =~ s(\s*\Z) ()s; # Strip trailing blanks as they are not needed for padding
2481 54978         106122 push @text, $text;
2482             }
2483              
2484 11856         80425 join("\n", @text)."\n"
2485             }
2486              
2487             sub formatTableClearUpLeft($) #P Blank identical column values up and left.
2488 124     124 1 620 {my ($data) = @_; # Array of arrays
2489              
2490 124         1364 for my $row(1..@$data) # Each row from last to first
2491 496         1736 {my $d = $$data[-$row];
2492 496 100       3596 last if $row == @$data;
2493              
2494 372         1364 my $p = $row+1;
2495 372         1612 for my $c(reverse 1..@$d) # Compare left values in current row to previous row
2496 1364 100       4216 {next unless my $dc = $$d[-$c];
2497 992 50       2480 next unless my $pc = $$data[-$p][-$c];
2498 992 100       2480 if ($dc eq $pc) # Blank equal value
2499 620         3348 {$$d[-$c] = q();
2500             }
2501             else # Values not equal terminates equal valued column suppression
2502 372         1116 {last;
2503             }
2504             }
2505             }
2506             }
2507              
2508             sub formatTableAA($$%) #P Tabularize an array of arrays.
2509 7898     7898 1 21356 {my ($data, $title, %options) = @_; # Data to be formatted, reference to an array of titles, options
2510 7898 50 33     73377 return dump($data) unless ref($data) =~ /array/i and @$data;
2511              
2512 7898         14342 my $d; # Copy of the input data because we are going to modify it
2513 7898         15295 for my $row(@$data) # Each row
2514 30647 50       86368 {ref($row) =~ /array/i or # Each row must be an array
2515             confess "Array reference required not:\n".dump($row)."\n";
2516 30647         80202 push @$d, [q(), @$row]; # Copy each row with space for a row number
2517             }
2518              
2519 7898 50       19901 if (my $w = $options{maximumColumnWidth}) # Apply maximum column width if supplied
2520 0         0 {for my $r(@$d)
2521 0         0 {for(@$r)
2522 0 0       0 {$_ = substr($_, 0, $w) if length > $w;
2523             }
2524             }
2525             }
2526              
2527 7898 100       19661 formatTableClearUpLeft($d) if $options{clearUpLeft}; # Clear up and left if requested
2528 7898         43566 $$d[$_-1][0] = $_ for 1..@$data; # Number each row now that we have suppressed duplicates
2529 7898 100       29081 unshift @$d, ['', @$title] if $title; # Add title
2530 7898         26601 formatTableBasic($d); # Format array
2531             }
2532              
2533             sub formatTableHA($;$) #P Tabularize a hash of arrays.
2534 1008     1008 1 5383 {my ($data, $title) = @_; # Data to be formatted, optional titles
2535 1008 50 33     13095 return dump($data) unless ref($data) =~ /hash/i and keys %$data;
2536 1008         2016 my $d;
2537 1008 100       4697 push @$d, $title if $title;
2538 1008         5711 push @$d, [$_, @{$data->{$_}}] for sort keys %$data;
  4029         10074  
2539 1008         3361 formatTableBasic($d);
2540             }
2541              
2542             sub formatTableAH($) #P Tabularize an array of hashes.
2543 1008     1008 1 2687 {my ($data) = @_; # Data to be formatted
2544 1008 50 33     10742 return dump($data) unless ref($data) =~ /array/i and @$data;
2545              
2546 1008         2016 my %k; @k{keys %$_}++ for @$data; # Column headers
  1008         13435  
2547 1008         5377 my @k = sort keys %k;
2548 1008         6048 $k{$k[$_-1]} = $_ for 1..@k;
2549              
2550 1008         6382 my $d = [['', @k]];
2551 1008         3695 for(1..@$data)
2552 3358         7381 {push @$d, [$_];
2553 3358         5037 my %h = %{$data->[$_-1]};
  3358         12093  
2554 3358         43850 $d->[-1][$k{$_}] = $h{$_} for keys %h;
2555             }
2556 1008         3361 formatTableBasic($d);
2557             }
2558              
2559             sub formatTableHH($) #P Tabularize a hash of hashes.
2560 1008     1008 1 2687 {my ($data) = @_; # Data to be formatted
2561 1008 50 33     12424 return dump($data) unless ref($data) =~ /hash/i and keys %$data;
2562              
2563 1008         2350 my %k; @k{keys %$_}++ for values %$data; # Column headers
  1008         7727  
2564 1008         5040 my @k = sort keys %k;
2565 1008         6048 $k{$k[$_-1]} = $_ for 1..@k;
2566              
2567 1008         6716 my $d = [['', @k]];
2568 1008         29756 for(sort keys %$data)
2569 3358         9075 {push @$d, [$_];
2570 3358         7390 my %h = %{$data->{$_}};
  3358         12755  
2571 3358         19477 $d->[-1][$k{$_}] = $h{$_} for keys %h;
2572             }
2573 1008         3698 formatTableBasic($d);
2574             }
2575              
2576             sub formatTableA($;$) #P Tabularize an array.
2577 671     671 1 1676 {my ($data, $title) = @_; # Data to be formatted, optional title
2578 671 50 33     11410 return dump($data) unless ref($data) =~ /array/i and @$data;
2579              
2580 671         2013 my $d;
2581 671 100       11112 push @$d, $title if $title;
2582 671         3355 for(keys @$data)
2583 2010 50       15421 {push @$d, @$data > 1 ? [$_, $data->[$_]] : [$data->[$_]]; # Skip line number if the array is degenerate
2584             }
2585 671         3021 formatTableBasic($d);
2586             }
2587              
2588             sub formatTableH($;$) #P Tabularize a hash.
2589 1008     1008 1 2353 {my ($data, $title) = @_; # Data to be formatted, optional title
2590              
2591 1008 50 33     12421 return dump($data) unless ref($data) =~ /hash/i and keys %$data;
2592              
2593 1008         2016 my $d;
2594 1008 100       3695 push @$d, $title if $title;
2595 1008         6722 for(sort keys %$data)
2596 2687         11437 {push @$d, [$_, $data->{$_}];
2597             }
2598 1008         3361 formatTableBasic($d);
2599             }
2600              
2601             our @formatTables; # Report of all the reports that have been created
2602              
2603             sub formatTableCheckKeys #P Options available for formatting tables.
2604 12624     12624 1 272716 {{title => <<'END',
2605             Title for the table
2606             END
2607             head => <<'END',
2608             Header text which will preceed the formatted table.
2609             DDDD will be replaced with the current date and time.
2610             NNNN will be replaced with the number of rows in the table.
2611             TTTT will be replaced with the title from the title keyword
2612             END
2613             columns => <<'END',
2614             Definition of each column one per line: the first word is the name of the column, while subsequent words describe the column.
2615             END
2616             foot => <<'END',
2617             Footer text which will follow the table
2618             END
2619             summarize => <<'END',
2620             If true, each column of an array of arrays will be summarized by printing its
2621             distinct values and a count of how often each value occurs in a series of
2622             smaller tables following the main table.
2623             END
2624             clearUpLeft => <<'END',
2625             If numeric +/-\$N, replace any left hand column values repeated in the
2626             following row with white space to make it easier to follow the range of keys.
2627             If a positive count is given the clearing will always be stopped after the
2628             numbered column (based from 1) if negative, then clearing will be stopped after
2629             the column obtained by counting back counting 1-\$N columns from the last
2630             column. Thus a value of -1 will stop clearing after the final column which
2631             could potentially produce a blank row if there are two duplicate rows in
2632             sequence.
2633             END
2634             file => q(The name of a file to which to write the formatted table.),
2635             rows => q(The number of rows in the report),
2636             zero => q(Write the report even if the table is empty.),
2637             wide => q(Write a note explaining the need to scroll to the right if true),
2638             msg => q(Write a message to STDERR summarizing the situation if true),
2639             csv => q(Write a csv version of the report if true),
2640             indent => q(Number of spaces to be used to indent the table, defaults to zero),
2641             debug => q(Debug table processing),
2642             facet => <
2643             Counts in html reports with the same facet will be plotted on the same chart to
2644             provide a visual indication of their relative sizes.
2645             END
2646             aspectColor => <
2647             The color in which to draw this aspect on charts and graphs.
2648             END
2649             maximumColumnWidth => <
2650             The maximum width permitted for a column, defaults to unlimited.
2651             END
2652             }} # formatTableCheckKeys
2653              
2654             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.
2655 12601     12601 1 55669 {my ($data, $columnTitles, @options) = @_; # Data to be formatted, optional reference to an array of titles or string of column descriptions, options
2656              
2657             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.
2658 12601 100 100 12601   76471 {if ($columnTitles and !ref($columnTitles) and
      100        
      66        
2659             $columnTitles eq q(columns) and scalar(@options) % 2 == 1)
2660 2         52 {my %o = ($columnTitles, @options);
2661 2         47 $columnTitles = $o{columns};
2662 2         83 return %o;
2663             }
2664 12599 50       34487 scalar(@options) % 2 and confess "Options fail to pair";
2665             @options
2666 12601         93200 }->();
  12599         28587  
2667              
2668 12601         76530 checkKeys(\%options, formatTableCheckKeys); # Check report options
2669              
2670             my ($titleString, $title) = sub # Title string, column headers
2671 12601 100   12601   33898 {return (undef, undef) unless defined $columnTitles; # No titles
2672 8563 100       42166 if (my $r = reftype $columnTitles) # Array of column titles
2673 8327 50       48192 {return (undef, $columnTitles) if $r =~ m(\Aarray\Z)si;
2674             }
2675 236 50       944 return (q(), q()) unless $columnTitles; # Column titles are not required for hash of hashes
2676 236         971 my @c = map {[split m(\s+), $_, 2]} split "\n", $columnTitles; # Column definitions
  706         4522  
2677 236         1226 my $s = &formatTable(\@c, [qw(Column Description)]); # Column definitions descriptions table
2678 236         712 ($s, [map {$$_[0]} @c])
  706         1915  
2679 12601         148975 }->();
2680              
2681 12601         69520 my ($a, $h, $o) = (0, 0, 0); # Check structure of input data tttt
2682             my $checkStructure = sub
2683 12601     12601   28610 {for(@_)
2684 46089         83166 {my $r = reftype($_); # Process arrays and hashes or objects built on them
2685 46089 100       64791 if ($r)
2686 41392 100       90801 {if ($r =~ /array/i) {++$a}
  34676 50       52153  
2687 6716         9737 elsif ($r =~ /hash/i) {++$h}
2688 0         0 else {++$o}
2689             }
2690 4697         6379 else {++$o}
2691             }
2692 12601         52615 };
2693              
2694             my $formattedTable = sub # Format table
2695 12601 100   12601   95410 {if (reftype($data) =~ /array/i)
    50          
2696 9577         25894 {$checkStructure->( @$data);
2697 9577 50 66     84677 return formatTableAA($data, $title, %options) if $a and !$h and !$o;
      66        
2698 1679 100 66     17467 return formatTableAH($data) if !$a and $h and !$o;
      66        
2699 671         5025 return formatTableA ($data, $title);
2700             }
2701             elsif (reftype($data) =~ /hash/i)
2702 3024         10417 {$checkStructure->(values %$data);
2703 3024 50 66     17470 return formatTableHA($data, $title) if $a and !$h and !$o;
      66        
2704 2016 100 66     17467 return formatTableHH($data) if !$a and $h and !$o;
      66        
2705 1008         3024 return formatTableH ($data, $title);
2706             }
2707 12601         65679 }->();
2708              
2709 12601 100       218743 return $formattedTable unless keys %options; # Return table as is unless report requested
2710              
2711 2903         10098 my ($Title, $head, $foot, $file, $zero, $summarize, $wide, $msg, $csv, $zwsp, $indent) = map{$options{$_}}
  31933         46892  
2712             qw(title head foot file zero summarize wide msg csv zwsp indent);
2713              
2714 2903         7065 my @report;
2715 2903         20920 my $date = dateTimeStamp;
2716 2903         16292 my $N = keyCount(1, $data);
2717 2903   100     44461 my $H = ($head//'') =~ s(DDDD) ($date)gr =~ s(NNNN) ($N)gr;
2718 2903 100       7998 $H =~ s(TTTT) ($title)gs if $Title;
2719 2903 100       12439 push @report, $Title if $Title;
2720 2903 100       8860 push @report, $H if $head;
2721 2903 100       8550 push @report, qq(This file: $file) if $file;
2722 2903 100       9872 push @report, $titleString if $titleString;
2723 2903 50       5895 push @report, <
2724             Please note that this is a wide report: you might have to scroll
2725             a long way to the right to see all the columns of data available!
2726             END
2727 2903 100       9697 push @report, <
2728             Summary_of_column - Count of unique values found in each column Use the Geany flick capability by placing your cursor on the first word
2729             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.
2730             END
2731              
2732 2903         7129 push @report, $formattedTable;
2733 2903 100       31781 push @report, $foot if $foot;
2734              
2735 2903   100     24305 push @formatTables, [$N, $Title//nws($H, 80), $file]; # Report of all the reports created
2736              
2737 2903 0 33     14128 if ($msg and $file and $head)
      33        
2738 0         0 {lll $H =~ s(\n.*\Z) ()gsr;
2739 0         0 lll qq(See file: $file);
2740             }
2741              
2742 2903 100       6827 if ($summarize) # Summarize an array of arrays if requested
2743 468         2106 {my $s = '';
2744 468 50       3276 if (reftype($data) =~ /array/i)
2745 468 50 33     5616 {if ($a and !$h and !$o)
      33        
2746 468         2574 {for my $col(1..@$title)
2747 1170         3510 {my $n = $title->[$col-1];
2748 1170         2808 my $c = qq(Summary_of_column_$n);
2749 1170         3510 my @s = summarizeColumn($data, $col-1);
2750 1170         7722 my $t = &formatTable(\@s, [q(Count), $n]);
2751 1170         6318 $s .= qq($c\n$t\n);
2752 1170         2106 if (1)
2753 1170         3978 {my $v = join ",", sort map {dump($$_[1])} @s;
  3978         241254  
2754 1170         151164 $s .= "Comma_Separated_Values_of_column_$n: $v\n\n";
2755             }
2756             }
2757             }
2758             }
2759 468         1404 push @report, $s;
2760             }
2761              
2762 2903 100       8608 if ($file) # Write a csv version of the report (Sabine)
2763 339 50       9438 {if (reftype($data) =~ /array/i)
2764 339 50 33     7135 {if ($a && !$h && !$o or $zero)
      33        
      33        
2765 339         704 {my @s;
2766              
2767 339 50       1352 if ($title) # Column headers
2768 339 100       2369 {my $r = join ',', map {defined($_) ? $_ : q(unknown)} @$title;
  681         6760  
2769 339         1691 push @s, $r;
2770             }
2771              
2772 339         2715 for my $d(@$data)
2773 685         25213 {push @s, join ',', map{dump($_)} @$d;
  1382         100688  
2774             }
2775 339         41930 my $csvFile = setFileExtension($file, q(csv));
2776 339         1373 my $csvData = join "\n", @s;
2777 339         4859 overWriteFile($csvFile, "$csvData\n");
2778             }
2779             }
2780             }
2781              
2782 2903         15231 my $report = join "\n\n", @report; # Create report
2783              
2784 2903 50 33     15481 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        
2785              
2786 2903 50       9352 $report = indentString($report, $indent) if $indent; # Indent table if requested
2787              
2788 2903         78244 $report
2789             } # formatTable
2790              
2791             sub formattedTablesReport(@) # Report of all the reports created. The optional parameters are the same as for L.
2792 234     234 1 936 {my (@options) = @_; # Options
2793              
2794 234   50     1404 formatTable([sort {($a->[1]//'') cmp ($b->[1]//'')} @formatTables], <
  2574   50     6318  
2795             Rows Number of entries in table
2796             Title Title of the report
2797             File File containing the report
2798             END
2799             @options);
2800             }
2801              
2802             sub summarizeColumn($$) # Count the number of unique instances of each value a column in a table assumes.
2803 1404     1404 1 3042 {my ($data, $column) = @_; # Table == array of arrays, column number to summarize.
2804 1404         2808 my @data = map {$$_[$column]} @$data;
  12636         17784  
2805 1404         2106 my %data;
2806 1404         8190 for my $d(@data)
2807 12636 50       31122 {$data{$d}++ if defined $d;
2808             }
2809 5382 100       13338 sort {return $$a[1] cmp $$b[1] if $$b[0] == $$a[0]; # Return array of [count, key]
2810 1404         5850 return $$b[0] <=> $$a[0]} map {[$data{$_}, $_]} sort keys %data;
  2808         9126  
  4914         13572  
2811             }
2812              
2813             sub keyCount($$) # Count keys down to the specified level.
2814 3577     3577 1 11400 {my ($maxDepth, $ref) = @_; # Maximum depth to count to, reference to an array or a hash
2815 3577         8285 my $n = 0;
2816 3577         8222 my $count;
2817             $count = sub
2818 6947     6947   11960 {my ($ref, $currentDepth) = @_;
2819 6947 100       33810 if (ref($ref) =~ /array/i)
    100          
2820 3914 100       8979 {if ($maxDepth == $currentDepth) {$n += scalar(@$ref)}
  3240         7739  
2821 674         3707 else {$count->($_, ++$currentDepth) for @$ref}
2822             }
2823             elsif (ref($ref) =~ /hash/i)
2824 1011 100       2022 {if ($maxDepth == $currentDepth) {$n += scalar(keys %$ref)}
  337         1011  
2825 674         4044 else {$count->($ref->{$_}, ++$currentDepth) for keys %$ref}
2826             }
2827 2022         4044 else {++$n}
2828 3577         27730 };
2829 3577         9686 $count->($ref, 1);
2830 3577         12334 $n
2831             }
2832              
2833             sub formatHtmlTable($%) # Format an array of arrays of scalars as an html table using the B<%options> described in L.
2834 23     23 1 551 {my ($data, %options) = @_; # Data to be formatted, options
2835 23 50       179 my $rows = $data ? scalar(@$data) : 0; # The number of rows in the report
2836              
2837 23         866 checkKeys(\%options, formatTableCheckKeys); # Check report options
2838              
2839 23 50 33     1242 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        
2840 0         0 {return q()
2841             }
2842              
2843 23         68 my @html; # Generated html
2844 23         257 my $cl = q(); # Table column names
2845 23         258 my $ct = q(); # Columns description table if present
2846              
2847 23 50       241 if (my $columns = $options{columns}) # Column headers
2848 23 50       130 {ref($columns) and confess <
2849             Expected one line per column wiith the forst weor dbeing teh column name and
2850             the remainder being a comment describing the comment.
2851             END
2852 23         268 my @c = map {[split m(\s+), $_, 2]} split "\n", $columns; # Parse column headers
  46         430  
2853             $cl = join '', q(
), join q(),
2854 23         94 map {my ($c, $d) = @$_; qq($c)} @c; # Column line with tool tips
  46         134  
  46         444  
2855 23         642 $ct = join "\n", q(

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

\n); # Column format
2856             }
2857              
2858 23 50       411 if (my $title = $options{title}) # Title
2859 23         1157 {push @html, <
2860            

$title

2861             END
2862             }
2863              
2864             my $hf = sub # Header / Footer
2865 46     46   262 {my ($text) = @_; # Text of header or footer
2866 46         574 my $d = dateTimeStamp;
2867 46   50     610 my $t = ($text//'') =~ s(DDDD) ($d)gr =~ s(NNNN) ($rows)gr; # Edit in NNNN and DDDD fields
2868              
2869 46         207 push @html, <
2870            

$t

2871             END
2872 23         415 };
2873              
2874 23 50       112 if (my $head = $options{head}) # Header
2875 23         68 {&$hf($head);
2876             }
2877              
2878 23         250 push @html, <
2879            

2880             END
2881              
2882 23 50       136 push @html, $cl if $cl; # Column headers
2883              
2884 23 50       193 if ($data) # Table data
2885 23         279 {for my $data(@$data)
2886 47   50     140 {push @html, join '', q(
), join q(), map {$_//q()} @$data;
  94         864  
2887             }
2888             }
2889              
2890 23         197 push @html, <
2891            

2892             END
2893              
2894 23 50       111 push @html, $ct if $ct; # Column descriptions block
2895              
2896 23 50       216 if (my $foot = $options{foot}) # Footer
2897 23         110 {&$hf($foot);
2898             }
2899              
2900 23         44 if (1) # Record options invisibly
2901 23         288 {my $options = dump({%options, rows=>$rows});
2902 23         18417 push @html, qq();
2903             }
2904              
2905 23         223 my $html = join "\n", @html; # Create html
2906 23 100       127 if (my $file = $options{file})
2907 2         37 {my $html = join "\n", @html;
2908 2         168 overWriteFile($file, $html);
2909             }
2910              
2911             $html
2912 23         777 } # formatHtmlTable
2913              
2914             sub formatHtmlTablesIndex($$$;$) # Create an index of html reports.
2915 17     17 1 238 {my ($reports, $title, $url, $columns) = @_; # Reports folder, title of report of reports, $url to get files, number of columns - defaults to 1
2916 17   50     799 $columns //= 1;
2917              
2918             my %reports = sub # Hash {file=>options} for each html report
2919 17     17   663 {my @r = searchDirectoryTreesForMatchingFiles($reports, q(.html)); # Find all html reports
2920 17         306 my %r;
2921 17         221 for my $r(@r) # Each html report
2922 34         850 {my $t = readFile($r);
2923 34 50       816 if ($t =~ m()s) # Extract report meta data
2924 34         5882 {my $d = eval $1;
2925 34 50       374 $@ and confess "Cannot retrieve report metadata:\n$r\n$@\n";
2926 34 50       391 if (my $t = $$d{title})
2927 34         510 {$r{$t} = $d;
2928             }
2929             else
2930 0         0 {cluck "No title in file:\n$r\n";
2931             }
2932             }
2933             }
2934             %r
2935 17         731 }->();
  17         204  
2936              
2937 17         527 my @toc; my %class; # List of reports
2938 17         272 for my $title(sort keys %reports) # Each report
2939 34         374 {my $options = $reports{$title};
2940 34         221 my $rows = $$options{rows};
2941 34 50       272 next unless my $file = $$options{file};
2942 34         595 my $class = containingFolderName($file); $class{$class}++; # Classification for report
  34         391  
2943 34         153 my $href = qq($url$file);
2944 34         136 my $link = qq($title);
2945             my $tick = sub # Flag items that we would like to be zero
2946 34 50 33 34   697 {return q() unless $file =~ m(/bad/) and $rows;
2947 0         0 q()
2948 34         442 }->();
2949              
2950 34         408 my $c = qq( class="report report_$class"); # Classification
2951              
2952 34         442 push @toc, join '', qq(),
2953             join( qq(), $rows, $tick, $link);
2954             }
2955              
2956 17         221 my $tocs = @toc;
2957             # my @tocs = rectangularArray(int(@toc / $columns), @toc); # Divide into columns
2958 17         561 my @tocs = rectangularArray2($columns, @toc); # Divide into columns
2959 17         306 my $toc = join "\n", map {q(
  34         323  
2960 17         340 my $dt = dateTimeStamp; # Date of run
2961 17 50       255 my $t = $title ? qq(

$title

) : q(); # Title if present
2962              
2963 17         153 my $groups = join ', ', map {qq("$_")} sort keys %class; # Groups
  17         255  
2964 17         204 my $select = join '', map {<
  17         119  
2965             $_
2966             END
2967              
2968 17         255 push my @html, <
2969            
2975              
2976            
2977            
2978             $tocs reports available on $dt
2979             Hide All
2980             $select
2981             Show All
2982            
2983            

2984             $toc
2985            

2986            
3023             END
3024              
3025 17         170 my $html = join "\n", @html; # Create html
3026              
3027 17 50       391 if (my $out = fpe($reports, qw(index_of_reports html)))
3028 17         867 {owf($out, $html);
3029             }
3030              
3031             $html # Return the html so created
3032 17         799 } # formatHtmlTablesIndex
3033              
3034             my @formatHtmlAndTextTablesPids; # Pids used to format tables in parallel
3035              
3036             sub formatHtmlAndTextTablesWaitPids # Wait on all table formatting pids to complete.
3037 17     17 1 89053582 {waitpid $_, 0 for @formatHtmlAndTextTablesPids;
3038             }
3039              
3040             sub formatHtmlAndTextTables($$$$$%) # Create text and html versions of a tabular report.
3041 40     40 1 847 {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
3042              
3043 40 50       328 my @prefix = ref($filePrefix) ? @$filePrefix : $filePrefix; # Flatten file prefixes into array
3044 40         395 my $file = $options{file}; # Relative report file
3045 40         584 my $columns = $options{columns}; # Columns must come first for the moment
3046              
3047 40 50       223 if ($reports) # Format table as text
3048 40         41252 {push @formatHtmlAndTextTablesPids, my $pid = fork;
3049 40 100       2357 unless($pid)
3050 2         183 {my $out = setFileExtension fpf($reports, $file), q(txt); # Output file name
3051 2         691 formatTable($data, columns=>$columns, %options, file=>$out);
3052 2         16806 exit;
3053             }
3054             }
3055              
3056 38 50       622 if ($html) # Format table as html
3057 38         33107 {push @formatHtmlAndTextTablesPids, my $pid = fork;
3058 38 100       38151 unless($pid)
3059 2         165 {my $out = setFileExtension fpf($html, $file), q(html); # Output file name
3060 2         121 my $start = time;
3061             my $h = sub # Turn file names into links in a table of scalars
3062 2     2   52 {my @r;
3063 2         39 for my $row(@$data)
3064 5         55 {my @c;
3065 5         55 for my $col(@$row)
3066             {push @c, sub
3067 10         80 {for my $filePrefix(@prefix) # Try each file prefix
3068 10 100 66     356 {if ($col and $col =~ m(\A$filePrefix)s)
3069 5         104 {return qq().
3070             swapFilePrefix($col, $filePrefix).q();
3071             }
3072             }
3073             $col # Use plain file name as no prefix matched
3074 10         171 }->();
  5         80  
3075             }
3076 5         39 push @r, \@c;
3077             }
3078             \@r # Return edited rows as a reference for convenient use with formatTable
3079 2         141 }->();
  2         41  
3080              
3081 2         142 formatHtmlTable($h, %options, file => $out); # Format table as html
3082 2         31262 exit;
3083             }
3084             }
3085             } # formatHtmlAndTextTables
3086              
3087             #D1 Lines # Load data structures from lines.
3088              
3089             sub loadArrayFromLines($) # Load an array from lines of text in a string.
3090 337     337 1 3033 {my ($string) = @_; # The string of lines from which to create an array
3091 337         2022 [grep {!/\A#/} split "\n", $string]
  674         8425  
3092             }
3093              
3094             sub loadHashFromLines($) # Load a hash: first word of each line is the key and the rest is the value.
3095 337     337 1 1348 {my ($string) = @_; # The string of lines from which to create a hash
3096 337         1685 +{map{split /\s+/, $_, 2} split "\n", $string}
  674         14828  
3097             }
3098              
3099             sub loadArrayArrayFromLines($) # Load an array of arrays from lines of text: each line is an array of words.
3100 337     337 1 1348 {my ($string) = @_; # The string of lines from which to create an array of arrays
3101 337         1348 [map{[split /\s+/]} split "\n", $string]
  674         14491  
3102             }
3103              
3104             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.
3105 337     337 1 1348 {my ($string) = @_; # The string of lines from which to create a hash of arrays
3106 337         1685 +{map{my @a = split /\s+/; (shift @a, [@a])} split "\n", $string}
  674         5055  
  674         9099  
3107             }
3108              
3109             sub loadArrayHashFromLines($) # Load an array of hashes from lines of text: each line is a hash of words.
3110 337     337 1 1011 {my ($string) = @_; # The string of lines from which to create an array of arrays
3111 337         1685 [map {+{split /\s+/}} split /\n/, $string]
  674         14154  
3112             }
3113              
3114             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.
3115 337     337 1 3033 {my ($string) = @_; # The string of lines from which to create a hash of arrays
3116 337         2359 +{map{my ($a, @a) = split /\s+/; ($a=>{@a})} split "\n", $string}
  674         3707  
  674         9099  
3117             }
3118              
3119             sub checkKeys($$) # Check the keys in a B confirm to those B<$permitted>.
3120 12624     12624 1 35883 {my ($hash, $permitted) = @_; # The hash to test, a hash of the permitted keys and their meanings
3121              
3122 12624 50       82490 ref($hash) =~ /hash/igs or # Check parameters
3123             confess "Hash reference required for first parameter\n";
3124 12624 50       54271 ref($permitted) =~ /hash/igs or
3125             confess "Hash reference required for second parameter\n";
3126              
3127 12624         34529 my %parms = %$hash; # Copy keys supplied
3128 12624         99093 delete $parms{$_} for keys %$permitted; # Remove permitted keys
3129 12624 50       47737 return '' unless keys %parms; # Success - all the keys in the test hash are permitted
3130              
3131 0         0 confess join "\n", # Failure - explain what went wrong
3132             "Invalid options chosen:",
3133             indentString(formatTable([sort keys %parms]), ' '),
3134             "",
3135             "Permitted options are:",
3136             indentString(formatTable($permitted), ' '),
3137             "";
3138             }
3139              
3140             #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.
3141              
3142             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.
3143 1348     1348 1 5729 {my (@names) = @_; # List of method names
3144 1348         4044 my ($package) = caller; # Package
3145 1348         5055 for my $m(@_) # Name each method
3146 2022         4381 {my $s;
3147 2022 100       7751 if ($m =~ m(::)s) # Package name supplied in name
3148 1011         4044 {my $M = $m =~ s(\A.*:) ()r; # Remove package
3149 1011         3707 $s =
3150             'sub '.$m. ':lvalue {$_[0]{"'.$M.'"}}'. # LValue version for get and set
3151             'sub '.$m.'X {$_[0]{"'.$M.'"} // q()}'; # Non lvalue version for get only returning q() instead of B
3152             }
3153             else # Use package of caller
3154 1011         4044 {$s =
3155             'sub '.$package.'::'.$m. ':lvalue {$_[0]{"'.$m.'"}}'. # LValue version for get and set
3156             'sub '.$package.'::'.$m.'X {$_[0]{"'.$m.'"} // q()}'; # Non lvalue version for get only returning q() instead of undef
3157             }
3158             # 'sub '.$package.'::'.$_. ':lvalue {my $v; $_[0]{"'.$_.'"} //= $v}'.
3159             # 'sub '.$package.'::'.$_.'X:lvalue {my $v = q(); $_[0]{"'.$_.'"} //= $v}';
3160             # 'sub '.$package.'::'.$_.'X:lvalue {my $v = $_[0]{"'.$_.'"}; confess q(No value supplied for "'.$_.'") unless defined($v); $v}';
3161 2022   0 1968   228823 eval $s;
  1968   0 2076   23717  
  2076   50 0   25148  
  0   50 0   0  
  0   0 503   0  
  503   0 508   2515  
  508     521   2540  
  521     490   5609  
  490     0   5175  
  0     0   0  
  0     0   0  
  0     0   0  
  0         0  
3162 2022 50       39429 confess "Unable to create LValue scalar method for: '$m' because\n$@\n" if $@;
3163             }
3164             }
3165              
3166             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.
3167 1348     1348 1 3370 {my (@names) = @_; # List of method names
3168 1348         3707 my ($package) = caller; # Package
3169 1348         4381 for my $m(@_) # Name each method
3170 1348 50       4381 {my $M = $m =~ m(::)s ? $m : $package.'::'.$m;
3171 1348 50       25949 next if defined &$M;
3172 0         0 genLValueScalarMethods($M);
3173             }
3174             }
3175              
3176             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.
3177 337     337 1 1348 {my (@names) = @_; # List of method names
3178 337         1348 my ($package) = caller; # Package
3179 337         1011 for(@_) # Name each method
3180 1011         3370 {my $s = 'sub '.$package.'::'.$_.':lvalue {my $v = "'.$_.'"; $_[0]{"'.$_.'"} //= $v}';
3181 1011   33 337   95034 eval $s;
  337   0 0   1011  
  337   0 0   4044  
  0         0  
  0         0  
  0         0  
  0         0  
3182 1011 50       12132 confess "Unable to create LValue scalar method for: '$_' because\n$@\n" if $@;
3183             }
3184             }
3185              
3186             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.
3187 337     337 1 1011 {my (@names) = @_; # List of method names
3188 337         1685 my ($package) = caller; # Package
3189 337         1011 for(@_) # Name each method
3190 1011         3370 {my $s = 'sub '.$package.'::'.$_.':lvalue {$_[0]{"'.$_.'"} //= []}';
3191 1011   100 674   58638 eval $s;
  674   0 0   10110  
  0   0 0   0  
  0         0  
3192 1011 50       11458 confess "Unable to create LValue array method for: '$_' because\n$@\n" if $@;
3193             }
3194             }
3195              
3196             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.
3197 337     337 1 1348 {my (@names) = @_; # Method names
3198 337         3707 my ($package) = caller; # Package
3199 337         1011 for(@_) # Name each method
3200 1011         4044 {my $s = 'sub '.$package.'::'.$_.':lvalue {$_[0]{"'.$_.'"} //= {}}';
3201 1011   100 674   60660 eval $s;
  674   0 0   10784  
  0   0 0   0  
  0         0  
3202 1011 50       11458 confess "Unable to create LValue hash method for: '$_' because\n$@\n" if $@;
3203             }
3204             }
3205              
3206             my %genHash; # Hash of methods created by genHash - these methods can be reused - others not so created cannot.
3207              
3208             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.
3209 7147     7147 1 420513 {my ($bless, %attributes) = @_; # Package name, hash of attribute names and values
3210 7147         47552 my $h = \%attributes;
3211 7147         40317 bless $h, $bless;
3212 7147         246448 for my $m(sort keys %attributes) # Add any attributes not already present
3213 462150 50       6841637 {unless ($m =~ m(\A[a-z_](\w|:)*\Z)is) # Silently skip anything we could not reasonably use as an attribute name
3214 0         0 {confess qq(Implausibly named attribute: "$m"\n);
3215             }
3216              
3217 462150         910594 my $M = $bless.q(::).$m; # The full name of the attribute
3218              
3219 462150 100       1841959 if ($h->can($m)) # Skip any methods that are already defined
3220 341270 50       4498256 {say STDERR dump(\%genHash, $m, $M) unless $genHash{$M};
3221              
3222             confess "Cannot define attribute because there is already ".
3223 341270 50       580133 "a method with the same name: $m\n" unless $genHash{$M};
3224              
3225 341270         581090 next;
3226             }
3227              
3228 120880 50       530520 if ($h->can($m.q(X))) # Confess to any methods that collide with X names
3229 0         0 {confess "Cannot define attribute because there is already ".
3230             "an X method with the same name: $m\n";
3231             }
3232              
3233 120880         387337 my $R = reftype($attributes{$m}); # Type of thing referred to
3234 120880 100       378748 my $r = !defined($R) ? q() : $R =~ m(array)i ? q( //= []) : q( //= {}); # Empty return type
    100          
3235 120880         148647 my $s = '';
3236 120880         255955 $s .= 'sub '.$bless.'::'.$m. ':lvalue {$_[0]{"'.$m.qq("}$r})."\n"; # LValue version for get and set
3237 120880         220410 $s .= 'sub '.$bless.'::'.$m. 'X {$_[0]{"'.$m.'"}//q()}'."\n"; # Default to blank for get
3238 120880 50       191296 if ($s) # Add any new methods needed
3239 120880   0 234   7786136 {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 160321      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 388614      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 2815860      
      0 0      
      0 2768193      
      0 0      
      0 0      
      0 115082      
      0 57775      
      0 0      
      0 0      
      0 56974      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 55165      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 1336      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 2672      
      0 234      
      0 0      
      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 56974      
      0 0      
      0 0      
      0 0      
      0 55726      
      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        
3240 120880 50       418700 confess "$@\n$s\n$@" if $@;
3241             }
3242 120880         420351 $genHash{$M}++; # Record attribute as being created by genHash
3243             }
3244              
3245             $h
3246 7147         106054 }
3247              
3248             sub loadHash($%) # Load the specified blessed B<$hash> generated with L with B<%attributes>. Confess to any unknown attribute names.
3249 2107     2107 1 14306 {my ($hash, %attributes) = @_; # Hash, hash of attribute names and values to be loaded
3250 2068         15798 for my $m(sort keys %attributes) # Add any attributes not already present
3251 1245 100       145959 {$hash->can($m) or confess "Cannot load attribute: $m\n"; # Unknown attribute
3252 668         18370 $hash->{$m} = $attributes{$m}; # Load known attribute
3253             }
3254             $hash # Return loaded hash
3255 1441         21997 }
3256              
3257             my $reloadHashesCount = 0; # Generate names for reloaded hashes that are not already blessed
3258              
3259             sub reloadHashes2($$) #P Ensures that all the hashes within a tower of data structures have LValue methods to get and set their current keys.
3260 16693     16964 1 36560 {my ($d, $progress) = @_; # Data structure, progress
3261 16682 100       67512 return unless my $r = reftype($d);
3262 5780 50       23824 return if $$progress{$d};
3263 5903 100       56104 if ($d =~ m(array)is) # Array
    50          
3264 2483         11220 {$$progress{$d}++;
3265 2372         17874 &reloadHashes2($_, $progress) for @$d;
3266             }
3267             elsif ($d =~ m(hash)is) # Hash
3268 3408         11226 {$$progress{$d}++;
3269 3408         21350 &reloadHashes2($_, $progress) for values %$d;
3270 3408 100       18644 if (my $b = blessed($d)) # Already blessed
3271 2406         13636 {genHash($b, %$d);
3272             }
3273             else # Create a name
3274 1002         4008 {my $b = q(reloadHash_).++$reloadHashesCount;
3275 1002         9352 bless($d, $b); # Bless hash
3276 1002         6012 genHash($b, %$d);
3277             }
3278             }
3279             }
3280              
3281             sub reloadHashes($) # Ensures that all the hashes within a tower of data structures have LValue methods to get and set their current keys.
3282 2706     2949 1 11928 {my ($d) = @_; # Data structure
3283 2706         16372 reloadHashes2($d, {});
3284 2706         36324 $d
3285             }
3286              
3287             sub showHashes2($$$) #P Create a map of all the keys within all the hashes within a tower of data structures.
3288 3510     3510 1 7488 {my ($d, $keys, $progress) = @_; # Data structure, keys found, progress
3289 3510 100       10296 return unless my $r = reftype($d);
3290 1170 50       3042 return if $$progress{$d};
3291 1170 100       10764 if ($d =~ m(array)is)
    50          
3292 234         1872 {$$progress{$d}++;
3293 947         13423 &showHashes2($_, $keys, $progress) for @$d;
3294             }
3295             elsif ($d =~ m(hash)is)
3296 936         2574 {$$progress{$d}++;
3297 936         6318 &showHashes2($_, $keys, $progress) for values %$d;
3298 936 50       6786 if (my $b = blessed($d))
3299 936         3744 {for my $k(keys %$d)
3300 2808         17316 {$keys->{$b}{$k}++
3301             }
3302             }
3303             }
3304             }
3305              
3306             sub showHashes($) #P Create a map of all the keys within all the hashes within a tower of data structures.
3307 234     301 1 468 {my ($d) = @_; # Data structure
3308 234         42120 showHashes2($d, my $keys = {}, {});
3309 234         6084 $keys
3310             }
3311              
3312             my %packageSearchOrder; # Method to package map
3313              
3314             sub setPackageSearchOrder($@) # Set a package search order for methods requested in the current package via AUTOLOAD.
3315 468     557 1 5148 {my ($set, @search) = @_; # Package to set, package names in search order.
3316 468         3042 %packageSearchOrder = (); # Reset method to package map
3317              
3318 468         2106 my $c = <<'END';
3319             if (1)
3320             {package $set;
3321             our $AUTOLOAD; # Method requested
3322             BEGIN{undef &AUTOLOAD}; # Replace autoload
3323             sub AUTOLOAD
3324             {my $s = $AUTOLOAD;
3325             return if $s =~ m(Destroy)is;
3326             if (my $t = $packageSearchOrder{$s}) # Reuse a cached method if possible
3327             {goto &$t;
3328             }
3329             else # Search for the first package that can provide the requested method
3330             {for my $package(@search)
3331             {my $t = $s =~ s(\A.+::) (${package}::)grs;
3332             if (defined &$t)
3333             {$packageSearchOrder{$s} = $t;
3334             goto &$t;
3335             }
3336             }
3337             confess "Cannot find a method implementing $s"; # No package supports the requested method
3338             }
3339             }
3340             }
3341             END
3342 468         3510 my $search = q/qw(/.join(' ', @search).q/)/; # Set search order
3343 468         25506 $c =~ s(\$set) ($set)gs;
3344 468         6786 $c =~ s(\@search) ($search)gs;
3345 468         88686 eval $c;
3346 468 50       30888 confess "$c\n$@\n" if $@;
3347             }
3348              
3349             sub isSubInPackage($$) # Test whether the specified B<$package> contains the subroutine <$sub>.
3350 4546     4624 1 17514 {my ($package, $sub) = @_; # Package name, subroutine name
3351 4546         161542 my $r = eval qq(defined(&${package}::${sub}));
3352 4546 50       17816 $@ and confess $@;
3353 3376         31288 $r
3354             }
3355              
3356             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>.
3357 1404     234 1 3978 {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.
3358 2106         9828 my @s;
3359 2106         15210 for my $method(setUnion @methods) # Replaceable methods
3360 1638         7488 {push @s, <<"END";
3361             if (isSubInPackage(q($from), q($method)))
3362             {undef &${to}::$method;
3363             *${to}::$method = *${from}::$method;
3364             }
3365             else
3366             {undef &${from}::$method;
3367             *${from}::$method = *${to}::$method;
3368             }
3369             END
3370             }
3371 1404         24570 my $s = join "\n", @s; # Replace methods
3372 234         48204 eval $s;
3373 234 100       6552 confess $@ if $@;
3374             }
3375              
3376             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.
3377 0     123 1 0 {my (@packages) = @_; # List of packages
3378 0 100       0 @packages or confess "No packages supplied"; # Check we have some packages
3379 0         0 my $base = $packages[-1]; # The last package
3380 0         0 my $om = qq(&${base}::overrideableMethods); # Sub to supply replaceable methods
3381 0         0 my @methods = eval $om; # Retrieve replaceable methods
3382 0 0       0 $@ and confess "Cannot retrieve replaceable methods via sub $om\n$@\n";
3383              
3384 0         0 my @s; # Replacement code
3385              
3386 0         0 for my $i(keys @packages) # Push methods down through the packages
3387 0 0       0 {last if $i == $#packages;
3388 0         0 my $from = $packages[$i];
3389 0         0 my $to = $packages[$i+1];
3390 0         0 for my $method(@methods) # Push each method down one level if possible
3391 0         0 {push @s, <<"END";
3392             if (isSubInPackage(q($from), q($method)))
3393             {undef &${to}::$method;
3394             *${to}::$method = *${from}::$method;
3395             }
3396             END
3397             }
3398             }
3399              
3400 0         0 for my $i(reverse keys @packages) # Pull methods up through the packages
3401 0 0       0 {next unless $i;
3402 0         0 my $from = $packages[$i];
3403 0         0 my $to = $packages[$i-1];
3404 0         0 for my $method(@methods) # Pull each method up one level if possible
3405 207         4173 {push @s, <<"END";
3406             if (isSubInPackage(q($from), q($method)) && !isSubInPackage(q($to), q($method)))
3407             {undef &${to}::$method;
3408             *${to}::$method = *${from}::$method;
3409             }
3410             END
3411             }
3412             }
3413              
3414 205         4245 my $s = join "\n", @s; # Replace methods
3415 156         3714 eval $s;
3416 0 0       0 confess "$@\n$s\n" if $@;
3417             }
3418              
3419             sub assertPackageRefs($@) # Confirm that the specified references are to the specified package.
3420 337     448 1 1348 {my ($package, @refs) = @_; # Package, references
3421 337         1011 for(@refs) # Check each reference
3422 571         4755 {my $r = ref($_);
3423 337 50 33     46169 $r && $r eq $package or confess "Wanted reference to $package, but got $r\n";
3424             }
3425             1
3426 0         0 }
3427              
3428             sub assertRef(@) # Confirm that the specified references are to the package into which this routine has been exported.
3429 337     337 1 1348 {my (@refs) = @_; # References
3430 337         1348 my ($package) = caller; # Package
3431 337         1011 for(@_) # Check each reference
3432 337         1011 {my $r = ref($_);
3433 337 50 33     68074 $r && $r eq $package or confess "Wanted reference to $package, but got $r\n";
3434             }
3435             1
3436 0         0 }
3437              
3438             sub arrayToHash(@) # Create a hash reference from an array.
3439 238     238 1 4929 {my (@array) = @_; # Array
3440 238         2146 +{map{$_=>1} @array}
  709         9640  
3441             }
3442              
3443             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.
3444 147     147 1 630 {my (@array) = @_; # Array to flatten
3445 147         336 my @a;
3446 147         357 for my $a(@array)
3447 231 100       1323 {if (ref($a) =~ m(\Aarray\Z)i)
    100          
3448 105         966 {push @a, &flattenArrayAndHashValues(@$a);
3449             }
3450             elsif (ref($a) =~ m(\Ahash\Z)i)
3451 21         126 {push @a, &flattenArrayAndHashValues(map {$$a{$_}} sort keys %$a);
  42         252  
3452             }
3453             else
3454 105         336 {push @a, $a;
3455             }
3456             }
3457             @a # Flattened array
3458 147         1113 }
3459              
3460             sub getSubName($) # Returns the (package, name, file, line) of a perl B<$sub> reference.
3461 3     3 1 12 {my ($sub) = @_; # Reference to a sub with a name.
3462 3 50       201 if (my $b = B::svref_2object($sub))
3463 3         9 {my $r = ref($b);
3464 3 50       45 if ($r =~ m(B::CV)i)
3465 3 50       126 {if (my $g = $b->GV)
3466 3         198 {return ($g->STASH->NAME, $g->NAME, $g->FILE, $g->LINE); # Package, name, file, line in file
3467             }
3468             }
3469             }
3470 0         0 confess "Unable to get name of sub referenced by $sub";
3471             }
3472              
3473             #D1 Strings # Actions on strings.
3474              
3475             sub stringMd5Sum($) # Get the Md5 sum of a B<$string> that might contain L code points.
3476 936     936 1 2106 {my ($string) = @_; # String
3477 936         2574 my $f = writeFile(undef, $string); # Write into a file
3478 936         3276 my $s = readBinaryFile($f); # Read as binary
3479 936         7254 my $m = md5_hex($s); # Md5sum of bytes
3480 936         563238 unlink $f;
3481 936         9828 $m;
3482             }
3483              
3484             sub indentString($$) # Indent lines contained in a string or formatted table by the specified string.
3485 1005     1005 1 6358 {my ($string, $indent) = @_; # The string of lines to indent, the indenting string
3486 1005 50       13080 join "\n", map {$indent.$_} split m(\n+), (ref($string) ? $$string : $string)
  3352         18466  
3487             }
3488              
3489             sub replaceStringWithString($$$) # Replace all instances in B<$string> of B<$source> with B<$target>.
3490 42     42 1 651 {my ($string, $source, $target) = @_; # String in which to replace substrings, the string to be replaced, the replacement string
3491 42         525 for(1..(1+length($string) / (length($source)+1))) # Avoid too much recursive expansion
3492 105         504 {my $i = index($string, $source);
3493 105 100       609 if ($i >= 0)
3494 84         357 {substr($string, $i, length($source)) = $target;
3495 84         315 next;
3496             }
3497 21         273 last;
3498             }
3499             $string
3500 42         882 }
3501              
3502             sub formatString($$) # Format the specified B<$string> so it can be displayed in B<$width> columns.
3503 21     21 1 210 {my ($string, $width) = @_; # The string of text to format, the formatted width.
3504              
3505 21         630 $string =~ s(\\m) (\n\n)gs; # Expand \m introduced by update documentation
3506              
3507 21         294 for(1..9)
3508 189 100       966 {if ($string =~ m((B<([^>]*)>))s)
3509 21         1092 {$string = replaceStringWithString(my $s = $string, $1, boldString($2));
3510 21 50       231 last if $s eq $string;
3511             }
3512             }
3513              
3514 21         336 my @f;
3515 21         525 my @w = split m/\s+/, $string; # Parse string into words
3516 21         189 for my $w(@w) # Bold B
3517 357 100       672 {if (!$f[-1]) {push @f, $w}
  21         168  
3518             else
3519 336         1281 {my $l = $f[-1].qq( $w);
3520 336 100       735 if (length($l) > $width)
3521 84         189 {push @f, $w;
3522             }
3523             else
3524 252         945 {$f[-1] = $l;
3525             }
3526             }
3527             }
3528              
3529 21         420 my $t = join "\n", @f; # Format punctuation
3530 21         525 $t =~ s(\s*([,;.!?])) ($1)gs;
3531 21         399 $t =~ s(\s*\Z) ()s;
3532              
3533 21         252 "$t\n"
3534             }
3535              
3536             sub isBlank($) # Test whether a string is blank.
3537 674     674 1 1685 {my ($string) = @_; # String
3538 674         6403 $string =~ m/\A\s*\Z/
3539             }
3540              
3541             sub trim($) # Remove any white space from the front and end of a string.
3542 337     337 1 1348 {my ($string) = @_; # String
3543 337         6740 $string =~ s/\A\s+//r =~ s/\s+\Z//r
3544             }
3545              
3546             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.
3547 1685     1685 1 4718 {my ($string, $length, $padding) = @_; # String, tab width, padding string
3548 1685 50       4381 defined($string) or confess "String required\n";
3549 1685         7077 $string =~ s/\s+\Z//;
3550 1685   100     7414 $padding //= q( );
3551 1685         3033 my $l = length($string);
3552 1685 100       5729 return $string if $l % $length == 0;
3553 1348         1685 my $p = $length - $l % $length;
3554 1348         6740 $string .= $padding x $p;
3555             }
3556              
3557             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.
3558 1685     1685 1 4381 {my ($string, $length, $padding) = @_; # String, tab width, padding string
3559 1685 50       4718 defined($string) or confess "String required\n";
3560 1685         9099 $string =~ s/\s+\Z//;
3561 1685   100     11795 $padding //= q( );
3562 1685         2359 my $l = length($string);
3563 1685 100       6066 return $string if $l % $length == 0;
3564 1348         2359 my $p = $length - $l % $length;
3565 1348         7077 ($padding x $p).$string;
3566             }
3567              
3568             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.
3569 1685     1685 1 4718 {my ($length, $string, $padding) = @_; # Tab width, string, padding string
3570 1685 50       6740 defined($string) or confess "String required\n";
3571 1685         8762 $string =~ s/\s+\Z//;
3572 1685   100     7077 $padding //= q( );
3573 1685         2696 my $l = length($string);
3574 1685 100       5392 return $string if $l % $length == 0;
3575 1348         2359 my $p = $length - $l % $length;
3576 1348         7414 $string .= $padding x $p;
3577             }
3578              
3579             sub firstNChars($$) # First N characters of a string.
3580 14100     14100 1 41060 {my ($string, $length) = @_; # String, length
3581 14100 100 100     118338 return $string if !$length or length($string) < $length;
3582 334         5010 substr($string, 0, $length);
3583             }
3584              
3585             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.
3586 11594     11594 1 40561 {my ($string, $length) = @_; # String to normalize, maximum length of result
3587 11594         9804468 my $s = $string =~ s((\x{200b}|\A\s+|\s+\Z)) ()gr =~ s/\s+/ /gr;
3588 11594         36501 firstNChars($s, $length) # Apply maximum length if requested
3589             }
3590              
3591             sub deduplicateSequentialWordsInString($) # Remove sequentially duplicate words in a string.
3592 17     17 1 187 {my ($s) = @_; # String to deduplicate
3593 17         340 my %a = map {$_=>1} grep {$_} split /\W+/, $s; # Split into words
  221         799  
  238         340  
3594 17         238 for my $w(sort keys %a)
3595 85         1598 {1 while $s =~ s($w\s+$w) ($w)gs;
3596             }
3597             $s
3598 17         119 }
3599              
3600             sub detagString($) # Remove L or L tags from a string.
3601 21     734 1 252 {my ($string) = @_; # String to detag
3602 21         609 $string =~ s(<[^>]*>) ()gsr # Remove xml/html tags
3603             }
3604              
3605             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.
3606 17     17 1 238 {my ($string) = @_; # String to parse
3607 17 50       425 return () unless $string;
3608              
3609 17         289 my $s = 0; # 0 - look for word or quote, 1 in word, 2 in ' string, 3 - in " string
3610 17         119 my @r;
3611             my $r;
3612              
3613             my $accept = sub # Accept a word or string
3614 170     170   697 {push @r, $r; $s = 0;
  170         442  
3615 17         629 };
3616              
3617 17         2040 for my $c(split m//, $string) # Each character in the string
3618 1020 100 100     2822 {next if $s == 0 and $c =~ m(\s); # Skip spaces while looking for a word or string
3619              
3620 680 100       1972 if ($s == 0) # String
    100          
    100          
    50          
3621 170 100       765 {if ($c =~ m(')) # Single quoted ' string
    100          
3622 51         238 {$r = ''; $s = 2;
  51         170  
3623             }
3624             elsif ($c =~ m(")) # Double quoted " string
3625 51         187 {$r = ''; $s = 3;
  51         136  
3626             }
3627             else # Word
3628 68         102 {$r = $c; $s = 1;
  68         340  
3629             }
3630             }
3631             elsif ($s == 1) # In word
3632 153 100       612 {if ($c =~ m(\s))
3633 51         306 {&$accept;
3634             }
3635             else
3636 1250         6221 {$r .= $c;
3637             }
3638             }
3639             elsif ($s == 2) # In ' string
3640 1292 100       6732 {if ($c =~ m('))
3641 51         340 {&$accept;
3642             }
3643             else
3644 51         289 {$r .= $c;
3645             }
3646             }
3647             elsif ($s == 3) # In " string
3648 255 100       765 {if ($c =~ m("))
3649 51         357 {&$accept;
3650             }
3651             else
3652 204         323 {$r .= $c;
3653             }
3654             }
3655             }
3656 17         323 &$accept;
3657             @r
3658 17         561 } # parseIntoWordsAndStrings
3659              
3660             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.
3661 1670     1670 1 6680 {my ($a, $b) = @_; # First string, second string
3662 1670         12024 my @a = split //, $a;
3663 1670         5678 my @b = split //, $b;
3664 1670         2338 my @c;
3665 1670   66     33734 while(@a and @b and $a[0] eq $b[0])
      100        
3666 7014         11356 {shift @a; push @c, shift @b;
  7014         24716  
3667             }
3668 1670         26720 (join(q(), @c), join(q(), @a), join(q(), @b))
3669             }
3670              
3671             sub showGotVersusWanted($$) # Show the difference between the wanted string and the wanted string.
3672 334     334 1 1670 {my ($g, $e) = @_; # First string, second string
3673 334         2004 my @s;
3674 334 50       3006 if ($g ne $e)
3675 334         4342 {my ($s, $G, $E) = stringsAreNotEqual($g, $e);
3676 334 50       1336 if (length($s))
3677 334         5678 {my $line = 1 + length($s =~ s([^\n]) ()gsr);
3678 334         2672 my $char = 1 + length($s =~ s(\A.*\n) ()sr);
3679 334         1670 push @s, "Comparing wanted with got failed at line: $line, character: $char";
3680 334         1002 push @s, "Start:\n$s";
3681             }
3682 334         2338 my $b1 = '+' x 80;
3683 334         2338 my $b2 = '_' x 80;
3684 334         5010 push @s, "Want $b1\n", firstNChars($E, 80);
3685 334         3340 push @s, "Got $b2\n", firstNChars($G, 80);
3686 334         3674 return join "\n", @s;
3687             }
3688             undef
3689 0         0 }
3690              
3691             sub printQw(@) # Print an array of words in qw() format.
3692 671     671 1 5377 {my (@words) = @_; # Array of words
3693 671         9048 'qw('.join(' ', @words).')'
3694             }
3695              
3696             sub numberOfLinesInString($) # The number of lines in a string.
3697 334     334 1 2338 {my ($string) = @_; # String
3698 334         12692 scalar split /\n/, $string;
3699             }
3700              
3701             sub javaPackage($) # Extract the package name from a java string or file.
3702 1345     1345 1 3695 {my ($java) = @_; # Java file if it exists else the string of java
3703              
3704             my $s = sub
3705 1345 100 66 1345   27255 {return readFile($java) if $java !~ m/\n/s and -e $java; # Read file of java
3706 334         1670 $java # Java string
3707 1345         15102 }->();
3708              
3709 1345         21836 my ($package) = $s =~ m(package\s+(\S+)\s*;);
3710 1345         6728 $package
3711             }
3712              
3713             sub javaPackageAsFileName($) # Extract the package name from a java string or file and convert it to a file name.
3714 337     337 1 1011 {my ($java) = @_; # Java file if it exists else the string of java
3715              
3716 337 50       1348 if (my $package = javaPackage($java))
3717 337         5729 {return $package =~ s/\./\//gr;
3718             }
3719             undef
3720 0         0 }
3721              
3722             sub perlPackage($) # Extract the package name from a perl string or file.
3723 671     671 1 5690 {my ($perl) = @_; # Perl file if it exists else the string of perl
3724 671         3349 my $p = javaPackage($perl); # Use same technique as Java
3725 671 50       3021 defined($p) or confess "There is no Perl module in file: $perl";
3726 671         2690 $p
3727             }
3728              
3729             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 }.
3730 234     234 1 936 {my ($fileOrString) = @_; # File or string
3731 234 50       3042 my $s = $fileOrString =~ m(\n) ? $fileOrString : readFile($fileOrString);
3732 234         702 my @s;
3733 234         702 my $state = 0;
3734 234         2340 for my $line(split /\n/, $s)
3735 2106 100       3978 {if ($state == 0)
    50          
3736 1170 100       4446 {if ($line =~ m(\Afunction.*\/\/E))
3737 468         702 {$state = 1;
3738 468         2574 push @s, q(), $line;
3739             }
3740             }
3741             elsif ($state == 1)
3742 936 100       2340 {$state = 0 if $line =~ m(\A \});
3743 936         2106 push @s, $line;
3744             }
3745             }
3746 234         2106 join "\n", @s, '';
3747             }
3748              
3749             sub chooseStringAtRandom(@) # Choose a string at random from the list of B<@strings> supplied.
3750 17     17 1 238 {my (@strings) = @_; # Strings to chose from
3751 17         102 my $r = int((rand() * @strings)) % @strings;
3752 17         204 $strings[$r]
3753             }
3754              
3755             sub randomizeArray(@) # Randomize an array.
3756 17     17 1 170 {my (@a) = @_; # Array to randomize
3757 17         221 for my $i(keys @a)
3758 68         221 {my $r = int(rand() * ($i+1)); # Uniform randomization
3759 68         136 my $s = $a[$i];
3760 68         85 my $t = $a[$r];
3761 68         170 $a[$i] = $t;
3762 68         204 $a[$r] = $s;
3763             }
3764             @a
3765 17         306 }
3766              
3767             #D1 Arrays and Hashes # Operations on arrays and hashes and array of of hashesh and ghashes of arrays and so on a infinitum.
3768              
3769             sub lengthOfLongestSubArray($) # Given an array of arrays find the length of the longest sub array.
3770 1     1 1 3 {my ($a) = @_; # Array reference
3771 1         8 max map{scalar @$_} @$a
  4         33  
3772             }
3773              
3774             sub cmpArrays($$) # Compare two arrays of strings.
3775 5     5 1 15 {my ($a, $b) = @_; # Array A, array B
3776 5         17 my @a = @$a;
3777 5         12 my @b = @$b;
3778 5   100     48 while(@a and @b and !($a[0] cmp $b[0]))
      100        
3779 8         12 {shift @a; shift @b;
  8         26  
3780             }
3781 5 100 100     50 return $a[0] cmp $b[0] if @a and @b;
3782 3 100       23 return -1 if @b;
3783 2 100       20 return +1 if @a;
3784 1         12 0
3785             }
3786              
3787             sub forEachKeyAndValue(&%) # Iterate over a hash for each key and value.
3788 1     1 1 6 {my ($body, %hash) = @_; # Body to be executed, hash to be iterated
3789 1         34 &$body($_, $hash{$_}) for sort keys %hash;
3790             }
3791              
3792             #D1 Unicode # Translate L alphanumerics in strings to various L blocks.
3793              
3794             my $normalString = join '', 'A'..'Z', 'a'..'z', '0'..'9';
3795             my $normalAlphaString = join '', 'A'..'Z', 'a'..'z';
3796             my $boldString = q(𝗔𝗕𝗖𝗗𝗘𝗙𝗚𝗛𝗜𝗝𝗞𝗟𝗠𝗡𝗢𝗣𝗤𝗥𝗦𝗧𝗨𝗩𝗪𝗫𝗬𝗭𝗮𝗯𝗰𝗱𝗲𝗳𝗴𝗵𝗶𝗷𝗸𝗹𝗺𝗻𝗼𝗽𝗾𝗿𝘀𝘁𝘂𝘃𝘄𝘅𝘆𝘇𝟬𝟭𝟮𝟯𝟰𝟱𝟲𝟳𝟴𝟵);
3797             my $squareString = q(🄰🄱🄲🄳🄴🄵🄶🄷🄸🄹🄺🄻🄼🄽🄾🄿🅀🅁🅂🅃🅄🅅🅆🅇🅈🅉🄰🄱🄲🄳🄴🄵🄶🄷🄸🄹🄺🄻🄼🄽🄾🄿🅀🅁🅂🅃🅄🅅🅆🅇🅈🅉0123456789);
3798             my $circleString = q(ⒶⒷⒸⒹⒺⒻⒼⒽⒾⒿⓀⓁⓂⓃⓄⓅⓆⓇⓈⓉⓊⓋⓌⓍⓎⓏⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ⓪①②③④⑤⑥⑦⑧⑨);
3799             my $darkString = q(🅐🅑🅒🅓🅔🅕🅖🅗🅘🅙🅚🅛🅜🅝🅞🅟🅠🅡🅢🅣🅤🅥🅦🅧🅨🅩🅐🅑🅒🅓🅔🅕🅖🅗🅘🅙🅚🅛🅜🅝🅞🅟🅠🅡🅢🅣🅤🅥🅦🅧🅨🅩⓿➊➋➌➍➎➏➐➑➒);
3800             my $superString = q(ᴬᴮCᴰᴱFᴳᴴᴵᴶᴷᴸᴹᴺᴼᴾQᴿSᵀᵁⱽᵂXYZᵃᵇᶜᵈᵉᶠᵍʰⁱʲᵏˡᵐⁿᵒᵖqʳˢᵗᵘᵛʷˣʸᶻ⁰¹²³⁴⁵⁶⁷⁸⁹);
3801             my $lowsubString = q(ₐbcdₑfgₕᵢⱼₖₗₘₙₒₚqᵣₛₜᵤᵥwₓyz₀₁₂₃₄₅₆₇₈₉);
3802             my $lowerString = join '', 'a'..'z', '0'..'9';
3803             my $mathematicalItalic = '𝐴𝐵𝐶𝐷𝐸𝐹𝐺𝐻𝐼𝐽𝐾𝐿𝑀𝑁𝑂𝑃𝑄𝑅𝑆𝑇𝑈𝑉𝑊𝑋𝑌𝑍𝑎𝑏𝑐𝑑𝑒𝑓𝑔𝑖𝑗𝑘𝑙𝑚𝑛𝑜𝑝𝑞𝑟𝑠𝑡𝑢𝑣𝑤𝑥𝑦𝑧';
3804             my $mathematicalBold = '𝐀𝐁𝐂𝐃𝐄𝐅𝐆𝐇𝐈𝐉𝐊𝐋𝐌𝐍𝐎𝐏𝐐𝐑𝐒𝐓𝐔𝐕𝐖𝐗𝐘𝐙𝐚𝐛𝐜𝐝𝐞𝐟𝐠𝐡𝐢𝐣𝐤𝐥𝐦𝐧𝐨𝐩𝐪𝐫𝐬𝐭𝐮𝐯𝐰𝐱𝐲𝐳';
3805             my $mathematicalBoldItalic = '𝑨𝑩𝑪𝑫𝑬𝑭𝑮𝑯𝑰𝑱𝑲𝑳𝑴𝑵𝑶𝑷𝑸𝑹𝑺𝑻𝑼𝑽𝑾𝑿𝒀𝒁𝒂𝒃𝒄𝒅𝒆𝒇𝒈𝒉𝒊𝒋𝒌𝒍𝒎𝒏𝒐𝒑𝒒𝒓𝒔𝒕𝒖𝒗𝒘𝒙𝒚𝒛';
3806             my $mathematicalSansSerif = '𝖠𝖡𝖢𝖣𝖤𝖥𝖦𝖧𝖨𝖩𝖪𝖫𝖬𝖭𝖮𝖯𝖰𝖱𝖲𝖳𝖴𝖵𝖶𝖷𝖸𝖹𝖺𝖻𝖼𝖽𝖾𝖿𝗀𝗁𝗂𝗃𝗄𝗅𝗆𝗇𝗈𝗉𝗊𝗋𝗌𝗍𝗎𝗏𝗐𝗑𝗒𝗓';
3807             my $mathematicalSansSerifBold = '𝗔𝗕𝗖𝗗𝗘𝗙𝗚𝗛𝗜𝗝𝗞𝗟𝗠𝗡𝗢𝗣𝗤𝗥𝗦𝗧𝗨𝗩𝗪𝗫𝗬𝗭𝗮𝗯𝗰𝗱𝗲𝗳𝗴𝗵𝗶𝗷𝗸𝗹𝗺𝗻𝗼𝗽𝗾𝗿𝘀𝘁𝘂𝘃𝘄𝘅𝘆𝘇';
3808             my $mathematicalSansSerifItalic = '𝘈𝘉𝘊𝘋𝘌𝘍𝘎𝘏𝘐𝘑𝘒𝘓𝘔𝘕𝘖𝘗𝘘𝘙𝘚𝘛𝘜𝘝𝘞𝘟𝘠𝘡𝘢𝘣𝘤𝘥𝘦𝘧𝘨𝘩𝘪𝘫𝘬𝘭𝘮𝘯𝘰𝘱𝘲𝘳𝘴𝘵𝘶𝘷𝘸𝘹𝘺𝘻';
3809             my $mathematicalSansSerifBoldItalic = '𝘼𝘽𝘾𝘿𝙀𝙁𝙂𝙃𝙄𝙅𝙆𝙇𝙈𝙉𝙊𝙋𝙌𝙍𝙎𝙏𝙐𝙑𝙒𝙓𝙔𝙕𝙖𝙗𝙘𝙙𝙚𝙛𝙜𝙝𝙞𝙟𝙠𝙡𝙢𝙣𝙤𝙥𝙦𝙧𝙨𝙩𝙪𝙫𝙬𝙭𝙮𝙯';
3810             my $mathematicalMonoSpace = '𝙰𝙱𝙲𝙳𝙴𝙵𝙶𝙷𝙸𝙹𝙺𝙻𝙼𝙽𝙾𝙿𝚀𝚁𝚂𝚃𝚄𝚅𝚆𝚇𝚈𝚉𝚊𝚋𝚌𝚍𝚎𝚏𝚐𝚑𝚒𝚓𝚔𝚕𝚖𝚗𝚘𝚙𝚚𝚛𝚜𝚝𝚞𝚟𝚠𝚡𝚢𝚣';
3811              
3812             sub mathematicalItalicString($) # Convert alphanumerics in a string to L Mathematical Italic.
3813 334     334 1 1002 {my ($string) = @_; # String to convert
3814 334         4676 my $h = $normalAlphaString =~ s(h) ()r; # Unicode does not have a small mathematical italic h
3815 334         22044 eval qq(\$string =~ tr($h) ($mathematicalItalic));
3816 334         2338 $string
3817             }
3818              
3819             sub mathematicalBoldString($) # Convert alphanumerics in a string to L Mathematical Bold.
3820 334     334 1 1336 {my ($string) = @_; # String to convert
3821 334         20374 eval qq(\$string =~ tr($normalAlphaString) ($mathematicalBold));
3822 334         2338 $string
3823             }
3824              
3825             sub mathematicalBoldStringUndo($) # Undo alphanumerics in a string to L Mathematical Bold.
3826 334     334 1 1002 {my ($string) = @_; # String to convert
3827 334         17368 eval qq(\$string =~ tr($mathematicalBold) ($normalAlphaString));
3828 334         2338 $string
3829             }
3830              
3831             sub mathematicalBoldItalicString($) # Convert alphanumerics in a string to L Mathematical Bold Italic.
3832 334     334 1 1336 {my ($string) = @_; # String to convert
3833 334         22378 eval qq(\$string =~ tr($normalAlphaString) ($mathematicalBoldItalic));
3834 334         2338 $string
3835             }
3836              
3837             sub mathematicalBoldItalicStringUndo($) # Undo alphanumerics in a string to L Mathematical Bold Italic.
3838 334     334 1 1336 {my ($string) = @_; # String to convert
3839 334         22044 eval qq(\$string =~ tr($mathematicalBoldItalic) ($normalAlphaString));
3840 334         2338 $string
3841             }
3842              
3843             sub mathematicalSansSerifString($) # Convert alphanumerics in a string to L Mathematical Sans Serif.
3844 334     334 1 1336 {my ($string) = @_; # String to convert
3845 334         22712 eval qq(\$string =~ tr($normalAlphaString) ($mathematicalSansSerif));
3846 334         2338 $string
3847             }
3848              
3849             sub mathematicalSansSerifStringUndo($) # Undo alphanumerics in a string to L Mathematical Sans Serif.
3850 334     334 1 1002 {my ($string) = @_; # String to convert
3851 334         22044 eval qq(\$string =~ tr($mathematicalSansSerif) ($normalAlphaString));
3852 334         2004 $string
3853             }
3854              
3855             sub mathematicalSansSerifBoldString($) # Convert alphanumerics in a string to L Mathematical Sans Serif Bold.
3856 334     334 1 1002 {my ($string) = @_; # String to convert
3857 334         19706 eval qq(\$string =~ tr($normalAlphaString) ($mathematicalSansSerifBold));
3858 334         2672 $string
3859             }
3860              
3861             sub mathematicalSansSerifBoldStringUndo($) # Undo alphanumerics in a string to L Mathematical Sans Serif Bold.
3862 334     334 1 1002 {my ($string) = @_; # String to convert
3863 334         17368 eval qq(\$string =~ tr($mathematicalSansSerifBold) ($normalAlphaString));
3864 334         2338 $string
3865             }
3866              
3867             sub mathematicalSansSerifItalicString($) # Convert alphanumerics in a string to L Mathematical Sans Serif Italic.
3868 334     334 1 1002 {my ($string) = @_; # String to convert
3869 334         23380 eval qq(\$string =~ tr($normalAlphaString) ($mathematicalSansSerifItalic));
3870 334         2338 $string
3871             }
3872              
3873             sub mathematicalSansSerifItalicStringUndo($) # Undo alphanumerics in a string to L Mathematical Sans Serif Italic.
3874 334     334 1 1002 {my ($string) = @_; # String to convert
3875 334         17034 eval qq(\$string =~ tr($mathematicalSansSerifItalic) ($normalAlphaString));
3876 334         2338 $string
3877             }
3878              
3879             sub mathematicalSansSerifBoldItalicString($) # Convert alphanumerics in a string to L Mathematical Sans Serif Bold Italic.
3880 334     334 1 1002 {my ($string) = @_; # String to convert
3881 334         20708 eval qq(\$string =~ tr($normalAlphaString) ($mathematicalSansSerifBoldItalic));
3882 334         2672 $string
3883             }
3884              
3885             sub mathematicalSansSerifBoldItalicStringUndo($) # Undo alphanumerics in a string to L Mathematical Sans Serif Bold Italic.
3886 334     334 1 1336 {my ($string) = @_; # String to convert
3887 334         17368 eval qq(\$string =~ tr($mathematicalSansSerifBoldItalic) ($normalAlphaString));
3888 334         2338 $string
3889             }
3890              
3891             sub mathematicalMonoSpaceString($) # Convert alphanumerics in a string to L Mathematical MonoSpace.
3892 334     334 1 1002 {my ($string) = @_; # String to convert
3893 334         19706 eval qq(\$string =~ tr($normalAlphaString) ($mathematicalMonoSpace));
3894 334         2338 $string
3895             }
3896              
3897             sub mathematicalMonoSpaceStringUndo($) # Undo alphanumerics in a string to L Mathematical MonoSpace.
3898 334     334 1 1002 {my ($string) = @_; # String to convert
3899 334         20374 eval qq(\$string =~ tr($mathematicalMonoSpace) ($normalAlphaString));
3900 334         2004 $string
3901             }
3902              
3903             sub boldString($) # Convert alphanumerics in a string to bold.
3904 1023     1023 1 4846 {my ($string) = @_; # String to convert
3905 1023     568   131871 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.
  568         71934  
  568         57396  
  334         6346  
3906 1023         8352 $string
3907             }
3908              
3909             sub boldStringUndo($) # Undo alphanumerics in a string to bold.
3910 334     334 1 668 {my ($string) = @_; # String to convert
3911 334         17702 eval qq(\$string =~ tr($boldString) ($normalString));
3912 334         29392 $string
3913             }
3914              
3915             sub enclosedString($) # Convert alphanumerics in a string to enclosed alphanumerics.
3916 668     668 1 2004 {my ($string) = @_; # String to convert
3917 668         42084 eval qq(\$string =~ tr($normalString) ($circleString));
3918 668         4008 $string
3919             }
3920              
3921             sub enclosedStringUndo($) # Undo alphanumerics in a string to enclosed alphanumerics.
3922 334     334 1 1002 {my ($string) = @_; # String to convert
3923 334         19038 eval qq(\$string =~ tr($circleString) ($normalString));
3924 334         2338 $string
3925             }
3926              
3927             sub enclosedReversedString($) # Convert alphanumerics in a string to enclosed reversed alphanumerics.
3928 668     668 1 2004 {my ($string) = @_; # String to convert
3929 668         44756 eval qq(\$string =~ tr($normalString) ($darkString));
3930 668         6012 $string
3931             }
3932              
3933             sub enclosedReversedStringUndo($) # Undo alphanumerics in a string to enclosed reversed alphanumerics.
3934 334     334 1 1002 {my ($string) = @_; # String to convert
3935 334         17034 eval qq(\$string =~ tr($darkString) ($normalString));
3936 334         2338 $string
3937             }
3938              
3939             sub superScriptString($) # Convert alphanumerics in a string to super scripts.
3940 668     668 1 2338 {my ($string) = @_; # String to convert
3941 668         39412 eval qq(\$string =~ tr($normalString) ($superString));
3942 668         4008 $string
3943             }
3944              
3945             sub superScriptStringUndo($) # Undo alphanumerics in a string to super scripts.
3946 334     334 1 1002 {my ($string) = @_; # String to convert
3947 160655         994355 eval qq(\$string =~ tr($superString) ($normalString));
3948 334         2672 $string
3949             }
3950              
3951             sub subScriptString($) # Convert alphanumerics in a string to sub scripts.
3952 668     668 1 1670 {my ($string) = @_; # String to convert
3953 668         36406 eval qq(\$string =~ tr($lowerString) ($lowsubString));
3954 668         4008 $string
3955             }
3956              
3957             sub subScriptStringUndo($) # Undo alphanumerics in a string to sub scripts.
3958 334     334 1 1002 {my ($string) = @_; # String to convert
3959 334         19706 eval qq(\$string =~ tr($lowsubString) ($lowerString));
3960 334         2338 $string
3961             }
3962              
3963             sub isFileUtf8($) # Return the file name quoted if its contents are in utf8 else return undef.
3964 0     0 1 0 {my ($file) = @_; # File to test
3965 0         0 my $f = quoteFile($file);
3966              
3967 0 0       0 return undef unless confirmHasCommandLineCommand(q(isutf8)); # Confirm we have isutf8
3968              
3969 0         0 qx(isutf8 -q $f); # Test
3970 0 0       0 return $f unless $?; # File is utf8
3971             undef # File is not utf8
3972 0         0 }
3973              
3974             sub convertUtf8ToUtf32($) # Convert a number representing a single unicode point coded in utf8 to utf32.
3975             {my ($c) = @_; # Unicode point encoded as utf8
3976              
3977             return $c if $c <= 0x7f; # Ascii
3978              
3979             my sub invalid # Invalid utf8
3980             {confess "Invalid utf8 character: ".sprintf("%08x", $c)."\n";
3981             };
3982              
3983             if ($c <= 0xdfff) # 2 bytes
3984             {my $d = $c >> 8; $d &= 0x1f;
3985             $c &= 0xff; $c <= 0xbf or invalid; $c &= 0x3f;
3986             return $c | ($d << 6);
3987             }
3988              
3989             if ($c <= 0xefffff) # 3 bytes
3990             {my $e = $c >> 16; $e &= 0x0f;
3991             my $d = $c >> 8; $d &= 0xff; $d <= 0xbf or invalid; $d &= 0x3f;
3992             $c &= 0xff; $c <= 0xbf or invalid; $c &= 0x3f;
3993             return $c | ($d << 6) | ($e << 12);
3994             }
3995              
3996             if ($c <= 0xf7FFFFFF) # 4 bytes
3997             {my $f = $c >> 24; $f &= 0x07;
3998             my $e = $c >> 16; $e &= 0xff; $e <= 0xbf or invalid; $e &= 0x3f;
3999             my $d = $c >> 8; $d &= 0xff; $d <= 0xbf or invalid; $d &= 0x3f;
4000             $c &= 0xff; $c <= 0xbf or invalid; $c &= 0x3f;
4001             return $c | ($d<<6) | ($e<<12) | ($f<<18);
4002             };
4003              
4004             confess "Invalid utf8 code: ".sprintf("%08x", $c). "\n";
4005             }
4006              
4007             sub convertUtf32ToUtf8($) # Convert a number representing a single unicode point coded in utf32 to utf8.
4008 6     6 1 18 {my ($c) = @_; # Unicode point encoded as utf32
4009              
4010 6 100       29 return $c if $c <= (1<<7); # Ascii
4011              
4012 5 100       13 if ($c <= (1 << 11)) # 2 bytes
4013 1         3 {my $d = ($c >> 0) & 0x3f;
4014 1         2 $c = ($c >> 6);
4015 1         11 return ($c<<8) | $d | 0xC080;
4016             }
4017              
4018 4 100       19 if ($c <= (1 << 16)) # 3 bytes
4019 3         8 {my $e = ($c >> 0) & 0x3f;
4020 3         4 my $d = ($c >> 6) & 0x3f;
4021 3         5 $c = ($c >> 12);
4022              
4023 3         27 return ($c<<16) | ($d<<8) | $e | 0xE08080
4024             }
4025              
4026 1 50       6 if ($c <= (1 << 21)) # 4 bytes
4027 1         3 {my $f = ($c >> 0) & 0x3f;
4028 1         2 my $e = ($c >> 6) & 0x3f;
4029 1         2 my $d = ($c >> 12) & 0x3f;
4030 1         2 $c = ($c >> 18);
4031 1         7 return ($c<<24) | ($d<<16) | ($e<<8) | $f | 0xF0808080
4032             }
4033              
4034 0         0 confess "Invalid utf32 code: $c";
4035             }
4036              
4037             #D1 Unix domain communications # Send messages between processes via a unix domain socket.
4038              
4039             sub newUdsr(@) #P Create a communicator - a means to communicate between processes on the same machine via L and L.
4040 0     0 1 0 {my (@parms) = @_; # Attributes per L
4041 0         0 my $u = genHash(q(Udsr), # Package name
4042             client => undef, # Client socket and connection socket
4043             headerLength => 8, #I Length of fixed header which carries the length of the following message
4044              
4045             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.
4046              
4047             serverPid => undef, # Server pid which can be used to kill the server via kill q(kill), $pid
4048             socketPath => q(unix-domain-socket-test.sock), #I Socket file
4049              
4050             serviceName => q(zzz), #I Service name for install by systemd
4051             serviceUser => q(), #I Userid for service
4052             @_
4053             );
4054             }
4055              
4056             sub newUdsrServer(@) # Create a communications server - a means to communicate between processes on the same machine via L and L.
4057 0     0 1 0 {my (@parms) = @_; # Attributes per L
4058 0         0 my $u = newUdsr(@_);
4059 0         0 my $f = $u->socketPath;
4060 0         0 unlink $f;
4061 0         0 my $s = IO::Socket::UNIX->new(Type=>SOCK_STREAM(), Local=>$f, Listen=>1); # Create socket
4062 0         0 xxx(qq(chmod ugo=rwx $f)); # Ensure that www-data can read and write to the socket
4063             # lll "Created unix domain socket as user:", qx(/usr/bin/whoami);
4064 0 0       0 if (my $pid = fork) # Run the server in a process by itself
4065 0         0 {$u->serverPid = $pid; # Record server pid so it can be killed
4066 0         0 return $u;
4067             }
4068             else # Run the server action on a client connection
4069 0         0 {while (my $con = $s->accept())
4070 0         0 {$u->client = $con;
4071 0     207   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  
4072 0         0 $con->close;
4073             }
4074 0         0 exit;
4075             }
4076             }
4077              
4078             sub newUdsrClient(@) # Create a new communications client - a means to communicate between processes on the same machine via L and L.
4079 0     205 1 0 {my (@parms) = @_; # Attributes per L
4080 0         0 my $u = newUdsr(@_);
4081 0         0 my $s = $u->client = IO::Socket::UNIX->new(Type=>SOCK_STREAM(), Peer => $u->socketPath);
4082 0         0 my $r1 = $!; my $r2 = $?;
  0         0  
4083 0 0       0 $s or confess join "\n", "Cannot create unix domain socket:",
4084             dump($u), dump({q($!)=>$r1, q($?)=>$r2, q(userId)=>qx(/usr/bin/whoami)});
4085 0         0 $u
4086             }
4087              
4088             sub Udsr::write($$) # Write a communications message to the L or the L.
4089 0     156   0 {my ($u, $msg) = @_; # Communicator, message
4090 0         0 my $con = $u->client;
4091             # $msg //= ''; # undef seems to get reported as wide char
4092 0         0 my $m = pad(length($msg), $u->headerLength).$msg;
4093 0 0       0 $con or confess "No unix domain socket:\n". dump($u); # Complain if the socket has not been created
4094 0         0 $con->print($m);
4095 0         0 $u
4096             }
4097              
4098             sub Udsr::read($) # Read a message from the L or the L.
4099 0     0   0 {my ($u) = @_; # Communicator
4100 0         0 my $con = $u->client;
4101 0         0 $con->read(my $length, $u->headerLength);
4102 0         0 $con->read(my $data, $length);
4103 0         0 $data
4104             }
4105              
4106             sub Udsr::kill($) # Kill a communications server.
4107 0     0   0 {my ($u) = @_; # Communicator
4108 0         0 my $p = $u->serverPid; # Server Pid
4109 0 0       0 kill 'KILL', $p if $p; # Kill server
4110 0         0 $u->serverPid = undef; # Server Pid
4111 0         0 unlink $u->socketPath; # Remove socket
4112 0         0 $u
4113             }
4114              
4115             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.
4116 0     0   0 {my ($u, $folder) = @_; # Communicator, folder to contain server code
4117              
4118 0         0 clearFolder($folder, 9); # Clear the output folder
4119              
4120             my $parms = join ', ', # Parameters to hand to server and client
4121 0 0       0 map {my $v = $$u{$_}; defined($v) ? qq($_ => q($v)) : ()}
  0         0  
4122 0         0 grep {!m/serverAction/} keys %$u;
  0         0  
4123              
4124 0         0 my $user = $u->serviceUser; # Communicator details
4125 0         0 my $code = $u->serverAction; # Server code minus
4126 0         0 $code =~ s(if \(!caller\).*\Z) ()s; # Remove initiator at end
4127 0         0 $code =~ s(##.*?\n) ()gs; # Remove some spare blank lines so line numbers match
4128              
4129             my $perlParameters = sub # Get perl parameters
4130 0 0   234   0 {if ($code =~ m(\A#!.*?perl\s*(.*?)\n)is)
4131 0         0 {my $p = $1;
4132 0         0 return $p;
4133             }
4134             q()
4135 0         0 }->();
  0         0  
4136              
4137 0         0 my $name = $u->serviceName;
4138              
4139 0         0 my $ssdt = fpe(qw(/etc systemd system), $name, q(service)); # Systemd folder
4140              
4141 0         0 my $cgif = fpd(qw(/usr lib cgi-bin), $name); # Cgi folder
4142 0         0 my $cgst = fpe($cgif, q(server), q(pl)); # Cgi server
4143 0         0 my $cgct = fpe($cgif, q(client), q(pl)); # Cgi client
4144              
4145 0         0 my $inst = fpe($folder, qw(install sh)); # Install script
4146 388614         92763263043 my $ssdl = fpe($folder, qw(service txt));
4147 0         0 my $cgsl = fpe($folder, q(server), q(pl));
4148 0         0 my $cgcl = fpe($folder, q(client), q(pl));
4149              
4150 0         0 owf($ssdl, <
4151             [Unit]
4152             Description=Http to unix domain socket server
4153              
4154             [Service]
4155             Type=forking
4156             ExecStart=/usr/lib/cgi-bin/$name/server.pl
4157             User=$user
4158              
4159             [Install]
4160             WantedBy=multi-user.target
4161             END
4162             # setPermissionsForFile($ssdl, q(ugo=rx));
4163 0         0 setPermissionsForFile($ssdl, q(ugo=r)); # Permissions will be copied to server if the file does not exist on the server
4164              
4165 0         0 my $server = join '', <
4166             #!/usr/bin/perl $perlParameters
4167             END
4168             <<'END';
4169             #-------------------------------------------------------------------------------
4170             # Http to unix domain socket server
4171             #-------------------------------------------------------------------------------
4172             use warnings FATAL => qw(all);
4173             use strict;
4174             use Carp;
4175             use Data::Dump qw(dump);
4176             use Data::Table::Text qw(:all);
4177             use utf8;
4178             use feature qw(say current_sub);
4179              
4180             makeDieConfess;
4181              
4182             # Server code which should contain a sub genResponse($hash) which returns the response to be sent to the client
4183            
4184              
4185             my $parms = newUdsr();
4186              
4187             $parms->serverAction = sub # Perform server action
4188             {my ($c) = @_; # Communicator
4189             my $parms = $c->read; # Parameter string from client
4190             my $data = $parms ? eval $parms : undef; # Decode parameter string
4191             $@ and confess "Unable to decode webUser request:\n$parms\n"; # Complain about parameter string
4192             my $resp = genResponse($data); # Execute server action and capture returned value
4193             $c->write($resp); # Write back to the client
4194             };
4195              
4196             unlink $parms->socketPath;
4197             newUdsrServer(%$parms);
4198             END
4199 0         0 $server =~ s() ($parms)s;
4200 0         0 $server =~ s() ($code)s;
4201 0         0 owf($cgsl, $server);
4202 0         0 setPermissionsForFile($cgsl, q(ugo=rx));
4203              
4204 0         0 my $client = <<'END';
4205             #!/usr/bin/perl -I/home/phil/perl/cpan/DataTableText/lib/
4206             #-------------------------------------------------------------------------------
4207             # Http to unix domain socket client
4208             #-------------------------------------------------------------------------------
4209             use warnings FATAL => qw(all);
4210             use strict;
4211             use Carp;
4212             use Data::Dump qw(dump);
4213             use Data::Table::Text qw(:all);
4214             use CGI;
4215             use utf8;
4216             use feature qw(say current_sub);
4217              
4218             makeDieConfess;
4219              
4220             my $cgi = CGI->new;
4221              
4222             my %v = $cgi->Vars;
4223             if (my $j = $cgi->param(q(POSTDATA))) # Load POST data
4224             {$v{POSTDATA} = $j;
4225             if (my $p = decodeJson($j))
4226             {if (ref($p) =~ m(hash)i)
4227             {%v = (%v, %$p);
4228             }
4229             }
4230             }
4231             #for my $k(keys %v)
4232             # {$v{$k} = wwwDecode($v{$k}) // q();
4233             # }
4234              
4235             my $parms = newUdsr();
4236             my $c = newUdsrClient(%$parms);
4237             say $c->read($c->write(dump({%v})));
4238             END
4239 0         0 $client =~ s() ($parms)s;
4240 0         0 owf($cgcl, $client);
4241 0         0 setPermissionsForFile($cgcl, q(ugo=rx));
4242              
4243 0         0 owf($inst, <
4244             sudo rm $ssdt $cgst $cgct
4245             sudo mkdir -p $cgif
4246             sudo cp $ssdl $ssdt
4247             sudo cp $cgsl $cgst
4248             sudo cp $cgcl $cgct
4249             sudo systemctl daemon-reload; sudo systemctl enable $name; sudo systemctl restart $name; sudo systemctl status $name
4250             END
4251              
4252 0         0 setPermissionsForFile $inst, q(u+x);
4253              
4254             # if (!$noInstall) # Install on server if available
4255             # {copyFolderToRemote($folder); # Copy code created locally to remote server
4256             # xxxr(qq(bash -x $inst)); # Install system by executing install procedure remotely
4257             # }
4258              
4259 0         0 lll <
4260             See status with:
4261              
4262             sudo systemctl status $name
4263              
4264             Install with:
4265              
4266             $inst
4267              
4268             Remove with:
4269              
4270             sudo rm $ssdt $cgst $cgct
4271              
4272             Access via:
4273              
4274             http://localhost/cgi-bin/$name/client.pl
4275              
4276             END
4277              
4278 0         0 $inst # Install script
4279             }
4280              
4281             #D2 www # Web processing
4282              
4283 0     0 1 0 sub wwwHeader {say STDOUT qq(Content-Type: text/html;charset=UTF-8\n\n)} # Html header.
4284              
4285             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.
4286 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
4287              
4288 0 0       0 if (!$code) # Show logon page if no code has been supplied
4289 2815860         93058575 {my $r = rand =~ s(\A0.) ()r;
4290 0         0 say STDOUT <
4291            
4292            
4293            
4294            

Logon with GitHub

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

Input:)gsi;

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

Output:)gsi;

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

Return:)gsi;

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

Parameters:)gsi;

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

Returns:)gsi;

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

Table of contents

7641            

7642             END
7643             for my $class(sort keys $d->parameters->%*) # Table of contents
7644             {my $comment = formatDocString classComment($class);
7645             my $m = stringMd5Sum $class;
7646             push @h, <
7647            
$class$comment
7648             END
7649             }
7650             push @h, <
7651            
7652             END
7653              
7654             for my $class(sort keys $d->parameters->%*) # Each class
7655             {my $comment = formatDocString classComment $class;
7656             my $m = stringMd5Sum $class;
7657             push @h, <
7658            

Class: $class

7659            

$comment

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

$title

7671             $comment
7672            

Parameters

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

Examples

7691            
 
7692             $examples
7693            
7694            
7695             END
7696             }
7697             }
7698              
7699             if (my $errors = $d->errors) # Errors by source file
7700             {push @h, q(

Possible improvements to documentation

);
7701              
7702             for my $file(sort keys %$errors) # Each file with errors
7703             {push @h, <
7704            

$file

7705            

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

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

Hello

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

Hello

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

HelloWorld

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

Sample html table

13067            
13068            

Head 2 rows

13069            
13070            

13071            
13072            
sourcetarget
13073            
1a
13074            
2b
13075            

13076            
13077            

 
13078             source The source number
13079             target The target letter
13080            
13081            

13082            
13083            

Footer

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

Hello World to you $user on $dtts!

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

Chapter 1

18117            

Section 1

18118            

Chapter 2

18119             XXXX
18120             END
18121            
18122             eq nws(<
18123            

Chapter 1

18124            

Section 1

18125            

Chapter 2

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