Coder Social home page Coder Social logo

unix-opts's Introduction

Unix-style command line options parser

License MIT Build Status Quicklisp

This is a minimalistic parser of command line options. The main advantage of the library is the ability to concisely define command line options once and then use this definition for parsing and extraction of command line arguments, as well as printing description of command line options (you get --help for free). This way you don't need to repeat yourself. Also, unix-opts doesn't depend on anything and allows to precisely control behavior of the parser via Common Lisp restarts.

Inspired by Haskell's optparse-applicative and Python's argparse.

It is portable accross implementations.

Installation

Copy files of this library in any place where ASDF can find them. Then you can use it in system definitions and ASDF will take care of the rest.

Via Quicklisp (recommended):

(ql:quickload "unix-opts")

Now you can also use its shorter nickname opts.

Functions

option condition

Take a condition condition (unknown-option, missing-arg, or arg-parser-failed) and return a string representing the option in question.


raw-arg condition

Take a condition of type arg-parser-failed and return the raw argument string.


define-opts &rest descriptions

Define command line options. Arguments of this macro must be plists containing various parameters. Here we enumerate all allowed parameters:

  • :name—keyword that will be included in list returned by get-opts function if actual option is supplied by user.

  • :description—description of the option (it will be used in describe function). This argument is optional, but it's recommended to supply it.

  • :short—single character, short variant of the option. You may omit this argument if you supply :long variant of option.

  • :long—string, long variant of option. You may omit this argument if you supply :short variant of option.

  • :arg-parser—if actual option must take an argument, supply this argument, it must be a function that takes a string and parses it.

  • :meta-var—if actual option requires an argument, this is how it will be printed in option description.

  • :required—whether the option is required. This only makes sense if the option takes an argument.

  • :default—the default value used if the option was not found. This can either be a function (which will be called to generate the default value) or a literal value. This option cannot be combined with :required. The default value will not be provided to the arg-parser.


argv

Return a list of the program's arguments, including the command used to execute the program as the first element of the list. Portable across implementations.


get-opts &optional options

Parse command line options. If options is given, it should be a list to parse. If it's not given, the function will use the argv function to get the list of command line arguments.

Returns two values:

  • a list that contains keywords associated with command line options with define-opts macro, and
  • a list of free arguments.

If some option requires an argument, you can use getf to test the presence of the option and get its argument if the option is present.

The parser may signal various conditions. Let's list them all specifying which restarts are available for every condition, and what kind of information the programmer can extract from the conditions.

  • unknown-option is thrown when the parser encounters an unknown (not previously defined with define-opts) option. Use the option reader to get the name of the option (string). Available restarts: use-value (substitute the option and try again), skip-option (ignore the option).

  • missing-arg is thrown when some option wants an argument, but there is no such argument given. Use the option reader to get the name of the option (string). Available restarts: use-value (supplied value will be used), skip-option (ignore the option).

  • arg-parser-failed is thrown when some option wants an argument, it's given but cannot be parsed by argument parser. Use the option reader to get name of the option (string) and raw-arg to get raw string representing the argument before parsing. Available restarts: use-value (supplied value will be used), skip-option (ignore the option), reparse-arg (supplied string will be parsed instead).

  • missing-required-option is thrown when a required option cannot be found. Use the missing-options reader to get all option objects that are missing. Available restarts: use-value (supplied list of values will be used), skip-option (ignore all these options, effectively binding them to nil)

describe &key prefix suffix usage-of args stream

Return a string describing all the options of the program that were defined with the previous define-opts macro. You can supply prefix and suffix arguments that will be printed before and after the options respectively. If usage-of is supplied, it should be a string, the name of the program for an "Usage: " section. This section is only printed if this name is given. If your program takes arguments (apart from options), you can specify how to print them in "Usage: " section with an args option (should be a string designator). Output goes to stream (default value is *standard-output*).


exit &optional (status 0)

Exit the program returning status.

Example

Go to the example directory. Now, you can use example.lisp file to see if unix-opts is cool enough for you to use. SBCL users can use example.sh file.

Take a look at example.lisp and you will see that the library is pretty sexy! Basically, we have defined all the options just like this:

