Confutils is a Nim library for creating command line interfaces with as little code as necessary. It focuses on providing a lot of compile-time configurability and extensibility.
The CLI run-time configuration is generated at compile-time from a Nim object type definition annotated with pragmas. This configuration is then used to:
- Parse command-line options and arguments.
- Read values from environment variables.
- Load options from configuration files (and the Windows registry).
- Automaticly generate the help page.
Here is an example of a simple Confutils program:
# hello.nim
import confutils
type
Hello = object
name {.
name: "name"
desc: "Name to greet" .}: string
count {.
name: "count"
defaultValue: 1
desc: "Number of greetings" .}: int
proc hello(conf: Hello) =
for x in 0 ..< conf.count:
echo "Hello ", conf.name
proc main() =
let conf = Hello.load(copyrightBanner="A simple hello program")
hello(conf)
when isMainModule:
main()Running the program:
$ nim c -r hello.nim --name="World" --count=3
Hello World
Hello World
Hello World
It automatically generates nicely formatted help pages:
$ nim c -r hello.nim --help
A simple hello program
Usage:
test2 [OPTIONS]...
The following options are available:
--name Name to greet.
--count Number of greetings [=1].
For simpler CLI utilities:
import confutils
cli do(
name {.name: "name", desc: "Name to greet".}: string,
count {.name: "count", defaultValue: 1, desc: "Number of greetings".}: int
):
for x in 0 ..< count:
echo "Hello ", nameFor even simpler CLI utilities:
import confutils
proc hello(name: string, count: int) =
for x in 0 ..< count:
echo "Hello ", name
dispatch(hello)Under the hood, using these APIs will result in calling load on an anonymous
configuration type having the same fields as the supplied proc params.
Any additional arguments given as cli(args) do ... and dispatch(fn, args)
will be passed to load without modification. Please note that this requires
all parameters types to be concrete (non-generic).
Confutils makes it easy to create CLIs in the style of git or nimble. The structure of the sub-command tree is encoded as a case object where the sub-command name is represented by an enum field having the command pragma. The sub-commands can be deeply nested. Any nested fields will be considered options of the particular sub-command. The top-level fields will be shared between all sub-commands. Each sub-command automatically provides a --help command.
# config.nim
import confutils/defs
import confutils/std/net
const defaultEth2TcpPort* = 9000
type
BNStartUpCmd* {.pure.} = enum
beaconNode
deposits
NimbusConf* = object
# Global options.
logLevel* {.
desc: "Sets the log level"
defaultValue: "INFO" .}: string
# Subcommands.
case cmd* {.
command
defaultValue: BNStartUpCmd.beaconNode .}: BNStartUpCmd
of BNStartUpCmd.beaconNode:
# Default subcommand when no subcommand is specified.
# The options defined here are not global.
listenAddress* {.
desc: "Listening address for the Ethereum LibP2P and Discovery v5 traffic"
defaultValueDesc: "*"
name: "listen-address" .}: Option[IpAddress]
tcpPort* {.
desc: "Listening TCP port for Ethereum LibP2P traffic"
defaultValue: defaultEth2TcpPort
defaultValueDesc: $defaultEth2TcpPort
name: "tcp-port" .}: Port
of BNStartUpCmd.deposits:
# deposits subcommand.
totalDeposits* {.
desc: "Number of deposits to generate"
defaultValue: 1
name: "count" .}: intThen from our main module:
# main.nim
import confutils
import ./config
when isMainModule:
let conf = NimbusConf.load()
echo "Log-level: ", conf.logLevel
case conf.cmd:
of BNStartUpCmd.beaconNode:
echo "Port: ", $conf.tcpPort.int
of BNStartUpCmd.deposits:
echo "Total deposits: ", conf.totalDepositsRunning the program:
$ nim c -r main.nim --loglevel=WARN deposits --count=123
Log level: WARN
Total deposits: 123
And that's it - calling load with default parameters will first process any
command-line options and then it will
try to load any missing options from the most appropriate
configuration location
for the platform. Diagnostic messages will be provided for many simple
configuration errors and the following help message will be produced
automatically when calling the program with program --help:
This covers the basic usage of the library and the rest of the documentation will describe the various ways the default behavior can be tweaked or extended.
A number of pragmas defined in confutils/defs can be attached to the
configuration fields to control the behavior of the library.
template desc*(v: string) {.pragma.}A description of the configuration option that will appear in the produced help messages.
template longDesc*(v: string) {.pragma.}A long description text that will appear below regular desc. You can use
one of {'\n', '\r'} to break it into multiple lines. But you can't use
'\p' as line break.
-x, --name regular description [=defVal].
longdesc line one.
longdesc line two.
longdesc line three.
template name*(v: string) {.pragma.}A long name of the option.
Typically, it will have to be be specified as --longOptionName value.
See Handling of command-line options
for more details.
template abbr*(v: string) {.pragma.}A short name of the option.
Typically, it will be required to be specified as -x value.
See Handling of command-line options
for more details.
template defaultValue*(v: untyped) {.pragma.}The default value of the option if no value was supplied by the user.
template defaultValueDesc*(v: string) {.pragma.}The default value description displayed in the help message.
If the value is a non-string const or compile-time expression,
it must be prefixed with $ (i.e: converted to string).
template required* {.pragma.}By default, all options without default values are considered required.
An exception to this rule are all seq[T] or Option[T] options for
which the "empty" value can be considered a reasonable default. You can
also extend this behavior to other user-defined types by providing the
following overloads:
template hasDefault*(T: type Foo): bool = true
template default*(T: type Foo): Foo = Foo(...)The required pragma can be applied to fields having such defaultable
types to make them required.
template command* {.pragma.}This must be applied to an enum field that represents a possible sub-command.
See the section on sub-commands for more details.
template argument* {.pragma.}This field represents an argument to the program. If the program expects
multiple arguments, this pragma can be applied to multiple fields or to
a single seq[T] field depending on the desired behavior.
template separator(v: string)* {.pragma.}Using this pragma, a customizable separator text will be displayed just before this field. E.g.:
Network Options: # this is a separator
-a, --opt1 desc
-b, --opt2 desc
---------------- # this is a separator too
-c, --opt3 desc
template hidden* {.pragma.}Apply it to a field to not show it in the help message. The value of such field can still be set in the CLI.
template debug* {.pragma.}Use this pragma to avoid listing the field in --help output.
Use --help:debug to list the debug fields.
template ignore* {.pragma.}This can be applied to a field to not
include it in the CLI. It won't show in the help message.
The value/argument won't be able to be set in the CLI.
The defaultValue won't be set.
template implicitlySelectable* {.pragma.}This can be applied to a case object discriminator to allow the value of the discriminator to be determined implicitly when the user specifies any of the sub-options that depend on the disciminator value.
The confutils/defs module provides a number of types frequently used
for configuration purposes:
Confutils will validate that the file/directory exists and that it can be read by the current user.
A valid path must be given.
Furthermore, you can extend the behavior of the library by providing overloads such as:
proc parseCmdArg*(T: type Foo, p: string): T =
## This provides parsing and validation for fields having the `Foo` type.
## You should raise `ConfigurationError` in case of detected problems.
toFoo(p)For config files, Confutils can work with any format supported by the
nim-serialization library
and it will use the standard serialization routines defined for the field
types in this format. Fields marked with the command or argument pragmas
will be ignored.
Confutils parser tries to follow the
robustness principle
by recognizing as many styles of passing command-line switches as possible.
A prefix of -- is used to indicate a long option name, while the - prefix
uses the short option name. Multiple short options such as -a, -b and
-c can be combined into a single -abc string. The option names are matched
in case-insensitive fashion and certain characters
such as _ will be ignored. The values can be separated from the
option names with a colon or an equal sign. bool flags default to
false and merely including them in the command line sets them to true.
After parsing the command-line options, the default behavior of Confutils is
to try to fill any missing options by examining the contents of the environment
variables. If you want to use Confutils only as a command-line processor
or a config file parser for example, you can supply an empty value to the
cmdLine parameter of the load call.
More specifically, the load call supports the following parameters:
The command-line parameters of the program.
By default, these will be obtained through Nim's os module.
The names of the environment variables are prefixed by the name of the
program by default and joined with the name of command line option, which is
uppercased and characters - and spaces are replaced with underscore:
let env_variable_name = &"{prefix}_{key}".toUpperAscii.multiReplace(("-", "_"), (" ", "_"))A callback to add secondary config file sources. The source must be a nim-serialization format.
Confutils implements a Windows register decoder. The default behavior of Windows is to obtain the configuration from the Windows registry by looking at the following keys:
HKEY_CURRENT_USER/SOFTWARE/{appVendor}/{appName}/
HKEY_LOCAL_MACHINE/SOFTWARE/{appVendor}/{appName}/
The load call offers few more optional parameters for modifying the
produced help messages:
A copyright banner or a similar message that will appear before the automatically generated help messages.
If you provide this parameter, Confutils will automatically respond
to the standard --version switch. If sub-commands are used, an
additional version top-level command will be inserted as well.
If -d:confutilsNoColors is defined all output will be colorless.
Otherwise, native colors are used. ANSI escape sequences on UNIX.
On Windows the Windows API is used.
The development of Confutils is sponsored by Status.im through the use of GitCoin. Please take a look at our tracker for any issues having the bounty tag.
When submitting pull requests, please add test cases for any new features
or fixes and make sure nimble test is still able to execute the entire
test suite successfully.
Licensed and distributed under either of
- MIT license: LICENSE-MIT or http://opensource.org/licenses/MIT
or
- Apache License, Version 2.0, (LICENSE-APACHEv2 or http://www.apache.org/licenses/LICENSE-2.0)
at your option. This file may not be copied, modified, or distributed except according to those terms.