Contents
The field of overdone versions of echo
has been too long dominated by Big GNU. Today, we start taking back the power. We will implement the say
command.
While face offers a Parser interface underneath, the canonical way to create even the simplest CLI is with the Command object.
To demonstrate, we'll start with the basics, positional arguments. say hello world
should print hello world
:
from face import Command, echo
def main():
cmd = Command(say, posargs=True) # posargs=True means we accept positional arguments
cmd.run()
def say(posargs_): # positional arguments are passed through the posargs_ parameter
echo(' '.join(posargs_)) # our business logic
if __name__ == '__main__': # standard fare Python: https://stackoverflow.com/questions/419163
main()
A basic Command takes a single function entrypoint, in our case, the say
function.
Note
Face's echo
function is a version of print
with improved options and handling of console states, ideal for CLIs.
Let's give say
some options:
say --upper hello world
or say -U hello world
should print HELLO WORLD
.
...
def main():
cmd = Command(say, posargs=True)
cmd.add('--upper', char='-U', parse_as=True, doc='uppercase all output')
cmd.run()
def say(posargs_, upper): # our --upper flag is bound to the upper parameter
args = posargs_
if upper:
args = [a.upper() for a in args]
echo(' '.join(args))
...
The parse_as
keyword argument being set to True
means that the presence of the flag results in the True
value itself. As we'll see below, flags can take arguments, too.
Let's add more flags, this time ones that take values.
say --separator . hello world
will print hello.world
. Likewise, say --count 2 hello world
will repeat it twice: hello world hello world
...
def main():
cmd = Command(say, posargs=True)
cmd.add('--upper', char='-U', parse_as=True, doc='uppercase all output')
cmd.add('--separator', missing=' ', doc='text to put between arguments')
cmd.add('--count', parse_as=int, missing=1, doc='how many times to repeat')
cmd.run()
def say(posargs_, upper, separator, count):
args = posargs_ * count
if upper:
args = [a.upper() for a in args]
echo(separator.join(args))
...
Now we can see that parse_as
:
- Can take a value (e.g.,
True
), which make the flag no-argument- Can take a callable (e.g.,
int
), which is used to convert the single argument- Defaults to
str
(as used byseparator
)
We can also see the missing
keyword argument, which specifies the value to be passed to the Command's handler function if the flag is absent. Without this, None
is passed.
Note
Face also supports required flags, though they are not an ideal CLI UX best practice. Simply set missing
to face.ERROR
.
say --multi-separator=@,# hello wonderful world
prints hello@wonderful#world
(The separators repeat)
say --from-file=fname
reads the file and adds all words from it to its output
say --animal=dog|cat|cow
will prepend "woof", "meow", or "moo" respectively.
(Details TBD!)
With echo
having met its match, we are on to bigger and better: this time, with math
$ num
<Big help text>
$ num add 1 2
3
$ num mul 3 5
15
$ num sub 10 5
5
$ num sub 5 10
Error: can't substract
$ num --allow-negatives 5 10
-5
$ num div 2 3
0.6666666666666666
$ num div --int 2 3
0
$ num add 0.1 0.2
0.30000000000000004
$ num add --precision=3 0.1 0.2
0.3
Oh, now let's add it to all subcommands.
(Details TBD!)
Doing math locally is all well and good, but sometimes we need to use the web.
We will add an "expression" sub-command to num that uses https://api.mathjs.org/v4/
. But since we want to unit test it, we will create the httpx.Client
in a middleware.
$ num expression "1 + (2 * 3)"
7
But we can also write a unit test that does not touch the web:
$ pytest test_num.py
There are more realistic examples of face usage out there, that can serve as a reference.
The script cut_mp4 is a quick but useful tool to cut recordings using ffmpeg
. I use it to slice and dice the Python meetup recordings. It does not have subcommands or middleware, just a few flags.
Glom is a command-line interface front end for the glom
library. It does not have any subcommands, but does have some middleware usage.
Pocket Protector is a secrets management tool. It is a medium-sized application with quite a few subcommands for manipulating a YAML file.
Montage Admin Tools is a larger application. It has nested subcommands and a database connection. It is used to administer a web application.