Skip to content. | Skip to navigation

Personal tools
Log in
Sections
You are here: Home How To Python CLI Program Skeleton

CLI Program Skeleton

This is skeleton code for a command-line interface (CLI) program.

 

IMPORTANT: This Has Been Superseded

I have now put this into a python package called phyles. Phyles offers simple config files with validation and all of the functionality below, without copy-pasting. See the phyles homepage at http://phyles.bravais.net.

 

Deprecated Instructions

I make these CLI programs fairly frequently as personal utilities and dislike re-writing this boilerplate. It may be amenable to a library, but I haven't put the time in to generalize it.

Below is a commented template text, which can be downloaded here.

For experts, a file without comments can be downloaded here.

 

#! /usr/bin/env python

"""
dosomething: a command line program that does something

Put a more elaborate description and other notes here.

Then do yourself a favor and include these two lines:

The general structure of this program is based on a template that can be found at
http://www.jamesstroud.com/jamess-miscellaneous-how-tos/python/cli-program-skeleton
"""

######################################################################
# import the next four no matter what
######################################################################
import os
import sys
import textwrap
from optparse import OptionParser

######################################################################
# for config file parsing, if needed
######################################################################
import yaml

######################################################################
# import other libraries and modules that do stuff
######################################################################
import dostuff

######################################################################
# edit next two to your liking
######################################################################
__program__ = "dosomething"
__version__ = "0.1"

######################################################################
# the defaults for the config, if needed
######################################################################
DEFAULTS = {"element" : "C",
            "max_measurement" : 1.0e9,
            "min_measurement" : 1.0,
            "max_sumsq" : 1.0e12,
            "convergence" : 1.0e-10,
            "maxiter" : 100,
            "guess" : 0.010,
            "vf_criterion" : 0.05,
            "debug" : False,
            "log_level" : 0}

######################################################################
# no need to touch banner() or usage()
######################################################################
def banner(width=70):
  hline = "=" * width
  sys.stderr.write(hline + "\n")
  p = ("%s v.%s " % (__program__, __version__)).center(width) + "\n"
  sys.stderr.write(p)
  sys.stderr.write(hline + "\n")

def usage(parser, msg=None, width=70, pad=4):
  lead_space = " " * (pad)
  w = width - pad
  err = ' ERROR '.center(w, '#').center(width)
  errbar = '#' * w
  errbar = errbar.center(width)
  hline = '=' * width
  if msg is not None:
    msg_list = str(msg).splitlines()
    msg = []
    for aline in msg_list:
      aline = lead_space + aline.rstrip()
      msg.append(aline)
    msg = "\n".join(msg)
    print '\n'.join(('', err, msg, errbar, ''))
    print hline
  print
  print parser.format_help()
  sys.exit(0)

######################################################################
# set up the options parser from the optparse module
#   - see http://docs.python.org/library/optparse.html
######################################################################
def doopts():

  ####################################################################
  # no need to edit the next line
  ####################################################################
  program = os.path.basename(sys.argv[0])

  ####################################################################
  # edit usg to reflect the options, usage, and info for user
  #   - see http://en.wikipedia.org/wiki/Backus-Naur_Form 
  #       - expressions in brackets are optional
  #       - expressions separated by bars are alternates
  #   - don't mess with the "%s", this is a template string
  #   - here, CSVFILE and associated usage info is just an example
  ####################################################################
  usg = """\
        usage: %s -h | -t | [-c CONFIG] CSVFILE

          - CSVFILE holds the data in csv format
            see http://en.wikipedia.org/wiki/Comma-separated_values

          - Use the -h flag to print this help
          - Use the -c flag to specify a config file in yaml format
          - Use the -t flag to output a template config to sdtout
        """
  usg = textwrap.dedent(usg) % program
  parser = OptionParser(usage=usg)

  ####################################################################
  # - these are only some examples
  # - but -t and -c options are recommended if using a config file
  ####################################################################  
  parser.add_option("-t", "--template", dest="template",
                    default=False, action="store_true",
                    help="print template settings file",
                    metavar="TEMPLATE")
  parser.add_option("-c", "--config", dest="config",
                    metavar="CONFIG", default=None,
                    help="config file to further specify conversion")
  return parser

######################################################################
# creates an easy configuration template for the user
######################################################################
def template():
  ####################################################################
  # this is yaml: http://www.yaml.org/spec/1.2/spec.html
  #   - keep the first document type line ("%YAML 1.2") and
  #     the document seperator ("---")
  #   - edit the template to match DEFAULTS
  ####################################################################
  t = """
      %YAML 1.2
      ---
      element : C
      max_measurement : 1.6e+7
      min_measurement : 3.0e+2
      max_sumsq : 1.0e10
      convergence : 1.0e-5
      maxiter : 100
      guess : 0.012
      vf_criterion : 0.05
      debug : False
      log_level : 0
      """

  ####################################################################
  # no need to edit the next two lines
  ####################################################################
  print textwrap.dedent(t)
  sys.exit(0)

######################################################################
# process the command line logic, parse the config, etc.
######################################################################
def main():

  ####################################################################
  # no need to touch the next two lines
  ####################################################################
  parser = doopts()
  (options, args) = parser.parse_args()

  ####################################################################
  # start processing the command line logic here
  #   - this logic is for
  #     "usage: %s -h | -t | [-c CONFIG] CSVFILE"
  #   - don't touch the next four if you have a config file
  ####################################################################
  if options.template:
    template()
  else:
    banner()

  ####################################################################
  # just as an example, we get the value of 'csvfile' from args
  ####################################################################
  if len(args) != 1:
    usage(parser)
  else:
    csvfile = args[0]

  ####################################################################
  # create the configuration that will be used within the program
  #   - first make a copy of DEFAULTS
  #   - then, update the copy with the user_config of
  #     the config file
  #   - this allows the user to specify only a subset of the config
  #   - try to catch problems with the config file and
  #     report them in usage()
  ####################################################################
  config = DEFAULTS.copy()
  if options.config:
    if os.path.exists(options.config):
      f = open(options.config)
      user_config = yaml.load(f.read())
      config.update(user_config)
    else:
      msg = "Config file '%s' does not exist." % options.config
      usage(parser, msg)

  ####################################################################
  # Put the stuff your program does within a try-except block.
  # This block catches a top-level module exception that
  # sentinels errors that you anticipate and want the program to
  # handle gracefully.
  #
  # Here, dostuff.something() process the mythical 'csvfile'.
  # It takes the 'csvfile and the 'config' dictionary as arguments.
  ####################################################################
  try:
    dostuff.something(csvfile, config)
  except dostuff.DoStuffError, e:
    usage(parser, e)

if __name__ == "__main__":
  main()