Command Line Module

lua-users home
wiki

This module is intended for processing command line parameters of Lua scripts. It doesn't try to be POSIX (or any other standard) compliant, since it is very Lua-oriented and allows to specify parameters of any Lua data type.

The module exports just one function: cmdline.getparam (its detailed description is given in the comment preceding the function body).

See also: GetOpt, AlternativeGetOpt and PosixGetOpt

cmdline.lua

-- started: 2008-04-12 by Shmuel Zeigerman
-- license: public domain

local ipairs,pairs,setfenv,tonumber,loadstring,type =
  ipairs,pairs,setfenv,tonumber,loadstring,type
local tinsert, tconcat = table.insert, table.concat
module(...)

local function commonerror (msg)
  return nil, ("[cmdline]: " .. msg)
end

local function argerror (msg, numarg)
  msg = msg and (": " .. msg) or ""
  return nil, ("[cmdline]: bad argument #" .. numarg .. msg)
end

local function iderror (numarg)
  return argerror("ID not valid", numarg)
end

local function idcheck (id)
  return id:match("^[%a_][%w_]*$") and true
end

--[[------------------------------------------------------------------------
Syntax:
  t_out = getparam(t_in [,options] [,params])

Parameters:
  t_in:   table - list of string arguments to be processed in order
          (usually it is the `arg' table created by the Lua interpreter).

     * if an argument begins with $, the $ is skipped and the rest is inserted
       into the array part of the output table.

     * if an argument begins with -, the rest is a sequence of variables
       (separated by commas or semicolons) that are all set to true;
            example: -var1,var2  --> var1,var2 = true,true

     * if an argument begins with !, the rest is a Lua chunk;
            example: !a=(40+3)*5;b=20;name="John";window={w=600,h=480}

     * if an argument contains =, then it is an assignment in the form
       var1,...=value (no space is allowed around the =)
         * if value begins with $, the $ is skipped, the rest is a string
                 example: var1,var2=$         --> var1,var2 = "",""
                 example: var1,var2=$125      --> var1,var2 = "125","125"
                 example: var1,var2=$$125     --> var1,var2 = "$125","$125"
         * if value is convertible to number, it is a number
                 example: var1,var2=125       --> var1,var2 = 125,125
         * otherwise it is a string
                 example: name=John           --> name = "John"

     * if an argument neither begins with one of the special characters (-,!,$),
       nor contains =, it is inserted as is into the array part of the output
       table.

  options (optional): a list of names of all command-line options and parameters
     permitted in the application; used to check that each found option
     is valid; no checks are done if not supplied.

  params (optional): a list of names of all command-line parameters required
     by the application; used to check that each required parameter is present;
     no checks are done if not supplied.

Returns:
  On success: the output table, e.g. { [1]="./myfile.txt", name="John", age=40 }
  On error: nil followed by error message string.

--]]------------------------------------------------------------------------
function getparam (t_in, options, params)
  local t_out = {}
  for i,v in ipairs(t_in) do
    local prefix, command = v:sub(1,1), v:sub(2)
    if prefix == "$" then
      tinsert(t_out, command)
    elseif prefix == "-" then
      for id in command:gmatch"[^,;]+" do
        if not idcheck(id) then return iderror(i) end
        t_out[id] = true
      end
    elseif prefix == "!" then
      local f, err = loadstring(command)
      if not f then return argerror(err, i) end
      setfenv(f, t_out)()
    elseif v:find("=") then
      local ids, val = v:match("^([^=]+)%=(.*)") -- no space around =
      if not ids then return argerror("invalid assignment syntax", i) end
      val = val:sub(1,1)=="$" and val:sub(2) or tonumber(val) or val
      for id in ids:gmatch"[^,;]+" do
        if not idcheck(id) then return iderror(i) end
        t_out[id] = val
      end
    else
      tinsert(t_out, v)
    end
  end
  if options then
    local lookup, unknown = {}, {}
    for _,v in ipairs(options) do lookup[v] = true end
    for k,_ in pairs(t_out) do
      if lookup[k]==nil and type(k)=="string" then tinsert(unknown, k) end
    end
    if #unknown > 0 then
      return commonerror("unknown options: " .. tconcat(unknown, ", "))
    end
  end
  if params then
    local missing = {}
    for _,v in ipairs(params) do
      if t_out[v]==nil then tinsert(missing, v) end
    end
    if #missing > 0 then
      return commonerror("missing parameters: " .. tconcat(missing, ", "))
    end
  end
  return t_out
end

test_cmdline.lua

require "cmdline"
local getparam = cmdline.getparam
local function assertgood(...) return assert(getparam(...)) end
local function assertbad(...) return assert(not getparam(...)) end

local t_in = {
  "var1,var1b=John",
  "var2,var2b=$Peter",
  "var3,var3b=$005",
  "var4,var4b=005",
  "!var5=8/2;var6=var5*40;var7=\"Ann\"",
  "!var8=nil",
  "!var9={} and 5 and 6",
  "-a,b2,c",
  "abcd",
  "/bin",
  "$-abc"
}

local function test1(t_out)
  assert (t_out.var1 == "John" and t_out.var1b == "John")
  assert (t_out.var2 == "Peter" and t_out.var2b == "Peter")
  assert (t_out.var3 == "005" and t_out.var3b == "005")
  assert (t_out.var4 == 5 and t_out.var4b == 5)
  assert (t_out.var5 == 4)
  assert (t_out.var6 == 160)
  assert (t_out.var7 == "Ann")
  assert (t_out.var8 == nil)
  assert (t_out.var9 == 6)
  assert (t_out.a==true and t_out.b2==true and t_out.c==true)
  assert (t_out[1] == "abcd")
  assert (t_out[2] == "/bin")
  assert (t_out[3] == "-abc")
end

local options = {
  "var1", "var1b", "var2", "var2b", "var3", "var3b", "var4", "var4b", "var5",
  "var6", "var7", "var8", "var9", "a", "b2", "c"
}

local t_out = assertgood(t_in) -- no options checking
test1(t_out)

local t_out = assertgood(t_in, options) -- options checking
test1(t_out)
assertbad(t_in, {}) -- no options permitted

local t_out = assertgood(t_in, nil, {3, "var1"}) -- parameters checking
test1(t_out)
assertbad(t_in, nil, {"var0"}) -- parameter missing

local t_bad = { -- bad syntax tests
  {"!abc"}, {"--88"}, {"-a,8,b"}, {"=8a=1"}
}
for _,v in ipairs(t_bad) do assertbad(v) end

print ("OK: test_cmdline")

RecentChanges · preferences
edit · history
Last edited December 12, 2009 12:40 am GMT (diff)