File Coverage

blib/lib/Data/Table/Text.pm
Criterion Covered Total %
statement 2614 3962 65.9
branch 793 1646 48.1
condition 255 1776 14.3
subroutine 380 987 38.5
pod 397 398 99.7
total 4439 8769 50.6


).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 and place in synopsis
16             package Data::Table::Text;
17 328     328   1374320 use v5.26;
  328         5248  
18             our $VERSION = 20210826; # Version
19 328     328   2296 use warnings FATAL => qw(all);
  328         328  
  328         16072  
20 328     328   1968 use strict;
  328         656  
  328         42968  
21 328     328   2624 use Carp qw(confess carp cluck);
  328         656  
  328         41000  
22 328     328   2296 use Cwd;
  328         656  
  328         37392  
23 328     328   2296 use Digest::MD5 qw(md5_hex);
  328         656  
  328         23944  
24 328     328   2296 use File::Path qw(make_path);
  328         656  
  328         25256  
25 328     328   2296 use File::Glob qw(:bsd_glob);
  328         656  
  328         85608  
26 328     328   291592 use File::Temp qw(tempfile tempdir);
  328         8709384  
  328         33784  
27 328     328   174496 use POSIX qw(:sys_wait_h strftime); # Http://www.cplusplus.com/reference/ctime/strftime/
  328         2307808  
  328         2296  
28 328     328   802616 use Data::Dump qw(dump);
  328         1833192  
  328         98728  
29 328     328   215168 use IO::Socket::UNIX;
  328         4618240  
  328         2296  
30 328     328   440176 use JSON;
  328         3729032  
  328         2624  
31 328     328   293888 use MIME::Base64;
  328         230912  
  328         24600  
32 328     328   2952 use Scalar::Util qw(blessed reftype looks_like_number);
  328         984  
  328         21320  
33 328     328   231896 use Storable qw(store retrieve dclone);
  328         1087320  
  328         33128  
34 328     328   213200 use Time::HiRes qw(time gettimeofday);
  328         484784  
  328         1968  
35 328     328   73472 use B;
  328         656  
  328         39032  
