Add chatcmdbuilder

master
rubenwardy 2017-11-07 22:50:49 +00:00
parent 730f0906de
commit 3f552c3ef8
6 changed files with 426 additions and 0 deletions

80
chatcmdbuilder/.gitignore vendored Normal file
View File

@ -0,0 +1,80 @@
# Created by https://www.gitignore.io/api/lin,linux,windows,lua
#!! ERROR: lin is undefined. Use list command to see defined gitignore types !!#
### Linux ###
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
### Windows ###
# Windows image file caches
Thumbs.db
ehthumbs.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msm
*.msp
# Windows shortcuts
*.lnk
### Lua ###
# Compiled Lua sources
luac.out
# luarocks build files
*.src.rock
*.zip
*.tar.gz
# Object files
*.o
*.os
*.ko
*.obj
*.elf
# Precompiled Headers
*.gch
*.pch
# Libraries
*.lib
*.a
*.la
*.lo
*.def
*.exp
# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib
# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex

19
chatcmdbuilder/LICENSE Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) 2016-17 rubenwardy
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

89
chatcmdbuilder/README.md Normal file
View File

@ -0,0 +1,89 @@
# ChatCmdBuilder
Easily create complex chat commands with no regex.
Created by rubenwardy
License: MIT
# Usage
## Registering Chat Commands
`ChatCmdBuilder.new(name, setup)` registers a new chat command called `name`.
Setup is called immediately after calling `new` to initialise subcommands.
You can set values in the chat command definition by using def:
`ChatCmdBuilder.new(name, setup, def)`.
Here is an example:
```Lua
ChatCmdBuilder.new("admin", function(cmd)
cmd:sub("kill :target", function(name, target)
local player = minetest.get_player_by_name(target)
if player then
player:set_hp(0)
return true, "Killed " .. target
else
return false, "Unable to find " .. target
end
end)
cmd:sub("move :target to :pos:pos", function(name, target, pos)
local player = minetest.get_player_by_name(target)
if player then
player:setpos(pos)
return true, "Moved " .. target .. " to " .. minetest.pos_to_string(pos)
else
return false, "Unable to find " .. target
end
end)
end, {
description = "Admin tools",
privs = {
kick = true,
ban = true
}
})
```
A player could then do `/admin kill player1` to kill player1,
or `/admin move player1 to 0,0,0` to teleport a user.
## Introduction to Routing
A route is a string. Let's look at `move :target to :pos:pos`:
* `move` and `to` are constants. They need to be there in order to match.
* `:target` and `:pos:pos` are parameters. They're passed to the function.
* The second `pos` in `:pos:pos` after `:` is the param type. `:target` has an implicit
type of `word`.
## Param Types
* `word` - default. Any string without spaces.
* `number` - Any number, including decimals
* `int` - Any integer, no decimals
* `text` - Any string
* `pos` - 1,2,3 or 1.1,2,3.4567 or (1,2,3) or 1.2, 2 ,3.2
## Build chat command function
If you don't want to register the chatcommand at this point, you can just generate
a function using `ChatCmdBuilder.build`.
For example, this is the full definition of ChatCmdBuilder.new:
```Lua
function ChatCmdBuilder.new(name, func, def)
def = def or {}
def.func = ChatCmdBuilder.build(name, func)
minetest.register_chatcommand(name, def)
end
```
## Run tests
```Bash
sudo apt-get install luajit
luajit init.lua
```

View File

@ -0,0 +1 @@
A library to make registering chat commands easier

230
chatcmdbuilder/init.lua Normal file
View File

