Skip to content

Hierarchical parsing of command line arguments with the built-in command-line #16

Open
@Metaxal

Description

@Metaxal

Macro

The purpose of the first macro is to make it easy to parse command line arguments in a hierarchical way using the built-in command-line form. The second macro is an additional helper that displays the help message automatically when no command-line argument is specified at this level, which avoids the case where the user tries one argument is then has no information about what to do next.

;; Remove the first argument of the command line arguments
(define-syntax-parse-rule (shift-command-line-arguments body ...)
  (λ args
    (parameterize ([current-command-line-arguments (vector-copy (current-command-line-arguments) 1)])
      body ...)))

;; If the command line arguments are empty, re-parameterize it to
;; default to #("--help")
(define-syntax-parse-rule (parameterize-help-if-empty-ccla body ...)
  (let ([ccla (current-command-line-arguments)])
    (parameterize ([current-command-line-arguments
                    (if (vector-empty? ccla)
                      #("--help")
                      ccla)])
      body ...)))

Example

#lang racket
(require racket/cmdline
         syntax/parse/define)

;==================;
;=== The macros ===;
;==================;

;; Remove the first argument of the command line arguments
(define-syntax-parse-rule (shift-command-line-arguments body ...)
  (λ args
    (parameterize ([current-command-line-arguments (vector-copy (current-command-line-arguments) 1)])
      body ...)))

;; If the command line arguments are empty, re-parameterize it to
;; default to #("--help")
(define-syntax-parse-rule (parameterize-help-if-empty-ccla body ...)
  (let ([ccla (current-command-line-arguments)])
    (parameterize ([current-command-line-arguments
                    (if (vector-empty? ccla)
                      #("--help")
                      ccla)])
      body ...)))

;===============;
;=== Example ===;
;===============;

(define prog "my-prog")

(define (parse-relative)
  (parameterize-help-if-empty-ccla
   (command-line
    #:program (string-append prog " --relative")
    #:once-each
    [("--left") => (shift-command-line-arguments
                    (displayln "You're going left!")
                    (parse-main))
                '("Go to the left")]
    [("--right") => (shift-command-line-arguments
                    (displayln "You're going right!")
                    (parse-main))
                '("Go to the right")])))

(define (parse-absolute)
  (parameterize-help-if-empty-ccla
   (command-line
    #:program (string-append prog " --absolute")
    #:once-each
    [("--north") => (shift-command-line-arguments
                     (displayln "You're going north!")
                     (parse-main))
                 '("Go to the north")]
    [("--south") => (shift-command-line-arguments
                     (displayln "You're going south!")
                     (parse-main))
                 '("Go to the south")])))

(define (parse-move)
  (parameterize-help-if-empty-ccla
   (command-line
    #:program (string-append prog " --move")
    #:once-each
    [("--relative") => (shift-command-line-arguments (parse-relative))
                    '("Specify a relative direction")]
    [("--absolute") => (shift-command-line-arguments (parse-absolute))
                    '("Specify an absolute direction")])))

(define (parse-main)
  (command-line
   #:program prog
   #:once-each
   [("--move") => (shift-command-line-arguments (parse-move))
               '("Specify directions")]
   [("--jump") => (shift-command-line-arguments
                   (displayln "You're jumping!")
                   (parse-main))
               '("jump")]))

(module+ main
  (parse-main))

#| Interaction example:

$ racket syntax-bee.rkt --move --relative --left --jump --jump --move --absolute --south --jump
You're going left!
You're jumping!
You're jumping!
You're going south!
You're jumping!

|#

Before and After

I've heard several times that command-line can't parse arguments hierarchically. Well, turns out it's easy with syntax-parse! (although admittedly, syntax-rules would have worked too. But at least with syntax-parse it's easy to extend the macro to parse additional keywords, such as if one wants to shift the arguments by more than 1.)

These macros were written for a PR to resyntax. See the diff there.

Licence

Please confirm that you are submitting this code under the same MIT License that the Racket language uses. https://github.com/racket/racket/blob/master/racket/src/LICENSE-MIT.txt

Yes.

Please confirm that the associated text is licensed under the Creative Commons Attribution 4.0 International License http://creativecommons.org/licenses/by/4.0/

Yes.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions