gitlab.com/zygoon/go-cmdr v1.9.0 has been released
August 26, 2023•487 words
I've just released go-cmdr version 1.9.0
This version brings in several small improvements and one deprecation I wanted to explain below.
Update: most of the new functionality is in: https://pkg.go.dev/gitlab.com/zygoon/go-cmdr@v1.9.0/format?tab=versions and https://pkg.go.dev/gitlab.com/zygoon/go-cmdr@v1.9.0/cmdtest?tab=versions
Prepared test invocation
The testcmd
package, which simplifies testing command line applications, has gained support for replacing standard input stream. The existing cmdtest.Invoke
function has been generalized to cmdtest.Prepare
, which returns an Invocation
object that can be further configured before being invoked with cmdtest.Invocation.RunPrepared
. If you had been using the lower-level interface for providing replacement stdin, you should take advantage of this functionality to reduce the number of lines of boiler-plate code you have to maintain. You can look a sample unit tests that shows stdin replacement in action: https://gitlab.com/zygoon/go-cmdr/-/blob/main/cmdtest/cmdtest_test.go?ref_type=heads#L178 but the essence is just:
inv := cmdtest.Prepare(cmd)
if _, err := inv.Stdin.WriteString("potato"); err != nil {
t.Fatal(err)
}
if err := inv.RunPrepared(); err != nil {
t.Fatal(err)
}
Output formats
I've previously introduced OUTPUT_FORMAT=
environment variable, which allows changing the format of the data printed to standard output. The go-cmdr library came with support for plain text, shell-variables and JSON output formats that application authors could integrate into their codebase.
The key operation was to select an output format and obtain a format-specific printer. In code it looks like this:
p, err := format.Negotiate(stdout, args, format.PlainTextSupport, format.ShellCompatSupport)
switch p := p.(type) {
case *format.PlainPrinter:
p.Println("Hello human")
case *format.ShellPrinter:
p.PrintSetVar("GREETING", "Hello automaton")
default:
panic("WAT?")
}
This worked fine but had some gotchas:
You have to type-switch on an
any
value. The type cases had to be exactly right*format.PlainPrinter
works butformat.PlainPrinter
- which compiles silently - would just panic, hitting theWAT?
default case.Each format has a pair of types the format support indicator and the format printer. This is a necessary internal mechanic but it surely makes using formats more complex than they could be.
Version 1.9.0 deprecates format.Negotiate
, introducing format.NegotiateCallback
as the superior replacement.
Let's look at how our code sample looks like in the new scheme:
err := format.NegotiateCallback(ctx, stdout, args,
format.PlainText(func(ctx context.Context, p *format.PlainPrinter) error {
p.Println("Hello human")
return nil
}),
format.Shell(func(ctx context.Context, p *format.ShellPrinter) error {
return p.PrintSetVar("GREETING", "Hello automaton")
}),
))
As we can see the type switch and panic are gone. If we get any of the pointer types wrong the code no longer compiles. In retrospective I didn't realize how fragile the earlier type-switch was. I have decided to address this and introduce NegotiateCallback
after hitting this in an untested branch of an application I was working on.
If the author of a library cannot use it correctly, it is a good sign the design needs improvement.
Old functionality remains in place, as I'm strongly against breaking backwards compatibility. You can keep using it, but consider migrating to the new symbol. I've marked is as deprecated to discourage new applications from using it by accident.