Tutorial¶
Contents
Part I: Say¶
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.
Positional arguments¶
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.
Flags¶
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.
Flags with values¶
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
.
More Interesting Flag Types¶
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.
Part II: Calc¶
(Details TBD!)
With echo
having met its match,
we are on to bigger and better:
this time,
with math
$ num
<Big help text>
Precision support¶
$ 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.
Part III: Middleware¶
(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
Part IV: Examples¶
There are more realistic examples of face usage out there, that can serve as a reference.
Cut MP4¶
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¶
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¶
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¶
Montage Admin Tools is a larger application. It has nested subcommands and a database connection. It is used to administer a web application.