In Racket, we can use command-line
to create single command with some flags.
I use
(define args (command-line ...))
(match args
[(list "command1") ...]
[(list "command2") ...]
[(list "command3") ...])
to create program with nested command. The problem is usage text will not mention these nested commands then.
Is that possible to use command-line
to complete this purpose and with proper usage help?
I think this style is good enough to be the answer.
Here's a version I've extracted from some unpublished code that is a bit less manual than Resyntax's CLI pattern, but that means there's more happening implicitly (IOW: tradeoffs apply):
#lang racket
(module+ main
(define (make-new-command-name command)
(and (current-command-name)
(format "~a ~a" (current-command-name) command)))
(define (run name c)
(parameterize ([current-command-name (make-new-command-name name)])
((command-invoke c))))
(define padding-length
(add1 (apply max (map symbol-length (hash-keys command-table)))))
(parse-command-line
(short-program+command-name)
(current-command-line-arguments)
`((usage-help "This tool is used to <do stuff>."
"Command is one of:"
,@(for/list ([(name c) (in-hash command-table)])
(format " ~a:~a\t~a"
name
(make-padding-string (symbol-length name) padding-length)
(command-description c)))))
(λ (_acc name-string . args)
(define name (string->symbol name-string))
(cond
[(hash-has-key? command-table name)
(parameterize ([current-command-line-arguments (list->vector args)])
(run name (hash-ref command-table name)))]
[else
(eprintf "~a: unknown command ~a\n" (short-program+command-name) name)
(eprintf "Expected one of: ~a\n"
(string-join (map symbol->string (hash-keys command-table)) ", "))
(exit 1)]))
'("command" "command-arguments")))
(require raco/command-name
syntax/parse/define
file/glob
db
axio
threading)
;; planet/private/command implements an svn-style command-line macro; for now,
;; we'll stick with something a bit simpler
(struct command (description invoke))
(define command-table (make-hash))
(define-syntax-parser define/command
[(_ name:id description:string body:expr ...+)
(syntax/loc this-syntax
(begin
(define (name)
body ...)
(hash-set! command-table 'name (command description name))))])
(define/command init "Initialize the <thing>"
(define opt-result "default")
(command-line
#:program (short-program+command-name)
#:once-each
[("--opt") --opt-value "Help for --opt [default]"
(set! opt-result --opt-value)]
#:args (positional)
(exit (do-init opt-result positional))))
(define (symbol-length s)
(string-length (symbol->string s)))
(define (make-padding-string occupied-length padding-length)
(make-string (- padding-length occupied-length) #\space))
;; vim: lispwords+=define/command
This code was used in a custom raco
tool, so the raco/command-name
and its exports may not be relevant for your use case.
1 Like