Thanks to visit codestin.com
Credit goes to Github.com

Skip to content

A Swift library for defining and parsing command line arguments.

License

Notifications You must be signed in to change notification settings

ouser4629/cmd-arg-lib

Repository files navigation

Command Argument Library

A Swift library for defining and parsing command line arguments.


Highlights

  • 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
    • 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 mdoc macros
      • 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-lib as 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 stdout
    • case stderrMessage(String) // Text to be printed to stderr
    • case 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 @CommandAction macro
    • Command work functions can be developed and debugged independently of the enclosing command
    • Any command in the structure can define its own meta-services

Usage

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.


Simple Command Line Utility

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.


Simple Command Line Utility With A Help Screen

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.


Simple Command Tree

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.

Stateful Command Tree

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

A sed Wrapper

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.
Manual Page
> ./mf8-sed  --manpage > mf8-man.1 && man ./mf8-man.1

ManPageInLess


Examples

Examples using the macros are included in the following repositories:

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.


Documentation

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.


Design

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.


License

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.


Project Status

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.

About

A Swift library for defining and parsing command line arguments.

Topics

Resources

License

Stars

Watchers

Forks

Languages