@ -0,0 +1,230 @@
ChatCmdBuilder = {}
function ChatCmdBuilder.new(name, func, def)
def = def or {}
local cmd = ChatCmdBuilder.build(func)
cmd.def = def
def.func = cmd.run
minetest.register_chatcommand(name, def)
return cmd
end
local STATE_READY = 1
local STATE_PARAM = 2
local STATE_PARAM_TYPE = 3
local bad_chars = {}
bad_chars["("] = true
bad_chars[")"] = true
bad_chars["."] = true
bad_chars["%"] = true
bad_chars["+"] = true
bad_chars["-"] = true
bad_chars["*"] = true
bad_chars["?"] = true
bad_chars["["] = true
bad_chars["^"] = true
bad_chars["$"] = true
local function escape(char)
if bad_chars[char] then
return "%" .. char
else
return char
end
end
function ChatCmdBuilder.build(func)
local cmd = {
_subs = {}
}
function cmd:sub(route, func, def)
print("Parsing " .. route)
def = def or {}
if string.trim then
route = string.trim(route)
end
local sub = {
pattern = "^",
params = {},
func = func
}
-- End of param reached: add it to the pattern
local param = ""
local param_type = ""
local should_be_eos = false
local function finishParam()
if param ~= "" and param_type ~= "" then
print(" - Found param " .. param .. " type " .. param_type)
if param_type == "pos" then
sub.pattern = sub.pattern .. "%(? *(%-?[%d.]+) *, *(%-?[%d.]+) *, *(%-?[%d.]+) *%)?"
elseif param_type == "text" then
sub.pattern = sub.pattern .. "(*+)"
should_be_eos = true
elseif param_type == "number" then
sub.pattern = sub.pattern .. "([%d.]+)"
elseif param_type == "int" then
sub.pattern = sub.pattern .. "([%d]+)"
else
if param_type ~= "word" then
print("Unrecognised param_type=" .. param_type .. ", using 'word' type instead")
param_type = "word"
end
sub.pattern = sub.pattern .. "([^ ]+)"
end
table.insert(sub.params, param_type)
param = ""
param_type = ""
end
end
-- Iterate through the route to find params
local state = STATE_READY
for i = 1, #route do
local c = route:sub(i, i)
if should_be_eos then
error("Should be end of string. Nothing is allowed after a param of type text.")
end
if state == STATE_READY then
if c == ":" then
print(" - Found :, entering param")
state = STATE_PARAM
param_type = "word"
else
sub.pattern = sub.pattern .. escape(c)
end
elseif state == STATE_PARAM then
if c == ":" then
print(" - Found :, entering param type")
state = STATE_PARAM_TYPE
param_type = ""
elseif c:match("%W") then
print(" - Found nonalphanum, leaving param")
state = STATE_READY
finishParam()
sub.pattern = sub.pattern .. escape(c)
else
param = param .. c
end
elseif state == STATE_PARAM_TYPE then
if c:match("%W") then
print(" - Found nonalphanum, leaving param type")
state = STATE_READY
finishParam()
sub.pattern = sub.pattern .. escape(c)
else
param_type = param_type .. c
end
end
end
print(" - End of route")
finishParam()
sub.pattern = sub.pattern .. "$"
print("Pattern: " .. sub.pattern)
table.insert(self._subs, sub)
end
if func then
func(cmd)
end
cmd.run = function(name, param)
for i = 1, #cmd._subs do
local sub = cmd._subs[i]
local res = { string.match(param, sub.pattern) }
if #res > 0 then
local pointer = 1
local params = { name }
for j = 1, #sub.params do
local param = sub.params[j]
if param == "pos" then
local pos = {
x = tonumber(res[pointer]),
y = tonumber(res[pointer + 1]),
z = tonumber(res[pointer + 2])
}
table.insert(params, pos)
pointer = pointer + 3
elseif param == "number" or param == "int" then
table.insert(params, tonumber(res[pointer]))
pointer = pointer + 1
else
table.insert(params, res[pointer])
pointer = pointer + 1
end
end
return sub.func(unpack(params))
end
end
print("No matches")
end
return cmd
end
local function run_tests()
if not (ChatCmdBuilder.build(function(cmd)
cmd:sub("bar :one and :two:word", function(name, one, two)
if name == "singleplayer" and one == "abc" and two == "def" then
return true
end
end)
end))("singleplayer", "bar abc and def") then
error("Test 1 failed")
end
local move = ChatCmdBuilder.build(function(cmd)
cmd:sub("move :target to :pos:pos", function(name, target, pos)
if name == "singleplayer" and target == "player1" and
pos.x == 0 and pos.y == 1 and pos.z == 2 then
return true
end
end)
end)
if not move("singleplayer", "move player1 to 0,1,2") then
error("Test 2 failed")
end
if not move("singleplayer", "move player1 to (0,1,2)") then
error("Test 3 failed")
end
if not move("singleplayer", "move player1 to 0, 1,2") then
error("Test 4 failed")
end
if not move("singleplayer", "move player1 to 0 ,1, 2") then
error("Test 5 failed")
end
if not move("singleplayer", "move player1 to 0, 1, 2") then
error("Test 6 failed")
end
if not move("singleplayer", "move player1 to 0 ,1 ,2") then
error("Test 7 failed")
end
if not move("singleplayer", "move player1 to ( 0 ,1 ,2)") then
error("Test 7 failed")
end
if move("singleplayer", "move player1 to abc,def,sdosd") then
error("Test 8 failed")
end
if move("singleplayer", "move player1 to abc def sdosd") then
error("Test 8 failed")
end
if not (ChatCmdBuilder.build(function(cmd)
cmd:sub("does :one:int plus :two:int equal :three:int", function(name, one, two, three)
if name == "singleplayer" and one + two == three then
return true
end
end)
end))("singleplayer", "does 1 plus 2 equal 3") then
error("Test 9 failed")
end
end
if not minetest then
run_tests()
end

7
chatcmdbuilder/mod.conf Normal file
View File

@ -0,0 +1,7 @@
name = lib_chatcmdbuilder
title = Chat Command Builder
author = rubenwardy
description = A library to make registering chat commands easier
license = MIT
forum = https://forum.minetest.net/viewtopic.php?t=14899
version = 0.1.0