argparse: Create a Command-line App with Python

argparse: Create a Command-line App with Python

argparse is a Python standard library makes it easy to write a CLI application. You should use this module instead of optparse which is deprecated since Python 2.7.

ref:
https://docs.python.org/3/library/argparse.html

Basic example

#!/usr/bin/env python

from __future__ import print_function

import argparse
import sys

import jokekappa

class JokeKappaCLI(object):

    def __init__(self):
        parser = argparse.ArgumentParser(
            prog='jokekappa',
            description='humor is a serious thing, you should take it seriously',
        )
        self.parser = parser
        self.parser.add_argument('-v', '--version', action='version', version=jokekappa.__version__)

        self.subparsers = parser.add_subparsers(title='sub commands')
        self.subparsers \
            .add_parser('one', help='print one joke randomly') \
            .set_defaults(func=self.tell_joke)
        self.subparsers \
            .add_parser('all', help='print all jokes') \
            .set_defaults(func=self.tell_jokes)
        self.subparsers \
            .add_parser('update', help='update jokes from sources') \
            .set_defaults(func=self.update_jokes)

    def parse_args(self):
        if len(sys.argv) == 1:
            namespace = self.parser.parse_args(['one', ])
        else:
            namespace = self.parser.parse_args()
        namespace.func()

    def tell_joke(self):
        joke = jokekappa.get_joke()
        print(joke['content'])

    def tell_jokes(self):
        for joke in jokekappa.get_jokes():
            print(joke['content'])

    def update_jokes(self):
        jokekappa.update_jokes()
        print('Done')

def main():
    JokeKappaCLI().parse_args()

if __name__ == '__main__':
    main()

ref:
https://github.com/CodeTengu/JokeKappa
https://stackoverflow.com/questions/5176691/argparse-how-to-specify-a-default-subcommand

Advanced example

In following code, you're able to create a Python module called pangu and a command-line tool also called pangu, both share the same codebase.

in pangu.py

from __future__ import print_function

import argparse
import sys

__version__ = '3.3.0'
__all__ = ['spacing_text', 'PanguCLI']

def spacing_text(text):
    """
    You could find real code in https://github.com/vinta/pangu.py
    """
    return text.upper().strip()

class PanguCLI(object):

    def __init__(self):
        parser = argparse.ArgumentParser(
            prog='pangu',
            description='paranoid text spacing',
        )
        self.parser = parser
        self.parser.add_argument('-v', '--version', action='version', version=__version__)
        self.parser.add_argument('text', action='store', type=str)

    def parse(self):
        if not sys.stdin.isatty():
            print(spacing_text(sys.stdin.read()))
        elif len(sys.argv) > 1:
            namespace = self.parser.parse_args()
            print(spacing_text(namespace.text))
        else:
            self.parser.print_help()
        sys.exit(0)

if __name__ == '__main__':
    PanguCLI().parse()

in bin/pangu

#!/usr/bin/env python

from pangu import PanguCLI

if __name__ == '__main__':
    PanguCLI().parse()

in setup.py

from setuptools import setup

setup(
    name='pangu',
    py_modules=['pangu', ],
    scripts=['bin/pangu', ],
    ...
)

As a result, there're multiple usages:

$ pangu "abc"
$ python -m pangu "abc"
$ echo "abc" | pangu
$ echo "abc" | python -m pangu
ABC

ref:
https://github.com/vinta/pangu.py

Accept a list as option

parser.add_argument('-u', '--usernames', type=lambda x: x.split(','), dest='usernames', required=True)
# your_command -u vinta
# your_command -u vinta,saiday

Accept conditional argument

ref:
https://stackoverflow.com/questions/15459997/passing-integer-lists-to-python

tmux: The terminal multiplexer

tmux: The terminal multiplexer

tmux is a command-line tool that can create multiple virtual terminals in one terminal.

ref:
https://tmux.github.io/

Install

# on Ubuntu
$ sudo apt-get install tmux

 # on macOS
$ brew install tmux

$ tmux -V
tmux 2.6

Configurations

in ~/.tmux.conf

# prefix: Control + a
set -g prefix C-a

# kill-session: Control + a >> X
bind X confirm-before "kill-session -t ''"

# select-pane: Option + arrow key
bind -n M-Left select-pane -L
bind -n M-Right select-pane -R
bind -n M-Up select-pane -U
bind -n M-Down select-pane -D

# start numbering at 1
set -g base-index 1
set-window-option -g pane-base-index 1

# allow for faster key repetition
set -s escape-time 0

# rather than constraining window size to the maximum size of any client
# connected to the *session*, constrain window size to the maximum size of any
# client connected to *that window*. Much more reasonable.
# CAUTION: not compatible with iTerm2
setw -g aggressive-resize off

set -g status-left-length 40

set -g mouse on

# list of plugins
set -g @plugin 'tmux-plugins/tpm'
set -g @plugin 'tmux-plugins/tmux-sensible'

# initialize TMUX plugin manager (keep this line at the very bottom of tmux.conf)
run '~/.tmux/plugins/tpm/tpm'

After adding new plugins, you need to press Control + A >> I to fetch and install them.

ref:
https://github.com/tony/tmux-config/blob/master/.tmux.conf

Usage

$ tmux

$ tmux new -s your_project

$ tmux ls

$ tmux attach

$ tmux attach -t your_project

# list all windows with their layouts
$ tmux list-windows

$ tmux kill-session -t your_project

# kill all sessions
$ tmux kill-server

# reload settings
$ tmux source ~/.tmux.conf

Shortcut Keys

Control + a >> Shift + ?
list all shortcut keys

Control + a >> Shift + ""
split window horizontally

Control + a >> Shift + %
split window vertically

Control + a >> Arrow keys
Command + Option + Arrow keys
move the cursor between panels

Control + a >> d
dettach

Control + a >> c
create a new window

Control + a >> p
switch to the previous window

Control + a >> n
switch to the next window

Control + a >> w
Control + a >> 0-9
switch to window 0-9

Control + a >> [
enable copy mode, you can use Option + Up or Option + Down to scroll

Control + a >> Shift + &
kill the current window

Control + a >> X
kill the current session

ref:
http://manpages.ubuntu.com/manpages/zesty/en/man1/tmux.1.html

Tools

tmuxp

$ pip install tmuxp

An sample config:

in ~/.tmuxp/your_project.yaml

session_name: swag
windows:
- window_name: swag
  layout: main-horizontal
  focus: 'true'
  options:
    automatic-rename: 'off'
    main-pane-height: 35
  start_directory: ~/Projects/swag-server
  shell_command_before:
    - clear
  panes:
  - shell_command:
    - make up
  - shell_command:
    focus: 'true'
  - shell_command:
    - cd ../swag-bot
    - clear
$ tmuxp load swag

Layouts
https://tmuxp.git-pull.com/en/latest/examples.html

ref:
https://github.com/tony/tmuxp
https://github.com/tony/tmuxp-config

tmux-resurrect

Control + a >> Control + s
save tmux env

Control + a >> Control + r
restore tmux env

ref:
https://github.com/tmux-plugins/tmux-resurrect

Integration with iTerm2

You must create tmux session with -CC argument to integrate with iTerm2.

$ tmux ls && read session && tmux -CC attach -t ${session:-default} || tmux -CC new -s ${session:-default}

ref:
https://gitlab.com/gnachman/iterm2/wikis/TmuxIntegration