Skip to content

Latest commit

History

49 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

smartcli

smartcli turns a help text block into a small CLI parser and a typed options object.

It is aimed at nimony projects that want a tiny docopt-like DSL without bringing in a larger parser layer.

The core design direction was inspired by @Araq's forum post: let cliapp"""...""" build a validator plus a typed object, so the result is more convenient than hand-written argument loops and nested case statements.

Why try it?

  • The help text is the source of truth.
  • cliapp"""...""" generates the parser at compile time.
  • Flags become bool fields, value options become string fields, and a|b choices become enums.
  • Commands are generated as a command enum instead of staying as raw strings.

Quick Start

import std / syncio
import smartcli
let options =cliapp"""Greeter v0.1This program greets.Usage: greeter [options] (greet INPUT | version)Commands: greet INPUT Greets NAME version Displays version and quitsArguments: INPUT Input fileOptions: --mode=fast|slow Output mode --output=FILE Output file -v, --verbose Enable verbose output -h, --help Show this help and exit"""echo options.input
echo options.output
echo options.verbose
echo options.command
echo options.mode

DSL Rules

  • Options become typed fields on options.
  • Entries under Commands: become values of options.command.
  • A command can declare its own positional arguments inline, for example greet INPUT.
  • If commands do not declare inline arguments, they all share the arguments listed under Arguments:.
  • Usage: is documentation only. It does not define parser behavior.

Current Limitations

  • Use the section headers exactly as written: Usage:, Commands:, Arguments:, and Options:.
  • Each command, argument, or option must fit on a single line. Wrapped descriptions are not supported.
  • Leave at least two consecutive whitespace characters between the entry itself and its description.
  • For inline command arguments, keep the whole command before the description, for example run ENV TARGET Execute a deployment.
  • Usage: is only shown to the user. It does not control how the parser is generated.

What cliapp Actually Gives You

The pleasant part of the greeter example is that it looks tiny:

let options =cliapp"""..."""

The useful part is what that one line buys you at compile time.

For the greeter spec, smartcli does not give you a loose bag of strings. It generates a real parser and a typed result model:

  • a CliCommand enum for greet|version
  • a generated CliMode enum for fast|slow
  • a CliOptions object with input, command, mode, output, and verbose
  • a parseCli() proc that drives parseopt directly
  • built-in -h/--help handling
  • built-in version handling that prints the title line and exits

That means the nice, tiny source:

echo options.input
echo options.output
echo options.verbose
echo options.command

is backed by something much closer to this:

block:
const spec ="""..."""typeCliCommand=enum
cmdNone
cmdGreet
cmdVersion
CliMode=enum
cliModeNone
cliModeFast
cliModeSlow
CliOptions=object
input: string
command: CliCommand
mode: CliMode
output: string
verbose: boolprocparseCli(): CliOptions=var p =initOptParser()
var argSlot =0result=CliOptions()
whiletrue:
next(p)
case p.kind
 of cmdEnd:
break of cmdArgument:
case argSlot
 of0:
case p.key
 of"greet":
result.command = cmdGreet
 of"version":
result.command = cmdVersion
else:
cliUnexpectedArgument(spec, p.key)
inc argSlot
else:
caseresult.command
 of cmdGreet:
case argSlot
 of1:
result.input = p.key
inc argSlot
else:
cliUnexpectedArgument(spec, p.key)
 of cmdVersion:
cliUnexpectedArgument(spec, p.key)
else:
cliUnexpectedArgument(spec, p.key)
 of cmdLongOption:
case p.key
 of"help":
cliExitHelp(spec)
 of"mode":
case p.val
 of"fast":
result.mode = cliModeFast
 of"slow":
result.mode = cliModeSlow
else:
cliInvalidValue(spec, "--mode", p.val)
 of"output":
result.output = p.val
 of"verbose":
result.verbose =trueelse:
cliUnknownLongOption(spec, p.key)
 of cmdShortOption:
case p.key
 of"h":
cliExitHelp(spec)
 of"v":
result.verbose =trueelse:
cliUnknownShortOption(spec, p.key)
ifresult.command == cmdVersion:
cliExitVersion(spec)
caseresult.command
 of cmdGreet:
if argSlot <2:
cliMissingArguments(spec)
 of cmdVersion:
discardelse:
cliMissingArguments(spec)

The point is not that you could write this parser yourself. The point is that you do not have to. You keep the help text as the source of truth, and you still end up with typed fields, command enums, and real parser behavior instead of manual string matching spread across your app.

Layout

Run

Compile an example directly with Nimony:

nimony c examples/greeter.nim

Run the package tests directly:

nimony c -r tests/tsmoke.nim
nimony c -r tests/tcommandargs.nim
nimony c -r tests/tsharedargs.nim
nimony c -r tests/tslots.nim

License

MIT. See LICENSE.

About

smartcli turns a help text block into a small CLI parser and a typed options object.

Topics

Resources

Stars

7 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages