2017-09-18

(sph cli)

create 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 examples use the few standard scheme aliases from sph-lib

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) (tail (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

(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 (ql ((command argument ...)) (dg-root))
    #:command-options (ql (dg-root #:value-optional? #t #:type string))
    #:commands
    (ql (("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

import name

(sph cli)

exports

cli-command-match

procedure

signature

arguments commands-spec ->

list list -> false/any

cli-create

procedure

signature

config ... ->

description

::

#:version string/(integer ...)

#:about string/procedure:{-> string}

#:description string

#:help string

#:help-parameters string/boolean

#:arguments (string ...)

#:missing-arguments-handler procedure:{symbol any ...}

#:unsupported-option-handler procedure:{symbol srfi-37-option option-name ...}

#:command-handler procedure:{list:(symbol ...):command-name list:rest-arguments -> any}

#:commands commands-spec

#:command-options option-spec

#:options (option-spec ...)

option-spec ...

->

procedure:{(string ...) -> list:alist:((symbol . any) ...):parsed-arguments}

# description

commands-options: options shared between commands

commands: specify sub-commands that are recognised as leading keywords in the program arguments. with options or handler procedures

command-handler: command-handler called for all commands (after individual command handlers)

options: specify long, short and unnamed options

# data-structures

custom-processor: procedure:args-fold-processor:{opt matched-name any result ->}

input-type-names: symbol:string/number/integer

option-spec: (symbol/list:name/pattern #:names character/string/(character/string) #:required? boolean

#:value-required? boolean #:value-optional? boolean #:type symbol #:description string #:processor procedure:custom-processor)

pattern: (symbol symbol/ellipsis:... ...)

commands-spec: (((string:command-name ...) procedure:{command arguments}/[cli-create-argument ...]) ...)

sph-cli-description

variable

tags: programming guile documentation library scheme sph-lib q1 interface command-line cli highlight sph-cli