docopt parses command-line arguments from a usage string in a docstring — no argument parser code needed. pip install docopt-ng. Basic: from docopt import docopt; args = docopt(__doc__) where __doc__ contains Usage: prog <name>. Options: --verbose Verbose output. --port=N Port [default: 8000]. Flags: --help --version. Commands: Usage: prog (start|stop|status). Arguments: <file> positional; [<path>] optional. Repeating: <file>... or FILE .... Multi-cmd: Usage: prog command <arg> matched by args["command"]. Result dict: keys are <arg>, --option, ARGUMENT, command booleans. args["<name>"] → string. args["--port"] → “8000”. args["--verbose"] → True/False. args["start"] → True if start was the command. docopt(doc, version="1.0") handles --version. docopt(doc, argv=["start","--verbose"]). Validation: combine with schema library for type coercion. Multiple occurrences: --verbose... count with args["--verbose"] → int. Optional group: [--host=HOST --port=PORT]. Required group: (--user=USER --pass=PASS). Naval_fate canonical example: commands and args in one docstring. Claude Code generates docopt CLIs, usage-driven tools, and multi-command dispatch scripts.
CLAUDE.md for docopt
## docopt Stack
- Version: docopt-ng >= 0.9 | pip install docopt-ng
- Usage: from docopt import docopt; args = docopt(__doc__) in main()
- Syntax: <arg> positional; [<opt>] optional; --flag boolean; --opt=VAL with value
- Default: --port=N Port number [default: 8000] — bracket in option description
- Commands: Usage: prog (start|stop|status) — args["start"] is True/False
- Repeating: <file>... → list; --verbose... → count (int)
- Validate: use schema library for type coercion after docopt parses
docopt CLI Pipeline
# app/cli.py — docopt usage-string driven CLI with commands, options, and validation
"""
Usage:
cli.py server start [--host=HOST] [--port=PORT] [--workers=N] [--debug]
cli.py server stop [--force]
cli.py server status
cli.py db migrate [--target=TARGET]
cli.py db seed [--env=ENV]
cli.py db backup <output>
cli.py deploy <version> [--env=ENV] [--dry-run] [--services=SVCS]
cli.py config show [--env=ENV]
cli.py config set <key> <value> [--env=ENV]
cli.py (-h | --help)
cli.py --version
Options:
-h --help Show this help screen.
--version Show version.
--host=HOST Bind host [default: 0.0.0.0].
--port=PORT Bind port [default: 8000].
--workers=N Worker count [default: 4].
--debug Enable debug mode.
--force Skip confirmation prompts.
--target=TARGET Migration target revision [default: head].
--env=ENV Target environment [default: development].
--dry-run Print actions without executing.
--services=SVCS Comma-separated service list [default: all].
"""
from __future__ import annotations
import json
import sys
from pathlib import Path
from typing import Any
from docopt import docopt
# ─────────────────────────────────────────────────────────────────────────────
# 1. Argument helpers
# ─────────────────────────────────────────────────────────────────────────────
def _int(args: dict, key: str) -> int:
"""Coerce a docopt string value to int."""
v = args.get(key)
if v is None:
raise ValueError(f"Missing required option {key}")
try:
return int(v)
except (ValueError, TypeError):
raise ValueError(f"{key} must be an integer, got {v!r}")
def _bool(args: dict, key: str) -> bool:
return bool(args.get(key))
def _str(args: dict, key: str, default: str = "") -> str:
return args.get(key) or default
def _list(args: dict, key: str, sep: str = ",") -> list[str]:
raw = _str(args, key)
if raw == "all" or not raw:
return []
return [s.strip() for s in raw.split(sep) if s.strip()]
# ─────────────────────────────────────────────────────────────────────────────
# 2. Command handlers
# ─────────────────────────────────────────────────────────────────────────────
def cmd_server_start(args: dict) -> None:
"""Start the development server."""
host = _str(args, "--host", "0.0.0.0")
port = _int(args, "--port")
workers = _int(args, "--workers")
debug = _bool(args, "--debug")
print(f" Starting server: {host}:{port} (workers={workers}, debug={debug})")
# In real code: uvicorn.run("app:app", host=host, port=port, ...)
def cmd_server_stop(args: dict) -> None:
"""Stop the server process."""
force = _bool(args, "--force")
if not force:
confirm = input(" Stop server? [y/N] ").strip().lower()
if confirm != "y":
print(" Aborted")
return
print(" Server stopped")
def cmd_server_status(_: dict) -> None:
"""Show server process status."""
print(" Server: running (pid=12345)")
print(" Uptime: 2h 15m")
def cmd_db_migrate(args: dict) -> None:
"""Run database migrations."""
target = _str(args, "--target", "head")
print(f" Running migrations → {target}")
def cmd_db_seed(args: dict) -> None:
"""Seed the database."""
env = _str(args, "--env", "development")
print(f" Seeding {env} database")
def cmd_db_backup(args: dict) -> None:
"""Back up the database to a file."""
output = _str(args, "<output>")
print(f" Backing up to {output}")
def cmd_deploy(args: dict) -> None:
"""Deploy a version to an environment."""
version = _str(args, "<version>")
env = _str(args, "--env", "development")
dry_run = _bool(args, "--dry-run")
services = _list(args, "--services")
svc_str = ", ".join(services) if services else "all"
msg = f" Deploy {version} → {env} (services: {svc_str})"
if dry_run:
print(f" [dry-run] {msg}")
else:
print(msg)
def cmd_config_show(args: dict) -> None:
"""Show configuration for an environment."""
env = _str(args, "--env", "development")
conf = {"env": env, "debug": env == "development", "log_level": "INFO"}
print(json.dumps(conf, indent=2))
def cmd_config_set(args: dict) -> None:
"""Set a configuration key."""
key = _str(args, "<key>")
value = _str(args, "<value>")
env = _str(args, "--env", "development")
print(f" [{env}] {key} = {value!r}")
# ─────────────────────────────────────────────────────────────────────────────
# 3. Dispatcher
# ─────────────────────────────────────────────────────────────────────────────
def dispatch(args: dict) -> None:
"""Route to the correct handler based on docopt command booleans."""
if args["server"] and args["start"]:
cmd_server_start(args)
elif args["server"] and args["stop"]:
cmd_server_stop(args)
elif args["server"] and args["status"]:
cmd_server_status(args)
elif args["db"] and args["migrate"]:
cmd_db_migrate(args)
elif args["db"] and args["seed"]:
cmd_db_seed(args)
elif args["db"] and args["backup"]:
cmd_db_backup(args)
elif args["deploy"]:
cmd_deploy(args)
elif args["config"] and args["show"]:
cmd_config_show(args)
elif args["config"] and args["set"]:
cmd_config_set(args)
else:
print(__doc__)
# ─────────────────────────────────────────────────────────────────────────────
# 4. Schema-based validation helper
# ─────────────────────────────────────────────────────────────────────────────
def validate_args(args: dict) -> dict:
"""
Optional schema validation after docopt parsing.
Requires: pip install schema
"""
try:
from schema import And, Optional, Or, Schema, Use
schema = Schema({
"server": bool,
"db": bool,
"deploy": bool,
"config": bool,
"start": bool,
"stop": bool,
"status": bool,
"migrate": bool,
"seed": bool,
"backup": bool,
"show": bool,
"set": bool,
"--host": str,
"--port": And(Use(int), lambda n: 1 <= n <= 65535),
"--workers": And(Use(int), lambda n: n >= 1),
"--debug": bool,
"--force": bool,
"--target": str,
"--env": And(str, lambda e: e in ("development", "staging", "production")),
"--dry-run": bool,
"--services": Or(None, str),
Optional("<output>"): Or(None, str),
Optional("<version>"): Or(None, str),
Optional("<key>"): Or(None, str),
Optional("<value>"): Or(None, str),
"--help": bool,
"--version": bool,
})
return schema.validate(args)
except ImportError:
return args
# ─────────────────────────────────────────────────────────────────────────────
# 5. Simple standalone docopt example (single-command tool)
# ─────────────────────────────────────────────────────────────────────────────
SIMPLE_DOC = """
Usage:
tool.py [options] <files>...
Options:
-o FILE --output=FILE Output file [default: stdout].
-n N --lines=N Number of lines [default: 10].
-v --verbose Verbose output.
-h --help Show help.
"""
def run_simple_tool(argv: list[str] | None = None) -> None:
"""
Standalone example to show how simple tools look.
Parses <files>... as a list.
"""
args = docopt(SIMPLE_DOC, argv=argv)
files = args["<files>"]
output = args["--output"]
lines = int(args["--lines"])
verbose = args["--verbose"]
if verbose:
print(f" Processing {len(files)} files, output={output}, lines={lines}")
for f in files:
path = Path(f)
if not path.exists():
print(f" Warning: {f} not found", file=sys.stderr)
continue
content = path.read_text(errors="replace").splitlines()[:lines]
if output and output != "stdout":
Path(output).write_text("\n".join(content))
else:
for line in content:
print(line)
# ─────────────────────────────────────────────────────────────────────────────
# Entry point
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
args = docopt(__doc__, version="1.0.0")
try:
args = validate_args(args)
except Exception as exc:
print(f"Argument error: {exc}", file=sys.stderr)
sys.exit(1)
dispatch(args)
For the argparse alternative — argparse requires one add_argument() call per parameter with explicit type and help strings scattered across the code; docopt puts the entire CLI contract in one readable usage string that doubles as documentation — the interface is visible at the top of the file without hunting through parser setup code. For the typer / click alternative — click and Typer are better choices for large CLIs where you need sub-command groups, callbacks, error formatting, and plugin architectures; docopt is better for small to medium tools where the usage pattern is clear and you want the CLI defined as a readable string rather than decorator-annotated functions. The Claude Skills 360 bundle includes docopt skill sets covering docopt() from doc usage string, Options section with defaults, positional <arg> and optional [<arg>], repeating <arg>... lists, command booleans (start/stop/status), —flag booleans and —opt=VAL strings, dispatch() routing to handler functions, _int/_bool/_list coercion helpers, schema-based validation, multi-level commands (server start/stop, db migrate/seed), and run_simple_tool() standalone example. Start with the free tier to try docstring CLI code generation.