A Swift library for defining and parsing command line arguments.
- Command argument definition
- Arguments mirror an annotated work function's parameters
- Parsing strategy is fixed, based solely on the work function's parameters
- Work function parameters have a well defined, documented, domain
- Any argument in the work function's domain can be parsed (including, say, negative integers)
- Simple command line utilties
- Write a work function
- Annotate it with @MainFunction
- The macro wraps the work function in
main() main()parses command arguments and passes them to the work function
- The macro wraps the work function in
- Errors thrown by the work function are are caught by main and printed
- Meta-services
- All triggered by meta-flags
- Strings - e.g., print a version number
- Help screens
- Any number of them, any names
- Flexible layout - an array of ShowElements
- Any number of headers, notes, usage lines, descriptions, in any order
- Complete control over what parameters are described and in what order
- Complete control over what parameters appear in a usage line and in what order
- Everything line-wraps, including customized usage lines
- Manual pages
- Defined the same way as help screens
- Easy to add sections defined using
mdocmacros - Option descriptions can be different (i.e., more detailed) than for help
- Tree diagrams for hierarchical commands
- Diagram at top level covers the entire structure
- Optionally provide tree invocation at any depth
- Completion scripts
- Eventually will be in a separate module
- Currently included in
cmd-arg-libas a convenience - Supports fish and zsh
- Works for hierarchical command structures
- Option descriptions can be differente (i.e., terser) than for help
- Exception - a public enum that conforms to Error
case stdoutMessage(String) // Text to be printed to stdoutcase stderrMessage(String) // Text to be printed to stderrcase errors([String]) // Errors to be renedered in an ErrorScreen
- ErrorScreen
- Printed to stderr when Exception.errors(let messages) is caught by a wrapper function generated by one of the library's macros
- Shows multiple line-wrapped error messages
- Refers to help (if a help screen has been provided), including the names of the command(s) invoked
- The internal parser reports all errors encountered by throwing
Exception.errors() - Prefer
Exception.errors()to provide a consistent experience for users
- Hierarchical command trees
- Supports stateless and stateful command trees
- Each command has a command action - a work function annotated by the
@CommandActionmacro - Command work functions can be developed and debugged independently of the enclosing command
- Any command in the structure can define its own meta-services
Begin by writing a "work function" that performs all or part of your program's logic. Annotate the work function with one of three macros provided the library. The macro generates peer a function that parses command line arguments and calls the work function with the parsed values.
You can generate a fully functional command line utility, with meaningful error reporting, just by annotating a
work function with the MainFunction macro.
Code
import CmdArgLib
import CmdArgLibMacros
typealias Phrase = String
@main
struct Main{
@MainFunction
static func greet(u upper: Flag = false, count: Int = 1, _ greeting: Phrase)
{
for _ in 0..<max(count, 1) { print(upper ? greeting.uppercased() : greeting)}
}
}Command Calls
> ./greet "Hello World"
Hello World
> greet -u --count 2 "Hello World"
HELLO WORLD
HELLO WORLD
Note that the command line calls mirror Swift function calls:
greet("Hello World") // --> Hello World
greet(u: true, count: 2, "Hello World") // --> HELLO WORLD\nHHELLO WORLD
This is more evident if you think of the flag "-u" as an abreviation for "-u true".
One of the library's goals is to preserve, to the extent possible, this correspondence for work functions with parameters that have simple types, optional types, array types and variadic types, with or without labels.
This explains why the syntax of command line arguments accepted by a utiltiy produced using the library differs slightly from that of utilities produced by many other argument parsers. For example, many argument parsers allow option arguments that may or may not consume a value. This is never allowed in Swift, and, accordingly, it is not allowed when using the library.
Command Argument Errors
The main function generated by MainFunction macro detects most command argument syntax errors in a single pass:
> greet -xuy --lower --count 1.5
Errors:
unrecognized options: "-x" and "-y", in "-xuy"
unrecognized option: "--lower"
missing a "<phrase>"
"1.5" is not a valid <int>
There is no reference like "see greet --help ..." in the error screen because "--help" has not been defined.
This is the same as the previous example except that the work function has a parameter with type MetaFlag whose default value defines a help screen using an array of "show elements"."
Code
import CmdArgLib
import CmdArgLibMacros
typealias Phrase = String
//@main
struct MainWithHelp {
/// Lay out the help screen
static let showElements: [ShowElement] = [
.text("DESCRIPTION:", "Print a greeting."),
.synopsis("\nUSAGE:"),
.text("\nPARAMETERS:"),
.parameter("greeting","A friendly greeting"),
.parameter("upper", "Uppercase the greeting"),
.parameter("count", "The number of times to print the greeting"),
.parameter("help", "Show this help message."),
]
static let helpFlag = MetaFlag(helpElements: showElements)
@MainFunction
static func greet(u upper: Flag = false, count: Int = 1, _ greeting: Phrase, h__help help: MetaFlag = helpFlag)
{
for _ in 0..<max(count, 1) { print(upper ? greeting.uppercased() : greeting)}
}
}Help Screen
> ./greet --help
DESCRIPTION: Print a greeting.
USAGE: greet [-uh] [--count <int>] <phrase>
PARAMETERS:
<phrase> A friendly greeting.
-u Uppercase the greeting.
--count <int> The number of times to print the greeting (default: 1).
-h/--help Show this help message.
Error Screen
As opposed to the first example, which does hot have a help screen, the error screen refers to the "--help" meta-flag.
> ./greet
Error:
missing a "<phrase>"
See 'greet --help' for more information.
Incidentaly, manual pages are also defined using meta-flags and arrays of show elements.
In a simple command tree state is not passed from parent node to child node. Only nodes with no children perform program logic. Parent nodes (as well as childless nodes), can however have meta-flags. I.e., you can provide, or not provide, meta-flags like "--help", or "--version" at any level.
Tree Hierarchy
> ./ca1-simple --tree
cf-ca1-simple
├── greet - Print a greeting.
└── quotes
├── general - Print quotes about life in general.
└── computing - Print quotes about computing.
Help Screens
> ./ca1-simple --help
DESCRIPTION: Print a greeting or print some famous quotes.
USAGE: ca1-simple [-ht] <command>
OPTIONS:
-h/--help Show help information.
-t/--tree Show command tree.
SUBCOMMANDS:
greet Print a greeting.
quotes Print some quotes.
> ./ca1-simple quotes general --help
DESCRIPTION: Print <count> quotes about life in general.
USAGE: ca1-simple quotes general [-lu] <count>
OPTIONS:
-l Lowercase the output.
-u Uppercase the output.
NOTE: The -l and -u options shadow each other.
Command Calls
> ./ca1-simple quotes general -u 2
Quotes:
SIMPLICITY IS COMPLEXITY RESOLVED. - CONSTANTIN BRANCUSI
WELL DONE IS BETTER THAN WELL SAID. - BENJAMIN FRANKLIN
./ca1-simple quotes general -x
Errors:
unrecognized option: "-x"
missing a "<count>"
See 'ca1-simple quotes general --help' for more information.
In a stateful command tree, "state" (of type [T], where T is any sendable type) is modified and passed
from parent commands to subcommands.
This example is similar to the previous example except that the formatting options are packaged in a struct, arrays of which are passed as state starting at the top level tree node.
Tree Hierarchy
> ./ca2-stateful --tree
ca2-stateful
├── general - Print quotes about life in general.
└── computing - Print quotes about computing.
Help Screens
> ./ca2-stateful --help
DESCRIPTION: Print quotes by famous people.
USAGE: ca2-stateful [-luth] <subcommand>
OPTIONS:
-l Lowercase the output of subcommands.
-u Uppercase the output of subcommands.
-h/--help Show help information.
-t/--tree Show command tree.
NOTE:
The -l and -u options shadow each other.
SUBCOMMANDS:
general Print quotes about life in general.
computing Print quotes about computing.
> ./ca2-stateful -u computing --help
DESCRIPTION: Print <count> quotes about computing.
USAGE: ca2-stateful computing <count>
Command Calls
> ./ca2-stateful computing 1
Quote:
If a machine is expected to be infallible, it cannot also be intelligent. - Alan Turing
> ./ca2-stateful -u computing 1
Quote:
IT IS MUCH MORE REWARDING TO DO MORE WITH LESS. - DONALD KNUTH
This example wraps sed soley to demonstate some advanced features of the built-in help screen and manual page generators.
Help Screen
> ./mf8-sed --help
DESCRIPTION
A sed wrapper.
USAGE
mf8-sed [-np] [-i <extension>] <command> [<file>...]
mf8-sed [-np] [-i <extension>] [-e <command>] [-f <command_file>] [<file>...]
OPTIONS
-n/--quiet By default, each line of input is echoed to
the standard output after all of the
commands have been applied to it. The -n
option suppresses this behavior.
-p/--preview Print the genrated sed command without
executing it.
-i/--inplace <extension> Edit the <file>s in-place, saving backups
with the specified <extension>. If a
zero-length extension is given (""), no
backup will be saved.
-e/--expression <command> Append <command> to the list of editing
<command>s (may be repeated).
-f/--command-file <command_file> Append the editing <command>s found in the
file <command_file> to the list of editing
<command>s (may be repeated). The editing
commands should each be listed on a
separate line. The <command>s are read from
the standard input if <command_file> is
“-”.
--generate-manpage Generate a man page.
-h/--help Show help information.
NOTES
The mf8-sed utility reads the specified <file>s, or the standard input if no
<file>s are specified, modifying the input as specified by a list of
<command>s. The input is then written to the standard output.
A single <command> may be specified as the first argument to mf8-sed, in
which case no -e or -f options are allowed. Multiple <command>s may be
specified by using the -e or -f options, in which case all arguments are
<file>s. All <command>s are applied to the input in the order they are
specified regardless of their origin.
Regular expressions are always interpreted as extended (modern) regular
expressions.
Examples using the macros are included in the following repositories:
MainFunctionmacro - CmdArgLib_MainFunctionCallFunctionmacro - CmdArgLib_CallFunctionCommandActionmacro - CmdArgLib_CommandAction
In addition, CmdArgLib_Completion has an example that generates fish shell and zsh shell completion scripts
If you are interested in the examples, please start with CmdArgLib_MainFunction.
If you want to experiment further, you can
use cmd-arg-lib-package-manager to initialize
fully functional packages that use cmd-arg-lib. This repository is itself a good example for generating completion
scripts and manual pages for a command with subcommands.
The library's documentation is sparse. It consists of the library's reference, the example packages, and quick help for the library's public functions and methods.
The overall design of the library is based on certain design principles used by Fish Shell, paraphrased and adapted as follows:
-
“The library should have a small set of orthogonal features. Any situation where two features are related but not identical, one of them should be removed, and the other should be made powerful and general enough to handle all common use cases of either feature.”
-
“Every configuration option in a program is a place where the program is too *** to figure out for itself what the user really wants.”
-
“The library's features should be as easy as possible to discover for the user.”
-
“The command argument syntax generated by the library should be uniform.”
The library has the following specific goals:
-
The command line syntax for a program written using the library should map directly to a Swift work function.
-
Command argument syntax errors should be detected in one pass and listed collectively in an error screen.
-
The library should provide full-featured help screen and manual page generators.
-
The library should provide a uniform interface for defining any number of custom meta-flags (not just, say, "--help" and "--version").
-
The library should provide support for stateful command hierarchies.
-
The library should be complete without the need for plug-ins and auxilary tools.
This software is licensed under the Functional Source License, Version 1.1, ALv2 Future License, "FSL-1.1-ALv2", the flagship Fair Source License "FSL".
It is a simple non-compete license with eventual Open Source conversion, in this case to be licensed under the Apache License Version 2.0 "ALv2" after two years.
The intent here, in non-binding terms, is that you can use the software as is, right now, and for any purpose, subject to the terms of ALv2, except for any "Competing Use" as defined in FSL-1.1-ALv2. After two years the "Computing Use" restriction lapses.
By the way, tuist uses another Fair Source License, fair core license. This is a variant of FSL that includes license key support, used for monetizing self-hosted software with commercial features.
In contrast, the license used by cmd-arg-lib, FSL-1.1-ALv2, has no provisions for monetization.
The library is in beta phase, version 0.4.6, and has only been implemented for macOS.
- The library requires Swift 6.2 and macOS 26.1, or above.
- The library is feature complete.
- Documention is sparse.
- There are no known bugs - but internal testing has been limited.
Bug identification and feedback are welcome.