Skip to content

Instantly share code, notes, and snippets.

@imliam
Created May 15, 2017 15:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save imliam/1af0d1f14cd85693984c113350650140 to your computer and use it in GitHub Desktop.
Save imliam/1af0d1f14cd85693984c113350650140 to your computer and use it in GitHub Desktop.
local inspect ={
_VERSION = 'inspect.lua 3.1.0',
_URL = 'http://github.com/kikito/inspect.lua',
_DESCRIPTION = 'human-readable representations of tables',
_LICENSE = [[
MIT LICENSE
Copyright (c) 2013 Enrique García Cota
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.
]]
}
local tostring = tostring
inspect.KEY = setmetatable({}, {__tostring = function() return 'inspect.KEY' end})
inspect.METATABLE = setmetatable({}, {__tostring = function() return 'inspect.METATABLE' end})
-- Apostrophizes the string if it has quotes, but not aphostrophes
-- Otherwise, it returns a regular quoted string
local function smartQuote(str)
if str:match('"') and not str:match("'") then
return "'" .. str .. "'"
end
return '"' .. str:gsub('"', '\\"') .. '"'
end
-- \a => '\\a', \0 => '\\0', 31 => '\31'
local shortControlCharEscapes = {
["\a"] = "\\a", ["\b"] = "\\b", ["\f"] = "\\f", ["\n"] = "\\n",
["\r"] = "\\r", ["\t"] = "\\t", ["\v"] = "\\v"
}
local longControlCharEscapes = {} -- \a => nil, \0 => \000, 31 => \031
for i=0, 31 do
local ch = string.char(i)
if not shortControlCharEscapes[ch] then
shortControlCharEscapes[ch] = "\\"..i
longControlCharEscapes[ch] = string.format("\\%03d", i)
end
end
local function escape(str)
return (str:gsub("\\", "\\\\")
:gsub("(%c)%f[0-9]", longControlCharEscapes)
:gsub("%c", shortControlCharEscapes))
end
local function isIdentifier(str)
return type(str) == 'string' and str:match( "^[_%a][_%a%d]*$" )
end
local function isSequenceKey(k, sequenceLength)
return type(k) == 'number'
and 1 <= k
and k <= sequenceLength
and math.floor(k) == k
end
local defaultTypeOrders = {
['number'] = 1, ['boolean'] = 2, ['string'] = 3, ['table'] = 4,
['function'] = 5, ['userdata'] = 6, ['thread'] = 7
}
local function sortKeys(a, b)
local ta, tb = type(a), type(b)
-- strings and numbers are sorted numerically/alphabetically
if ta == tb and (ta == 'string' or ta == 'number') then return a < b end
local dta, dtb = defaultTypeOrders[ta], defaultTypeOrders[tb]
-- Two default types are compared according to the defaultTypeOrders table
if dta and dtb then return defaultTypeOrders[ta] < defaultTypeOrders[tb]
elseif dta then return true -- default types before custom ones
elseif dtb then return false -- custom types after default ones
end
-- custom types are sorted out alphabetically
return ta < tb
end
-- For implementation reasons, the behavior of rawlen & # is "undefined" when
-- tables aren't pure sequences. So we implement our own # operator.
local function getSequenceLength(t)
local len = 1
local v = rawget(t,len)
while v ~= nil do
len = len + 1
v = rawget(t,len)
end
return len - 1
end
local function getNonSequentialKeys(t)
local keys = {}
local sequenceLength = getSequenceLength(t)
for k,_ in pairs(t) do
if not isSequenceKey(k, sequenceLength) then table.insert(keys, k) end
end
table.sort(keys, sortKeys)
return keys, sequenceLength
end
local function getToStringResultSafely(t, mt)
local __tostring = type(mt) == 'table' and rawget(mt, '__tostring')
local str, ok
if type(__tostring) == 'function' then
ok, str = pcall(__tostring, t)
str = ok and str or 'error: ' .. tostring(str)
end
if type(str) == 'string' and #str > 0 then return str end
end
local function countTableAppearances(t, tableAppearances)
tableAppearances = tableAppearances or {}
if type(t) == 'table' then
if not tableAppearances[t] then
tableAppearances[t] = 1
for k,v in pairs(t) do
countTableAppearances(k, tableAppearances)
countTableAppearances(v, tableAppearances)
end
countTableAppearances(getmetatable(t), tableAppearances)
else
tableAppearances[t] = tableAppearances[t] + 1
end
end
return tableAppearances
end
local copySequence = function(s)
local copy, len = {}, #s
for i=1, len do copy[i] = s[i] end
return copy, len
end
local function makePath(path, ...)
local keys = {...}
local newPath, len = copySequence(path)
for i=1, #keys do
newPath[len + i] = keys[i]
end
return newPath
end
local function processRecursive(process, item, path, visited)
if item == nil then return nil end
if visited[item] then return visited[item] end
local processed = process(item, path)
if type(processed) == 'table' then
local processedCopy = {}
visited[item] = processedCopy
local processedKey
for k,v in pairs(processed) do
processedKey = processRecursive(process, k, makePath(path, k, inspect.KEY), visited)
if processedKey ~= nil then
processedCopy[processedKey] = processRecursive(process, v, makePath(path, processedKey), visited)
end
end
local mt = processRecursive(process, getmetatable(processed), makePath(path, inspect.METATABLE), visited)
setmetatable(processedCopy, mt)
processed = processedCopy
end
return processed
end
-------------------------------------------------------------------
local Inspector = {}
local Inspector_mt = {__index = Inspector}
function Inspector:puts(...)
local args = {...}
local buffer = self.buffer
local len = #buffer
for i=1, #args do
len = len + 1
buffer[len] = args[i]
end
end
function Inspector:down(f)
self.level = self.level + 1
f()
self.level = self.level - 1
end
function Inspector:tabify()
self:puts(self.newline, string.rep(self.indent, self.level))
end
function Inspector:alreadyVisited(v)
return self.ids[v] ~= nil
end
function Inspector:getId(v)
local id = self.ids[v]
if not id then
local tv = type(v)
id = (self.maxIds[tv] or 0) + 1
self.maxIds[tv] = id
self.ids[v] = id
end
return tostring(id)
end
function Inspector:putKey(k)
if isIdentifier(k) then return self:puts(k) end
self:puts("[")
self:putValue(k)
self:puts("]")
end
function Inspector:putTable(t)
if t == inspect.KEY or t == inspect.METATABLE then
self:puts(tostring(t))
elseif self:alreadyVisited(t) then
self:puts('<table ', self:getId(t), '>')
elseif self.level >= self.depth then
self:puts('{...}')
else
if self.tableAppearances[t] > 1 then self:puts('<', self:getId(t), '>') end
local nonSequentialKeys, sequenceLength = getNonSequentialKeys(t)
local mt = getmetatable(t)
local toStringResult = getToStringResultSafely(t, mt)
self:puts('{')
self:down(function()
if toStringResult then
self:puts(' -- ', escape(toStringResult))
if sequenceLength >= 1 then self:tabify() end
end
local count = 0
for i=1, sequenceLength do
if count > 0 then self:puts(',') end
self:puts(' ')
self:putValue(t[i])
count = count + 1
end
for _,k in ipairs(nonSequentialKeys) do
if count > 0 then self:puts(',') end
self:tabify()
self:putKey(k)
self:puts(' = ')
self:putValue(t[k])
count = count + 1
end
if mt then
if count > 0 then self:puts(',') end
self:tabify()
self:puts('<metatable> = ')
self:putValue(mt)
end
end)
if #nonSequentialKeys > 0 or mt then -- result is multi-lined. Justify closing }
self:tabify()
elseif sequenceLength > 0 then -- array tables have one extra space before closing }
self:puts(' ')
end
self:puts('}')
end
end
function Inspector:putValue(v)
local tv = type(v)
if tv == 'string' then
self:puts(smartQuote(escape(v)))
elseif tv == 'number' or tv == 'boolean' or tv == 'nil' then
self:puts(tostring(v))
elseif tv == 'table' then
self:putTable(v)
else
self:puts('<',tv,' ',self:getId(v),'>')
end
end
-------------------------------------------------------------------
function inspect.inspect(root, options)
options = options or {}
local depth = options.depth or math.huge
local newline = options.newline or '\n'
local indent = options.indent or ' '
local process = options.process
if process then
root = processRecursive(process, root, {}, {})
end
local inspector = setmetatable({
depth = depth,
level = 0,
buffer = {},
ids = {},
maxIds = {},
newline = newline,
indent = indent,
tableAppearances = countTableAppearances(root)
}, Inspector_mt)
inspector:putValue(root)
return table.concat(inspector.buffer)
end
setmetatable(inspect, { __call = function(_, ...) return inspect.inspect(...) end })
return inspect
-----------------------------------------------------------------------------
-- JSON4Lua: JSON encoding / decoding support for the Lua language.
-- json Module.
-- Author: Craig Mason-Jones
-- Homepage: http://github.com/craigmj/json4lua/
-- Version: 1.0.0
-- This module is released under the MIT License (MIT).
-- Please see LICENCE.txt for details.
--
-- USAGE:
-- This module exposes two functions:
-- json.encode(o)
-- Returns the table / string / boolean / number / nil / json.null value as a JSON-encoded string.
-- json.decode(json_string)
-- Returns a Lua object populated with the data encoded in the JSON string json_string.
--
-- REQUIREMENTS:
-- compat-5.1 if using Lua 5.0
--
-- CHANGELOG
-- 0.9.20 Introduction of local Lua functions for private functions (removed _ function prefix).
-- Fixed Lua 5.1 compatibility issues.
-- Introduced json.null to have null values in associative arrays.
-- json.encode() performance improvement (more than 50%) through table.concat rather than ..
-- Introduced decode ability to ignore /**/ comments in the JSON string.
-- 0.9.10 Fix to array encoding / decoding to correctly manage nil/null values in arrays.
-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
-- Module declaration
-----------------------------------------------------------------------------
local json = {} -- Public namespace
local json_private = {} -- Private namespace
-- Public constants
json.EMPTY_ARRAY={}
json.EMPTY_OBJECT={}
-- Public functions
-- Private functions
local decode_scanArray
local decode_scanComment
local decode_scanConstant
local decode_scanNumber
local decode_scanObject
local decode_scanString
local decode_scanWhitespace
local encodeString
local isArray
local isEncodable
-----------------------------------------------------------------------------
-- PUBLIC FUNCTIONS
-----------------------------------------------------------------------------
--- Encodes an arbitrary Lua object / variable.
-- @param v The Lua object / variable to be JSON encoded.
-- @return String containing the JSON encoding in internal Lua string format (i.e. not unicode)
function json.encode (v)
-- Handle nil values
if v==nil then
return "null"
end
local vtype = type(v)
-- Handle strings
if vtype=='string' then
return '"' .. json_private.encodeString(v) .. '"' -- Need to handle encoding in string
end
-- Handle booleans
if vtype=='number' or vtype=='boolean' then
return tostring(v)
end
-- Handle tables
if vtype=='table' then
local rval = {}
-- Consider arrays separately
local bArray, maxCount = isArray(v)
if bArray then
for i = 1,maxCount do
table.insert(rval, json.encode(v[i]))
end
else -- An object, not an array
for i,j in pairs(v) do
if isEncodable(i) and isEncodable(j) then
table.insert(rval, '"' .. json_private.encodeString(i) .. '":' .. json.encode(j))
end
end
end
if bArray then
return '[' .. table.concat(rval,',') ..']'
else
return '{' .. table.concat(rval,',') .. '}'
end
end
-- Handle null values
if vtype=='function' and v==json.null then
return 'null'
end
assert(false,'encode attempt to encode unsupported type ' .. vtype .. ':' .. tostring(v))
end
--- Decodes a JSON string and returns the decoded value as a Lua data structure / value.
-- @param s The string to scan.
-- @param [startPos] Optional starting position where the JSON string is located. Defaults to 1.
-- @param Lua object, number The object that was scanned, as a Lua table / string / number / boolean or nil,
-- and the position of the first character after
-- the scanned JSON object.
function json.decode(s, startPos)
startPos = startPos and startPos or 1
startPos = decode_scanWhitespace(s,startPos)
assert(startPos<=string.len(s), 'Unterminated JSON encoded object found at position in [' .. s .. ']')
local curChar = string.sub(s,startPos,startPos)
-- Object
if curChar=='{' then
return decode_scanObject(s,startPos)
end
-- Array
if curChar=='[' then
return decode_scanArray(s,startPos)
end
-- Number
if string.find("+-0123456789.e", curChar, 1, true) then
return decode_scanNumber(s,startPos)
end
-- String
if curChar==[["]] or curChar==[[']] then
return decode_scanString(s,startPos)
end
if string.sub(s,startPos,startPos+1)=='/*' then
return json.decode(s, decode_scanComment(s,startPos))
end
-- Otherwise, it must be a constant
return decode_scanConstant(s,startPos)
end
--- The null function allows one to specify a null value in an associative array (which is otherwise
-- discarded if you set the value with 'nil' in Lua. Simply set t = { first=json.null }
function json.null()
return json.null -- so json.null() will also return null ;-)
end
-----------------------------------------------------------------------------
-- Internal, PRIVATE functions.
-- Following a Python-like convention, I have prefixed all these 'PRIVATE'
-- functions with an underscore.
-----------------------------------------------------------------------------
--- Scans an array from JSON into a Lua object
-- startPos begins at the start of the array.
-- Returns the array and the next starting position
-- @param s The string being scanned.
-- @param startPos The starting position for the scan.
-- @return table, int The scanned array as a table, and the position of the next character to scan.
function decode_scanArray(s,startPos)
local array = {} -- The return value
local stringLen = string.len(s)
assert(string.sub(s,startPos,startPos)=='[','decode_scanArray called but array does not start at position ' .. startPos .. ' in string:\n'..s )
startPos = startPos + 1
-- Infinite loop for array elements
repeat
startPos = decode_scanWhitespace(s,startPos)
assert(startPos<=stringLen,'JSON String ended unexpectedly scanning array.')
local curChar = string.sub(s,startPos,startPos)
if (curChar==']') then
return array, startPos+1
end
if (curChar==',') then
startPos = decode_scanWhitespace(s,startPos+1)
end
assert(startPos<=stringLen, 'JSON String ended unexpectedly scanning array.')
object, startPos = json.decode(s,startPos)
table.insert(array,object)
until false
end
--- Scans a comment and discards the comment.
-- Returns the position of the next character following the comment.
-- @param string s The JSON string to scan.
-- @param int startPos The starting position of the comment
function decode_scanComment(s, startPos)
assert( string.sub(s,startPos,startPos+1)=='/*', "decode_scanComment called but comment does not start at position " .. startPos)
local endPos = string.find(s,'*/',startPos+2)
assert(endPos~=nil, "Unterminated comment in string at " .. startPos)
return endPos+2
end
--- Scans for given constants: true, false or null
-- Returns the appropriate Lua type, and the position of the next character to read.
-- @param s The string being scanned.
-- @param startPos The position in the string at which to start scanning.
-- @return object, int The object (true, false or nil) and the position at which the next character should be
-- scanned.
function decode_scanConstant(s, startPos)
local consts = { ["true"] = true, ["false"] = false, ["null"] = nil }
local constNames = {"true","false","null"}
for i,k in pairs(constNames) do
if string.sub(s,startPos, startPos + string.len(k) -1 )==k then
return consts[k], startPos + string.len(k)
end
end
assert(nil, 'Failed to scan constant from string ' .. s .. ' at starting position ' .. startPos)
end
--- Scans a number from the JSON encoded string.
-- (in fact, also is able to scan numeric +- eqns, which is not
-- in the JSON spec.)
-- Returns the number, and the position of the next character
-- after the number.
-- @param s The string being scanned.
-- @param startPos The position at which to start scanning.
-- @return number, int The extracted number and the position of the next character to scan.
function decode_scanNumber(s,startPos)
local endPos = startPos+1
local stringLen = string.len(s)
local acceptableChars = "+-0123456789.e"
while (string.find(acceptableChars, string.sub(s,endPos,endPos), 1, true)
and endPos<=stringLen
) do
endPos = endPos + 1
end
local stringValue = 'return ' .. string.sub(s,startPos, endPos-1)
local stringEval = loadstring(stringValue)
assert(stringEval, 'Failed to scan number [ ' .. stringValue .. '] in JSON string at position ' .. startPos .. ' : ' .. endPos)
return stringEval(), endPos
end
--- Scans a JSON object into a Lua object.
-- startPos begins at the start of the object.
-- Returns the object and the next starting position.
-- @param s The string being scanned.
-- @param startPos The starting position of the scan.
-- @return table, int The scanned object as a table and the position of the next character to scan.
function decode_scanObject(s,startPos)
local object = {}
local stringLen = string.len(s)
local key, value
assert(string.sub(s,startPos,startPos)=='{','decode_scanObject called but object does not start at position ' .. startPos .. ' in string:\n' .. s)
startPos = startPos + 1
repeat
startPos = decode_scanWhitespace(s,startPos)
assert(startPos<=stringLen, 'JSON string ended unexpectedly while scanning object.')
local curChar = string.sub(s,startPos,startPos)
if (curChar=='}') then
return object,startPos+1
end
if (curChar==',') then
startPos = decode_scanWhitespace(s,startPos+1)
end
assert(startPos<=stringLen, 'JSON string ended unexpectedly scanning object.')
-- Scan the key
key, startPos = json.decode(s,startPos)
assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key)
startPos = decode_scanWhitespace(s,startPos)
assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key)
assert(string.sub(s,startPos,startPos)==':','JSON object key-value assignment mal-formed at ' .. startPos)
startPos = decode_scanWhitespace(s,startPos+1)
assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key)
value, startPos = json.decode(s,startPos)
object[key]=value
until false -- infinite loop while key-value pairs are found
end
-- START SoniEx2
-- Initialize some things used by decode_scanString
-- You know, for efficiency
local escapeSequences = {
["\\t"] = "\t",
["\\f"] = "\f",
["\\r"] = "\r",
["\\n"] = "\n",
["\\b"] = "\b"
}
setmetatable(escapeSequences, {__index = function(t,k)
-- skip "\" aka strip escape
return string.sub(k,2)
end})
-- END SoniEx2
--- Scans a JSON string from the opening inverted comma or single quote to the
-- end of the string.
-- Returns the string extracted as a Lua string,
-- and the position of the next non-string character
-- (after the closing inverted comma or single quote).
-- @param s The string being scanned.
-- @param startPos The starting position of the scan.
-- @return string, int The extracted string as a Lua string, and the next character to parse.
function decode_scanString(s,startPos)
assert(startPos, 'decode_scanString(..) called without start position')
local startChar = string.sub(s,startPos,startPos)
-- START SoniEx2
-- PS: I don't think single quotes are valid JSON
assert(startChar == [["]] or startChar == [[']],'decode_scanString called for a non-string')
--assert(startPos, "String decoding failed: missing closing " .. startChar .. " for string at position " .. oldStart)
local t = {}
local i,j = startPos,startPos
while string.find(s, startChar, j+1) ~= j+1 do
local oldj = j
i,j = string.find(s, "\\.", j+1)
local x,y = string.find(s, startChar, oldj+1)
if not i or x < i then
i,j = x,y-1
end
table.insert(t, string.sub(s, oldj+1, i-1))
if string.sub(s, i, j) == "\\u" then
local a = string.sub(s,j+1,j+4)
j = j + 4
local n = tonumber(a, 16)
assert(n, "String decoding failed: bad Unicode escape " .. a .. " at position " .. i .. " : " .. j)
-- math.floor(x/2^y) == lazy right shift
-- a % 2^b == bitwise_and(a, (2^b)-1)
-- 64 = 2^6
-- 4096 = 2^12 (or 2^6 * 2^6)
local x
if n < 0x80 then
x = string.char(n % 0x80)
elseif n < 0x800 then
-- [110x xxxx] [10xx xxxx]
x = string.char(0xC0 + (math.floor(n/64) % 0x20), 0x80 + (n % 0x40))
else
-- [1110 xxxx] [10xx xxxx] [10xx xxxx]
x = string.char(0xE0 + (math.floor(n/4096) % 0x10), 0x80 + (math.floor(n/64) % 0x40), 0x80 + (n % 0x40))
end
table.insert(t, x)
else
table.insert(t, escapeSequences[string.sub(s, i, j)])
end
end
table.insert(t,string.sub(j, j+1))
assert(string.find(s, startChar, j+1), "String decoding failed: missing closing " .. startChar .. " at position " .. j .. "(for string at position " .. startPos .. ")")
return table.concat(t,""), j+2
-- END SoniEx2
end
--- Scans a JSON string skipping all whitespace from the current start position.
-- Returns the position of the first non-whitespace character, or nil if the whole end of string is reached.
-- @param s The string being scanned
-- @param startPos The starting position where we should begin removing whitespace.
-- @return int The first position where non-whitespace was encountered, or string.len(s)+1 if the end of string
-- was reached.
function decode_scanWhitespace(s,startPos)
local whitespace=" \n\r\t"
local stringLen = string.len(s)
while ( string.find(whitespace, string.sub(s,startPos,startPos), 1, true) and startPos <= stringLen) do
startPos = startPos + 1
end
return startPos
end
--- Encodes a string to be JSON-compatible.
-- This just involves back-quoting inverted commas, back-quotes and newlines, I think ;-)
-- @param s The string to return as a JSON encoded (i.e. backquoted string)
-- @return The string appropriately escaped.
local escapeList = {
['"'] = '\\"',
['\\'] = '\\\\',
['/'] = '\\/',
['\b'] = '\\b',
['\f'] = '\\f',
['\n'] = '\\n',
['\r'] = '\\r',
['\t'] = '\\t'
}
function json_private.encodeString(s)
local s = tostring(s)
return s:gsub(".", function(c) return escapeList[c] end) -- SoniEx2: 5.0 compat
end
-- Determines whether the given Lua type is an array or a table / dictionary.
-- We consider any table an array if it has indexes 1..n for its n items, and no
-- other data in the table.
-- I think this method is currently a little 'flaky', but can't think of a good way around it yet...
-- @param t The table to evaluate as an array
-- @return boolean, number True if the table can be represented as an array, false otherwise. If true,
-- the second returned value is the maximum
-- number of indexed elements in the array.
function isArray(t)
-- Next we count all the elements, ensuring that any non-indexed elements are not-encodable
-- (with the possible exception of 'n')
if (t == json.EMPTY_ARRAY) then return true, 0 end
if (t == json.EMPTY_OBJECT) then return false end
local maxIndex = 0
for k,v in pairs(t) do
if (type(k)=='number' and math.floor(k)==k and 1<=k) then -- k,v is an indexed pair
if (not isEncodable(v)) then return false end -- All array elements must be encodable
maxIndex = math.max(maxIndex,k)
else
if (k=='n') then
if v ~= (t.n or #t) then return false end -- False if n does not hold the number of elements
else -- Else of (k=='n')
if isEncodable(v) then return false end
end -- End of (k~='n')
end -- End of k,v not an indexed pair
end -- End of loop across all pairs
return true, maxIndex
end
--- Determines whether the given Lua object / table / variable can be JSON encoded. The only
-- types that are JSON encodable are: string, boolean, number, nil, table and json.null.
-- In this implementation, all other types are ignored.
-- @param o The object to examine.
-- @return boolean True if the object should be JSON encoded, false if it should be ignored.
function isEncodable(o)
local t = type(o)
return (t=='string' or t=='boolean' or t=='number' or t=='nil' or t=='table') or
(t=='function' and o==json.null)
end
return json
-- https://github.com/profburke/colorswatches/tree/master/src/colorswatches
-- https://github.com/kikito/i18n.lua
-- https://github.com/Tieske/date
local object = {
x = 0,
y = 0,
width = 0,
height = 0,
rotation = 0,
}
function isInSquare(x, y, object)
local theta, c, s, cx, cy
theta = math.rad(object.rotation or 0)
c, s = math.cos(-theta), math.sin(-theta)
cx = object.x + c*(x - object.x) - s*(y - object.y)
cy = object.y + s*(x - object.x) + c*(y - object.y)
if math.abs(cx-object.x)<object.length/2 and math.abs(cy-object.y)<object.height/2 then
return true
end
return false
end
function eventClick(name, x, y)
for i=#map.grounds,1,-1 do
local ground = isInSquare(x, y, object)
end
end
function definitely_not_intersecting(a, b)
-- we put everything into a coordinate system where rectangle a is centered at the origin
-- with no rotation. i.e., it's corners are (-a.w/2,-a.h/2) and (a.w/2,a.h/2)
--
-- we'll need these a lot
(a_w2, a_h2, b_w2, b_h2) = (a.w/2, a.h/2, b.w/2, b.h/2)
--
-- first we put the center of b into this system
-- translate
b_xc_tmp = (b.x+b_w2) - (a.x+a_w2)
b_yc_tmp = (b.y+b_h2) - (a.y+a_h2)
-- rotate by -a.theta
c = math.cos(-a.theta)
s = math.sin(-a.theta)
b_xc = b_xc_tmp*c - b_yc_tmp*s
b_yc = b_yc_tmp*c + b_xc_tmp*s
--
-- next we compute each corner of rectangle b relative to (b_xc,b_yc)
theta = b.theta - a.theta
c = math.cos(theta)
s = math.sin(theta)
-- because we're rotating around the center, we have some symmetry
b_x1 = b_w2*c - b_h2*s
b_y1 = b_w2*s + b_h2*c
b_x2 = b_w2*c + b_h2*s
b_y2 = b_w2*s - b_h2*c
b_xs = [b_x1, b_x2, -b_x1, -b_x2]
b_ys = [b_y1, b_y2, -b_y1, -b_y2]
--
-- find the bounding rectangle
b_xmin = b_xc + min(b_xs)
b_xmax = b_xc + max(b_xs)
b_ymin = b_yc + min(b_ys)
b_ymax = b_yc + max(b_ys)
--
-- check for intersection with rectangle a
return b_xmax < -a_w2 or b_xmin > a_w2 or b_ymax < -a_h2 or b_ymin > a_h2
end
float a = d.Dot( d ) ;
float b = 2*f.Dot( d ) ;
float c = f.Dot( f ) - r*r ;
a = {x=0, y=200}
b = {x=800, y=200}
circle = {x=400, y=200, r=100}
function line_intersects_circle(pointA,pointB,circle)
local discriminant = b * b - 4 * a * c
if discriminant < 0 then
-- no intersection
else
-- ray didn't totally miss sphere,
-- so there is a solution to
-- the equation.
discriminant = math.sqrt(discriminant)
-- either solution may be on or off the ray so need to test both
-- t1 is always the smaller value, because BOTH discriminant and
-- a are nonnegative.
float t1 = (-b - discriminant)/(2*a)
float t2 = (-b + discriminant)/(2*a)
-- 3x HIT cases:
-- -o-> --|--> | | --|->
-- Impale(t1 hit,t2 hit), Poke(t1 hit,t2>1), ExitWound(t1<0, t2 hit),
-- 3x MISS cases:
-- -> o o -> | -> |
-- FallShort (t1>1,t2>1), Past (t1<0,t2<0), CompletelyInside(t1<0, t2>1)
if t1 >= 0 and t1 <= 1 then
-- t1 is the intersection, and it's closer than t2
-- (since t1 uses -b - discriminant)
-- Impale, Poke
return true
end
-- here t1 didn't intersect so we are either started
-- inside the sphere or completely past it
if t2 >= 0 and t2 <= 1 then
-- ExitWound
return true
end
-- no intn: FallShort, Past, CompletelyInside
return false
end
end
function dump(value, valueName, tabs)
tabs=tabs or ""
local str = ''
local t=type(value)
if valueName then
str = str .. (tabs .. t .. " " .. tostring(valueName) .. " = " .. tostring(value) .. "\n")
else
str = str .. (tabs .. t .. " " .. tostring(value) .. "\n")
end
if t=="table" then
for n,v in pairs(value) do
if v==value then
str = str .. tabs.."\tself "..n
else
str = str .. dump(v,n,tabs.."\t")
end
end
end
return str
end
function lua_to_string(value)
if type(value) == 'table' then
-- to table
elseif type(value) == 'number' then
return tostring(value)
elseif type(value) == 'string' then
return '"' .. value .. '"'
elseif type(value) == 'boolean' then
return value and 'true' or 'false'
end
end
function string_to_lua(value)
if value == 'true' then
return true
elseif value == 'false' then
return false
elseif value == 'nil' then
return nil
elseif tonumber(value) then
return tonumber(value)
else
return value
end
end
function tables_equal(tbl1, tbl2)
local tbl1count, tbl2count = 0, 0
for key, value in pairs(tbl1) do
tbl1count = tbl1count + 1
if tbl2[key] and value == tbl2[key] then
if type(value) == 'table' then
if not tables_equal(value, tbl2[key]) then
return false
end
end
else
return false
end
end
for key, value in pairs(tbl2) do
tbl2count = tbl2count + 1
end
if tbl1count ~= tbl2count then
return false
end
return true
end
--=====================================================
-- the reason this routine is needed is because lua does not
-- have a sort indexed table function
function table_sort(a, sortfield)
local new1 = {}
local new2 = {}
for k,v in pairs(a) do
table.insert(new1, { key=k, val=v } )
end
table.sort(new1, function (a,b) return (a.val[sortfield] < b.val[sortfield]) end)
for k,v in pairs(new1) do
table.insert(new2, v.val)
end
return new2
end
---============================================================
function padzero(s, count)
return string.rep("0", count-string.len(s)) .. s
end
--========================================================================
-- get date parts for a given ISO 8601 date format (http://richard.warburton.it )
function get_date_parts(date_str)
_,_,y,m,d=string.find(date_str, "(%d+)-(%d+)-(%d+)")
return tonumber(y),tonumber(m),tonumber(d)
end
--====================================================
function getmonth(month)
local months = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }
return months[tonumber(month)]
end
--====================================================
function getday_posfix(day)
local idd = math.mod(day,10)
return (idd==1 and day~=11 and "st") or (idd==2 and day~=12 and "nd") or (idd==3 and day~=13 and "rd") or "th"
end
--========================================================================
-- Given a date of certain date_format, returns the date parts yy,mm,dd
-- eg. "12 May 2007" with date_format "dd mmm yyyy"
-- or "12.05.07" with date_format "dd.mm.yy"
-- will return 2007, 5, 12
--
function get_formatted_date_parts(date_str, date_format)
local d,m,y, arr, x, yy, mm, dd, use_month_names
local months = { jan=1, feb=2, mar=3, apr=4, may=5, jun=6, jul=7, aug=8, sep=9, oct=10, nov=11, dec=12 }
if (date_format) then
if string.find(date_format, "mmm") then
use_month_names = true
else
use_month_names = false
end
d = string.find(date_format, "dd")
m = string.find(date_format, "mm")
y = string.find(date_format, "yy")
arr = { { pos=y, b="yy" }, { pos=m, b="mm" } , { pos=d, b="dd" } }
arr = table_sort(arr, "pos")
date_format = string.gsub(date_format,"yyyy","(%%d+)")
date_format = string.gsub(date_format,"mmm","(%%a+)")
date_format = string.gsub(date_format,"yy","(%%d+)")
date_format = string.gsub(date_format,"mm","(%%d+)")
date_format = string.gsub(date_format,"dd","(%%d+)")
date_format = string.gsub(date_format," ","%%s")
else
date_format = "(%d+)-(%d+)-(%d+)"
arr = { { pos=1, b="yy" }, { pos=2, b="mm" } , { pos=3, b="dd" } }
end
if (date_str and date_str~="") then
_, _, arr[1].c, arr[2].c, arr[3].c = string.find(string.lower(date_str), date_format)
else
return nil, nil, nil
end
arr = table_sort(arr, "b")
yy = arr[3].c
mm = arr[2].c
dd = arr[1].c
if (use_month_names) then
mm = months[lower(string.sub(mm,1,3))]
if (not mm) then
error("Invalid month name.")
end
end
-- for naughty people who still use two digit years.
if (string.len(yy)==2) then
if (tonumber(yy)>40) then
yy = "19"..yy
else
yy = "20"..yy
end
end
return tonumber(yy),tonumber(mm),tonumber(dd)
end
--========================================================================
-- Note : date_str has to be ISO 8601 date format ie. yyyy-mm-dd
--
function format_date(date_str, dateformat)
local iyy, imm, idd
if (date_str and date_str~="") then
iyy, imm, idd = get_date_parts(date_str)
dateformat = string.gsub(dateformat, "DDD", idd..string.upper(getday_posfix(idd)))
dateformat = string.gsub(dateformat, "ddd", idd..getday_posfix(idd) )
dateformat = string.gsub(dateformat, "dd", padzero(idd,2))
dateformat = string.gsub(dateformat, "MMM", string.upper(getmonth(imm)))
dateformat = string.gsub(dateformat, "mmm", getmonth(imm))
dateformat = string.gsub(dateformat, "mm", padzero(imm,2))
dateformat = string.gsub(dateformat, "yyyy", padzero(iyy,4))
dateformat = string.gsub(dateformat, "yy", string.sub(padzero(iyy,4),3,4))
else
dateformat = ""
end
return(dateformat)
end
--===============================================
-- convert date to excel serial day number
--
function date_to_excel_date(dd, mm, yy)
local days, monthdays, leapyears, nonleapyears, nonnonleapyears
monthdays= { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
leapyears=tonumber(math.floor((yy-1900)/4));
nonleapyears=tonumber(math.floor((yy-1900)/100))
nonnonleapyears=tonumber(math.floor((yy-1600)/400))
if ((math.mod(yy,4)==0) and mm<3) then
leapyears = leapyears - 1
end
days= 365 * (yy-1900) + leapyears - nonleapyears + nonnonleapyears
c=1
while (c<mm) do
days = days + monthdays[c]
c=c+1
end
days=days+dd+1
return days
end
-- adate = os.date()
-- print("Todays date according to your OS format :", adate)
-- -- for example "mm/dd/yyyy" could be your OS locale date format.
-- yy,mm,dd = get_formatted_date_parts(adate, "mm/dd/yyyy")
-- ISO_date = yy .. "-" .. mm .. "-" .. dd
-- print("Date formated in ISO 8601", ISO_date)
-- print(format_date(ISO_date, "dd MMM yyyy") )
-- print(format_date(ISO_date, "ddd mmm yyyy") )
-- print(format_date(ISO_date, "dd/mm/yy") )
-- print(format_date(ISO_date, "yyyy.mm.dd") )
-- print(format_date(ISO_date, "Today is the ddd of mmm, yyyy.") )
-- print( date_to_excel_date(22, 2, 2009) ) -- outputs 39866
print( tables_equal({'a', {'a'}}, {'a', {'a'}}) )
local argparse = require "argparse"
local inspect = require "inspect"
local json = require "json"
function dump(value)
print(inspect(value))
end
function get_tip(context, wrong_name)
local context_pool = {}
local possible_name
local possible_names = {}
for name in pairs(context) do
if type(name) == "string" then
for i = 1, #name do
possible_name = name:sub(1, i - 1) .. name:sub(i + 1)
if not context_pool[possible_name] then
context_pool[possible_name] = {}
end
table.insert(context_pool[possible_name], name)
end
end
end
for i = 1, #wrong_name + 1 do
possible_name = wrong_name:sub(1, i - 1) .. wrong_name:sub(i + 1)
if context[possible_name] then
possible_names[possible_name] = true
elseif context_pool[possible_name] then
for _, name in ipairs(context_pool[possible_name]) do
possible_names[name] = true
end
end
end
local first = next(possible_names)
if first then
if next(possible_names, first) then
local possible_names_arr = {}
for name in pairs(possible_names) do
table.insert(possible_names_arr, "'" .. name .. "'")
end
table.sort(possible_names_arr)
return "\nDid you mean one of these: " .. table.concat(possible_names_arr, " ") .. "?"
else
return "\nDid you mean '" .. first .. "'?"
end
else
return ""
end
end
dump(str)
--[[
!img
!img -h
!img --help
!img remove
!img [name]
!omo hello world
!omo hello world --size=24
if [name] == '*' then
foreach
--all
end
else
--single
end
]]
function names(name, fnc, ...)
if name == '*' then
for key, value in pairs(users) do
fnc(...)
end
else
fnc(...)
end
end
config = {
name = 'utility',
owner = 'Username',
locale = 'en',
debug = true,
official = false
}
user = {
keybinds = {
dash = {'CTRL' = true, 'LEFT' = true},
up = {'UP'}
},
stats = {
ph = {
rounds = 0,
wins = 0,
losses = 0
}
}
}
commands ={
-- command definition
cmd = {
hidden = true, -- hidden from
permissions = {}, -- list of user groups who can use the command
helpMessage = "thing.help",
options = {
['--help'] =
['-h'] =
}
execute = function(userWhoExecuted, originalCommand, )
end
}
}
if not config.official then
tfm.exec.addImage = function()
print('Sorry, you cannot use this function.')
return 1
end
tfm.exec.chatMessage = function(str, name)
print(str .. ' (for ' .. name .. ')')
end
loop = eventLoop
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment