12 Command Line Interface
Xiden’s command line interface emphasizes full control over the runtime configuration at the cost of short commands. All commands generate exact program definitions, such that unit tests can verify the full behavior of a Xiden process.
This API is private, and may change. To understand this section, you must understand parse-command-line.
12.1 Command Line Flags
(require xiden/cli-flag) | package: xiden |
When parsing command line flags, Xiden does not store user-defined values as a side effect. Instead, it defers binding values from a command line to settings. Once a process is ready to determine a runtime configuration, it binds values from a command line using call-with-bound-cli-flags.
In addition to the below, xiden/cli-flag provides many identifiers bound to element of all-flags.
struct
(struct cli-flag ( setting kind additional-flag-strings arity convert help-strings) #:transparent) setting : setting? kind : (or/c 'once-each 'once-any 'multi) additional-flag-strings : (listof string?) arity : exact-nonnegative-integer? convert : procedure? help-strings : (listof string?)
An instance of this structure corresponds to a suitable flag table entry in parse-command-line. The value of kind has the same meaning as it does in parse-command-line.
An instance is used to generate deferred bindings for setting, such that the strings passed in the command line are turned into a Racket values for the setting using convert.
Each instance of cli-flag implicitly carries a longform flag equal to (format "--~a" (setting-id setting)). additional-flag-strings holds acceptable flags recognized by an instance.
convert and help-strings are related in the same way as they are in a program using parse-command-line (see example below). However, arity describes the number of formals accepted by convert minus the flag argument. Therefore it must be the case that (equal? (procedure-arity convert) (add1 arity)).
The below example shows a setting that holds a list of lists, where each sublist holds three numbers. The following cli-flag instance describes the corresponding command line flag and the conversion from user-provided strings to a valid value for the setting.
(define-setting TRIPLET_LIST (listof (list/c real? real? real?)) null) (define --triplet (cli-flag TRIPLET_LIST 'multi '("-t" "--triplet") 3 (λ (flag a b c) (cons (list (string->number a) (string->number b) (string->number c)) (TRIPLET_LIST))) '("number" "number" "number")))
struct
(struct cli-flag-state (flag-string flag-definition bind) #:transparent) flag-string : string? flag-definition : cli-flag? bind : (-> (-> any) any)
flag-string is bound to the exact flag used by the user. bind applies a given thunk such that the setting in flag-definition is bound to a user-defined value in the extent of said thunk.
A particular use of --triplet from the earlier example could generate this state.
; Generated from -t 9 7 22 -t 10 0 0 -t 2 4 5 (cli-flag-state "-t" --triplet (lambda (proc) (TRIPLET_LIST '((2 4 5) (10 0 0) (9 7 22)) proc)))
procedure
(find-cli-flag s) → (or/c #f cli-flag?)
s : setting?
procedure
(make-cli-flag-table c ...) → list?
c : cli-flag?
procedure
(cli-flag-strings c) → (non-empty-listof string?)
c : cli-flag?
procedure
(shortest-cli-flag c) → string?
c : cli-flag?
procedure
(format-cli-flags c) → string?
c : cli-flag?
procedure
(call-with-bound-cli-flags flag-states continue) → any flag-states : (listof cli-flag-state?) continue : (-> any)
12.2 Command Line Utilities
(require xiden/cmdline) | package: xiden |
xiden/cmdline provides all bindings from racket/cmdline, as well as the bindings documented in this section. Unlike xiden/cli, xiden/cmdline does not actually define a command line interface. It only helps xiden/cli do so.
12.2.1 CLI Value Types
value
exit-code/c : flat-contract? = (integer-in 0 255)
Xiden does not currently lean on the exit code to convey much meaning, so expect to see 1 (E_FAIL) to represent an error state. Lean on the program log for specifics.
value
exit-handler/c : contract? = (-> exit-code/c any)
value
= (-> (-> exit-code/c messy-log/c any) any)
The only argument is a procedure (probably representing a continuation) that accepts an exit code and a messy log representing program output. That continuation will decide how to react to the given information.
value
= (-> arguments/c (values (listof cli-flag-state?) bound-program/c))
12.2.2 CLI Flow Control
procedure
(run-entry-point! args formatter parse-args on-exit) → any args : arguments/c formatter : message-formatter/c parse-args : argument-parser/c on-exit : exit-handler/c
This module mimics production behavior.
(module+ main (run-entry-point! (current-command-line-arguments) (get-message-formatter) top-level-cli exit))
procedure
(cli #:program program [ #:flags flags #:args args #:help-suffix-string-key help-suffix-string-key] #:arg-help-strings arg-help-strings handle-arguments)
→
(listof cli-flag-state?) bound-program/c program : string? flags : list? = null
args : (or/c (vector/c string?) (listof string?)) = (current-command-line-arguments) help-suffix-string-key : (or/c #f symbol?) = #f arg-help-strings : (listof string?)
handle-arguments :
(->* ((listof cli-flag-state?)) () #:rest list? (values (listof cli-flag-state?) bound-program/c))
Applies the following expression:
(parse-command-line program argv flags handle-arguments arg-help-strings handle-help)
Note that some arguments used as-is. Others are computed towards the same ends.
argv is bound to a vector coerced from args.
handle-help is configured to generates a program that uses help-suffix-string-key to add localized contextual help. The exit code from such a program is 0 if the user actually requested help in args. 1 otherwise. The same help suffix is used in the event the user does not pass enough positional arguments for handle-arguments.
The following example shows the relationship between command line parsing, a runtime configuration based on flags, and actual program execution.
(define (say-hello . args) (cli #:program "hi" #:flags null #:args args #:arg-help-strings '("names") (lambda (flags . names) (values flags (lambda (halt) (halt 0 (for/list ([name names]) ($show-string (format "Hello, ~a!" name))))))))) (define-values (flags program) (say-hello "john" "sage" "mary")) (define message->string (get-message-formatter)) (call-with-bound-cli-flags flags (lambda () (program (lambda (exit-code messages) (printf "Exit code: ~a~n" exit-code) (for ([m messages]) (write-message m message->string))))))
12.2.3 CLI Messages
struct
(struct $cli:undefined-command $cli (command) #:prefab) command : string?
struct
(struct $cli:show-help $cli (body-string string-suffix-key) #:prefab) body-string : string? string-suffix-key : (or/c #f symbol?)
12.3 Command Line Argument Parsers
(require xiden/cli) | package: xiden |
xiden/cli implements the command line parsers for Xiden in terms of xiden/cmdline. Each parser prepares a program that uses continuation passing style to return output and an exit code.
(submod xiden/cli main) is the entry point for the xiden and raco zcpkg commands.
value
top-level-cli : argument-parser/c
value
do-command : argument-parser/c
Otherwise, the command will build a transaction where command-line flags add work to execute in reading order.
value
gc-command : argument-parser/c
Assuming no exceptional behavior, the bound program halts with exit code 0 with output ($finished-collecting-garbage (xiden-collect-garbage)).
value
show-command : argument-parser/c
If A is "config", the program halts with exit code 0 and output ($show-datum (dump-xiden-settings)).
If A is "installed", the program halts with exit code 0 and output (list ($show-string S) ...), where S is a string formatted to show a package query, a package output and a directory path. The list represents all installed outputs in the target workspace.
If "links", the program halts with exit code 0 and output (list ($show-string L) ...), where L is a string containing a path to a symbolic link and a path to another filesystem entry referenced by that link. These symbolic links are special in that they are tracked by Xiden for garbage collection purposes.
If "workspace", the program halts with exit code 0 and output ($show-string (path->string (workspace-directory))).
In all other cases, the program halts with exit code 1 and output ($cli:undefined-command A).
value
fetch-command : argument-parser/c
A is treated as a string representation of a datum to evaluate using eval-untrusted-source-expression. If the result is a source and the evaluation produced no I/O (for security), then the command sents bytes produced from the source to (current-output-port). Information about the process is sent to (current-error-port).
This all happens under a runtime configuration, so transfers can be halted by settings like XIDEN_FETCH_TOTAL_SIZE_MB.
value
mkinput-command : argument-parser/c
The program generates an input expression, with a sources form containing each of the arguments. The data used to compute the integrity and signature information is read from current-input-port.