pax_global_header00006660000000000000000000000064137012172760014520gustar00rootroot0000000000000052 comment=27967d7b52295ea7885671af734332038c132837 argparse-0.7.1/000077500000000000000000000000001370121727600133315ustar00rootroot00000000000000argparse-0.7.1/.gitignore000066400000000000000000000000471370121727600153220ustar00rootroot00000000000000luacov.report.out luacov.stats.out doc argparse-0.7.1/.luacheckrc000066400000000000000000000000531370121727600154340ustar00rootroot00000000000000std = "min" files["spec/"].std = "+busted" argparse-0.7.1/.luacov000066400000000000000000000000631370121727600146220ustar00rootroot00000000000000return {modules = {argparse = "src/argparse.lua"}} argparse-0.7.1/.travis.yml000066400000000000000000000007431370121727600154460ustar00rootroot00000000000000language: python sudo: false env: - LUA="lua 5.1" - LUA="lua 5.2" - LUA="lua 5.3" - LUA="luajit 2.0" - LUA="luajit 2.1" before_install: - pip install codecov - pip install hererocks - hererocks lua_install --$LUA -r latest - source lua_install/bin/activate - luarocks install busted - luarocks install cluacov - luarocks install luacheck install: - luarocks make script: - luacheck src spec - busted -c after_script: - luacov - codecov -X gcov argparse-0.7.1/CHANGELOG.md000066400000000000000000000111031370121727600151360ustar00rootroot00000000000000# Changelog ## 0.7.1 (2020-07-08) Tested with Lua 5.4 ### New features * Hidden aliases can be added to an option or command by setting the `hidden_name` property ## 0.7.0 (2019-08-14) The repository has been moved to a [new location](https://github.com/luarocks/argparse). ### New features * Added support for generating shell completion scripts for Bash, Zsh, and Fish using the Parser methods `:get_bash_complete()`, `:get_zsh_complete()`, and `:get_fish_complete()`. The Parser methods `:add_complete()` and `:add_complete_command()` add a `--completion` option or `completion` command to the parser. * Added `:add_help_command()` method to Parser and Command objects. The help command shows help for the specified subcommand. * Added `choices` option and argument property for specifying a set of acceptable values for the argument. * Added `summary` command property for specifying the description shown in the parent parser's help message. ### Improvements * The location of the help flag in usage and help messages is now determined by when it is added to the parser, rather than always being the last option. By default it is now the first option. ## 0.6.0 (2018-04-13) ### New features * An array of functions can now be used as the value of `converter` property, so that multi-argument options can use different conversion rules for different arguments (#14). * Mutexes can now include positional arguments (#11). * Added `hidden` property for arguments, options and commands, removing them from the autogenerated usage and help strings. * Added `:group(name, ...)` method to Parser and Command objects, allowing custom grouping of arguments, options, and commands in autogenerated help string. * Added `help_vertical_space` property for configuring number of extra newlines between help strings for different arguments and options in autogenerated help string. * Added `usage_margin` and `usage_max_width` properties for configuring usage string autogeneration. * Added `help_usage_margin` and `help_description_margin` properties for configuring help string autogeneration. * Added `help_max_width` property. If set, descriptions in help string are automatically wrapped to fit into given number of columns. * Argparse version is now available as `argparse.version`. ### Improvements * `--` can now be used as a normal option name, with arguments after `--` always passed to it verbatim (#17). * When generating help messages for options with arguments and multiple aliases, usage strings for different aliases are put on separate lines and vertically aligned (#15). ## 0.5.0 (2015-12-09) ### New features * Actions can now be used to implement custom processing logic. * Added actions for arguments. * Added string aliases for actions such as `store_false`. * Command actions are now called after the parsing, with result target as the argument. * Added `command_target` property for storing name of used command. ### Improvements * Improved error messages on missing arguments. * `-f123` is now interpreted as `-f 123` when `-f` takes an optional argument. ## 0.4.1 (2015-08-08) ### Miscellaneous * Added license header to `argparse.lua` for ease of bundling and packaging (#3). ## 0.4.0 (2015-06-20) ### Breaking changes * Removed `aliases` property, aliases can now be set using several space separated strings as name, or, as it was possible before, by setting `name` property several times. ### New features * Added `handle_options` property (#2). * Often used properties now can be passed as arguments when calling or constructing an element. ### Improvements * Cleaned up trailing whitespace in generated messages. ## 0.3.2 (2015-01-15) ### Miscellaneous * Argparse no longer bundles 30log. ## 0.3.1 (2014-11-06) ### Fixes * Fixed incompatibility with old Luarocks versions. ## 0.3.0 (2014-08-25) ### New features * New `show_default` field disables automatic indication of default values in help messages. ### Improvements * In default targets `-` is now replaced with `_`. * Changed usage message generation to show options taking variable number of arguments after positional arguments. ### Fixes * Fixed incompatibility with strict.lua and other checkers (#1). ## 0.2.0 (2014-03-09) ### New features * Added mutually exclusive groups. * Options and arguments can now be configured to use different argument placeholders for first, second, etc. arguments. E.g. `--pair `. ### Fixes * Fixed script name inference not working for `Parser:get_help()` and `Parser:get_usage()` methods. ## 0.1.0 (2014-03-02) Initial release. argparse-0.7.1/LICENSE000066400000000000000000000021541370121727600143400ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2013 - 2018 Peter Melnichenko 2019 Paul Ouellette Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. argparse-0.7.1/README.md000066400000000000000000000057541370121727600146230ustar00rootroot00000000000000# argparse [![Build Status](https://travis-ci.org/luarocks/argparse.png?branch=master)](https://travis-ci.org/luarocks/argparse) [![Coverage status](https://codecov.io/gh/luarocks/argparse/branch/master/graph/badge.svg)](https://codecov.io/gh/luarocks/argparse) Argparse is a feature-rich command line parser for Lua inspired by argparse for Python. Argparse supports positional arguments, options, flags, optional arguments, subcommands and more. Argparse automatically generates usage, help, and error messages, and can generate shell completion scripts. ## Contents * [Example](#example) * [Installation](#installation) * [Tutorial](#tutorial) * [Testing](#testing) * [License](#license) ## Example Simple example: ```lua -- script.lua local argparse = require "argparse" local parser = argparse("script", "An example.") parser:argument("input", "Input file.") parser:option("-o --output", "Output file.", "a.out") parser:option("-I --include", "Include locations."):count("*") local args = parser:parse() ``` `args` contents depending on command line arguments: ```bash $ lua script.lua foo ``` ```lua { input = "foo", output = "a.out", include = {} } ``` ```bash $ lua script.lua foo -I/usr/local/include -Isrc -o bar ``` ```lua { input = "foo", output = "bar", include = {"/usr/local/include", "src"} } ``` Error messages depending on command line arguments: ```bash $ lua script.lua foo bar ``` ``` Usage: script [-h] [-o ] [-I ] Error: too many arguments ``` ```bash $ lua script.lua --help ``` ``` Usage: script [-h] [-o ] [-I ] An example. Arguments: input Input file. Options: -h, --help Show this help message and exit. -o , --output Output file. (default: a.out) -I , --include Include locations. ``` ```bash $ lua script.lua foo --outptu=bar ``` ``` Usage: script [-h] [-o ] [-I ] Error: unknown option '--outptu' Did you mean '--output'? ``` ## Installation ### Using LuaRocks Installing argparse using [LuaRocks](http://luarocks.org) is simple: ```bash $ luarocks install argparse ``` ### Without LuaRocks Download `src/argparse.lua` file and put it into the directory for Lua libraries or your working directory. ## Tutorial The tutorial is available [online](http://argparse.readthedocs.org). If argparse has been installed using LuaRocks 2.1.2 or later, it can be viewed using `luarocks doc argparse` command. Tutorial HTML files can be built using [Sphinx](http://sphinx-doc.org/): `sphinx-build docsrc doc`, the files will be found inside `doc/`. ## Testing argparse comes with a testing suite located in `spec` directory. [busted](http://olivinelabs.com/busted/) is required for testing, it can be installed using LuaRocks. Run the tests using `busted` command from the argparse folder. ## License argparse is licensed under the same terms as Lua itself (MIT license). argparse-0.7.1/argparse-scm-2.rockspec000066400000000000000000000011461370121727600176110ustar00rootroot00000000000000package = "argparse" version = "scm-2" source = { url = "git+https://github.com/luarocks/argparse.git" } description = { summary = "A feature-rich command-line argument parser", detailed = "Argparse supports positional arguments, options, flags, optional arguments, subcommands and more. Argparse automatically generates usage, help, and error messages, and can generate shell completion scripts.", homepage = "https://github.com/luarocks/argparse", license = "MIT" } dependencies = { "lua >= 5.1, < 5.5" } build = { type = "builtin", modules = { argparse = "src/argparse.lua" } } argparse-0.7.1/docsrc/000077500000000000000000000000001370121727600146065ustar00rootroot00000000000000argparse-0.7.1/docsrc/arguments.rst000066400000000000000000000052341370121727600173510ustar00rootroot00000000000000Adding and configuring arguments ================================ Positional arguments can be added using ``:argument(name, description, default, convert, args)`` method. It returns an Argument instance, which can be configured in the same way as Parsers. The ``name`` property is required. This and the following examples show contents of the result table returned by `parser:parse()` when the script is executed with given command-line arguments. .. code-block:: lua :linenos: parser:argument "input" .. code-block:: none $ lua script.lua foo .. code-block:: lua { input = "foo" } The data passed to the argument is stored in the result table at index ``input`` because it is the argument's name. The index can be changed using ``target`` property. Setting number of consumed arguments ------------------------------------ ``args`` property sets how many command line arguments the argument consumes. Its value is interpreted as follows: ================================================= ============================= Value Interpretation ================================================= ============================= Number ``N`` Exactly ``N`` arguments String ``A-B``, where ``A`` and ``B`` are numbers From ``A`` to ``B`` arguments String ``N+``, where ``N`` is a number ``N`` or more arguments String ``?`` An optional argument String ``*`` Any number of arguments String ``+`` At least one argument ================================================= ============================= If more than one argument can be consumed, a table is used to store the data. .. code-block:: lua :linenos: parser:argument("pair", "A pair of arguments.") :args(2) parser:argument("optional", "An optional argument.") :args "?" .. code-block:: none $ lua script.lua foo bar .. code-block:: lua { pair = {"foo", "bar"} } .. code-block:: none $ lua script.lua foo bar baz .. code-block:: lua { pair = {"foo", "bar"}, optional = "baz" } Setting argument choices ------------------------ The ``choices`` property can be used to restrict an argument to a set of choices. Its value is an array of string choices. .. code-block:: lua :linenos: parser:argument "direction" :choices {"north", "south", "east", "west"} .. code-block:: none $ lua script.lua foo .. code-block:: none Usage: script.lua [-h] {north,south,east,west} Error: argument 'direction' must be one of 'north', 'south', 'east', 'west' argparse-0.7.1/docsrc/callbacks.rst000066400000000000000000000140711370121727600172620ustar00rootroot00000000000000Callbacks ========= Converters ---------- argparse can perform automatic validation and conversion on arguments. If ``convert`` property of an element is a function, it will be applied to all the arguments passed to it. The function should return ``nil`` and, optionally, an error message if conversion failed. Standard ``tonumber`` and ``io.open`` functions work exactly like that. .. code-block:: lua :linenos: parser:argument "input" :convert(io.open) parser:option "-t --times" :convert(tonumber) .. code-block:: none $ lua script.lua foo.txt -t5 .. code-block:: lua { input = file_object, times = 5 } .. code-block:: none $ lua script.lua nonexistent.txt .. code-block:: none Usage: script.lua [-h] [-t ] Error: nonexistent.txt: No such file or directory .. code-block:: none $ lua script.lua foo.txt --times=many .. code-block:: none Usage: script.lua [-h] [-t ] Error: malformed argument 'many' If ``convert`` property of an element is an array of functions, they will be used as converters for corresponding arguments in case the element accepts multiple arguments. .. code-block:: lua :linenos: parser:option "--line-style" :args(2) :convert {string.lower, tonumber} .. code-block:: none $ lua script.lua --line-style DASHED 1.5 .. code-block:: lua { line_style = {"dashed", 1.5} } Table converters ^^^^^^^^^^^^^^^^ If convert property of an element is a table and doesn't have functions in array part, arguments passed to it will be used as keys. If a key is missing, an error is raised. .. code-block:: lua :linenos: parser:argument "choice" :convert { foo = "Something foo-related", bar = "Something bar-related" } .. code-block:: none $ lua script.lua bar .. code-block:: lua { choice = "Something bar-related" } .. code-block:: none $ lua script.lua baz .. code-block:: none Usage: script.lua [-h] Error: malformed argument 'baz' Actions ------- .. _actions: Argument and option actions ^^^^^^^^^^^^^^^^^^^^^^^^^^^ argparse uses action callbacks to process invocations of arguments and options. Default actions simply put passed arguments into the result table as a single value or insert into an array depending on number of arguments the option can take and how many times it can be used. A custom action can be set using ``action`` property. An action must be a function. and will be called after each invocation of the option or the argument it is assigned to. Four arguments are passed: result table, target index in that table, an argument or an array of arguments passed by user, and overwrite flag used when an option is invoked too many times. Converters are applied before actions. Initial value to be stored at target index in the result table can be set using ``init`` property, or also using ``default`` property if the value is not a string. .. code-block:: lua :linenos: parser:option("--exceptions"):args("*"):action(function(args, _, exceptions) for _, exception in ipairs(exceptions) do table.insert(args.exceptions, exception) end end):init({"foo", "bar"}) parser:flag("--no-exceptions"):action(function(args) args.exceptions = {} end) .. code-block:: none $ lua script.lua --exceptions x y --exceptions z t .. code-block:: lua { exceptions = { "foo", "bar", "x", "y", "z", "t" } } .. code-block:: none $ lua script.lua --exceptions x y --no-exceptions .. code-block:: lua { exceptions = {} } Actions can also be used when a flag needs to print some message and exit without parsing remaining arguments. .. code-block:: lua :linenos: parser:flag("-v --version"):action(function() print("script v1.0.0") os.exit(0) end) .. code-block:: none $ lua script.lua -v .. code-block:: none script v1.0.0 Built-in actions ^^^^^^^^^^^^^^^^ These actions can be referred to by their string names when setting ``action`` property: =========== ======================================================= Name Description =========== ======================================================= store Stores argument or arguments at target index. store_true Stores ``true`` at target index. store_false Stores ``false`` at target index. count Increments number at target index. append Appends argument or arguments to table at target index. concat Appends arguments one by one to table at target index. =========== ======================================================= Examples using ``store_false`` and ``concat`` actions: .. code-block:: lua :linenos: parser:flag("--candy") parser:flag("--no-candy"):target("candy"):action("store_false") parser:flag("--rain", "Enable rain", false) parser:option("--exceptions"):args("*"):action("concat"):init({"foo", "bar"}) .. code-block:: none $ lua script.lua .. code-block:: lua { rain = false } .. code-block:: none $ lua script.lua --candy .. code-block:: lua { candy = true, rain = false } .. code-block:: none $ lua script.lua --no-candy --rain .. code-block:: lua { candy = false, rain = true } .. code-block:: none $ lua script.lua --exceptions x y --exceptions z t .. code-block:: lua { exceptions = { "foo", "bar", "x", "y", "z", "t" }, rain = false } Command actions ^^^^^^^^^^^^^^^ Actions for parsers and commands are simply callbacks invoked after parsing, with result table and command name as the arguments. Actions for nested commands are called first. .. code-block:: lua :linenos: local install = parser:command("install"):action(function(args, name) print("Running " .. name) -- Use args here ) parser:action(function(args) print("Callbacks are fun!") end) .. code-block:: none $ lua script.lua install .. code-block:: none Running install Callbacks are fun! argparse-0.7.1/docsrc/commands.rst000066400000000000000000000071141370121727600171440ustar00rootroot00000000000000Adding and configuring commands =============================== A command is a subparser invoked when its name is passed as an argument. For example, in `git `_ CLI ``add``, ``commit``, ``push``, etc. are commands. Each command has its own set of arguments and options, but inherits options of its parent. Commands can be added using ``:command(name, description, epilog)`` method. Just as options, commands can have several aliases. .. code-block:: lua :linenos: parser:command "install i" If a command it used, ``true`` is stored in the corresponding field of the result table. .. code-block:: none $ lua script.lua install .. code-block:: lua { install = true } A typo will result in an appropriate error message. .. code-block:: none $ lua script.lua instal .. code-block:: none Usage: script.lua [-h] ... Error: unknown command 'instal' Did you mean 'install'? Getting name of selected command -------------------------------- Use ``command_target`` property of the parser to store the name of used command in a field of the result table. .. code-block:: lua :linenos: parser:command_target("command") parser:command("install") parser:command("remove") .. code-block:: none $ lua script.lua install .. code-block:: lua { install = true, command = "install" } Adding elements to commands --------------------------- The Command class is a subclass of the Parser class, so all the Parser's methods for adding elements work on commands, too. .. code-block:: lua :linenos: local install = parser:command "install" install:argument "rock" install:option "-f --from" .. code-block:: none $ lua script.lua install foo --from=bar .. code-block:: lua { install = true, rock = "foo", from = "bar" } Commands have their own usage and help messages. .. code-block:: none $ lua script.lua install .. code-block:: none Usage: script.lua install [-h] [-f ] Error: too few arguments .. code-block:: none $ lua script.lua install --help .. code-block:: none Usage: script.lua install [-h] [-f ] Arguments: rock Options: -h, --help Show this help message and exit. -f , --from Making a command optional ------------------------- By default, if a parser has commands, using one of them is obligatory. .. code-block:: lua :linenos: local parser = argparse() parser:command "install" .. code-block:: none $ lua script.lua .. code-block:: none Usage: script.lua [-h] ... Error: a command is required This can be changed using ``require_command`` property. .. code-block:: lua :linenos: local parser = argparse() :require_command(false) parser:command "install" Command summaries ----------------- The description for commands shown in the parent parser help message can be set with the ``summary`` property. .. code-block:: lua :linenos: parser:command "install" :summary "Install a rock." :description "A long description for the install command." .. code-block:: none $ lua script.lua --help .. code-block:: none Usage: script.lua [-h] ... Options: -h, --help Show this help message and exit. Commands: install Install a rock. .. code-block:: none $ lua script.lua install --help .. code-block:: none Usage: script.lua install [-h] A long description for the install command. Options: -h, --help Show this help message and exit. argparse-0.7.1/docsrc/completions.rst000066400000000000000000000042231370121727600176750ustar00rootroot00000000000000Shell completions ================= Argparse can generate shell completion scripts for `Bash `_, `Zsh `_, and `Fish `_. The completion scripts support completing options, commands, and argument choices. The Parser methods ``:get_bash_complete()``, ``:get_zsh_complete()``, and ``:get_fish_complete()`` return completion scripts as a string. Adding a completion option or command ------------------------------------- A ``--completion`` option can be added to a parser using the ``:add_complete([value])`` method. The optional ``value`` argument is a string or table used to configure the option (by calling the option with ``value``). .. code-block:: lua :linenos: local parser = argparse() :add_complete() .. code-block:: none $ lua script.lua -h .. code-block:: none Usage: script.lua [-h] [--completion {bash,zsh,fish}] Options: -h, --help Show this help message and exit. --completion {bash,zsh,fish} Output a shell completion script for the specified shell. A similar ``completion`` command can be added to a parser using the ``:add_complete_command([value])`` method. Using completions ----------------- Bash ^^^^ Save the generated completion script at ``/usr/share/bash-completion/completions/script.lua`` or ``~/.local/share/bash-completion/completions/script.lua``. Alternatively, add the following line to the ``~/.bashrc``: .. code-block:: bash source <(script.lua --completion bash) Zsh ^^^ Save the completion script in the ``/usr/share/zsh/site-functions/`` directory or any directory in the ``$fpath``. The file name should be an underscore followed by the program name. A new directory can be added to to the ``$fpath`` by adding e.g. ``fpath=(~/.zfunc $fpath)`` in the ``~/.zshrc`` before ``compinit``. Fish ^^^^ Save the completion script at ``/usr/share/fish/vendor_completions.d/script.lua.fish`` or ``~/.config/fish/completions/script.lua.fish``. Alternatively, add the following line to the file ``~/.config/fish/config.fish``: .. code-block:: fish script.lua --completion fish | source argparse-0.7.1/docsrc/conf.py000066400000000000000000000203751370121727600161140ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # argparse documentation build configuration file, created by # sphinx-quickstart on Sat Jun 20 14:03:24 2015. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys import os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [] # Add any paths that contain templates here, relative to this directory. templates_path = [] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'argparse' copyright = u'2013-2018 Peter Melnichenko; 2019 Paul Ouellette' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = '0.7.1' # The full version, including alpha/beta/rc tags. release = '0.7.1' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all # documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. #keep_warnings = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. if os.environ.get('READTHEDOCS', None) != 'True': try: import sphinx_rtd_theme html_theme = 'sphinx_rtd_theme' html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] except ImportError: pass # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". html_title = "argparse " + version + " tutorial" # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = [] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. #html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'argparsetutorial' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ ('index', 'argparse.tex', u'argparse tutorial', u'Peter Melnichenko', 'manual') ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'argparse', u'argparse tutorial', [u'Peter Melnichenko', u'Paul Ouellette'], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'argparse', u'argparse tutorial', u'Peter Melnichenko, Paul Ouellette', 'argparse', 'Command line parser for Lua.', 'Miscellaneous') ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. #texinfo_no_detailmenu = False argparse-0.7.1/docsrc/defaults.rst000066400000000000000000000050331370121727600171500ustar00rootroot00000000000000Default values ============== For elements such as arguments and options, if ``default`` property is set to a string, its value is stored in case the element was not used (if it's not a string, it'll be used as ``init`` property instead, see :ref:`actions`). .. code-block:: lua :linenos: parser:option("-o --output", "Output file.", "a.out") -- Equivalent: parser:option "-o" "--output" :description "Output file." :default "a.out" .. code-block:: none $ lua script.lua .. code-block:: lua { output = "a.out" } The existence of a default value is reflected in help message, unless ``show_default`` property is set to ``false``. .. code-block:: none $ lua script.lua --help .. code-block:: none Usage: script.lua [-h] [-o ] Options: -h, --help Show this help message and exit. -o , --output Output file. (default: a.out) Note that invocation without required arguments is still an error. .. code-block:: none $ lua script.lua -o .. code-block:: none Usage: script.lua [-h] [-o ] Error: too few arguments Default mode ------------ ``defmode`` property regulates how argparse should use the default value of an element. By default, or if ``defmode`` contains ``u`` (for unused), the default value will be automatically passed to the element if it was not invoked at all. It will be passed minimal required of times, so that if the element is allowed to consume no arguments (e.g. using ``:args "?"``), the default value is ignored. If ``defmode`` contains ``a`` (for argument), the default value will be automatically passed to the element if not enough arguments were passed, or not enough invocations were made. Consider the difference: .. code-block:: lua :linenos: parser:option "-o" :default "a.out" parser:option "-p" :default "password" :defmode "arg" .. code-block:: none $ lua script.lua -h .. code-block:: none Usage: script.lua [-h] [-o ] [-p [

]] Options: -h, --help Show this help message and exit. -o default: a.out -p [

] default: password .. code-block:: none $ lua script.lua .. code-block:: lua { o = "a.out" } .. code-block:: none $ lua script.lua -p .. code-block:: lua { o = "a.out", p = "password" } .. code-block:: none $ lua script.lua -o .. code-block:: none Usage: script.lua [-h] [-o ] [-p [

]] Error: too few arguments argparse-0.7.1/docsrc/index.rst000066400000000000000000000004541370121727600164520ustar00rootroot00000000000000Argparse tutorial ================= Contents: .. toctree:: parsers arguments options mutexes commands defaults callbacks messages completions misc This is a tutorial for `argparse `_, a feature-rich command line parser for Lua. argparse-0.7.1/docsrc/messages.rst000066400000000000000000000220721370121727600171520ustar00rootroot00000000000000Configuring help and usage messages =================================== The usage and help messages of parsers and commands can be generated on demand using ``:get_usage()`` and ``:get_help()`` methods, and overridden using ``help`` and ``usage`` properties. When using the autogenerated usage and help messages, there are several ways to adjust them. Hiding arguments, options, and commands from messages ----------------------------------------------------- If ``hidden`` property for an argument, an option or a command is set to ``true``, it is not included into help and usage messages, but otherwise works normally. .. code-block:: lua :linenos: parser:option "--normal-option" parser:option "--deprecated-option" :hidden(true) .. code-block:: none $ lua script.lua --help .. code-block:: none Usage: script.lua [-h] [--normal-option ] Options: -h, --help Show this help message and exit. --normal-option .. code-block:: none $ lua script.lua --deprecated-option value .. code-block:: lua { deprecated_option = "value" } Hiding option and command aliases --------------------------------- Hidden aliases can be added to an option or command by setting the ``hidden_name`` property. Its value is interpreted in the same way as the ``name`` property. .. code-block:: lua :linenos: parser:option "--server" :hidden_name "--from" .. code-block:: none $ lua script.lua --help .. code-block:: none Usage: script.lua [-h] [--server ] Options: -h, --help Show this help message and exit. --server .. code-block:: none $ lua script.lua --server foo $ lua script.lua --from foo .. code-block:: lua { server = "foo" } Setting argument placeholder ---------------------------- For options and arguments, ``argname`` property controls the placeholder for the argument in the usage message. .. code-block:: lua :linenos: parser:option "-f" "--from" :argname "" .. code-block:: none $ lua script.lua --help .. code-block:: none Usage: script.lua [-h] [-f ] Options: -h, --help Show this help message and exit. -f , --from ``argname`` can be an array of placeholders. .. code-block:: lua :linenos: parser:option "--pair" :args(2) :argname {"", ""} .. code-block:: none $ lua script.lua --help .. code-block:: none Usage: script.lua [-h] [--pair ] Options: -h, --help Show this help message and exit. --pair Grouping elements ----------------- ``:group(name, ...)`` method of parsers and commands puts passed arguments, options, and commands into a named group with its own section in the help message. Elements outside any groups are put into a default section. .. code-block:: lua :linenos: parser:group("Configuring output format", parser:flag "-v --verbose", parser:flag "--use-colors", parser:option "--encoding" ) parser:group("Configuring processing", parser:option "--compression-level", parser:flag "--skip-broken-chunks" ) parser:flag "--version" :action(function() print("script.lua 1.0.0") os.exit(0) end) .. code-block:: none $ lua script.lua --help .. code-block:: none Usage: script.lua [-h] [-v] [--use-colors] [--encoding ] [--compression-level ] [--skip-broken-chunks] [--version] Configuring output format: -v, --verbose --use-colors --encoding Configuring processing: --compression-level --skip-broken-chunks Other options: -h, --help Show this help message and exit. --version Help message line wrapping -------------------------- If ``help_max_width`` property of a parser or a command is set, when generating its help message, argparse will automatically wrap lines, attempting to fit into given number of columns. This includes wrapping lines in parser description and epilog and descriptions of arguments, options, and commands. Line wrapping preserves existing line endings and only splits overly long input lines. When breaking a long line, it replicates indentation of the line in the continuation lines. Additionally, if the first non-space token in a line is ``*``, ``+``, or ``-``, the line is considered a list item, and the continuation lines are aligned with the first word after the list item marker. .. code-block:: lua :linenos: parser:help_max_width(80) parser:option "-f --foo" :description("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor " .. "incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation " .. "ullamco laboris nisi ut aliquip ex ea commodo consequat.\n" .. "The next paragraph is indented:\n" .. " Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. " .. "Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.") parser:option "-b --bar" :description("Here is a list:\n".. "* Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor...\n" .. "* Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip...\n" .. "* Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.") .. code-block:: none $ lua script.lua --help .. code-block:: none Usage: script.lua [-h] [-f ] [-b ] Options: -h, --help Show this help message and exit. -f , Lorem ipsum dolor sit amet, consectetur adipiscing --foo elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. The next paragraph is indented: Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. -b , Here is a list: --bar * Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor... * Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip... * Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. ``help_max_width`` property is inherited by commands. Configuring help and usage message layout ----------------------------------------- Several other parser and command properties can be used to tweak help and usage message format. Like ``help_max_width``, all of them are inherited by commands when set on the parser or a parent command. ``usage_max_width`` property sets maximum width of the usage string. Default is ``70``. ``usage_margin`` property sets margin width used when line wrapping long usage strings. Default is ``7``. .. code-block:: lua :linenos: parser:usage_max_width(50) :usage_margin(#"Usage: script.lua ") parser:option "--foo" parser:option "--bar" parser:option "--baz" parser:option "--qux" print(parser:get_usage()) .. code-block:: none $ lua script.lua .. code-block:: none Usage: script.lua [-h] [--foo ] [--bar ] [--baz ] [--qux ] Help message for a group of arguments, options, or commands is organized into two columns, with usage template on the left side and descriptions on the right side. ``help_usage_margin`` property sets horizontal offset for the first column (``3`` by default). ``help_description_margin`` property sets horizontal offset for the second column (``25`` by default). ``help_vertical_space`` property sets number of extra empty lines to put between descriptions for different elements within a group (``0`` by default). .. code-block:: lua :linenos: parser:help_usage_margin(2) :help_description_margin(17) :help_vertical_space(1) parser:option("--foo", "Set foo.") parser:option("--bar", "Set bar.") parser:option("--baz", "Set baz.") parser:option("--qux", "Set qux.") .. code-block:: none $ lua script.lua --help .. code-block:: none Usage: script.lua [-h] [--foo ] [--bar ] [--baz ] [--qux ] Options: -h, --help Show this help message and exit. --foo Set foo. --bar Set bar. --baz Set baz. --qux Set qux. argparse-0.7.1/docsrc/misc.rst000066400000000000000000000165121370121727600163000ustar00rootroot00000000000000Miscellaneous ============= Argparse version ---------------- ``argparse`` module is a table with ``__call`` metamethod. ``argparse.version`` is a string in ``MAJOR.MINOR.PATCH`` format specifying argparse version. Overwriting default help option ------------------------------- If the property ``add_help`` of a parser is set to ``false``, no help option will be added to it. Otherwise, the value of the field will be used to configure it. .. code-block:: lua :linenos: local parser = argparse() :add_help "/?" .. code-block:: none $ lua script.lua /? .. code-block:: none Usage: script.lua [/?] Options: /? Show this help message and exit. Help command ------------ A help command can be added to the parser using the ``:add_help_command([value])`` method. It accepts an optional string or table value which is used to configure the command. .. code-block:: lua :linenos: local parser = argparse() :add_help_command() parser:command "install" :description "Install a rock." .. code-block:: none $ lua script.lua help .. code-block:: none Usage: script.lua [-h] ... Options: -h, --help Show this help message and exit. Commands: help Show help for commands. install Install a rock. .. code-block:: none $ lua script.lua help install .. code-block:: none Usage: script.lua install [-h] Install a rock. Options: -h, --help Show this help message and exit. Disabling option handling ------------------------- When ``handle_options`` property of a parser or a command is set to ``false``, all options will be passed verbatim to the argument list, as if the input included double-hyphens. .. code-block:: lua :linenos: parser:handle_options(false) parser:argument "input" :args "*" parser:option "-f" "--foo" :args "*" .. code-block:: none $ lua script.lua bar -f --foo bar .. code-block:: lua { input = {"bar", "-f", "--foo", "bar"} } Prohibiting overuse of options ------------------------------ By default, if an option is invoked too many times, latest invocations overwrite the data passed earlier. .. code-block:: lua :linenos: parser:option "-o --output" .. code-block:: none $ lua script.lua -oFOO -oBAR .. code-block:: lua { output = "BAR" } Set ``overwrite`` property to ``false`` to prohibit this behavior. .. code-block:: lua :linenos: parser:option "-o --output" :overwrite(false) .. code-block:: none $ lua script.lua -oFOO -oBAR .. code-block:: none Usage: script.lua [-h] [-o ] Error: option '-o' must be used at most 1 time Parsing algorithm ----------------- argparse interprets command line arguments in the following way: ============= ================================================================================================================ Argument Interpretation ============= ================================================================================================================ ``foo`` An argument of an option or a positional argument. ``--foo`` An option. ``--foo=bar`` An option and its argument. The option must be able to take arguments. ``-f`` An option. ``-abcdef`` Letters are interpreted as options. If one of them can take an argument, the rest of the string is passed to it. ``--`` The rest of the command line arguments will be interpreted as positional arguments. ============= ================================================================================================================ Property lists -------------- Parser properties ^^^^^^^^^^^^^^^^^ Properties that can be set as arguments when calling or constructing a parser, in this order: =============== ====== Property Type =============== ====== ``name`` String ``description`` String ``epilog`` String =============== ====== Other properties: =========================== ========================== Property Type =========================== ========================== ``usage`` String ``help`` String ``require_command`` Boolean ``handle_options`` Boolean ``add_help`` Boolean or string or table ``command_target`` String ``usage_max_width`` Number ``usage_margin`` Number ``help_max_width`` Number ``help_usage_margin`` Number ``help_description_margin`` Number ``help_vertical_space`` Number =========================== ========================== Command properties ^^^^^^^^^^^^^^^^^^ Properties that can be set as arguments when calling or constructing a command, in this order: =============== ====== Property Type =============== ====== ``name`` String ``description`` String ``epilog`` String =============== ====== Other properties: =========================== ========================== Property Type =========================== ========================== ``hidden_name`` String ``summary`` String ``target`` String ``usage`` String ``help`` String ``require_command`` Boolean ``handle_options`` Boolean ``action`` Function ``add_help`` Boolean or string or table ``command_target`` String ``hidden`` Boolean ``usage_max_width`` Number ``usage_margin`` Number ``help_max_width`` Number ``help_usage_margin`` Number ``help_description_margin`` Number ``help_vertical_space`` Number =========================== ========================== Argument properties ^^^^^^^^^^^^^^^^^^^ Properties that can be set as arguments when calling or constructing an argument, in this order: =============== ================= Property Type =============== ================= ``name`` String ``description`` String ``default`` Any ``convert`` Function or table ``args`` Number or string =============== ================= Other properties: =================== =============== Property Type =================== =============== ``target`` String ``defmode`` String ``show_default`` Boolean ``argname`` String or table ``choices`` Table ``action`` Function or string ``init`` Any ``hidden`` Boolean =================== =============== Option and flag properties ^^^^^^^^^^^^^^^^^^^^^^^^^^ Properties that can be set as arguments when calling or constructing an option or a flag, in this order: =============== ================= Property Type =============== ================= ``name`` String ``description`` String ``default`` Any ``convert`` Function or table ``args`` Number or string ``count`` Number or string =============== ================= Other properties: =================== ================== Property Type =================== ================== ``hidden_name`` String ``target`` String ``defmode`` String ``show_default`` Boolean ``overwrite`` Booleans ``argname`` String or table ``choices`` Table ``action`` Function or string ``init`` Any ``hidden`` Boolean =================== ================== argparse-0.7.1/docsrc/mutexes.rst000066400000000000000000000016751370121727600170430ustar00rootroot00000000000000Mutually exclusive groups ========================= A group of arguments and options can be marked as mutually exclusive using ``:mutex(argument_or_option, ...)`` method of the Parser class. .. code-block:: lua :linenos: parser:mutex( parser:argument "input" :args "?", parser:flag "--process-stdin" ) parser:mutex( parser:flag "-q --quiet", parser:flag "-v --verbose" ) If more than one element of a mutually exclusive group is used, an error is raised. .. code-block:: none $ lua script.lua -qv .. code-block:: none Usage: script.lua ([-q] | [-v]) [-h] ([] | [--process-stdin]) Error: option '-v' can not be used together with option '-q' .. code-block:: none $ lua script.lua file --process-stdin .. code-block:: none Usage: script.lua ([-q] | [-v]) [-h] ([] | [--process-stdin]) Error: option '--process-stdin' can not be used together with argument 'input' argparse-0.7.1/docsrc/options.rst000066400000000000000000000105671370121727600170440ustar00rootroot00000000000000Adding and configuring options ============================== Options can be added using ``:option(name, description, default, convert, args, count)`` method. It returns an Option instance, which can be configured in the same way as Parsers. The ``name`` property is required. An option can have several aliases, which can be set as space separated substrings in its name or by continuously setting ``name`` property. .. code-block:: lua :linenos: -- These lines are equivalent: parser:option "-f" "--from" parser:option "-f --from" .. code-block:: none $ lua script.lua --from there $ lua script.lua --from=there $ lua script.lua -f there $ lua script.lua -fthere .. code-block:: lua { from = "there" } For an option, default index used to store arguments passed to it is the first "long" alias (an alias starting with two control characters, typically hyphens) or just the first alias, without control characters. Hyphens in the default index are replaced with underscores. In the following table it is assumed that ``local args = parser:parse()`` has been executed. ======================== ============================== Option's aliases Location of option's arguments ======================== ============================== ``-o`` ``args.o`` ``-o`` ``--output`` ``args.output`` ``-s`` ``--from-server`` ``args.from_server`` ======================== ============================== As with arguments, the index can be explicitly set using ``target`` property. Flags ----- Flags are almost identical to options, except that they don't take an argument by default. .. code-block:: lua :linenos: parser:flag("-q --quiet") .. code-block:: none $ lua script.lua -q .. code-block:: lua { quiet = true } Control characters ------------------ The first characters of all aliases of all options of a parser form the set of control characters, used to distinguish options from arguments. Typically the set only consists of a hyphen. Setting number of consumed arguments ------------------------------------ Just as arguments, options can be configured to take several command line arguments. .. code-block:: lua :linenos: parser:option "--pair" :args(2) parser:option "--optional" :args "?" .. code-block:: none $ lua script.lua --pair foo bar .. code-block:: lua { pair = {"foo", "bar"} } .. code-block:: none $ lua script.lua --pair foo bar --optional .. code-block:: lua { pair = {"foo", "bar"}, optional = {} } .. code-block:: none $ lua script.lua --optional=baz .. code-block:: lua { optional = {"baz"} } Note that the data passed to ``optional`` option is stored in an array. That is necessary to distinguish whether the option was invoked without an argument or it was not invoked at all. Setting argument choices ------------------------ The ``choices`` property can be used to specify a list of choices for an option argument in the same way as for arguments. .. code-block:: lua :linenos: parser:option "--format" :choices {"short", "medium", "full"} .. code-block:: none $ lua script.lua --format foo .. code-block:: none Usage: script.lua [-h] [--format {short,medium,full}] Error: argument for option '--format' must be one of 'short', 'medium', 'full' Setting number of invocations ----------------------------- For options, it is possible to control how many times they can be used. argparse uses ``count`` property to set how many times an option can be invoked. The value of the property is interpreted in the same way ``args`` is. .. code-block:: lua :linenos: parser:option("-e --exclude") :count "*" .. code-block:: none $ lua script.lua -eFOO -eBAR .. code-block:: lua { exclude = {"FOO", "BAR"} } If an option can be used more than once and it can consume more than one argument, the data is stored as an array of invocations, each being an array of arguments. As a special case, if an option can be used more than once and it consumes no arguments (e.g. it's a flag), than the number of invocations is stored in the associated field of the result table. .. code-block:: lua :linenos: parser:flag("-v --verbose", "Sets verbosity level.") :count "0-2" :target "verbosity" .. code-block:: none $ lua script.lua -vv .. code-block:: lua { verbosity = 2 } argparse-0.7.1/docsrc/parsers.rst000066400000000000000000000065671370121727600170350ustar00rootroot00000000000000Creating and using parsers ========================== The ``argparse`` module is a function which, when called, creates an instance of the Parser class. .. code-block:: lua :linenos: -- script.lua local argparse = require "argparse" local parser = argparse() ``parser`` is now an empty parser which does not recognize any command line arguments or options. Parsing command line arguments ------------------------------ ``:parse([argv])`` method of the Parser class returns a table with processed data from the command line or ``argv`` array. .. code-block:: lua :linenos: local args = parser:parse() After this is executed with ``lua script.lua``, ``args`` is an empty table because the parser is empty and no command line arguments were supplied. Error handling ^^^^^^^^^^^^^^ If the provided command line arguments are not recognized by the parser, it will print an error message and call ``os.exit(1)``. .. code-block:: none $ lua script.lua foo .. code-block:: none Usage: script.lua [-h] Error: too many arguments If halting the program is undesirable, ``:pparse([args])`` method should be used. It returns boolean flag indicating success of parsing and result or error message. An error can raised manually using ``:error()`` method. .. code-block:: lua :linenos: parser:error("manual argument validation failed") .. code-block:: none Usage: script.lua [-h] Error: manual argument validation failed Help option ^^^^^^^^^^^ As the automatically generated usage message states, there is a help option ``-h`` added to any parser by default. When a help option is used, parser will print a help message and call ``os.exit(0)``. .. code-block:: none $ lua script.lua -h .. code-block:: none Usage: script.lua [-h] Options: -h, --help Show this help message and exit. Typo autocorrection ^^^^^^^^^^^^^^^^^^^ When an option is not recognized by the parser, but there is an option with a similar name, a suggestion is automatically added to the error message. .. code-block:: none $ lua script.lua --hepl .. code-block:: none Usage: script.lua [-h] Error: unknown option '--hepl' Did you mean '--help'? Configuring parsers ------------------- Parsers have several properties affecting their behavior. For example, ``description`` and ``epilog`` properties set the text to be displayed in the help message after the usage message and after the listings of options and arguments, respectively. Another is ``name``, which overwrites the name of the program which is used in the usage message (default value is inferred from command line arguments). There are several ways to set properties. The first is to chain setter methods of Parser object. .. code-block:: lua :linenos: local parser = argparse() :name "script" :description "A testing script." :epilog "For more info, see http://example.com" The second is to call a parser with a table containing some properties. .. code-block:: lua :linenos: local parser = argparse() { name = "script", description = "A testing script.", epilog "For more info, see http://example.com." } Finally, ``name``. ``description`` and ``epilog`` properties can be passed as arguments when calling a parser. .. code-block:: lua :linenos: local parser = argparse("script", "A testing script.", "For more info, see http://example.com.") argparse-0.7.1/spec/000077500000000000000000000000001370121727600142635ustar00rootroot00000000000000argparse-0.7.1/spec/actions_spec.lua000066400000000000000000000156021370121727600174440ustar00rootroot00000000000000local Parser = require "argparse" getmetatable(Parser()).error = function(_, msg) error(msg) end describe("actions", function() it("for arguments are called", function() local parser = Parser() local foo parser:argument("foo"):action(function(_, _, passed_foo) foo = passed_foo end) local baz parser:argument("baz"):args("*"):action(function(_, _, passed_baz) baz = passed_baz end) parser:parse{"a"} assert.equals("a", foo) assert.same({}, baz) parser:parse{"b", "c"} assert.equals("b", foo) assert.same({"c"}, baz) parser:parse{"d", "e", "f"} assert.equals("d", foo) assert.same({"e", "f"}, baz) end) it("for options are called", function() local action1 = spy.new(function(_, _, arg) assert.equal("nowhere", arg) end) local expected_args = {"Alice", "Bob"} local action2 = spy.new(function(_, _, args) assert.same(expected_args, args) expected_args = {"Emma", "John"} end) local parser = Parser() parser:option "-f" "--from" { action = function(...) return action1(...) end } parser:option "-p" "--pair" { action = function(...) return action2(...) end, count = "*", args = 2 } parser:parse{"-fnowhere", "--pair", "Alice", "Bob", "-p", "Emma", "John"} assert.spy(action1).called(1) assert.spy(action2).called(2) end) it("for flags are called", function() local action1 = spy.new(function() end) local action2 = spy.new(function() end) local action3 = spy.new(function() end) local parser = Parser() parser:flag "-v" "--verbose" { action = function(...) return action1(...) end, count = "0-3" } parser:flag "-q" "--quiet" { action = function(...) return action2(...) end } parser:flag "-a" "--another-flag" { action = function(...) return action3(...) end } parser:parse{"-vv", "--quiet"} assert.spy(action1).called(2) assert.spy(action2).called(1) assert.spy(action3).called(0) end) it("for options allow custom storing of arguments", function() local parser = Parser() parser:option("-p --path"):action(function(result, target, argument) result[target] = (result[target] or ".") .. "/" .. argument end) local args = parser:parse{"-pfirst", "--path", "second", "--path=third"} assert.same({path = "./first/second/third"}, args) end) it("for options with several arguments allow custom storing of arguments", function() local parser = Parser() parser:option("-p --path"):args("*"):action(function(result, target, arguments) for _, argument in ipairs(arguments) do result[target] = (result[target] or ".") .. "/" .. argument end end) local args = parser:parse{"-p", "first", "second", "third"} assert.same({path = "./first/second/third"}, args) end) it("for options allow using strings as actions", function() local parser = Parser() parser:flag("--no-foo"):target("foo"):action("store_false") parser:flag("--no-bar"):target("bar"):action("store_false") parser:option("--things"):args("+"):count("*"):action("concat") local args = parser:parse{"--things", "a", "b", "--no-foo", "--things", "c", "d"} assert.same({foo = false, things = {"a", "b", "c", "d"}}, args) end) it("for options allow setting initial stored value", function() local parser = Parser() parser:flag("--no-foo"):target("foo"):action("store_false"):init(true) parser:flag("--no-bar"):target("bar"):action("store_false"):init(true) local args = parser:parse{"--no-foo"} assert.same({foo = false, bar = true}, args) end) it("'append' and 'concat' respect initial value", function() local parser = Parser() parser:option("-f"):count("*"):init(nil) parser:option("-g"):args("*"):count("*"):action("concat"):init(nil) local args = parser:parse{} assert.same({}, args) args = parser:parse{"-fabc", "-fdef", "-g"} assert.same({f = {"abc", "def"}, g = {}}, args) args = parser:parse{"-g", "abc", "def", "-g123", "-f123"} assert.same({f = {"123"}, g = {"abc", "def", "123"}}, args) end) it("'concat' action can't handle too many invocations", function() local parser = Parser() parser:option("-x"):args("*"):count("0-2"):action("concat") assert.has_error(function() parser:parse{"-x", "foo", "-x", "bar", "baz", "-x", "thing"} end, "'concat' action can't handle too many invocations") end) it("for options allow setting initial stored value as non-string argument to default", function() local parser = Parser() parser:flag("--no-foo", "Foo the bar.", true):target("foo"):action("store_false") parser:flag("--no-bar", "Bar the foo.", true):target("bar"):action("store_false") local args = parser:parse{"--no-foo"} assert.same({foo = false, bar = true}, args) end) it("pass overwrite flag as the fourth argument", function() local parser = Parser() local overwrites = {} parser:flag("-f"):count("0-2"):action(function(_, _, _, overwrite) table.insert(overwrites, overwrite) end) parser:parse{"-ffff"} assert.same({false, false, true, true}, overwrites) end) it("pass user-defined target", function() local parser = Parser() local target parser:flag("-f"):target("force"):action(function(_, passed_target) target = passed_target end) parser:parse{"-f"} assert.equals("force", target) end) it("apply convert before passing arguments", function() local parser = Parser() local numbers = {} parser:option("-n"):convert(tonumber):default("0"):defmode("a"):action(function(_, _, n) table.insert(numbers, n) end) parser:parse{"-n", "-n1", "-n", "-n", "2"} assert.same({0, 1, 0, 2}, numbers) end) it("for parser are called", function() local parser = Parser() parser:flag("-f"):count("0-3") local args parser:action(function(passed_args) args = passed_args end) parser:parse{"-ff"} assert.same({f = 2}, args) end) it("for commands are called in reverse order", function() local args = {} local parser = Parser():action(function(passed_args) args[1] = passed_args args.last = 1 end) parser:flag("-f"):count("0-3") local foo = parser:command("foo"):action(function(passed_args, name) assert.equals("foo", name) args[2] = passed_args args.last = 2 end) foo:flag("-g") parser:parse{"foo", "-f", "-g", "-f"} assert.same({ last = 1, {foo = true, f = 2, g = true}, {foo = true, f = 2, g = true} }, args) end) end) argparse-0.7.1/spec/arguments_spec.lua000066400000000000000000000133341370121727600200110ustar00rootroot00000000000000local Parser = require "argparse" getmetatable(Parser()).error = function(_, msg) error(msg) end describe("tests related to positional arguments", function() describe("passing correct arguments", function() it("handles empty parser correctly", function() local parser = Parser() local args = parser:parse({}) assert.same({}, args) end) it("handles one argument correctly", function() local parser = Parser() parser:argument "foo" local args = parser:parse({"bar"}) assert.same({foo = "bar"}, args) end) it("handles optional argument correctly", function() local parser = Parser() parser:argument "foo" :args "?" local args = parser:parse({"bar"}) assert.same({foo = "bar"}, args) end) it("handles several arguments correctly", function() local parser = Parser() parser:argument "foo1" parser:argument "foo2" local args = parser:parse({"bar", "baz"}) assert.same({foo1 = "bar", foo2 = "baz"}, args) end) it("handles multi-argument correctly", function() local parser = Parser() parser:argument "foo" { args = "*" } local args = parser:parse({"bar", "baz", "qu"}) assert.same({foo = {"bar", "baz", "qu"}}, args) end) it("handles restrained multi-argument correctly", function() local parser = Parser() parser:argument "foo" { args = "2-4" } local args = parser:parse({"bar", "baz"}) assert.same({foo = {"bar", "baz"}}, args) end) it("handles several multi-arguments correctly", function() local parser = Parser() parser:argument "foo1" { args = "1-2" } parser:argument "foo2" { args = "*" } local args = parser:parse({"bar"}) assert.same({foo1 = {"bar"}, foo2 = {}}, args) args = parser:parse({"bar", "baz", "qu"}) assert.same({foo1 = {"bar", "baz"}, foo2 = {"qu"}}, args) end) it("handles hyphen correctly", function() local parser = Parser() parser:argument "foo" local args = parser:parse({"-"}) assert.same({foo = "-"}, args) end) it("handles double hyphen correctly", function() local parser = Parser() parser:argument "foo" local args = parser:parse({"--", "-q"}) assert.same({foo = "-q"}, args) end) end) describe("passing incorrect arguments", function() it("handles extra arguments with empty parser correctly", function() local parser = Parser() assert.has_error(function() parser:parse{"foo"} end, "too many arguments") end) it("handles extra arguments with one argument correctly", function() local parser = Parser() parser:argument "foo" assert.has_error(function() parser:parse{"bar", "baz"} end, "too many arguments") end) it("handles too few arguments with one argument correctly", function() local parser = Parser() parser:argument "foo" assert.has_error(function() parser:parse{} end, "missing argument 'foo'") end) it("handles extra arguments with several arguments correctly", function() local parser = Parser() parser:argument "foo1" parser:argument "foo2" assert.has_error(function() parser:parse{"bar", "baz", "qu"} end, "too many arguments") end) it("handles too few arguments with several arguments correctly", function() local parser = Parser() parser:argument "foo1" parser:argument "foo2" assert.has_error(function() parser:parse{"bar"} end, "missing argument 'foo2'") end) it("handles too few arguments with multi-argument correctly", function() local parser = Parser() parser:argument "foo" { args = "+" } assert.has_error(function() parser:parse{} end, "missing argument 'foo'") end) it("handles too many arguments with multi-argument correctly", function() local parser = Parser() parser:argument "foo" { args = "2-4" } assert.has_error(function() parser:parse{"foo", "bar", "baz", "qu", "quu"} end, "too many arguments") end) it("handles too few arguments with multi-argument correctly", function() local parser = Parser() parser:argument "foo" { args = "2-4" } assert.has_error(function() parser:parse{"foo"} end, "argument 'foo' requires at least 2 arguments") end) it("handles too many arguments with several multi-arguments correctly", function() local parser = Parser() parser:argument "foo1" { args = "1-2" } parser:argument "foo2" { args = "0-1" } assert.has_error(function() parser:parse{"foo", "bar", "baz", "qu"} end, "too many arguments") end) it("handles too few arguments with several multi-arguments correctly", function() local parser = Parser() parser:argument "foo1" { args = "1-2" } parser:argument "foo2" { args = "*" } assert.has_error(function() parser:parse{} end, "missing argument 'foo1'") end) it("handles invalid argument choices correctly", function() local parse = Parser() parse:argument "foo" { choices = {"bar", "baz", "qu"} } assert.has_error(function() parse:parse{"foo", "quu"} end, "argument 'foo' must be one of 'bar', 'baz', 'qu'") end) end) end) argparse-0.7.1/spec/commands_spec.lua000066400000000000000000000044631370121727600176100ustar00rootroot00000000000000local Parser = require "argparse" getmetatable(Parser()).error = function(_, msg) error(msg) end describe("tests related to commands", function() it("handles commands after arguments", function() local parser = Parser "name" parser:argument "file" parser:command "create" parser:command "remove" local args = parser:parse{"temp.txt", "remove"} assert.same({file = "temp.txt", remove = true}, args) end) it("switches context properly", function() local parser = Parser "name" :add_help(false) local install = parser:command "install" install:flag "-q" "--quiet" local args = parser:parse{"install", "-q"} assert.same({install = true, quiet = true}, args) assert.has_error(function() parser:parse{"-q", "install"} end, "unknown option '-q'") end) it("uses command_target property to save command name", function() local parser = Parser "name" :add_help(false) :command_target("command") local install = parser:command "install" install:flag "-q" "--quiet" local args = parser:parse{"install", "-q"} assert.same({install = true, quiet = true, command = "install"}, args) end) it("allows to continue passing old options", function() local parser = Parser "name" parser:flag "-v" "--verbose" { count = "*" } parser:command "install" local args = parser:parse{"-vv", "install", "--verbose"} assert.same({install = true, verbose = 3}, args) end) it("handles nested commands", function() local parser = Parser "name" local foo = parser:command "foo" foo:command "bar" foo:command "baz" local args = parser:parse{"foo", "bar"} assert.same({foo = true, bar = true}, args) end) it("handles no commands depending on parser.require_command", function() local parser = Parser "name" parser:command "install" assert.has_error(function() parser:parse{} end, "a command is required") parser:require_command(false) local args = parser:parse{} assert.same({}, args) end) it("Detects wrong commands", function() local parser = Parser "name" parser:command "install" assert.has_error(function() parser:parse{"run"} end, "unknown command 'run'") end) end) argparse-0.7.1/spec/completion_spec.lua000066400000000000000000000231341370121727600201540ustar00rootroot00000000000000local script = "./spec/comptest" local script_cmd = "lua" if package.loaded["luacov.runner"] then script_cmd = script_cmd .. " -lluacov" end script_cmd = script_cmd .. " " .. script local function get_output(args) local handler = io.popen(script_cmd .. " " .. args .. " 2>&1", "r") local output = handler:read("*a") handler:close() return output end describe("tests related to generation of shell completion scripts", function() it("generates correct bash completion script", function() assert.equal([=[ _comptest() { local IFS=$' \t\n' local args cur prev cmd opts arg args=("${COMP_WORDS[@]}") cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" opts="-h --help --completion -v --verbose -f --files" case "$prev" in --completion) COMPREPLY=($(compgen -W "bash zsh fish" -- "$cur")) return 0 ;; -f|--files) COMPREPLY=($(compgen -f -- "$cur")) return 0 ;; esac args=("${args[@]:1}") for arg in "${args[@]}"; do case "$arg" in help) cmd="help" opts="$opts -h --help" break ;; completion) cmd="completion" opts="$opts -h --help" break ;; install|i) cmd="install" opts="$opts -h --help --deps-mode --no-doc" break ;; admin) cmd="admin" opts="$opts -h --help" args=("${args[@]:1}") for arg in "${args[@]}"; do case "$arg" in help) cmd="$cmd help" opts="$opts -h --help" break ;; add) cmd="$cmd add" opts="$opts -h --help" break ;; remove) cmd="$cmd remove" opts="$opts -h --help" break ;; esac done break ;; esac done case "$cmd" in '') COMPREPLY=($(compgen -W "help completion install i admin" -- "$cur")) ;; 'help') COMPREPLY=($(compgen -W "help completion install i admin" -- "$cur")) ;; 'install') case "$prev" in --deps-mode) COMPREPLY=($(compgen -W "all one order none" -- "$cur")) return 0 ;; esac ;; 'admin') COMPREPLY=($(compgen -W "help add remove" -- "$cur")) ;; 'admin help') COMPREPLY=($(compgen -W "help add remove" -- "$cur")) ;; esac if [[ "$cur" = -* ]]; then COMPREPLY=($(compgen -W "$opts" -- "$cur")) fi } complete -F _comptest -o bashdefault -o default comptest ]=], get_output("completion bash")) end) it("generates correct zsh completion script", function() assert.equal([=[ #compdef comptest _comptest() { local context state state_descr line typeset -A opt_args local -a options=( {-h,--help}"[Show this help message and exit]" "--completion[Output a shell completion script for the specified shell]: :(bash zsh fish)" "*"{-v,--verbose}"[Set the verbosity level]" {-f,--files}"[A description with illegal \"' characters]:*: :_files" ) _arguments -s -S \ $options \ ": :_comptest_cmds" \ "*:: :->args" \ && return 0 case $words[1] in help) options=( $options {-h,--help}"[Show this help message and exit]" ) _arguments -s -S \ $options \ ": :(help completion install i admin)" \ && return 0 ;; completion) options=( $options {-h,--help}"[Show this help message and exit]" ) _arguments -s -S \ $options \ ": :(bash zsh fish)" \ && return 0 ;; install|i) options=( $options {-h,--help}"[Show this help message and exit]" "--deps-mode: :(all one order none)" "--no-doc[Install without documentation]" ) _arguments -s -S \ $options \ && return 0 ;; admin) options=( $options {-h,--help}"[Show this help message and exit]" ) _arguments -s -S \ $options \ ": :_comptest_admin_cmds" \ "*:: :->args" \ && return 0 case $words[1] in help) options=( $options {-h,--help}"[Show this help message and exit]" ) _arguments -s -S \ $options \ ": :(help add remove)" \ && return 0 ;; add) options=( $options {-h,--help}"[Show this help message and exit]" ) _arguments -s -S \ $options \ ": :_files" \ && return 0 ;; remove) options=( $options {-h,--help}"[Show this help message and exit]" ) _arguments -s -S \ $options \ ": :_files" \ && return 0 ;; esac ;; esac return 1 } _comptest_cmds() { local -a commands=( "help:Show help for commands" "completion:Output a shell completion script" {install,i}":Install a rock" "admin:Rock server administration interface" ) _describe "command" commands } _comptest_admin_cmds() { local -a commands=( "help:Show help for commands" "add:Add a rock to a server" "remove:Remove a rock from a server" ) _describe "command" commands } _comptest ]=], get_output("completion zsh")) end) it("generates correct fish completion script", function() assert.equal([=[ function __fish_comptest_print_command set -l cmdline (commandline -poc) set -l cmd set -e cmdline[1] for arg in $cmdline switch $arg case help set cmd $cmd help break case completion set cmd $cmd completion break case install i set cmd $cmd install break case admin set cmd $cmd admin set -e cmdline[1] for arg in $cmdline switch $arg case help set cmd $cmd help break case add set cmd $cmd add break case remove set cmd $cmd remove break end end break end end echo "$cmd" end function __fish_comptest_using_command test (__fish_comptest_print_command) = "$argv" and return 0 or return 1 end function __fish_comptest_seen_command string match -q "$argv*" (__fish_comptest_print_command) and return 0 or return 1 end complete -c comptest -n '__fish_comptest_using_command' -xa 'help' -d 'Show help for commands' complete -c comptest -n '__fish_comptest_using_command' -xa 'completion' -d 'Output a shell completion script' complete -c comptest -n '__fish_comptest_using_command' -xa 'install i' -d 'Install a rock' complete -c comptest -n '__fish_comptest_using_command' -xa 'admin' -d 'Rock server administration interface' complete -c comptest -s h -l help -d 'Show this help message and exit' complete -c comptest -l completion -xa 'bash zsh fish' -d 'Output a shell completion script for the specified shell' complete -c comptest -s v -l verbose -d 'Set the verbosity level' complete -c comptest -s f -l files -r -d 'A description with illegal "\' characters' complete -c comptest -n '__fish_comptest_using_command help' -xa 'help completion install i admin' complete -c comptest -n '__fish_comptest_seen_command help' -s h -l help -d 'Show this help message and exit' complete -c comptest -n '__fish_comptest_seen_command completion' -s h -l help -d 'Show this help message and exit' complete -c comptest -n '__fish_comptest_seen_command install' -s h -l help -d 'Show this help message and exit' complete -c comptest -n '__fish_comptest_seen_command install' -l deps-mode -xa 'all one order none' complete -c comptest -n '__fish_comptest_seen_command install' -l no-doc -d 'Install without documentation' complete -c comptest -n '__fish_comptest_using_command admin' -xa 'help' -d 'Show help for commands' complete -c comptest -n '__fish_comptest_using_command admin' -xa 'add' -d 'Add a rock to a server' complete -c comptest -n '__fish_comptest_using_command admin' -xa 'remove' -d 'Remove a rock from a server' complete -c comptest -n '__fish_comptest_seen_command admin' -s h -l help -d 'Show this help message and exit' complete -c comptest -n '__fish_comptest_using_command admin help' -xa 'help add remove' complete -c comptest -n '__fish_comptest_seen_command admin help' -s h -l help -d 'Show this help message and exit' complete -c comptest -n '__fish_comptest_seen_command admin add' -s h -l help -d 'Show this help message and exit' complete -c comptest -n '__fish_comptest_seen_command admin remove' -s h -l help -d 'Show this help message and exit' ]=], get_output("completion fish")) end) end) argparse-0.7.1/spec/comptest000077500000000000000000000016371370121727600160560ustar00rootroot00000000000000#!/usr/bin/env lua local argparse = require "argparse" local parser = argparse() :add_help_command() :add_complete_command() :add_complete() parser:flag "-v --verbose" :description "Set the verbosity level." :count "*" parser:option "-f --files" :description "A description with illegal \"' characters." :args "+" local install = parser:command "install i" :description "Install a rock." install:option "--deps-mode" :choices {"all", "one", "order", "none"} install:flag "--no-doc" :description "Install without documentation." local admin = parser:command "admin" :description "Rock server administration interface." :add_help_command() local admin_add = admin:command "add" :description "Add a rock to a server." admin_add:argument "rock" local admin_remove = admin:command "remove" :description "Remove a rock from a server." admin_remove:argument "rock" parser:parse() argparse-0.7.1/spec/convert_spec.lua000066400000000000000000000043521370121727600174640ustar00rootroot00000000000000local Parser = require "argparse" getmetatable(Parser()).error = function(_, msg) error(msg) end describe("tests related to converters", function() it("converts arguments", function() local parser = Parser() parser:argument "numbers" { convert = tonumber, args = "+" } local args = parser:parse{"1", "2", "500"} assert.same({numbers = {1, 2, 500}}, args) end) it("accepts an array of converters", function() local function tocoords(str) local x, y = str:match("^([^,]*),([^,]*)$") x = tonumber(x) y = tonumber(y) return x and y and {x, y} end local parser = Parser() parser:option "-c --circle" { convert = {tonumber, tocoords}, args = 2 } local args = parser:parse{"-c", "123", "456,567"} assert.same({circle = {123, {456, 567}}}, args) end) it("converts arguments using mapping", function() local choice = { foo = 1, bar = 2 } local parser = Parser() parser:argument "choice" { convert = choice, args = "+" } local args = parser:parse{"foo", "bar"} assert.same({choice = {1, 2}}, args) end) it("accepts false", function() local function toboolean(x) if x == "true" then return true elseif x == "false" then return false end end local parser = Parser() parser:argument "booleans" { convert = toboolean, args = "+" } local args = parser:parse{"true", "false"} assert.same({booleans = {true, false}}, args) end) it("raises an error when it can't convert", function() local parser = Parser() parser:argument "numbers" { convert = tonumber, args = "+" } assert.has_error(function() parser:parse{"foo", "bar", "baz"} end, "malformed argument 'foo'") end) it("second return value is used as error message", function() local parser = Parser() parser:argument "numbers" { convert = function(x) return tonumber(x), x .. " is not a number" end } assert.has_error(function() parser:parse{"foo"} end, "foo is not a number") end) end) argparse-0.7.1/spec/default_spec.lua000066400000000000000000000114671370121727600174350ustar00rootroot00000000000000local Parser = require "argparse" getmetatable(Parser()).error = function(_, msg) error(msg) end describe("tests related to default values", function() describe("default values for arguments", function() it("handles default argument correctly", function() local parser = Parser() parser:argument "foo" :default "bar" local args = parser:parse{} assert.same({foo = "bar"}, args) args = parser:parse{"baz"} assert.same({foo = "baz"}, args) end) it("handles default argument for multi-argument correctly", function() local parser = Parser() parser:argument "foo" { args = 3, default = "bar", defmode = "arg" } local args = parser:parse{"baz"} assert.same({foo = {"baz", "bar", "bar"}}, args) end) it("handles default value for multi-argument correctly", function() local parser = Parser() parser:argument "foo" { args = 3, default = "bar" } local args = parser:parse{} assert.same({foo = {"bar", "bar", "bar"}}, args) end) it("does not use default values if not needed", function() local parser = Parser() parser:argument "foo" { args = "1-2", default = "bar" } local args = parser:parse({"baz"}) assert.same({foo = {"baz"}}, args) end) end) describe("default values for options", function() it("handles option with default value correctly", function() local parser = Parser() parser:option "-o" "--output" :default "a.out" :defmode "unused" local args = parser:parse{} assert.same({output = "a.out"}, args) args = parser:parse{"--output", "foo.txt"} assert.same({output = "foo.txt"}, args) assert.has_error(function() parser:parse{"-o"} end, "option '-o' requires an argument") end) it("handles option with default value for multi-argument option correctly", function() local parser = Parser() parser:option("-s --several", "Two or three things", "foo", nil, "2-3") local args = parser:parse{} assert.same({several = {"foo", "foo"}}, args) end) it("handles option with default value and argument", function() local parser = Parser() parser:option "-o" "--output" { default = "a.out", defmode = "arg+count" } local args = parser:parse{} assert.same({output = "a.out"}, args) args = parser:parse{"-o"} assert.same({output = "a.out"}, args) args = parser:parse{"-o", "value"} assert.same({output = "value"}, args) end) it("handles option with default argument correctly", function() local parser = Parser() parser:option "-p" "--protected" :target "password" :default "password" :defmode "arg" local args = parser:parse{"-p"} assert.same({password = "password"}, args) end) it("doesn't use default argument if option is not invoked", function() local parser = Parser() parser:option "-f" "--foo" { default = "bar", defmode = "arg" } local args = parser:parse{} assert.same({}, args) end) it("handles default multi-argument correctly", function() local parser = Parser() parser:option "-f" "--foo" { args = 3, default = "bar", defmode = "arg" } local args = parser:parse({"--foo=baz"}) assert.same({foo = {"baz", "bar", "bar"}}, args) end) it("does not use default values if not needed", function() local parser = Parser() parser:option "-f" "--foo" { args = "1-2", default = "bar", defmode = "arg" } local args = parser:parse({"-f", "baz"}) assert.same({foo = {"baz"}}, args) end) it("handles multi-count options with default value correctly", function() local parser = Parser() parser:option "-f" "--foo" { count = "*", default = "bar", defmode = "arg + count" } local args = parser:parse({"-f", "--foo=baz", "--foo"}) assert.same({foo = {"bar", "baz", "bar"}}, args) end) it("completes missing invocations for multi-count options with default argument", function() local parser = Parser() parser:option "-f" "--foo" { count = "2", default = "bar", defmode = "arg" } local args = parser:parse({"-ffff"}) assert.same({foo = {"fff", "bar"}}, args) end) end) end) argparse-0.7.1/spec/help_spec.lua000066400000000000000000000500741370121727600167360ustar00rootroot00000000000000local Parser = require "argparse" getmetatable(Parser()).error = function(_, msg) error(msg) end describe("tests related to help message generation", function() it("creates correct help message for empty parser", function() local parser = Parser "foo" assert.equal([[ Usage: foo [-h] Options: -h, --help Show this help message and exit.]], parser:get_help()) end) it("uses custom help option ", function() local parser = Parser "foo" :add_help "/?" assert.equal([[ Usage: foo [/?] Options: /? Show this help message and exit.]], parser:get_help()) end) it("uses description and epilog", function() local parser = Parser("foo", "A description.", "An epilog.") assert.equal([[ Usage: foo [-h] A description. Options: -h, --help Show this help message and exit. An epilog.]], parser:get_help()) end) it("creates correct help message for arguments", function() local parser = Parser "foo" parser:argument "first" parser:argument "second-and-third" :args "2" parser:argument "maybe-fourth" :args "?" parser:argument("others", "Optional.") :args "*" assert.equal([[ Usage: foo [-h] [] [] ... Arguments: first second-and-third maybe-fourth others Optional. Options: -h, --help Show this help message and exit.]], parser:get_help()) end) it("creates correct help message for options", function() local parser = Parser "foo" parser:flag "-q" "--quiet" parser:option "--from" :count "1" :target "server" parser:option "--config" assert.equal([[ Usage: foo [-h] [-q] --from [--config ] Options: -h, --help Show this help message and exit. -q, --quiet --from --config ]], parser:get_help()) end) it("creates correct help message for arguments with choices", function() local parser = Parser "foo" parser:argument "move" :choices {"rock", "paper", "scissors"} assert.equal([[ Usage: foo [-h] {rock,paper,scissors} Arguments: {rock,paper,scissors} Options: -h, --help Show this help message and exit.]], parser:get_help()) end) it("creates correct help message for options with argument choices", function() local parser = Parser "foo" parser:option "--format" :choices {"short", "medium", "full"} assert.equal([[ Usage: foo [-h] [--format {short,medium,full}] Options: -h, --help Show this help message and exit. --format {short,medium,full}]], parser:get_help()) end) it("adds margin for multiline descriptions", function() local parser = Parser "foo" parser:flag "-v" :count "0-2" :target "verbosity" :description [[ Sets verbosity level. -v: Report all warnings. -vv: Report all debugging information.]] assert.equal([[ Usage: foo [-h] [-v] Options: -h, --help Show this help message and exit. -v Sets verbosity level. -v: Report all warnings. -vv: Report all debugging information.]], parser:get_help()) end) it("puts different aliases on different lines if there are arguments", function() local parser = Parser "foo" parser:option "-o --output" assert.equal([[ Usage: foo [-h] [-o ] Options: -h, --help Show this help message and exit. -o , --output ]], parser:get_help()) end) it("handles description with more lines than usage", function() local parser = Parser "foo" parser:option "-o --output" :description [[ Sets output file. If missing, 'a.out' is used by default. If '-' is passed, output to stdount. ]] assert.equal([[ Usage: foo [-h] [-o ] Options: -h, --help Show this help message and exit. -o , Sets output file. --output If missing, 'a.out' is used by default. If '-' is passed, output to stdount.]], parser:get_help()) end) it("handles description with less lines than usage", function() local parser = Parser "foo" parser:option "-o --output" :description "Sets output file." assert.equal([[ Usage: foo [-h] [-o ] Options: -h, --help Show this help message and exit. -o , Sets output file. --output ]], parser:get_help()) end) it("handles very long argument lists", function() local parser = Parser "foo" parser:option "-t --at-least-three" :args("3+") :argname {"", "", ""} :description "Sometimes argument lists are really long." assert.equal([[ Usage: foo [-h] [-t ...] Options: -h, --help Show this help message and exit. -t ..., --at-least-three ... Sometimes argument lists are really long.]], parser:get_help()) end) it("shows default values", function() local parser = Parser "foo" parser:option "-o" :default "a.out" parser:option "-p" :default "8080" :description "Port." assert.equal([[ Usage: foo [-h] [-o ] [-p

] Options: -h, --help Show this help message and exit. -o default: a.out -p

