2025-08-29

(sph cli)

create command-line interfaces with guile scheme.

part of sph-lib

features

parsing

  • supports short, long, and positional arguments
  • commands and sub-commands (like "git")
  • pattern matching for positional arguments with variables/repetition: (a b ...) (a ... c d)
  • type checking (number, integer, string), with alternative types
  • results as alist or via custom handler
  • required/optional options and values
  • per-option converters, validators, processors
  • multiple patterns allowed

automatic description

  • built-in --help with option/command/argument info
  • formats required/optional values clearly
  • options and commands sorted alphabetically
  • human- and machine-readable output
  • --interface option prints machine-readable spec
  • default options overrideable

specification

  • multiple names per option
  • keyword arguments for config
  • version settable (string or int list)
  • about/help text configurable

creation

  • cli is a procedure, callable with args

  • modular composition of interfaces

  • no args = uses program-arguments

  • based on srfi-37 args-fold

  • custom handlers for:

    • missing arguments (with count)
    • unsupported options
  • common command handler

  • per-command handlers (non-conflicting)

command-line examples

example 1

the command-line interface created by development example 3 (below). we see an argument pattern, the default options and several commands with argument 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

(use-modules (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 an association list with symbols as keys ((keyname . "value") ...)
  (display (alist-ref options (quote 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

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