(opts:define-opts
  (:name :help
   :description "print this help text"
   :short #\h
   :long "help")
  (:name :verbose
   :description "verbose output"
   :short #\v
   :long "verbose")
  (:name :level
   :description "the program will run on LEVEL level"
   :short #\l
   :long "level"
   :required t
   :arg-parser #'parse-integer
   :meta-var "LEVEL")
  (:name :output
   :description "redirect output to file FILE"
   :short #\o
   :long "output"
   :arg-parser #'identity
   :meta-var "FILE"))

and we read them with (opts:get-opts) which returns two values, a list of parsed options and the remaining arguments, so:

(multiple-value-bind (options free-args)
    (opts:get-opts)
  (if (getf options :verbose)
      ...

See the example for helpers and how to handle malformed or incomplete arguments.

And here is some action:

$ sh example.sh --help
example—program to demonstrate unix-opts library

Usage: example.sh [-h|--help] [-v|--verbose] [-l|--level LEVEL]
                  [-o|--output FILE] [FREE-ARGS]

Available options:
  -h, --help               print this help text
  -v, --verbose            verbose output
  -l, --level LEVEL        the program will run on LEVEL level
  -o, --output FILE        redirect output to file FILE

so that's how it works…
free args:

$ sh example.sh --level 1 -v file1.txt file2.txt
OK, running in verbose mode…
I see you've supplied level option, you want 1 level!
free args: file1.txt, file2.txt

$ sh example.sh --level 10 --output foo.txt bar.txt
I see you've supplied level option, you want 10 level!
I see you want to output the stuff to "foo.txt"!
free args: bar.txt

$ sh example.sh --level kitty foo.txt
fatal: cannot parse "kitty" as argument of "--level"
free args:

$ sh example.sh --hoola-boola noola.txt
warning: "--hoola-boola" option is unknown!
fatal: missing required options: "--level"

$ sh example.sh -vgl=10
warning: "-g" option is unknown!
OK, running in verbose mode…
I see you've supplied level option, you want 10 level!
free args:

License

Copyright © 2015–2018 Mark Karpov

Distributed under MIT License.

unix-opts's People

Contributors

hineios avatar libre-man avatar mrkkrp avatar travv0 avatar vindarel avatar williamyaoh avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

unix-opts's Issues

Required options in conflict with typical use of --help

A possibility to defining an option as required is great idea, but this is in conflict with typical use of --help or --version. When I start a program only with --help I expect only a usage output, and version number for --version. In this case the required options should be ignored. When a start the program for "normal use" the required options should be checked. At the moment I get an error message fatal: missing required options: "--level" when I start example.sh -h.

Can you solve this problem?

Unbound variable: CCL::COMMAND-LINE-ARGUMENTS on CCL

I'm using CCL 1.11.5. When using unix-opts, I get the following error: Unbound variable: CCL::COMMAND-LINE-ARGUMENTS on CCL. Looking at the code, it appears that both #+:ccl and #+clozure are being triggered, resulting in the wrong variable being used. According to the docs, *command-line-argument-list* is the correct variable to use.

Here's the contents of *features* with both :ccl and :clozure:

(:QUICKLISP :ASDF-PACKAGE-SYSTEM :ASDF3.1 :ASDF3 :ASDF2 :ASDF :OS-UNIX :ASDF-UNICODE :PRIMARY-CLASSES :COMMON-LISP :OPENMCL :CCL :CCL-1.2 :CCL-1.3 :CCL-1.4 :CCL-1.5 :CCL-1.6 :CCL-1.7 :CCL-1.8 :CCL-1.9 :CCL-1.10 :CCL-1.11 :CLOZURE :CLOZURE-COMMON-LISP :ANSI-CL :UNIX :OPENMCL-UNICODE-STRINGS :IPV6 :OPENMCL-NATIVE-THREADS :OPENMCL-PARTIAL-MOP :MCL-COMMON-MOP-SUBSET :OPENMCL-MOP-2 :OPENMCL-PRIVATE-HASH-TABLES :STATIC-CONSES-SHOULD-WORK-WITH-EGC-IN-CCL :X86-64 :X86_64 :X86-TARGET :X86-HOST :X8664-TARGET :X8664-HOST :LINUX-HOST :LINUX-TARGET :LINUXX86-TARGET :LINUXX8664-TARGET :LINUXX8664-HOST :64-BIT-TARGET :64-BIT-HOST :LINUX :LITTLE-ENDIAN-TARGET :LITTLE-ENDIAN-HOST)

A shorter unix-opts:define-opts for "common-case" worth the code?

The following macro

(defmacro expand-opts (&rest descriptions)
  "Each description should be of the format (name-as-keyword description &optional parser)."
  `(opts:define-opts
     ,@(let* ((used-short-names (make-hash-set))
              (get-unique-short-name
               (lambda (name)
                 (iter (for ch in-string name)
                       (when (and (not (hs-memberp used-short-names ch))
                                  (alpha-char-p ch))
                         (hs-ninsert used-short-names ch)
                         (return ch))
                       (finally (error #?"Could not find a unique short name for ${name}"))))))
         (iter (for description in descriptions)
               (destructuring-bind (name description &optional parser short) description
                 (let ((name-string (string-downcase (symbol-name name))))
                   (collect `(:name ,name
                                    :description ,description
                                    :short ,(or short (funcall get-unique-short-name
                                                               name-string))
                                    :long ,name-string
                                    :arg-parser ,parser))))))))

