A Simple Script#
To create your first simple program using cmdkit, define a basic
Application
class for structuring your entry-point.
This associates the command-line Interface
with the
business logic or your program, defined by the run()
method.
Essentially, we want to map the necessary parameters to some function that might
define our task. The expected design defines class-level attributes as our
command-line arguments and parameters. The interface parses these from the raw
arguments. Our main()
method accepts the raw
arguments and passes the parsed namespace to the constructor of the class,
assigning them to these class-level attributes.
#!/usr/bin/env python3
"""Tiny example script using CmdKit."""
# type annotations
from __future__ import annotations
# standard libs
import os
import sys
# external libs
from cmdkit import Application, Interface
# metadata
version = '0.1.0'
prog_name = os.path.basename(sys.argv[0])
usage_text = f"""\
Usage:
{prog_name} [-v] NAME
{__doc__}\
"""
help_text = f"""\
{usage_text}
Arguments:
NAME Name of person to greet.
Options:
-v, --version Print version and exit.
-h, --help Print this message and exit.\
"""
class TinyApp(Application):
"""Application class for tiny-script program."""
interface = Interface(prog_name, usage_text, help_text)
interface.add_argument('-v', '--version', action='version', version=version)
name: str
interface.add_argument('name')
def run(self: TinyApp) -> None:
"""Run program."""
print(f'Hello, {self.name}!')
if __name__ == '__main__':
sys.exit(TinyApp.main(sys.argv[1:]))
A slightly more sophisticated iteration of this hello-world program could include logging, configuration, and additional parameters to help us understand the state of the program at runtime.
Try manipulating the configuration files, and environment variables starting with
FULLHELLO_
, see the -s
and -c
options to see how these changes manifest.
#!/usr/bin/env python3
"""Full example script using CmdKit."""
# type annotations
from __future__ import annotations
from typing import IO
# standard libs
import os
import sys
import json
from getpass import getuser
# external libs
from cmdkit.app import Application, exit_status
from cmdkit.cli import Interface
from cmdkit.config import Configuration
from cmdkit.namespace import Namespace
from cmdkit.logging import Logger, logging_styles, level_by_name
# metadata
version = '0.1.0'
appname = 'FullHello'
program = os.path.basename(sys.argv[0])
default_config = Namespace({
'logging': {
'style': 'short',
'level': 'info'
}
# Add more configuration here ...
})
try:
ctx, cfg = Configuration.from_context(name=appname, default_config=default_config)
log = Logger.default(program,
level=level_by_name[cfg.logging.level.upper()],
format=logging_styles[cfg.logging.style.lower()]['format'])
except Exception as error:
print(f'error: {program}: {error}', file=sys.stderr)
sys.exit(exit_status.bad_config)
usage_text = f"""\
Usage:
{program} [-v] [NAME | --config | --site] [-o FILE]
{__doc__}\
"""
help_text = f"""\
{usage_text}
Arguments:
NAME Name of person to greet (default: $USER).
Options:
-c, --config Show configuration and exit.
-s, --site Show site paths and exit.
-o, --output FILE Path to output file.
-v, --version Print version and exit.
-h, --help Print this message and exit.\
"""
site_text = f"""\
[system]
data: {ctx.path.system.lib}
logs: {ctx.path.system.log}
config: {ctx.path.system.config}
[user]
data: {ctx.path.user.lib}
logs: {ctx.path.user.log}
config: {ctx.path.user.config}
[local]
data: {ctx.path.local.lib}
logs: {ctx.path.local.log}
config: {ctx.path.local.config}
"""
site_text = site_text.replace(
f'[{ctx.default_site}]',
f'[{ctx.default_site}] (default)',
)
class FullHello(Application):
"""Application class for program."""
interface = Interface(program, usage_text, help_text)
interface.add_argument('-v', '--version', action='version', version=version)
name: str = getuser()
interface.add_argument('name', nargs='?', default=name)
show_config: bool = False
show_site: bool = False
mode = interface.add_mutually_exclusive_group()
mode.add_argument('-c', '--config', action='store_true', dest='show_config')
mode.add_argument('-s', '--site', action='store_true', dest='show_site')
outpath: str = '-'
output_stream: IO = sys.stdout
interface.add_argument('-o', '--output', dest='outpath', default=outpath)
def run(self: FullHello) -> None:
"""Run program."""
if self.show_config:
log.info('Showing configuration')
print(json.dumps(dict(cfg), indent=4), file=self.output_stream)
elif self.show_site:
log.info('Showing site details')
print(site_text, file=self.output_stream)
else:
log.info(f'Greeting user ({self.name})')
print(f'Hello, {self.name}!', file=self.output_stream)
def __enter__(self: FullHello) -> Application:
"""Open output file path."""
if self.outpath != '-':
self.output_stream = open(self.outpath, mode='w')
return super().__enter__()
def __exit__(self: FullHello, *exc) -> None:
"""Close resources."""
if self.output_stream is not sys.stdout:
self.output_stream.close()
if __name__ == '__main__':
sys.exit(FullHello.main(sys.argv[1:]))
Note
Both of these short programs are included with the project under
the examples/
directory.