Add chatcmdbuilder
This commit is contained in:
parent
730f0906de
commit
3f552c3ef8
80
chatcmdbuilder/.gitignore
vendored
Normal file
80
chatcmdbuilder/.gitignore
vendored
Normal 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
19
chatcmdbuilder/LICENSE
Normal 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
89
chatcmdbuilder/README.md
Normal 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
|
||||
```
|
1
chatcmdbuilder/description.txt
Normal file
1
chatcmdbuilder/description.txt
Normal file
@ -0,0 +1 @@
|
||||
A library to make registering chat commands easier
|
230
chatcmdbuilder/init.lua
Normal file
230
chatcmdbuilder/init.lua
Normal 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
7
chatcmdbuilder/mod.conf
Normal 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
|
Loading…
x
Reference in New Issue
Block a user