NAME
Util::Con - Small everyday helpers: directory listing, shell commands, JSON files, number formatting, and data-structure shapes
Synopsis
use Util::Con;
my @scripts = dir(dir => '.', regex => '\.pl$', recursive => 1);
my $branch = execute('git rev-parse --abbrev-ref HEAD', 'stdout');
my $text = file2string('notes.txt');
print format_commas(1234567.891), "\n"; # 1,234,567.89
my $data = json_file_to_ref('results.json');
ref_to_json_file($data, 'copy.json');
st $data; # print the shape of $data, not its values
p $data; # Data::Printer's p(), loaded on first use
Description
Util::Con is a collection of the functions I reach for in nearly every script: listing a directory with file tests and a regex, running a shell command and keeping its output, slurping a file, reading and writing JSON, putting commas in a number, and seeing the shape of a large data structure without scrolling through every value in it.
Loading it is meant to be cheap. Everything that is not needed to compile the caller is loaded the first time a function needs it, so a script that only calls format_commas does not pay for JSON::MaybeXS, Capture::Tiny or Data::Printer.
What use Util::Con does
Besides exporting the ten functions below, use Util::Con does three things to the file and package that loads it:
It turns on
autodie ':default'in that file, from theuseline onward, so a bareopenorclosethat fails dies with a message rather than returning false.It gives that package
pandnpfromData::Printer(DDP). They are stubs until first called, soDDPis loaded only by a script that prints something. Apornpthe package already has, such as one from an earlieruse DDP, is left alone.It loads
Devel::Confess, which is process-wide: everydieandwarnin the program carries a stack trace.
use Util::Con (); imports nothing and skips the first two; Devel::Confess is still loaded. use Util::Con 'format_commas'; imports only the names given, and still does all three.
Functions
All functions are exported by default.
dir
my @files = dir(dir => '/data', regex => '\.csv$');
my @tree = dir(dir => '/data', regex => '\.csv$', recursive => 1, cpu => 8);
dir(dir => '/data', recursive => 1, on_match => sub { say $_[0] });
Returns the paths under dir whose base name matches regex and that pass every file test in tests. The paths are dir joined to the entry with /; with the default dir => '.', the leading ./ is left off. . and .. are never returned.
It takes these named arguments:
dir(default.): The directory to list. Trailing slashes are removed, but/stays/.regex(default.): A pattern the base name must match, given as a string or aqr//. An invalid pattern dies, naming it.tests(defaultf): One or more of perl's file tests, as letters:rwxoRWXOezsfdlpSbcugkTB. Every one must pass.'d'lists directories;''applies no test at all.recursive(default0):1descends into subdirectories, throughFile::Find, without following symbolic links.cpu(default1): For a recursive listing, the number of worker processes (throughParallel::ForkManager). Ignored whenrecursiveis0.on_match(no default): A code reference called with each path as it is found;dirthen returns an empty list, so a very large tree is never held in memory.
The file tests share one stat per path, so asking for four of them costs no more than asking for one. -l is checked first, with lstat.
With cpu greater than 1, dir walks the tree breadth-first until one level holds four directories per worker, then shares those directories out. The results are the same as a serial walk, in a different order. on_match still runs in the calling process, so it can change the caller's variables, but it is called once a worker's whole batch arrives rather than as each file is found.
An unknown argument, a letter that is not a file test, a cpu that is not a positive integer, and a directory that cannot be opened all die.
execute
my $exit = execute('make test'); # 0, or dies
my $out = execute('git log -1 --format=%H', 'stdout');
my $err = execute('perl -c script.pl', 'stderr');
my $all = execute('ls /nonexistent', 'all', 0); # does not die
# $all = { exit => 512, stdout => '', stderr => "ls: cannot access ..." }
Runs $cmd through the shell with system, capturing its standard output and standard error with Capture::Tiny. The second argument says what to return:
exit(the default): the wait status,$?-- 0 for success, 256 forexit 1stdout: standard output, chompedstderr: standard error, chompedall: a hash reference withexit,stdoutandstderr
The third argument, $die, is 1 by default: a command that exits non-zero prints its exit status, standard output and standard error to STDERR and dies. Pass 0 to have the status returned instead.
The command's standard input is the null device (/dev/null, or nul on Windows). A command that would prompt -- rm on a write-protected file, cp -i -- reads end-of-file and declines rather than waiting, unseen, for an answer. The caller's STDIN is restored afterwards.
file2string
my $text = file2string('notes.txt');
Returns the whole of a file as one string, read without any decoding layer. A file that cannot be opened dies, naming it.
format_commas
format_commas(1234567.891); # '1,234,567.89'
format_commas(1234567, '%.0u'); # '1,234,567'
format_commas(999999.999); # '1,000,000.00'
format_commas(-0.5); # '-0.50'
format_commas(1e20); # '100,000,000,000,000,000,000.00'
Puts a comma between each group of three digits of the integer part and appends the hundredths, rounded half up. The second argument is the sprintf format the hundredths are printed with, '.%02d' by default; '%.0u' prints nothing, for a whole number. Rounding that carries into the integer part does so (0.999 is 1.00), a negative number keeps its sign, a number that rounds to zero has none, and a large number is grouped as digits rather than printed in exponent form.
json_file_to_ref
my $ref = json_file_to_ref('results.json');
Reads a JSON file and returns the decoded data, through JSON::MaybeXS. Python's json module and NumPy write NaN, Infinity and -Infinity as bare words, which are not JSON; where one of those stands as a value it is read as null, so it becomes undef. The same words inside a string are left alone. A file that does not exist, or is empty, dies.
ref_to_json_file
ref_to_json_file($data, 'results.json'); # prints "Wrote results.json"
Writes a data structure to a file as JSON, through JSON::MaybeXS, prints Wrote and the file name, and returns the file name. The file name must be a plain string; a reference dies.
random_key, random_value
my $key = random_key(\%hash);
my $value = random_value(\%hash);
Return one key, or one value, of a hash -- whichever pair perl's hash iterator yields first, which is quick no matter how big the hash is. This is any key, not a randomly chosen one: the same hash returns the same pair each time. It is useful for looking at one record of a hash of records. An empty hash returns undef.
Both reset the hash's iterator, so do not call them on a hash in the middle of an each loop.
structure
my $text = structure($ref, output => 'string');
structure(\%config, max_depth => 2, as => 'config');
Prints the shape of a data structure rather than its values: p shows every element of a 50,000-element array of records, while structure merges them into one and shows the record once.
my @people = (
{ name => 'Ann', age => 31, langs => ['perl', 'R'] },
{ name => 'Bo', age => 28, langs => [], email => undef },
);
st @people;
prints
ARRAY[2]
[*]: HASH{4} (from 2 instances)
age: number
email?: null
langs: ARRAY[0-2] (from 2 instances)
[*]: string
name: string
A key that only some of the merged hashes have is marked ?. Values in the same place that are of different kinds are shown as a UNION of each kind. Arrays show the range of their lengths. Blessed references show their class. A reference back to a containing structure is shown as (cycle) rather than followed.
It takes these options after the reference:
output(defaultstdout):stdoutorstderrto print there;stringto print nothing. The text is returned in every case.color(defaultauto):1or0.autocolours the output when it goes to a terminal, or whenCLICOLOR_FORCEis set, unlessNO_COLORis set.max_depth(default0): How many levels to descend before printing(...);0is no limit.hash_max(default32): A hash with more keys than this is taken to be a lookup table, and its values are merged into one entry,<*>, rather than listed by key.0lists every key.as(no default): A line to print above the structure.
st
st %hash;
st @array;
st $ref, max_depth => 2;
structure, called the way p is called: the argument is a variable, not a reference to one, and the options follow it. As with p, it must be a variable -- st(\%h) and st(func()) do not compile; call structure for those.
Installation
cpanm Util::Con
or, from a copy of the repository:
perl Makefile.PL
make
make test
make install
Compatibility
Util::Con runs on perl 5.10.1 and later, and is tested there. execute hands its command to the shell, so the command has to be written for the shell of the system it runs on. On Windows, the paths dir returns are joined with /, which perl accepts there; recursive listings with cpu rely on Parallel::ForkManager's emulated fork.
Author
David E. Condon mailto:dec986@gmail.com
License
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.