RandName Newbie cheater Reputation: 0
Joined: 19 Jun 2015 Posts: 22
|
Posted: Fri Apr 08, 2016 5:08 pm Post subject: Custom Command Line Parser |
|
|
Hey,
I tried myself at writing a custom command line parser in Lua.
Putting the ui aside, there were a couple difficulties I encountered.
I have finally decided to choose a class based command system.
You will see in the code what I mean with that
The advantages of using classes is, that the command structure is more flexible and also more organized.
Here is the example code:
Code: |
-- some globals
local script = {}
script.name = "Script"
script.ceversion = 6.5
script.state = false
-- ui stuff
function script.toggleCE(sender)
if not script.state and not script.debugging then
hideAllCEWindows()
script.state = true
elseif script.state then
ui = nil
script.state = false
print = _print
object_destroy(sender)
unhideMainCEwindow()
else
script.state = true
end
end
script.toggleCE()
-- Creating basic ui elements
local ui = {}
ui.form = createForm(true)
ui.output = createMemo(ui.form)
ui.input = createEdit(ui.form)
-- Defining the properties
-- -- form
form_centerScreen(ui.form)
form_onClose(ui.form, script.toggleCE)
setProperty(ui.form, "BiDiMode", "bdLeftToRight")
setProperty(ui.form, "Caption", script.name)
ui.form.setSize(450, 350)
-- -- output
ui.output.setWordWrap(false)
ui.output.setScrollbars(6)
setProperty(ui.output, "BorderStyle", "bsSingle")
ui.output.setPosition(5, 5)
local xp, yp = ui.output.getPosition()
local formxs, formys = ui.form.getSize()
ui.output.setSize(formxs - xp * 2, formys * 0.9)
-- -- input
ui.input.OnKeyPress = function (sender, key)
local vk = string.byte(key)
if (vk == VK_RETURN) then -- send input to pInput() function
if pInput(control_getCaption(sender)) then
sender.clear()
end
elseif (vk == 35) then -- (#) enable lua mode (bypass pInput)
if not script.luamode then script.luamode = true else script.luamode = false end
return
elseif not script.luamode then
if (vk == 60) then -- (<) going one back in inputHistory
if script.inputHistory then
if not script.inputIndex then script.inputIndex = #script.inputHistory else if script.inputIndex > 1 then script.inputIndex = script.inputIndex - 1 end end
control_setCaption(sender, script.inputHistory[script.inputIndex])
doKeyPress(35)
return
end
elseif (vk == 62) then -- (>) going one forward in inputHistory
if script.inputHistory then
if not script.inputIndex then script.inputIndex = #script.inputHistory else if script.inputIndex < #script.inputHistory then script.inputIndex = script.inputIndex + 1 end end
control_setCaption(sender, script.inputHistory[script.inputIndex])
doKeyPress(35)
return
end
end
end
return key
end
local xs, ys = ui.output.getSize()
setProperty(ui.input, "BorderStyle", "bsSingle")
ui.input.setPosition(xp, ys + 2 * yp - 1)
ui.input.setSize(xs, 0)
-- custom print
-- send output to new ui
if not _print then _print = print end -- backup of original print function
if not format then format = string.format end
if not fprint then fprint = function (str, ...) print(format(str, ...)) end end
function print(...)
local function split(s, delimiter)
local result = {};
for match in (s..delimiter):gmatch("(.-)["..delimiter.."]") do
table.insert(result, match);
end
return result;
end
local strTable = split(..., '\n')
for i = 1,#strTable do
local str = strTable[i]
memo_append(ui.output, str)
if script.debugging then _print(str) end
end
end
-- command input handler
-- _ = input from user
-- returns -> true, if input was accepted, else false
function pInput(_)
if _:find("[.].+") == 1 then assert(loadstring(_:sub(2)))() return true end -- direct lua escape
if not script.ready then print("error, commands are not available until the script is done with initializing!") return false end -- waiting for init to end
if not script.inputHistory then script.inputHistory = {} script.inputHistory[1] = _ else script.inputHistory[#script.inputHistory + 1] = _ end -- adding entry to inputHistory
script.inputIndex = #script.inputHistory + 1 -- increasing inputIndex
local input = {}
for str in _:gmatch('([^,]+)') do -- seperating input by [, ]
str = str:gsub("^%s+", "")
input[#input + 1] = str
end
if not cmdTable then print("ERROR: No commands loaded!") return false end
--[[
if cmdTable['alias'].state then -- if alias function is at work
return aliasFunc(_)
end
--]]
local ret = false
for i,v in ipairs(input) do
local inlist = {}
for str in v:gmatch('([^%s]+)') do -- seperating input by spaces
inlist[#inlist + 1] = str
end
print('> ' .. v)
local result, argval = cmdTable:getFunc(inlist)
if result then result:func(argval) ret = true else print("error, [".. i .."] unknown command!") end
end
return ret
end
-- file handling
function script.rf(f, mode) local r = io.open(f, mode or 'rb') local ret = r:read('*all') r:close() return ret end
function script.wf(f, mode, str) local r = io.open(f, mode or 'w+') r:write(str) r:close() end
-- check CE version => has to be 6.5
if getCEVersion() == script.ceversion then
else
showMessage('Your CE version ('.. getCEVersion() ..') is incompatible with this script.\nPlease install CE ' .. script.ceversion)
print('Your CE version ('.. getCEVersion() ..') is incompatible with this script.\nPlease install CE ' .. script.ceversion .. '!')
end
function class(a,b)local c={}if not b and type(a)=='function'then b=a;a=nil elseif type(a)=='table'then for d,e in pairs(a)do c[d]=e end;c._base=a end;c.__index=c;local f={}f.__call=function(g,...)local h={}setmetatable(h,c)if b then b(h,...)else if a and a.init then a.init(h,...)end end;return h end;c.init=b;c.is_a=function(self,i)local j=getmetatable(self)while j do if j==i then return true end;j=j._base end;return false end;setmetatable(c,f)return c end
Command = class(function(cmd, name, kind, ...)
cmd[1] = name
cmd[2] = kind
local args = table.pack(...)
for i,v in ipairs(args) do
if type(v) == 'table' then
cmd[#cmd + 1] = Command(unpack(v))
else
cmd.func = v
end
end
end)
CList = class(function(cl) end)
function CList.insert(cl, cmd)
if type(cmd) == 'table' and getmetatable(cmd) == Command then
cl[#cl + 1] = cmd
end
end
function CList.getFunc( cl, tbl )
local input = type(tbl) == 'table' and tbl or nil
if input then
local retTbl = nil
for i,v in ipairs(input) do
if type(tonumber(v)) ~= 'number' then
retTbl = cl:getTableByName(v, retTbl)
if not retTbl then return nil end
else
return retTbl, v
end
end
return retTbl
else return nil end
end
function CList.getTableByName(cl, name, tbl)
if not tbl then
for i,v in ipairs(cl) do
if type(v) == 'table' then
if v[1] == name then
return cl[i]
end
end
end
return nil
else
for i,v in ipairs(tbl) do
if v[1] == name then
return tbl[i]
end
end
return nil
end
end
-- example command
cmdTable = CList()
cmdTable:insert(Command(
'foo', -- name
'cmd', -- kind (not used yet)
-- following args are either subcommands or a execution function
{
'bar', -- name
'subcmd', -- kind
function(self) print('foo.bar()') end, -- execution function
{
'bor',
'subcmd',
function(self) print('foo.bar.bor()') end,
},
{
'val',
'argcmd',
function(self, val) print('prev val: ' .. tostring(self.val)) self.val = val print('val: ' .. tostring(val)) end, -- note that 'val' is passed here
}
},
{
'other',
'subcmd';
function() print('foo.other()') end,
{
'toggle',
'togcmd',
function(self) print('prev state: ' .. tostring(self.state)) self.state = not self.state print('state: ' .. tostring(self.state)) end,
}
}))
script.ready = true
|
Commands are defined this way:
Code: |
-- example command
cmdTable = CList()
cmdTable:insert(Command(
'foo', -- name
'cmd', -- kind (not used yet)
-- following args are either subcommands or a execution function
{
'bar', -- name
'subcmd', -- kind
function(self) print('foo.bar()') end, -- execution function
{
'bor',
'subcmd',
function(self) print('foo.bar.bor()') end,
},
{
'val',
'argcmd',
function(self, val) print('prev val: ' .. tostring(self.val)) self.val = val print('val: ' .. tostring(val)) end, -- note that 'val' is passed here
}
},
{
'other',
'subcmd';
function() print('foo.other()') end,
{
'toggle',
'togcmd',
function(self) print('prev state: ' .. tostring(self.state)) self.state = not self.state print('state: ' .. tostring(self.state)) end,
}
}))
|
Like you can see, you can have nested commands with as much sub-commands as you like.
Here is a test session:
Code: |
> foo bar
foo.bar()
> foo bar bor
foo.bar.bor()
> foo bar val 10
prev val: nil
val: 10
> foo bar val 2
prev val: 10
val: 2
> foo other
foo.other()
> foo other toggle
prev state: nil
state: true
> foo other toggle
prev state: true
state: false
|
(Note that the input is written with a '>' character before.)
Like you can see, passing of arguments to the sub-commands is possible too.
UPDATE:
I have modified the code to support toggle values (see above).
And also added the support for multi commands in one line (separated through commas).
I hope that this can help or inspire you in some way or another.
I appreciate any kind of feedback and suggestions for improvements!
Greetings,
RandName/Randshot
|
|