Command Argument Library - Beta

Hi everyone,

Command Argument Library, or cmd-arg-lib is now in beta, version 0.4.2.

During the last three months the library has evolved from a "little brother" prototype to a feature complete library.

Overview:

  • Command argument definition
    • Arguments mirror an annotated work function's parameters
    • Parsing strategy is fixed, based solely on the work function's parameters
    • Work functions 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 and printed by main()
  • 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 ShowElement
      • Any number of headers, notes, usage lines, descriptions, in any order
      • Complete control over which 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
    • 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
  • 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 an 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
    • All elements are instances of the same struct, StatefulCommand<T>
      • name, synopis, subcommands,
      • a method of type: StatefulCommandAction<T>, called when the command is encountered
    • StatefulCommandAction<T>
      • A work function annotated by the @CommandAction macro
      • The generated commandAction parses command arguments and passes them on to the work function
      • The work function is the same as for @MainFunction macro except that it takes a state parameter, [T] and returns [T]
    • There is syntatic suger for stateless commands:
      • typealias SimpleCommand = StatefulCommand<Void>
      • The work function does not have the [T] parameter
    • Command work functions can be developed and debugged independently of the enclosing Command
    • Any command in the structure can define its own meta-services

Please give cmd-arg-lib a try. I think that it has some unique aspects that showcase some of Swift's strengths. I also think that it fills a void (e.g., try generating a manual page with Swfit Argument Parser tools).

I have released an auxilary package cmd-arg-lib-package-manager which, in conjunction with swift-package-manager, makes it easy to initialize packages that use cmd-arg-lib; and easy to install the associated executable products. It provides a command line utility, calpm with two subcommands: init and install. You might want to compare calpm init with swift package init. Compare the help screens. Test the help screens' line wrapping. Try command completion. Try to use the initialized packages without referencing documentation.

Thanks for reading.

6 Likes

I made some code breaking changes to cmd-arg-cmd's BasicParameterEnum protocol.

Some of its poorly thought out computed properties have been replaced. Now we have:

  • static var casesArray: [String]
  • static func casesJoinedWith(conjunction:quoteChar,separator) -> String
  • static var spacedCases: String
  • static func orCases(prefix:suffix) -> String
  • static func andCases(prefix:suffix) -> String

A "String" enum that conforms to BasicParameterEnum can be used as the type of a parameter of a work function annotated by one of cmd-arg-lib's macros.

The new static computed properties and functions are useful when describing such parameters in an array of ShowElements.

Here an excerpt from the CmdArgLib_Completion repository.

import CmdArgLib
import CmdArgLibMacros

public enum Name: String, BasicParameterEnum {case manny, moe, jack }
public enum Vname: String, BasicParameterEnum {case manny_v, moe_v, jack_v }
public enum Fruit: String, BasicParameterEnum {case apple, banana, orange }

typealias Count = Int
typealias Text = String
typealias TextFile = String
typealias Directory = String
typealias Path = String

@main
struct Main {

    @MainFunction
    private static func cfPrint(
        c__count count: Count = 1,
        n__name names: [Name] = [],
        v__vnames vnames: Variadic<Vname> = [],
        f__file file: TextFile?,
        d__directory directory: Directory?,
        _ fruit: Fruit = .apple,
        p__path path: Path?,
        u__upper upper: Flag = false,
        generateFishCompletionScript: MetaFlag = MetaFlag(completionScriptFor: .fish, showElements: helpElements),
        generateZshCompletionScript: MetaFlag = MetaFlag(completionScriptFor: .zsh, showElements: helpElements),
        h__help help: MetaFlag = MetaFlag(helpElements: helpElements)) throws 
    { ... }
    
    private static let helpElements: [ShowElement] = [
        ...
        .text("\nARGUMENT"),
        .parameter("fruit", Fruit.orCases("One of"), .list(Fruit.casesArray)),
        .text("\nOPTIONS"),
        .parameter("count", "An integer"),
        .parameter("names", Name.orCases("One of", "(can be repeated)"), .list(Name.casesArray)),
        .parameter("vnames", Vname.orCases("One or more of"), .list(Vname.casesArray)),
        .parameter("file", "A file whose name matches '*.txt'", .file("*.txt")),
        .parameter("directory", "A directory whose name matches 'C*'", .directory("C*")),
        .parameter("path", "A path, starting from the current directory", .path),
        .parameter("upper", "Upper case flag"),
        ...
     ]
}

Here is how the strings generated by the new BasicParameterEnum functions show up in the help screen.

e> ./cf-print -h
DESCRIPTION
  Print values passed in from the command line.

USAGE
  cf-print [-uh] [-c <count>] [-n <name>] [-v <vname>...] [-f <text_file>]
           [-d <directory>] [<fruit>] [-p <path>]

ARGUMENT
  <fruit>                             One of "apple", "banana" or "orange"
                                      (default: "apple").

OPTIONS
  -c/--count <count>                  An integer (default: 1).
  -n/--name <name>                    One of "manny", "moe" or "jack" (can be
                                      repeated).
  -v/--vnames <vname>...              One or more of "manny_v", "moe_v" or
                                      "jack_v".
  -f/--file <text_file>               A file whose name matches '*.txt'.
  -d/--directory <directory>          A directory whose name matches 'C*'.
  -p/--path <path>                    A path, starting from the current
                                      directory.
  -u/--upper                          Upper case flag.

META-OPTIONS
  -h/--help                           Show this help message.
  --generate-fish-completion-script   Print a fish completion script.
  --generate-zsh-completion-script    Print a zsh completion script.

Unfortunately, all of the example repositories were broken by this change. They now need cmd-arg-lib 0.4.6

1 Like