├── .gitignore ├── lib └── optparse │ ├── _strict.lua │ └── init.lua ├── spec ├── version_spec.yaml ├── spec_helper.lua └── optparse_spec.yaml ├── Makefile ├── optparse-git-1.rockspec ├── LICENSE.md ├── .github └── workflows │ └── spec.yml ├── .luacov ├── NEWS.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | .DS_Store 3 | /*.src.rock 4 | /build-aux/config.ld 5 | /doc/* 6 | /lib/optparse/version.lua 7 | /luacov.*.out 8 | /optparse-*.tar.gz 9 | -------------------------------------------------------------------------------- /lib/optparse/_strict.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Simple Command Line Option Parsing for Lua 5.1, 5.2, 5.3 & 5.4 3 | Copyright (C) 2014-2018, 2021-2022 Gary V. Vaughan 4 | ]] 5 | 6 | local setfenv = rawget(_G, 'setfenv') or function() end 7 | 8 | 9 | local strict 10 | do 11 | local ok, _debug = pcall(require, 'std._debug.init') 12 | if ok and _debug.strict then 13 | ok, strict = pcall(require, 'std.strict.init') 14 | end 15 | if not ok then 16 | strict = function(env) 17 | return env 18 | end 19 | end 20 | end 21 | 22 | 23 | return function(env, level) 24 | env = strict(env) 25 | setfenv(1+(level or 1), env) 26 | return env 27 | end 28 | -------------------------------------------------------------------------------- /spec/version_spec.yaml: -------------------------------------------------------------------------------- 1 | # Simple Command Line Option Parsing for Lua 5.1, 5.2, 5.3 & 5.4 2 | # Copyright (C) 2014-2018, 2021-2022 Gary V. Vaughan 3 | 4 | before: 5 | this_module = 'optparse.version' 6 | 7 | M = require(this_module) 8 | 9 | specify optparse.version: 10 | - context when required: 11 | - it returns a string: 12 | expect(type(M)).to_be 'string' 13 | - it does not touch the global table: 14 | expect(show_apis {added_to='_G', by=this_module}). 15 | to_equal {} 16 | 17 | - describe version: 18 | - it describes this module: 19 | expect(M).to_match '^Parse Command%-Line Options' 20 | - it ends with the release number: 21 | expect(M).to_match.any_of {' git$', ' %d[%.%d]*$'} 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Simple Command Line Option Parsing for Lua 5.1, 5.2, 5.3 & 5.4 2 | # Copyright (C) 2014-2018, 2021-2022 Gary V. Vaughan 3 | 4 | LDOC = ldoc 5 | LUA = lua 6 | MKDIR = mkdir -p 7 | SED = sed 8 | SPECL = specl 9 | 10 | VERSION = git 11 | 12 | luadir = lib/optparse 13 | SOURCES = \ 14 | $(luadir)/init.lua \ 15 | $(luadir)/version.lua \ 16 | $(NOTHING_ELSE) 17 | 18 | 19 | all: $(luadir)/version.lua 20 | 21 | 22 | $(luadir)/version.lua: .FORCE 23 | @echo "return 'Parse Command-Line Options / $(VERSION)'" > '$@T'; \ 24 | if cmp -s '$@' '$@T'; then \ 25 | rm -f '$@T'; \ 26 | else \ 27 | echo "echo return 'Parse Command-Line Options / $(VERSION)' > $@"; \ 28 | mv '$@T' '$@'; \ 29 | fi 30 | 31 | doc: build-aux/config.ld $(SOURCES) 32 | $(LDOC) -c build-aux/config.ld . 33 | 34 | build-aux/config.ld: build-aux/config.ld.in 35 | $(SED) -e "s,@PACKAGE_VERSION@,$(VERSION)," '$<' > '$@' 36 | 37 | 38 | CHECK_ENV = LUA=$(LUA) 39 | 40 | check: $(SOURCES) 41 | LUA=$(LUA) $(SPECL) --unicode $(SPECL_OPTS) spec/*_spec.yaml 42 | 43 | 44 | .FORCE: 45 | -------------------------------------------------------------------------------- /optparse-git-1.rockspec: -------------------------------------------------------------------------------- 1 | local _MODREV, _SPECREV = 'git', '-1' 2 | 3 | package = 'optparse' 4 | version = _MODREV .. _SPECREV 5 | 6 | description = { 7 | summary = 'Parse and process command-line options', 8 | detailed = [[ 9 | Automatically generate a custom command-line option parser from 10 | just the long-form help text for your program. 11 | ]], 12 | homepage = 'http://gvvaughan.github.io/optparse', 13 | license = 'MIT/X11', 14 | } 15 | 16 | source = { 17 | url = 'http://github.com/gvvaughan/optparse/archive/v' .. _MODREV .. '.zip', 18 | dir = 'optparse-' .. _MODREV, 19 | } 20 | 21 | dependencies = { 22 | 'lua >= 5.1, < 5.5', 23 | } 24 | 25 | build = { 26 | type = 'builtin', 27 | modules = { 28 | optparse = 'lib/optparse/init.lua', 29 | ['optparse._strict'] = 'lib/optparse/_strict.lua', 30 | ['optparse.version'] = 'lib/optparse/version.lua', 31 | }, 32 | copy_directories = {'doc'}, 33 | } 34 | 35 | if _MODREV == 'git' then 36 | build.copy_directories = nil 37 | 38 | source = { 39 | url = 'git://github.com/gvvaughan/optparse.git', 40 | } 41 | end 42 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (C) 2014-2018,2021-2022 Gary V. Vaughan 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without restriction, 6 | including without limitation the rights to use, copy, modify, merge, 7 | publish, distribute, sublicense, and/or sell copies of the Software, 8 | and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGE- 17 | MENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 18 | FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 19 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /.github/workflows/spec.yml: -------------------------------------------------------------------------------- 1 | name: spec 2 | 3 | on: 4 | push: 5 | branches: [ '*' ] 6 | pull_request: 7 | branches: [ 'master' ] 8 | 9 | jobs: 10 | test: 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | lua-version: ["5.4", "5.3", "5.2", "5.1", "luajit"] 15 | strict: ["std.strict", ""] 16 | 17 | runs-on: ubuntu-latest 18 | 19 | steps: 20 | - uses: actions/checkout@v2 21 | 22 | - uses: leafo/gh-actions-lua@v8.0.0 23 | with: 24 | luaVersion: ${{ matrix.lua-version }} 25 | 26 | - uses: leafo/gh-actions-luarocks@v4.0.0 27 | 28 | - name: install 29 | run: | 30 | sudo apt-get install -y libyaml-dev 31 | luarocks install ansicolors 32 | luarocks install ldoc 33 | luarocks install luacov 34 | luarocks install specl 35 | test -n "${{ matrix.strict }}" && luarocks install std._debug || true 36 | test -n "${{ matrix.strict }}" && luarocks install std.strict || true 37 | 38 | - name: build 39 | run: | 40 | make all doc 41 | luarocks make 42 | 43 | - name: test 44 | run: | 45 | make check SPECL_OPTS='-vfreport --coverage' 46 | bash <(curl -s https://codecov.io/bash) -f luacov.report.out 47 | -------------------------------------------------------------------------------- /.luacov: -------------------------------------------------------------------------------- 1 | return { 2 | -- filename to store stats collected 3 | ["statsfile"] = "luacov.stats.out", 4 | 5 | -- filename to store report 6 | ["reportfile"] = "luacov.report.out", 7 | 8 | -- luacov.stats file updating frequency. 9 | -- The lower this value - the more frequenty results will be written out to luacov.stats 10 | -- You may want to reduce this value for short lived scripts (to for example 2) to avoid losing coverage data. 11 | ["savestepsize"] = 100, 12 | 13 | -- Run reporter on completion? (won't work for ticks) 14 | runreport = true, 15 | 16 | -- Delete stats file after reporting? 17 | deletestats = false, 18 | 19 | -- Process Lua code loaded from raw strings 20 | -- (that is, when the 'source' field in the debug info 21 | -- does not start with '@') 22 | codefromstrings = false, 23 | 24 | -- Patterns for files to include when reporting 25 | -- all will be included if nothing is listed 26 | -- (exclude overrules include, do not include 27 | -- the .lua extension, path separator is always '/') 28 | ["include"] = { 29 | "lib/optparse/init$", 30 | "lib/optparse/version$", 31 | }, 32 | 33 | -- Patterns for files to exclude when reporting 34 | -- all will be included if nothing is listed 35 | -- (exclude overrules include, do not include 36 | -- the .lua extension, path separator is always '/') 37 | ["exclude"] = { 38 | "luacov$", 39 | "luacov/reporter$", 40 | "luacov/defaults$", 41 | "luacov/runner$", 42 | "luacov/stats$", 43 | "luacov/tick$", 44 | }, 45 | } 46 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # optparse NEWS - User visible changes 2 | 3 | ## Noteworthy changes in release ?.? (????-??-??) [?] 4 | 5 | 6 | ## Noteworthy changes in release 1.5 (2022-07-30) [stable] 7 | 8 | ### Bug fixes 9 | 10 | - The parser no longer shadows the internal `optparse.version` 11 | `on_handler` by also saving the last word of the parsed 12 | `versiontext` into `optparse.version`. 13 | 14 | But, we don't want to break existing clients that use the content 15 | of the saved `optparse.version` string either. Since the 16 | `on_handler` was never available to callers before due to being 17 | shadowed, rename it to `optparse.showversion`. 18 | 19 | ### Incompatible changes 20 | 21 | - For consistency with the renaming of `optparse.showversion`, 22 | similarly rename `optparse.help` to `optparse.showhelp`. For 23 | backwards compatibility, `optparse.help` continues to be available 24 | too, but is now undocumented. Consider using the `showhelp` in 25 | your projects, especially if you also start using `showversion`. 26 | 27 | 28 | ## Noteworthy changes in release 1.4 (2018-09-16) [stable] 29 | 30 | ### New Features 31 | 32 | - Initial support for Lua 5.4. 33 | 34 | - No need to preinstall `std._debug` and `std.normalize` for deployment, 35 | of course without runtime checking. In development environments, 36 | installed `std._debug`, `std.strict` will be loaded and used for 37 | runtime checks as before. 38 | 39 | ### Bug fixes 40 | 41 | - Don't hang when option description text contains a '-' character. 42 | 43 | 44 | ## Noteworthy changes in release 1.3 (2017-12-17) [stable] 45 | 46 | ### Bug fixes 47 | 48 | - Don't hang when help text has a bare '-' as the first non-whitespace 49 | character on the line. 50 | 51 | ### Incompatible changes 52 | 53 | - The implementation now depends upon and requires the luarocks modules 54 | `std.normalize` and `std._debug`. 55 | 56 | 57 | ## Noteworthy changes in release 1.2 (2017-06-03) [stable] 58 | 59 | ### Bug fixes 60 | 61 | - Don't crash when first unrecognized argument is also a handler 62 | name (boolean, file, finished, flag, etc...) 63 | 64 | - Don't hang when help text option table formats long option name 65 | on its own line before indented description. 66 | 67 | 68 | ## Noteworthy changes in release 1.1.1 (2016-02-07) [stable] 69 | 70 | ### Bug fixes 71 | 72 | - Update for change in std.strict module path. 73 | 74 | ### Incompatible changes 75 | 76 | - `optparse._VERSION` is now `optparse.version` for consistency with 77 | other former stdlib modules. 78 | 79 | ## Noteworthy changes in release 1.1 (2016-01-29) [stable] 80 | 81 | ### New features 82 | 83 | - If lua-stdlib's `std.debug_init` module is loadable and has 84 | `_DEBUG.strict` set to `false`, then don't try to load the standalone 85 | `strict` module. 86 | 87 | ### Bug fixes 88 | 89 | - Uninstalled `std.debug_init` is handled correctly. 90 | 91 | - `optparse._VERSION` is now documented properly. 92 | 93 | 94 | ## Noteworthy changes in release 1.0.1 (2016-01-17) [stable] 95 | 96 | ### Bug fixes 97 | 98 | - Propagate metatable to parser factory output objects. 99 | 100 | 101 | ## Noteworthy changes in release 1.0 (2016-01-17) [stable] 102 | 103 | ### New features 104 | 105 | - Initial release, now separated out from lua-stdlib. 106 | -------------------------------------------------------------------------------- /spec/spec_helper.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Simple Command Line Option Parsing for Lua 5.1, 5.2, 5.3 & 5.4 3 | Copyright (C) 2014-2018, 2021-2022 Gary V. Vaughan 4 | ]] 5 | 6 | local inprocess = require 'specl.inprocess' 7 | local hell = require 'specl.shell' 8 | local std = require 'specl.std' 9 | 10 | badargs = require 'specl.badargs' 11 | 12 | package.path = std.package.normalize('./lib/?.lua', './lib/?/init.lua', package.path) 13 | 14 | 15 | -- Allow user override of LUA binary used by hell.spawn, falling 16 | -- back to environment PATH search for 'lua' if nothing else works. 17 | local LUA = os.getenv 'LUA' or 'lua' 18 | 19 | 20 | -- Allow use of bare 'unpack' even in Lua > 5.2. 21 | unpack = table.unpack or unpack 22 | 23 | 24 | local gsub = string.gsub 25 | 26 | 27 | -- Simplified version for specifications, does not support functable 28 | -- valued __len metamethod, so don't write examples that need that! 29 | function len(x) 30 | local __len = getmetatable(x) or {} 31 | if type(__len) == 'function' then 32 | return __len(x) 33 | end 34 | if type(x) ~= 'table' then 35 | return #x 36 | end 37 | 38 | local n = #x 39 | for i = 1, n do 40 | if x[i] == nil then 41 | return i -1 42 | end 43 | end 44 | return n 45 | end 46 | 47 | 48 | -- Error message specifications use this to shorten argument lists. 49 | -- Copied from functional.lua to avoid breaking all tests if functional 50 | -- cannot be loaded correctly. 51 | function bind(f, fix) 52 | return function(...) 53 | local arg = {} 54 | for i, v in pairs(fix) do 55 | arg[i] = v 56 | end 57 | local i = 1 58 | for _, v in pairs {...} do 59 | while arg[i] ~= nil do 60 | i = i + 1 61 | end 62 | arg[i] = v 63 | end 64 | return f(unpack(arg, 1, len(arg))) 65 | end 66 | end 67 | 68 | 69 | local function mkscript(code) 70 | local f = os.tmpname() 71 | local h = io.open(f, 'w') 72 | h:write(code) 73 | h:close() 74 | return f 75 | end 76 | 77 | 78 | --- Run some Lua code with the given arguments and input. 79 | -- @string code valid Lua code 80 | -- @tparam[opt={}] string|table arg single argument, or table of 81 | -- arguments for the script invocation. 82 | -- @string[opt] stdin standard input contents for the script process 83 | -- @treturn specl.shell.Process|nil status of resulting process if 84 | -- execution was successful, otherwise nil 85 | function luaproc(code, arg, stdin) 86 | local f = mkscript(code) 87 | if type(arg) ~= 'table' then 88 | arg = {arg} 89 | end 90 | local cmd = {LUA, f, unpack(arg, 1, len(arg))} 91 | -- inject env and stdin keys separately to avoid truncating `...` in 92 | -- cmd constructor 93 | cmd.env = { LUA_PATH=package.path, LUA_INIT='', LUA_INIT_5_2='' } 94 | cmd.stdin = stdin 95 | local proc = hell.spawn(cmd) 96 | os.remove(f) 97 | return proc 98 | end 99 | 100 | 101 | local function tabulate_output(code) 102 | local proc = luaproc(code) 103 | if proc.status ~= 0 then 104 | return error(proc.errout) 105 | end 106 | local r = {} 107 | gsub(proc.output, '(%S*)[%s]*', function(x) 108 | if x ~= '' then 109 | r[x] = true 110 | end 111 | end) 112 | return r 113 | end 114 | 115 | 116 | --- Show changes to tables wrought by a require statement. 117 | -- Lists new keys in T1 after `require 'import'`: 118 | -- 119 | -- show_apis {added_to=T1, by=import} 120 | -- 121 | -- @tparam table argt arguments table 122 | -- @treturn table a list of keys according to criteria above 123 | function show_apis(argt) 124 | return tabulate_output([[ 125 | local before, after = {}, {} 126 | for k in pairs(]] .. argt.added_to .. [[) do 127 | before[k] = true 128 | end 129 | 130 | local M = require ']] .. argt.by .. [[' 131 | for k in pairs(]] .. argt.added_to .. [[) do 132 | after[k] = true 133 | end 134 | 135 | for k in pairs(after) do 136 | if not before[k] then 137 | print(k) 138 | end 139 | end 140 | ]]) 141 | end 142 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Parse and Process Command Line Options 2 | ====================================== 3 | 4 | Copyright (C) 2014-2018, 2021-2022 [Gary V. Vaughan][github] 5 | 6 | [![License](http://img.shields.io/:license-mit-blue.svg)](http://mit-license.org) 7 | [![workflow status](https://github.com/gvvaughan/optparse/actions/workflows/spec.yml/badge.svg?branch=master)](https://github.com/gvvaughan/optparse/actions) 8 | [![codecov.io](https://codecov.io/gh/gvvaughan/optparse/branch/master/graph/badge.svg)](https://codecov.io/gh/gvvaughan/optparse) 9 | 10 | In the common case, you can write the long-form help output typical of 11 | a modern command line program, and let this module generate a custom 12 | parser that collects and diagnoses the options it describes. 13 | 14 | The parser is actually an object instance which can then be tweaked for 15 | the uncommon case, by hand, or by using the "on" method to tie your 16 | custom handlers to options that are not handled quite the way you'd 17 | like. 18 | 19 | This is a pure Lua library compatible with [LuaJIT][], [Lua][] 5.1, 20 | 5.2, 5.3 and 5.4. 21 | 22 | [github]: https://github.com/gvvaughan/optparse/ "Github repository" 23 | [lua]: https://www.lua.org "The Lua Project" 24 | [luajit]: https://luajit.org "The LuaJIT Project" 25 | 26 | 27 | Installation 28 | ------------ 29 | 30 | The simplest and best way to install optparse is with [LuaRocks][]. To 31 | install the latest release (recommended): 32 | 33 | ```bash 34 | luarocks install optparse 35 | ``` 36 | 37 | To install current git master (for testing, before submitting a bug 38 | report for example): 39 | 40 | ```bash 41 | luarocks install http://raw.githubusercontent.com/gvvaughan/optparse/master/optparse-git-1.rockspec 42 | ``` 43 | 44 | The best way to install without [LuaRocks][] is to copy the 45 | `optparse.lua` file into a directory in your package search path. 46 | 47 | [luarocks]: https://www.luarocks.org "Lua package manager" 48 | 49 | 50 | Use 51 | --- 52 | 53 | The optparse package returns a parser factory when loaded: 54 | 55 | ```lua 56 | local OptionParser = require "optparse" 57 | 58 | local help = [[ 59 | parseme (optparse spec) 0α1 60 | 61 | Copyright © 2018 Gary V. Vaughan 62 | This test program comes with ABSOLUTELY NO WARRANTY. 63 | 64 | Usage: parseme [] ... 65 | 66 | Banner text. 67 | 68 | Long description. 69 | 70 | Options: 71 | 72 | -h, --help display this help, then exit 73 | --version display version information, then exit 74 | -b a short option with no long option 75 | --long a long option with no short option 76 | --another-long a long option with internal hypen 77 | --true a Lua keyword as an option name 78 | -v, --verbose a combined short and long option 79 | -n, --dryrun, --dry-run several spellings of the same option 80 | -u, --name=USER require an argument 81 | -o, --output=[FILE] accept an optional argument 82 | -- end of options 83 | 84 | Footer text. 85 | 86 | Please report bugs at . 87 | ]] 88 | 89 | local parser = OptionParser (help) 90 | local arg, opts = parser:parse (_G.arg) 91 | ``` 92 | 93 | 94 | 95 | Documentation 96 | ------------- 97 | 98 | The latest release is [documented with LDoc][github.io]. 99 | Pre-built HTML files are included in the [release][] tarball. 100 | 101 | [github.io]: https://gvvaughan.github.io/optparse 102 | [release]: https://github.com/gvvaughan/optparse/releases 103 | 104 | 105 | Bug reports and code contributions 106 | ---------------------------------- 107 | 108 | Please make bug reports and suggestions as [GitHub Issues][issues]. 109 | Pull requests are especially appreciated. 110 | 111 | But first, please check that your issue has not already been reported by 112 | someone else, and that it is not already fixed by [master][github] in 113 | preparation for the next release (see Installation section above for how 114 | to temporarily install master with [LuaRocks][]). 115 | 116 | There is no strict coding style, but please bear in mind the following 117 | points when proposing changes: 118 | 119 | 0. Follow existing code. There are a lot of useful patterns and avoided 120 | traps there. 121 | 122 | 1. 3-character indentation using SPACES in Lua sources: It makes rogue 123 | TABs easier to see, and lines up nicely with 'if' and 'end' keywords. 124 | 125 | 2. Simple strings are easiest to type using single-quote delimiters, 126 | saving double-quotes for where a string contains apostrophes. 127 | 128 | 3. Save horizontal space by only using SPACEs where the parser requires 129 | them. 130 | 131 | 4. Use vertical space to separate out compound statements to help the 132 | coverage reports discover untested lines. 133 | 134 | 5. Prefer explicit string function calls over object methods, to mitigate 135 | issues with monkey-patching in caller environment. 136 | 137 | [issues]: https://github.com/gvvaughan/optparse/issues 138 | -------------------------------------------------------------------------------- /spec/optparse_spec.yaml: -------------------------------------------------------------------------------- 1 | # Simple Command Line Option Parsing for Lua 5.1, 5.2, 5.3 & 5.4 2 | # Copyright (C) 2014-2018, 2021-2022 Gary V. Vaughan 3 | 4 | before: 5 | this_module = "optparse" 6 | 7 | hell = require "specl.shell" 8 | 9 | M = require (this_module) 10 | 11 | specify optparse: 12 | - before: | 13 | M.version = nil -- previous specs may have autoloaded it 14 | 15 | OptionParser = M 16 | 17 | help = [[ 18 | parseme (optparse spec) 0α1 19 | 20 | Copyright © 2018 Gary V. Vaughan 21 | This test program comes with ABSOLUTELY NO WARRANTY. 22 | 23 | Usage: parseme [] ... 24 | 25 | Banner text. 26 | 27 | Long description. 28 | 29 | Options: 30 | 31 | -h, --help display this help, then exit 32 | --version display version information, then exit 33 | -b a short option with no long option 34 | --long a long option with no short option 35 | --another-long a long option with internal hypen 36 | --hyphenated hyphenated-option is hyphenated 37 | --true a Lua keyword as an option name 38 | --boolean an optparse handler as an option name 39 | --really-long-option-name 40 | with description on following line 41 | -v, --verbose a combined short and long option 42 | -n, --dryrun, --dry-run several spellings of the same option 43 | -u, --name=USER require an argument 44 | -o, --output=[FILE] accept an optional argument 45 | -- end of options 46 | 47 | Footer text. 48 | 49 | Please report bugs at . 50 | ]] 51 | 52 | -- strip off the leading whitespace required for YAML 53 | parser = OptionParser (help:gsub ("^ ", "")) 54 | 55 | - context when required: 56 | - context by name: 57 | - it does not touch the global table: 58 | expect (show_apis {added_to="_G", by="optparse"}). 59 | to_equal {} 60 | 61 | - context when lazy loading: 62 | - it has no submodules on initial load: 63 | for _, v in pairs (M) do 64 | if (getmetatable (v) or {})._type ~= "OptionParser" then 65 | expect (type (v)).not_to_be "table" 66 | end 67 | end 68 | - it loads submodules on demand: 69 | lazy = M.version 70 | expect (lazy).to_be (require "optparse.version") 71 | 72 | 73 | - describe OptionParser: 74 | - it recognises the program name: 75 | expect (parser.program).to_be "parseme" 76 | - it recognises the version number: 77 | expect (parser.version).to_be "0α1" 78 | - it recognises the version text: 79 | expect (parser.versiontext). 80 | to_match "^parseme .*Copyright .*NO WARRANTY%." 81 | - it recognises the help text: | 82 | expect (parser.helptext). 83 | to_match ("^Usage: parseme .*Banner .*Long .*Options:.*" .. 84 | "Footer .*/issues>%.") 85 | - it diagnoses incorrect input text: 86 | expect (OptionParser "garbage in").to_error "argument must match" 87 | - it ignores bare leading hyphens: | 88 | expect (OptionParser ( 89 | "barehypen 1.0\n" .. 90 | "\n" .. 91 | "Usage: barehyphen\n" .. 92 | "\n" .. 93 | " -a a\n" .. 94 | " - b\n")).not_to_error "with anything" 95 | 96 | 97 | - describe parser: 98 | - before: | 99 | code = [[ 100 | package.path = "]] .. package.path .. [[" 101 | local OptionParser = require 'optparse' 102 | local help = [=[]] .. help .. [[]=] 103 | help = help:match ("^[%s\n]*(.-)[%s\n]*$") 104 | 105 | local parser = OptionParser (help) 106 | local arg, opts = parser:parse (_G.arg) 107 | 108 | o = {} 109 | for k, v in pairs (opts) do 110 | table.insert (o, k .. " = " .. tostring (v)) 111 | end 112 | if #o > 0 then 113 | table.sort (o) 114 | print ("opts = { " .. table.concat (o, ", ") .. " }") 115 | end 116 | if #arg > 0 then 117 | print ("args = { " .. table.concat (arg, ", ") .. " }") 118 | end 119 | ]] 120 | parse = bind (luaproc, {code}) 121 | 122 | - it collects non-option arguments: 123 | expect (parse {"foo"}).to_output "args = { foo }\n" 124 | - it collects non-options arguments matching argparse handler names: 125 | expect (parse {"file"}).to_output "args = { file }\n" 126 | - it collects arguments matching argparse handler names: 127 | expect (parse {"--boolean"}).to_output "opts = { boolean = true }\n" 128 | 129 | - it responds to --version with version text: 130 | expect (parse {"--version"}). 131 | to_match_output "^%s*parseme .*Copyright .*NO WARRANTY%.\n$" 132 | - it responds to --help with help text: | 133 | expect (parse {"--help"}). 134 | to_match_output ("^%s*Usage: parseme .*Banner.*Long.*" .. 135 | "Options:.*Footer.*/issues>%.\n$") 136 | - it leaves behind unrecognised short options: 137 | expect (parse {"-x"}).to_output "args = { -x }\n" 138 | - it recognises short options: 139 | expect (parse {"-b"}).to_output "opts = { b = true }\n" 140 | - it leaves behind unrecognised options: 141 | expect (parse {"--not-an-option"}). 142 | to_output "args = { --not-an-option }\n" 143 | - it recognises long options: 144 | expect (parse {"--long"}).to_output "opts = { long = true }\n" 145 | - it recognises long options with hyphens: 146 | expect (parse {"--another-long"}). 147 | to_output "opts = { another_long = true }\n" 148 | - it recognises long options named after Lua keywords: 149 | expect (parse {"--true"}).to_output "opts = { true = true }\n" 150 | - it recognises combined short and long option specs: 151 | expect (parse {"-v"}).to_output "opts = { verbose = true }\n" 152 | expect (parse {"--verbose"}).to_output "opts = { verbose = true }\n" 153 | - it recognises options with several spellings: 154 | expect (parse {"-n"}).to_output "opts = { dry_run = true }\n" 155 | expect (parse {"--dry-run"}).to_output "opts = { dry_run = true }\n" 156 | expect (parse {"--dryrun"}).to_output "opts = { dry_run = true }\n" 157 | - it recognises end of options marker: 158 | expect (parse {"-- -n"}).to_output "args = { -n }\n" 159 | - context given an unhandled long option: 160 | - it leaves behind unmangled argument: 161 | expect (parse {"--not-an-option=with-an-argument"}). 162 | to_output "args = { --not-an-option=with-an-argument }\n" 163 | - context given an option with a required argument: 164 | - it records an argument to a long option following an '=' delimiter: 165 | expect (parse {"--name=Gary"}). 166 | to_output "opts = { name = Gary }\n" 167 | - it records an argument to a short option without a space: 168 | expect (parse {"-uGary"}). 169 | to_output "opts = { name = Gary }\n" 170 | - it records an argument to a long option following a space: 171 | expect (parse {"--name Gary"}). 172 | to_output "opts = { name = Gary }\n" 173 | - it records an argument to a short option following a space: 174 | expect (parse {"-u Gary"}). 175 | to_output "opts = { name = Gary }\n" 176 | - it diagnoses a missing argument: 177 | expect (parse {"--name"}). 178 | to_contain_error "'--name' requires an argument" 179 | expect (parse {"-u"}). 180 | to_contain_error "'-u' requires an argument" 181 | - context given an option with an optional argument: 182 | - it records an argument to a long option following an '=' delimiter: 183 | expect (parse {"--output=filename"}). 184 | to_output "opts = { output = filename }\n" 185 | - it records an argument to a short option without a space: 186 | expect (parse {"-ofilename"}). 187 | to_output "opts = { output = filename }\n" 188 | - it records an argument to a long option following a space: 189 | expect (parse {"--output filename"}). 190 | to_output "opts = { output = filename }\n" 191 | - it records an argument to a short option following a space: 192 | expect (parse {"-o filename"}). 193 | to_output "opts = { output = filename }\n" 194 | - it doesn't consume the following option: 195 | expect (parse {"--output -v"}). 196 | to_output "opts = { output = true, verbose = true }\n" 197 | expect (parse {"-o -v"}). 198 | to_output "opts = { output = true, verbose = true }\n" 199 | - context when splitting combined short options: 200 | - it separates non-argument options: 201 | expect (parse {"-bn"}). 202 | to_output "opts = { b = true, dry_run = true }\n" 203 | expect (parse {"-vbn"}). 204 | to_output "opts = { b = true, dry_run = true, verbose = true }\n" 205 | - it stops separating at a required argument option: 206 | expect (parse {"-vuname"}). 207 | to_output "opts = { name = name, verbose = true }\n" 208 | expect (parse {"-vuob"}). 209 | to_output "opts = { name = ob, verbose = true }\n" 210 | - it stops separating at an optional argument option: 211 | expect (parse {"-vofilename"}). 212 | to_output "opts = { output = filename, verbose = true }\n" 213 | expect (parse {"-vobn"}). 214 | to_output "opts = { output = bn, verbose = true }\n" 215 | - it leaves behind unsplittable short options: 216 | expect (parse {"-xvb"}).to_output "args = { -xvb }\n" 217 | expect (parse {"-vxb"}).to_output "args = { -vxb }\n" 218 | expect (parse {"-vbx"}).to_output "args = { -vbx }\n" 219 | - it separates short options before unsplittable options: 220 | expect (parse {"-vb -xvb"}). 221 | to_output "opts = { b = true, verbose = true }\nargs = { -xvb }\n" 222 | expect (parse {"-vb -vxb"}). 223 | to_output "opts = { b = true, verbose = true }\nargs = { -vxb }\n" 224 | expect (parse {"-vb -vbx"}). 225 | to_output "opts = { b = true, verbose = true }\nargs = { -vbx }\n" 226 | - it separates short options after unsplittable options: 227 | expect (parse {"-xvb -vb"}). 228 | to_output "opts = { b = true, verbose = true }\nargs = { -xvb }\n" 229 | expect (parse {"-vxb -vb"}). 230 | to_output "opts = { b = true, verbose = true }\nargs = { -vxb }\n" 231 | expect (parse {"-vbx -vb"}). 232 | to_output "opts = { b = true, verbose = true }\nargs = { -vbx }\n" 233 | 234 | - context with option defaults: 235 | - before: | 236 | function main (arg) 237 | local OptionParser = require "optparse" 238 | local parser = OptionParser ("program 0\nUsage: program\n" .. 239 | " -x set x\n" .. 240 | " -y set y\n" .. 241 | " -z set z\n") 242 | local state = { arg = {}, opts = { x={"t"}, y=false }} 243 | state.arg, state.opts = parser:parse (arg, state.opts) 244 | return state 245 | end 246 | - it prefers supplied argument: 247 | expect (main {"-x", "-y"}). 248 | to_equal { arg = {}, opts = { x=true, y=true }} 249 | expect (main {"-x", "-y", "-z"}). 250 | to_equal { arg = {}, opts = { x=true, y=true, z=true }} 251 | expect (main {"-w", "-x", "-y"}). 252 | to_equal { arg = {"-w"}, opts = { x=true, y=true }} 253 | - it defers to default value: 254 | expect (main {}). 255 | to_equal { arg = {}, opts = { x={"t"}, y=false }} 256 | expect (main {"-z"}). 257 | to_equal { arg = {}, opts = { x={"t"}, y=false, z=true }} 258 | expect (main {"-w"}). 259 | to_equal { arg = {"-w"}, opts = { x={"t"}, y=false }} 260 | 261 | - context with io.die: 262 | - before: | 263 | runscript = function (code) 264 | return luaproc ([[ 265 | local OptionParser = require "optparse" 266 | local parser = OptionParser ("program 0\nUsage: program\n") 267 | _G.arg, _G.opts = parser:parse (_G.arg) 268 | ]] .. code .. [[ 269 | require "std.io".die "By 'eck!" 270 | ]]) 271 | end 272 | - it prefers `prog.name` to `opts.program`: | 273 | code = [[prog = { file = "file", name = "name" }]] 274 | expect (runscript (code)).to_fail_while_matching ": name: By 'eck!\n" 275 | - it prefers `prog.file` to `opts.program`: | 276 | code = [[prog = { file = "file" }]] 277 | expect (runscript (code)).to_fail_while_matching ": file: By 'eck!\n" 278 | - it appends `prog.line` if any to `prog.file` over using `opts`: | 279 | code = [[ 280 | prog = { file = "file", line = 125 }; opts.line = 99]] 281 | expect (runscript (code)). 282 | to_fail_while_matching ": file:125: By 'eck!\n" 283 | - it prefixes `opts.program` if any: | 284 | expect (runscript ("")).to_fail_while_matching ": program: By 'eck!\n" 285 | - it appends `opts.line` if any, to `opts.program`: | 286 | code = [[opts.line = 99]] 287 | expect (runscript (code)). 288 | to_fail_while_matching ": program:99: By 'eck!\n" 289 | 290 | - context with io.warn: 291 | - before: | 292 | runscript = function (code) 293 | return luaproc ([[ 294 | local OptionParser = require "optparse" 295 | local parser = OptionParser ("program 0\nUsage: program\n") 296 | _G.arg, _G.opts = parser:parse (_G.arg) 297 | ]] .. code .. [[ 298 | require "std.io".warn "Ayup!" 299 | ]]) 300 | end 301 | - it prefers `prog.name` to `opts.program`: | 302 | code = [[prog = { file = "file", name = "name" }]] 303 | expect (runscript (code)).to_output_error "name: Ayup!\n" 304 | - it prefers `prog.file` to `opts.program`: | 305 | code = [[prog = { file = "file" }]] 306 | expect (runscript (code)).to_output_error "file: Ayup!\n" 307 | - it appends `prog.line` if any to `prog.file` over using `opts`: | 308 | code = [[ 309 | prog = { file = "file", line = 125 }; opts.line = 99]] 310 | expect (runscript (code)). 311 | to_output_error "file:125: Ayup!\n" 312 | - it prefixes `opts.program` if any: | 313 | expect (runscript ("")).to_output_error "program: Ayup!\n" 314 | - it appends `opts.line` if any, to `opts.program`: | 315 | code = [[opts.line = 99]] 316 | expect (runscript (code)). 317 | to_output_error "program:99: Ayup!\n" 318 | 319 | - describe parser:on: 320 | - before: | 321 | function parseargs (onargstr, arglist) 322 | code = [[ 323 | package.path = "]] .. package.path .. [[" 324 | local OptionParser = require 'optparse' 325 | local help = [=[]] .. help .. [[]=] 326 | help = help:match ("^[%s\n]*(.-)[%s\n]*$") 327 | 328 | local parser = OptionParser (help) 329 | 330 | parser:on (]] .. onargstr .. [[) 331 | 332 | _G.arg, _G.opts = parser:parse (_G.arg) 333 | 334 | o = {} 335 | for k, v in pairs (opts) do 336 | table.insert (o, k .. " = " .. tostring (v)) 337 | end 338 | if #o > 0 then 339 | table.sort (o) 340 | print ("opts = { " .. table.concat (o, ", ") .. " }") 341 | end 342 | if #arg > 0 then 343 | print ("args = { " .. table.concat (arg, ", ") .. " }") 344 | end 345 | ]] 346 | 347 | return luaproc (code, arglist) 348 | end 349 | 350 | - it recognises short options: 351 | expect (parseargs ([["x"]], {"-x"})). 352 | to_output "opts = { x = true }\n" 353 | - it recognises long options: 354 | expect (parseargs([["something"]], {"--something"})). 355 | to_output "opts = { something = true }\n" 356 | - it recognises long options with hyphens: 357 | expect (parseargs([["some-thing"]], {"--some-thing"})). 358 | to_output "opts = { some_thing = true }\n" 359 | - it recognises long options named after Lua keywords: 360 | expect (parseargs ([["if"]], {"--if"})). 361 | to_output "opts = { if = true }\n" 362 | - it recognises combined short and long option specs: 363 | expect (parseargs ([[{"x", "if"}]], {"-x"})). 364 | to_output "opts = { if = true }\n" 365 | expect (parseargs ([[{"x", "if"}]], {"--if"})). 366 | to_output "opts = { if = true }\n" 367 | - it recognises options with several spellings: 368 | expect (parseargs ([[{"x", "blah", "if"}]], {"-x"})). 369 | to_output "opts = { if = true }\n" 370 | expect (parseargs ([[{"x", "blah", "if"}]], {"--blah"})). 371 | to_output "opts = { if = true }\n" 372 | expect (parseargs ([[{"x", "blah", "if"}]], {"--if"})). 373 | to_output "opts = { if = true }\n" 374 | - it recognises end of options marker: 375 | expect (parseargs ([["x"]], {"--", "-x"})). 376 | to_output "args = { -x }\n" 377 | - context given an option with a required argument: 378 | - it records an argument to a short option without a space: 379 | expect (parseargs ([["x", parser.required]], {"-y", "-xarg", "-b"})). 380 | to_contain_output "opts = { b = true, x = arg }" 381 | - it records an argument to a short option following a space: 382 | expect (parseargs ([["x", parser.required]], {"-y", "-x", "arg", "-b"})). 383 | to_contain_output "opts = { b = true, x = arg }\n" 384 | - it records an argument to a long option following a space: 385 | expect (parseargs ([["this", parser.required]], {"--this", "arg"})). 386 | to_output "opts = { this = arg }\n" 387 | - it records an argument to a long option following an '=' delimiter: 388 | expect (parseargs ([["this", parser.required]], {"--this=arg"})). 389 | to_output "opts = { this = arg }\n" 390 | - it diagnoses a missing argument: 391 | expect (parseargs ([[{"x", "this"}, parser.required]], {"-x"})). 392 | to_contain_error "'-x' requires an argument" 393 | expect (parseargs ([[{"x", "this"}, parser.required]], {"--this"})). 394 | to_contain_error "'--this' requires an argument" 395 | - context with a boolean handler function: 396 | - it records a truthy argument: 397 | for _, optarg in ipairs {"1", "TRUE", "true", "yes", "Yes", "y"} 398 | do 399 | expect (parseargs ([["x", parser.required, parser.boolean]], 400 | {"-x", optarg})). 401 | to_output "opts = { x = true }\n" 402 | end 403 | - it records a falsey argument: 404 | for _, optarg in ipairs {"0", "FALSE", "false", "no", "No", "n"} 405 | do 406 | expect (parseargs ([["x", parser.required, parser.boolean]], 407 | {"-x", optarg})). 408 | to_output "opts = { x = false }\n" 409 | end 410 | - context with a file handler function: 411 | - it records an existing file: 412 | expect (parseargs ([["x", parser.required, parser.file]], 413 | {"-x", "/dev/null"})). 414 | to_output "opts = { x = /dev/null }\n" 415 | - it diagnoses a missing file: | 416 | expect (parseargs ([["x", parser.required, parser.file]], 417 | {"-x", "/this/file/does/not/exist"})). 418 | to_contain_error "error: /this/file/does/not/exist: " 419 | - context with a custom handler function: 420 | - it calls the handler: 421 | expect (parseargs ([["x", parser.required, function (p,o,a) 422 | return "custom" 423 | end 424 | ]], {"-x", "ignored"})). 425 | to_output "opts = { x = custom }\n" 426 | - it diagnoses a missing argument: 427 | expect (parseargs ([["x", parser.required, function (p,o,a) 428 | return "custom" 429 | end 430 | ]], {"-x"})). 431 | to_contain_error "option '-x' requires an argument" 432 | - context given an option with an optional argument: 433 | - it records an argument to a short option without a space: 434 | expect (parseargs ([["x", parser.optional]], {"-y", "-xarg", "-b"})). 435 | to_contain_output "opts = { b = true, x = arg }" 436 | - it records an argument to a short option following a space: 437 | expect (parseargs ([["x", parser.optional]], {"-y", "-x", "arg", "-b"})). 438 | to_contain_output "opts = { b = true, x = arg }\n" 439 | - it records an argument to a long option following a space: 440 | expect (parseargs ([["this", parser.optional]], {"--this", "arg"})). 441 | to_output "opts = { this = arg }\n" 442 | - it records an argument to a long option following an '=' delimiter: 443 | expect (parseargs ([["this", parser.optional]], {"--this=arg"})). 444 | to_output "opts = { this = arg }\n" 445 | - it does't consume the following option: 446 | expect (parseargs ([[{"x", "this"}, parser.optional]], {"-x", "-b"})). 447 | to_output "opts = { b = true, this = true }\n" 448 | expect (parseargs ([[{"x", "this"}, parser.optional]], {"--this", "-b"})). 449 | to_output "opts = { b = true, this = true }\n" 450 | - context with a boolean handler function: 451 | - it records a truthy argument: 452 | for _, optarg in ipairs {"1", "TRUE", "true", "yes", "Yes", "y"} 453 | do 454 | expect (parseargs ([["x", parser.optional, parser.boolean]], 455 | {"-x", optarg})). 456 | to_output "opts = { x = true }\n" 457 | end 458 | - it records a falsey argument: 459 | for _, optarg in ipairs {"0", "FALSE", "false", "no", "No", "n"} 460 | do 461 | expect (parseargs ([["x", parser.optional, parser.boolean]], 462 | {"-x", optarg})). 463 | to_output "opts = { x = false }\n" 464 | end 465 | - it defaults to a truthy value: 466 | expect (parseargs ([["x", parser.optional, parser.boolean]], 467 | {"-x", "-b"})). 468 | to_output "opts = { b = true, x = true }\n" 469 | - context with a file handler function: 470 | - it records an existing file: 471 | expect (parseargs ([["x", parser.optional, parser.file]], 472 | {"-x", "/dev/null"})). 473 | to_output "opts = { x = /dev/null }\n" 474 | - it diagnoses a missing file: | 475 | expect (parseargs ([["x", parser.optional, parser.file]], 476 | {"-x", "/this/file/does/not/exist"})). 477 | to_contain_error "error: /this/file/does/not/exist: " 478 | - context with a custom handler function: 479 | - it calls the handler: 480 | expect (parseargs ([["x", parser.optional, function (p,o,a) 481 | return "custom" 482 | end 483 | ]], {"-x", "ignored"})). 484 | to_output "opts = { x = custom }\n" 485 | - it does not consume a following option: 486 | expect (parseargs ([["x", parser.optional, function (p,o,a) 487 | return a or "default" 488 | end 489 | ]], {"-x", "-b"})). 490 | to_output "opts = { b = true, x = default }\n" 491 | - context when splitting combined short options: 492 | - it separates non-argument options: 493 | expect (parseargs ([["x"]], {"-xb"})). 494 | to_output "opts = { b = true, x = true }\n" 495 | expect (parseargs ([["x"]], {"-vxb"})). 496 | to_output "opts = { b = true, verbose = true, x = true }\n" 497 | - it stops separating at a required argument option: 498 | expect (parseargs ([[{"x", "this"}, parser.required]], {"-bxbit"})). 499 | to_output "opts = { b = true, this = bit }\n" 500 | - it stops separating at an optional argument option: 501 | expect (parseargs ([[{"x", "this"}, parser.optional]], {"-bxbit"})). 502 | to_output "opts = { b = true, this = bit }\n" 503 | -------------------------------------------------------------------------------- /lib/optparse/init.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Simple Command Line Option Parsing for Lua 5.1, 5.2, 5.3 & 5.4 3 | Copyright (C) 2014-2018, 2021-2022 Gary V. Vaughan 4 | ]] 5 | --[[-- 6 | Parse and process command line options. 7 | 8 | In the common case, you can write the long-form help output typical of 9 | a modern-command line program, and let this module generate a custom 10 | parser that collects and diagnoses the options it describes. 11 | 12 | The parser is an object instance which can then be tweaked for 13 | the uncommon case, by hand, or by using the @{on} method to tie your 14 | custom handlers to options that are not handled quite the way you'd 15 | like. 16 | 17 | @module optparse 18 | ]] 19 | 20 | 21 | local _ENV = require 'optparse._strict' { 22 | assert = assert, 23 | error = error, 24 | exit = os.exit, 25 | find = string.find, 26 | getmetatable = getmetatable, 27 | gsub = string.gsub, 28 | insert = table.insert, 29 | ipairs = ipairs, 30 | lower = string.lower, 31 | match = string.match, 32 | next = next, 33 | nonempty = next, 34 | open = io.open, 35 | pcall = pcall, 36 | print = print, 37 | rawset = rawset, 38 | require = require, 39 | setmetatable = setmetatable, 40 | stderr = io.stderr, 41 | sub = string.sub, 42 | tostring = tostring, 43 | type = type, 44 | } 45 | 46 | 47 | 48 | --[[ ================= ]]-- 49 | --[[ Helper Functions. ]]-- 50 | --[[ ================= ]]-- 51 | 52 | 53 | local function iscallable(x) 54 | return type((getmetatable(x) or {}).__call or x) == 'function' 55 | end 56 | 57 | 58 | local function getmetamethod(x, n) 59 | local m = (getmetatable(x) or {})[tostring(n)] 60 | if iscallable(m) then 61 | return m 62 | end 63 | end 64 | 65 | 66 | local function rawlen(x) 67 | if type(x) ~= 'table' then 68 | return #x 69 | end 70 | 71 | local n = #x 72 | for i = 1, n do 73 | if x[i] == nil then 74 | return i - 1 75 | end 76 | end 77 | return n 78 | end 79 | 80 | 81 | local function len(x) 82 | local m = getmetamethod(x, '__len') 83 | if m then 84 | return m(x) 85 | end 86 | if getmetamethod(x, '__tostring') then 87 | x = tostring(x) 88 | end 89 | return rawlen(x) 90 | end 91 | 92 | 93 | local function merge(t, r) 94 | r = r or {} 95 | for k, v in next, t do 96 | r[k] = r[k] or v 97 | end 98 | return r 99 | end 100 | 101 | 102 | local function extend(r, items) 103 | for i = 1, len(items) do 104 | r[#r + 1] = items[i] 105 | end 106 | end 107 | 108 | 109 | local function last(t) 110 | return t[len(t)] 111 | end 112 | 113 | 114 | local optional, required 115 | 116 | 117 | --- Expand an argument list. 118 | -- Separate short options, remove `=` separators from 119 | -- `--long-option=optarg` etc. 120 | -- @local 121 | -- @function expandargs 122 | -- @tparam table arglist list of arguments to expand 123 | -- @treturn table expanded argument list 124 | local function expandargs(self, arglist) 125 | local r = {} 126 | local i = 0 127 | while i < len(arglist) do 128 | i = i + 1 129 | local opt = arglist[i] 130 | 131 | -- Split '--long-option=option-argument'. 132 | if match(opt, '^%-%-') then 133 | local x = find(opt, '=', 3, true) 134 | if x then 135 | local optname = sub(opt, 1, x -1) 136 | 137 | -- Only split recognised long options. 138 | if self[optname] then 139 | extend(r, {optname, sub(opt, x + 1)}) 140 | else 141 | x = nil 142 | end 143 | end 144 | 145 | if x == nil then 146 | -- No '=', or substring before '=' is not a known option name. 147 | insert(r, opt) 148 | end 149 | 150 | elseif sub(opt, 1, 1) == '-' and len(opt) > 2 then 151 | local orig, split, rest = opt, {} 152 | repeat 153 | opt, rest = match(opt, '^(%-%S)(.*)$') 154 | insert(split, opt) 155 | 156 | -- If there's no handler, the option was a typo, or not supposed 157 | -- to be an option at all. 158 | if self[opt] == nil then 159 | opt, split = nil, {orig} 160 | 161 | -- Split '-xyz' into '-x -yz', and reiterate for '-yz' 162 | elseif self[opt].handler ~= optional and 163 | self[opt].handler ~= required then 164 | if len(rest) > 0 then 165 | opt = '-' .. rest 166 | else 167 | opt = nil 168 | end 169 | 170 | -- Split '-xshortargument' into '-x shortargument'. 171 | else 172 | insert(split, rest) 173 | opt = nil 174 | end 175 | until opt == nil 176 | 177 | -- Append split options to expanded list 178 | extend(r, split) 179 | else 180 | insert(r, opt) 181 | end 182 | end 183 | 184 | r[-1], r[0] = arglist[-1], arglist[0] 185 | return r 186 | end 187 | 188 | 189 | --- Store `value` with `opt`. 190 | -- @local 191 | -- @function set 192 | -- @string opt option name 193 | -- @param value option argument value 194 | local function set(self, opt, value) 195 | local key = self[opt].key 196 | local opts = self.opts[key] 197 | 198 | if type(opts) == 'table' then 199 | insert(opts, value) 200 | elseif opts ~= nil then 201 | self.opts[key] = {opts, value} 202 | else 203 | self.opts[key] = value 204 | end 205 | end 206 | 207 | 208 | 209 | --[[ ============= ]]-- 210 | --[[ Option Types. ]]-- 211 | --[[ ============= ]]-- 212 | 213 | 214 | --- Option at `arglist[i]` can take an argument. 215 | -- Argument is accepted only if there is a following entry that does not 216 | -- begin with a '-'. 217 | -- 218 | -- This is the handler automatically assigned to options that have 219 | -- `--opt=[ARG]` style specifications in the @{OptionParser} spec 220 | -- argument. You can also pass it as the `handler` argument to @{on} for 221 | -- options you want to add manually without putting them in the 222 | -- @{OptionParser} spec. 223 | -- 224 | -- Like @{required}, this handler will store multiple occurrences of a 225 | -- command-line option. 226 | -- @static 227 | -- @tparam table arglist list of arguments 228 | -- @int i index of last processed element of *arglist* 229 | -- @param[opt=true] value either a function to process the option 230 | -- argument, or a default value if encountered without an optarg 231 | -- @treturn int index of next element of *arglist* to process 232 | -- @usage 233 | -- parser:on('--enable-nls', parser.optional, parser.boolean) 234 | function optional(self, arglist, i, value) 235 | if i + 1 <= len(arglist) and sub(arglist[i + 1], 1, 1) ~= '-' then 236 | return self:required(arglist, i, value) 237 | end 238 | 239 | if iscallable(value) then 240 | value = value(self, arglist[i], nil) 241 | elseif value == nil then 242 | value = true 243 | end 244 | 245 | set(self, arglist[i], value) 246 | return i + 1 247 | end 248 | 249 | 250 | --- Option at `arglist[i]` requires an argument. 251 | -- 252 | -- This is the handler automatically assigned to options that have 253 | -- `--opt=ARG` style specifications in the @{OptionParser} spec argument. 254 | -- You can also pass it as the `handler` argument to @{on} for options 255 | -- you want to add manually without putting them in the @{OptionParser} 256 | -- spec. 257 | -- 258 | -- Normally the value stored in the `opt` table by this handler will be 259 | -- the string given as the argument to that option on the command line. 260 | -- However, if the option is given on the command-line multiple times, 261 | -- `opt['name']` will end up with all those arguments stored in the 262 | -- array part of a table: 263 | -- 264 | -- $ cat ./prog 265 | -- ... 266 | -- parser:on({'-e', '-exec'}, required) 267 | -- _G.arg, _G.opt = parser:parse(_G.arg) 268 | -- print(tostring(_G.opt.exec)) 269 | -- ... 270 | -- $ ./prog -e '(foo bar)' -e '(foo baz)' -- qux 271 | -- {1=(foo bar),2=(foo baz)} 272 | -- @static 273 | -- @tparam table arglist list of arguments 274 | -- @int i index of last processed element of *arglist* 275 | -- @param[opt] value either a function to process the option argument, 276 | -- or a forced value to replace the user's option argument. 277 | -- @treturn int index of next element of *arglist* to process 278 | -- @usage 279 | -- parser:on({'-o', '--output'}, parser.required) 280 | function required(self, arglist, i, value) 281 | local opt = arglist[i] 282 | if i + 1 > len(arglist) then 283 | self:opterr("option '" .. opt .. "' requires an argument") 284 | return i + 1 285 | end 286 | 287 | if iscallable(value) then 288 | value = value(self, opt, arglist[i + 1]) 289 | elseif value == nil then 290 | value = arglist[i + 1] 291 | end 292 | 293 | set(self, opt, value) 294 | return i + 2 295 | end 296 | 297 | 298 | --- Finish option processing 299 | -- 300 | -- This is the handler automatically assigned to the option written as 301 | -- `--` in the @{OptionParser} spec argument. You can also pass it as 302 | -- the `handler` argument to @{on} if you want to manually add an end 303 | -- of options marker without writing it in the @{OptionParser} spec. 304 | -- 305 | -- This handler tells the parser to stop processing arguments, so that 306 | -- anything after it will be an argument even if it otherwise looks 307 | -- like an option. 308 | -- @static 309 | -- @tparam table arglist list of arguments 310 | -- @int i index of last processed element of `arglist` 311 | -- @treturn int index of next element of `arglist` to process 312 | -- @usage 313 | -- parser:on('--', parser.finished) 314 | local function finished(self, arglist, i) 315 | for opt = i + 1, len(arglist) do 316 | insert(self.unrecognised, arglist[opt]) 317 | end 318 | return 1 + len(arglist) 319 | end 320 | 321 | 322 | --- Option at `arglist[i]` is a boolean switch. 323 | -- 324 | -- This is the handler automatically assigned to options that have 325 | -- `--long-opt` or `-x` style specifications in the @{OptionParser} spec 326 | -- argument. You can also pass it as the `handler` argument to @{on} for 327 | -- options you want to add manually without putting them in the 328 | -- @{OptionParser} spec. 329 | -- 330 | -- Beware that, _unlike_ @{required}, this handler will store multiple 331 | -- occurrences of a command-line option as a table **only** when given a 332 | -- `value` function. Automatically assigned handlers do not do this, so 333 | -- the option will simply be `true` if the option was given one or more 334 | -- times on the command-line. 335 | -- @static 336 | -- @tparam table arglist list of arguments 337 | -- @int i index of last processed element of *arglist* 338 | -- @param[opt] value either a function to process the option argument, 339 | -- or a value to store when this flag is encountered 340 | -- @treturn int index of next element of *arglist* to process 341 | -- @usage 342 | -- parser:on({'--long-opt', '-x'}, parser.flag) 343 | local function flag(self, arglist, i, value) 344 | local opt = arglist[i] 345 | if iscallable(value) then 346 | set(self, opt, value(self, opt, true)) 347 | elseif value == nil then 348 | local key = self[opt].key 349 | self.opts[key] = true 350 | end 351 | 352 | return i + 1 353 | end 354 | 355 | 356 | --- Option should display help text, then exit. 357 | -- 358 | -- This is the handler automatically assigned tooptions that have 359 | -- `--help` in the specification, e.g. `-h, -?, --help`. 360 | -- @static 361 | -- @function showhelp 362 | -- @usage 363 | -- parser:on('-?', parser.showhelp) 364 | local function showhelp(self) 365 | print(self.helptext) 366 | exit(0) 367 | end 368 | 369 | 370 | --- Option should display version text, then exit. 371 | -- 372 | -- This is the handler automatically assigned tooptions that have 373 | -- `--version` in the specification, e.g. `-V, --version`. 374 | -- @static 375 | -- @function showversion 376 | -- @usage 377 | -- parser:on('-V', parser.showversion) 378 | local function showversion(self) 379 | print(self.versiontext) 380 | exit(0) 381 | end 382 | 383 | 384 | 385 | --[[ =============== ]]-- 386 | --[[ Argument Types. ]]-- 387 | --[[ =============== ]]-- 388 | 389 | 390 | --- Map various option strings to equivalent Lua boolean values. 391 | -- @table boolvals 392 | -- @field false false 393 | -- @field 0 false 394 | -- @field no false 395 | -- @field n false 396 | -- @field true true 397 | -- @field 1 true 398 | -- @field yes true 399 | -- @field y true 400 | local boolvals = { 401 | ['false'] = false, ['true'] = true, 402 | ['0'] = false, ['1'] = true, 403 | no = false, yes = true, 404 | n = false, y = true, 405 | } 406 | 407 | 408 | --- Return a Lua boolean equivalent of various *optarg* strings. 409 | -- Report an option parse error if *optarg* is not recognised. 410 | -- 411 | -- Pass this as the `value` function to @{on} when you want various 412 | -- 'truthy' or 'falsey' option arguments to be coerced to a Lua `true` 413 | -- or `false` respectively in the options table. 414 | -- @static 415 | -- @string opt option name 416 | -- @string[opt='1'] optarg option argument, must be a key in @{boolvals} 417 | -- @treturn bool `true` or `false` 418 | -- @usage 419 | -- parser:on('--enable-nls', parser.optional, parser.boolean) 420 | local function boolean(self, opt, optarg) 421 | if optarg == nil then 422 | optarg = '1' -- default to truthy 423 | end 424 | local b = boolvals[lower(tostring(optarg))] 425 | if b == nil then 426 | return self:opterr(optarg .. ': Not a valid argument to ' ..opt[1] .. '.') 427 | end 428 | return b 429 | end 430 | 431 | 432 | --- Report an option parse error unless *optarg* names an 433 | -- existing file. 434 | -- 435 | -- Pass this as the `value` function to @{on} when you want to accept 436 | -- only option arguments that name an existing file. 437 | -- @fixme this only checks whether the file has read permissions 438 | -- @static 439 | -- @string opt option name 440 | -- @string optarg option argument, must be an existing file 441 | -- @treturn string *optarg* 442 | -- @usage 443 | -- parser:on('--config-file', parser.required, parser.file) 444 | local function file(self, opt, optarg) 445 | local h, errmsg = open(optarg, 'r') 446 | if h == nil then 447 | return self:opterr(optarg .. ': ' .. errmsg) 448 | end 449 | h:close() 450 | return optarg 451 | end 452 | 453 | 454 | 455 | --[[ =============== ]]-- 456 | --[[ Option Parsing. ]]-- 457 | --[[ =============== ]]-- 458 | 459 | 460 | --- Report an option parse error, then exit with status 2. 461 | -- 462 | -- Use this in your custom option handlers for consistency with the 463 | -- error output from built-in @{optparse} error messages. 464 | -- @static 465 | -- @string msg error message 466 | local function opterr(self, msg) 467 | local prog = self.program 468 | -- Ensure final period. 469 | if match(msg, '%.$') == nil then 470 | msg = msg .. '.' 471 | end 472 | stderr:write(prog .. ': error: ' .. msg .. '\n') 473 | stderr:write(prog .. ": Try '" .. prog .. " --help' for help.\n") 474 | exit(2) 475 | end 476 | 477 | 478 | ------ 479 | -- Function signature of an option handler for @{on}. 480 | -- @function on_handler 481 | -- @tparam table arglist list of arguments 482 | -- @int i index of last processed element of *arglist* 483 | -- @param[opt=nil] value additional `value` registered with @{on} 484 | -- @treturn int index of next element of *arglist* to process 485 | 486 | 487 | --- Add an option handler. 488 | -- 489 | -- When the automatically assigned option handlers don't do everything 490 | -- you require, or when you don't want to put an option into the 491 | -- @{OptionParser} `spec` argument, use this function to specify custom 492 | -- behaviour. If you write the option into the `spec` argument anyway, 493 | -- calling this function will replace the automatically assigned handler 494 | -- with your own. 495 | -- 496 | -- When writing your own handlers for @{optparse:on}, you only need 497 | -- to deal with expanded arguments, because combined short arguments 498 | -- (`-xyz`), equals separators to long options (`--long=ARG`) are fully 499 | -- expanded before any handler is called. 500 | -- @function on 501 | -- @tparam[string|table] opts name of the option, or list of option names 502 | -- @tparam on_handler handler function to call when any of *opts* is 503 | -- encountered 504 | -- @param value additional value passed to @{on_handler} 505 | -- @usage 506 | -- -- Don't process any arguments after `--` 507 | -- parser:on('--', parser.finished) 508 | local function on(self, opts, handler, value) 509 | if type(opts) == 'string' then 510 | opts = {opts} 511 | end 512 | handler = handler or flag -- unspecified options behave as flags 513 | 514 | local args = {} 515 | for _, optspec in ipairs(opts) do 516 | gsub(optspec, '(%S+)', function(opt) 517 | -- 'x' => '-x' 518 | if len(opt) == 1 then 519 | opt = '-' .. opt 520 | 521 | -- 'option-name' => '--option-name' 522 | elseif match(opt, '^[^%-]') then 523 | opt = '--' .. opt 524 | end 525 | 526 | if match(opt, '^%-[^%-]+') then 527 | -- '-xyz' => '-x -y -z' 528 | for i = 2, len(opt) do 529 | insert(args, '-' .. sub(opt, i, i)) 530 | end 531 | else 532 | insert(args, opt) 533 | end 534 | end) 535 | end 536 | 537 | if nonempty(args) then 538 | -- strip leading '-', and convert non-alphanums to '_' 539 | local key = gsub(match(last(args), '^%-*(.*)$'), '%W', '_') 540 | 541 | for _, opt in ipairs(args) do 542 | self[opt] = {key=key, handler=handler, value=value} 543 | end 544 | end 545 | end 546 | 547 | 548 | ------ 549 | -- Parsed options table, with a key for each encountered option, each 550 | -- with value set by that option's @{on_handler}. Where an option 551 | -- has one or more long-options specified, the key will be the first 552 | -- one of those with leading hyphens stripped and non-alphanumeric 553 | -- characters replaced with underscores. For options that can only be 554 | -- specified by a short option, the key will be the letter of the first 555 | -- of the specified short options: 556 | -- 557 | -- {'-e', '--eval-file'} => opts.eval_file 558 | -- {'-n', '--dryrun', '--dry-run'} => opts.dryrun 559 | -- {'-t', '-T'} => opts.t 560 | -- 561 | -- Generally there will be one key for each previously specified 562 | -- option (either automatically assigned by @{OptionParser} or 563 | -- added manually with @{on}) containing the value(s) assigned by the 564 | -- associated @{on_handler}. For automatically assigned handlers, 565 | -- that means `true` for straight-forward flags and 566 | -- optional-argument options for which no argument was given; or else 567 | -- the string value of the argument passed with an option given only 568 | -- once; or a table of string values of the same for arguments given 569 | -- multiple times. 570 | -- 571 | -- ./prog -x -n -x => opts = {x=true, dryrun=true} 572 | -- ./prog -e '(foo bar)' -e '(foo baz)' 573 | -- => opts = {eval_file={'(foo bar)', '(foo baz)'}} 574 | -- 575 | -- If you write your own handlers, or otherwise specify custom 576 | -- handling of options with @{on}, then whatever value those handlers 577 | -- return will be assigned to the respective keys in `opts`. 578 | -- @table opts 579 | 580 | 581 | --- Parse an argument list. 582 | -- @tparam table arglist list of arguments 583 | -- @tparam[opt] table defaults table of default option values 584 | -- @treturn table a list of unrecognised *arglist* elements 585 | -- @treturn opts parsing results 586 | local function parse(self, arglist, defaults) 587 | self.unrecognised, self.opts = {}, {} 588 | 589 | arglist = expandargs(self, arglist) 590 | 591 | local i = 1 592 | while i > 0 and i <= len(arglist) do 593 | local opt = arglist[i] 594 | 595 | if self[opt] == nil or match(opt, '^[^%-]') then 596 | insert(self.unrecognised, opt) 597 | i = i + 1 598 | 599 | -- Following non-'-' prefixed argument is an optarg. 600 | if i <= len(arglist) and match(arglist[i], '^[^%-]') then 601 | insert(self.unrecognised, arglist[i]) 602 | i = i + 1 603 | end 604 | 605 | -- Run option handler functions. 606 | else 607 | assert(iscallable(self[opt].handler)) 608 | 609 | i = self[opt].handler(self, arglist, i, self[opt].value) 610 | end 611 | end 612 | 613 | -- Merge defaults into user options. 614 | self.opts = merge(defaults or {}, self.opts) 615 | 616 | -- metatable allows `io.warn` to find `parser.program` when assigned 617 | -- back to _G.opts. 618 | return self.unrecognised, setmetatable(self.opts, {__index=self}) 619 | end 620 | 621 | 622 | --- Take care not to register duplicate handlers. 623 | -- @param current current handler value 624 | -- @param new new handler value 625 | -- @return `new` if `current` is nil 626 | local function set_handler(current, new) 627 | assert(current == nil, 'only one handler per option') 628 | return new 629 | end 630 | 631 | 632 | local function _init(self, spec) 633 | local parser = {} 634 | 635 | parser.versiontext, parser.version, parser.helptext, parser.program = 636 | match(spec, '^([^\n]-(%S+)\n.-)%s*([Uu]sage: (%S+).-)%s*$') 637 | 638 | if parser.versiontext == nil then 639 | error("OptionParser spec argument must match '\\n" .. 640 | "...Usage: ...'") 641 | end 642 | 643 | -- Collect helptext lines that begin with two or more spaces followed 644 | -- by a '-'. 645 | local specs = {} 646 | gsub(parser.helptext, '\n %s*(%-[^\n]+)', function(spec) 647 | insert(specs, spec) 648 | end) 649 | 650 | -- Register option handlers according to the help text. 651 | for _, spec in ipairs(specs) do 652 | -- append a trailing space separator to match %s in patterns below 653 | local options, spec, handler = {}, spec .. ' ' 654 | 655 | -- Loop around each '-' prefixed option on this line. 656 | while match(spec, '^%-[%-%w]') do 657 | 658 | -- Capture end of options processing marker. 659 | if match(spec, '^%-%-,?%s') then 660 | handler = set_handler(handler, finished) 661 | 662 | -- Capture optional argument in the option string. 663 | elseif match(spec, '^%-[%-%w]+=%[.+%],?%s') then 664 | handler = set_handler(handler, optional) 665 | 666 | -- Capture required argument in the option string. 667 | elseif match(spec, '^%-[%-%w]+=%S+,?%s') then 668 | handler = set_handler(handler, required) 669 | 670 | -- Capture any specially handled arguments. 671 | elseif match(spec, '^%-%-help,?%s') then 672 | handler = set_handler(handler, showhelp) 673 | 674 | elseif match(spec, '^%-%-version,?%s') then 675 | handler = set_handler(handler, showversion) 676 | end 677 | 678 | -- Consume argument spec, now that it was processed above. 679 | spec = gsub(spec, '^(%-[%-%w]+)=%S+%s', '%1 ') 680 | 681 | -- Consume short option. 682 | local _, c = gsub(spec, '^%-([-%w]),?%s+(.*)$', function(opt, rest) 683 | if opt == '-' then 684 | opt = '--' 685 | end 686 | insert(options, opt) 687 | spec = rest 688 | end) 689 | 690 | -- Be careful not to consume more than one option per iteration, 691 | -- otherwise we might miss a handler test at the next loop. 692 | if c == 0 then 693 | -- Consume long option. 694 | gsub(spec, '^%-%-([%-%w]+),?%s+(.*)$', function(opt, rest) 695 | insert(options, opt) 696 | spec = rest 697 | end) 698 | end 699 | end 700 | 701 | -- Unless specified otherwise, treat each option as a flag. 702 | on(parser, options, handler or flag) 703 | end 704 | 705 | return setmetatable(parser, getmetatable(self)) 706 | end 707 | 708 | 709 | --- Signature for initialising a custom OptionParser. 710 | -- 711 | -- Read the documented options from *spec* and return custom parser that 712 | -- can be used for parsing the options described in *spec* from a run-time 713 | -- argument list. Options in *spec* are recognised as lines that begin 714 | -- with at least two spaces, followed by a hyphen. 715 | -- @static 716 | -- @function OptionParser_Init 717 | -- @string spec option parsing specification 718 | -- @treturn OptionParser a parser for options described by *spec* 719 | -- @usage 720 | -- customparser = optparse(optparse_spec) 721 | 722 | 723 | return setmetatable({ 724 | --- Module table. 725 | -- @table optparse 726 | -- @string version release version identifier 727 | 728 | 729 | --- OptionParser prototype object. 730 | -- 731 | -- Most often, after instantiating an @{OptionParser}, everything else 732 | -- is handled automatically. 733 | -- 734 | -- Then, calling `parser:parse` as shown below saves unparsed arguments 735 | -- into `_G.arg` (usually filenames or similar), and `_G.opts` will be a 736 | -- table of successfully parsed option values. The keys into this table 737 | -- are the long-options with leading hyphens stripped, and non-word 738 | -- characters turned to `_`. For example if `--another-long` had been 739 | -- found in the initial `_G.arg`, then `_G.opts` will have a key named 740 | -- `another_long`, with an appropriate value. If there is no long 741 | -- option name, then the short option is used, i.e. `_G.opts.b` will be 742 | -- set. 743 | -- 744 | -- The values saved against those keys are controlled by the option 745 | -- handler, usually just `true` or the option argument string as 746 | -- appropriate. 747 | -- @object OptionParser 748 | -- @tparam OptionParser_Init _init initialisation function 749 | -- @string program the first word following 'Usage:' from *spec* 750 | -- @string version the last white-space delimited word on the first line 751 | -- of text from *spec* 752 | -- @string versiontext everything preceding 'Usage:' from *spec*, and 753 | -- which will be displayed by the @{showversion} @{on_handler} 754 | -- @string helptext everything including and following 'Usage:' from 755 | -- *spec* string and which will be displayed by the @{showhelp} 756 | -- @{on_handler} 757 | -- @usage 758 | -- local optparse = require 'optparse' 759 | -- 760 | -- local optparser = optparse [[ 761 | -- any text VERSION 762 | -- Additional lines of text to show when the --version 763 | -- option is passed. 764 | -- 765 | -- Several lines or paragraphs are permitted. 766 | -- 767 | -- Usage: PROGNAME 768 | -- 769 | -- Banner text. 770 | -- 771 | -- Optional long description text to show when the --help 772 | -- option is passed. 773 | -- 774 | -- Several lines or paragraphs of long description are permitted. 775 | -- 776 | -- Options: 777 | -- 778 | -- -b a short option with no long option 779 | -- --long a long option with no short option 780 | -- --another-long a long option with internal hypen 781 | -- --really-long-option-name 782 | -- with description on following line 783 | -- -v, --verbose a combined short and long option 784 | -- -n, --dryrun, --dry-run several spellings of the same option 785 | -- -u, --name=USER require an argument 786 | -- -o, --output=[FILE] accept an optional argument 787 | -- --version display version information, then exit 788 | -- --help display this help, then exit 789 | -- 790 | -- Footer text. Several lines or paragraphs are permitted. 791 | -- 792 | -- Please report bugs at bug-list@yourhost.com 793 | -- ]] 794 | -- 795 | -- -- Note that `std.io.die` and `std.io.warn` will only prefix messages 796 | -- -- with `parser.program` if the parser options are assigned back to 797 | -- -- `_G.opts`: 798 | -- _G.arg, _G.opts = optparser:parse(_G.arg) 799 | prototype = setmetatable({ 800 | -- Prototype initial values. 801 | opts = {}, 802 | helptext = '', 803 | program = '', 804 | versiontext = '', 805 | version = 0, 806 | }, { 807 | _type = 'OptionParser', 808 | 809 | __call = _init, 810 | 811 | --- @export 812 | __index = { 813 | boolean = boolean, 814 | file = file, 815 | finished = finished, 816 | flag = flag, 817 | help = showhelp, -- undocumented, for backwards compatibility 818 | optional = optional, 819 | required = required, 820 | showhelp = showhelp, 821 | showversion = showversion, 822 | 823 | on = on, 824 | opterr = opterr, 825 | parse = parse, 826 | }, 827 | }), 828 | }, { 829 | --- Metamethods 830 | -- @section Metamethods 831 | 832 | _type = 'Module', 833 | 834 | 835 | -- Pass through options to the OptionParser prototype. 836 | __call = function(self, ...) 837 | return self.prototype(...) 838 | end, 839 | 840 | 841 | --- Lazy loading of optparse submodules. 842 | -- Don't load everything on initial startup, wait until first attempt 843 | -- to access a submodule, and then load it on demand. 844 | -- @function __index 845 | -- @string name submodule name 846 | -- @treturn table|nil the submodule that was loaded to satisfy the missing 847 | -- `name`, otherwise `nil` if nothing was found 848 | -- @usage 849 | -- local optparse = require 'optparse' 850 | -- local version = optparse.version 851 | __index = function(self, name) 852 | local ok, t = pcall(require, 'optparse.' .. name) 853 | if ok then 854 | rawset(self, name, t) 855 | return t 856 | end 857 | end, 858 | }) 859 | --------------------------------------------------------------------------------