36 328     328   252232 use utf8;
  328         5248  
  328         2296  
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 3183     3183 1 223947 {strftime('%Y-%m-%d at %H:%M:%S', localtime)
42             }
43              
44             sub dateTimeStampName # Date time stamp without white space.
45 325     325 1 16900 {strftime('_on_%Y_%m_%d_at_%H_%M_%S', localtime)
46             }
47              
48             sub dateStamp # Year-monthName-day.
49 325     325 1 19500 {strftime('%Y-%b-%d', localtime)
50             }
51              
52             sub versionCode # YYYYmmdd-HHMMSS.
53 325     325 1 16900 {strftime('%Y%m%d-%H%M%S', localtime)
54             }
55              
56             sub versionCodeDashed # YYYY-mm-dd-HH:MM:SS.
57 325     325 1 27300 {strftime('%Y-%m-%d-%H:%M:%S', localtime)
58             }
59              
60             sub timeStamp # Hours:minute:seconds.
61 55480     55480 1 3400106 {strftime('%H:%M:%S', localtime)
62             }
63              
64             sub microSecondsSinceEpoch # Micro seconds since unix epoch.
65 325     325 1 5200 {my ($s, $u) = gettimeofday();
66 325         3900 $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 675     675 1 5400 {my (@cmd) = @_; # Command to execute followed by an optional regular expression to test the results
151 675 50       7200 @cmd or confess "No command\n"; # Check that there is a command to execute
152 675   33     9225 $_ or confess "Missing command component\n" for @cmd; # Check that there are no undefined command components
153 675         3375 my $success = $cmd[-1]; # Error check if present
154 675         11925 my $check = ref($success) =~ /RegExp/i; # Check for error check
155 675 50       7875 pop @cmd if $check; # Remove check from command
156 675         4950 my $cmd = join ' ', @cmd; # Command to execute
157 675 50       4500 say STDERR $cmd unless $check; # Print the command unless there is a check in place
158 675   50     3150 my $response = eval {qx($cmd 2>&1)} // "No such command"; # Execute command
  675         28334025  
159 675         15525 $response =~ s/\s+\Z//s; # Remove trailing white space from response
160 675 50 33     9000 say STDERR $response if $response and !$check; # Print non blank error message
161 675 0 33     10575 confess $response if $response and $check and $response !~ m/$success/; # Error check if an error checking regular expression has been supplied
      33        
162 675 50 33     7875 confess $response if $response and $response =~ m/Syntax error:.*unexpected/; # Check for a particularly annoying error
163 675         127125 $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 656     656 1 4920 {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 656 50       4592 $cmd or confess "No command\n"; # Check that there is a command to execute
191 656         2296 my @c; # Commands
192 656         4592 for(split /\n/, $cmd) # Split commands on new lines
193 1312         4920 {s(#.*\Z)()gs; # Remove comments
194 1312 50       7544 next unless m(\S); # Skip blank lines
195 1312         3280 push @c, $_; # Save command
196             }
197 656         2624 my $c = join ' && ', @c; # Command string to execute
198 656         1786944 my $r = qx($c 2>&1); # Execute command
199 656         17384 my $R = $?;
200 656         22632 $r =~ s/\s+\Z//s; # Remove trailing white space from response
201              
202 656 50 0     1099128 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 328         19352 $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 984     984 1 6560 {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 984 100   984   7872 {return () unless $valid; # No keywords definitions
224 656 50       3936 return map {lc($_)=>0} @$valid if ref($valid) =~ m(array)is; # Keyword names as an array but with no explanation
  1968         8200  
225 0         0 %$valid # Hash of keyword name=>explanation
226 984         9840 }->();
227              
228 984         6560 my %keywords;
229             my @positionals;
230 984         5248 for my $arg(@$args) # Each arg
231 4920 100       28864 {if ($arg =~ m/\A-+(\S+?)\s*(=\s*(.+)\s*)?\Z/) # Keyword parameters with leading and trailing blanks removed
232 3280 100 100     16072 {if ($valid and !defined($valid{lc($1)})) # Validate keyword name
233 328         656 {my @s;
234 328         1640 for my $k(sort keys %valid) # Create a table of valid keywords
235 984 50       2624 {if (my $v = $valid{$k})
236 0         0 {push @s, [$k, $v];
237             }
238             else
239 984         5248 {push @s, [$k];
240             }
241             }
242 328 50       2296 if (@s) # Format error message
243 328         5904 {my $s = formatTable(\@s, [qw(Keyword Description)]);
244 328         68552 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 2952         22632 $keywords{lc($1)} = $3; # Save valid keyword parameter
251             }
252             else # Positional parameter
253 1640         5248 {push @positionals, $arg;
254             }
255             }
256 656         17712 $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 981     981 1 3923 {my ($sub, @our) = @_; # Sub to call, names of our variable names with preceding sigils to copy back
261 981         3924 my ($package) = caller; # Caller's package
262 981         5882 my $folder = &temporaryFolder; # Folder for returned data files
263 981         1208320 my $pid = fork; # Fork
264 981 50       69857 if (!defined($pid)) # Fork failed
    100          
265 0         0 {confess "Unable to fork!\n";
266             }
267             elsif ($pid == 0) # Fork - child
268 3         1136 {&$sub; # Execute the sub
269 3         45 my @save = ''; # Code to copy back our variables
270 3         77 for my $o(@our) # Each variable
271 9         215 {my ($sigil, $var) = $o =~ m(\A(.)(.+)\Z)s; # Sigil, variable name
272 9         84 my $our = $sigil.$package.q(::).$var; # Add caller's package to variable name
273 9         60 my $char = ord($sigil); # Differentiate between variables with the same type but different sigils
274 9         228 my $file = fpe($folder, qq(${$}$var$char), q(data)); # File for this variable
275 9         89 push @save, <
276             store \\$our, q($file);
277             END
278             }
279 3         87 my $save = join "\n", @save; # Perl code to store our variables
280 3         887 eval $save; # Evaluate code to store our variables
281 3 50       501 confess $@ if $@; # Confess any errors
282 3         17078 exit; # End of child process
283             }
284             else # Fork - parent
285 978         3645045164 {waitpid $pid,0; # Wait for child
286 978         46982 my @save = ''; # Code to retrieve our variables
287 978         5879 my @file; # Transfer files
288 978         14027 for my $o(@our)
289 2934         62256 {my ($sigil, $var) = $o =~ m(\A(.)(.+)\Z)s; # Sigil, variable name
290 2934         11744 my $our = $sigil.$package.q(::).$var; # Add caller's package to variable name
291 2934         6198 my $char = ord($sigil); # Differentiate between variables with the same type but different sigils
292 2934         29678 my $file = fpe($folder, qq($pid$var$char), q(data)); # Save file
293 2934         16629 push @save, <
294             $our = ${sigil}{retrieve q($file)};
295             END
296 2934         10435 push @file, $file; # Remove transfer files
297             }
298 978         6204 my $save = join "\n", @save;
299 978         272227 eval $save; # Evaluate perl code
300 978         92591 my $r = $@; # Save result
301 978         15981 clearFolder($folder, scalar(@our)+1); # Remove transfer files
302 978 50       270624 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 77859     77859 1 13256856 {my ($file) = @_; # File name
311 77859 50       183880088 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 115     115 1 1495 {my (@files) = @_; # File names
317 1035         2070 my ($l) = map {$$_[1]} sort {$$b[0] <=> $$a[0]} # Largest file
  2185         3910  
318 115   50     1150 map {[fileSize($_)//0, $_]} @files;
  1035         3335  
319 115         5520 $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 450     450 1 1350 {my ($file) = @_; # File or string
332 450 50 33     13050 if ($file !~ m(\0|\n|\A\.|\A\/\Z)s and -e $file) # From file - this is not entirely satisfactory.
333 450         2475 {my $s = readBinaryFile($file);
334 450         5175 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 450     450 1 1575 {my ($m) = @_; # Md5 hash
344 450 50 0     2025 length($m) == 32 or confess "Not an md5 hash: ". ($m//"undef");
345 450         6525 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 225     225 1 1125 {my ($G) = @_; # Guid
351 225 50       1125 length($G) >= 41 or confess "Incorrect length for guid: $G"; # Check guid
352 225         900 my $g = substr($G, 0, 41);
353 225 50       6750 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 225     225 1 900 {my ($string) = @_; # String
359 225         2250 guidFromMd5 &stringMd5Sum($string);
360             }
361              
362             sub fileModTime($) # Get the modified time of a B<$file> as seconds since the epoch.
363 325     325 1 3250 {my ($file) = @_; # File name
364 325   50     12025 (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 328     328 1 984 {my (@files) = @_; # Files to check
390 328         1312 for(@files)
391 656 100       53792 {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 225     225 1 675 {my ($file) = @_; # File
398 225         2700 $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 155371     155371 1 88559380 {$^O =~ m(MSWin32)
407             }
408              
409             sub onMac #P Are we on mac.
410 553     553 1 15682 {$^O =~ m(darwin)
411             }
412              
413             sub filePathSeparatorChar #P File path separator.
414 32157 50   32157 1 2657737 {onWindows ? '\\' : '/';
415             }
416              
417             sub denormalizeFolderName($) #P Remove any trailing folder separator from a folder name.
418 159299     159299 1 7814705 {my ($name) = @_; # Folder name
419 159299         44360343 $name =~ s([\/\\]+\Z) ()gsr;
420             }
421              
422             sub renormalizeFolderName($) #P Normalize a folder name by ensuring it has a single trailing directory separator.
423 11384     11384 1 45504 {my ($name) = @_; # Name
424 11384         78156 ($name =~ s([\/\\]+\Z) ()gsr).filePathSeparatorChar; # Put a trailing / on the folder name
425             }
426              
427             sub prefferedFileName($) #P Normalize a file name.
428 93721     93721 1 5568382 {my ($name) = @_; # Name
429 93721 50       6381136 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 77709     77709 1 25384297 {my (@file) = @_; # File name components
435 77709   50     23619260 defined($_) or confess "Missing file component\n" for @file; # Check that there are no undefined file components
436 77709         3055576 my @components = grep {$_} map {denormalizeFolderName($_)} @file; # Skip blank components
  159299         67744771  
  159299         50692357  
437 77709 100       25631766 return '' unless @components; # No components resolves to '' rather than '/'
438 77381         43041747 prefferedFileName join '/', @components; # Join separate components
439             }
440              
441             sub filePathDir(@) # Create a folder name from a list of names. Identical to L.
442 11384     11384 1 3424584 {my (@file) = @_; # Directory name components
443 11384         67406 my $file = filePath(@_);
444 11384 100       64537 return '' unless $file; # No components resolves to '' rather than '/'
445 11056         2695391 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 65873     65873 1 85349642 {my (@File) = @_; # File name components and extension
450 65873 50       9089888 my @file = grep{defined and /\S/} @_; # Remove undefined and blank components
  201765         76581480  
451 65873 50       17998280 @file > 1 or confess "At least two non blank file name components required\n";
452 65873         11496880 my $x = pop @file;
453 65873         4881460 my $n = pop @file;
454 65873         21460922 my $f = "$n.$x";
455 65873 100       10011860 return $f unless @file;
456 64336         64174867 filePath(@file, $f)
457             }
458              
459 328     328   1504208 BEGIN{*fpd=*filePathDir}
460 328     328   9840 BEGIN{*fpe=*filePathExt}
461 328     328   980720 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 2215     2215 1 8546 {my ($file) = @_; # File name
467 2215 50       6627 $file or confess "File required";
468 2215 50       4999 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 2215 50       7758 {return '' unless $file =~ m(/); # Must have a / in it else no path
474 2215         25391 $file =~ s([^/]*\Z) ()gsr
475             }
476             }
477              
478             sub fpn($) # Remove the extension from a file name.
479 656     656 1 1640 {my ($file) = @_; # File name
480 656 50       2296 $file or confess "File required";
481 656 50       1312 if (onWindows)
482 0 0       0 {return '' unless $file =~ m(\\); # Must have a \ in it else no path
483             }
484             else
485 656 50       4264 {return '' unless $file =~ m(/); # Must have a / in it else no path
486             }
487 656         6232 $file =~ s(\.[^.]+?\Z) ()gsr
488             }
489              
490             sub fn($) #I Remove the path and extension from a file name.
491 2296     2296 1 8725 {my ($file) = @_; # File name
492 2296 50       7131 $file or confess "File required";
493 2296 50       5925 if (onWindows)
494 0         0 {$file =~ s(\A.*\\) ()gsr =~ s(\.[^.]+?\Z) ()gsr
495             }
496             else
497 2296         33020 {$file =~ s(\A.*/) ()gsr =~ s(\.[^.]+?\Z) ()gsr
498             }
499             }
500              
501             sub fne($) # Remove the path from a file name.
502 3615     3615 1 13140 {my ($file) = @_; # File name
503 3615 50       10896 $file or confess "File required";
504 3615 50       10483 if (onWindows)
505 0         0 {$file =~ s(\A.*\\) ()gsr;
506             }
507             else
508 3615         83228 {$file =~ s(\A.*/) ()gsr;
509             }
510             }
511              
512             sub fe($) # Get the extension of a file name.
513 2506     2506 1 9068 {my ($file) = @_; # File name
514 2506 50       7649 $file or confess "File required";
515 2506 50       12458 return '' unless $file =~ m(\.)s; # Must have a period
516 2506         13227 my $f = $file =~ s(\.[^.]*?\Z) ()gsr;
517 2506         17108 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 1309     1309 1 7842 {my ($file) = @_; # File to check
522 1309 100       20953 unless(-e $file)
523 653         4565 {confess "Can only find the prefix (below) of the file (further below):\n".
524             matchPath($file)."\n$file\n";
525             }
526             $file
527 656         3608 }
528              
529             sub quoteFile($) # Quote a file name.
530 325     325 1 2600 {my ($file) = @_; # File name
531 325 50       3250 $file or confess "Undefined file to quote";
532 325         3250 $file =~ s(") (\\\")gs;
533 325         2600 $file =~ s(\$) (\\\$)gs;
534 325         1950 qq(\"$file\")
535             }
536              
537             sub removeFilePrefix($@) # Removes a file B<$prefix> from an array of B<@files>.
538 656     656 1 2296 {my ($prefix, @files) = @_; # File prefix, array of file names
539 656         1968 my @f = map {s(\A$prefix) ()r} @files;
  984         13776  
540 656 50 66     5904 return $f[0] if @f == 1 and !wantarray; # Special case of wanting one file in scalar context
541             @f
542 656         50184 }
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 455     455 1 2504 {my ($file, $known, $new) = @_; # File name, existing prefix, optional new prefix defaults to q()
546 455         1135 my $L = length($file);
547 455         1148 my $l = length($known);
548 455 50       2952 if ($L >= $l)
549 455 50       3428 {if (substr($file, 0, $l) eq $known)
550 455   100     8275 {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 1009     1009 1 15756 {my ($file, $extension) = @_; # File name, optional new extension
559 1009 50       4017 return $file =~ s(\.\w+\Z) ()sr unless defined $extension; # Remove extension
560 1009         5031 my $ext = $extension =~ s(\A\.+) ()gsr; # Remove leading dots
561 1009 50       3139 return $file unless $ext; # No extension after dot removal
562 1009         11510 ($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 225     225 1 1125 {my ($file, $known, $new) = @_; # File name, existing prefix, new prefix
567 225         900 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 900     900 1 3150 {my ($file, $prefix) = @_; # File name to test, file name prefix
572 900 100       5625 return $file =~ m(\A/)s unless $prefix; # Check against /
573 450         3825 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 984     984 1 4920 {my ($file) = @_; # File name
584 984 100       7544 return $file unless $file =~ m(/)s; # No path to deduplicate
585 656 50       4264 return $file if $file =~ m(\A[/.]); # Later
586 656         2952 my ($p, @p) = split m(/), $file;
587 656   66     4264 shift @p while @p && $p[0] eq $p;
588 656         3608 join "/", $p, @p;
589             } # removeDuplicatePrefixes
590              
591             sub containingFolderName($) # The name of a folder containing a file.
592 35     35 1 208 {my ($file) = @_; # File name
593 35         329 my @p = split m(/), $file;
594 35 50       415 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 328     328 1 6560 {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 4161     4161 1 10656 {my ($file) = @_; # File name to parse
615 4161 50       10637 defined($file) or confess "File required";
616 4161 100 66     28154 return ($file) if $file =~ m{\/\Z}s or $file =~ m/\.\.\Z/s; # Its a folder
617 3177 100       13421 if ($file =~ m/\.[^\/]+?\Z/s) # The file name has an extension
618 2193 100       7948 {if ($file =~ m/\A.+[\/]/s) # The file name has a preceding path
619 1865         7582 {my @f = $file =~ m/(\A.+[\/])([^\/]*)\.([^\/]+?)\Z/s; # File components
620 1865         13852 return @f;
621             }
622             else # There is no preceding path
623 328         1640 {my @f = $file =~ m/(\A.+)\.([^\/]+?)\Z/s; # File components
624 328         2296 return (undef, @f)
625             }
626             }
627             else # The file name has no extension
628 984 100       4592 {if ($file =~ m/\A.+[\/]/s) # The file name has a preceding path
    50          
    0          
629 656         2952 {my @f = $file =~ m/(\A.+\/)([^\/]+?)\Z/s; # File components
630 656         4264 return @f;
631             }
632             elsif ($file =~ m/\A[\/]./s) # The file name has a single preceding /
633 328         2296 {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 14657     14657 1 55901 {my ($a, $b) = @_; # Absolute file to be made relative, against this absolute file.
652              
653 14657 100       34356 my $m = length($a) < length($b) ? length($a) : length($b); # Shortest length
654              
655 14657 50       45237 $a =~ m(\A/) or confess "$a is not absolute"; # Require absolute file names
656 14657 50       29333 $b =~ m(\A/) or confess "$b is not absolute";
657 14657         54524 $b =~ s([^/]+\Z) (); # Make the against file into a folder
658              
659 14657         21442 my $s = 0; # Position of last matching /
660              
661 14657         32426 for my $i(1..$m-1) # Locate first non matching character - the first character of both file names is / which matches
662 120734 100       230685 {if (substr($a, $i, 1) ne substr($b, $i, 1)) # First mismatch
    100          
663 11705         13795 {my $u = 0; # Number of jumps up from $b
664 11705         15313 my $p = $s; # Last /
665 11705         32182 ++$u while(($p = index($b, q(/), $p+1)) > -1); # Number of / to jump up
666 11705         72068 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 24085         31873 {$s = $i;
670             }
671             }
672 2952         3936 my $u = 0; # Number of jumps up from $b
673 2952         3608 my $p = $s; # Last /
674 2952         9184 ++$u while(($p = index($b, q(/), $p+1)) > -1); # Number of / to jump up
675 2952         20664 ((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 15416     15416 1 37392 {my ($a, $r) = @_; # Absolute file, relative file
680              
681 15416 50       32800 return $r if $r =~ m(\A/); # Return absolute file if such is supplied
682 15416 50       35752 $a =~ m(\A/) or confess "$a is not absolute"; # Require absolute file name
683 15416         55432 $a =~ s([^/]+\Z) (); # Make the absolute file into a folder
684 15416         28208 $r =~ s(\A\./) (); # Remove any leading ./ from relative file
685 15416         22960 $r =~ s(\.\.\Z) (../); # Make trailing .. into a folder
686              
687 15416         22632 my $R = qq($a$r); # Combine and .
688 15416         87576 undef while $R =~ s([^/]+/\.\./) (); # Squeeze out jumps
689              
690 15416         60024 $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 1312     1312 1 1968 {my ($file) = @_; # File to test
695 1312 100       6560 return $file if $file =~ m(\A/);
696             undef
697 656         1640 }
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 328     328 1 984 {my (@files) = @_; # Absolute and relative file names
701 328         3608 unshift @files, currentDirectory;
702 328         1640 while(@files > 1)
703 984         1640 {my $a = shift @files;
704 984         1312 my $b = shift @files;
705 984 100       1968 unshift @files, absFile($b) ? $b : absFromAbsPlusRel($a, $b);
706             }
707 328         1968 $files[0]
708             } # sumAbsAndRel
709              
710             #D2 Temporary # Temporary files and folders
711              
712             sub temporaryFile # Create a new, empty, temporary file.
713 5714     5714 1 54105 {my ($fh, $filename) = tempfile;
714 5714         2845614 $filename
715             }# temporaryFile
716              
717             sub temporaryFolder # Create a new, empty, temporary folder.
718 5316     5316 1 6335455 {my $d = tempdir();
719 5316         99473150 $d =~ s/[\/\\]+\Z//s;
720 5316         38769 $d.filePathSeparatorChar;
721             } # temporaryFolder
722              
723 328     328   1330696 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 8753     8753 1 44008 {my ($folder, $dirs) = @_; # Folder to start the search with, true if only folders are required
729 8753         18535 my @files; # Files
730              
731 8753 50       32554 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 8753 50       3448439 return undef unless confirmHasCommandLineCommand(q(find)); # Confirm we have find
743 8753 100       101239 my $c = qq(find "$folder" -print0 -type ).($dirs ? 'd' : 'f'); # Use find command to find files
744 8753         70960270 my $res = qx($c); # Execute find command
745 8753 50       172785 defined($res) or confess "No result from find command below\n$c\n"; # Find failed for some reason
746 8753         3493411 utf8::decode($res); # Decode unicode file names
747 8753         4147194 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 4310     4310 1 31450 {my ($folder, $filter) = @_; # Folder to start the search with, optional regular expression to filter files
752 4310         8968 my @files; # Files
753 4310         18573 for(findAllFilesAndFolders($folder, 0)) # All files and folders
754 60808 100       768018 {next if -d $_; # Do not include folder names
755 52893 50 66     169296 next if $filter and $filter and !m($filter)s; # Filter out files that do not match the regular expression
      66        
756 52893         141325 push @files, $_;
757             }
758             @files
759 4310         134009 } # 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 3326     3326 1 38937 {my ($folder, $filter) = @_; # Folder to start the search with, optional regular expression to filter files
763 3326 50       54652 return findAllFilesAndFolders($folder, 1) if onWindows; # All folders if on windows
764              
765 3326         10777 my @dir; # Directories
766 3326         3108030 for(findAllFilesAndFolders($folder, 1)) # All files and folders
767 54901 100       667386 {next unless -d $_; # Include only folders
768 4960 0 33     31858 next if $filter and $filter and !m($filter)s; # Filter out directories that do not match the regular expression
      33        
769 4960         60620 push @dir, fpd($_);
770             }
771             @dir
772 3326         115328 } # findDirs
773              
774             sub fileList($) # Files that match a given search pattern interpreted by L.
775 650     650 1 2275 {my ($pattern) = @_; # Search pattern
776 650         109525 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 792     792 1 8207 {my (@FoldersandExtensions) = @_; # Mixture of folder names and extensions
781 792 50       7286 my (@foldersandExtensions) = map {ref($_) ? @$_ : $_} @_;
  809         12698  
782              
783 792 100 66     2618 my @extensions = grep {$_ and !-d $_ and !m([\/])} @_; # Extensions are not directories
  809         36720  
784 792         5035 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 792 100       6070 my $ext = @extensions ? join '|', @extensions : undef; # Extensions
789 792         1994 my @file; # Files
790              
791 792         7320 for my $dir(@_) # Directories
792 809 100 66     15038 {next unless $dir && -d $dir; # Do not include folder names
793              
794 792         9403 for my $d(findAllFilesAndFolders($dir, 0)) # All files and folders beneath each folder
795 2769 100       64458 {next if -d $d; # Do not include folder names
796 1977 100 100     66088 push @file, $d if !$ext or $d =~ m(($ext)\Z)is; # Filter by extension if requested.
797             }
798             }
799             @file # Return files
800 792         48313 } # searchDirectoryTreesForMatchingFiles
801              
802             sub searchDirectoryTreeForSubFolders($) #I Search the specified directory under the specified folder for sub folders.
803 325     325 1 2600 {my ($folder) = @_; # The folder at which to start the search
804 325         2275 my @f; # Folders found
805 325         4225 for my $d(findAllFilesAndFolders($folder, 0)) # All files and folders beneath the start folder
806 1625 100       39000 {push @f, $d if -d $d; # Do not include file names
807             }
808             @f # Return folder names
809 325         16575 } # searchDirectoryTreeForSubFolders
810              
811             sub hashifyFolderStructure(@) # Hashify a list of file names to get the corresponding folder structure.
812 17     17 1 340 {my (@files) = @_; # File names
813 17         51 my %h;
814 17         119 for my $f(@files) # Map each file
815 68         408 {my @f = split m(/), $f;
816 68         238 my $s = join '', map {q({).dump($_).q(})} @f; # Hashify directory structure
  272         20145  
817 68         5984 my $c = "\$h$s = ".dump($f); # Load targets
818 68         12563 eval $c;
819 68 50       578 confess $@ if $@;
820             }
821 17         170 \%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 981     981 1 3924 {my ($file) = @_; # File name
869 981 100       12109 return $file if -e $file; # File exists so nothing more to match
870 653         12094 my @path = split /[\/\\]/, $file; # Split path into components
871 653         2937 while(@path) # Remove components one by one
872 978         1956 {pop @path; # Remove deepest component and try again
873 978         2937 my $path = join filePathSeparatorChar, @path, ''; # Containing folder
874 978 100       1801509 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 328     328 1 1968 {my ($file, @ext) = @_; # File name minus extensions, possible extensions
881 328         1968 for my $ext(@ext) # Each extension
882 984         2952 {my $f = fpe($file, $ext); # Possible file
883 984 100       23616 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 4004     4004 1 3912248 {my ($folder, $limitCount, $noMsg) = @_; # Folder, maximum number of files to remove to limit damage, no message if the folder cannot be completely removed.
890 4004 100       107409 return unless -d $folder; # Only works on a folder that exists
891 3329         7852379 my @files = findFiles($folder); # Find files to be removed
892 3329 100       53171 if (@files > $limitCount) # Limit the number of files that can be deleted to limit potential opportunity for damage
893 328         7872 {my $f = @files;
894 328         1078464 confess "Limit is $limitCount, but $f files under folder:\n$folder\n";
895             }
896 3001         39972 my @dirs = findDirs($folder); # These directories should be empty and thus removable after removing the files
897 3001         1825350 unlink $_ for @files; # Remove files
898 3001         208209 rmdir $_ for reverse @dirs; # Remove empty folders
899 3001 50 33     58794 unless($noMsg or onWindows)
900 3001 50       281384 {-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 8193     8193 1 31689 {my ($file) = @_; # Name of file to read
908 8193 50       26848 defined($file) or
909             confess "Cannot read undefined file\n";
910 8193 50       55412 $file =~ m(\n|\r) and
911             confess "File name contains a new line:\n=$file=\n";
912 8193 50       134819 -e $file or
913             confess "Cannot read file because it does not exist, file:\n$file\n";
914 328 50   328   2624 open(my $F, "<:encoding(UTF-8)", $file) or
  328         656  
  328         2296  
  8193         421703  
915             confess "Cannot open file for unicode input, file:\n$file\n$!\n";
916 8193 100       4684535 if (wantarray) # Read as an array
917 979         4243 {my @string = eval {<$F>};
  979         24504  
918 979 50       20050 $@ and confess "$@ reading file:\n$file\n";
919 979         29678 return @string;
920             }
921             else # Read as a string
922 7214         65492 {local $/ = undef;
923 7214         23451 my $string = eval {<$F>};
  7214         240259  
924 7214 50       169868 $@ and confess "$@ reading file:\n$file\n";
925 7214         171598 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 11 {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         11 {local $/ = undef;
937 1         4 my $string = eval {};
  1         24  
938 1 50       8 $@ and confess "$@ reading STDIN\n";
939 1         16 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 1625     1625 1 6175 {my ($file) = @_; # File to read
958 1625         6500 my $string = readFile($file);
959 1625         112450 my $res = eval $string;
960 1625 50       10400 $@ and confess "$@\nin file:\n$file\n";
961 1625         9100 reloadHashes($res);
962 1625         10075 $res
963             } # evalFile
964              
965             sub evalFileAsJson($) # Read a B<$file> containing L and return the corresponding L data structure.
966 650     650 1 1950 {my ($file) = @_; # File to read
967 650         2600 my $string = readFile($file);
968 650         3250 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 325     325 1 3575 {my ($file) = @_; # File to read
973 325         8125 my $string = readGZipFile($file);
974 325         72800 my $res = eval $string;
975 325 50       7800 $@ and confess "$@\n";
976 325         8125 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 327     327 1 1332 {my ($file) = @_; # File to read
981 327 50       5308 -e $file or confess "No such file: $file\n"; # Check file exists
982 327         2333 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 327         33915 $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 2678     2678 1 8874 {my ($file) = @_; # File to read
1005 2678 50       30401 -e $file or
1006             confess "Cannot read binary file because it does not exist:\n$file\n";
1007 2678 50       82370 open my $F, "<$file" or
1008             confess "Cannot open binary file for input:\n$file\n$!\n";
1009 2678         11837 binmode $F;
1010 2678         13905 local $/ = undef;
1011 2678         95374 <$F>;
1012             } # readBinaryFile
1013              
1014             sub readGZipFile($) # Read the specified file containing compressed L content represented as L through L.
1015 650     650 1 5850 {my ($file) = @_; # File to read.
1016 650 50       6825 defined($file) or
1017             confess "Cannot read undefined file\n";
1018 650 50       11050 $file =~ m(\n|\r) and
1019             confess "File name contains a new line:\n=$file=\n";
1020 650 50       14950 -e $file or
1021             confess "Cannot read file because it does not exist, file:\n$file\n";
1022 650 50       15600 return undef unless confirmHasCommandLineCommand(q(gunzip)); # Confirm we have gunzip
1023 650 50       1786525 open(my $F, "gunzip < $file|") or # Unzip input file
1024             confess "Cannot open file for input, file:\n$file\n$!\n$?\n";
1025 650         72150 binmode($F, "encoding(UTF-8)");
1026 650         179075 local $/ = undef;
1027 650         1867450 my $string = <$F>;
1028 650         265850 $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 16882     16882 1 109897 {my ($file) = @_; # File or folder name
1033 16882         179935 my @path = split /[\\\/]+/, $file;
1034 16882 100       79889 return undef unless @path > 1; # Its just a file
1035 14479 100       88758 pop @path unless $file =~ /[\\\/]\Z/; # Remove file component allowing us to present files as well as folders
1036 14479         93535 my $path = join filePathSeparatorChar, @path;
1037 14479 100       297791 return undef if -d $path;
1038 3062         8955 eval {make_path($path)};
  3062         1116327  
1039 3062 50       59198 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 11304     11304 1 2144040 {my ($file, $string) = @_; # File to write to or B for a temporary file, unicode string to write
1059 11304   66     70792 $file //= temporaryFile;
1060 11304 50       75244 $file =~ m(\n|\r)s and confess "File name contains a new line:\n=$file=\n";
1061 11304 50       39262 defined($string) or cluck "No string for file:\n$file\n";
1062 11304         48519 makePath($file);
1063 11304 50       751810 open my $F, ">$file" or
1064             confess "Cannot open file for write because:\n$file\n$!\n";
1065 11304         110376 binmode($F, ":utf8");
1066 11304         33374 print {$F} $string;
  11304         147092  
1067 11304 50       684765 close ($F) or confess "Could not close file:\n$file\n$!\n";;
1068 11304 50       177815 -e $file or confess "Failed to write to file:\n$file\n";
1069 11304         272670 $file
1070             } # overWriteFile
1071              
1072 328     328   19935512 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 4649     4649 1 216622 {my ($file, $string) = @_; # New file to write to or B for a temporary file, string to write
1076 4649 100       24653 if (defined $file)
1077 1343 100       1095788 {-e $file and confess "File already exists:\n$file\n";
1078             }
1079 4324         24576 &overWriteFile(@_);
1080             } # writeFile
1081              
1082             sub writeTempFile(@) # Write an array of strings as lines to a temporary file and return the file name.
1083 327     327 1 7496 {my (@strings) = @_; # Array of lines
1084 327         2933 overWriteFile(undef, join '', map{"$_\n"} @strings);
  652         5874  
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 1103     1103 1 4743 {my ($file, $string) = @_; # File to write to or B for a temporary file, L string to write
1096 1103   66     7984 $file //= temporaryFile;
1097 1103 50       11635 $file =~ m(\n|\r)s and confess "File name contains a new line:\n=$file=\n";
1098 1103 50       3965 $string or carp "No string for binary write to file:\n$file\n";
1099 1103         8676 makePath($file);
1100 1103 50       96163 open my $F, ">$file" or
1101             confess "Cannot open file for binary write because:\n$file\n$!\n";
1102 1103         6393 binmode($F);
1103 1103         4071 print {$F} $string;
  1103         10557  
1104 1103         91659 close ($F);
1105 1103 50       23188 -e $file or confess "Failed to write in binary to file:\n=$file=\n$!\n";
1106 1103         29215 $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 878     878 1 7490 {my ($file, $string) = @_; # New file to write to or B for a temporary file, string to write
1111 878 100       6030 if (defined $file)
1112 553 100       702439 {-e $file and confess "Binary file already exists:\n$file\n";
1113             }
1114 653         6536 &overWriteBinaryFile(@_);
1115             }
1116              
1117             sub dumpFile($$) # Dump to a B<$file> the referenced data B<$structure>.
1118 1300     1300 1 4875 {my ($file, $structure) = @_; # File to write to or B for a temporary file, address of data structure to write
1119 1300         15925 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 325     325 1 975 {my ($structure) = @_; # Data structure to write
1124 325         6500 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 325     325 1 1625 {my ($file, $structure) = @_; # File to write to or B for a temporary file, address of data structure to write
1129 325         6500 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 325     325 1 1300 {my ($structure) = @_; # Data structure to write
1134 325         1625 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 326     326 1 999 {my ($file, $structure) = @_; # File to write to or B for a temporary file, address of data structure to write
1139 326 100       1647 if (!$file) # Use a temporary file or create a path to the named file
1140 325   33     4550 {$file //= temporaryFile;
1141             }
1142             else
1143 1         58 {makePath($file);
1144             }
1145 326 50       2315 ref($structure) or confess "Reference required for structure parameter";
1146 326         6540 store $structure, $file;
1147 326         80708 $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 650     650 1 284700 {my ($file, $string) = @_; # File to write to, string to write
1152 650         4875 makePath($file);
1153 650 50       2197975 open my $F, "| gzip>$file" or # Compress via gzip
1154             confess "Cannot open file for write because:\n$file\n$!\n";
1155 650         29575 binmode($F, ":utf8"); # Input to gzip encoded as utf8
1156 650         11375 print {$F} $string;
  650         42250  
1157 650         1273675 close ($F);
1158 650 50       42900 -e $file or confess "Failed to write to file:\n$file\n";
1159 650         117975 $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 325     325 1 3900 {my ($file, $structure) = @_; # File to write, reference to data
1164 325 50       4875 ref($structure) or confess "\$structure must contain a reference to data, not a scalar";
1165 325         11050 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 450     450 1 4050 {my ($hash, $old, $new) = @_; # Hash of key value pairs representing files and data, optional old prefix, new prefix
1170 450         8100 for my $file(sort keys %$hash) # Write file data for each hash key
1171 900 50 33     8775 {my $target = $old && $new ? swapFilePrefix($file, $old, $new) : $file; # Optionally swap file prefix
1172 900         7425 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 450     450 1 3825 {my (@folders) = @_; # Folders to read
1178 450         3150 my %h;
1179 450         7875 for my $file(searchDirectoryTreesForMatchingFiles(@folders)) # Files
1180 900         10800 {eval {$h{$file} = readFile($file)};
  900         11925  
1181             }
1182 450         95400 \%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 325     325 1 3250 {my ($file, $string) = @_; # File to append to, string to append
1187 325 50       2925 $file or confess "No file name supplied\n";
1188 325 50       2925 $string or carp "No string for file:\n$file\n";
1189 325         2275 makePath($file);
1190 325 50       12675 open my $F, ">>$file" or
1191             confess "Cannot open file for write file:\n$file\n$!\n";
1192 325         2600 binmode($F, ":utf8");
1193 325         5200 flock($F, 2);
1194 325         2275 print {$F} $string;
  325         5525  
1195 325         8450 close ($F);
1196 325 50       6175 -e $file or confess "Failed to write to file:\n$file\n";
1197 325         11375 $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 1856     1856 1 8742 {my ($file) = @_; # File to create or B for a temporary file
1202 1856   33     7211 $file //= temporaryFile;
1203 1856 50       30195 return $file if -e $file; # Return file name as proxy for success if file already exists
1204 1856         9505 makePath($file);
1205 1856 50       133288 open my $F, ">$file" or confess "Cannot create empty file:\n$file\n$!\n";
1206 1856         10483 binmode($F);
1207 1856         3062 print {$F} '';
  1856         8980  
1208 1856         18932 close ($F);
1209 1856 50       21725 -e $file or confess "Failed to create empty file:\n$file\n";
1210 1856         56102 $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 450     450 1 1800 {my ($file, $permissions) = @_; # File, permissions settings per chmod
1219 450 50       7650 return undef unless confirmHasCommandLineCommand(q(chmod)); # Confirm we have chmod
1220 450         1795275 qx(chmod $permissions $file); # Use chmod to set permissions
1221             }
1222              
1223             sub numberOfLinesInFile($) # Return the number of lines in a file.
1224 325     325 1 4550 {my ($file) = @_; # File
1225 325         9100 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 225     225 1 4050 {my ($source, $target) = @_; # Source file, target file
1256 225         7425 owf($target, readFile($source));
1257 225         4050 my $s = fileSize($source);
1258 225         3375 my $t = fileSize($target);
1259 225 50       5175 $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 225         8100 $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 450     450 1 3150 {my ($source, $target) = @_; # Source file, target file
1270 450 100 66     16200 if (-e $source and !-e $target) # Rename possible
1271 225         7875 {rename $source, $target;
1272 225         3375 return 1;
1273             }
1274             0 # Rename not possible
1275 225         3150 }
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 225     225 1 2700 {my ($source, $target) = @_; # Source file, target file
1279 225 50       6525 if (-e $source) # Source file exists so rename
1280 225         188325 {unlink $target;
1281 225         14625 rename $source, $target;
1282 225         3375 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 187 {my ($source, $targetFolder) = @_; # Source file, target folder
1289 17         306 writeFile fpf(fp($targetFolder), fne($source)), readFile $source;
1290             }
1291              
1292 1125     1125 1 4275 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 900     900 1 2925 {my ($string, %options) = @_; # String, options
1296              
1297 900         1800 my @name;
1298 900 100       7200 if ($string =~ m(<(bookmap))s) # The ghastly compromise
    50          
1299 450         900 {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 900         6975 $string =~ s(<[^>]*>) (_)gs; # Remove xml/html tags
1306 900         5625 $string =~ s([^a-z0-9_])(_)gis; # Reduce character set to produce a readable name
1307 900         2250 push @name, $string;
1308              
1309 900         2475 my $name = join q(_), @name;
1310 900         3825 $name =~ s(_+)(_)gs; # Remove runs of underscores
1311 900         5625 $name =~ s((\A_+|_+\Z)) ()gs; # Remove leading and trailing underscores
1312              
1313 900   33     3600 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 225     225 1 900 {my ($string, %options) = @_; # String, options
1318 225         675 my @name;
1319 225 50       2925 if ($string =~ m(<(bookmap))s) # The ghastly compromise
    0          
1320 225         900 {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 225         1575 for my $t(qw(title mainbooktitle booktitlealt )) # Various title tags
1327 675 100       17325 {if ($string =~ m(<$t[^>]*>([^<]*))is)
1328 225         1350 {push @name, $1;
1329             }
1330             }
1331              
1332 225         1125 my $name = lc join '_', @name; # Mim believes in lc
1333 225         900 $name =~ s(<[^>]*>) (_)gs; # Remove xml/html tags
1334 225         1125 $name =~ s([^a-z0-9_])(_)gis; # Reduce character set to produce a readable name
1335 225         1350 $name =~ s(_+)(_)gs; # Remove runs of underscores
1336 225         1350 $name =~ s((\A_+|_+\Z)) ()gs; # Remove leading and trailing underscores
1337              
1338 225   33     4950 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 225     225 1 675 {my ($source) = @_; # Source file
1343 225         2925 my $sourceFile = fn $source; # File name component
1344 225 50       1575 return fne($source) if $sourceFile =~ m([0-9a-z]{32}\Z)is; # Name already normalized
1345 225         900 my $sourceFileLimited = nameFromString($sourceFile); # File name with limited character set
1346 225         1125 my $md5 = fileMd5Sum($source); # Normalizing Md5 sum
1347 225         2700 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 225     225 1 450 {my ($file) = @_; # File name
1352 225         1125 my $p = fp $file;
1353 225 50       900 my @p = onWindows ? split m(\\), $p : split m(/), $p;
1354 225 50       2025 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 225     225 1 900 {my ($source, $target) = @_; # Source file, target file
1444 225         1125 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 225         6300 $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 450     450 1 8100 {my ($source, $target) = @_; # Source file, target file
1528 450 50       14400 -d $source or confess "No such folder:\n$source\n";
1529 450         18225 my $s = fpd($source);
1530 450         4950 my $t = fpd($target);
1531 450         7650 makePath($t);
1532 450         9225 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 450         11700 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 225     225 1 3375 {my ($source, $target) = @_; # Source file, target file
1539 225 50       7200 -d $source or confess "No such folder:\n$source\n";
1540 225         6300 my $s = fpd($source);
1541 225         3600 my $t = fpd($target);
1542 225         5400 makePath($t);
1543 225         1350 my $c = qq(rsync -r $s $t);
1544             #lll $c;
1545 225         7650 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 105 {my ($structure) = @_; # Data structure reference
1577 42         1197 my $s = dump($structure); # Dump structure
1578 42         45318 $s =~ s("[^"]*/) (")gs; # Remove file prefixes in strings
1579 42         2667 my $r = eval $s; # New version of structure
1580 42 50       210 confess "Unable to remove file prefixes from structure\n$@\n$s\n" if $@; # Complain if removal fails
1581 42         525 $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 168 {my ($structure, $expr) = @_; # Data structure reference, expression
1586 21         315 my $s = nws(dump($structure)); # Dump structure
1587 21         336 $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         147 <
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 7169     7169 1 12395 {my ($d) = @_; # Unbless a L data structure.
1787 7169 100       47351 return $d unless ref $d;
1788 2607         8810 my $r = reftype $d;
1789 2607 100       9457 return [map { __SUB__->( $_ )} @$d] if $r eq q(ARRAY);
  3906         8465  
1790 979 50       4921 return {map {$_ => __SUB__->($$d{$_})} keys %$d} if $r eq q(HASH);
  2284         11435  
1791 0         0 confess "Unknown container: $r\n";
1792             }
1793              
1794             sub encodeJson($) # Convert a L data B<$structure> to a L string.
1795 978     978 1 3587 {my ($structure) = @_; # Data to encode
1796 978         47449 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 978     978 1 3587 {my ($string) = @_; # Data to decode
1801 978         27724 JSON->new->utf8->pretty->canonical->decode($string)
1802             }
1803              
1804             sub encodeBase64($) # Encode an L B<$string> in base 64.
1805 328     328 1 656 {my ($string) = @_; # String to encode
1806 328         656 my $s = eval {encode_base64($string, '')};
  328         1640  
1807 328 50       1312 confess $@ if $@; # So we get a trace back
1808 328         5576 $s
1809             }
1810              
1811             sub decodeBase64($) # Decode an L B<$string> in base 64.
1812 328     328 1 656 {my ($string) = @_; # String to decode
1813 328         984 my $s = eval {decode_base64($string)};
  328         1312  
1814 328 50       984 confess $@ if $@; # So we get a trace back
1815 328         5576 $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 328     328 1 1312 {my ($string) = @_; # String to convert
1820 328         2296 my $t = '';
1821 328         2624 for(split //, $string) # Each letter in the source
1822 4592         4592 {my $n = ord($_);
1823 4592 100       7872 my $c = $n > 127 ? "&#$n;" : $_; # Use xml representation beyond u+127
1824 4592         6560 $t .= $c;
1825             }
1826             $t # Return resulting string
1827 328         1968 }
1828              
1829             sub asciiToHexString($) # Encode an L string as a string of L digits.
1830 225     225 1 1125 {my ($ascii) = @_; # Ascii string
1831 225         1350 my $c = ''; # Result
1832 225         2250 for my $a(split //, $ascii) # Each ascii character
1833 2700         5400 {$c .= sprintf("%x", ord $a) # Format as hex
1834             }
1835             $c # Return string of hexadecimal digits
1836 225         1575 }
1837              
1838             sub hexToAsciiString($) # Decode a string of L digits as an L string.
1839 225     225 1 900 {my ($hex) = @_; # Hexadecimal string
1840 225         1800 my @c = grep {m/[0-9a-f]/i} split //, $hex; # Each hexadecimal digit
  5400         9450  
1841 225         2025 my $c = ''; # Result
1842 225         2250 for my $i(keys @c) # Index of each hexadecimal digit
1843 5400 100       6975 {if ($i % 2 == 1) # End of latest pair
1844 2700         6525 {$c .= chr hex $c[$i-1].$c[$i]; # Convert to character
1845             }
1846             }
1847             $c # Return result
1848 225         1800 }
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 2275     2275 1 311675 {my ($string) = @_; # String
1874 2275   66     8775 join '', map {$translatePercentEncoding{$_}//$_} split //, $string
  12025         42900  
1875             }
1876              
1877             sub wwwDecode($) # Percent decode a L B<$string> per: https://en.wikipedia.org/wiki/Percent-encoding#Percent-encoding_reserved_characters.
1878 1625     1625 1 3250 {my ($string) = @_; # String
1879 1625         4225 my $r = '';
1880 1625         6175 my @s = split //, $string;
1881 1625         7150 while(@s)
1882 7800         11375 {my $c = shift @s;
1883 7800 100 66     23725 if ($c eq q(%) and @s >= 2)
1884 5850         12350 {$c .= shift(@s).shift(@s);
1885 5850   33     14950 $r .= $TranslatePercentEncoding{$c}//$c;
1886             }
1887             else
1888 1950         5525 {$r .= $c;
1889             }
1890             }
1891 1625         5525 $r =~ s(%0d0a) (\n)gs; # Awkward characters that appear in urls
1892 1625         3900 $r =~ s(\+) ( )gs;
1893 1625         5850 $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 1312     1312 1 3936 {my ($n) = @_; # Number to check
1900 1312         2952 for(0..128)
1901 2952 100       8856 {return $_ if 1<<$_ == $n;
1902 1968 100       3936 last if 1<<$_ > $n;
1903             }
1904             undef
1905 328         984 }
1906              
1907             sub containingPowerOfTwo($) # Find log two of the lowest power of two greater than or equal to a number B<$n>.
1908 1968     1968 1 3280 {my ($n) = @_; # Number to check
1909 1968         4264 for(0..128)
1910 5576 100       15416 {return $_ if $n <= 1<<$_;
1911             }
1912             undef
1913 0         0 }
1914              
1915             sub numberWithCommas($) # Place commas in a number.
1916 656     656 1 3608 {my ($n) = @_; # Number to add commas to
1917 656         6560 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 656     656 1 1968 {my (@m) = @_; # Numbers
1924 656 50       1312 my @n = grep {defined($_) and looks_like_number($_)} @_;
  1640         9184  
1925 656 50       2624 @_ == @n or confess q(Undefined or non numeric parameters present);
1926 656 50       1640 return undef unless @n;
1927 656         984 my $M = shift;
1928 656         1968 for(@n)
1929 1640 100       3280 {$M = $_ if $_ < $M;
1930             }
1931             $M
1932 656         2952 }
1933              
1934             sub indexOfMin(@) # Find the index of the minimum number in a list of numbers confessing to any ill defined values.
1935 225     225 1 900 {my (@m) = @_; # Numbers
1936 225 50       900 my @n = grep {defined($_) and looks_like_number($_)} @_;
  900         5850  
1937 225 50       1125 @_ == @n or confess q(Undefined or non numeric parameters present);
1938 225 50       1125 return undef unless @n;
1939 225         675 my $M = 0;
1940 225         1800 for my $i(keys @n)
1941 900         1800 {my $n = $n[$i];
1942 900 100       2700 $M = $i if $n < $n[$M];
1943             }
1944             $M
1945 225         1350 }
1946              
1947             sub max(@) # Find the maximum number in a list of numbers confessing to any ill defined values.
1948 19835     19835 1 40983 {my (@m) = @_; # Numbers
1949 19835 50       30581 my @n = grep {defined($_) and looks_like_number($_)} @_;
  32519         122020  
1950 19835 50       40659 @_ == @n or confess q(Undefined or non numeric parameters present);
1951 19835 100       68961 return undef unless @n;
1952 14957         44864 my $M = shift;
1953 14957         25692 for(@n)
1954 32519 100       56919 {$M = $_ if $_ > $M;
1955             }
1956             $M
1957 14957         39358 }
1958              
1959             sub indexOfMax(@) # Find the index of the maximum number in a list of numbers confessing to any ill defined values.
1960 225     225 1 1125 {my (@m) = @_; # Numbers
1961 225 50       900 my @n = grep {defined($_) and looks_like_number($_)} @_;
  900         6075  
1962 225 50       1125 @_ == @n or confess q(Undefined or non numeric parameters present);
1963 225 50       1125 return undef unless @n;
1964 225         450 my $M = 0;
1965 225         1125 for my $i(keys @n)
1966 900         1350 {my $n = $n[$i];
1967 900 100       2250 $M = $i if $n > $n[$M];
1968             }
1969             $M
1970 225         1575 }
1971              
1972             sub arraySum(@) # Find the sum of any strings that look like numbers in an array.
1973 225     225 1 1125 {my (@a) = @_; # Array to sum
1974 225 50       1575 my @n = grep {defined($_) and looks_like_number($_)} @_;
  2250         9900  
1975 225 50       1350 @_ == @n or confess q(Undefined or non numeric parameters present);
1976 225         450 my $sum = 0; $sum += $_ for @n;
  225         1125  
1977 225         1350 $sum
1978             }
1979              
1980             sub arrayProduct(@) # Find the product of any strings that look like numbers in an array.
1981 225     225 1 675 {my (@a) = @_; # Array to multiply
1982 225 50       675 my @n = grep {defined($_) and looks_like_number($_)} @_;
  1125         6075  
1983 225 50       2025 @_ == @n or confess q(Undefined or non numeric parameters present);
1984 225         675 my $product = 1; $product *= $_ for @n;
  225         900  
1985 225         1350 $product
1986             }
1987              
1988             sub arrayTimes($@) # Multiply by B<$multiplier> each element of the array B<@a> and return as the result.
1989 450     450 1 1125 {my ($multiplier, @a) = @_; # Multiplier, array to multiply and return
1990 450         1350 map {$multiplier * $_} @a
  1800         5175  
1991             }
1992              
1993             #D1 Sets # Set operations.
1994              
1995             sub mergeHashesBySummingValues(@) # Merge a list of hashes B<@h> by summing their values.
1996 225     225 1 1800 {my (@h) = @_; # List of hashes to be summed
1997 225         900 my %h;
1998 225         1575 for my $h(@h)
1999 675         7650 {$h{$_} += $$h{$_} for sort keys %$h;
2000             }
2001 225         1350 \%h
2002             }
2003              
2004             sub invertHashOfHashes(@) # Invert a hash of hashes: given {a}{b} = c return {b}{c} = c.
2005 34     34 1 102 {my ($h) = @_; # Hash of hashes
2006 34         119 my %i; # Resulting inverted hash of hashes
2007 34         255 for my $a(keys $h->%*)
2008 68         272 {for my $b(keys $$h{$a}->%*)
2009 136         527 {$i{$b}{$a} = $$h{$a}{$b};
2010             }
2011             }
2012              
2013 34         272 \%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 252 {my (@h) = @_; # List of hashes to be united
2018 42 50       504 return {} unless @h;
2019 42 50       147 return $h[0] if @h == 1;
2020 42         168 my %u; # Union
2021 42         462 for my $h(@h) # Each hash to be united
2022 126         336 {for my $k(keys %$h) # Keys in current hash
2023 231         336 {$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 231 {my (@h) = @_; # List of hashes to be intersected
2032 21 50       252 return {} unless @h;
2033 21 50       84 return $h[0] if @h == 1;
2034              
2035 21         84 my $u = unionOfHashKeys(@h); # Union
2036 21         273 my $N = @h; # Number of hashes
2037 21         42 my %i; # Intersection
2038 21         147 for my $k(keys %$u) # Each key
2039 63 100       168 {if ($$u{$k} == $N) # Key present in all hashes
2040 21         63 {$i{$k}++ # Add hash value to intersection
2041             }
2042             }
2043              
2044 21         231 \%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 126 {my (@h) = @_; # List of hashes to be united
2049 21         126 my %u; # Union
2050 21         189 for my $i(keys @h) # Each hash to be united
2051 63         126 {my $h = $h[$i]; # Current hash
2052 63         168 for my $k(keys %$h) # Keys in current hash
2053 105 50       315 {if (defined(my $v = $$h{$k})) # Value defined at current key
2054 105         231 {$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 189 {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         273 for my $h(@h) # Each hash
2066 63 50       483 {defined($$h{$_}) ? ++$n{$_} : undef for keys %$h # Count the number of hashes that have this key
2067             }
2068              
2069 21         147 my %i; # Intersection
2070 21         84 for my $k(keys %n) # Each key
2071 63 100       168 {if ($n{$k} == $N) # Key present in all hashes
2072 21         252 {$i{$k}[$_] = $h[$_]{$k} for keys @h # Add hash value to intersection array
2073             }
2074             }
2075              
2076 21         273 \%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 18880     18880 1 28273 {my (@s) = @_; # Array of arrays of strings and/or hashes
2081 18880         24046 my %e;
2082 18880         26715 for my $s(@s) # Intersect each set
2083 32864         61205 {my $t = reftype($s);
2084 32864 100       86889 if (!defined $t) # Scalar as a set of one
    100          
    50          
2085 4058         7170 {$e{$s}++
2086             }
2087             elsif ($t =~ m(array)is) # Intersect array of strings
2088 27822         39267 {for my $e(@$s) # Count instances of each string
2089 402974         608681 {$e{$e}++
2090             }
2091             }
2092             elsif ($t =~ m(hash)is) # Intersect keys of hash
2093 984         3280 {for my $e(keys %$s) # Count instances of each key
2094 2952         7872 {$e{$e}++
2095             }
2096             }
2097             else # Unknown set type
2098 0         0 {confess "Unknown set type: $t";
2099             }
2100             }
2101 18880         31723 \%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 8729     8729 1 15808 {my (@s) = @_; # Array of arrays of strings and/or hashes
2106 8729         16280 my $e = setCombination(@_);
2107 8729         77303 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 656     656 1 2296 {my (@s) = @_; # Array of arrays of strings and/or hashes
2112 656         3280 my $e = setCombination(@_);
2113 656         3280 my $S = @s; # Set count
2114 656         3936 grep {$e->{$_} == $S} sort keys %$e # Return words that appear in all the sets
  4592         10496  
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 9495     9495 1 17017 {my (@s) = @_; # Array of arrays of strings and/or hashes
2119 9495         12757 my $e = setCombination(@_); # Set element count
2120 9495         16040 my $u = keys %$e; # Union size
2121 9495 50       14936 $u == 0 and confess "Empty union"; # 0/0 can be anything
2122 9495         10556 my $S = @s; # Set count
2123 9495         34274 my $i = grep {$e->{$_} == $S} keys %$e; # Intersection size
  307142         542608  
2124 9495         45768 $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 1343     1343 1 4846 {my ($confidence, @sets) = @_; # Minimum setIntersectionOverUnion, array of arrays of strings and/or hashes representing sets
2129 1343         2434 my @s = sort {scalar(@$b) <=> scalar(@$a)} map {[setUnion($_)]} @sets; # Input sets as arrays in descending order of length
  4395         8453  
  4416         7106  
2130              
2131 1343         2134 my @partition;
2132 1343         3413 while(@s) # The proposed partition
2133 4416         6295 {my $base = shift @s; # Each set starting with the largest
2134 4416 100       9505 next unless defined $base; # No longer present
2135 3104         5171 my @base = ($base); # Create set of elements congruent with the base set
2136 3104         7380 for my $i(keys @s) # Each remaining set
2137 9495         11772 {my $s = $s[$i]; # Current set to compare with base set
2138 9495 100       14229 next unless defined $s; # Current set has already been classified
2139 9167 50       19396 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 9167         14607 my $o = setIntersectionOverUnion($base, $s); # Overlap
2141 9167 100       19664 if ($o > $confidence) # Overlap is better than confidence
2142 1312         2624 {push @base, $s; # Include in partition
2143 1312         2952 $s[$i] = undef; # Remove from further consideration
2144             }
2145             }
2146 3104         7926 push @partition, \@base; # Save partition
2147             }
2148 1343         6449 @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 1015     1015 1 3116 {my ($confidence, @sets) = @_; # Minimum setIntersectionOverUnion, array of arrays of strings and/or hashes representing sets
2153              
2154 1015         1353 my %u; # Normalized set to input sets with this normalization
2155 1015         3211 for my $s(@sets) # Each set
2156 3432         4931 {push @{$u{join ' ', setUnion($s)}}, $s; # Normalized set back to each input set of words
  3432         6837  
2157             }
2158             my @partition = setPartitionOnIntersectionOverUnion($confidence, # Partition normalized sets
2159 1015         3872 map {[split /\s+/, $_]} sort keys %u);
  3432         26951  
2160              
2161 1015         4180 my @P;
2162 1015         1736 for my $partition(@partition) # Each partition
2163 2448         2798 {my @p;
2164 2448         4292 for my $set(@$partition) # Each set in the current partition
2165 3432         4621 {push @p, @{$u{join ' ', @$set}};
  3432         9443  
2166             }
2167              
2168 2448         4099 push @P, \@p;
2169             }
2170             @P
2171 1015         7277 }
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 687     687 1 5651 {my ($confidence, @strings) = @_; # Minimum setIntersectionOverUnion, sets represented by strings
2175              
2176 687         1490 my %u; # Normalized set to input sets with this normalization
2177 687         3050 for my $s(@strings) # Each set
2178 5712         21675 {my $n = nws($s =~ s([^a-z ]) ()girs);
2179 5712         124819 push @{$u{$n}}, $s; # Normalized set back to each input set of words
  5712         13949  
2180             }
2181              
2182             my @partition = setPartitionOnIntersectionOverUnionOfSetsOfWords($confidence, # Partition normalized strings
2183 687         4830 map {[split /\s+/, $_]} sort {length($a) <=> length($b)} sort keys %u);
  2448         28243  
  2427         4734  
2184              
2185 687         3866 my @P; # Partition of strings
2186 687         1753 for my $partition(@partition) # Each partition
2187 1792         2109 {my @p;
2188 1792         2250 for my $set(@$partition) # Each set in the current partition
2189 2448         3459 {push @p, @{$u{join ' ', @$set}};
  2448         8750  
2190             }
2191              
2192 1792         3419 push @P, \@p;
2193             }
2194             @P
2195 687         5732 }
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 345     345 1 1137 {my ($confidence, $hashSet) = @_; # Minimum setIntersectionOverUnion, sets represented by the hash value strings
2199 345 50       4842 reftype($hashSet) =~ m(hash)is or confess "Second parameter must be a hash";
2200              
2201 345         1363 my %u; # Invert the hash so we can present the partitions by hash key
2202 345         5592 for my $s(sort keys %$hashSet) # Each set
2203 4520         5793 {push @{$u{$$hashSet{$s}}}, $s; # Invert
  4520         9463  
2204             }
2205              
2206             my @partition = setPartitionOnIntersectionOverUnionOfStringSets($confidence, # Partition strings
2207 345         3928 sort {length($a) <=> length($b)} sort values %$hashSet);
  4175         5827  
2208              
2209 345         1154 my @P; # Partition of strings
2210 345         1052 for my $partition(@partition) # Each partition
2211 1098         1613 {my @p;
2212             my %p; # If n sets are identical we get n repetitions - this hash prevents that.
2213 1098         2252 for my $set(@$partition) # Each set in the current partition
2214 4520 50       6893 {if (my $u = $u{$set})
2215 4520         6359 {for my $U(@$u)
2216 29272 100       45192 {push @p, $U unless $p{$U}++;
2217             }
2218             }
2219             }
2220              
2221 1098         4144 push @P, [sort @p];
2222             }
2223 345         775 sort {scalar(@$b) <=> scalar(@$a)} @P
  753         5697  
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 85 {my ($confidence, $hashSet) = @_; # Minimum setIntersectionOverUnion, sets represented by the hash value strings
2228 17 50       374 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         3723 for my $s(sort keys %$hashSet) # Each set
2232 3536         3876 {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         3927  
2236 17         527 my @square = squareArray(@strings);
2237              
2238 17         34 my @partition;
2239              
2240             &runInParallel(&numberOfCpus(8), # Partition strings in square root blocks in parallel
2241             sub
2242 14     14   972 {[setPartitionOnIntersectionOverUnionOfStringSets($confidence, $_[0]->@*)]; # Partition strings
2243             },
2244             sub # Consolidate partitions
2245 3     3   111 {for my $p(@_)
2246 42         648 {push @partition, @$p;
2247             }
2248 17         476 }, @square);
2249              
2250 3         135 my @P; # Partition of strings
2251 3         33 for my $partition(@partition) # Each partition
2252 114         189 {my @p;
2253             my %p; # If n sets are identical we get n repetitions - this hash prevents that.
2254 114         198 for my $set(@$partition) # Each set in the current partition
2255 624 50       1059 {if (my $u = $u{$set})
2256 624         747 {for my $U(@$u)
2257 4992 100       8442 {push @p, $U unless $p{$U}++;
2258             }
2259             }
2260             }
2261              
2262 114         489 push @P, [sort @p];
2263             }
2264 3         90 sort {scalar(@$b) <=> scalar(@$a)} @P
  111         1020  
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 1312     1312 1 9184 {my ($item, @array) = @_; # Item, array
2269 1312         1968 my @r;
2270 1312 100       8856 if (ref($item) =~ m(Regexp)) # Match via a regular expression
    100          
2271 328         2624 {for(keys @array)
2272 3280 100       15088 {push @r, $_ if $array[$_] =~ m($item)s;
2273             }
2274             }
2275             elsif (looks_like_number($item)) # Match as a number
2276 656         2624 {for(keys @array)
2277 2624 100       5904 {push @r, $_ if $array[$_]+0 == $item;
2278             }
2279             }
2280             else # Match as a string
2281 328         1312 {for(keys @array)
2282 3280 100       5576 {push @r, $_ if $array[$_] eq $item;
2283             }
2284             }
2285             @r
2286 1312         7544 }
2287              
2288             sub countOccurencesInString($$) # Returns the number of occurrences in B<$inString> of B<$searchFor>.
2289 225     225 1 900 {my ($inString, $searchFor) = @_; # String to search in, string to search for.
2290 225         675 my $n = 0;
2291 225 50       1125 length($inString) >= length($searchFor) or
2292             confess "String to search must be longer than string to look for";
2293 225         450 my $p = -1;
2294 225         25650 ++$n while(($p = index($inString, $searchFor, $p+1)) > -1);
2295 225         1350 $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 2814 {my ($maxSize, %Sizes) = @_; # Maximum size of a partition, {string=>size}... hash to be partitioned
2300              
2301 336         798 my %paths; # Path to each character in each string
2302             my %sizes; # Size associate with each path
2303 336         1722 for my $string(sort keys %Sizes) # Create a path of hashes with single character keys
2304 2079         3717 {my $size = $Sizes{$string}; # Size associated with the string
2305 2079         3066 my $paths = '';
2306 2079         5250 my @s = split m(), $string; # String as single characters
2307 2079         4221 while(@s) # Shorten path
2308 5943         7938 {my $k = join '', map {qq({'$_'})} @s; # Path of hashes with single character keys
  11697         22155  
2309 5943         8988 $paths .= qq(\$paths$k //= {};\n); # Auto vivify
2310 5943         7623 my $d = join '', @s; # Path name
2311 5943         8862 $sizes{$d} += $size; # Aggregate size
2312 5943         10605 pop @s; # Move up one level
2313             }
2314 2079         3087 $sizes{q()} += $size; # Total size
2315 2079         140007 eval $paths; # Create paths - this level of aggregation seems to give the fastest overall response
2316 2079 50       7581 confess "$paths\n$@\n" if $@; # Unable to create path
2317             }
2318              
2319 336         777 my %partition; # Partition the paths
2320              
2321             my $partition; $partition = sub # Partition paths at the current level
2322 1428     1428   2268 {my ($paths, @path) = @_; # Path at this level, path to this level
2323              
2324 1428         2268 my $p = join '', @path; # Path name
2325 1428         1869 my $s = $sizes{$p}; # Size of path
2326              
2327 1428 100 100     3276 if ($s <= $maxSize or !keys %$paths) # Small enough or complete path
2328 903         2016 {$partition{$p} = $s; # Path => size
2329             }
2330             else # Still too big
2331 525         1470 {for my $d(sort keys %$paths) # Next level
2332 1113         2499 {&$partition($$paths{$d}, @path, $d); # Try at the next level
2333             }
2334             }
2335 336         2583 };
2336              
2337 336 100       1365 &$partition(\%paths) if keys %paths; # Partition from the top
2338              
2339 336         10857 %partition
2340             }
2341              
2342             sub transitiveClosure($) # Transitive closure of a hash of hashes.
2343 1     1 1 16 {my ($h) = @_; # Hash of hashes
2344              
2345 1         28 my %keys = arrayToHash(keys %$h)->%*; # Find all the keys to consider
2346 1         17 for my $i(keys %$h)
2347 3         18 {my $value = $$h{$i};
2348 3 50       40 if (reftype($value) =~ m(hash)i)
2349 3         15 {%keys = (%keys, arrayToHash(keys %$value)->%*); # Just the sub keys
2350             }
2351             }
2352              
2353 1         3 my %t; # Transitive closure
2354 1         7 for my $a(keys %keys)
2355 4         12 {my $i = $$h{$a};
2356 4 100 66     43 if ($i and reftype($i) =~ m(hash)i)
2357 3         8 {for my $b(keys %keys)
2358 12 100       31 {$t{$a}{$b} = 1 if $$i{$b}
2359             }
2360             }
2361             }
2362              
2363 1         6 for(1..100)
2364 2         5 {my $changes = 0;
2365 2         10 for my $a(keys %keys)
2366 8         16 {for my $b(keys %keys)
2367 32 100       54 {if ($t{$a}{$b})
2368 10   100     64 {$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       12 last unless $changes;
2373             }
2374              
2375 1         4 for my $s(keys %t) # Remove empty hashes
2376 4 100       10 {delete $t{$s} unless keys $t{$s}->%*;
2377             }
2378              
2379 1         8 my %s;
2380             my @s;
2381 1         11 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       14 if (defined(my $i = $s{$k})) # Reuse a matching entry indexed from zero
2384 1         3 {$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         11 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 14300     14300 1 23400 {my ($string) = @_; # String of lines of text
2401 14300   100     50375 max(map {length($_)} split /\n/, ($string//'')) // 0 # Length of longest line
  18850   100     34775  
2402             }
2403              
2404             sub formatTableMultiLine($;$) #P Tabularize text that has new lines in it.
2405 1625     1625 1 7475 {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 1625 50       17225 ref($data) =~ /array/i or
2407             confess "Array reference required not:\n".dump($data)."\n";
2408              
2409 1625         6825 my @width; # Maximum width of each column
2410 1625         8125 for my $row(@$data) # Find maximum width of each column
2411 4550 50       20150 {ref($row) =~ /array/i or
2412             confess "Array reference required not:\n".dump($row)."\n";
2413 4550         35425 for my $col(0..$#$row) # Each column index
2414 13975   100     34775 {my $a = $width[$col] // 0; # Maximum length of data so far
2415 13975         24375 my $b = maximumLineLength($row->[$col]); # Length of longest line in current item
2416 13975 100       33150 $width[$col] = ($a > $b ? $a : $b); # Update maximum length
2417             }
2418             }
2419              
2420 1625         2925 my @text; # Formatted data
2421 1625         4875 for my $row(@$data) # Each row
2422 4550         6825 {my @row; # Laid out text
2423 4550         9425 for my $col(0..$#$row) # Each column
2424 13975         20150 {my $m = $width[$col]; # Maximum width
2425 13975   100     41275 for my $i(split /\n/, $row->[$col]//'') # Each line of item
2426 17875 100       77675 {if ($i !~ /\A\s*[-+]?\s*(\d|[,])+(\.\d+)?([Ee]\s*[-+]?\s*\d+)?\s*\Z/) # Not a number - left justify
2427 12675         14300 {push @{$row[$col]}, substr($i.(' 'x$m), 0, $m);
  12675         56550  
2428             }
2429             else # Number - right justify
2430 5200         6500 {push @{$row[$col]}, substr((' 'x$m).$i, -$m);
  5200         24700  
2431             }
2432             }
2433             }
2434              
2435 4550   100     9425 my $n = max(map {scalar @{$_//[]}} @row)//0; # Maximum number of rows
  12025   100     15600  
  12025         35425  
2436              
2437 4550         10725 for my $r(1..$n) # Each row of the items
2438 10400         19175 {my $text = '';
2439 10400         16575 for my $col(0..$#$row) # Each item
2440 32500   66     88075 {$text .= ($row[$col][$r-1] // (q( ) x $width[$col])).q( );
2441             }
2442 10400         67925 $text =~ s(\s*\Z) ()s; # Strip trailing blanks as they are not needed for padding
2443 10400         33150 push @text, $text;
2444             }
2445             }
2446              
2447 1625   50     14625 my $s = $separator//"\n";
2448 1625         29250 join($s, @text).$s
2449             }
2450              
2451             sub formatTableBasic($) # Tabularize an array of arrays of text.
2452 13112     13112 1 27547 {my ($data) = @_; # Reference to an array of arrays of data to be formatted as a table.
2453 13112 50       72137 ref($data) =~ /array/i or # Must be an array
2454             confess "Array reference required not:\n".dump($data)."\n";
2455 13112         22476 my @width; # Maximum width of each column
2456              
2457 13112         34782 for my $row(@$data) # Each row
2458 57105         88540 {for my $col(0..$#$row) # Each column index
2459 187728   100     291841 {my $text = $row->[$col] // ''; # Text of current line
2460 187728 100       294231 return &formatTableMultiLine(@_) if $text =~ m(\n); # Element has a new line in it
2461 186103   100     304907 my $a = $width[$col] // 0; # Maximum length of data so far
2462 186103         204322 my $b = length($text); # Length of longest line in current item
2463 186103 100       289633 $width[$col] = ($a > $b ? $a : $b); # Update maximum length
2464             }
2465             }
2466              
2467 11487         16994 my @text; # Formatted data
2468 11487         19824 for my $row(@$data)
2469 53205         65131 {my $text = ''; # Formatted text
2470 53205         82500 for my $col(0..$#$row)
2471 176678         214309 {my $m = $width[$col]; # Maximum width
2472 176678   100     274777 my $i = $row->[$col]//''; # Current item
2473 176678 100       487996 if ($i !~ /\A\s*[-+]?\s*(\d|[,])+(\.\d+)?([Ee]\s*[-+]?\s*\d+)?\s*\Z/) # Not a number - left justify
2474 99286         216286 {$text .= substr($i.(' 'x$m), 0, $m)." ";
2475             }
2476             else # Number - right justify
2477 77392         168240 {$text .= substr((' 'x$m).$i, -$m)." ";
2478             }
2479             }
2480 53205         228671 $text =~ s(\s*\Z) ()s; # Strip trailing blanks as they are not needed for padding
2481 53205         105656 push @text, $text;
2482             }
2483              
2484 11487         94688 join("\n", @text)."\n"
2485             }
2486              
2487             sub formatTableClearUpLeft($) #P Blank identical column values up and left.
2488 115     115 1 345 {my ($data) = @_; # Array of arrays
2489              
2490 115         1380 for my $row(1..@$data) # Each row from last to first
2491 460         920 {my $d = $$data[-$row];
2492 460 100       2070 last if $row == @$data;
2493              
2494 345         575 my $p = $row+1;
2495 345         920 for my $c(reverse 1..@$d) # Compare left values in current row to previous row
2496 1265 100       3220 {next unless my $dc = $$d[-$c];
2497 920 50       1955 next unless my $pc = $$data[-$p][-$c];
2498 920 100       1840 if ($dc eq $pc) # Blank equal value
2499 575         2185 {$$d[-$c] = q();
2500             }
2501             else # Values not equal terminates equal valued column suppression
2502 345         1725 {last;
2503             }
2504             }
2505             }
2506             }
2507              
2508             sub formatTableAA($$%) #P Tabularize an array of arrays.
2509 7637     7637 1 19763 {my ($data, $title, %options) = @_; # Data to be formatted, reference to an array of titles, options
2510 7637 50 33     59889 return dump($data) unless ref($data) =~ /array/i and @$data;
2511              
2512 7637         12495 my $d; # Copy of the input data because we are going to modify it
2513 7637         13827 for my $row(@$data) # Each row
2514 29585 50       96582 {ref($row) =~ /array/i or # Each row must be an array
2515             confess "Array reference required not:\n".dump($row)."\n";
2516 29585         76505 push @$d, [q(), @$row]; # Copy each row with space for a row number
2517             }
2518              
2519 7637 50       24356 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 7637 100       15286 formatTableClearUpLeft($d) if $options{clearUpLeft}; # Clear up and left if requested
2528 7637         42036 $$d[$_-1][0] = $_ for 1..@$data; # Number each row now that we have suppressed duplicates
2529 7637 100       37122 unshift @$d, ['', @$title] if $title; # Add title
2530 7637         40922 formatTableBasic($d); # Format array
2531             }
2532              
2533             sub formatTableHA($;$) #P Tabularize a hash of arrays.
2534 981     981 1 1962 {my ($data, $title) = @_; # Data to be formatted, optional titles
2535 981 50 33     9476 return dump($data) unless ref($data) =~ /hash/i and keys %$data;
2536 981         1962 my $d;
2537 981 100       8832 push @$d, $title if $title;
2538 981         5555 push @$d, [$_, @{$data->{$_}}] for sort keys %$data;
  3921         31762  
2539 981         2943 formatTableBasic($d);
2540             }
2541              
2542             sub formatTableAH($) #P Tabularize an array of hashes.
2543 981     981 1 2615 {my ($data) = @_; # Data to be formatted
2544 981 50 33     10126 return dump($data) unless ref($data) =~ /array/i and @$data;
2545              
2546 981         1959 my %k; @k{keys %$_}++ for @$data; # Column headers
  981         10126  
2547 981         4905 my @k = sort keys %k;
2548 981         5230 $k{$k[$_-1]} = $_ for 1..@k;
2549              
2550 981         3268 my $d = [['', @k]];
2551 981         2615 for(1..@$data)
2552 3268         6208 {push @$d, [$_];
2553 3268         4249 my %h = %{$data->[$_-1]};
  3268         9470  
2554 3268         16343 $d->[-1][$k{$_}] = $h{$_} for keys %h;
2555             }
2556 981         2290 formatTableBasic($d);
2557             }
2558              
2559             sub formatTableHH($) #P Tabularize a hash of hashes.
2560 981     981 1 2612 {my ($data) = @_; # Data to be formatted
2561 981 50 33     12082 return dump($data) unless ref($data) =~ /hash/i and keys %$data;
2562              
2563 981         2287 my %k; @k{keys %$_}++ for values %$data; # Column headers
  981         8814  
2564 981         4905 my @k = sort keys %k;
2565 981         5555 $k{$k[$_-1]} = $_ for 1..@k;
2566              
2567 981         6536 my $d = [['', @k]];
2568 981         3921 for(sort keys %$data)
2569 3268         5886 {push @$d, [$_];
2570 3268         4577 my %h = %{$data->{$_}};
  3268         9482  
2571 3268         14709 $d->[-1][$k{$_}] = $h{$_} for keys %h;
2572             }
2573 981         2943 formatTableBasic($d);
2574             }
2575              
2576             sub formatTableA($;$) #P Tabularize an array.
2577 653     653 1 2284 {my ($data, $title) = @_; # Data to be formatted, optional title
2578 653 50 33     7818 return dump($data) unless ref($data) =~ /array/i and @$data;
2579              
2580 653         1306 my $d;
2581 653 100       9500 push @$d, $title if $title;
2582 653         5564 for(keys @$data)
2583 1956 50       8823 {push @$d, @$data > 1 ? [$_, $data->[$_]] : [$data->[$_]]; # Skip line number if the array is degenerate
2584             }
2585 653         1959 formatTableBasic($d);
2586             }
2587              
2588             sub formatTableH($;$) #P Tabularize a hash.
2589 981     981 1 2287 {my ($data, $title) = @_; # Data to be formatted, optional title
2590              
2591 981 50 33     11444 return dump($data) unless ref($data) =~ /hash/i and keys %$data;
2592              
2593 981         1634 my $d;
2594 981 100       3593 push @$d, $title if $title;
2595 981         3924 for(sort keys %$data)
2596 2615         10147 {push @$d, [$_, $data->{$_}];
2597             }
2598 981         3271 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 12237     12237 1 238477 {{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 12214     12214 1 40782 {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 12214 100 100 12214   70966 {if ($columnTitles and !ref($columnTitles) and
      100        
      66        
2659             $columnTitles eq q(columns) and scalar(@options) % 2 == 1)
2660 2         66 {my %o = ($columnTitles, @options);
2661 2         49 $columnTitles = $o{columns};
2662 2         59 return %o;
2663             }
2664 12212 50       57931 scalar(@options) % 2 and confess "Options fail to pair";
2665             @options
2666 12214         88148 }->();
  12212         28312  
2667              
2668 12214         63161 checkKeys(\%options, formatTableCheckKeys); # Check report options
2669              
2670             my ($titleString, $title) = sub # Title string, column headers
2671 12214 100   12214   33930 {return (undef, undef) unless defined $columnTitles; # No titles
2672 8284 100       36097 if (my $r = reftype $columnTitles) # Array of column titles
2673 8057 50       52346 {return (undef, $columnTitles) if $r =~ m(\Aarray\Z)si;
2674             }
2675 227 50       2276 return (q(), q()) unless $columnTitles; # Column titles are not required for hash of hashes
2676 227         1609 my @c = map {[split m(\s+), $_, 2]} split "\n", $columnTitles; # Column definitions
  679         3011  
2677 227         2522 my $s = &formatTable(\@c, [qw(Column Description)]); # Column definitions descriptions table
2678 227         681 ($s, [map {$$_[0]} @c])
  679         2053  
2679 12214         85233 }->();
2680              
2681 12214         69810 my ($a, $h, $o) = (0, 0, 0); # Check structure of input data tttt
2682             my $checkStructure = sub
2683 12214     12214   21900 {for(@_)
2684 44613         82452 {my $r = reftype($_); # Process arrays and hashes or objects built on them
2685 44613 100       60346 if ($r)
2686 40042 100       112829 {if ($r =~ /array/i) {++$a}
  33506 50       47875  
2687 6536         9148 elsif ($r =~ /hash/i) {++$h}
2688 0         0 else {++$o}
2689             }
2690 4571         6208 else {++$o}
2691             }
2692 12214         48008 };
2693              
2694             my $formattedTable = sub # Format table
2695 12214 100   12214   106190 {if (reftype($data) =~ /array/i)
    50          
2696 9271         28446 {$checkStructure->( @$data);
2697 9271 50 66     88023 return formatTableAA($data, $title, %options) if $a and !$h and !$o;
      66        
2698 1634 100 66     17628 return formatTableAH($data) if !$a and $h and !$o;
      66        
2699 653         3259 return formatTableA ($data, $title);
2700             }
2701             elsif (reftype($data) =~ /hash/i)
2702 2943         8170 {$checkStructure->(values %$data);
2703 2943 50 66     10460 return formatTableHA($data, $title) if $a and !$h and !$o;
      66        
2704 1962 100 66     13391 return formatTableHH($data) if !$a and $h and !$o;
      66        
2705 981         2287 return formatTableH ($data, $title);
2706             }
2707 12214         54768 }->();
2708              
2709 12214 100       197813 return $formattedTable unless keys %options; # Return table as is unless report requested
2710              
2711 2795         6931 my ($Title, $head, $foot, $file, $zero, $summarize, $wide, $msg, $csv, $zwsp, $indent) = map{$options{$_}}
  30745         48892  
2712             qw(title head foot file zero summarize wide msg csv zwsp indent);
2713              
2714 2795         8791 my @report;
2715 2795         18644 my $date = dateTimeStamp;
2716 2795         18504 my $N = keyCount(1, $data);
2717 2795   100     25507 my $H = ($head//'') =~ s(DDDD) ($date)gr =~ s(NNNN) ($N)gr;
2718 2795 100       9218 $H =~ s(TTTT) ($title)gs if $Title;
2719 2795 100       11301 push @report, $Title if $Title;
2720 2795 100       8638 push @report, $H if $head;
2721 2795 100       13177 push @report, qq(This file: $file) if $file;
2722 2795 100       7549 push @report, $titleString if $titleString;
2723 2795 50       7758 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 2795 100       6809 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 2795         5477 push @report, $formattedTable;
2733 2795 100       5836 push @report, $foot if $foot;
2734              
2735 2795   100     21252 push @formatTables, [$N, $Title//nws($H, 80), $file]; # Report of all the reports created
2736              
2737 2795 0 33     12890 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 2795 100       7445 if ($summarize) # Summarize an array of arrays if requested
2743 450         2250 {my $s = '';
2744 450 50       3600 if (reftype($data) =~ /array/i)
2745 450 50 33     6525 {if ($a and !$h and !$o)
      33        
2746 450         2925 {for my $col(1..@$title)
2747 1125         2925 {my $n = $title->[$col-1];
2748 1125         2475 my $c = qq(Summary_of_column_$n);
2749 1125         3600 my @s = summarizeColumn($data, $col-1);
2750 1125         7200 my $t = &formatTable(\@s, [q(Count), $n]);
2751 1125         5625 $s .= qq($c\n$t\n);
2752 1125         2250 if (1)
2753 1125         2250 {my $v = join ",", sort map {dump($$_[1])} @s;
  3825         227475  
2754 1125         90000 $s .= "Comma_Separated_Values_of_column_$n: $v\n\n";
2755             }
2756             }
2757             }
2758             }
2759 450         1350 push @report, $s;
2760             }
2761              
2762 2795 100       10852 if ($file) # Write a csv version of the report (Sabine)
2763 330 50       2687 {if (reftype($data) =~ /array/i)
2764 330 50 33     9216 {if ($a && !$h && !$o or $zero)
      33        
      33        
2765 330         987 {my @s;
2766              
2767 330 50       1311 if ($title) # Column headers
2768 330 100       1322 {my $r = join ',', map {defined($_) ? $_ : q(unknown)} @$title;
  663         5903  
2769 330         1096 push @s, $r;
2770             }
2771              
2772 330         3290 for my $d(@$data)
2773 667         25649 {push @s, join ',', map{dump($_)} @$d;
  1346         112412  
2774             }
2775 330         43894 my $csvFile = setFileExtension($file, q(csv));
2776 330         1659 my $csvData = join "\n", @s;
2777 330         5040 overWriteFile($csvFile, "$csvData\n");
2778             }
2779             }
2780             }
2781              
2782 2795         11556 my $report = join "\n\n", @report; # Create report
2783              
2784 2795 50 33     14312 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 2795 50       9399 $report = indentString($report, $indent) if $indent; # Indent table if requested
2787              
2788 2795         73707 $report
2789             } # formatTable
2790              
2791             sub formattedTablesReport(@) # Report of all the reports created. The optional parameters are the same as for L.
2792 225     225 1 450 {my (@options) = @_; # Options
2793              
2794 225   50     2025 formatTable([sort {($a->[1]//'') cmp ($b->[1]//'')} @formatTables], <
  2475   50     6300  
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 1350     1350 1 3600 {my ($data, $column) = @_; # Table == array of arrays, column number to summarize.
2804 1350         2700 my @data = map {$$_[$column]} @$data;
  12150         18450  
2805 1350         2025 my %data;
2806 1350         3375 for my $d(@data)
2807 12150 50       29700 {$data{$d}++ if defined $d;
2808             }
2809 5175 100       12600 sort {return $$a[1] cmp $$b[1] if $$b[0] == $$a[0]; # Return array of [count, key]
2810 1350         5850 return $$b[0] <=> $$a[0]} map {[$data{$_}, $_]} sort keys %data;
  2700         6075  
  4725         15525  
2811             }
2812              
2813             sub keyCount($$) # Count keys down to the specified level.
2814 3451     3451 1 14757 {my ($maxDepth, $ref) = @_; # Maximum depth to count to, reference to an array or a hash
2815 3451         8101 my $n = 0;
2816 3451         5135 my $count;
2817             $count = sub
2818 6731     6731   13153 {my ($ref, $currentDepth) = @_;
2819 6731 100       32309 if (ref($ref) =~ /array/i)
    100          
2820 3779 100       9327 {if ($maxDepth == $currentDepth) {$n += scalar(@$ref)}
  3123         7683  
2821 656         2952 else {$count->($_, ++$currentDepth) for @$ref}
2822             }
2823             elsif (ref($ref) =~ /hash/i)
2824 984 100       2296 {if ($maxDepth == $currentDepth) {$n += scalar(keys %$ref)}
  328         984  
2825 656         3608 else {$count->($ref->{$_}, ++$currentDepth) for keys %$ref}
2826             }
2827 1968         3608 else {++$n}
2828 3451         31631 };
2829 3451         9895 $count->($ref, 1);
2830 3451         12981 $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 771 {my ($data, %options) = @_; # Data to be formatted, options
2835 23 50       235 my $rows = $data ? scalar(@$data) : 0; # The number of rows in the report
2836              
2837 23         604 checkKeys(\%options, formatTableCheckKeys); # Check report options
2838              
2839 23 50 33     1125 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         90 my @html; # Generated html
2844 23         374 my $cl = q(); # Table column names
2845 23         153 my $ct = q(); # Columns description table if present
2846              
2847 23 50       237 if (my $columns = $options{columns}) # Column headers
2848 23 50       153 {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         174 my @c = map {[split m(\s+), $_, 2]} split "\n", $columns; # Parse column headers
  46         243  
2853             $cl = join '', q(
), join q(),
2854 23         240 map {my ($c, $d) = @$_; qq($c)} @c; # Column line with tool tips
  46         114  
  46         209  
2855 23         418 $ct = join "\n", q(

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

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

$title

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

$t

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

2880             END
2881              
2882 23 50       280 push @html, $cl if $cl; # Column headers
2883              
2884 23 50       216 if ($data) # Table data
2885 23         66 {for my $data(@$data)
2886 47   50     137 {push @html, join '', q(
), join q(), map {$_//q()} @$data;
  94         329  
2887             }
2888             }
2889              
2890 23         67 push @html, <
2891            

2892             END
2893              
2894 23 50       68 push @html, $ct if $ct; # Column descriptions block
2895              
2896 23 50       67 if (my $foot = $options{foot}) # Footer
2897 23         88 {&$hf($foot);
2898             }
2899              
2900 23         66 if (1) # Record options invisibly
2901 23         265 {my $options = dump({%options, rows=>$rows});
2902 23         15881 push @html, qq();
2903             }
2904              
2905 23         263 my $html = join "\n", @html; # Create html
2906 23 100       107 if (my $file = $options{file})
2907 2         46 {my $html = join "\n", @html;
2908 2         123 overWriteFile($file, $html);
2909             }
2910              
2911             $html
2912 23         694 } # formatHtmlTable
2913              
2914             sub formatHtmlTablesIndex($$$;$) # Create an index of html reports.
2915 17     17 1 374 {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     867 $columns //= 1;
2917              
2918             my %reports = sub # Hash {file=>options} for each html report
2919 17     17   816 {my @r = searchDirectoryTreesForMatchingFiles($reports, q(.html)); # Find all html reports
2920 17         340 my %r;
2921 17         238 for my $r(@r) # Each html report
2922 34         935 {my $t = readFile($r);
2923 34 50       1037 if ($t =~ m()s) # Extract report meta data
2924 34         5678 {my $d = eval $1;
2925 34 50       459 $@ and confess "Cannot retrieve report metadata:\n$r\n$@\n";
2926 34 50       272 if (my $t = $$d{title})
2927 34         561 {$r{$t} = $d;
2928             }
2929             else
2930 0         0 {cluck "No title in file:\n$r\n";
2931             }
2932             }
2933             }
2934             %r
2935 17         986 }->();
  17         323  
2936              
2937 17         493 my @toc; my %class; # List of reports
2938 17         221 for my $title(sort keys %reports) # Each report
2939 34         238 {my $options = $reports{$title};
2940 34         204 my $rows = $$options{rows};
2941 34 50       374 next unless my $file = $$options{file};
2942 34         1020 my $class = containingFolderName($file); $class{$class}++; # Classification for report
  34         204  
2943 34         170 my $href = qq($url$file);
2944 34         238 my $link = qq($title);
2945             my $tick = sub # Flag items that we would like to be zero
2946 34 50 33 34   408 {return q() unless $file =~ m(/bad/) and $rows;
2947 0         0 q()
2948 34         544 }->();
2949              
2950 34         187 my $c = qq( class="report report_$class"); # Classification
2951              
2952 34         561 push @toc, join '', qq(),
2953             join( qq(), $rows, $tick, $link);
2954             }
2955              
2956 17         119 my $tocs = @toc;
2957             # my @tocs = rectangularArray(int(@toc / $columns), @toc); # Divide into columns
2958 17         748 my @tocs = rectangularArray2($columns, @toc); # Divide into columns
2959 17         170 my $toc = join "\n", map {q(
  34         272  
2960 17         255 my $dt = dateTimeStamp; # Date of run
2961 17 50       425 my $t = $title ? qq(

$title

) : q(); # Title if present
2962              
2963 17         187 my $groups = join ', ', map {qq("$_")} sort keys %class; # Groups
  17         204  
2964 17         153 my $select = join '', map {<
  17         306  
2965             $_
2966             END
2967              
2968 17         289 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         119 my $html = join "\n", @html; # Create html
3026              
3027 17 50       1207 if (my $out = fpe($reports, qw(index_of_reports html)))
3028 17         1037 {owf($out, $html);
3029             }
3030              
3031             $html # Return the html so created
3032 17         782 } # 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 94651750 {waitpid $_, 0 for @formatHtmlAndTextTablesPids;
3038             }
3039              
3040             sub formatHtmlAndTextTables($$$$$%) # Create text and html versions of a tabular report.
3041 40     40 1 708 {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       202 my @prefix = ref($filePrefix) ? @$filePrefix : $filePrefix; # Flatten file prefixes into array
3044 40         80 my $file = $options{file}; # Relative report file
3045 40         82 my $columns = $options{columns}; # Columns must come first for the moment
3046              
3047 40 50       206 if ($reports) # Format table as text
3048 40         209907 {push @formatHtmlAndTextTablesPids, my $pid = fork;
3049 40 100       2561 unless($pid)
3050 2         189 {my $out = setFileExtension fpf($reports, $file), q(txt); # Output file name
3051 2         401 formatTable($data, columns=>$columns, %options, file=>$out);
3052 2         20804 exit;
3053             }
3054             }
3055              
3056 38 50       844 if ($html) # Format table as html
3057 38         245592 {push @formatHtmlAndTextTablesPids, my $pid = fork;
3058 38 100       30216 unless($pid)
3059 2         195 {my $out = setFileExtension fpf($html, $file), q(html); # Output file name
3060 2         55 my $start = time;
3061             my $h = sub # Turn file names into links in a table of scalars
3062 2     2   59 {my @r;
3063 2         59 for my $row(@$data)
3064 5         51 {my @c;
3065 5         78 for my $col(@$row)
3066             {push @c, sub
3067 10         61 {for my $filePrefix(@prefix) # Try each file prefix
3068 10 100 66     1127 {if ($col and $col =~ m(\A$filePrefix)s)
3069 5         68 {return qq().
3070             swapFilePrefix($col, $filePrefix).q();
3071             }
3072             }
3073             $col # Use plain file name as no prefix matched
3074 10         164 }->();
  5         169  
3075             }
3076 5         19 push @r, \@c;
3077             }
3078             \@r # Return edited rows as a reference for convenient use with formatTable
3079 2         193 }->();
  2         44  
3080              
3081 2         228 formatHtmlTable($h, %options, file => $out); # Format table as html
3082 2         18898 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 328     328 1 1640 {my ($string) = @_; # The string of lines from which to create an array
3091 328         2952 [grep {!/\A#/} split "\n", $string]
  656         8528  
3092             }
3093              
3094             sub loadHashFromLines($) # Load a hash: first word of each line is the key and the rest is the value.
3095 328     328 1 2296 {my ($string) = @_; # The string of lines from which to create a hash
3096 328         1640 +{map{split /\s+/, $_, 2} split "\n", $string}
  656         13448  
3097             }
3098              
3099             sub loadArrayArrayFromLines($) # Load an array of arrays from lines of text: each line is an array of words.
3100 328     328 1 984 {my ($string) = @_; # The string of lines from which to create an array of arrays
3101 328         1968 [map{[split /\s+/]} split "\n", $string]
  656         14432  
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 328     328 1 984 {my ($string) = @_; # The string of lines from which to create a hash of arrays
3106 328         1640 +{map{my @a = split /\s+/; (shift @a, [@a])} split "\n", $string}
  656         4920  
  656         9184  
3107             }
3108              
3109             sub loadArrayHashFromLines($) # Load an array of hashes from lines of text: each line is a hash of words.
3110 328     328 1 656 {my ($string) = @_; # The string of lines from which to create an array of arrays
3111 328         1312 [map {+{split /\s+/}} split /\n/, $string]
  656         14104  
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 328     328 1 984 {my ($string) = @_; # The string of lines from which to create a hash of arrays
3116 328         1312 +{map{my ($a, @a) = split /\s+/; ($a=>{@a})} split "\n", $string}
  656         3608  
  656         7872  
3117             }
3118              
3119             sub checkKeys($$) # Check the keys in a B confirm to those B<$permitted>.
3120 12237     12237 1 32028 {my ($hash, $permitted) = @_; # The hash to test, a hash of the permitted keys and their meanings
3121              
3122 12237 50       86365 ref($hash) =~ /hash/igs or # Check parameters
3123             confess "Hash reference required for first parameter\n";
3124 12237 50       46248 ref($permitted) =~ /hash/igs or
3125             confess "Hash reference required for second parameter\n";
3126              
3127 12237         37928 my %parms = %$hash; # Copy keys supplied
3128 12237         88427 delete $parms{$_} for keys %$permitted; # Remove permitted keys
3129 12237 50       46217 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 1312     1312 1 5576 {my (@names) = @_; # List of method names
3144 1312         4264 my ($package) = caller; # Package
3145 1312         4920 for my $m(@_) # Name each method
3146 1968         2624 {my $s;
3147 1968 100       7216 if ($m =~ m(::)s) # Package name supplied in name
3148 984         4264 {my $M = $m =~ s(\A.*:) ()r; # Remove package
3149 984         3280 $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 984         3936 {$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 1968   0 1916   204344 eval $s;
  1916   0 2020   22586  
  2020   50 0   23990  
  0   50 0   0  
  0   0 486   0  
  486   0 498   2272  
  498     472   2320  
  472     512   3816  
  512     0   4056  
  0     0   0  
  0     0   0  
  0     0   0  
  0         0  
3162 1968 50       38376 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 1312     1312 1 2624 {my (@names) = @_; # List of method names
3168 1312         3608 my ($package) = caller; # Package
3169 1312         3936 for my $m(@_) # Name each method
3170 1312 50       3936 {my $M = $m =~ m(::)s ? $m : $package.'::'.$m;
3171 1312 50       26240 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 328     328 1 2952 {my (@names) = @_; # List of method names
3178 328         1640 my ($package) = caller; # Package
3179 328         1312 for(@_) # Name each method
3180 984         3280 {my $s = 'sub '.$package.'::'.$_.':lvalue {my $v = "'.$_.'"; $_[0]{"'.$_.'"} //= $v}';
3181 984   33 328   60680 eval $s;
  328   0 0   656  
  328   0 0   3280  
  0         0  
  0         0  
  0         0  
  0         0  
3182 984 50       11152 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 328     328 1 1312 {my (@names) = @_; # List of method names
3188 328         984 my ($package) = caller; # Package
3189 328         1312 for(@_) # Name each method
3190 984         2624 {my $s = 'sub '.$package.'::'.$_.':lvalue {$_[0]{"'.$_.'"} //= []}';
3191 984   100 656   50512 eval $s;
  656   0 0   10824  
  0   0 0   0  
  0         0  
3192 984 50       11152 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 328     328 1 3280 {my (@names) = @_; # Method names
3198 328         1312 my ($package) = caller; # Package
3199 328         1640 for(@_) # Name each method
3200 984         3608 {my $s = 'sub '.$package.'::'.$_.':lvalue {$_[0]{"'.$_.'"} //= {}}';
3201 984   100 656   52480 eval $s;
  656   0 0   9840  
  0   0 0   0  
  0         0  
3202 984 50       10824 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 6879     6879 1 614561 {my ($bless, %attributes) = @_; # Package name, hash of attribute names and values
3210 6879         30305 my $h = \%attributes;
3211 6879         41181 bless $h, $bless;
3212 6879         236336 for my $m(sort keys %attributes) # Add any attributes not already present
3213 434562 50       9148297 {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 434562         862165 my $M = $bless.q(::).$m; # The full name of the attribute
3218              
3219 434562 100       1725182 if ($h->can($m)) # Skip any methods that are already defined
3220 316967 50       5681475 {say STDERR dump(\%genHash, $m, $M) unless $genHash{$M};
3221              
3222             confess "Cannot define attribute because there is already ".
3223 316967 50       583696 "a method with the same name: $m\n" unless $genHash{$M};
3224              
3225 316967         518342 next;
3226             }
3227              
3228 117595 50       537396 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 117595         379944 my $R = reftype($attributes{$m}); # Type of thing referred to
3234 117595 100       424405 my $r = !defined($R) ? q() : $R =~ m(array)i ? q( //= []) : q( //= {}); # Empty return type
    100          
3235 117595         178257 my $s = '';
3236 117595         235575 $s .= 'sub '.$bless.'::'.$m. ':lvalue {$_[0]{"'.$m.qq("}$r})."\n"; # LValue version for get and set
3237 117595         226033 $s .= 'sub '.$bless.'::'.$m. 'X {$_[0]{"'.$m.'"}//q()}'."\n"; # Default to blank for get
3238 117595 50       194775 if ($s) # Add any new methods needed
3239 117595   0 225   8387507 {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 154141      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 371255      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 2719913      
      0 0      
      0 2672943      
      0 0      
      0 0      
      0 110069      
      0 55930      
      0 0      
      0 0      
      0 55156      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 52793      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 1300      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 2592      
      0 233      
      0 0      
      0 0      
      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 55156      
      0 0      
      0 0      
      0 0      
      0 53336      
      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      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0 0      
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      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 117595 50       405350 confess "$@\n$s\n$@" if $@;
3241             }
3242 117595         427814 $genHash{$M}++; # Record attribute as being created by genHash
3243             }
3244              
3245             $h
3246 6879         3023284 }
3247              
3248             sub loadHash($%) # Load the specified blessed B<$hash> generated with L with B<%attributes>. Confess to any unknown attribute names.
3249 1989     1989 1 14167 {my ($hash, %attributes) = @_; # Hash, hash of attribute names and values to be loaded
3250 2046         17239 for my $m(sort keys %attributes) # Add any attributes not already present
3251 1209 100       120068 {$hash->can($m) or confess "Cannot load attribute: $m\n"; # Unknown attribute
3252 650         1625 $hash->{$m} = $attributes{$m}; # Load known attribute
3253             }
3254             $hash # Return loaded hash
3255 1394         21820 }
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 16178     16499 1 39045 {my ($d, $progress) = @_; # Data structure, progress
3261 16175 100       54625 return unless my $r = reftype($d);
3262 5600 50       26475 return if $$progress{$d};
3263 5711 100       65221 if ($d =~ m(array)is) # Array
    50          
3264 2414         12204 {$$progress{$d}++;
3265 2300         18050 &reloadHashes2($_, $progress) for @$d;
3266             }
3267             elsif ($d =~ m(hash)is) # Hash
3268 3300         13350 {$$progress{$d}++;
3269 3300         20650 &reloadHashes2($_, $progress) for values %$d;
3270 3300 100       16375 if (my $b = blessed($d)) # Already blessed
3271 2325         11350 {genHash($b, %$d);
3272             }
3273             else # Create a name
3274 975         4225 {my $b = q(reloadHash_).++$reloadHashesCount;
3275 975         11050 bless($d, $b); # Bless hash
3276 975         7475 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 2625     2859 1 12725 {my ($d) = @_; # Data structure
3283 2625         19225 reloadHashes2($d, {});
3284 2625         34675 $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 3375     3375 1 5625 {my ($d, $keys, $progress) = @_; # Data structure, keys found, progress
3289 3375 100       20925 return unless my $r = reftype($d);
3290 1125 50       3375 return if $$progress{$d};
3291 1125 100       13050 if ($d =~ m(array)is)
    50          
3292 225         1800 {$$progress{$d}++;
3293 904         13319 &showHashes2($_, $keys, $progress) for @$d;
3294             }
3295             elsif ($d =~ m(hash)is)
3296 900         2700 {$$progress{$d}++;
3297 900         7425 &showHashes2($_, $keys, $progress) for values %$d;
3298 900 50       3375 if (my $b = blessed($d))
3299 900         3150 {for my $k(keys %$d)
3300 2700         6750 {$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 225     297 1 1125 {my ($d) = @_; # Data structure
3308 225         1350 showHashes2($d, my $keys = {}, {});
3309 225         5850 $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 450     528 1 3600 {my ($set, @search) = @_; # Package to set, package names in search order.
3316 450         2250 %packageSearchOrder = (); # Reset method to package map
3317              
3318 450         2925 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 450         2700 my $search = q/qw(/.join(' ', @search).q/)/; # Set search order
3343 450         10350 $c =~ s(\$set) ($set)gs;
3344 450         7875 $c =~ s(\@search) ($search)gs;
3345 450         84150 eval $c;
3346 450 50       17550 confess "$c\n$@\n" if $@;
3347             }
3348              
3349             sub isSubInPackage($$) # Test whether the specified B<$package> contains the subroutine <$sub>.
3350 4375     4450 1 13650 {my ($package, $sub) = @_; # Package name, subroutine name
3351 4375         167250 my $r = eval qq(defined(&${package}::${sub}));
3352 4375 50       15675 $@ and confess $@;
3353 3250         33350 $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 1350     225 1 3150 {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 2025         9450 my @s;
3359 2025         16200 for my $method(setUnion @methods) # Replaceable methods
3360 1575         5850 {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 1350         24750 my $s = join "\n", @s; # Replace methods
3372 225         34875 eval $s;
3373 225 100       5625 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     111 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 218         4484 {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 183         3285 my $s = join "\n", @s; # Replace methods
3415 149         3031 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 328     442 1 1312 {my ($package, @refs) = @_; # Package, references
3421 328         984 for(@refs) # Check each reference
3422 553         2681 {my $r = ref($_);
3423 328 50 33     35424 $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 328     328 1 1640 {my (@refs) = @_; # References
3430 328         2296 my ($package) = caller; # Package
3431 328         1640 for(@_) # Check each reference
3432 328         984 {my $r = ref($_);
3433 328 50 33     45920 $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 229     229 1 2488 {my (@array) = @_; # Array
3440 229         1808 +{map{$_=>1} @array}
  682         7011  
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 609 {my (@array) = @_; # Array to flatten
3445 147         315 my @a;
3446 147         315 for my $a(@array)
3447 231 100       1386 {if (ref($a) =~ m(\Aarray\Z)i)
    100          
3448 105         672 {push @a, &flattenArrayAndHashValues(@$a);
3449             }
3450             elsif (ref($a) =~ m(\Ahash\Z)i)
3451 21         252 {push @a, &flattenArrayAndHashValues(map {$$a{$_}} sort keys %$a);
  42         315  
3452             }
3453             else
3454 105         420 {push @a, $a;
3455             }
3456             }
3457             @a # Flattened array
3458 147         1365 }
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       234 if (my $b = B::svref_2object($sub))
3463 3         9 {my $r = ref($b);
3464 3 50       48 if ($r =~ m(B::CV)i)
3465 3 50       126 {if (my $g = $b->GV)
3466 3         162 {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 900     900 1 2475 {my ($string) = @_; # String
3477 900         3150 my $f = writeFile(undef, $string); # Write into a file
3478 900         4050 my $s = readBinaryFile($f); # Read as binary
3479 900         7650 my $m = md5_hex($s); # Md5sum of bytes
3480 900         522225 unlink $f;
3481 900         16650 $m;
3482             }
3483              
3484             sub indentString($$) # Indent lines contained in a string or formatted table by the specified string.
3485 978     978 1 7174 {my ($string, $indent) = @_; # The string of lines to indent, the indenting string
3486 978 50       14360 join "\n", map {$indent.$_} split m(\n+), (ref($string) ? $$string : $string)
  3262         20574  
3487             }
3488              
3489             sub replaceStringWithString($$$) # Replace all instances in B<$string> of B<$source> with B<$target>.
3490 42     42 1 462 {my ($string, $source, $target) = @_; # String in which to replace substrings, the string to be replaced, the replacement string
3491 42         378 for(1..(1+length($string) / (length($source)+1))) # Avoid too much recursive expansion
3492 105         525 {my $i = index($string, $source);
3493 105 100       567 if ($i >= 0)
3494 84         357 {substr($string, $i, length($source)) = $target;
3495 84         546 next;
3496             }
3497 21         42 last;
3498             }
3499             $string
3500 42         525 }
3501              
3502             sub formatString($$) # Format the specified B<$string> so it can be displayed in B<$width> columns.
3503 21     21 1 315 {my ($string, $width) = @_; # The string of text to format, the formatted width.
3504              
3505 21         672 $string =~ s(\\m) (\n\n)gs; # Expand \m introduced by update documentation
3506              
3507 21         210 for(1..9)
3508 189 100       966 {if ($string =~ m((B<([^>]*)>))s)
3509 21         1008 {$string = replaceStringWithString(my $s = $string, $1, boldString($2));
3510 21 50       609 last if $s eq $string;
3511             }
3512             }
3513              
3514 21         168 my @f;
3515 21         399 my @w = split m/\s+/, $string; # Parse string into words
3516 21         252 for my $w(@w) # Bold B
3517 357 100       735 {if (!$f[-1]) {push @f, $w}
  21         84  
3518             else
3519 336         714 {my $l = $f[-1].qq( $w);
3520 336 100       819 if (length($l) > $width)
3521 84         336 {push @f, $w;
3522             }
3523             else
3524 252         567 {$f[-1] = $l;
3525             }
3526             }
3527             }
3528              
3529 21         273 my $t = join "\n", @f; # Format punctuation
3530 21         609 $t =~ s(\s*([,;.!?])) ($1)gs;
3531 21         504 $t =~ s(\s*\Z) ()s;
3532              
3533 21         483 "$t\n"
3534             }
3535              
3536             sub isBlank($) # Test whether a string is blank.
3537 656     656 1 1968 {my ($string) = @_; # String
3538 656         5904 $string =~ m/\A\s*\Z/
3539             }
3540              
3541             sub trim($) # Remove any white space from the front and end of a string.
3542 328     328 1 1312 {my ($string) = @_; # String
3543 328         6232 $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 1640     1640 1 4264 {my ($string, $length, $padding) = @_; # String, tab width, padding string
3548 1640 50       3936 defined($string) or confess "String required\n";
3549 1640         8200 $string =~ s/\s+\Z//;
3550 1640   100     7216 $padding //= q( );
3551 1640         2296 my $l = length($string);
3552 1640 100       4920 return $string if $l % $length == 0;
3553 1312         1968 my $p = $length - $l % $length;
3554 1312         6888 $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 1640     1640 1 3608 {my ($string, $length, $padding) = @_; # String, tab width, padding string
3559 1640 50       3936 defined($string) or confess "String required\n";
3560 1640         8856 $string =~ s/\s+\Z//;
3561 1640   100     9184 $padding //= q( );
3562 1640         2624 my $l = length($string);
3563 1640 100       4592 return $string if $l % $length == 0;
3564 1312         1968 my $p = $length - $l % $length;
3565 1312         6888 ($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 1640     1640 1 5248 {my ($length, $string, $padding) = @_; # Tab width, string, padding string
3570 1640 50       3936 defined($string) or confess "String required\n";
3571 1640         7544 $string =~ s/\s+\Z//;
3572 1640   100     7216 $padding //= q( );
3573 1640         2624 my $l = length($string);
3574 1640 100       5576 return $string if $l % $length == 0;
3575 1312         2296 my $p = $length - $l % $length;
3576 1312         6888 $string .= $padding x $p;
3577             }
3578              
3579             sub firstNChars($$) # First N characters of a string.
3580 13776     13776 1 45894 {my ($string, $length) = @_; # String, length
3581 13776 100 100     119274 return $string if !$length or length($string) < $length;
3582 325         5200 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 11351     11351 1 43055 {my ($string, $length) = @_; # String to normalize, maximum length of result
3587 11351         9219754 my $s = $string =~ s((\x{200b}|\A\s+|\s+\Z)) ()gr =~ s/\s+/ /gr;
3588 11351         38923 firstNChars($s, $length) # Apply maximum length if requested
3589             }
3590              
3591             sub deduplicateSequentialWordsInString($) # Remove sequentially duplicate words in a string.
3592 17     17 1 204 {my ($s) = @_; # String to deduplicate
3593 17         459 my %a = map {$_=>1} grep {$_} split /\W+/, $s; # Split into words
  221         646  
  238         442  
3594 17         306 for my $w(sort keys %a)
3595 85         1479 {1 while $s =~ s($w\s+$w) ($w)gs;
3596             }
3597             $s
3598 17         136 }
3599              
3600             sub detagString($) # Remove L or L tags from a string.
3601 21     700 1 420 {my ($string) = @_; # String to detag
3602 21         714 $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 340 {my ($string) = @_; # String to parse
3607 17 50       578 return () unless $string;
3608              
3609 17         204 my $s = 0; # 0 - look for word or quote, 1 in word, 2 in ' string, 3 - in " string
3610 17         238 my @r;
3611             my $r;
3612              
3613             my $accept = sub # Accept a word or string
3614 170     170   1649 {push @r, $r; $s = 0;
  170         476  
3615 17         680 };
3616              
3617 17         1938 for my $c(split m//, $string) # Each character in the string
3618 1020 100 100     3213 {next if $s == 0 and $c =~ m(\s); # Skip spaces while looking for a word or string
3619              
3620 680 100       2091 if ($s == 0) # String
    100          
    100          
    50          
3621 170 100       969 {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         153 {$r = ''; $s = 3;
  51         204  
3626             }
3627             else # Word
3628 68         357 {$r = $c; $s = 1;
  68         306  
3629             }
3630             }
3631             elsif ($s == 1) # In word
3632 153 100       612 {if ($c =~ m(\s))
3633 51         374 {&$accept;
3634             }
3635             else
3636 1145         6181 {$r .= $c;
3637             }
3638             }
3639             elsif ($s == 2) # In ' string
3640 1334 100       7278 {if ($c =~ m('))
3641 51         459 {&$accept;
3642             }
3643             else
3644 51         187 {$r .= $c;
3645             }
3646             }
3647             elsif ($s == 3) # In " string
3648 255 100       544 {if ($c =~ m("))
3649 51         340 {&$accept;
3650             }
3651             else
3652 204         289 {$r .= $c;
3653             }
3654             }
3655             }
3656 17         408 &$accept;
3657             @r
3658 17         595 } # 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 1625     1625 1 4550 {my ($a, $b) = @_; # First string, second string
3662 1625         9425 my @a = split //, $a;
3663 1625         9100 my @b = split //, $b;
3664 1625         3250 my @c;
3665 1625   66     17550 while(@a and @b and $a[0] eq $b[0])
      100        
3666 6825         9100 {shift @a; push @c, shift @b;
  6825         26325  
3667             }
3668 1625         22100 (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 325     325 1 1625 {my ($g, $e) = @_; # First string, second string
3673 325         650 my @s;
3674 325 50       3250 if ($g ne $e)
3675 325         3900 {my ($s, $G, $E) = stringsAreNotEqual($g, $e);
3676 325 50       4225 if (length($s))
3677 325         4550 {my $line = 1 + length($s =~ s([^\n]) ()gsr);
3678 325         3575 my $char = 1 + length($s =~ s(\A.*\n) ()sr);
3679 325         1950 push @s, "Comparing wanted with got failed at line: $line, character: $char";
3680 325         4875 push @s, "Start:\n$s";
3681             }
3682 325         1300 my $b1 = '+' x 80;
3683 325         975 my $b2 = '_' x 80;
3684 325         1950 push @s, "Want $b1\n", firstNChars($E, 80);
3685 325         1300 push @s, "Got $b2\n", firstNChars($G, 80);
3686 325         4225 return join "\n", @s;
3687             }
3688             undef
3689 0         0 }
3690              
3691             sub printQw(@) # Print an array of words in qw() format.
3692 653     653 1 4565 {my (@words) = @_; # Array of words
3693 653         9449 'qw('.join(' ', @words).')'
3694             }
3695              
3696             sub numberOfLinesInString($) # The number of lines in a string.
3697 325     325 1 4225 {my ($string) = @_; # String
3698 325         9750 scalar split /\n/, $string;
3699             }
3700              
3701             sub javaPackage($) # Extract the package name from a java string or file.
3702 1309     1309 1 2615 {my ($java) = @_; # Java file if it exists else the string of java
3703              
3704             my $s = sub
3705 1309 100 66 1309   24552 {return readFile($java) if $java !~ m/\n/s and -e $java; # Read file of java
3706 325         1300 $java # Java string
3707 1309         14038 }->();
3708              
3709 1309         18308 my ($package) = $s =~ m(package\s+(\S+)\s*;);
3710 1309         6545 $package
3711             }
3712              
3713             sub javaPackageAsFileName($) # Extract the package name from a java string or file and convert it to a file name.
3714 328     328 1 984 {my ($java) = @_; # Java file if it exists else the string of java
3715              
3716 328 50       984 if (my $package = javaPackage($java))
3717 328         6560 {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 653     653 1 2284 {my ($perl) = @_; # Perl file if it exists else the string of perl
3724 653         4234 my $p = javaPackage($perl); # Use same technique as Java
3725 653 50       2940 defined($p) or confess "There is no Perl module in file: $perl";
3726 653         2946 $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 225     225 1 900 {my ($fileOrString) = @_; # File or string
3731 225 50       3825 my $s = $fileOrString =~ m(\n) ? $fileOrString : readFile($fileOrString);
3732 225         675 my @s;
3733 225         450 my $state = 0;
3734 225         2250 for my $line(split /\n/, $s)
3735 2025 100       4500 {if ($state == 0)
    50          
3736 1125 100       4500 {if ($line =~ m(\Afunction.*\/\/E))
3737 450         675 {$state = 1;
3738 450         1350 push @s, q(), $line;
3739             }
3740             }
3741             elsif ($state == 1)
3742 900 100       1800 {$state = 0 if $line =~ m(\A \});
3743 900         2025 push @s, $line;
3744             }
3745             }
3746 225         2250 join "\n", @s, '';
3747             }
3748              
3749             sub chooseStringAtRandom(@) # Choose a string at random from the list of B<@strings> supplied.
3750 17     17 1 255 {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         187 for my $i(keys @a)
3758 68         221 {my $r = int(rand() * ($i+1)); # Uniform randomization
3759 68         153 my $s = $a[$i];
3760 68         238 my $t = $a[$r];
3761 68         119 $a[$i] = $t;
3762 68         136 $a[$r] = $s;
3763             }
3764             @a
3765 17         323 }
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 7 {my ($a) = @_; # Array reference
3771 1         6 max map{scalar @$_} @$a
  4         29  
3772             }
3773              
3774             sub cmpArrays($$) # Compare two arrays of strings.
3775 5     5 1 18 {my ($a, $b) = @_; # Array A, array B
3776 5         17 my @a = @$a;
3777 5         16 my @b = @$b;
3778 5   100     68 while(@a and @b and !($a[0] cmp $b[0]))
      100        
3779 8         19 {shift @a; shift @b;
  8         33  
3780             }
3781 5 100 100     42 return $a[0] cmp $b[0] if @a and @b;
3782 3 100       9 return -1 if @b;
3783 2 100       9 return +1 if @a;
3784 1         13 0
3785             }
3786              
3787             sub forEachKeyAndValue(&%) # Iterate over a hash for each key and value.
3788 1     1 1 5 {my ($body, %hash) = @_; # Body to be executed, hash to be iterated
3789 1         33 &$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 325     325 1 1300 {my ($string) = @_; # String to convert
3814 325         5200 my $h = $normalAlphaString =~ s(h) ()r; # Unicode does not have a small mathematical italic h
3815 325         21125 eval qq(\$string =~ tr($h) ($mathematicalItalic));
3816 325         1950 $string
3817             }
3818              
3819             sub mathematicalBoldString($) # Convert alphanumerics in a string to L Mathematical Bold.
3820 325     325 1 975 {my ($string) = @_; # String to convert
3821 325         18525 eval qq(\$string =~ tr($normalAlphaString) ($mathematicalBold));
3822 325         2600 $string
3823             }
3824              
3825             sub mathematicalBoldStringUndo($) # Undo alphanumerics in a string to L Mathematical Bold.
3826 325     325 1 975 {my ($string) = @_; # String to convert
3827 325         22100 eval qq(\$string =~ tr($mathematicalBold) ($normalAlphaString));
3828 325         2600 $string
3829             }
3830              
3831             sub mathematicalBoldItalicString($) # Convert alphanumerics in a string to L Mathematical Bold Italic.
3832 325     325 1 1300 {my ($string) = @_; # String to convert
3833 325         25025 eval qq(\$string =~ tr($normalAlphaString) ($mathematicalBoldItalic));
3834 325         2600 $string
3835             }
3836              
3837             sub mathematicalBoldItalicStringUndo($) # Undo alphanumerics in a string to L Mathematical Bold Italic.
3838 325     325 1 975 {my ($string) = @_; # String to convert
3839 325         18200 eval qq(\$string =~ tr($mathematicalBoldItalic) ($normalAlphaString));
3840 325         2600 $string
3841             }
3842              
3843             sub mathematicalSansSerifString($) # Convert alphanumerics in a string to L Mathematical Sans Serif.
3844 325     325 1 975 {my ($string) = @_; # String to convert
3845 325         24700 eval qq(\$string =~ tr($normalAlphaString) ($mathematicalSansSerif));
3846 325         2275 $string
3847             }
3848              
3849             sub mathematicalSansSerifStringUndo($) # Undo alphanumerics in a string to L Mathematical Sans Serif.
3850 325     325 1 975 {my ($string) = @_; # String to convert
3851 325         21125 eval qq(\$string =~ tr($mathematicalSansSerif) ($normalAlphaString));
3852 325         2600 $string
3853             }
3854              
3855             sub mathematicalSansSerifBoldString($) # Convert alphanumerics in a string to L Mathematical Sans Serif Bold.
3856 325     325 1 1300 {my ($string) = @_; # String to convert
3857 325         20800 eval qq(\$string =~ tr($normalAlphaString) ($mathematicalSansSerifBold));
3858 325         2275 $string
3859             }
3860              
3861             sub mathematicalSansSerifBoldStringUndo($) # Undo alphanumerics in a string to L Mathematical Sans Serif Bold.
3862 325     325 1 1300 {my ($string) = @_; # String to convert
3863 325         17875 eval qq(\$string =~ tr($mathematicalSansSerifBold) ($normalAlphaString));
3864 325         2600 $string
3865             }
3866              
3867             sub mathematicalSansSerifItalicString($) # Convert alphanumerics in a string to L Mathematical Sans Serif Italic.
3868 325     325 1 975 {my ($string) = @_; # String to convert
3869 325         22100 eval qq(\$string =~ tr($normalAlphaString) ($mathematicalSansSerifItalic));
3870 325         2600 $string
3871             }
3872              
3873             sub mathematicalSansSerifItalicStringUndo($) # Undo alphanumerics in a string to L Mathematical Sans Serif Italic.
3874 325     325 1 975 {my ($string) = @_; # String to convert
3875 325         24050 eval qq(\$string =~ tr($mathematicalSansSerifItalic) ($normalAlphaString));
3876 325         2600 $string
3877             }
3878              
3879             sub mathematicalSansSerifBoldItalicString($) # Convert alphanumerics in a string to L Mathematical Sans Serif Bold Italic.
3880 325     325 1 975 {my ($string) = @_; # String to convert
3881 325         26000 eval qq(\$string =~ tr($normalAlphaString) ($mathematicalSansSerifBoldItalic));
3882 325         2600 $string
3883             }
3884              
3885             sub mathematicalSansSerifBoldItalicStringUndo($) # Undo alphanumerics in a string to L Mathematical Sans Serif Bold Italic.
3886 325     325 1 975 {my ($string) = @_; # String to convert
3887 325         17550 eval qq(\$string =~ tr($mathematicalSansSerifBoldItalic) ($normalAlphaString));
3888 325         2600 $string
3889             }
3890              
3891             sub mathematicalMonoSpaceString($) # Convert alphanumerics in a string to L Mathematical MonoSpace.
3892 325     325 1 1300 {my ($string) = @_; # String to convert
3893 325         21125 eval qq(\$string =~ tr($normalAlphaString) ($mathematicalMonoSpace));
3894 325         2600 $string
3895             }
3896              
3897             sub mathematicalMonoSpaceStringUndo($) # Undo alphanumerics in a string to L Mathematical MonoSpace.
3898 325     325 1 1300 {my ($string) = @_; # String to convert
3899 325         21775 eval qq(\$string =~ tr($mathematicalMonoSpace) ($normalAlphaString));
3900 325         2925 $string
3901             }
3902              
3903             sub boldString($) # Convert alphanumerics in a string to bold.
3904 996     996 1 3796 {my ($string) = @_; # String to convert
3905 996     550   163035 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.
  550         65625  
  550         52725  
  325         6825  
3906 996         8849 $string
3907             }
3908              
3909             sub boldStringUndo($) # Undo alphanumerics in a string to bold.
3910 325     325 1 975 {my ($string) = @_; # String to convert
3911 325         17550 eval qq(\$string =~ tr($boldString) ($normalString));
3912 325         35425 $string
3913             }
3914              
3915             sub enclosedString($) # Convert alphanumerics in a string to enclosed alphanumerics.
3916 650     650 1 1950 {my ($string) = @_; # String to convert
3917 650         47775 eval qq(\$string =~ tr($normalString) ($circleString));
3918 650         4875 $string
3919             }
3920              
3921             sub enclosedStringUndo($) # Undo alphanumerics in a string to enclosed alphanumerics.
3922 325     325 1 975 {my ($string) = @_; # String to convert
3923 325         37375 eval qq(\$string =~ tr($circleString) ($normalString));
3924 325         3250 $string
3925             }
3926              
3927             sub enclosedReversedString($) # Convert alphanumerics in a string to enclosed reversed alphanumerics.
3928 650     650 1 2925 {my ($string) = @_; # String to convert
3929 650         47775 eval qq(\$string =~ tr($normalString) ($darkString));
3930 650         6175 $string
3931             }
3932              
3933             sub enclosedReversedStringUndo($) # Undo alphanumerics in a string to enclosed reversed alphanumerics.
3934 325     325 1 975 {my ($string) = @_; # String to convert
3935 325         22425 eval qq(\$string =~ tr($darkString) ($normalString));
3936 325         2600 $string
3937             }
3938              
3939             sub superScriptString($) # Convert alphanumerics in a string to super scripts.
3940 650     650 1 1950 {my ($string) = @_; # String to convert
3941 650         41600 eval qq(\$string =~ tr($normalString) ($superString));
3942 650         4225 $string
3943             }
3944              
3945             sub superScriptStringUndo($) # Undo alphanumerics in a string to super scripts.
3946 325     325 1 1300 {my ($string) = @_; # String to convert
3947 154466         1271571 eval qq(\$string =~ tr($superString) ($normalString));
3948 325         3250 $string
3949             }
3950              
3951             sub subScriptString($) # Convert alphanumerics in a string to sub scripts.
3952 650     650 1 2275 {my ($string) = @_; # String to convert
3953 650         43550 eval qq(\$string =~ tr($lowerString) ($lowsubString));
3954 650         4550 $string
3955             }
3956              
3957             sub subScriptStringUndo($) # Undo alphanumerics in a string to sub scripts.
3958 325     325 1 975 {my ($string) = @_; # String to convert
3959 325         15925 eval qq(\$string =~ tr($lowsubString) ($lowerString));
3960 325         1950 $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 big endian.
4008 6     6 1 22 {my ($c) = @_; # Unicode point encoded as utf32
4009              
4010 6 100       38 return $c if $c <= (1<<7); # Ascii
4011              
4012 5 100       13 if ($c <= (1 << 11)) # 2 bytes
4013 1         8 {my $d = ($c >> 0) & 0x3f;
4014 1         2 $c = ($c >> 6);
4015 1         8 return ($c<<8) | $d | 0xC080;
4016             }
4017              
4018 4 100       17 if ($c <= (1 << 16)) # 3 bytes
4019 3         8 {my $e = ($c >> 0) & 0x3f;
4020 3         4 my $d = ($c >> 6) & 0x3f;
4021 3         4 $c = ($c >> 12);
4022              
4023 3         38 return ($c<<16) | ($d<<8) | $e | 0xE08080
4024             }
4025              
4026 1 50       13 if ($c <= (1 << 21)) # 4 bytes
4027 1         10 {my $f = ($c >> 0) & 0x3f;
4028 1         4 my $e = ($c >> 6) & 0x3f;
4029 1         3 my $d = ($c >> 12) & 0x3f;
4030 1         6 $c = ($c >> 18);
4031 1         9 return ($c<<24) | ($d<<16) | ($e<<8) | $f | 0xF0808080
4032             }
4033              
4034 0         0 confess "Invalid utf32 code: $c";
4035             }
4036              
4037             sub convertUtf32ToUtf8LE($) # Convert a number representing a single unicode point coded in utf32 to utf8 little endian.
4038 6     6 1 14 {my ($c) = @_; # Unicode point encoded as utf32
4039              
4040 6 100       25 return $c if $c <= (1<<7); # Ascii
4041              
4042 5 100       21 if ($c <= (1 << 11)) # 2 bytes
4043 1         4 {my $d = ($c >> 0) & 0x3f;
4044 1         8 $c = ($c >> 6);
4045 1         6 return ($c) | ($d<<8) | 0x80C0;
4046             }
4047              
4048 4 100       16 if ($c <= (1 << 16)) # 3 bytes
4049 3         6 {my $e = ($c >> 0) & 0x3f;
4050 3         5 my $d = ($c >> 6) & 0x3f;
4051 3         4 $c = ($c >> 12);
4052              
4053 3         20 return ($c) | ($d<<8) | ($e<<16) | 0x8080E0
4054             }
4055              
4056 1 50       5 if ($c <= (1 << 21)) # 4 bytes
4057 1         4 {my $f = ($c >> 0) & 0x3f;
4058 1         9 my $e = ($c >> 6) & 0x3f;
4059 1         3 my $d = ($c >> 12) & 0x3f;
4060 1         1 $c = ($c >> 18);
4061 1         7 return ($c) | ($d<<8) | ($e<<16) | ($f<<24) | 0x808080F0
4062             }
4063              
4064 0         0 confess "Invalid utf32 code: $c";
4065             }
4066              
4067             #D1 Unix domain communications # Send messages between processes via a unix domain socket.
4068              
4069             sub newUdsr(@) #P Create a communicator - a means to communicate between processes on the same machine via L and L.
4070 0     0 1 0 {my (@parms) = @_; # Attributes per L
4071 0         0 my $u = genHash(q(Udsr), # Package name
4072             client => undef, # Client socket and connection socket
4073             headerLength => 8, #I Length of fixed header which carries the length of the following message
4074              
4075             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.
4076              
4077             serverPid => undef, # Server pid which can be used to kill the server via kill q(kill), $pid
4078             socketPath => q(unix-domain-socket-test.sock), #I Socket file
4079              
4080             serviceName => q(zzz), #I Service name for install by systemd
4081             serviceUser => q(), #I Userid for service
4082             @_
4083             );
4084             }
4085              
4086             sub newUdsrServer(@) # Create a communications server - a means to communicate between processes on the same machine via L and L.
4087 0     218 1 0 {my (@parms) = @_; # Attributes per L
4088 0         0 my $u = newUdsr(@_);
4089 0         0 my $f = $u->socketPath;
4090 0         0 unlink $f;
4091 0         0 my $s = IO::Socket::UNIX->new(Type=>SOCK_STREAM(), Local=>$f, Listen=>1); # Create socket
4092 0         0 xxx(qq(chmod ugo=rwx $f)); # Ensure that www-data can read and write to the socket
4093             # lll "Created unix domain socket as user:", qx(/usr/bin/whoami);
4094 0 0       0 if (my $pid = fork) # Run the server in a process by itself
4095 0         0 {$u->serverPid = $pid; # Record server pid so it can be killed
4096 0         0 return $u;
4097             }
4098             else # Run the server action on a client connection
4099 0         0 {while (my $con = $s->accept())
4100 0         0 {$u->client = $con;
4101 0     183   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  
4102 0         0 $con->close;
4103             }
4104 0         0 exit;
4105             }
4106             }
4107              
4108             sub newUdsrClient(@) # Create a new communications client - a means to communicate between processes on the same machine via L and L.
4109 0     149 1 0 {my (@parms) = @_; # Attributes per L
4110 0         0 my $u = newUdsr(@_);
4111 0         0 my $s = $u->client = IO::Socket::UNIX->new(Type=>SOCK_STREAM(), Peer => $u->socketPath);
4112 0         0 my $r1 = $!; my $r2 = $?;
  0         0  
4113 0 0       0 $s or confess join "\n", "Cannot create unix domain socket:",
4114             dump($u), dump({q($!)=>$r1, q($?)=>$r2, q(userId)=>qx(/usr/bin/whoami)});
4115 0         0 $u
4116             }
4117              
4118             sub Udsr::write($$) # Write a communications message to the L or the L.
4119 0     0   0 {my ($u, $msg) = @_; # Communicator, message
4120 0         0 my $con = $u->client;
4121             # $msg //= ''; # undef seems to get reported as wide char
4122 0         0 my $m = pad(length($msg), $u->headerLength).$msg;
4123 0 0       0 $con or confess "No unix domain socket:\n". dump($u); # Complain if the socket has not been created
4124 0         0 $con->print($m);
4125 0         0 $u
4126             }
4127              
4128             sub Udsr::read($) # Read a message from the L or the L.
4129 0     0   0 {my ($u) = @_; # Communicator
4130 0         0 my $con = $u->client;
4131 0         0 $con->read(my $length, $u->headerLength);
4132 0         0 $con->read(my $data, $length);
4133 0         0 $data
4134             }
4135              
4136             sub Udsr::kill($) # Kill a communications server.
4137 0     0   0 {my ($u) = @_; # Communicator
4138 0         0 my $p = $u->serverPid; # Server Pid
4139 0 0       0 kill 'KILL', $p if $p; # Kill server
4140 0         0 $u->serverPid = undef; # Server Pid
4141 0         0 unlink $u->socketPath; # Remove socket
4142 0         0 $u
4143             }
4144              
4145             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.
4146 0     225   0 {my ($u, $folder) = @_; # Communicator, folder to contain server code
4147              
4148 0         0 clearFolder($folder, 9); # Clear the output folder
4149              
4150             my $parms = join ', ', # Parameters to hand to server and client
4151 371255 0       85124443439 map {my $v = $$u{$_}; defined($v) ? qq($_ => q($v)) : ()}
  0         0  
4152 0         0 grep {!m/serverAction/} keys %$u;
  0         0  
4153              
4154 0         0 my $user = $u->serviceUser; # Communicator details
4155 0         0 my $code = $u->serverAction; # Server code minus
4156 0         0 $code =~ s(if \(!caller\).*\Z) ()s; # Remove initiator at end
4157 0         0 $code =~ s(##.*?\n) ()gs; # Remove some spare blank lines so line numbers match
4158              
4159             my $perlParameters = sub # Get perl parameters
4160 0 0   0   0 {if ($code =~ m(\A#!.*?perl\s*(.*?)\n)is)
4161 0         0 {my $p = $1;
4162 0         0 return $p;
4163             }
4164             q()
4165 0         0 }->();
  0         0  
4166              
4167 0         0 my $name = $u->serviceName;
4168              
4169 0         0 my $ssdt = fpe(qw(/etc systemd system), $name, q(service)); # Systemd folder
4170              
4171 0         0 my $cgif = fpd(qw(/usr lib cgi-bin), $name); # Cgi folder
4172 0         0 my $cgst = fpe($cgif, q(server), q(pl)); # Cgi server
4173 0         0 my $cgct = fpe($cgif, q(client), q(pl)); # Cgi client
4174              
4175 0         0 my $inst = fpe($folder, qw(install sh)); # Install script
4176 0         0 my $ssdl = fpe($folder, qw(service txt));
4177 0         0 my $cgsl = fpe($folder, q(server), q(pl));
4178 0         0 my $cgcl = fpe($folder, q(client), q(pl));
4179              
4180 2719913         140665870 owf($ssdl, <
4181             [Unit]
4182             Description=Http to unix domain socket server
4183              
4184             [Service]
4185             Type=forking
4186             ExecStart=/usr/lib/cgi-bin/$name/server.pl
4187             User=$user
4188              
4189             [Install]
4190             WantedBy=multi-user.target
4191             END
4192             # setPermissionsForFile($ssdl, q(ugo=rx));
4193 0         0 setPermissionsForFile($ssdl, q(ugo=r)); # Permissions will be copied to server if the file does not exist on the server
4194              
4195 2672943         14928267 my $server = join '', <
4196             #!/usr/bin/perl $perlParameters
4197             END
4198             <<'END';
4199             #-------------------------------------------------------------------------------
4200             # Http to unix domain socket server
4201             #-------------------------------------------------------------------------------
4202             use warnings FATAL => qw(all);
4203             use strict;
4204             use Carp;
4205             use Data::Dump qw(dump);
4206             use Data::Table::Text qw(:all);
4207             use utf8;
4208             use feature qw(say current_sub);
4209              
4210             makeDieConfess;
4211              
4212             # Server code which should contain a sub genResponse($hash) which returns the response to be sent to the client
4213            
4214              
4215             my $parms = newUdsr();
4216              
4217             $parms->serverAction = sub # Perform server action
4218             {my ($c) = @_; # Communicator
4219             my $parms = $c->read; # Parameter string from client
4220             my $data = $parms ? eval $parms : undef; # Decode parameter string
4221             $@ and confess "Unable to decode webUser request:\n$parms\n"; # Complain about parameter string
4222             my $resp = genResponse($data); # Execute server action and capture returned value
4223             $c->write($resp); # Write back to the client
4224             };
4225              
4226             unlink $parms->socketPath;
4227             newUdsrServer(%$parms);
4228             END
4229 0         0 $server =~ s() ($parms)s;
4230 0         0 $server =~ s() ($code)s;
4231 110069         18374508 owf($cgsl, $server);
4232 55930         542094 setPermissionsForFile($cgsl, q(ugo=rx));
4233              
4234 0         0 my $client = <<'END';
4235             #!/usr/bin/perl -I/home/phil/perl/cpan/DataTableText/lib/
4236             #-------------------------------------------------------------------------------
4237             # Http to unix domain socket client
4238             #-------------------------------------------------------------------------------
4239             use warnings FATAL => qw(all);
4240             use strict;
4241             use Carp;
4242             use Data::Dump qw(dump);
4243             use Data::Table::Text qw(:all);
4244             use CGI;
4245             use utf8;
4246             use feature qw(say current_sub);
4247              
4248             makeDieConfess;
4249              
4250             my $cgi = CGI->new;
4251              
4252             my %v = $cgi->Vars;
4253             if (my $j = $cgi->param(q(POSTDATA))) # Load POST data
4254             {$v{POSTDATA} = $j;
4255             if (my $p = decodeJson($j))
4256             {if (ref($p) =~ m(hash)i)
4257             {%v = (%v, %$p);
4258             }
4259             }
4260             }
4261             #for my $k(keys %v)
4262             # {$v{$k} = wwwDecode($v{$k}) // q();
4263             # }
4264              
4265             my $parms = newUdsr();
4266             my $c = newUdsrClient(%$parms);
4267             say $c->read($c->write(dump({%v})));
4268             END
4269 0         0 $client =~ s() ($parms)s;
4270 55156         490971 owf($cgcl, $client);
4271 0         0 setPermissionsForFile($cgcl, q(ugo=rx));
4272              
4273 0         0 owf($inst, <
4274             sudo rm $ssdt $cgst $cgct
4275             sudo mkdir -p $cgif
4276             sudo cp $ssdl $ssdt
4277             sudo cp $cgsl $cgst
4278             sudo cp $cgcl $cgct
4279             sudo systemctl daemon-reload; sudo systemctl enable $name; sudo systemctl restart $name; sudo systemctl status $name
4280             END
4281              
4282 0         0 setPermissionsForFile $inst, q(u+x);
4283              
4284             # if (!$noInstall) # Install on server if available
4285             # {copyFolderToRemote($folder); # Copy code created locally to remote server
4286             # xxxr(qq(bash -x $inst)); # Install system by executing install procedure remotely
4287             # }
4288              
4289 0         0 lll <
4290             See status with:
4291              
4292             sudo systemctl status $name
4293              
4294             Install with:
4295              
4296             $inst
4297              
4298             Remove with:
4299              
4300             sudo rm $ssdt $cgst $cgct
4301              
4302             Access via:
4303              
4304             http://localhost/cgi-bin/$name/client.pl
4305              
4306             END
4307              
4308 0         0 $inst # Install script
4309             }
4310              
4311             #D2 www # Web processing
4312              
4313 0     0 1 0 sub wwwHeader {say STDOUT qq(Content-Type: text/html;charset=UTF-8\n\n)} # Html header.
4314              
4315             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.
4316 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
4317              
4318 0 0       0 if (!$code) # Show logon page if no code has been supplied
4319 0         0 {my $r = rand =~ s(\A0.) ()r;
4320 0         0 say STDOUT <
4321            
4322            
4323            
4324            

Logon with GitHub

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

Input:)gsi;

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

Output:)gsi;

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

Return:)gsi;

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

Parameters:)gsi;

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

Returns:)gsi;

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

Table of contents

7671            

7672             END
7673             for my $class(sort keys $d->parameters->%*) # Table of contents
7674             {my $comment = formatDocString classComment($class);
7675             my $m = stringMd5Sum $class;
7676             push @h, <
7677            
$class$comment
7678             END
7679             }
7680             push @h, <
7681            
7682             END
7683              
7684             for my $class(sort keys $d->parameters->%*) # Each class
7685             {my $comment = formatDocString classComment $class;
7686             my $m = stringMd5Sum $class;
7687             push @h, <
7688            

Class: $class

7689            

$comment

7690             END
7691              
7692             for my $defn(sort keys $d->parameters->{$class}->%*) # Each class method
7693             {my $comment = formatDocString $d->comments->{$class}{$defn};
7694             my $title = $defn;
7695             my $shortOp = $defn =~ s(_) ()gr;
7696             if (my $op = $opCodes{$shortOp})
7697             {$title .= " **$op**" unless $op eq $shortOp;
7698             }
7699             push @h, trim <
7700            

$title

7701             $comment
7702            

Parameters

7703            
7704            
NameDescription
7705             END
7706             if (my $parameters = $d->parameters->{$class}{$defn}) # Parameters
7707             {for my $p(@$parameters)
7708             {my ($n, $c) = (@$p, (q()) x 2);
7709             push @h, <
7710            
$n$c
7711             END
7712             }
7713             }
7714             push @h, <
7715            
7716             END
7717             my $examples = join "\n", map {nws $_} $d->tests->{$class}{$defn}->@*;
7718              
7719             push @h, trim <
7720            

Examples

7721            
 
7722             $examples
7723            
7724            
7725             END
7726             }
7727             }
7728              
7729             if (my $errors = $d->errors) # Errors by source file
7730             {push @h, q(

Possible improvements to documentation

);
7731              
7732             for my $file(sort keys %$errors) # Each file with errors
7733             {push @h, <
7734            

$file

7735            

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

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

Hello

)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
10808              
10809             overWritePerlCgiFile(q(gen.pl), q(...));
10810            
10811              
10812             =head3 overWritePerlCgiFile($file, $data)
10813              
10814             Write a L file to /usr/lib/cgi-bin and make it executable after checking it for syntax errors.
10815              
10816             Parameter Description
10817             1 $file Target file relative to /var/www/html
10818             2 $data Data to write
10819              
10820             B
10821              
10822              
10823             overWriteHtmlFile (q(index.html), q(

Hello

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

HelloWorld

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

Sample html table

13097            
13098            

Head 2 rows

13099            
13100            

13101            
13102            
sourcetarget
13103            
1a
13104            
2b
13105            

13106            
13107            

 
13108             source The source number
13109             target The target letter
13110            
13111            

13112            
13113            

Footer

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

Hello World to you $user on $dtts!

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

Chapter 1

18176            

Section 1

18177            

Chapter 2

18178             XXXX
18179             END
18180            
18181             eq nws(<
18182            

Chapter 1

18183            

Section 1

18184            

Chapter 2

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