Port. (default: 8080)]], parser:get_help()) end) it("does not show default value when show_default == false", function() local parser = Parser "foo" parser:option "-o" :default "a.out" :show_default(false) parser:option "-p" :default "8080" :show_default(false) :description "Port." assert.equal([[ Usage: foo [-h] [-o ] [-p

] Options: -h, --help Show this help message and exit. -o -p

Port.]], parser:get_help()) end) it("creates correct help message for commands", function() local parser = Parser "foo" parser:flag "-q --quiet" local run = parser:command "run" :description "Run! " run:option "--where" assert.equal([[ Usage: foo [-h] [-q] ... Options: -h, --help Show this help message and exit. -q, --quiet Commands: run Run! ]], parser:get_help()) end) it("creates correct help message for subcommands", function() local parser = Parser "foo" parser:flag "-q" "--quiet" local run = parser:command "run" run:option "--where" assert.equal([[ Usage: foo run [-h] [--where ] Options: -h, --help Show this help message and exit. --where ]], run:get_help()) end) it("uses message provided by user", function() local parser = Parser "foo" :help "I don't like your format of help messages" parser:flag "-q" "--quiet" assert.equal([[ I don't like your format of help messages]], parser:get_help()) end) it("does not mention hidden arguments, options, and commands", function() local parser = Parser "foo" parser:argument "normal" parser:argument "deprecated" :args "?" :hidden(true) parser:flag "--feature" parser:flag "--misfeature" :hidden(true) parser:command "good" parser:command "okay" parser:command "never-use-this-one" :hidden(true) assert.equal([[ Usage: foo [-h] [--feature] ... Arguments: normal Options: -h, --help Show this help message and exit. --feature Commands: good okay]], parser:get_help()) end) it("omits categories if all elements are hidden", function() local parser = Parser "foo" :add_help(false) parser:argument "deprecated" :args "?" :hidden(true) parser:flag "--misfeature" :hidden(true) assert.equal([[ Usage: foo]], parser:get_help()) end) it("does not mention hidden option and command aliases", function() local parser = Parser "foo" parser:option "--server" :hidden_name "--from" parser:command "newname" :hidden_name "oldname" assert.equal([[ Usage: foo [-h] [--server ] ... Options: -h, --help Show this help message and exit. --server Commands: newname]], parser:get_help()) end) it("supports grouping options", function() local parser = Parser "foo" :add_help(false) parser:argument "thing" parser:group("Options for setting position", parser:option "--coords" :args(2) :argname {"", ""} :description "Set coordinates.", parser:option "--polar" :args(2) :argname {"", ""} :description "Set polar coordinates." ) parser:group("Options for setting style", parser:flag "--dotted" :description "More dots.", parser:option "--width" :argname "" :description "Set width." ) assert.equal([[ Usage: foo [--coords ] [--polar ] [--dotted] [--width ] Arguments: thing Options for setting position: --coords Set coordinates. --polar Set polar coordinates. Options for setting style: --dotted More dots. --width Set width.]], parser:get_help()) end) it("adds default group with 'other' prefix if not all elements of a type are grouped", function() local parser = Parser "foo" parser:group("Main arguments", parser:argument "foo", parser:argument "bar", parser:flag "--use-default-args" ) parser:argument "optional" :args "?" parser:group("Main options", parser:flag "--something", parser:option "--test" ) parser:flag "--version" parser:group("Some commands", parser:command "foo", parser:command "bar" ) parser:command "another-command" assert.equal([[ Usage: foo [-h] [--use-default-args] [--something] [--test ] [--version] [] ... Main arguments: foo bar --use-default-args Other arguments: optional Main options: --something --test Other options: -h, --help Show this help message and exit. --version Some commands: foo bar Other commands: another-command]], parser:get_help()) end) it("allows spacing out element help blocks more with help_vertical_space", function() local parser = Parser "foo" :help_vertical_space(1) parser:argument "arg1" :description "Argument number one." parser:argument "arg2" :description "Argument number two." parser:flag "-p" :description "This is a thing." parser:option "-f --foo" :description [[ And this things uses many lines. Because it has lots of complex behaviour. That needs documenting.]] assert.equal([[ Usage: foo [-h] [-p] [-f ] Arguments: arg1 Argument number one. arg2 Argument number two. Options: -h, --help Show this help message and exit. -p This is a thing. -f , And this things uses many lines. --foo Because it has lots of complex behaviour. That needs documenting.]], parser:get_help()) end) it("inherits help_vertical_space in commands", function() local parser = Parser "foo" :help_vertical_space(1) local cmd1 = parser:command "cmd1" :help_vertical_space(2) cmd1:flag("-a", "Do a thing.") cmd1:flag("-b", "Do b thing.") local cmd2 = parser:command "cmd2" cmd2:flag("-c", "Do c thing.") cmd2:flag("-d", "Do d thing.") assert.equal([[ Usage: foo cmd1 [-h] [-a] [-b] Options: -h, --help Show this help message and exit. -a Do a thing. -b Do b thing.]], cmd1:get_help()) assert.equal([[ Usage: foo cmd2 [-h] [-c] [-d] Options: -h, --help Show this help message and exit. -c Do c thing. -d Do d thing.]], cmd2:get_help()) end) it("allows configuring margins using help_usage_margin and help_description_margin", function() local parser = Parser "foo" :help_usage_margin(2) :help_description_margin(15) parser:argument "arg1" :description "Argument number one." parser:argument "arg2" :description "Argument number two." parser:flag "-p" :description "This is a thing." parser:option "-f --foo" :description [[ And this things uses many lines. Because it has lots of complex behaviour. That needs documenting.]] assert.equal([[ Usage: foo [-h] [-p] [-f ] Arguments: arg1 Argument number one. arg2 Argument number two. Options: -h, --help Show this help message and exit. -p This is a thing. -f , And this things uses many lines. --foo Because it has lots of complex behaviour. That needs documenting.]], parser:get_help()) end) describe("autowrap", function() it("automatically wraps descriptions to match given max width", function() local parser = Parser "foo" :help_max_width(80) parser:option "-f --foo" :description("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor " .. "incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation " .. "ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit " .. "in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat " .. "non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.") parser:option "-b --bar" :description "See above." assert.equal([[ Usage: foo [-h] [-f ] [-b ] Options: -h, --help Show this help message and exit. -f , Lorem ipsum dolor sit amet, consectetur adipiscing --foo elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. -b , See above. --bar ]], parser:get_help()) end) it("preserves existing line breaks", function() local parser = Parser "foo" :help_max_width(80) parser:option "-f --foo" :description("This is a long line, it should be broken down into several lines. " .. [[ It just keeps going and going. This should always be a new line. Another one. ]]) parser:option "-b --bar" assert.equal([[ Usage: foo [-h] [-f ] [-b ] Options: -h, --help Show this help message and exit. -f , This is a long line, it should be broken down into --foo several lines. It just keeps going and going. This should always be a new line. Another one. -b , --bar ]], parser:get_help()) end) it("preserves indentation", function() local parser = Parser "foo" :help_max_width(80) parser:option "-f --foo" :description("This is a long line, it should be broken down into several lines.\n" .. " This paragraph is indented with three spaces, so when it gets broken down into several lines, " .. "they will be, too.\n\n" .. " That was an empty line there, preserve it.") assert.equal([[ Usage: foo [-h] [-f ] Options: -h, --help Show this help message and exit. -f , This is a long line, it should be broken down into --foo several lines. This paragraph is indented with three spaces, so when it gets broken down into several lines, they will be, too. That was an empty line there, preserve it.]], parser:get_help()) end) it("preserves indentation of list items", function() local parser = Parser "foo" :help_max_width(80) parser:option "-f --foo" :description("Let's start a list:\n\n" .. "* Here is a list item.\n" .. "* Here is another one, this one is very long so it needs several lines. More words. Word. Word.\n" .. " + Here is a nested list item. Word. Word. Word. Word. Word. Bird. Word. Bird. Bird. Bird.\n" .. "* Back to normal list, this one uses several spaces after the list item mark. Bird. Bird. Bird.") assert.equal([[ Usage: foo [-h] [-f ] Options: -h, --help Show this help message and exit. -f , Let's start a list: --foo * Here is a list item. * Here is another one, this one is very long so it needs several lines. More words. Word. Word. + Here is a nested list item. Word. Word. Word. Word. Word. Bird. Word. Bird. Bird. Bird. * Back to normal list, this one uses several spaces after the list item mark. Bird. Bird. Bird.]], parser:get_help()) end) it("preserves multiple spaces between words", function() local parser = Parser "foo" :help_max_width(80) parser:option "-f --foo" :description("This is a long line with two spaces between words, it should be broken down.") assert.equal([[ Usage: foo [-h] [-f ] Options: -h, --help Show this help message and exit. -f , This is a long line with two spaces between --foo words, it should be broken down.]], parser:get_help()) end) it("autowraps description and epilog", function() local parser = Parser "foo" :help_max_width(80) :description("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor " .. "incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation " .. "ullamco laboris nisi ut aliquip ex ea commodo consequat.") :epilog("Duis aute irure dolor in reprehenderit " .. "in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat " .. "non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.") assert.equal([[ Usage: foo [-h] Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Options: -h, --help Show this help message and exit. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.]], parser:get_help()) end) end) end) argparse-0.7.1/spec/integrity_spec.lua000066400000000000000000000130271370121727600200210ustar00rootroot00000000000000local script = "./spec/script.lua" local script_cmd = "lua" if package.loaded["luacov.runner"] then script_cmd = script_cmd .. " -lluacov" end script_cmd = script_cmd .. " " .. script local function get_output(args) local handler = io.popen(script_cmd .. " " .. args .. " 2>&1", "r") local output = handler:read("*a") handler:close() return output end describe("tests related to CLI behaviour #unsafe", function() describe("error messages", function() it("generates correct error message without arguments", function() assert.equal([[ Usage: ]]..script..[[ [-h] [-v] [] ... Error: missing argument 'input' ]], get_output("")) end) it("generates correct error message with too many arguments", function() assert.equal([[ Usage: ]]..script..[[ [-h] [-v] [] ... Error: unknown command 'bar' ]], get_output("foo bar")) end) it("generates correct error message with unexpected argument", function() assert.equal([[ Usage: ]]..script..[[ [-h] [-v] [] ... Error: option '--verbose' does not take arguments ]], get_output("--verbose=true")) end) it("generates correct error message with unexpected option", function() assert.equal([[ Usage: ]]..script..[[ [-h] [-v] [] ... Error: unknown option '-q' Did you mean one of these: '-h' '-v'? ]], get_output("-vq")) end) it("generates correct error message and tip with unexpected command", function() assert.equal([[ Usage: ]]..script..[[ [-h] [-v] [] ... Error: unknown command 'nstall' Did you mean 'install'? ]], get_output("foo nstall")) end) it("generates correct error message without arguments in command", function() assert.equal([[ Usage: ]]..script..[[ install [-h] [-f ] [] Error: missing argument 'rock' ]], get_output("foo install")) end) it("generates correct error message and tip in command", function() assert.equal([[ Usage: ]]..script..[[ install [-h] [-f ] [] Error: unknown option '--form' Did you mean '--from'? ]], get_output("foo install bar --form=there")) end) end) describe("help messages", function() it("generates correct help message using help flag", function() assert.equal([[ Usage: ]]..script..[[ [-h] [-v] [] ... A testing program. Arguments: input Options: -h, --help Show this help message and exit. -v, --verbose Sets verbosity level. Commands: help Show help for commands. install Install a rock. ]], get_output("--help")) end) it("generates correct help message using help command", function() assert.equal([[ Usage: ]]..script..[[ [-h] [-v] [] ... A testing program. Arguments: input Options: -h, --help Show this help message and exit. -v, --verbose Sets verbosity level. Commands: help Show help for commands. install Install a rock. ]], get_output("foo help")) end) it("generates correct help message for command using help flag", function() assert.equal([[ Usage: ]]..script..[[ install [-h] [-f ] [] Install a rock. Arguments: rock Name of the rock. version Version of the rock. Options: -h, --help Show this help message and exit. -f , Fetch the rock from this server. --from ]], get_output("foo install --help")) end) it("generates correct help message for command using help command", function() assert.equal([[ Usage: ]]..script..[[ install [-h] [-f ] [] Install a rock. Arguments: rock Name of the rock. version Version of the rock. Options: -h, --help Show this help message and exit. -f , Fetch the rock from this server. --from ]], get_output("foo help install")) end) end) describe("data flow", function() it("works with one argument", function() local handler = io.popen(script_cmd .. " foo 2>&1", "r") assert.equal("foo", handler:read "*l") assert.equal("0", handler:read "*l") handler:close() end) it("works with one argument and a flag", function() local handler = io.popen(script_cmd .. " -v foo --verbose 2>&1", "r") assert.equal("foo", handler:read "*l") assert.equal("2", handler:read "*l") handler:close() end) it("works with command", function() local handler = io.popen(script_cmd .. " foo -v install bar 2>&1", "r") assert.equal("foo", handler:read "*l") assert.equal("1", handler:read "*l") assert.equal("true", handler:read "*l") assert.equal("bar", handler:read "*l") assert.equal("nil", handler:read "*l") assert.equal("nil", handler:read "*l") handler:close() end) it("works with command and options", function() local handler = io.popen(script_cmd .. " foo --verbose install bar 0.1 --from=there -vv 2>&1", "r") assert.equal("foo", handler:read "*l") assert.equal("2", handler:read "*l") assert.equal("true", handler:read "*l") assert.equal("bar", handler:read "*l") assert.equal("0.1", handler:read "*l") assert.equal("there", handler:read "*l") handler:close() end) end) end) argparse-0.7.1/spec/invalid_property_spec.lua000066400000000000000000000025651370121727600214020ustar00rootroot00000000000000local Parser = require "argparse" describe("invalid property detection", function() it("detects properties with invalid type", function() assert.has_error(function() Parser():name(12345) end, "bad property 'name' (string expected, got number)") assert.has_error(function() Parser():option "--foo":convert(true) end, "bad property 'convert' (function or table expected, got boolean)") end) it("detects invalid count and args properties", function() assert.has_error(function() Parser():option "--foo":count(false) end, "bad property 'count' (number or string expected, got boolean)") assert.has_error(function() Parser():option "--foo":args({}) end, "bad property 'args' (number or string expected, got table)") assert.has_error(function() Parser():option "--foo":count("foobar") end, "bad property 'count'") assert.has_error(function() Parser():option "--foo":args("123-") end, "bad property 'args'") end) it("detects unknown named actions", function() assert.has_error(function() Parser():option "--foo":action(false) end, "bad property 'action' (function or string expected, got boolean)") assert.has_error(function() Parser():option "--foo":action("catcat") end, "unknown action 'catcat'") end) end) argparse-0.7.1/spec/mutex_spec.lua000066400000000000000000000072511370121727600171470ustar00rootroot00000000000000local Parser = require "argparse" getmetatable(Parser()).error = function(_, msg) error(msg) end describe("tests related to mutexes", function() it("handles mutex correctly", function() local parser = Parser() parser:mutex( parser:flag "-q" "--quiet" :description "Supress logging. ", parser:flag "-v" "--verbose" :description "Print additional debug information. " ) local args = parser:parse{"-q"} assert.same({quiet = true}, args) args = parser:parse{"-v"} assert.same({verbose = true}, args) args = parser:parse{} assert.same({}, args) end) it("handles mutex with an argument", function() local parser = Parser() parser:mutex( parser:flag "-q" "--quiet" :description "Supress output.", parser:argument "log" :args "?" :description "Log file" ) local args = parser:parse{"-q"} assert.same({quiet = true}, args) args = parser:parse{"log.txt"} assert.same({log = "log.txt"}, args) args = parser:parse{} assert.same({}, args) end) it("handles mutex with default value", function() local parser = Parser() parser:mutex( parser:flag "-q" "--quiet", parser:option "-o" "--output" :default "a.out" ) local args = parser:parse{"-q"} assert.same({quiet = true, output = "a.out"}, args) end) it("raises an error if mutex is broken", function() local parser = Parser() parser:mutex( parser:flag "-q" "--quiet" :description "Supress logging. ", parser:flag "-v" "--verbose" :description "Print additional debug information. " ) assert.has_error(function() parser:parse{"-qv"} end, "option '-v' can not be used together with option '-q'") assert.has_error(function() parser:parse{"-v", "--quiet"} end, "option '--quiet' can not be used together with option '-v'") end) it("raises an error if mutex with an argument is broken", function() local parser = Parser() parser:mutex( parser:flag "-q" "--quiet" :description "Supress output.", parser:argument "log" :args "?" :description "Log file" ) assert.has_error(function() parser:parse{"-q", "log.txt"} end, "argument 'log' can not be used together with option '-q'") assert.has_error(function() parser:parse{"log.txt", "--quiet"} end, "option '--quiet' can not be used together with argument 'log'") end) it("handles multiple mutexes", function() local parser = Parser() parser:mutex( parser:flag "-q" "--quiet", parser:flag "-v" "--verbose" ) parser:mutex( parser:flag "-l" "--local", parser:option "-f" "--from" ) local args = parser:parse{"-qq", "-fTHERE"} assert.same({quiet = true, from = "THERE"}, args) args = parser:parse{"-vl"} assert.same({verbose = true, ["local"] = true}, args) end) it("handles mutexes in commands", function() local parser = Parser() parser:mutex( parser:flag "-q" "--quiet", parser:flag "-v" "--verbose" ) local install = parser:command "install" install:mutex( install:flag "-l" "--local", install:option "-f" "--from" ) local args = parser:parse{"install", "-l"} assert.same({install = true, ["local"] = true}, args) assert.has_error(function() parser:parse{"install", "-qlv"} end, "option '-v' can not be used together with option '-q'") end) end) argparse-0.7.1/spec/options_spec.lua000066400000000000000000000277651370121727600175140ustar00rootroot00000000000000local Parser = require "argparse" getmetatable(Parser()).error = function(_, msg) error(msg) end describe("tests related to options", function() describe("passing correct options", function() it("handles no options passed correctly", function() local parser = Parser() parser:option "-s" "--server" local args = parser:parse({}) assert.same({}, args) end) it("handles one option correctly", function() local parser = Parser() parser:option "-s" "--server" local args = parser:parse({"--server", "foo"}) assert.same({server = "foo"}, args) end) it("normalizes default target", function() local parser = Parser() parser:option "--from-server" local args = parser:parse({"--from-server", "foo"}) assert.same({from_server = "foo"}, args) end) it("handles non-standard charset", function() local parser = Parser() parser:option "/s" parser:flag "/?" local args = parser:parse{"/s", "foo", "/?"} assert.same({s = "foo", ["?"] = true}, args) end) it("handles GNU-style long options", function() local parser = Parser() parser:option "-s" "--server" local args = parser:parse({"--server=foo"}) assert.same({server = "foo"}, args) end) it("handles GNU-style long options even when it could take more arguments", function() local parser = Parser() parser:option "-s" "--server" { args = "*" } local args = parser:parse({"--server=foo"}) assert.same({server = {"foo"}}, args) end) it("handles GNU-style long options for multi-argument options", function() local parser = Parser() parser:option "-s" "--server" { args = "1-2" } local args = parser:parse({"--server=foo", "bar"}) assert.same({server = {"foo", "bar"}}, args) end) it("handles short option correctly", function() local parser = Parser() parser:option "-s" "--server" local args = parser:parse({"-s", "foo"}) assert.same({server = "foo"}, args) end) it("handles flag correctly", function() local parser = Parser() parser:flag "-q" "--quiet" local args = parser:parse({"--quiet"}) assert.same({quiet = true}, args) args = parser:parse({}) assert.same({}, args) end) it("handles combined flags correctly", function() local parser = Parser() parser:flag "-q" "--quiet" parser:flag "-f" "--fast" local args = parser:parse({"-qf"}) assert.same({quiet = true, fast = true}, args) end) it("handles short options without space between option and argument", function() local parser = Parser() parser:option "-s" "--server" local args = parser:parse({"-sfoo"}) assert.same({server = "foo"}, args) end) it("handles flags combined with short option correctly", function() local parser = Parser() parser:flag "-q" "--quiet" parser:option "-s" "--server" local args = parser:parse({"-qsfoo"}) assert.same({quiet = true, server = "foo"}, args) end) it("interprets extra option arguments as positional arguments", function() local parser = Parser() parser:argument "input" :args "2+" parser:option "-s" "--server" local args = parser:parse{"foo", "-sFROM", "bar"} assert.same({input = {"foo", "bar"}, server = "FROM"}, args) end) it("does not interpret extra option arguments as other option's arguments", function() local parser = Parser() parser:argument "output" parser:option "--input" :args "+" parser:option "-s" "--server" local args = parser:parse{"--input", "foo", "-sFROM", "bar"} assert.same({input = {"foo"}, server = "FROM", output = "bar"}, args) end) it("does not pass arguments to options after double hyphen", function() local parser = Parser() parser:argument "input" :args "?" parser:option "--exclude" :args "*" local args = parser:parse{"--exclude", "--", "foo"} assert.same({input = "foo", exclude = {}}, args) end) it("does not interpret options if disabled", function() local parser = Parser() parser:handle_options(false) parser:argument "input" :args "*" parser:option "-f" "--foo" :args "*" local args = parser:parse{"bar", "-f", "--foo" , "bar"} assert.same({input = {"bar", "-f", "--foo" , "bar"}}, args) end) it("allows using -- as an option", function() local parser = Parser() parser:flag "--unrelated" parser:option "--" :args "*" :target "tail" local args = parser:parse{"--", "foo", "--unrelated", "bar"} assert.same({tail = {"foo", "--unrelated", "bar"}}, args) end) it("handles hidden option aliases", function() local parser = Parser() parser:option "--server" :hidden_name "--from" local args = parser:parse{"--from", "foo"} assert.same({server = "foo"}, args) end) describe("Special chars set", function() it("handles windows-style options", function() local parser = Parser() :add_help(false) parser:option "\\I" :count "*" :target "include" local args = parser:parse{"\\I", "src", "\\I", "misc"} assert.same({include = {"src", "misc"}}, args) end) it("corrects charset in commands", function() local parser = Parser "name" :add_help(false) parser:flag "-v" "--verbose" :count "*" parser:command "deep" :add_help(false) :option "/s" local args = parser:parse{"-v", "deep", "/s", "foo", "-vv"} assert.same({verbose = 3, deep = true, s = "foo"}, args) end) end) describe("Options with optional argument", function() it("handles emptiness correctly", function() local parser = Parser() parser:option("-p --password", "Secure password for special security", nil, nil, "?") local args = parser:parse({}) assert.same({}, args) end) it("handles option without argument correctly", function() local parser = Parser() parser:option "-p" "--password" { args = "?" } local args = parser:parse({"-p"}) assert.same({password = {}}, args) end) it("handles option with argument correctly", function() local parser = Parser() parser:option "-p" "--password" { args = "?" } local args = parser:parse({"-p", "password"}) assert.same({password = {"password"}}, args) end) end) it("handles multi-argument options correctly", function() local parser = Parser() parser:option "--pair" { args = 2 } local args = parser:parse({"--pair", "Alice", "Bob"}) assert.same({pair = {"Alice", "Bob"}}, args) end) describe("Multi-count options", function() it("handles multi-count option correctly", function() local parser = Parser() parser:option "-e" "--exclude" { count = "*" } local args = parser:parse({"-efoo", "--exclude=bar", "-e", "baz"}) assert.same({exclude = {"foo", "bar", "baz"}}, args) end) it("handles not used multi-count option correctly", function() local parser = Parser() parser:option "-e" "--exclude" { count = "*" } local args = parser:parse({}) assert.same({exclude = {}}, args) end) it("handles multi-count multi-argument option correctly", function() local parser = Parser() parser:option "-e" "--exclude" { count = "*", args = 2 } local args = parser:parse({"-e", "Alice", "Bob", "-e", "Emma", "Jacob"}) assert.same({exclude = {{"Alice", "Bob"}, {"Emma", "Jacob"}}}, args) end) it("handles multi-count flag correctly", function() local parser = Parser() parser:flag "-q" "--quiet" { count = "*" } local args = parser:parse({"-qq", "--quiet"}) assert.same({quiet = 3}, args) end) it("overwrites old invocations", function() local parser = Parser() parser:option "-u" "--user" { count = "0-2" } local args = parser:parse({"-uAlice", "--user=Bob", "--user", "John"}) assert.same({user = {"Bob", "John"}}, args) end) it("handles not used multi-count flag correctly", function() local parser = Parser() parser:flag "-q" "--quiet" { count = "*" } local args = parser:parse({}) assert.same({quiet = 0}, args) end) end) end) describe("passing incorrect options", function() it("handles lack of required argument correctly", function() local parser = Parser() parser:option "-s" "--server" assert.has_error(function() parser:parse{"--server"} end, "option '--server' requires an argument") assert.has_error(function() parser:parse{"-s"} end, "option '-s' requires an argument") end) it("handles unknown options correctly", function() local parser = Parser() :add_help(false) parser:option "--option" assert.has_error(function() parser:parse{"--server"} end, "unknown option '--server'") assert.has_error(function() parser:parse{"--server=localhost"} end, "unknown option '--server'") assert.has_error(function() parser:parse{"-s"} end, "unknown option '-s'") assert.has_error(function() parser:parse{"-slocalhost"} end, "unknown option '-s'") end) it("handles too many arguments correctly", function() local parser = Parser() parser:option "-s" "--server" assert.has_error(function() parser:parse{"-sfoo", "bar"} end, "too many arguments") end) it("handles invalid argument choices correctly", function() local parse = Parser() parse:option "-s" "--server" { choices = {"foo", "bar", "baz"} } assert.has_error(function() parse:parse{"-slocalhost"} end, "argument for option '-s' must be one of 'foo', 'bar', 'baz'") end) it("doesn't accept GNU-like long options when it doesn't need arguments", function() local parser = Parser() parser:flag "-q" "--quiet" assert.has_error(function() parser:parse{"--quiet=very_quiet"} end, "option '--quiet' does not take arguments") end) it("handles too many invocations correctly", function() local parser = Parser() parser:flag "-q" "--quiet" { count = 1, overwrite = false } assert.has_error(function() parser:parse{"-qq"} end, "option '-q' must be used 1 time") end) it("handles too few invocations correctly", function() local parser = Parser() parser:option "-f" "--foo" { count = "3-4" } assert.has_error(function() parser:parse{"-fFOO", "--foo=BAR"} end, "option '--foo' must be used at least 3 times") assert.has_error( function() parser:parse{} end, "missing option '-f'") end) end) end) argparse-0.7.1/spec/pparse_spec.lua000066400000000000000000000016331370121727600172750ustar00rootroot00000000000000local Parser = require "argparse" getmetatable(Parser()).error = function(_, msg) error(msg) end describe("tests related to :pparse()", function() it("returns true and result on success", function() local parser = Parser() parser:option "-s --server" local ok, args = parser:pparse{"--server", "foo"} assert.is_true(ok) assert.same({server = "foo"}, args) end) it("returns false and bare error message on failure", function() local parser = Parser() parser:argument "foo" local ok, errmsg = parser:pparse{} assert.is_false(ok) assert.equal("missing argument 'foo'", errmsg) end) it("rethrows errors from callbacks", function() local parser = Parser() parser:flag "--foo" :action(function() error("some error message") end) assert.error_matches(function() parser:pparse{"--foo"} end, "some error message") end) end) argparse-0.7.1/spec/script.lua000077500000000000000000000014411370121727600162750ustar00rootroot00000000000000#!/usr/bin/env lua local Parser = require "argparse" local parser = Parser() :description "A testing program." :add_help_command() :require_command(false) parser:argument "input" parser:flag "-v" "--verbose" :description "Sets verbosity level." :target "verbosity" :count "0-2" local install = parser:command "install" :description "Install a rock." install:argument "rock" :description "Name of the rock." install:argument "version" :description "Version of the rock." :args "?" install:option "-f" "--from" :description "Fetch the rock from this server." :target "server" parser:get_usage() parser:get_help() local args = parser:parse() print(args.input) print(args.verbosity) print(args.install) print(args.rock) print(args.version) print(args.server) argparse-0.7.1/spec/tip_spec.lua000066400000000000000000000060721370121727600166010ustar00rootroot00000000000000local Parser = require "argparse" getmetatable(Parser()).error = function(_, msg) error(msg) end describe("tests related to tips", function() describe("provides tips when data is too long", function() it("for options", function() local parser = Parser() parser:option "-q" "--quiet" assert.has_error(function() parser:parse{"--quiett=true"} end, "unknown option '--quiett'\nDid you mean '--quiet'?") end) it("for commands", function() local parser = Parser "name" parser:command "install" assert.has_error(function() parser:parse{"installq"} end, "unknown command 'installq'\nDid you mean 'install'?") end) end) describe("provides tips when data is too short", function() it("for options", function() local parser = Parser() parser:option "-q" "--quiet" assert.has_error(function() parser:parse{"--quet=true"} end, "unknown option '--quet'\nDid you mean '--quiet'?") end) it("for commands", function() local parser = Parser "name" parser:command "install" assert.has_error(function() parser:parse{"nstall"} end, "unknown command 'nstall'\nDid you mean 'install'?") end) end) describe("provides tips on substitution", function() it("for options", function() local parser = Parser() parser:option "-q" "--quiet" assert.has_error(function() parser:parse{"--qriet=true"} end, "unknown option '--qriet'\nDid you mean '--quiet'?") end) it("for commands", function() local parser = Parser "name" parser:command "install" assert.has_error(function() parser:parse{"inntall"} end, "unknown command 'inntall'\nDid you mean 'install'?") end) end) describe("provides tips on transpositions", function() it("for options", function() local parser = Parser() parser:option "-q" "--quiet" assert.has_error(function() parser:parse{"--queit=true"} end, "unknown option '--queit'\nDid you mean '--quiet'?") end) it("for commands", function() local parser = Parser "name" parser:command "install" assert.has_error(function() parser:parse{"isntall"} end, "unknown command 'isntall'\nDid you mean 'install'?") end) end) describe("provides multiple tips", function() it("for options", function() local parser = Parser() parser:option "-q" "--quiet" parser:option "--quick" assert.has_error(function() parser:parse{"--quiec=true"} end, "unknown option '--quiec'\nDid you mean one of these: '--quick' '--quiet'?") end) it("for commands", function() local parser = Parser "name" parser:command "install" parser:command "instant" assert.has_error(function() parser:parse{"instanl"} end, "unknown command 'instanl'\nDid you mean one of these: 'install' 'instant'?") end) end) end) argparse-0.7.1/spec/usage_spec.lua000066400000000000000000000244211370121727600171070ustar00rootroot00000000000000local Parser = require "argparse" getmetatable(Parser()).error = function(_, msg) error(msg) end describe("tests related to usage message generation", function() it("creates correct usage message for empty parser", function() local parser = Parser "foo" :add_help(false) assert.equal(parser:get_usage(), "Usage: foo") end) it("creates correct usage message for arguments", function() local parser = Parser "foo" :add_help(false) parser:argument "first" parser:argument "second-and-third" :args "2" parser:argument "maybe-fourth" :args "?" parser:argument "others" :args "*" assert.equal([[ Usage: foo [] [] ...]], parser:get_usage() ) end) it("creates correct usage message for options", function() local parser = Parser "foo" :add_help(false) parser:flag "-q" "--quiet" parser:option "--from" :count "1" :target "server" parser:option "--config" assert.equal( [=[Usage: foo [-q] --from [--config ]]=], parser:get_usage() ) end) it("creates correct usage message for options with variable argument count", function() local parser = Parser "foo" :add_help(false) parser:argument "files" :args "+" parser:flag "-q" "--quiet" parser:option "--globals" :args "*" assert.equal( [=[Usage: foo [-q] [] ... [--globals [] ...]]=], parser:get_usage() ) end) it("creates correct usage message for arguments with default value", function() local parser = Parser "foo" :add_help(false) parser:argument "input" :default "a.in" parser:argument "pair" :args(2) :default "foo" parser:argument "pair2" :args(2) :default "bar" :defmode "arg" assert.equal( [=[Usage: foo [] [ ] [] []]=], parser:get_usage() ) end) it("creates correct usage message for options with default value", function() local parser = Parser "foo" :add_help(false) parser:option "-f" "--from" :default "there" parser:option "-o" "--output" :default "a.out" :defmode "arg" assert.equal( [=[Usage: foo [-f ] [-o []]]=], parser:get_usage() ) end) it("creates correct usage message for arguments with choices", function() local parser = Parser "foo" :add_help(false) parser:argument "move" :choices {"rock", "paper", "scissors"} assert.equal( [=[Usage: foo {rock,paper,scissors}]=], parser:get_usage() ) end) it("creates correct usage message for options with argument choices", function() local parser = Parser "foo" :add_help(false) parser:option "--format" :choices {"short", "medium", "full"} assert.equal( [=[Usage: foo [--format {short,medium,full}]]=], parser:get_usage() ) end) it("creates correct usage message for commands", function() local parser = Parser "foo" :add_help(false) parser:flag "-q" "--quiet" local run = parser:command "run" run:option "--where" assert.equal( [=[Usage: foo [-q] ...]=], parser:get_usage() ) end) it("creates correct usage message for subcommands", function() local parser = Parser "foo" :add_help(false) parser:flag "-q" "--quiet" local run = parser:command "run" :add_help(false) run:option "--where" assert.equal( [=[Usage: foo run [--where ]]=], run:get_usage() ) end) it("omits usage for hidden arguments and options", function() local parser = Parser "foo" :add_help(false) parser:flag "-d" "--deprecated" :hidden(true) parser:flag "-n" "--not-deprecated" parser:argument "normal" parser:argument "deprecated" :args "?" :hidden(true) assert.equal( [=[Usage: foo [-n] ]=], parser:get_usage() ) end) it("omits usage for mutexes if all elements are hidden", function() local parser = Parser "foo" :add_help(false) parser:mutex( parser:flag "--misfeature" :hidden(true), parser:flag "--no-misfeature" :action "store_false" :target "misfeature" :hidden(true) ) parser:flag "--feature" assert.equal( [=[Usage: foo [--feature]]=], parser:get_usage() ) end) it("usage messages for commands are correct after several invocations", function() local parser = Parser "foo" :add_help(false) parser:flag "-q" "--quiet" local run = parser:command "run" :add_help(false) run:option "--where" parser:parse{"run"} parser:parse{"run"} assert.equal( [=[Usage: foo run [--where ]]=], run:get_usage() ) end) describe("usage generation can be customized", function() it("uses message provided by user", function() local parser = Parser "foo" :usage "Usage: obvious" :add_help(false) parser:flag "-q" "--quiet" assert.equal( [=[Usage: obvious]=], parser:get_usage() ) end) it("uses argnames provided by user", function() local parser = Parser "foo" :add_help(false) parser:argument "inputs" :args "1-2" :argname "" assert.equal( [=[Usage: foo []]=], parser:get_usage() ) end) it("uses array of argnames provided by user", function() local parser = Parser "foo" :add_help(false) parser:option "--pair" :args(2) :count "*" :argname{"", ""} assert.equal( [=[Usage: foo [--pair ]]=], parser:get_usage() ) end) end) it("creates correct usage message for mutexes", function() local parser = Parser "foo" :add_help(false) parser:mutex( parser:flag "-q" "--quiet", parser:flag "-v" "--verbose", parser:flag "-i" "--interactive" ) parser:mutex( parser:flag "-l" "--local", parser:option "-f" "--from" ) parser:option "--yet-another-option" assert.equal([=[ Usage: foo ([-q] | [-v] | [-i]) ([-l] | [-f ]) [--yet-another-option ]]=], parser:get_usage() ) end) it("creates correct usage message for mutexes with arguments", function() local parser = Parser "foo" :add_help(false) parser:argument "first" parser:mutex( parser:flag "-q" "--quiet", parser:flag "-v" "--verbose", parser:argument "second":args "?" ) parser:argument "third" parser:mutex( parser:flag "-l" "--local", parser:option "-f" "--from" ) parser:option "--yet-another-option" assert.equal([=[ Usage: foo ([-l] | [-f ]) [--yet-another-option ] ([-q] | [-v] | []) ]=], parser:get_usage() ) end) it("puts vararg option and mutex usages after positional arguments", function() local parser = Parser "foo" :add_help(false) parser:argument("argument") parser:mutex( parser:flag "-q" "--quiet", parser:flag "-v" "--verbose", parser:flag "-i" "--interactive" ) parser:mutex( parser:flag "-a -all", parser:option "-i --ignore":args("*") ) parser:option "--yet-another-option" parser:option "--vararg-option":args("1-2") assert.equal([=[ Usage: foo ([-q] | [-v] | [-i]) [--yet-another-option ] ([-a] | [-i [] ...]) [--vararg-option []]]=], parser:get_usage() ) end) it("doesn't repeat usage of elements within several mutexes", function() local parser = Parser "foo" :add_help(false) parser:argument("arg1") local arg2 = parser:argument("arg2"):args "?" parser:argument("arg3"):args "?" local arg4 = parser:argument("arg4"):args "?" local opt1 = parser:option("--opt1") local opt2 = parser:option("--opt2") local opt3 = parser:option("--opt3") local opt4 = parser:option("--opt4") local opt5 = parser:option("--opt5") local opt6 = parser:option("--opt6") parser:option("--opt7") parser:mutex(arg2, opt1, opt2) parser:mutex(arg4, opt2, opt3, opt4) parser:mutex(opt1, opt3, opt5) parser:mutex(opt1, opt3, opt6) assert.equal([=[ Usage: foo ([--opt1 ] | [--opt3 ] | [--opt5 ]) [--opt6 ] [--opt7 ] ([] | [--opt2 ]) [] ([] | [--opt4 ])]=], parser:get_usage() ) end) it("allows configuring usage margin using usage_margin property", function() local parser = Parser "foo" :usage_margin(2) parser:argument "long_argument_name" parser:argument "very_long_words" parser:option "--set-important-property" parser:option "--include" :args "*" assert.equals([=[ Usage: foo [-h] [--set-important-property ] [--include [] ...]]=], parser:get_usage()) end) it("allows configuring max usage width using usage_max_width property", function() local parser = Parser "foo" :usage_max_width(50) parser:argument "long_argument_name" parser:argument "very_long_words" parser:option "--set-important-property" parser:option "--include" :args "*" assert.equals([=[ Usage: foo [-h] [--set-important-property ] [--include [] ...]]=], parser:get_usage()) end) end) argparse-0.7.1/src/000077500000000000000000000000001370121727600141205ustar00rootroot00000000000000argparse-0.7.1/src/argparse.lua000066400000000000000000001611441370121727600164360ustar00rootroot00000000000000-- The MIT License (MIT) -- Copyright (c) 2013 - 2018 Peter Melnichenko -- 2019 Paul Ouellette -- Permission is hereby granted, free of charge, to any person obtaining a copy of -- this software and associated documentation files (the "Software"), to deal in -- the Software without restriction, including without limitation the rights to -- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -- the Software, and to permit persons to whom the Software is furnished to do so, -- subject to the following conditions: -- The above copyright notice and this permission notice shall be included in all -- copies or substantial portions of the Software. -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. local function deep_update(t1, t2) for k, v in pairs(t2) do if type(v) == "table" then v = deep_update({}, v) end t1[k] = v end return t1 end -- A property is a tuple {name, callback}. -- properties.args is number of properties that can be set as arguments -- when calling an object. local function class(prototype, properties, parent) -- Class is the metatable of its instances. local cl = {} cl.__index = cl if parent then cl.__prototype = deep_update(deep_update({}, parent.__prototype), prototype) else cl.__prototype = prototype end if properties then local names = {} -- Create setter methods and fill set of property names. for _, property in ipairs(properties) do local name, callback = property[1], property[2] cl[name] = function(self, value) if not callback(self, value) then self["_" .. name] = value end return self end names[name] = true end function cl.__call(self, ...) -- When calling an object, if the first argument is a table, -- interpret keys as property names, else delegate arguments -- to corresponding setters in order. if type((...)) == "table" then for name, value in pairs((...)) do if names[name] then self[name](self, value) end end else local nargs = select("#", ...) for i, property in ipairs(properties) do if i > nargs or i > properties.args then break end local arg = select(i, ...) if arg ~= nil then self[property[1]](self, arg) end end end return self end end -- If indexing class fails, fallback to its parent. local class_metatable = {} class_metatable.__index = parent function class_metatable.__call(self, ...) -- Calling a class returns its instance. -- Arguments are delegated to the instance. local object = deep_update({}, self.__prototype) setmetatable(object, self) return object(...) end return setmetatable(cl, class_metatable) end local function typecheck(name, types, value) for _, type_ in ipairs(types) do if type(value) == type_ then return true end end error(("bad property '%s' (%s expected, got %s)"):format(name, table.concat(types, " or "), type(value))) end local function typechecked(name, ...) local types = {...} return {name, function(_, value) typecheck(name, types, value) end} end local multiname = {"name", function(self, value) typecheck("name", {"string"}, value) for alias in value:gmatch("%S+") do self._name = self._name or alias table.insert(self._aliases, alias) table.insert(self._public_aliases, alias) -- If alias contains '_', accept '-' also. if alias:find("_", 1, true) then table.insert(self._aliases, (alias:gsub("_", "-"))) end end -- Do not set _name as with other properties. return true end} local multiname_hidden = {"hidden_name", function(self, value) typecheck("hidden_name", {"string"}, value) for alias in value:gmatch("%S+") do table.insert(self._aliases, alias) if alias:find("_", 1, true) then table.insert(self._aliases, (alias:gsub("_", "-"))) end end return true end} local function parse_boundaries(str) if tonumber(str) then return tonumber(str), tonumber(str) end if str == "*" then return 0, math.huge end if str == "+" then return 1, math.huge end if str == "?" then return 0, 1 end if str:match "^%d+%-%d+$" then local min, max = str:match "^(%d+)%-(%d+)$" return tonumber(min), tonumber(max) end if str:match "^%d+%+$" then local min = str:match "^(%d+)%+$" return tonumber(min), math.huge end end local function boundaries(name) return {name, function(self, value) typecheck(name, {"number", "string"}, value) local min, max = parse_boundaries(value) if not min then error(("bad property '%s'"):format(name)) end self["_min" .. name], self["_max" .. name] = min, max end} end local actions = {} local option_action = {"action", function(_, value) typecheck("action", {"function", "string"}, value) if type(value) == "string" and not actions[value] then error(("unknown action '%s'"):format(value)) end end} local option_init = {"init", function(self) self._has_init = true end} local option_default = {"default", function(self, value) if type(value) ~= "string" then self._init = value self._has_init = true return true end end} local add_help = {"add_help", function(self, value) typecheck("add_help", {"boolean", "string", "table"}, value) if self._help_option_idx then table.remove(self._options, self._help_option_idx) self._help_option_idx = nil end if value then local help = self:flag() :description "Show this help message and exit." :action(function() print(self:get_help()) os.exit(0) end) if value ~= true then help = help(value) end if not help._name then help "-h" "--help" end self._help_option_idx = #self._options end end} local Parser = class({ _arguments = {}, _options = {}, _commands = {}, _mutexes = {}, _groups = {}, _require_command = true, _handle_options = true }, { args = 3, typechecked("name", "string"), typechecked("description", "string"), typechecked("epilog", "string"), typechecked("usage", "string"), typechecked("help", "string"), typechecked("require_command", "boolean"), typechecked("handle_options", "boolean"), typechecked("action", "function"), typechecked("command_target", "string"), typechecked("help_vertical_space", "number"), typechecked("usage_margin", "number"), typechecked("usage_max_width", "number"), typechecked("help_usage_margin", "number"), typechecked("help_description_margin", "number"), typechecked("help_max_width", "number"), add_help }) local Command = class({ _aliases = {}, _public_aliases = {} }, { args = 3, multiname, typechecked("description", "string"), typechecked("epilog", "string"), multiname_hidden, typechecked("summary", "string"), typechecked("target", "string"), typechecked("usage", "string"), typechecked("help", "string"), typechecked("require_command", "boolean"), typechecked("handle_options", "boolean"), typechecked("action", "function"), typechecked("command_target", "string"), typechecked("help_vertical_space", "number"), typechecked("usage_margin", "number"), typechecked("usage_max_width", "number"), typechecked("help_usage_margin", "number"), typechecked("help_description_margin", "number"), typechecked("help_max_width", "number"), typechecked("hidden", "boolean"), add_help }, Parser) local Argument = class({ _minargs = 1, _maxargs = 1, _mincount = 1, _maxcount = 1, _defmode = "unused", _show_default = true }, { args = 5, typechecked("name", "string"), typechecked("description", "string"), option_default, typechecked("convert", "function", "table"), boundaries("args"), typechecked("target", "string"), typechecked("defmode", "string"), typechecked("show_default", "boolean"), typechecked("argname", "string", "table"), typechecked("choices", "table"), typechecked("hidden", "boolean"), option_action, option_init }) local Option = class({ _aliases = {}, _public_aliases = {}, _mincount = 0, _overwrite = true }, { args = 6, multiname, typechecked("description", "string"), option_default, typechecked("convert", "function", "table"), boundaries("args"), boundaries("count"), multiname_hidden, typechecked("target", "string"), typechecked("defmode", "string"), typechecked("show_default", "boolean"), typechecked("overwrite", "boolean"), typechecked("argname", "string", "table"), typechecked("choices", "table"), typechecked("hidden", "boolean"), option_action, option_init }, Argument) function Parser:_inherit_property(name, default) local element = self while true do local value = element["_" .. name] if value ~= nil then return value end if not element._parent then return default end element = element._parent end end function Argument:_get_argument_list() local buf = {} local i = 1 while i <= math.min(self._minargs, 3) do local argname = self:_get_argname(i) if self._default and self._defmode:find "a" then argname = "[" .. argname .. "]" end table.insert(buf, argname) i = i+1 end while i <= math.min(self._maxargs, 3) do table.insert(buf, "[" .. self:_get_argname(i) .. "]") i = i+1 if self._maxargs == math.huge then break end end if i < self._maxargs then table.insert(buf, "...") end return buf end function Argument:_get_usage() local usage = table.concat(self:_get_argument_list(), " ") if self._default and self._defmode:find "u" then if self._maxargs > 1 or (self._minargs == 1 and not self._defmode:find "a") then usage = "[" .. usage .. "]" end end return usage end function actions.store_true(result, target) result[target] = true end function actions.store_false(result, target) result[target] = false end function actions.store(result, target, argument) result[target] = argument end function actions.count(result, target, _, overwrite) if not overwrite then result[target] = result[target] + 1 end end function actions.append(result, target, argument, overwrite) result[target] = result[target] or {} table.insert(result[target], argument) if overwrite then table.remove(result[target], 1) end end function actions.concat(result, target, arguments, overwrite) if overwrite then error("'concat' action can't handle too many invocations") end result[target] = result[target] or {} for _, argument in ipairs(arguments) do table.insert(result[target], argument) end end function Argument:_get_action() local action, init if self._maxcount == 1 then if self._maxargs == 0 then action, init = "store_true", nil else action, init = "store", nil end else if self._maxargs == 0 then action, init = "count", 0 else action, init = "append", {} end end if self._action then action = self._action end if self._has_init then init = self._init end if type(action) == "string" then action = actions[action] end return action, init end -- Returns placeholder for `narg`-th argument. function Argument:_get_argname(narg) local argname = self._argname or self:_get_default_argname() if type(argname) == "table" then return argname[narg] else return argname end end function Argument:_get_choices_list() return "{" .. table.concat(self._choices, ",") .. "}" end function Argument:_get_default_argname() if self._choices then return self:_get_choices_list() else return "<" .. self._name .. ">" end end function Option:_get_default_argname() if self._choices then return self:_get_choices_list() else return "<" .. self:_get_default_target() .. ">" end end -- Returns labels to be shown in the help message. function Argument:_get_label_lines() if self._choices then return {self:_get_choices_list()} else return {self._name} end end function Option:_get_label_lines() local argument_list = self:_get_argument_list() if #argument_list == 0 then -- Don't put aliases for simple flags like `-h` on different lines. return {table.concat(self._public_aliases, ", ")} end local longest_alias_length = -1 for _, alias in ipairs(self._public_aliases) do longest_alias_length = math.max(longest_alias_length, #alias) end local argument_list_repr = table.concat(argument_list, " ") local lines = {} for i, alias in ipairs(self._public_aliases) do local line = (" "):rep(longest_alias_length - #alias) .. alias .. " " .. argument_list_repr if i ~= #self._public_aliases then line = line .. "," end table.insert(lines, line) end return lines end function Command:_get_label_lines() return {table.concat(self._public_aliases, ", ")} end function Argument:_get_description() if self._default and self._show_default then if self._description then return ("%s (default: %s)"):format(self._description, self._default) else return ("default: %s"):format(self._default) end else return self._description or "" end end function Command:_get_description() return self._summary or self._description or "" end function Option:_get_usage() local usage = self:_get_argument_list() table.insert(usage, 1, self._name) usage = table.concat(usage, " ") if self._mincount == 0 or self._default then usage = "[" .. usage .. "]" end return usage end function Argument:_get_default_target() return self._name end function Option:_get_default_target() local res for _, alias in ipairs(self._public_aliases) do if alias:sub(1, 1) == alias:sub(2, 2) then res = alias:sub(3) break end end res = res or self._name:sub(2) return (res:gsub("-", "_")) end function Option:_is_vararg() return self._maxargs ~= self._minargs end function Parser:_get_fullname(exclude_root) local parent = self._parent if exclude_root and not parent then return "" end local buf = {self._name} while parent do if not exclude_root or parent._parent then table.insert(buf, 1, parent._name) end parent = parent._parent end return table.concat(buf, " ") end function Parser:_update_charset(charset) charset = charset or {} for _, command in ipairs(self._commands) do command:_update_charset(charset) end for _, option in ipairs(self._options) do for _, alias in ipairs(option._aliases) do charset[alias:sub(1, 1)] = true end end return charset end function Parser:argument(...) local argument = Argument(...) table.insert(self._arguments, argument) return argument end function Parser:option(...) local option = Option(...) table.insert(self._options, option) return option end function Parser:flag(...) return self:option():args(0)(...) end function Parser:command(...) local command = Command():add_help(true)(...) command._parent = self table.insert(self._commands, command) return command end function Parser:mutex(...) local elements = {...} for i, element in ipairs(elements) do local mt = getmetatable(element) assert(mt == Option or mt == Argument, ("bad argument #%d to 'mutex' (Option or Argument expected)"):format(i)) end table.insert(self._mutexes, elements) return self end function Parser:group(name, ...) assert(type(name) == "string", ("bad argument #1 to 'group' (string expected, got %s)"):format(type(name))) local group = {name = name, ...} for i, element in ipairs(group) do local mt = getmetatable(element) assert(mt == Option or mt == Argument or mt == Command, ("bad argument #%d to 'group' (Option or Argument or Command expected)"):format(i + 1)) end table.insert(self._groups, group) return self end local usage_welcome = "Usage: " function Parser:get_usage() if self._usage then return self._usage end local usage_margin = self:_inherit_property("usage_margin", #usage_welcome) local max_usage_width = self:_inherit_property("usage_max_width", 70) local lines = {usage_welcome .. self:_get_fullname()} local function add(s) if #lines[#lines]+1+#s <= max_usage_width then lines[#lines] = lines[#lines] .. " " .. s else lines[#lines+1] = (" "):rep(usage_margin) .. s end end -- Normally options are before positional arguments in usage messages. -- However, vararg options should be after, because they can't be reliable used -- before a positional argument. -- Mutexes come into play, too, and are shown as soon as possible. -- Overall, output usages in the following order: -- 1. Mutexes that don't have positional arguments or vararg options. -- 2. Options that are not in any mutexes and are not vararg. -- 3. Positional arguments - on their own or as a part of a mutex. -- 4. Remaining mutexes. -- 5. Remaining options. local elements_in_mutexes = {} local added_elements = {} local added_mutexes = {} local argument_to_mutexes = {} local function add_mutex(mutex, main_argument) if added_mutexes[mutex] then return end added_mutexes[mutex] = true local buf = {} for _, element in ipairs(mutex) do if not element._hidden and not added_elements[element] then if getmetatable(element) == Option or element == main_argument then table.insert(buf, element:_get_usage()) added_elements[element] = true end end end if #buf == 1 then add(buf[1]) elseif #buf > 1 then add("(" .. table.concat(buf, " | ") .. ")") end end local function add_element(element) if not element._hidden and not added_elements[element] then add(element:_get_usage()) added_elements[element] = true end end for _, mutex in ipairs(self._mutexes) do local is_vararg = false local has_argument = false for _, element in ipairs(mutex) do if getmetatable(element) == Option then if element:_is_vararg() then is_vararg = true end else has_argument = true argument_to_mutexes[element] = argument_to_mutexes[element] or {} table.insert(argument_to_mutexes[element], mutex) end elements_in_mutexes[element] = true end if not is_vararg and not has_argument then add_mutex(mutex) end end for _, option in ipairs(self._options) do if not elements_in_mutexes[option] and not option:_is_vararg() then add_element(option) end end -- Add usages for positional arguments, together with one mutex containing them, if they are in a mutex. for _, argument in ipairs(self._arguments) do -- Pick a mutex as a part of which to show this argument, take the first one that's still available. local mutex if elements_in_mutexes[argument] then for _, argument_mutex in ipairs(argument_to_mutexes[argument]) do if not added_mutexes[argument_mutex] then mutex = argument_mutex end end end if mutex then add_mutex(mutex, argument) else add_element(argument) end end for _, mutex in ipairs(self._mutexes) do add_mutex(mutex) end for _, option in ipairs(self._options) do add_element(option) end if #self._commands > 0 then if self._require_command then add("") else add("[]") end add("...") end return table.concat(lines, "\n") end local function split_lines(s) if s == "" then return {} end local lines = {} if s:sub(-1) ~= "\n" then s = s .. "\n" end for line in s:gmatch("([^\n]*)\n") do table.insert(lines, line) end return lines end local function autowrap_line(line, max_length) -- Algorithm for splitting lines is simple and greedy. local result_lines = {} -- Preserve original indentation of the line, put this at the beginning of each result line. -- If the first word looks like a list marker ('*', '+', or '-'), add spaces so that starts -- of the second and the following lines vertically align with the start of the second word. local indentation = line:match("^ *") if line:find("^ *[%*%+%-]") then indentation = indentation .. " " .. line:match("^ *[%*%+%-]( *)") end -- Parts of the last line being assembled. local line_parts = {} -- Length of the current line. local line_length = 0 -- Index of the next character to consider. local index = 1 while true do local word_start, word_finish, word = line:find("([^ ]+)", index) if not word_start then -- Ignore trailing spaces, if any. break end local preceding_spaces = line:sub(index, word_start - 1) index = word_finish + 1 if (#line_parts == 0) or (line_length + #preceding_spaces + #word <= max_length) then -- Either this is the very first word or it fits as an addition to the current line, add it. table.insert(line_parts, preceding_spaces) -- For the very first word this adds the indentation. table.insert(line_parts, word) line_length = line_length + #preceding_spaces + #word else -- Does not fit, finish current line and put the word into a new one. table.insert(result_lines, table.concat(line_parts)) line_parts = {indentation, word} line_length = #indentation + #word end end if #line_parts > 0 then table.insert(result_lines, table.concat(line_parts)) end if #result_lines == 0 then -- Preserve empty lines. result_lines[1] = "" end return result_lines end -- Automatically wraps lines within given array, -- attempting to limit line length to `max_length`. -- Existing line splits are preserved. local function autowrap(lines, max_length) local result_lines = {} for _, line in ipairs(lines) do local autowrapped_lines = autowrap_line(line, max_length) for _, autowrapped_line in ipairs(autowrapped_lines) do table.insert(result_lines, autowrapped_line) end end return result_lines end function Parser:_get_element_help(element) local label_lines = element:_get_label_lines() local description_lines = split_lines(element:_get_description()) local result_lines = {} -- All label lines should have the same length (except the last one, it has no comma). -- If too long, start description after all the label lines. -- Otherwise, combine label and description lines. local usage_margin_len = self:_inherit_property("help_usage_margin", 3) local usage_margin = (" "):rep(usage_margin_len) local description_margin_len = self:_inherit_property("help_description_margin", 25) local description_margin = (" "):rep(description_margin_len) local help_max_width = self:_inherit_property("help_max_width") if help_max_width then local description_max_width = math.max(help_max_width - description_margin_len, 10) description_lines = autowrap(description_lines, description_max_width) end if #label_lines[1] >= (description_margin_len - usage_margin_len) then for _, label_line in ipairs(label_lines) do table.insert(result_lines, usage_margin .. label_line) end for _, description_line in ipairs(description_lines) do table.insert(result_lines, description_margin .. description_line) end else for i = 1, math.max(#label_lines, #description_lines) do local label_line = label_lines[i] local description_line = description_lines[i] local line = "" if label_line then line = usage_margin .. label_line end if description_line and description_line ~= "" then line = line .. (" "):rep(description_margin_len - #line) .. description_line end table.insert(result_lines, line) end end return table.concat(result_lines, "\n") end local function get_group_types(group) local types = {} for _, element in ipairs(group) do types[getmetatable(element)] = true end return types end function Parser:_add_group_help(blocks, added_elements, label, elements) local buf = {label} for _, element in ipairs(elements) do if not element._hidden and not added_elements[element] then added_elements[element] = true table.insert(buf, self:_get_element_help(element)) end end if #buf > 1 then table.insert(blocks, table.concat(buf, ("\n"):rep(self:_inherit_property("help_vertical_space", 0) + 1))) end end function Parser:get_help() if self._help then return self._help end local blocks = {self:get_usage()} local help_max_width = self:_inherit_property("help_max_width") if self._description then local description = self._description if help_max_width then description = table.concat(autowrap(split_lines(description), help_max_width), "\n") end table.insert(blocks, description) end -- 1. Put groups containing arguments first, then other arguments. -- 2. Put remaining groups containing options, then other options. -- 3. Put remaining groups containing commands, then other commands. -- Assume that an element can't be in several groups. local groups_by_type = { [Argument] = {}, [Option] = {}, [Command] = {} } for _, group in ipairs(self._groups) do local group_types = get_group_types(group) for _, mt in ipairs({Argument, Option, Command}) do if group_types[mt] then table.insert(groups_by_type[mt], group) break end end end local default_groups = { {name = "Arguments", type = Argument, elements = self._arguments}, {name = "Options", type = Option, elements = self._options}, {name = "Commands", type = Command, elements = self._commands} } local added_elements = {} for _, default_group in ipairs(default_groups) do local type_groups = groups_by_type[default_group.type] for _, group in ipairs(type_groups) do self:_add_group_help(blocks, added_elements, group.name .. ":", group) end local default_label = default_group.name .. ":" if #type_groups > 0 then default_label = "Other " .. default_label:gsub("^.", string.lower) end self:_add_group_help(blocks, added_elements, default_label, default_group.elements) end if self._epilog then local epilog = self._epilog if help_max_width then epilog = table.concat(autowrap(split_lines(epilog), help_max_width), "\n") end table.insert(blocks, epilog) end return table.concat(blocks, "\n\n") end function Parser:add_help_command(value) if value then assert(type(value) == "string" or type(value) == "table", ("bad argument #1 to 'add_help_command' (string or table expected, got %s)"):format(type(value))) end local help = self:command() :description "Show help for commands." help:argument "command" :description "The command to show help for." :args "?" :action(function(_, _, cmd) if not cmd then print(self:get_help()) os.exit(0) else for _, command in ipairs(self._commands) do for _, alias in ipairs(command._aliases) do if alias == cmd then print(command:get_help()) os.exit(0) end end end end help:error(("unknown command '%s'"):format(cmd)) end) if value then help = help(value) end if not help._name then help "help" end help._is_help_command = true return self end function Parser:_is_shell_safe() if self._basename then if self._basename:find("[^%w_%-%+%.]") then return false end else for _, alias in ipairs(self._aliases) do if alias:find("[^%w_%-%+%.]") then return false end end end for _, option in ipairs(self._options) do for _, alias in ipairs(option._aliases) do if alias:find("[^%w_%-%+%.]") then return false end end if option._choices then for _, choice in ipairs(option._choices) do if choice:find("[%s'\"]") then return false end end end end for _, argument in ipairs(self._arguments) do if argument._choices then for _, choice in ipairs(argument._choices) do if choice:find("[%s'\"]") then return false end end end end for _, command in ipairs(self._commands) do if not command:_is_shell_safe() then return false end end return true end function Parser:add_complete(value) if value then assert(type(value) == "string" or type(value) == "table", ("bad argument #1 to 'add_complete' (string or table expected, got %s)"):format(type(value))) end local complete = self:option() :description "Output a shell completion script for the specified shell." :args(1) :choices {"bash", "zsh", "fish"} :action(function(_, _, shell) io.write(self["get_" .. shell .. "_complete"](self)) os.exit(0) end) if value then complete = complete(value) end if not complete._name then complete "--completion" end return self end function Parser:add_complete_command(value) if value then assert(type(value) == "string" or type(value) == "table", ("bad argument #1 to 'add_complete_command' (string or table expected, got %s)"):format(type(value))) end local complete = self:command() :description "Output a shell completion script." complete:argument "shell" :description "The shell to output a completion script for." :choices {"bash", "zsh", "fish"} :action(function(_, _, shell) io.write(self["get_" .. shell .. "_complete"](self)) os.exit(0) end) if value then complete = complete(value) end if not complete._name then complete "completion" end return self end local function base_name(pathname) return pathname:gsub("[/\\]*$", ""):match(".*[/\\]([^/\\]*)") or pathname end local function get_short_description(element) local short = element:_get_description():match("^(.-)%.%s") return short or element:_get_description():match("^(.-)%.?$") end function Parser:_get_options() local options = {} for _, option in ipairs(self._options) do for _, alias in ipairs(option._aliases) do table.insert(options, alias) end end return table.concat(options, " ") end function Parser:_get_commands() local commands = {} for _, command in ipairs(self._commands) do for _, alias in ipairs(command._aliases) do table.insert(commands, alias) end end return table.concat(commands, " ") end function Parser:_bash_option_args(buf, indent) local opts = {} for _, option in ipairs(self._options) do if option._choices or option._minargs > 0 then local compreply if option._choices then compreply = 'COMPREPLY=($(compgen -W "' .. table.concat(option._choices, " ") .. '" -- "$cur"))' else compreply = 'COMPREPLY=($(compgen -f -- "$cur"))' end table.insert(opts, (" "):rep(indent + 4) .. table.concat(option._aliases, "|") .. ")") table.insert(opts, (" "):rep(indent + 8) .. compreply) table.insert(opts, (" "):rep(indent + 8) .. "return 0") table.insert(opts, (" "):rep(indent + 8) .. ";;") end end if #opts > 0 then table.insert(buf, (" "):rep(indent) .. 'case "$prev" in') table.insert(buf, table.concat(opts, "\n")) table.insert(buf, (" "):rep(indent) .. "esac\n") end end function Parser:_bash_get_cmd(buf, indent) if #self._commands == 0 then return end table.insert(buf, (" "):rep(indent) .. 'args=("${args[@]:1}")') table.insert(buf, (" "):rep(indent) .. 'for arg in "${args[@]}"; do') table.insert(buf, (" "):rep(indent + 4) .. 'case "$arg" in') for _, command in ipairs(self._commands) do table.insert(buf, (" "):rep(indent + 8) .. table.concat(command._aliases, "|") .. ")") if self._parent then table.insert(buf, (" "):rep(indent + 12) .. 'cmd="$cmd ' .. command._name .. '"') else table.insert(buf, (" "):rep(indent + 12) .. 'cmd="' .. command._name .. '"') end table.insert(buf, (" "):rep(indent + 12) .. 'opts="$opts ' .. command:_get_options() .. '"') command:_bash_get_cmd(buf, indent + 12) table.insert(buf, (" "):rep(indent + 12) .. "break") table.insert(buf, (" "):rep(indent + 12) .. ";;") end table.insert(buf, (" "):rep(indent + 4) .. "esac") table.insert(buf, (" "):rep(indent) .. "done") end function Parser:_bash_cmd_completions(buf) local cmd_buf = {} if self._parent then self:_bash_option_args(cmd_buf, 12) end if #self._commands > 0 then table.insert(cmd_buf, (" "):rep(12) .. 'COMPREPLY=($(compgen -W "' .. self:_get_commands() .. '" -- "$cur"))') elseif self._is_help_command then table.insert(cmd_buf, (" "):rep(12) .. 'COMPREPLY=($(compgen -W "' .. self._parent:_get_commands() .. '" -- "$cur"))') end if #cmd_buf > 0 then table.insert(buf, (" "):rep(8) .. "'" .. self:_get_fullname(true) .. "')") table.insert(buf, table.concat(cmd_buf, "\n")) table.insert(buf, (" "):rep(12) .. ";;") end for _, command in ipairs(self._commands) do command:_bash_cmd_completions(buf) end end function Parser:get_bash_complete() self._basename = base_name(self._name) assert(self:_is_shell_safe()) local buf = {([[ _%s() { local IFS=$' \t\n' local args cur prev cmd opts arg args=("${COMP_WORDS[@]}") cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" opts="%s" ]]):format(self._basename, self:_get_options())} self:_bash_option_args(buf, 4) self:_bash_get_cmd(buf, 4) if #self._commands > 0 then table.insert(buf, "") table.insert(buf, (" "):rep(4) .. 'case "$cmd" in') self:_bash_cmd_completions(buf) table.insert(buf, (" "):rep(4) .. "esac\n") end table.insert(buf, ([=[ if [[ "$cur" = -* ]]; then COMPREPLY=($(compgen -W "$opts" -- "$cur")) fi } complete -F _%s -o bashdefault -o default %s ]=]):format(self._basename, self._basename)) return table.concat(buf, "\n") end function Parser:_zsh_arguments(buf, cmd_name, indent) if self._parent then table.insert(buf, (" "):rep(indent) .. "options=(") table.insert(buf, (" "):rep(indent + 2) .. "$options") else table.insert(buf, (" "):rep(indent) .. "local -a options=(") end for _, option in ipairs(self._options) do local line = {} if #option._aliases > 1 then if option._maxcount > 1 then table.insert(line, '"*"') end table.insert(line, "{" .. table.concat(option._aliases, ",") .. '}"') else table.insert(line, '"') if option._maxcount > 1 then table.insert(line, "*") end table.insert(line, option._name) end if option._description then local description = get_short_description(option):gsub('["%]:`$]', "\\%0") table.insert(line, "[" .. description .. "]") end if option._maxargs == math.huge then table.insert(line, ":*") end if option._choices then table.insert(line, ": :(" .. table.concat(option._choices, " ") .. ")") elseif option._maxargs > 0 then table.insert(line, ": :_files") end table.insert(line, '"') table.insert(buf, (" "):rep(indent + 2) .. table.concat(line)) end table.insert(buf, (" "):rep(indent) .. ")") table.insert(buf, (" "):rep(indent) .. "_arguments -s -S \\") table.insert(buf, (" "):rep(indent + 2) .. "$options \\") if self._is_help_command then table.insert(buf, (" "):rep(indent + 2) .. '": :(' .. self._parent:_get_commands() .. ')" \\') else for _, argument in ipairs(self._arguments) do local spec if argument._choices then spec = ": :(" .. table.concat(argument._choices, " ") .. ")" else spec = ": :_files" end if argument._maxargs == math.huge then table.insert(buf, (" "):rep(indent + 2) .. '"*' .. spec .. '" \\') break end for _ = 1, argument._maxargs do table.insert(buf, (" "):rep(indent + 2) .. '"' .. spec .. '" \\') end end if #self._commands > 0 then table.insert(buf, (" "):rep(indent + 2) .. '": :_' .. cmd_name .. '_cmds" \\') table.insert(buf, (" "):rep(indent + 2) .. '"*:: :->args" \\') end end table.insert(buf, (" "):rep(indent + 2) .. "&& return 0") end function Parser:_zsh_cmds(buf, cmd_name) table.insert(buf, "\n_" .. cmd_name .. "_cmds() {") table.insert(buf, " local -a commands=(") for _, command in ipairs(self._commands) do local line = {} if #command._aliases > 1 then table.insert(line, "{" .. table.concat(command._aliases, ",") .. '}"') else table.insert(line, '"' .. command._name) end if command._description then table.insert(line, ":" .. get_short_description(command):gsub('["`$]', "\\%0")) end table.insert(buf, " " .. table.concat(line) .. '"') end table.insert(buf, ' )\n _describe "command" commands\n}') end function Parser:_zsh_complete_help(buf, cmds_buf, cmd_name, indent) if #self._commands == 0 then return end self:_zsh_cmds(cmds_buf, cmd_name) table.insert(buf, "\n" .. (" "):rep(indent) .. "case $words[1] in") for _, command in ipairs(self._commands) do local name = cmd_name .. "_" .. command._name table.insert(buf, (" "):rep(indent + 2) .. table.concat(command._aliases, "|") .. ")") command:_zsh_arguments(buf, name, indent + 4) command:_zsh_complete_help(buf, cmds_buf, name, indent + 4) table.insert(buf, (" "):rep(indent + 4) .. ";;\n") end table.insert(buf, (" "):rep(indent) .. "esac") end function Parser:get_zsh_complete() self._basename = base_name(self._name) assert(self:_is_shell_safe()) local buf = {("#compdef %s\n"):format(self._basename)} local cmds_buf = {} table.insert(buf, "_" .. self._basename .. "() {") if #self._commands > 0 then table.insert(buf, " local context state state_descr line") table.insert(buf, " typeset -A opt_args\n") end self:_zsh_arguments(buf, self._basename, 2) self:_zsh_complete_help(buf, cmds_buf, self._basename, 2) table.insert(buf, "\n return 1") table.insert(buf, "}") local result = table.concat(buf, "\n") if #cmds_buf > 0 then result = result .. "\n" .. table.concat(cmds_buf, "\n") end return result .. "\n\n_" .. self._basename .. "\n" end local function fish_escape(string) return string:gsub("[\\']", "\\%0") end function Parser:_fish_get_cmd(buf, indent) if #self._commands == 0 then return end table.insert(buf, (" "):rep(indent) .. "set -e cmdline[1]") table.insert(buf, (" "):rep(indent) .. "for arg in $cmdline") table.insert(buf, (" "):rep(indent + 4) .. "switch $arg") for _, command in ipairs(self._commands) do table.insert(buf, (" "):rep(indent + 8) .. "case " .. table.concat(command._aliases, " ")) table.insert(buf, (" "):rep(indent + 12) .. "set cmd $cmd " .. command._name) command:_fish_get_cmd(buf, indent + 12) table.insert(buf, (" "):rep(indent + 12) .. "break") end table.insert(buf, (" "):rep(indent + 4) .. "end") table.insert(buf, (" "):rep(indent) .. "end") end function Parser:_fish_complete_help(buf, basename) local prefix = "complete -c " .. basename table.insert(buf, "") for _, command in ipairs(self._commands) do local aliases = table.concat(command._aliases, " ") local line if self._parent then line = ("%s -n '__fish_%s_using_command %s' -xa '%s'") :format(prefix, basename, self:_get_fullname(true), aliases) else line = ("%s -n '__fish_%s_using_command' -xa '%s'"):format(prefix, basename, aliases) end if command._description then line = ("%s -d '%s'"):format(line, fish_escape(get_short_description(command))) end table.insert(buf, line) end if self._is_help_command then local line = ("%s -n '__fish_%s_using_command %s' -xa '%s'") :format(prefix, basename, self:_get_fullname(true), self._parent:_get_commands()) table.insert(buf, line) end for _, option in ipairs(self._options) do local parts = {prefix} if self._parent then table.insert(parts, "-n '__fish_" .. basename .. "_seen_command " .. self:_get_fullname(true) .. "'") end for _, alias in ipairs(option._aliases) do if alias:match("^%-.$") then table.insert(parts, "-s " .. alias:sub(2)) elseif alias:match("^%-%-.+") then table.insert(parts, "-l " .. alias:sub(3)) end end if option._choices then table.insert(parts, "-xa '" .. table.concat(option._choices, " ") .. "'") elseif option._minargs > 0 then table.insert(parts, "-r") end if option._description then table.insert(parts, "-d '" .. fish_escape(get_short_description(option)) .. "'") end table.insert(buf, table.concat(parts, " ")) end for _, command in ipairs(self._commands) do command:_fish_complete_help(buf, basename) end end function Parser:get_fish_complete() self._basename = base_name(self._name) assert(self:_is_shell_safe()) local buf = {} if #self._commands > 0 then table.insert(buf, ([[ function __fish_%s_print_command set -l cmdline (commandline -poc) set -l cmd]]):format(self._basename)) self:_fish_get_cmd(buf, 4) table.insert(buf, ([[ echo "$cmd" end function __fish_%s_using_command test (__fish_%s_print_command) = "$argv" and return 0 or return 1 end function __fish_%s_seen_command string match -q "$argv*" (__fish_%s_print_command) and return 0 or return 1 end]]):format(self._basename, self._basename, self._basename, self._basename)) end self:_fish_complete_help(buf, self._basename) return table.concat(buf, "\n") .. "\n" end local function get_tip(context, wrong_name) local context_pool = {} local possible_name local possible_names = {} for name in pairs(context) do if type(name) == "string" then for i = 1, #name do possible_name = name:sub(1, i - 1) .. name:sub(i + 1) if not context_pool[possible_name] then context_pool[possible_name] = {} end table.insert(context_pool[possible_name], name) end end end for i = 1, #wrong_name + 1 do possible_name = wrong_name:sub(1, i - 1) .. wrong_name:sub(i + 1) if context[possible_name] then possible_names[possible_name] = true elseif context_pool[possible_name] then for _, name in ipairs(context_pool[possible_name]) do possible_names[name] = true end end end local first = next(possible_names) if first then if next(possible_names, first) then local possible_names_arr = {} for name in pairs(possible_names) do table.insert(possible_names_arr, "'" .. name .. "'") end table.sort(possible_names_arr) return "\nDid you mean one of these: " .. table.concat(possible_names_arr, " ") .. "?" else return "\nDid you mean '" .. first .. "'?" end else return "" end end local ElementState = class({ invocations = 0 }) function ElementState:__call(state, element) self.state = state self.result = state.result self.element = element self.target = element._target or element:_get_default_target() self.action, self.result[self.target] = element:_get_action() return self end function ElementState:error(fmt, ...) self.state:error(fmt, ...) end function ElementState:convert(argument, index) local converter = self.element._convert if converter then local ok, err if type(converter) == "function" then ok, err = converter(argument) elseif type(converter[index]) == "function" then ok, err = converter[index](argument) else ok = converter[argument] end if ok == nil then self:error(err and "%s" or "malformed argument '%s'", err or argument) end argument = ok end return argument end function ElementState:default(mode) return self.element._defmode:find(mode) and self.element._default end local function bound(noun, min, max, is_max) local res = "" if min ~= max then res = "at " .. (is_max and "most" or "least") .. " " end local number = is_max and max or min return res .. tostring(number) .. " " .. noun .. (number == 1 and "" or "s") end function ElementState:set_name(alias) self.name = ("%s '%s'"):format(alias and "option" or "argument", alias or self.element._name) end function ElementState:invoke() self.open = true self.overwrite = false if self.invocations >= self.element._maxcount then if self.element._overwrite then self.overwrite = true else local num_times_repr = bound("time", self.element._mincount, self.element._maxcount, true) self:error("%s must be used %s", self.name, num_times_repr) end else self.invocations = self.invocations + 1 end self.args = {} if self.element._maxargs <= 0 then self:close() end return self.open end function ElementState:check_choices(argument) if self.element._choices then for _, choice in ipairs(self.element._choices) do if argument == choice then return end end local choices_list = "'" .. table.concat(self.element._choices, "', '") .. "'" local is_option = getmetatable(self.element) == Option self:error("%s%s must be one of %s", is_option and "argument for " or "", self.name, choices_list) end end function ElementState:pass(argument) self:check_choices(argument) argument = self:convert(argument, #self.args + 1) table.insert(self.args, argument) if #self.args >= self.element._maxargs then self:close() end return self.open end function ElementState:complete_invocation() while #self.args < self.element._minargs do self:pass(self.element._default) end end function ElementState:close() if self.open then self.open = false if #self.args < self.element._minargs then if self:default("a") then self:complete_invocation() else if #self.args == 0 then if getmetatable(self.element) == Argument then self:error("missing %s", self.name) elseif self.element._maxargs == 1 then self:error("%s requires an argument", self.name) end end self:error("%s requires %s", self.name, bound("argument", self.element._minargs, self.element._maxargs)) end end local args if self.element._maxargs == 0 then args = self.args[1] elseif self.element._maxargs == 1 then if self.element._minargs == 0 and self.element._mincount ~= self.element._maxcount then args = self.args else args = self.args[1] end else args = self.args end self.action(self.result, self.target, args, self.overwrite) end end local ParseState = class({ result = {}, options = {}, arguments = {}, argument_i = 1, element_to_mutexes = {}, mutex_to_element_state = {}, command_actions = {} }) function ParseState:__call(parser, error_handler) self.parser = parser self.error_handler = error_handler self.charset = parser:_update_charset() self:switch(parser) return self end function ParseState:error(fmt, ...) self.error_handler(self.parser, fmt:format(...)) end function ParseState:switch(parser) self.parser = parser if parser._action then table.insert(self.command_actions, {action = parser._action, name = parser._name}) end for _, option in ipairs(parser._options) do option = ElementState(self, option) table.insert(self.options, option) for _, alias in ipairs(option.element._aliases) do self.options[alias] = option end end for _, mutex in ipairs(parser._mutexes) do for _, element in ipairs(mutex) do if not self.element_to_mutexes[element] then self.element_to_mutexes[element] = {} end table.insert(self.element_to_mutexes[element], mutex) end end for _, argument in ipairs(parser._arguments) do argument = ElementState(self, argument) table.insert(self.arguments, argument) argument:set_name() argument:invoke() end self.handle_options = parser._handle_options self.argument = self.arguments[self.argument_i] self.commands = parser._commands for _, command in ipairs(self.commands) do for _, alias in ipairs(command._aliases) do self.commands[alias] = command end end end function ParseState:get_option(name) local option = self.options[name] if not option then self:error("unknown option '%s'%s", name, get_tip(self.options, name)) else return option end end function ParseState:get_command(name) local command = self.commands[name] if not command then if #self.commands > 0 then self:error("unknown command '%s'%s", name, get_tip(self.commands, name)) else self:error("too many arguments") end else return command end end function ParseState:check_mutexes(element_state) if self.element_to_mutexes[element_state.element] then for _, mutex in ipairs(self.element_to_mutexes[element_state.element]) do local used_element_state = self.mutex_to_element_state[mutex] if used_element_state and used_element_state ~= element_state then self:error("%s can not be used together with %s", element_state.name, used_element_state.name) else self.mutex_to_element_state[mutex] = element_state end end end end function ParseState:invoke(option, name) self:close() option:set_name(name) self:check_mutexes(option, name) if option:invoke() then self.option = option end end function ParseState:pass(arg) if self.option then if not self.option:pass(arg) then self.option = nil end elseif self.argument then self:check_mutexes(self.argument) if not self.argument:pass(arg) then self.argument_i = self.argument_i + 1 self.argument = self.arguments[self.argument_i] end else local command = self:get_command(arg) self.result[command._target or command._name] = true if self.parser._command_target then self.result[self.parser._command_target] = command._name end self:switch(command) end end function ParseState:close() if self.option then self.option:close() self.option = nil end end function ParseState:finalize() self:close() for i = self.argument_i, #self.arguments do local argument = self.arguments[i] if #argument.args == 0 and argument:default("u") then argument:complete_invocation() else argument:close() end end if self.parser._require_command and #self.commands > 0 then self:error("a command is required") end for _, option in ipairs(self.options) do option.name = option.name or ("option '%s'"):format(option.element._name) if option.invocations == 0 then if option:default("u") then option:invoke() option:complete_invocation() option:close() end end local mincount = option.element._mincount if option.invocations < mincount then if option:default("a") then while option.invocations < mincount do option:invoke() option:close() end elseif option.invocations == 0 then self:error("missing %s", option.name) else self:error("%s must be used %s", option.name, bound("time", mincount, option.element._maxcount)) end end end for i = #self.command_actions, 1, -1 do self.command_actions[i].action(self.result, self.command_actions[i].name) end end function ParseState:parse(args) for _, arg in ipairs(args) do local plain = true if self.handle_options then local first = arg:sub(1, 1) if self.charset[first] then if #arg > 1 then plain = false if arg:sub(2, 2) == first then if #arg == 2 then if self.options[arg] then local option = self:get_option(arg) self:invoke(option, arg) else self:close() end self.handle_options = false else local equals = arg:find "=" if equals then local name = arg:sub(1, equals - 1) local option = self:get_option(name) if option.element._maxargs <= 0 then self:error("option '%s' does not take arguments", name) end self:invoke(option, name) self:pass(arg:sub(equals + 1)) else local option = self:get_option(arg) self:invoke(option, arg) end end else for i = 2, #arg do local name = first .. arg:sub(i, i) local option = self:get_option(name) self:invoke(option, name) if i ~= #arg and option.element._maxargs > 0 then self:pass(arg:sub(i + 1)) break end end end end end end if plain then self:pass(arg) end end self:finalize() return self.result end function Parser:error(msg) io.stderr:write(("%s\n\nError: %s\n"):format(self:get_usage(), msg)) os.exit(1) end -- Compatibility with strict.lua and other checkers: local default_cmdline = rawget(_G, "arg") or {} function Parser:_parse(args, error_handler) return ParseState(self, error_handler):parse(args or default_cmdline) end function Parser:parse(args) return self:_parse(args, self.error) end local function xpcall_error_handler(err) return tostring(err) .. "\noriginal " .. debug.traceback("", 2):sub(2) end function Parser:pparse(args) local parse_error local ok, result = xpcall(function() return self:_parse(args, function(_, err) parse_error = err error(err, 0) end) end, xpcall_error_handler) if ok then return true, result elseif not parse_error then error(result, 0) else return false, parse_error end end local argparse = {} argparse.version = "0.7.1" setmetatable(argparse, {__call = function(_, ...) return Parser(default_cmdline[0]):add_help(true)(...) end}) return argparse