Revision history for SimpleFlow
0.16 2026-08-28 (Claude Opus 5 helped)
[Fixed]
- **`die => 0` never reported a failure.** The `will.do => "FAILED"` assignment
sat inside the `if ($r{die})` branch, so it could only run on the path that
immediately died. Under `die => 0` — the mode in which the caller is meant
to read `will.do` — a command that exited non-zero was reported as `"done"`,
and nothing warned. `will.do` is now `"FAILED"` for a non-zero exit, a
timeout, or a missing output file regardless of `die`, and `die => 0` emits
a warning naming the exit code.
- **The log lost the record of the task that killed the run.** The log
filehandle was never autoflushed. Measured with a `SIGKILL` part-way through
a pipeline (the shape of an OOM kill or a scheduler eviction), a log holding
862 bytes on a clean exit held 139 bytes after the kill: everything written
after the last command started — its exit code, duration and captured output
— was still in stdio's buffer. `task` and `say2` now switch the handle to
autoflush.
- **An undefined filename still crashed.** 0.14 added a `defined` guard to the
0-length check, but the `-f -r` filetest ran first, so an `undef` element of
an `input.files` array died as `Use of uninitialized value $_ in -r` under
`warnings FATAL => 'all'`. Names are now validated before anything is
filetested.
- **The 0-length `input.files` check was unreachable.** `''` fails `-f`, so an
empty input filename was reported as `"missing or unreadable"` and the
0-length check below it could never fire. Both undefined and 0-length names
are now reported as what they are, and the message names the offending index.
- **`cmd` was not type-checked.** Only definedness was checked, so any reference
was stringified straight into the shell: `task(cmd => ['echo','hi'])` ran the
literal command `ARRAY(0x5ed9d076e618)`. `cmd` must now be a non-empty string
or a non-empty array ref of defined values.
- **Skip detection and the post-run check disagreed.** Skipping tested a bare
`-f` while the post-run check tested `-f -r`, so an output file that existed
but could not be read counted as already done. Both use `-f -r` now.
- **The result record changed shape between paths.** `exit`, `signal`, `stdout`
and `stderr` were absent after a skip or a dry run, so a caller running under
the `warnings FATAL => 'all'` this module recommends died just by reading
`$t->{'exit'}`. They are now always present, holding their empty values.
- **`string_max` was uncapped**, so a chatty command had its whole capture echoed
to the terminal and written to the log — a measured 3 MB stdout wrote
3,002,832 bytes to each. It is now capped at 4096 characters; Data::Printer
marks what it drops. The full capture is still on the result hash.
- **Loading SimpleFlow polluted `main::`.** `use DDP` and `use Cwd 'getcwd'` sat
above the `package` statement, so `p`, `np` and `getcwd` were imported into
every program that loaded the module. The `package` statement now comes
first, and the duplicated `use` lines are gone.
- **Unbalanced parenthesis** in the 0-length `output.files` error message.
[Added]
- **`stale`**: also re-run when an input file is newer than an output file, the
rule `make` and `snakemake` use. Off by default, so existing pipelines are
unaffected. The result carries `out.of.date`.
- **`timeout`**: a wall-clock budget in whole seconds. The command runs in its
own process group and the whole group is killed if the budget is exceeded,
so a wedged pipeline does not leave orphans behind. The result carries
`timed.out`. POSIX only.
- **An array-ref `cmd`** runs the command without a shell, so arguments coming
from data need no quoting.
- **`quiet`**: suppress the record printed to the terminal without silencing the
log or `STDERR`.
- **`input.file`**, the single-file convenience form of `input.files`, matching
`output.file`.
[Changed]
- `$VERSION` is now a quoted string. As a bare number it was stringified through
`%g`, so a future `0.20` would have become `"0.2"` and compared as older than
`"0.15"` on CPAN.
- **Incompatible:** `input.files` on the result is now always an array ref, as
`output.files` always was. A scalar argument used to be stored raw.
- `POSIX` (core) is now a dependency, for `_exit` in the timeout child.
0.15 2026-07-17 (Claude Opus 4.8 helped)
- addition of `output.file`, a single-file convenience form of `output.files`. It
takes one plain filename, cannot be combined with `output.files`, and dies if
given a reference or an empty name.
- removal of Term::ANSIColor dependency
- improved coverage testing
0.14 2026-06-29 (Claude Opus 4.8 helped)
[`task`]
- **New:** accepts a flat key/value list as well as a hash ref —
`task(cmd => ...)` and `task( cmd => ... )` are now equivalent. A lone
non-hashref scalar or any odd-length argument list is fatal.
- **Bug fix:** the default `die => 1` was ignored when checking for missing
`output.files`. The block tested the raw `$args->{'die'}` (undef when the
caller omitted it) instead of the resolved `$r{'die'}`, so a command that
failed to produce its declared outputs only warned instead of dying. Now
consistent with the exit-code check.
- **Bug fix:** removed a stray `)` (and an extraneous leading space) from the
"command is" line written to the log file; it now matches the on-screen form.
- **Bug fix:** `length $_ == 0` could throw a fatal uninitialized-value warning
(under `warnings FATAL => 'all'`) on an undef element of the `input.files`
array branch and the `output.files` empty-name check. Both now guard with
`(defined $_) && (length $_ == 0)`, matching the `input.files` scalar branch.
0.13 2026-06-11
[Fixed (Claude Opus 4.8 helped)]
- **Exit status and signal are now decoded correctly.** `task()` previously
computed the exit code (`$status >> 8`) and *then* derived the signal as
`$exit & 127`. Because the signal lives in the low byte of the raw wait
status, which `>> 8` discards the `signal` field was always wrong: a clean
`exit 42` was reported as `signal 42`, and a process actually killed by a
signal reported `signal 0`. The signal is now read from the raw status before
shifting, so `exit` and `signal` are independent and accurate.
- **No longer dies on a missing output file when `die => 0`.** The zero-size
check did `(-s $file) == 0`, which is `undef == 0` when a declared output file
is absent. Under `use warnings FATAL => 'all'` that "uninitialized value"
warning was fatal, so a task that was meant to *warn* about missing output
(with `die => 0`) crashed instead. Missing sizes are now treated as `0`, so
the task warns and returns its result hash as intended.
- **The "already done" result is now logged with its `duration`.** In the
short-circuit path (output files already exist), `duration` was set *after*
the record was written to the log, so the logged hash was missing it; the
duplicate `done => 'before'` assignment was also removed.
[Changed / Windows support]
- **Portable exit-status handling.** Decoding now branches on `$^O`: Windows has
no POSIX signals (`signal` is reported as `0` there), and a `system()` that
fails to launch the command (`-1`) yields `exit => -1` instead of a garbage
value from shifting `-1`.
- **ANSI colour is disabled on the legacy Windows console.** `Term::ANSIColor`
output is suppressed on `MSWin32` unless an ANSI-capable terminal is detected
(Windows Terminal, ConEmu, or ANSICON), so `cmd.exe` no longer prints raw
escape sequences and redirected logs stay clean. Unix and modern Windows
terminals are unaffected.
[Tests]
- Rewrote `t/01.t` to be cross-platform: shell commands now invoke the running
Perl interpreter (`"$^X" -e ...`) instead of Unix-only tools (`which`, `ls`,
`ln`, `cp`), and temp files use the system temp directory instead of a
hard-coded `/tmp`.
- Added regression tests for both fixed bugs (exit/signal decoding; surviving a
missing output file with `die => 0`).
- Added coverage for the `note` field, the `input.file.size` / `output.file.size`
hashes, scalar-vs-array normalisation of `input.files` / `output.files`, the
`dir` / `source.file` / `source.line` metadata, captured `stdout` / `stderr`
(including trailing-whitespace stripping), and argument validation (missing
`cmd`, unknown keys, bad `log.fh`, missing input files).
0.12 2026-02-14
- exit code now matches what shell would show it as; signal now appears
0.11 2026-01-13
- max string length now corresponds to max of output strings, no more truncated output
added List::Util dependency for string length maxes
memory size now shows when output
directory is now output during dry runs