Package: clii
cliiGenerate argument parsers from Python 3 function annotations with minimal
boilerplate.#!/usr/bin/env python3.8fromcliiimportAppfrompathlibimportPathfromsubprocessimportruncli=App()@cli.cmddefadd(a:int,b:int=3):print(a+b)@cli.cmddefsubtract(path:Path):run(f'rm -rf{path}')if__name__=='__main__':cli.run()No dependencies.This library has no dependencies and is a single file.
You wanna vendor it? Vendor it.Short implementation.Take 10 minutes, skim the implementation, convince
yourself I'm not exfiltrating yourid_rsa, then vendor this puppy and never
think about anything again.Nothing to learn.if you know how to use Python function annotations, you
already know 98% of this library.Optimized for the common case.Check outtest_bad_git.py. I know what
you want to do (create a subpar reproduction of git), and I've made it
concise.Okay, you and I both know the last thing that anyone needs is another way to
generate command line interfaces. The idea of adding an additional dependency
to your project just so you can learn yet another
only-slightly-more-ergonomic-than-stdlib interface for parsing args is right up
there with rewriting all your Makefiles in whatever flavor-of-the-week
Javascript-based build system. I get it.Yes, instead of writing this library I should probably do something actually
useful like try to find a life partner or see how much grain alcohol I can
drink within the span of an X-Files episode, but each time I'm typing out some
overly verboseargparseincantation that I had to look up on docs.python.org
for the sixteenth time in a year, one of the few remaining shreds of childlike
wonder for computing left in my over-caffeinated heart gets crosslegged and
sets itself on fire.Clickis the equivalent of calling
in an architect to fix your kitchen sink. It's a lot of code and the interface
is wordy and unintuitive.Docoptis neat
but it's slow, a novelty, also a ton of code, and I have to read 3 examples
each time before I use it.Argparseis an alright
builtin, and the noble progenitor of this library, but it's overly verbose and
the common task of wiring up subparsers that call functions is a pain.Don't immolate your childlike wonder. Use function annotations. Use this
stupid library.Installation# Requires Python >=3.7python3-mpipinstall--usercliiSubstantial usage example#!/usr/bin/env python3.8"""A really lame version of git."""frompathlibimportPathimporttypingastfromcliiimportAppcli=App(description=__doc__)cli.add_arg('--verbose','-v',action='store_true',default=False)@cli.cmddefclone(url:str,target:Path,branch:t.Optional[str]=None):"""Clone the branch so you can melt your computer."""branch=f' -b{branch}'ifbranchelse''# We can reference global args since all parsed arguments are attached# to `cli.args` after parsing.ifcli.args.verbose:print(f'git clone{branch}{url}{target}')@cli.cmddefpush(remote:str,branch:str,force:bool=False):force_flag=' -f'ifforceelse''ifcli.args.verbose:print(f'git push{force_flag}{remote}{branch}')@cli.cmd@cli.arg('all','-a')# add a short flag, or any other argparse setting@cli.arg('message','-m')defcommit(all:bool=False,message:str=''):# Arguments are --all, -a and --message, -mprint(all)print(message)@cli.cmd@cli.arg('updated','-u')defadd(*files,updated:bool=False):# `files` will be a variadic positional arg, while --updated/-u is a bool# flag.ifcli.args.verbose:print(f"adding files:{files}")if__name__=='__main__':cli.run()which then gets you% ./test_bad_git.py --help
usage: test_bad_git.py [-h] [--verbose] {clone,push,commit,add} ...

A really lame version of git.

positional arguments:
  {clone,push,commit,add}

optional arguments:
  -h, --help            show this help message and exit
  --verbose, -v

% ./test_bad_git.py clone --help
usage: test_bad_git.py clone [-h] [--branch BRANCH] url target

Clone the branch so you can melt your computer.

positional arguments:
  url
  target

optional arguments:
  -h, --help       show this help message and exit
  --branch BRANCH  default: NoneUsage notesHelp text from docstringsclii will pull argument help text from docstrings that are formatted like so:importcliicli=clii.App()@cli.cmddeffoo(bar:str):"""Args:bar: some kind of helpful docstring."""cli.run()Specifically, the docstring is searched for "  [parameter name]:" - if that
pattern is found, the contents after the colon are used as help text.store_trueandstore_falseinferenceArguments that are declared type bool and given a default value are inferred
as beingstore_trueorstore_falsedepending upon their default value;
it is inferred that if the flag is given on the commandline, the reverse of
the default is desired.For example, in@cli.cmddefcommit(force:bool=False):...if--forceis given, the function will be called withforce=True, but
otherwiseforcewill stay False.If you like using this library, consider sending me some magic internet money:bc1qlj36t63qlgkywg83gwslcu3ehl76h2k2d6hcv2