lets me use something shorter instead of unix-opts:define-opts:

(expand-opts
 (:help "Show this help text.")
 (:port "Port number on which to run the server." #'parse-integer)
 (:swank-port "Port number at which to start swank [default: 8080]" #'parse-integer)
 (:debug "Run in debug mode if specified" #'identity))

This doesn't take into account the case for :meta-var and :required, which I'd hope isn't "common case" either, or may be :required is, and can be easily accomodated.

Description Text Alignment and Spacing

First off, thank you for the nice library.

I have noticed that if the combination of the short opt, long opt, and meta-var is longer than the default padding before the description text, it will run into the description text and push it over without leaving a space in the help message:

-h, --help               Print this help text.
-o, --long-opt-is-long META VAR IS LONGThis is the description text.

It would be nice if the padding before the description text would automatically adjust or at least if a space would be put before the description text in this case. Also, is there some way to have the description text take up multiple lines but have each new line start at the same position (instead of all the way to the left)? For example:

-h, --help               Print this help text.
                         Print this next line aligned.

I think that would be nice as well.

On an unrelated note, emacs is indenting the args to define-opts weirdly for me. The first two "(:name ...)"s are being indented 4 spaces and the rest of them are being indented 2 spaces. Would you happen to have any idea why this is happening?

(define-opts
    (:name :help
     ...)
    (:name ...)
  (:name ...)
  (:name ...))

get-opts uses argv when nil is explicitly passed as argument

This seems to not match up with the behavior described in its docstring which states:

If OPTIONS is given, it should be a list to
parse. If it's not given, the function will use `argv' function to get list
of command line arguments.

This led me to believe that argv would only be used when the optional argument wasn't passed, not when nil was passed as the optional argument.

I can submit a pull request if you'd like, but I wanted to get your thoughts first since this change would potentially be breaking.

Making a new release?

Hi!

I would like to upgrade the version of this library included in guix (as the packaged version is still the 0.1.7 from more than three year ago) but guix people wrote me that would prefer to package an official release instead of an arbitrary commit (and this makes sense to me). As there are a bunch on new feature and bugfix in the current state of unix-opts i wonder if would consider tagging a new release.

Thanks in advance!
C.

PS: for reference here is the thread on guix issue tracker:
https://issues.guix.gnu.org/issue/48291

unix-opts seems to be losing a few options

It appears impossible to pass -b, -d or --debug to unix-opts, at least in CCL:

(matrix) dfm@helmsley:/tmp$ cat show-args.lisp 
(eval-when (:compile-toplevel :load-toplevel :execute)
  (load "~/quicklisp/setup.lisp")
  (ql:quickload :unix-opts))

(save-application "show-args"
                  :toplevel-function #'(lambda ()
                                         (print (opts:argv))
                                         (terpri))
                  :prepend-kernel t)
(matrix) dfm@helmsley:/tmp$ ccl -V
Version 1.11-r16635  (LinuxX8664)
(matrix) dfm@helmsley:/tmp$ ccl -n -l ~/quicklisp/setup.lisp -l show-args.lisp 
To load "unix-opts":
  Load 1 ASDF system:
    unix-opts
; Loading "unix-opts"

(matrix) dfm@helmsley:/tmp$ ./show-args -a --debug -x -d -y -d -z -b -z

("./show-args" "-a" "-x" "-y" "-z" "-z") 
(matrix) dfm@helmsley:/tmp$ 

Error: Unbound variable: CCL::COMMAND-LINE-ARGUMENTS

We found a bug here: atlas-engineer/nyxt#106 (comment)

Error: Unbound variable: CCL::COMMAND-LINE-ARGUMENTS
While executing: UNIX-OPTS:ARGV, in process Initial(0).
Type :POP to abort, :R for a list of available restarts.
Type :? for other options

This is the related code:

(defun handle-malformed-cli-arg (condition)
  (format t "Error parsing argument ~a: ~a.~&" (opts:option condition) condition)
  (opts:describe)
  (uiop:quit))

(defun parse-cli-args ()
  "Parse command line arguments."
  (opts:define-opts
    (:name :help
           :description "Print this help and exit."
           :short #\h
           :long "help")
    (:name :verbose
           :short #\v
           :long "verbose"
           :description "Print debugging information to stdout."))

  (handler-bind ((opts:unknown-option #'handle-malformed-cli-arg)
                 (opts:missing-arg #'handle-malformed-cli-arg)
                 (opts:arg-parser-failed #'handle-malformed-cli-arg)
                 ;; (opts:missing-required-option #'handle-malformed-cli-arg)
                 )
    (opts:get-opts)))

(defun start ()
  (multiple-value-bind (options free-args)
      (parse-cli-args)
    (when (getf options :help)
      (opts:describe :prefix "Next command line usage:")
      (uiop:quit))
    (when (getf options :verbose)
      (format t "Arguments parsed: ~a and ~a~&" options free-args))

Maintaining unix-opts

Hi I would be interested in maintaining unix-opts. I use the project for an internal app and do kind of know how the internals work. I would be interested in maintaining the project as is, but hopefully also expand it to have a bit more options.

Why not add 'when-option' to the package?

when-option mentioned in examples/example.lisp seems like a useful little macro. Why don't you add it to the unix-opts package? That would help the community abide better by the DRY principle - I mean everyone would want to use it, right?

(defmacro when-option ((options opt) &body body)
  `(let ((it (getf ,options ,opt)))
     (when it
       ,@body)))

Removing two hardcoded strings

Hi!

I noticed there are two hardcoded string in the code (in describe function):

These two do not work fine in a program that is internationalized (that is a pity for me because i found this library awesome); i wonder if would be a good idea to add two more key arguments for describe function that default to these two hardcoded values?

I am thinking something like:

(defun describe (&key ... (usage-of-label "usage") (available-options-label "Available options") ...

If you agree that the problem actually exists and with the proposed solution (and of course i am OK for suggestions!) i happily would send a PR (it is an easy task i guess ;-) ).

Bye!
C.

Why prefix and not exact-match?

(let ((matches (remove-if-not #'prefix-p *options* :key key)))

Either my knowledge of unix is wrong, or this is a bug: why are we using prefix rather than exact-match? ls --al rather than ls --all clearly gets me an error on linux.

Condition names aren't exported

The condition names (troublesome-option, and its sub-conditions) don't seem to be exported from the unix-opts package. Shouldn't they be, so we can tidily cite them when we want to catch them elsewhere?

Please let me know if you'd prefer I submit a pull request for this.

CLISP argv

Hi, on CLISP ext:argv is a function, i think unix-opts:argv should be something like this

+clisp (map 'list (lambda (x) x) (ext:argv)) ; convert (ext:argv) from array to list

or better:

+clisp ext:args

(note that CLISP's ext:args doesn't include the binary name. In my scripts i had to manually add "clisp" as an argv parameter to get the same behaviour as with sbcl)

Btw unix-opts is really cool!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.