Upgrade to Pro — share decks privately, control downloads, hide ads and more …

PyOhio 2016 - Let’s Make Better Command Line Applications

PyOhio 2016 - Let’s Make Better Command Line Applications

Dave Forgac

July 30, 2016
Tweet

More Decks by Dave Forgac

Other Decks in Technology

Transcript

  1. The Zen of Python >>> import this The Zen of

    Python, by Tim Peters Beautiful is better than ugly. Explicit is better than implicit. Simple is better than complex. Complex is better than complicated. Flat is better than nested. Sparse is better than dense. Readability counts. Special cases aren't special enough to break the rules. Although practicality beats purity. Errors should never pass silently. Unless explicitly silenced. In the face of ambiguity, refuse the temptation to guess. There should be one-- and preferably only one --obvious way to do it. Although that way may not be obvious at first unless you're Dutch. Now is better than never. Although never is often better than *right* now. If the implementation is hard to explain, it's a bad idea. If the implementation is easy to explain, it may be a good idea. Namespaces are one honking great idea -- let's do more of those!
  2. The Unix Philosophy Write programs that do one thing and

    do it well. Write programs to work together. Write programs to handle text streams, because that is a universal interface.
  3. Standard Streams Text terminal Keyboard Display Program ① stdin ②

    stdout ③ stderr Image by - Own work, Public Domain, ScotXW https://commons.wikimedia.org/w/index.php?curid=687268
  4. Redirection $ grep "Hello" * grep: example_directory: Is a directory

    example.txt:Hello, World! grep: protected-file.txt: Permission denied $ grep "Hello" * > matches.txt grep: example_directory: Is a directory grep: protected-file.txt: Permission denied $ cat matches.txt example.txt:Hello, World! $ grep "Hello" * > matches.txt 2> diagnostics.txt $ cat diagnostics.txt grep: example_directory: Is a directory grep: protected-file.txt: Permission denied
  5. Pipelines ls -l | grep example | less Image by

    - en:File:Pipeline.svg, Public Domain, TyIzaeL https://commons.wikimedia.org/w/index.php?curid=11524651
  6. A Good Python Command Line App ...'s design is informed

    by The Zen of Python and The Unix Philosophy.
  7. Execute or Import def cli(): pass # do stuff here

    if __name__ == '__main__': cli()
  8. hello_argparse.py import argparse def hello(): """ Argparse CLI to greet

    the provided NAME. """ parser = argparse.ArgumentParser() parser.add_argument('name', help="name of person to greet") parser.add_argument('-c', '--count', type=int, default=1, help="number of times to print the greeting") args = parser.parse_args() for i in xrange(args.count): print("Hello, {0}!".format(args.name))
  9. hello-argparse --help $ hello-argparse --help usage: hello-argparse [-h] [-c COUNT]

    name positional arguments: name name of person to greet optional arguments: -h, --help show this help message and exit -c COUNT, --count COUNT number of times to print the greeting
  10. hello-argparse $ hello-argparse usage: hello-argparse [-h] [-c COUNT] name hello-argparse:

    error: too few arguments $ hello-argparse PyOhio Hello, PyOhio! $ hello-argparse --count 3 PyOhio Hello, PyOhio! Hello, PyOhio! Hello, PyOhio!
  11. First: Decorators def wrapper(func): # wraps a function in order

    to add additional behavior @wrapper def my_function(example_arg): # does stuff
  12. hello_click.py import click @click.command() @click.argument('name') @click.option('--count', '-c', default=1, help="Number of

    times to print the greeting") def hello(name, count): """ Click CLI to greet the provided NAME. """ for i in xrange(count): print("Hello, {0}!".format(name))
  13. hello-click --help $ hello-click --help Usage: hello-click [OPTIONS] NAME Click

    CLI to greet the provided NAME. Options: -c, --count INTEGER Number of times to print the greeting --help Show this message and exit.
  14. hello-click $ hello-click Usage: hello-click [OPTIONS] NAME Error: Missing argument

    "name". $ hello-click PyOhio Hello, PyOhio! $ hello-click --count 3 PyOhio Hello, PyOhio! Hello, PyOhio! Hello, PyOhio!
  15. hello_docopt.py """Hello, Docopt. Usage: hello-docopt <name> hello-docopt [--count=<int>] <name> hello-docopt

    -h | --help Options: -h --help Show this screen. -c --count=<int> Times to print greeting [default: 1]. """ from docopt import docopt def cli(): args = docopt(__doc__) for i in xrange(int(args['--count'])): print("Hello, {0}!".format(args['<name>']))
  16. hello-docopt --help $ hello-docopt --help Hello, Docopt. Usage: hello-docopt <name>

    hello-docopt [--count=<int>] <name> hello-docopt -h | --help Options: -h --help Show this screen. -c --count=<int> Times to print greeting [default: 1].
  17. hello-docopt $ hello-docopt Usage: hello-docopt <name> hello-docopt [--count=<int>] <name> hello-docopt

    -h | --help $ hello-docopt PyOhio Hello, PyOhio! $ hello-docopt --count 3 PyOhio Hello, PyOhio! Hello, PyOhio! Hello, PyOhio!
  18. Let's make a better CLI app! Read from a file

    or stdin Write to a file or stdout Update to log or stderr Has an option or two Has colors! Does something
  19. Sample data Declaration of Independence [Adopted in Congress 4 July

    1776] The Unanimous Declaration of the Thirteen United States of When, in the course of human events, it becomes necessary for dissolve the political bands which have connected them with an assume among the powers of the earth, the separate and equal s which the laws of nature and of nature's God entitle them, a d to the opinions of mankind requires that they should declare t which impel them to the separation. …
  20. cli_click.py import click import sys from pyohio2016 import counter @click.command()

    @click.argument('infile', type=click.File('r'), default='-') @click.argument('outfile', type=click.File('w'), default='-') @click.option('--log-file', '-l', type=click.File('w'), default=sys.stderr) @click.option('--verbose', '-v', is_flag=True) def cli(infile, outfile, log_file, verbose): """ Count char occurances in INFILE, output to OUTFILE. """ …
  21. cli_click.py (cont'd) … if verbose: click.echo("Infile: {0}".format(infile), file=log_file) click.echo("Outfile: {0}".format(outfile),

    file=log_file) text = infile.read() char_counts = counter.count_chars(text) output = "\n".join( ["{0} {1}".format(k, v) for k, v in char_counts]) click.echo("Counted characters...", file=log_file) click.secho(output, file=outfile, fg='green') if verbose: click.echo("Done.".format(outfile), file=log_file)
  22. pyohio-click --help Usage: pyohio-click [OPTIONS] [INFILE] [OUTFILE] Count char occurances

    in INFILE, output to OUTFILE. Options: -l, --log-file FILENAME -v, --verbose --help Show this message and exit.
  23. stdin input $ echo -n "test" | pyohio-click Counted characters...

    t 2 s 1 e 1 $ echo -n "test" | pyohio-click - results.txt Counted characters... $ cat results.txt t 2 s 1 e 1
  24. Verbose with log file $ echo -n "test" | pyohio-click

    -v --log-file log.txt - out.txt $ cat out.txt t 2 s 1 e 1 $ cat log.txt Infile: <_io.TextIOWrapper name='<stdin>' encoding='utf-8'> Outfile: <unopened file 'results.txt' w> Counted characters... Done.