Skip to main content

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:

  1. Subcommand structure: It's gotta be cosmofy bundle and cosmofy self update
  2. Colored output: Gotta auto-detect that stuff. Luckily, I had fun with brush years ago, so I know about terminal color codes.
  3. Global flags: Some of those flags gotta be global.
  4. 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:

  1. I really don't want 3rd party dependencies (even my own). cosmofy needs to stay tight and small.
  2. I want argument parsing to go until it hits the subcommand and then delegate the rest of the args to the subcommand parser.
  3. 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.