├── README.md ├── UNLICENSE ├── getopt.lua └── tests.lua /README.md: -------------------------------------------------------------------------------- 1 | # `getopt(3)`-like option parsing for Lua 2 | 3 | Simple, conventional argument parsing with a friendly license. For Lua 4 | 5.1 and later. See the source file header for full documentation. 5 | 6 | ## Example usage 7 | 8 | ```lua 9 | getopt = require('getopt') 10 | 11 | local append = false 12 | local binary = false 13 | local color = 'white' 14 | local nonoptions = {} 15 | local infile = io.input() 16 | 17 | for opt, arg in getopt(arg, 'abc:h', nonoptions) do 18 | if opt == 'a' then 19 | append = true 20 | elseif opt == 'b' then 21 | binary = true 22 | elseif opt == 'c' then 23 | color = arg 24 | elseif opt == 'h' then 25 | usage() 26 | os.exit(0) 27 | elseif opt == '?' then 28 | print('error: unknown option: ' .. arg) 29 | os.exit(1) 30 | elseif opt == ':' then 31 | print('error: missing argument: ' .. arg) 32 | os.exit(1) 33 | end 34 | end 35 | 36 | if #nonoptions == 1 then 37 | infile = io.open(nonoptions[1], 'r') 38 | elseif #nonoptions > 1 then 39 | print('error: wrong number of arguments: ' .. #nonoptions) 40 | os.exit(1) 41 | end 42 | 43 | -- ... 44 | ``` 45 | 46 | ## Run the tests 47 | 48 | $ lua tests.lua 49 | -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /getopt.lua: -------------------------------------------------------------------------------- 1 | --- getopt(3)-like functionality for Lua 5.1 and later 2 | -- This is free and unencumbered software released into the public domain. 3 | 4 | --- getopt(argv, optstring [, nonoptions]) 5 | -- 6 | -- Returns a closure suitable for "for ... in" loops. On each call the 7 | -- closure returns the next (option, optarg). For unknown options, it 8 | -- returns ('?', option). When a required optarg is missing, it returns 9 | -- (':', option). It's reasonable to continue parsing after errors. 10 | -- Returns nil when done. 11 | -- 12 | -- The optstring follows the same format as POSIX getopt(3). However, 13 | -- this function will never print output on its own. 14 | -- 15 | -- Non-option arguments are accumulated, in order, in the optional 16 | -- "nonoptions" table. If a "--" argument is encountered, appends the 17 | -- remaining arguments to the nonoptions table and returns nil. 18 | -- 19 | -- The input argv table is left unmodified. 20 | local function getopt(argv, optstring, nonoptions) 21 | local optind = 1 22 | local optpos = 2 23 | nonoptions = nonoptions or {} 24 | return function() 25 | while true do 26 | local arg = argv[optind] 27 | if arg == nil then 28 | return nil 29 | elseif arg == '--' then 30 | for i = optind + 1, #argv do 31 | table.insert(nonoptions, argv[i]) 32 | end 33 | return nil 34 | elseif arg:sub(1, 1) == '-' then 35 | local opt = arg:sub(optpos, optpos) 36 | local start, stop = optstring:find(opt .. ':?') 37 | if not start then 38 | optind = optind + 1 39 | optpos = 2 40 | return '?', opt 41 | elseif stop > start and #arg > optpos then 42 | local optarg = arg:sub(optpos + 1) 43 | optind = optind + 1 44 | optpos = 2 45 | return opt, optarg 46 | elseif stop > start then 47 | local optarg = argv[optind + 1] 48 | optind = optind + 2 49 | optpos = 2 50 | if optarg == nil then 51 | return ':', opt 52 | end 53 | return opt, optarg 54 | else 55 | optpos = optpos + 1 56 | if optpos > #arg then 57 | optind = optind + 1 58 | optpos = 2 59 | end 60 | return opt, nil 61 | end 62 | else 63 | optind = optind + 1 64 | table.insert(nonoptions, arg) 65 | end 66 | end 67 | end 68 | end 69 | 70 | return getopt 71 | -------------------------------------------------------------------------------- /tests.lua: -------------------------------------------------------------------------------- 1 | getopt = require('getopt') 2 | 3 | --- Test Harness 4 | 5 | function fail(...) 6 | print('\027[91;1mFAIL\027[0m', ...) 7 | return false 8 | end 9 | 10 | function pass(...) 11 | print('\027[92;1mPASS\027[0m', ...) 12 | return true 13 | end 14 | 15 | function check(name, argv, optstring, expect) 16 | local actual = {} 17 | local nonoptions = {} 18 | for opt, arg in getopt(argv, optstring, nonoptions) do 19 | table.insert(actual, {opt, arg}) 20 | end 21 | if #expect ~= #actual then 22 | return fail(name, 'differing option lengths') 23 | elseif #nonoptions ~= #expect.nonoptions then 24 | return fail(name, 'differing nonoption lengths') 25 | else 26 | for i = 1, #expect do 27 | local e = expect[i] 28 | local a = actual[i] 29 | if e[1] ~= a[1] or e[2] ~= a[2] then 30 | return fail(name, 'option mismatch ' .. i) 31 | end 32 | end 33 | for i = 1, #nonoptions do 34 | if expect.nonoptions[i] ~= nonoptions[i] then 35 | return fail(name, 'nonoption mismatch ' .. i) 36 | end 37 | end 38 | end 39 | return pass(name) 40 | end 41 | 42 | --- Tests 43 | 44 | check('basic', {'-a', 'foo', '-b', '-c', 'bar'}, 'abc', { 45 | {'a', nil}, 46 | {'b', nil}, 47 | {'c', nil}, 48 | nonoptions = {'foo', 'bar'} 49 | }) 50 | 51 | check('optarg', {'-a', '-bfoo', 'bar', '-c', '-b', 'baz'}, 'ab:c', { 52 | {'a', nil}, 53 | {'b', 'foo'}, 54 | {'c', nil}, 55 | {'b', 'baz'}, 56 | nonoptions = {'bar'} 57 | }) 58 | 59 | check('validate', {'-x', '-b', '-x', 'extra', '-b'}, 'ab:c', { 60 | {'?', 'x'}, 61 | {'b', '-x'}, 62 | {':', 'b'}, 63 | nonoptions = {'extra'} 64 | }) 65 | 66 | check('group', {'-abc', '-cba', '-abxc'}, 'abcx:', { 67 | {'a', nil}, 68 | {'b', nil}, 69 | {'c', nil}, 70 | {'c', nil}, 71 | {'b', nil}, 72 | {'a', nil}, 73 | {'a', nil}, 74 | {'b', nil}, 75 | {'x', 'c'}, 76 | nonoptions = {} 77 | }) 78 | 79 | check('no-options', {'foo', 'bar', 'baz'}, 'abcdef', { 80 | nonoptions = {'foo', 'bar', 'baz'} 81 | }) 82 | 83 | check('empty-args', {'', '-a', '', ''}, 'a:', { 84 | {'a', ''}, 85 | nonoptions = {'', ''} 86 | }) 87 | 88 | check('dash-dash', {'-a', '-b', '--', '-c', '--', '-a', '-x'}, 'ab:c', { 89 | {'a', nil}, 90 | {'b', '--'}, 91 | {'c', nil}, 92 | nonoptions = {'-a', '-x'} 93 | }) 94 | --------------------------------------------------------------------------------