diff --git a/doc/reference/getopt.md b/doc/reference/getopt.md index 15be2222b..726ef89f1 100644 --- a/doc/reference/getopt.md +++ b/doc/reference/getopt.md @@ -9,9 +9,7 @@ The `:std/getopt` library provides facilities for command line argument parsing. ## Interface ### getopt - -::: tip usage -``` +```scheme (getopt ...) => @@ -27,19 +25,17 @@ cmd-specifier: (rest-arguments id [help: text] [value: proc]) ``` -::: + `getopt` creates a command line parser, which can be used to parse arguments with `getopt-parse`. ### getopt-parse - -::: tip usage -``` +```scheme (getopt-parse args) => (values cmd-id options) options ``` -::: + `getopt-parse` accepts a parser and a list of string arguments and parses according to the parser specification. If it is parsing a specification with subcommands, it returns two values, the command id and a hash table with the @@ -47,21 +43,16 @@ parsed options. Otherwise it just returns the hash table with the parsed options An exception is raised if parsing the arguments fails. ### getopt-error? - -::: tip usage -``` +```scheme (getopt-error? obj) => boolean ``` -::: If parsing fails, then a `getopt-error` is raised, which can be guarded with `getopt-error?`. ### getopt-display-help - -::: tip usage -``` +```scheme (getopt-display-help program-name [port = (current-output-port)]) @@ -70,46 +61,58 @@ tip: ``` -::: The procedure `getopt-display-help` can be used to display a help message for a getopt error according to the argument specification. ### getopt-display-help-topic -::: tip usage -``` +```scheme (getopt-display-help-topic topic program-name [port = (current-output-port)]) ``` -::: The procedure `getopt-display-help-topic` can be used to display a help page for a subcommand. ### getopt? - -::: tip usage -``` +```scheme (getopt? obj) => boolean ``` -::: Returns true if the object is a getopt parser ### getopt-object? - -::: tip usage -``` +```scheme (getopt-object? obj) => boolean ``` -::: Returns true if the object is a getopt command or command specifier. +### call-with-getopt +```scheme +(call-with-getopt proc args + program: program + help: (help #f) + exit-on-error: (exit-on-error? #t) + . gopts) +``` + +This shim around getopt parsing eliminates all the repetitive +boilerplate around argument parsing with getopt. + +It creates a getopt parser that parses with options `gopts`, automatically +including a help option or command accordingly. + +It then uses the parser to pare `args`, handling the exceptions and +displayin help accordingly; if `exit-on-error` is true (the default), +then parsing errors will exit the program. + +If the parse succeeds it invokes `proc` with the output of the parse. + ## Example -For an example, here is a command line parser for the `gxpkg` program: +For an example, here the a command line parser for the `gxpkg` program: ```scheme (def (main . args) (def install-cmd @@ -140,49 +143,46 @@ For an example, here is a command line parser for the `gxpkg` program: (command 'list help: "list installed packages")) (def retag-cmd (command 'retag help: "retag installed packages")) - (def help-cmd - (command 'help help: "display help; help for command help" - (optional-argument 'command value: string->symbol))) - (def gopt - (getopt install-cmd - uninstall-cmd - update-cmd - link-cmd - unlink-cmd - build-cmd - clean-cmd - list-cmd - retag-cmd - help-cmd)) - - (try - (let ((values cmd opt) (getopt-parse gopt args)) - (let-hash opt - (case cmd - ((install) - (install-pkgs .pkg)) - ((uninstall) - (uninstall-pkgs .pkg .?force)) - ((update) - (update-pkgs .pkg)) - ((link) - (link-pkg .pkg .src)) - ((unlink) - (unlink-pkgs .pkg .?force)) - ((build) - (build-pkgs .pkg)) - ((clean) - (clean-pkgs .pkg)) - ((list) - (list-pkgs)) - ((retag) - (retag-pkgs)) - ((help) - (getopt-display-help-topic gopt .?command "gxkpg"))))) - (catch (getopt-error? exn) - (getopt-display-help exn "gxpkg" (current-error-port)) - (exit 1)) - (catch (e) - (display-exception e (current-error-port)) - (exit 2)))) + (def search-cmd + (command 'search help: "search the package directory" + (rest-arguments 'keywords help: "keywords to search for"))) + + (call-with-getopt gxpkg-main args + program: "gxpkg" + help: "The Gerbil Package Manager" + install-cmd + uninstall-cmd + update-cmd + link-cmd + unlink-cmd + build-cmd + clean-cmd + list-cmd + retag-cmd + search-cmd)) + +(def (gxpkg-main cmd opt) + (let-hash opt + (case cmd + ((install) + (install-pkgs .pkg)) + ((uninstall) + (uninstall-pkgs .pkg .?force)) + ((update) + (update-pkgs .pkg)) + ((link) + (link-pkg .pkg .src)) + ((unlink) + (unlink-pkgs .pkg .?force)) + ((build) + (build-pkgs .pkg)) + ((clean) + (clean-pkgs .pkg)) + ((list) + (list-pkgs)) + ((retag) + (retag-pkgs)) + ((search) + (search-pkgs .keywords))))) + ``` diff --git a/src/std/build-deps b/src/std/build-deps index eef9566b1..cc9bfe188 100644 --- a/src/std/build-deps +++ b/src/std/build-deps @@ -204,7 +204,9 @@ gerbil/gambit/ports std/misc/repr)) (std/assert "assert" (gerbil/core gerbil/expander std/format std/sugar)) - (std/getopt "getopt" (gerbil/core std/error std/format)) + (std/getopt + "getopt" + (gerbil/core gerbil/gambit/exceptions std/error std/format std/sugar)) (std/logger "logger" (gerbil/core diff --git a/src/std/getopt.ss b/src/std/getopt.ss index e4fe91149..b0e2d0577 100644 --- a/src/std/getopt.ss +++ b/src/std/getopt.ss @@ -2,7 +2,9 @@ ;;; (C) vyzo ;;; Command-line option and command argument parsing -(import :std/error +(import :gerbil/gambit/exceptions + :std/error + :std/sugar :std/format) (export getopt (rename: !getopt? getopt?) @@ -17,6 +19,7 @@ argument optional-argument rest-arguments + call-with-getopt ) (defstruct (getopt-error ) (e)) @@ -351,3 +354,50 @@ (if (fx< len tablen) (make-string (fx- tablen len) #\space) ""))) + +(def (call-with-getopt proc args + program: program + help: (help #f) + exit-on-error: (exit-on-error? #t) + . gopts) + (def (parse! gopt return) + (try + (getopt-parse gopt args) + (catch (getopt-error? exn) + (getopt-display-help exn program (current-error-port)) + (if exit-on-error? + (exit 1) + (return 'error))) + (catch (e) + (display-exception e (current-error-port)) + (if exit-on-error? + (exit 2) + (return 'error))))) + + (let/cc return + (let* ((gopt (apply getopt help: help gopts)) + (cmds (!getopt-cmds gopt))) + (if (null? cmds) + ;; it only has options; add -h/--help + (let ((help-flag + (flag 'help "-h" "--help" + help: "display help")) + (opts (!getopt-opts gopt))) + (if (null? opts) + (set! (!getopt-opts gopt) + [help-flag]) + (set-cdr! (last-pair opts) + [help-flag])) + (let (opt (parse! gopt return)) + (if (hash-get opt 'help) + (getopt-display-help gopt program) + (proc opt)))) + ;; it has commands; add help + (let (help-cmd + (command 'help help: "display help; help for command help" + (optional-argument 'command value: string->symbol))) + (set-cdr! (last-pair cmds) [help-cmd]) + (let ((values cmd opt) (parse! gopt return)) + (if (eq? cmd 'help) + (getopt-display-help-topic gopt (hash-get opt 'command) program) + (proc cmd opt)))))))) diff --git a/src/tools/gxpkg.ss b/src/tools/gxpkg.ss index 4ddcac9c5..43ca2ad88 100644 --- a/src/tools/gxpkg.ss +++ b/src/tools/gxpkg.ss @@ -73,56 +73,44 @@ (def search-cmd (command 'search help: "search the package directory" (rest-arguments 'keywords help: "keywords to search for"))) - (def help-cmd - (command 'help help: "display help; help for command help" - (optional-argument 'command value: string->symbol))) - (def gopt - (getopt - help: "the Gerbil Package Manager" - install-cmd - uninstall-cmd - update-cmd - link-cmd - unlink-cmd - build-cmd - clean-cmd - list-cmd - retag-cmd - search-cmd - help-cmd)) - - (try - (let ((values cmd opt) (getopt-parse gopt args)) - (let-hash opt - (case cmd - ((install) - (install-pkgs .pkg)) - ((uninstall) - (uninstall-pkgs .pkg .?force)) - ((update) - (update-pkgs .pkg)) - ((link) - (link-pkg .pkg .src)) - ((unlink) - (unlink-pkgs .pkg .?force)) - ((build) - (build-pkgs .pkg)) - ((clean) - (clean-pkgs .pkg)) - ((list) - (list-pkgs)) - ((retag) - (retag-pkgs)) - ((search) - (search-pkgs .keywords)) - ((help) - (getopt-display-help-topic gopt .?command "gxkpg"))))) - (catch (getopt-error? exn) - (getopt-display-help exn "gxpkg" (current-error-port)) - (exit 1)) - (catch (e) - (display-exception e (current-error-port)) - (exit 2)))) + + (call-with-getopt gxpkg-main args + program: "gxpkg" + help: "The Gerbil Package Manager" + install-cmd + uninstall-cmd + update-cmd + link-cmd + unlink-cmd + build-cmd + clean-cmd + list-cmd + retag-cmd + search-cmd)) + +(def (gxpkg-main cmd opt) + (let-hash opt + (case cmd + ((install) + (install-pkgs .pkg)) + ((uninstall) + (uninstall-pkgs .pkg .?force)) + ((update) + (update-pkgs .pkg)) + ((link) + (link-pkg .pkg .src)) + ((unlink) + (unlink-pkgs .pkg .?force)) + ((build) + (build-pkgs .pkg)) + ((clean) + (clean-pkgs .pkg)) + ((list) + (list-pkgs)) + ((retag) + (retag-pkgs)) + ((search) + (search-pkgs .keywords))))) ;;; commands (defrules fold-pkgs () diff --git a/src/tools/gxtags.ss b/src/tools/gxtags.ss index 73bef9e52..3e3c375dc 100644 --- a/src/tools/gxtags.ss +++ b/src/tools/gxtags.ss @@ -17,36 +17,20 @@ (export main make-tags) (def (main . args) - (def gopt - (getopt - help: "generate emacs tags for Gerbil code" - (flag 'append "-a" - help: "append to existing tag file") - (option 'output "-o" default: "TAGS" - help: "explicit name of file for tag table") - (flag 'help "-h" "--help" - help: "display help") - (rest-arguments 'input - help: "source file or directory"))) - - (def (help what) - (getopt-display-help what "gxtags")) - - (try - (let (opt (getopt-parse gopt args)) - (if (hash-get opt 'help) - (help gopt) - (let (inputs (hash-get opt 'input)) - (if (null? inputs) - (begin - (help gopt) - (exit 1)) - (run (hash-get opt 'input) - (hash-get opt 'output) - (hash-get opt 'append)))))) - (catch (getopt-error? exn) - (help exn) - (exit 1)))) + (call-with-getopt gxtags-main args + program: "gxtags" + help: "generate emacs tags for Gerbil code" + (flag 'append "-a" + help: "append to existing tag file") + (option 'output "-o" default: "TAGS" + help: "explicit name of file for tag table") + (rest-arguments 'input + help: "source file or directory"))) + +(def (gxtags-main opt) + (run (hash-ref opt 'input ["."]) + (hash-get opt 'output) + (hash-get opt 'append))) (def (run inputs tagfile append?) (_gx#load-expander!) diff --git a/src/tools/gxtest.ss b/src/tools/gxtest.ss index 9c4d9a289..92cffaefe 100644 --- a/src/tools/gxtest.ss +++ b/src/tools/gxtest.ss @@ -14,9 +14,9 @@ (export main) (def (main . args) - (def gopt - (getopt - help: "run Gerbil tests in the command line" + (call-with-getopt gxtest-main args + program: "gxtest" + help: "run Gerbil tests in the command line" (flag 'verbose "-v" help: "run in verbose mode where all test execution progress is displayed in stdout.") (option 'run "-r" "--run" @@ -24,29 +24,16 @@ ;; TODO this should be a multi-option for multiple features (option 'features "-D" help: "define one or more conditional expansion feature (comma separated) for enabling tests that require external services") - (flag 'help "-h" "--help" - help: "display help") (rest-arguments 'args help: "test files or directories to execute tests in; appending /... to a directory will recursively execute or tests in it. If no arguments are passed, all tests in the current directory are executed."))) - (def (help what) - (getopt-display-help what "gxtest")) - - (try - (let (opt (getopt-parse gopt args)) - (let-hash opt - (cond - (.?help (help gopt)) - ((null? .args) - (run-tests ["."] .run .features .?verbose)) - (else - (run-tests .args .run .features .?verbose))))) - (catch (getopt-error? exn) - (help exn) - (exit 1)) - (catch (e) - (display-exception e (current-error-port)) - (exit 2)))) +(def (gxtest-main opt) + (let-hash opt + (cond + ((null? .args) + (run-tests ["."] .run .features .?verbose)) + (else + (run-tests .args .run .features .?verbose))))) (def (run-tests args filter features verbose?) (def import-errors [])