2018-11-01

(sph cli)

reate command-line interfaces

part of sph-lib

features

parsing

  • program arguments parsing for short, long and non-option arguments
  • commands/sub-commands support (for example sub-commands like "git" has)
  • pattern matching for non-option arguments with variables and repetition. examples: (a b ...) (a ... c d)
  • type-checking (number, integer, string) with support for alternative types for values
  • association lists as parser result, or alternatively the result of a custom command handler procedure
  • required and optional options/values
  • type conversion and other custom value processing handlers/validators for individual options
  • multiple patterns for non-option argument matching

automatic description

  • automatic "help" option that prints information about options, option values, commands and non-option argument patterns
  • required and optional option values are formatted appropriately
  • description texts can be specified with options
  • option list is sorted alphabetically
  • description output is printed in a machine and human readable format
  • command list is sorted alphabetically
  • "--interface" default option that displays a machine readable interface specification for automated integration/analysis
  • default options can be overridden

specification

  • multiple alternative names per option
  • use of keyword arguments
  • version can be set via string or integer list
  • about option text can be set with a keyword argument
  • help text can be set manually

creation

  • command-line interface as a procedure that can be called with command-line arguments
  • modular command-line interface composition
  • call without arguments uses program-arguments
  • uses srfi-37 args-fold
  • custom handler for handling missing arguments that receives the count of missing arguments
  • custom handler for handling unsupported options
  • common (sub-)command handler
  • individual command handler (not conflicting with common command handler)

command-line examples

example 1

the command-line interface created by development example 3 (below) we see a non-option pattern, the default options and several commands with non-option patterns

$ sph-cms --help

parameters
  options ... command argument ...
options
  --about | -a
  --help | -h
  --interface
options shared by all commands
  --dg-root[=string]
commands
  content add :: facet ...
  content delete :: content-id
  content display :: content-id
  content list :: facet ...
  content path :: content-id
  facet add :: facets content-id ...
  facet delete :: facet ...
  facet list :: content-id
  facet remove :: facets content-id ...
  facet replace :: facets facets-replacements content-id ...
  perm remove :: content-id entity-id perm-name
  perm set :: content-id entity-id perm-name
  statistics
  type list :: content-id
  type set :: content-id type-name ...
  type set-guess :: content-id
  user add :: name
  user delete :: name
  user list
  user set :: name key:value ...

example 2

more options, typed values

$ pack --help

parameters
  options ... source-path ...
description
  one program for file and directory compounding, compression and encryption
options
  --about | -a
  --compression | -c
  --delete-input-files | -d
  --dry-run
  --encryption | -e
  --help | -h
  --implicit-compression[=number] | -i [number]  add compression if source is below a certain size. the optional value is in megabits.
  --interface
  --read-paths=string | -p string
  --target=string | -t string
  --unpack | -u

example 3

$ sph-cms facet add

1 missing argument (facets content-id ...)

example 4

$ pack --xyz

unsupported option "xyz"

development examples

the module needs to have been imported

(import (sph cli))

example 1

create a minimal command-line interface parser and immediately use it with the current program arguments to create a parser result

(define arguments ((cli-create)))

is equivalent to

(define arguments ((cli-create) (cdr (program-arguments))))

example 2

(define command-line-interface
  (cli-create
    #:version "1.2"
    #:description "compile sc to c reading from files or standard-input, and writing to a file or standard-out, depending if paths are given as arguments."
    #:options
    (quote (
      ((paths ...)) (compression #:names #\c) (encryption #:names #\e) (read-paths #:names #\p #:value-required? #t)
      (target #:names #\t #:value-required? #t) (unpack #:names #\u) (delete-input-files #:names #\d) (dry-run))))

(let (arguments (command-line-interface))
  ;access to parsed values. options is just an association list with symbols as keys ((keyname . "value") ...)
  (display (alist-q-ref options paths) (list)))

;custom application
(command-line-interface (list "--help"))

example 3

comprehensive command-line interface specification with many commands and command-handler. the definitions of the cms-* command-handler procedures are not shown, but they are all procedures that take parsed arguments as one argument

(import (sph) (sph cli) (sph string))

(define (command-handler command arguments)
  (apply
    (l* (a #:optional (b ""))
      ( (string-case a
          ("facet"
            (string-case b ("add" cms-facet-add)
              ("remove" cms-facet-remove) ("list" cms-facet-list)
              ("replace" cms-facet-replace) ("delete" cms-facet-delete) identity))
          ("content"
            (string-case b ("path" cms-content-path)
              ("add" cms-content-add) ("add-and-edit" cms-content-add-and-edit)
              ("delete" cms-content-delete) ("list" cms-content-list)
              ("display" cms-content-display) identity))
          ("perm" (string-case b ("set" cms-type-set) ("remove" #t) identity))
          ("type"
            (string-case b ("list" cms-type-list)
              ("set" cms-type-set) ("set-guess" cms-type-set-guess) identity))
          ("user"
            (string-case b ("add" #t)
              ("delete" cms-user-delete) ("set" #t) ("list" cms-user-list) identity))
          ("statistics" cms-statistics) identity)
        arguments))
    command))

(define command-line-interface
  (cli-create #:options (list-q ((command argument ...)) (dg-root))
    #:command-options (list-q (dg-root #:value-optional? #t #:type string))
    #:commands
    (list-q (("facet" "add") ((facets content-id ...) #:required? #t))
      (("facet" "remove") ((facets content-id ...) #:required? #t)) (("facet" "list") ((content-id)))
      (("facet" "delete") ((facet ...)))
      (("facet" "replace") ((facets facets-replacements content-id ...) #:required? #t))
      (("type" "list") ((content-id)))
      (("type" "set") ((content-id type-name ...) #f #:required? #t))
      (("type" "set-guess") ((content-id) #:required? #t))
      (("content" "delete") ((content-id) #:required? #t))
      (("content" "path") ((content-id) #:required? #t))
      (("content" "display") ((content-id) #:required? #t))
      (("content" "list") ((facet ...)) (show-only-ids))
      ( ("content" "add") ((facet ...)) (type #:value-optional? #t #:type string)
        (file #:value-optional? #t #:type string) (display-id-only))
      ( ("content" "add-and-edit") ((facet ...)) (type #:value-optional? #t #:type string)
        (file #:value-optional? #t #:type string) (display-id-only))
      (("user" "list")) (("user" "delete") ((name) #:required? #t))
      "statistics"
      ;implement when needed
      (("user" "add") ((name) #:required? #t)) (("user" "set") ((name key:value ...) #:required? #t))
      (("perm" "set") ((content-id entity-id perm-name) #:required? #t))
      (("perm" "remove") ((content-id entity-id perm-name) #:required? #t)))
    #:command-handler command-handler #:about "manage sph-cms content"))

(command-line-interface)

possible enhancements

  • "one-or-more" repetition for non-option patterns

similar libraries

  • commander.js for node.js