A SCUA tool's output is now safe for another program to parse. A log
line can no longer land in the middle of a result and break whatever is
reading it: standard output is the answer, standard error is everything
else. info, warn and severe write
to stderr; print and sys.write stay on stdout.
That is one changed default, and it is a breaking change, so read the
upgrade section before you install. Alongside it:
--output=json for a stdout that carries the result and
nothing else, sys.write_err for raw bytes on stderr,
print that reaches a pipe as it happens, a
sys.parse_args that no longer eats a shell loop's list, and
a scua fmt fix.
#Log lines now go to standard error
warn("cache is cold")
print("42")
$ scua tool.scua 2>/dev/null
42
The caller of a SCUA tool is increasingly a program: a shell pipeline, a CI step, an AI agent. All of them parse what comes out of stdout, and to a parser a log line in the middle of the payload is not a warning, it is a syntax error. Diagnostics belong on the stream that has always existed for them.
#A result channel for tools
--output=json makes the guarantee explicit.
sys.emit writes the result, and nothing else reaches
stdout: print, sys.write and log lines all
divert to stderr. A tool can print progress through a long job and its
caller still gets one clean JSON document.
import sys
print("scanning")
warn("cache is cold")
sys.emit({ answer = 42 })
$ scua tool.scua --output=json 2>/dev/null
{"answer":42}
The progress line and the warning are not lost, they are on stderr:
$ scua tool.scua --output=json 2>&1 >/dev/null
scanning
[warn] cache is cold
It is a flag on the run, not a declaration in the script, because the caller is the one who knows it intends to parse the output. Run it by hand and it reads as before.
#Raw bytes on standard error
sys.write_err(s) is sys.write for stderr:
raw bytes, no trailing newline, no [level] prefix. Until
now the only ways onto stderr were a log level, which stamps
[warn] on the front, or sys.fail, which exits.
A progress bar or a status line fitted neither.
#print reaches a pipe as it happens
A script that printed a line and then computed for ten seconds used
to show nothing until it paused or exited, so a long job with progress
lines looked hung. Output is now handed over once it is about a
millisecond old, or sooner once enough builds up. A tight loop printing
thousands of lines still batches, so nothing got slower. Terminal, pipe
or file, it is the same. SCUA has no equivalent of Python's
-u.
#A tool in a loop keeps the loop
while read f; do mytool "$f"; done < files.txt
sys.parse_args used to read stdin to end-of-file the
moment it was called, before looking at the arguments. In that loop the
first run swallowed the rest of the list. It then usually exited 2
complaining about bad JSON on stdin for a payload you never supplied; if
the leftover happened to parse as a JSON object, it exited 0 and
processed one file. Neither told you the list was gone.
It now reads stdin only when a JSON body could still supply
something: nothing on the command line at all, or a field with no
default still unfilled after flags and positional arguments.
--help and an unrecognised option answer without touching
stdin.
#Upgrading: the one breaking change
Log lines move from stdout to stderr. Two things to check.
If you pass --log-stderr, remove it. It
described what is now the default, so it is gone. A run that still
passes it fails with scua: --log-stderr: cannot read file
and exit status 2: the flag is no longer recognised, so it is read as
the name of the script to run. If you see that message, this is why.
If anything reads log lines off stdout, point it at
stderr. A wrapper grepping for [warn], a CI step,
a log collector. The new --log-stdout puts them back on
stdout while you migrate, and 2>&1 merges the
streams in written order.
#Also in this release
scua fmt no longer mis-indents everything after a table
key or record field named like a keyword: on = 2,
then = 3, a field do: int, a member access
like spec.then. Each was counted as opening a block nothing
closed, so every following line gained two spaces. As a backstop, the
formatter now refuses and writes nothing if it reaches end of file with
a block still open. Its indentation rule is in the manual for the first
time.
The full changelog has the detail.