summaryrefslogtreecommitdiffstats
path: root/include/dnsjit/lib/getopt.lua
diff options
context:
space:
mode:
Diffstat (limited to 'include/dnsjit/lib/getopt.lua')
-rw-r--r--include/dnsjit/lib/getopt.lua368
1 files changed, 368 insertions, 0 deletions
diff --git a/include/dnsjit/lib/getopt.lua b/include/dnsjit/lib/getopt.lua
new file mode 100644
index 0000000..ede3e12
--- /dev/null
+++ b/include/dnsjit/lib/getopt.lua
@@ -0,0 +1,368 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit is distributed in the hope that it will be useful,
+-- but WITHOUT ANY WARRANTY; without even the implied warranty of
+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+-- GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License
+-- along with dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.lib.getopt
+-- Parse and handle arguments
+-- local getopt = require("dnsjit.lib.getopt").new({
+-- { "v", "verbose", 0, "Enable verbosity", "?+" },
+-- { nil, "host", "localhost", "Set host", "?" },
+-- { "p", nil, 53, "Set port", "?" },
+-- })
+-- .
+-- local left = getopt:parse()
+-- .
+-- print("host", getopt:val("host"))
+-- print("port", getopt:val("p"))
+--
+-- A "getopt long" implementation to easily handle command line arguments
+-- and display usage.
+-- An option is the short name (one character), long name,
+-- default value (which also defines the type), help text and extensions.
+-- Options are by default required, see extensions to change this.
+-- .LP
+-- The Lua types allowed are
+-- .BR boolean ,
+-- .BR string ,
+-- .BR number .
+-- .LP
+-- The extensions available are:
+-- .TP
+-- .B ?
+-- Make the option optional.
+-- .TP
+-- .B *
+-- For string and number options this make it possible to specified it
+-- multiple times and all values will be returned in a table.
+-- .TP
+-- .B +
+-- For number options this will act as an counter increaser, the value will
+-- be the default value + 1 for each time the option is given.
+-- .LP
+-- Option
+-- .I -h
+-- and
+-- .I --help
+-- are automatically added if the option
+-- .I --help
+-- is not already defined.
+-- .SS Attributes
+-- .TP
+-- left
+-- A table that contains the arguments left after parsing, same as returned by
+-- .IR parse() .
+-- .TP
+-- usage_desc
+-- A string that describes the usage of the program, if not set then the
+-- default will be "
+-- .I "program [options...]"
+-- ".
+module(...,package.seeall)
+
+local log = require("dnsjit.core.log")
+
+local module_log = log.new("lib.getopt")
+Getopt = {}
+
+-- Create a new Getopt object.
+-- .I args
+-- is a table with tables that specifies the options available.
+-- Each entry is unpacked and sent to
+-- .BR Getopt:add() .
+function Getopt.new(args)
+ local self = setmetatable({
+ left = {},
+ usage_desc = nil,
+ _opt = {},
+ _s2l = {},
+ _log = log.new("lib.getopt", module_log),
+ }, { __index = Getopt })
+
+ self._log:debug("new()")
+
+ for k, v in pairs(args) do
+ local short, long, default, help, extensions = unpack(v)
+ self:add(short, long, default, help, extensions)
+ end
+
+ return self
+end
+
+-- Return the Log object to control logging of this instance or module.
+function Getopt:log()
+ if self == nil then
+ return module_log
+ end
+ return self._log
+end
+
+-- Add an option.
+function Getopt:add(short, long, default, help, extensions)
+ local optional = false
+ local multiple = false
+ local counter = false
+ local name = long or short
+
+ if type(name) ~= "string" then
+ error("long|short) need to be a string")
+ elseif name == "" then
+ error("name (long|short) needs to be set")
+ end
+ if short and (type(short) ~= "string" or #short ~= 1) then
+ error("short needs to be a string of length 1")
+ end
+
+ if self._opt[name] then
+ error("option "..name.." alredy exists")
+ elseif short and self._s2l[short] then
+ error("option "..short.." alredy exists")
+ end
+
+ local t = type(default)
+ if t ~= "string" and t ~= "number" and t ~= "boolean" then
+ error("option "..name..": invalid type "..t)
+ end
+
+ if type(extensions) == "string" then
+ local n
+ for n = 1, extensions:len() do
+ local extension = extensions:sub(n, n)
+ if extension == "?" then
+ optional = true
+ elseif extension == "*" then
+ multiple = true
+ elseif extension == "+" then
+ counter = true
+ else
+ error("option "..name..": invalid extension "..extension)
+ end
+ end
+ end
+
+ self._opt[name] = {
+ value = nil,
+ short = short,
+ long = long,
+ type = t,
+ default = default,
+ help = help,
+ optional = optional,
+ multiple = multiple,
+ counter = counter,
+ }
+ if long and short then
+ self._s2l[short] = long
+ elseif short and not long then
+ self._s2l[short] = short
+ end
+
+ if not self._opt["help"] then
+ self._opt["help"] = {
+ short = nil,
+ long = "help",
+ type = "boolean",
+ default = false,
+ help = "Display this help text",
+ optional = true,
+ }
+ if not self._s2l["h"] then
+ self._opt["help"].short = "h"
+ self._s2l["h"] = "help"
+ end
+ end
+end
+
+-- Print the usage.
+function Getopt:usage()
+ if self.usage_desc then
+ print("usage: " .. self.usage_desc)
+ else
+ print("usage: program [options...]")
+ end
+
+ local opts = {}
+ for k, _ in pairs(self._opt) do
+ if k ~= "help" then
+ table.insert(opts, k)
+ end
+ end
+ table.sort(opts)
+ table.insert(opts, "help")
+
+ for _, k in pairs(opts) do
+ local v = self._opt[k]
+ local arg
+ if v.type == "string" then
+ arg = " \""..v.default.."\""
+ elseif v.type == "number" and v.counter == false then
+ arg = " "..v.default
+ else
+ arg = ""
+ end
+ if v.long then
+ print("", (v.short and "-"..v.short or " ").." --"..v.long..arg, v.help)
+ else
+ print("", "-"..v.short..arg, v.help)
+ end
+ end
+end
+
+-- Parse the options.
+-- If
+-- .I args
+-- is not specified or nil then the global
+-- .B arg
+-- is used.
+-- If
+-- .I startn
+-- is given, it will start parsing arguments in the table from that position.
+-- The default position to start at is 2 for
+-- .IR dnsjit ,
+-- see
+-- .BR dnsjit.core (3).
+function Getopt:parse(args, startn)
+ if not args then
+ args = arg
+ end
+
+ local n
+ local opt = nil
+ local left = {}
+ local need_arg = false
+ local stop = false
+ local name
+ for n = startn or 2, table.maxn(args) do
+ if need_arg then
+ if opt.multiple then
+ if opt.value == nil then
+ opt.value = {}
+ end
+ if opt.type == "number" then
+ table.insert(opt.value, tonumber(args[n]))
+ else
+ table.insert(opt.value, args[n])
+ end
+ else
+ if opt.type == "number" then
+ opt.value = tonumber(args[n])
+ else
+ opt.value = args[n]
+ end
+ end
+ need_arg = false
+ elseif stop or args[n] == "-" then
+ table.insert(left, args[n])
+ elseif args[n] == "--" then
+ stop = true
+ elseif args[n]:sub(1, 1) == "-" then
+ if args[n]:sub(1, 2) == "--" then
+ name = args[n]:sub(3)
+ else
+ name = args[n]:sub(2)
+ if name:len() > 1 then
+ local n2, name2
+ for n2 = 1, name:len() - 1 do
+ name2 = name:sub(n2, n2)
+ opt = self._opt[self._s2l[name2]]
+ if not opt then
+ error("unknown option "..name2)
+ end
+ if opt.type == "number" and opt.counter then
+ if opt.value == nil then
+ opt.value = opt.default
+ end
+ opt.value = opt.value + 1
+ elseif opt.type == "boolean" then
+ if opt.value == nil then
+ opt.value = opt.default
+ end
+ if opt.value then
+ opt.value = false
+ else
+ opt.value = true
+ end
+ else
+ error("invalid short option '"..name2.."' in multioption statement")
+ end
+ end
+ name = name:sub(-1)
+ end
+ end
+ if self._s2l[name] then
+ name = self._s2l[name]
+ end
+ if not self._opt[name] then
+ error("unknown option "..name)
+ end
+ opt = self._opt[name]
+ if opt.type == "string" then
+ need_arg = true
+ elseif opt.type == "number" then
+ if opt.counter then
+ if opt.value == nil then
+ opt.value = opt.default
+ end
+ opt.value = opt.value + 1
+ else
+ need_arg = true
+ end
+ elseif opt.type == "boolean" then
+ if opt.value == nil then
+ opt.value = opt.default
+ end
+ if opt.value then
+ opt.value = false
+ else
+ opt.value = true
+ end
+ else
+ error("internal error, invalid option type "..opt.type)
+ end
+ else
+ table.insert(left, args[n])
+ end
+ end
+
+ if need_arg then
+ error("option "..name.." needs argument")
+ end
+
+ for k, v in pairs(self._opt) do
+ if v.optional == false and v.value == nil then
+ error("missing required option "..k.."")
+ end
+ end
+
+ self.left = left
+ return left
+end
+
+-- Return the value of an option.
+function Getopt:val(name)
+ local opt = self._opt[name] or self._opt[self._s2l[name]]
+ if not opt then
+ return
+ end
+ if opt.value == nil then
+ return opt.default
+ else
+ return opt.value
+ end
+end
+
+-- dnsjit.core (3)
+return Getopt