baton: emulating Astral's CLI approach #
How much of the Astral CLI theme can I emulate in Python?
Previously: pythonoid
Imitation is the highest form of flattery which is why as part of the cosmofy 0.2.0 release, I decided to change everything about how the CLI behaved to make it work more like the way the tools from Astral work.
I have a long-term plan for Astral to take over making Cosmopolitan Python apps. It's a long shot, but if they do, it'll be a huge win for cross-platform compatible executables. I also saw this popular issue that there should be a uv bundle command that bundles everything up.
To make it easier to adopt, I decided to make the interface follow Astral's style in three important ways:
- Subcommand structure: It's gotta be
cosmofy bundleandcosmofy self update - Colored output: Gotta auto-detect that stuff. Luckily, I had fun with brush years ago, so I know about terminal color codes.
- Global flags: Some of those flags gotta be global.
- Smart ENV defaults: smart defaults + pulling from environment variables to override.
Now I didn't start out wanting to build my own argument parser (really, I promise I didn't!). I tried going the argparse route (I even tried my own attrbox / docopt solution), but I had a few constraints:
- I really don't want 3rd party dependencies (even my own).
cosmofyneeds to stay tight and small. - I want argument parsing to go until it hits the subcommand and then delegate the rest of the args to the subcommand parser.
- I want to pass global options from parent to child sub-parser as needed.
Together these pushed for a dedicated parser. This lets me write things like:
usage = f"""\
Print contents of a file within a Cosmopolitan bundle.
Usage: cosmofy fs cat <BUNDLE> <FILE>... [OPTIONS]
Arguments:
{common_args}
<FILE>... one or more file patterns to show
tip: Use `--` to separate options from filenames that start with `-`
Example: cosmofy fs cat bundle.zip -- -weird-filename.txt
Options:
-p, --prompt prompt for a decryption password
{global_options}
"""
@dataclass
class Args(CommonArgs):
__doc__ = usage
file: list[str] = arg(list, positional=True, required=True)
prompt: bool = arg(False, short="-p")
...
def run(args: Args) -> int:
...
cmd = Command("cosmofy.fs.cat", Args, run)
if __name__ == "__main__":
sys.exit(cmd.main())
For the colored output, I took inspiration from William McGuan's rich which uses tag-like indicators to style text.
Mine is much worse and minimal, but it gets the job done for the bits of color I need.