initial release

This commit is contained in:
Draqoken
2025-07-01 23:28:00 +03:00
commit e888d9dfb9
250 changed files with 132057 additions and 0 deletions

1678
cosmic rage/lua/InfoBox.lua Normal file

File diff suppressed because it is too large Load Diff

158
cosmic rage/lua/addxml.lua Normal file
View File

@@ -0,0 +1,158 @@
-- addxml.lua
-- lets you add triggers, aliases, timers, macros using XML code
--
-- See: http://www.gammon.com.au/forum/?id=7123
--[[
This lets you add items by simply setting up a table of their attributes and then adding it.
Exposed functions are:
addxml.trigger -- add a trigger
addxml.alias -- add an alias
addxml.timer -- add a timer
addxml.macro -- add a macro
addxml.save -- convert one of the above back into a table
Example of adding a trigger:
require "addxml"
addxml.trigger { match = "swordfish",
regexp = true,
['repeat'] = true, -- repeat is lua keyword
send = "hi there",
sequence = 50,
enabled = true,
name = "boris",
}
Example of converting a trigger back into a table:
require "tprint"
require "addxml"
tprint (addxml.save ("trigger", "boris"))
--]]
module (..., package.seeall)
local html_replacements = {
["<"] = "&lt;",
[">"] = "&gt;",
["&"] = "&amp;",
['"'] = "&quot;",
}
-- fix text so that < > & and double-quote are escaped
local function fixhtml (s)
return (string.gsub (tostring (s), '[<>&"]',
function (str)
return html_replacements [str] or str
end ))
end -- fixhtml
local function GeneralAdd (t, which, plural)
assert (type (t) == "table", "Table must be supplied to add a " .. which)
local k, v
local xml = {}
local send = fixhtml (t.send or "") -- send is done differently
t.send = nil
-- turn into XML options
for k, v in pairs (t) do
-- fix true/false to y/n
if v == true then
v = "y"
elseif v == false then
v = "n"
end -- if true or false
table.insert (xml, k .. '="' .. fixhtml (v) .. '"')
end -- for loop
assert (ImportXML (string.format (
"<%s><%s %s ><send>%s</send></%s></%s>",
plural, -- eg. triggers
which, -- eg. trigger
table.concat (xml, "\n"), -- eg. match="nick"
send, -- eg. "go north"
which, -- eg. trigger
plural) -- eg. triggers
) == 1, "Import of " .. which .. " failed")
end -- GeneralAdd
function trigger (t)
GeneralAdd (t, "trigger", "triggers")
-- force script entry-point resolution
if t.name and t.script then
SetTriggerOption (t.name, "script", t.script)
end -- if trigger has a name, and a script name
end -- addxml.trigger
function alias (t)
GeneralAdd (t, "alias", "aliases")
-- force script entry-point resolution
if t.name and t.script then
SetAliasOption (t.name, "script", t.script)
end -- if alias has a name, and a script name
end -- addxml.alias
function timer (t)
GeneralAdd (t, "timer", "timers")
-- force script entry-point resolution
if t.name and t.script then
SetTimerOption (t.name, "script", t.script)
end -- if timer has a name, and a script name
end -- addxml.timer
function macro (t)
GeneralAdd (t, "macro", "macros")
end -- addxml.macro
function save (which, name)
local typeconversion =
{
trigger = 0,
alias = 1,
timer = 2,
macro = 3,
-- variable = 4,
-- keypad = 5
}
local itemtype = assert (typeconversion [which], "Unknown type: " .. which)
local xml = ExportXML (itemtype, name)
-- if not found returns empty string
assert (xml ~= "", "Can't find " .. which .. ": " .. name)
-- parse back into table entries
local xmlnodes = assert (utils.xmlread (xml), "Bad XML")
-- all attributes should be a couple of levels down
local result = xmlnodes.nodes [1].nodes [1].attributes
-- find "send" text
-- another level?
if xmlnodes.nodes [1].nodes [1].nodes then
if xmlnodes.nodes [1].nodes [1].nodes [1].name == "send" then
result.send = xmlnodes.nodes [1].nodes [1].nodes [1].content
end -- have a "send" field
end -- have a child node
return result
end -- addxml.save

View File

@@ -0,0 +1,80 @@
-- alphanum.lua
--
-- Adapted somewhat from: http://www.davekoelle.com/files/alphanum.lua
-- Also see: http://www.davekoelle.com/alphanum.html
--
-- Implements a sort function that does a more "human readable" sort order.
-- It breaks the sort strings into "chunks" and then compares each one naturally,
-- depending on whether it is a string or a number (eg. z9.doc compares less than z20.doc)
-- It also does a case-insensitive compare (so "nick" and "Nick" come out together).
-- See also: http://www.gammon.com.au/forum/?id=8698
--[[
Example:
require "alphanum"
t={
"z18.doc","z19.doc","z2.doc","z16.doc","z17.doc",
"z1.doc","z101.doc","z102.doc","z11.doc","z12.doc",
"z13.doc","z14.doc","z15.doc","z20.doc","z3.doc",
"z4.doc","z5.doc","z6.doc","z10.doc","z100.doc",
"z7.doc","z8.doc","z9.doc", "Z9A.doc",
}
table.sort(t, alphanum (t))
for i=1, #t do
print(t[i])
end
--]]
function alphanum (t)
assert (type (t) == "table", "Must pass table to be sorted to alphanum")
local function chunkString(str)
local c = {}
for a, b in tostring (str):gmatch("(%d*)(%D*)") do
if a ~= "" then c[#c+1] = tonumber(a) end
if b ~= "" then c[#c+1] = b end
end
return c
end
local chunks = {}
-- build temporary table of the keys
for i=1, #t do
chunks [t [i]] = chunkString (t [i])
end
return function (a, b) -- return our sort comparison function
-- lookup chunked information from previously-built table
local achunks = chunks [a]
local bchunks = chunks [b]
for i = 1, math.min (#achunks, #bchunks) do
local as, bs = achunks [i], bchunks [i]
-- if one is a string, make them both strings
if type (as) == "string" or type (bs) == "string" then
as = (tostring (as)):upper ()
bs = (tostring (bs)):upper ()
end -- at least one is a string
-- if they are equal, move onto the next chunk
if as ~= bs then
return as < bs
end -- if
end -- for each chunk
-- still equal? the one with fewer chunks compares less
return #achunks < #bchunks
end -- sort function
end -- alphanum
return alphanum

29
cosmic rage/lua/check.lua Normal file
View File

@@ -0,0 +1,29 @@
--
-- check.lua
--
-- ----------------------------------------------------------
-- return-code checker for MUSHclient functions that return error codes
-- ----------------------------------------------------------
--
--[[
Call for those MUSHclient functions that return a result code (like eOK).
Not all functions return such a code.
eg.
require "check
check (SetVariable ("abc", "def")) --> works ok
check (SetVariable ("abc-", "def")) --> The name of this object is invalid
--]]
function check (result)
if result ~= error_code.eOK then
error (error_desc [result] or
string.format ("Unknown error code: %i", result),
2) -- error level - whoever called this function
end -- if
end -- function check
return check

View File

@@ -0,0 +1,90 @@
-- checkplugin.lua
-- Checks the nominated plugin is installed
function do_plugin_check_now (id, name)
local me -- who am I? plugin or main world script?
local location -- location to attempt to load plugin from
-- allow for being called from main world script
if GetPluginID () == "" then
me = "world script"
location = GetInfo (60)
else
me = GetPluginName () .. " plugin"
location = GetPluginInfo(GetPluginID (), 20)
end -- if
-- first check if installed
if not IsPluginInstalled (id) then
ColourNote ("white", "green", "Plugin '" .. name .. "' not installed. Attempting to install it...")
LoadPlugin (location .. name .. ".xml")
if IsPluginInstalled (id) then
ColourNote ("white", "green", "Success!")
-- here if still not installed
else
ColourNote ("white", "red", string.rep ("-", 80))
ColourNote ("white", "red", "Plugin '" .. name .. "' not installed. Please download and install it.")
ColourNote ("white", "red", "It is required for the correct operation of the " .. me)
ColourNote ("white", "red", string.rep ("-", 80))
return -- skip enabled check
end -- if not installed
end -- plugin was not installed
-- now make sure enabled (suggested by Fiendish - version 4.74+ )
if not GetPluginInfo(id, 17) then
ColourNote ("white", "green", "Plugin '" .. name .. "' not enabled. Attempting to enable it...")
EnablePlugin(id, true)
if GetPluginInfo(id, 17) then
ColourNote ("white", "green", "Success!")
else
ColourNote ("white", "red", string.rep ("-", 80))
ColourNote ("white", "red", "Plugin '" .. name .. "' not enabled. Please make sure it can be enabled.")
ColourNote ("white", "red", "It is required for the correct operation of the " .. me)
ColourNote ("white", "red", string.rep ("-", 80))
end -- if
end -- if not enabled
end -- do_plugin_check_now
function checkplugin (id, name)
if GetOption ("enable_timers") ~= 1 then
ColourNote ("white", "red", "WARNING! Timers not enabled. Plugin dependency checks will not work properly.")
end -- if timers disabled
-- give them time to load
DoAfterSpecial (2,
"do_plugin_check_now ('" .. id .. "', '" .. name .. "')",
sendto.script)
end -- checkplugin
function load_ppi (id, name)
local PPI = require "ppi"
local ppi = PPI.Load(id)
if ppi then
return ppi
end
ColourNote ("white", "green", "Plugin '" .. name .. "' not installed. Attempting to install it...")
LoadPlugin (GetPluginInfo(GetPluginID (), 20) .. name .. ".xml")
ppi = PPI.Load(id) -- try again
if ppi then
ColourNote ("white", "green", "Success!")
return ppi
end
ColourNote ("white", "red", string.rep ("-", 80))
ColourNote ("white", "red", "Plugin '" .. name .. "' not installed. Please download and install it.")
ColourNote ("white", "red", string.rep ("-", 80))
return nil
end -- function load_ppi

269
cosmic rage/lua/colors.lua Normal file
View File

@@ -0,0 +1,269 @@
-----------------------------------------------------------------------------
-- Provides support for color manipulation in HSL color space.
--
-- http://sputnik.freewisdom.org/lib/colors/
--
-- License: MIT/X
--
-- (c) 2008 Yuri Takhteyev (yuri@freewisdom.org) *
--
-- * rgb_to_hsl() implementation was contributed by Markus Fleck-Graffe.
-----------------------------------------------------------------------------
module(..., package.seeall)
local Color = {}
local Color_mt = {__metatable = {}, __index = Color}
-----------------------------------------------------------------------------
-- Instantiates a new "color".
--
-- @param H hue (0-360) _or_ an RGB string ("#930219")
-- @param S saturation (0.0-1.0)
-- @param L lightness (0.0-1.0)
-- @return an instance of Color
-----------------------------------------------------------------------------
function new(H, S, L)
if type(H) == "string" and H:sub(1,1)=="#" and H:len() == 7 then
H, S, L = rgb_string_to_hsl(H)
end
assert(Color_mt)
return setmetatable({H = H, S = S, L = L}, Color_mt)
end
-----------------------------------------------------------------------------
-- Converts an HSL triplet to RGB
-- (see http://homepages.cwi.nl/~steven/css/hsl.html).
--
-- @param h hue (0-360)
-- @param s saturation (0.0-1.0)
-- @param L lightness (0.0-1.0)
-- @return an R, G, and B component of RGB
-----------------------------------------------------------------------------
function hsl_to_rgb(h, s, L)
h = h/360
local m1, m2
if L<=0.5 then
m2 = L*(s+1)
else
m2 = L+s-L*s
end
m1 = L*2-m2
local function _h2rgb(m1, m2, h)
if h<0 then h = h+1 end
if h>1 then h = h-1 end
if h*6<1 then
return m1+(m2-m1)*h*6
elseif h*2<1 then
return m2
elseif h*3<2 then
return m1+(m2-m1)*(2/3-h)*6
else
return m1
end
end
return _h2rgb(m1, m2, h+1/3), _h2rgb(m1, m2, h), _h2rgb(m1, m2, h-1/3)
end
-----------------------------------------------------------------------------
-- Converts an RGB triplet to HSL.
-- (see http://easyrgb.com)
--
-- @param r red (0.0-1.0)
-- @param g green (0.0-1.0)
-- @param b blue (0.0-1.0)
-- @return corresponding H, S and L components
-----------------------------------------------------------------------------
function rgb_to_hsl(r, g, b)
--r, g, b = r/255, g/255, b/255
local min = math.min(r, g, b)
local max = math.max(r, g, b)
local delta = max - min
local h, s, l = 0, 0, ((min+max)/2)
if l > 0 and l < 0.5 then s = delta/(max+min) end
if l >= 0.5 and l < 1 then s = delta/(2-max-min) end
if delta > 0 then
if max == r and max ~= g then h = h + (g-b)/delta end
if max == g and max ~= b then h = h + 2 + (b-r)/delta end
if max == b and max ~= r then h = h + 4 + (r-g)/delta end
h = h / 6;
end
if h < 0 then h = h + 1 end
if h > 1 then h = h - 1 end
return h * 360, s, l
end
function rgb_string_to_hsl(rgb)
return rgb_to_hsl(tonumber(rgb:sub(2,3), 16)/255,
tonumber(rgb:sub(4,5), 16)/255,
tonumber(rgb:sub(6,7), 16)/255)
end
-----------------------------------------------------------------------------
-- Converts the color to an RGB string.
--
-- @return a 6-digit RGB representation of the color prefixed
-- with "#" (suitable for inclusion in HTML)
-----------------------------------------------------------------------------
function Color:to_rgb()
local rgb = {hsl_to_rgb(self.H, self.S, self.L)}
local buffer = "#"
for i,v in ipairs(rgb) do
buffer = buffer..string.format("%02x",math.floor(v*255+0.5))
end
return buffer
end
-----------------------------------------------------------------------------
-- Creates a new color with hue different by delta.
--
-- @param delta a delta for hue.
-- @return a new instance of Color.
-----------------------------------------------------------------------------
function Color:hue_offset(delta)
return new((self.H + delta) % 360, self.S, self.L)
end
-----------------------------------------------------------------------------
-- Creates a complementary color.
--
-- @return a new instance of Color
-----------------------------------------------------------------------------
function Color:complementary()
return self:hue_offset(180)
end
-----------------------------------------------------------------------------
-- Creates two neighboring colors (by hue), offset by "angle".
--
-- @param angle the difference in hue between this color and the
-- neighbors
-- @return two new instances of Color
-----------------------------------------------------------------------------
function Color:neighbors(angle)
local angle = angle or 30
return self:hue_offset(angle), self:hue_offset(360-angle)
end
-----------------------------------------------------------------------------
-- Creates two new colors to make a triadic color scheme.
--
-- @return two new instances of Color
-----------------------------------------------------------------------------
function Color:triadic()
return self:neighbors(120)
end
-----------------------------------------------------------------------------
-- Creates two new colors, offset by angle from this colors complementary.
--
-- @param angle the difference in hue between the complementary and
-- the returned colors
-- @return two new instances of Color
-----------------------------------------------------------------------------
function Color:split_complementary(angle)
return self:neighbors(180-(angle or 30))
end
-----------------------------------------------------------------------------
-- Creates a new color with saturation set to a new value.
--
-- @param saturation the new saturation value (0.0 - 1.0)
-- @return a new instance of Color
-----------------------------------------------------------------------------
function Color:desaturate_to(saturation)
return new(self.H, saturation, self.L)
end
-----------------------------------------------------------------------------
-- Creates a new color with saturation set to a old saturation times r.
--
-- @param r the multiplier for the new saturation
-- @return a new instance of Color
-----------------------------------------------------------------------------
function Color:desaturate_by(r)
return new(self.H, self.S*r, self.L)
end
-----------------------------------------------------------------------------
-- Creates a new color with lightness set to a new value.
--
-- @param lightness the new lightness value (0.0 - 1.0)
-- @return a new instance of Color
-----------------------------------------------------------------------------
function Color:lighten_to(lightness)
return new(self.H, self.S, lightness)
end
-----------------------------------------------------------------------------
-- Creates a new color with lightness set to a old lightness times r.
--
-- @param r the multiplier for the new lightness
-- @return a new instance of Color
-----------------------------------------------------------------------------
function Color:lighten_by(r)
return new(self.H, self.S, self.L*r)
end
-----------------------------------------------------------------------------
-- Creates n variations of this color using supplied function and returns
-- them as a table.
--
-- @param f the function to create variations
-- @param n the number of variations
-- @return a table with n values containing the new colors
-----------------------------------------------------------------------------
function Color:variations(f, n)
n = n or 5
local results = {}
for i=1,n do
table.insert(results, f(self, i, n))
end
return results
end
-----------------------------------------------------------------------------
-- Creates n tints of this color and returns them as a table
--
-- @param n the number of tints
-- @return a table with n values containing the new colors
-----------------------------------------------------------------------------
function Color:tints(n)
local f = function (color, i, n)
return color:lighten_to(color.L + (1-color.L)/n*i)
end
return self:variations(f, n)
end
-----------------------------------------------------------------------------
-- Creates n shades of this color and returns them as a table
--
-- @param n the number of shades
-- @return a table with n values containing the new colors
-----------------------------------------------------------------------------
function Color:shades(n)
local f = function (color, i, n)
return color:lighten_to(color.L - (color.L)/n*i)
end
return self:variations(f, n)
end
function Color:tint(r)
return self:lighten_to(self.L + (1-self.L)*r)
end
function Color:shade(r)
return self:lighten_to(self.L - self.L*r)
end
Color_mt.__tostring = Color.to_rgb

223
cosmic rage/lua/commas.lua Normal file
View File

@@ -0,0 +1,223 @@
-- commas.lua
-- ----------------------------------------------------------
-- Rounding, duration, comma functions
-- See forum thread:
-- http://www.gammon.com.au/forum/?id=7805
--[[
This function rounds any number to the closest integer.
The "tricky" case is exactly half-way.
That is, should 1.5 round to 1 or 2? How about -1.5?
This function rounds 1.5 "up" to 2, and -1.5 "down" to -2.
--]]
-- ----------------------------------------------------------
-- round "up" to absolute value, so we treat negative differently
-- that is, round (-1.5) will return -2
function round (x)
if x >= 0 then
return math.floor (x + 0.5)
end -- if positive
return math.ceil (x - 0.5)
end -- function round
--[[
Duration
This function is designed to display a time interval in "short form".
That is, rounded to the nearest major time interval. Some examples of intervals:
* 3.6 days - displays "4 d"
* 3.5 days - displays "4 d"
* 3.4 days - displays "3 d"
* 3.6 hours - displays "4 h"
* 3.5 hours - displays "4 h"
* 3.4 hours - displays "3 h"
* 3.6 minutes - displays "4 m"
* 3.5 minutes - displays "4 m"
* 3.4 minutes - displays "3 m"
* 59 seconds - displays "59 s"
* 58 seconds - displays "58 s"
* 57 seconds - displays "57 s" ... and so on to "0 s"
--]]
-- ----------------------------------------------------------
function convert_time (secs)
-- handle negative numbers
local sign = ""
if secs < 0 then
secs = math.abs (secs)
sign = "-"
end -- if negative seconds
-- weeks
if secs >= (60 * 60 * 24 * 6.5) then
return sign .. round (secs / (60 * 60 * 24 * 7)) .. " w"
end -- 6.5 or more days
-- days
if secs >= (60 * 60 * 23.5) then
return sign .. round (secs / (60 * 60 * 24)) .. " d"
end -- 23.5 or more hours
-- hours
if secs >= (60 * 59.5) then
return sign .. round (secs / (60 * 60)) .. " h"
end -- 59.5 or more minutes
-- minutes
if secs >= 59.5 then
return sign .. round (secs / 60) .. " m"
end -- 59.5 or more seconds
-- seconds
return sign .. round (secs) .. " s"
end -- function convert_time
--[[
Commas in numbers
This function adds commas to big numbers.
For example 123456 becomes "123,456".
--]]
-- ----------------------------------------------------------
function commas (num)
assert (type (num) == "number" or
type (num) == "string")
local result = ""
-- split number into 3 parts, eg. -1234.545e22
-- sign = + or -
-- before = 1234
-- after = .545e22
local sign, before, after =
string.match (tostring (num), "^([%+%-]?)(%d*)(%.?.*)$")
-- pull out batches of 3 digits from the end, put a comma before them
while string.len (before) > 3 do
result = "," .. string.sub (before, -3, -1) .. result
before = string.sub (before, 1, -4) -- remove last 3 digits
end -- while
-- we want the original sign, any left-over digits, the comma part,
-- and the stuff after the decimal point, if any
return sign .. before .. result .. after
end -- function commas
-- trim leading and trailing spaces from a string
function trim (s)
return (string.gsub (s, "^%s*(.-)%s*$", "%1"))
end -- trim
--[[
Shuffle a table
-- see: http://en.wikipedia.org/wiki/Fisher-Yates_shuffle
Example:
cards = { "Ace", "King", "Queen", "Jack", 10, 9, 8, 7, 6, 5, 4, 3, 2 }
shuffle (cards)
--]]
function shuffle(t)
local n = #t
while n >= 2 do
-- n is now the last pertinent index
local k = math.random(n) -- 1 <= k <= n
-- Quick swap
t[n], t[k] = t[k], t[n]
n = n - 1
end
return t
end
--[[
Directory scanner.
Calls function "f" for every file found in a path starting at "path".
Recurses to handle nested directories. Function "f" is called with two
arguments: (full) filename, statistics
See utils.readdir for the exact names of the entries supplied for each file.
See: http://www.gammon.com.au/forum/?id=9906
Example:
plugins = {}
-- this function is called for every found file
function load_file (name, stats)
if stats.size > 0 and
string.match (name:lower (), "%.xml$") and
not stats.hidden then
table.insert (plugins, name)
end -- if
end -- load_file
-- Scan plugins folder, passing each file found to "load_file" function.
scan_dir (GetInfo (60), load_file)
--]]
function scan_dir (path, f)
-- find all files in that directory
local t = assert (utils.readdir (path .. "\\*"))
for k, v in pairs (t) do
-- recurse to process subdirectory
if v.directory then
if k ~= "." and k ~= ".." then
scan_dir (path .. "\\" .. k, f)
end -- not current or owner directory
else -- call supplied function
f (path .. "\\" .. k, v)
end -- if
end -- for
end -- scan_dir

View File

@@ -0,0 +1,89 @@
-- copytable.lua
--[[
Table copying functions.
See: http://www.gammon.com.au/forum/?id=8042
Ideas by Shaun Biggs, David Haley, Nick Gammon
Date: 21st July 2007
This is intended to copy tables (make a real copy, rather than just the table
reference).
You can do a deep or shallow copy.
Shallow: Simply copies the keys and values.
If a value is a table, you will get the same table as in the original.
Deep: Copies keys and values recursively.
If a value is a table, makes a copy of that table, and so on.
Deep copy based on: http://lua-users.org/wiki/CopyTable
Restrictions: Items must be "safe" to copy (eg. not file IO userdata).
The deep copied tables share the same metatable as the original ones.
To change this, change the line:
return setmetatable(new_table, getmetatable(object))
to:
return setmetatable(new_table, _copy (getmetatable(object))
Example:
t1 = {
m = { a = 1, b = 2 },
n = { c = 3, d = 4 },
}
require "copytable" -- load this file
t2 = copytable.shallow (t1) -- shallow copy
t3 = copytable.deep (t1) -- copies sub tables as well
--]]
module (..., package.seeall)
function deep (object)
local lookup_table = {}
local function _copy (object)
if type (object) ~= "table" then
return object
elseif lookup_table [object] then
return lookup_table [object]
end -- if
local new_table = {}
lookup_table [object] = new_table
for index, value in pairs (object) do
new_table [_copy (index)] = _copy (value)
end -- for
return setmetatable (new_table, getmetatable (object))
end -- function _copy
return _copy (object)
end -- function deepcopy
function shallow (t)
assert (type (t) == "table", "You must specify a table to copy")
local result = {}
for k, v in pairs (t) do
result [k] = v
end -- for each table element
-- copy the metatable
return setmetatable (result, getmetatable (t))
end -- function shallow

View File

@@ -0,0 +1,47 @@
-- declare.lua
-- See: http://www.gammon.com.au/forum/?id=7327
--
-- If you use this inside a function you cannot access global variables that have
-- not already been declared, and must declare all local variables
function force_declarations ()
setfenv (2, setmetatable ({},
{
__index = function (t, n)
error("variable '"..n.."' is not declared", 2)
end,
__newindex = function (t, n, v)
error("assign to undeclared variable '"..n.."'", 2)
end })
)
end -- force_declarations
return force_declarations
--[[
Example of use:
require "declare"
function test (x)
-- capture any global variables we want
local print = print
-- after this we can't access global variables, and must declare local ones
force_declarations ()
-- must declare every variable now before we use it
local a, b, c
print (a)
a = 1
b = 2
c = x * 2
print (c)
end -- test
test (1)
--]]

186
cosmic rage/lua/gauge.lua Normal file
View File

@@ -0,0 +1,186 @@
--[[
Function to draw a gauge (health bar).
You specify the starting point, width, and height.
Also the name to show for the mouse-over window (eg. "HP).
Author: Nick Gammon
Date: 14 February 2010
--]]
function gauge (win, -- miniwindow ID to draw in
name, -- string, eg: "HP"
current, max, -- current and max value (eg. 50, 100)
-- if max is nil, then current is a percentage
left, top, width, height, -- where to put it inside the window
fg_colour, bg_colour, -- colour for bar, colour for unfilled part
ticks, tick_colour, -- number of ticks to draw, and in what colour
frame_colour, -- colour for frame around bar
shadow_colour, -- colour for shadow, nil for no shadow
no_gradient) -- don't use the gradient fill effect
local Fraction
if not current then
return
end -- if
-- max == nil, means current is a percentage
if max then
if max <= 0 then
return
end -- no divide by zero
Fraction = current / max
else
Fraction = current / 100
end -- if
-- fraction in range 0 to 1
Fraction = math.min (math.max (Fraction, 0), 1)
-- set up some defaults
height = height or 15
fg_colour = fg_colour or ColourNameToRGB "mediumblue"
bg_colour = bg_colour or ColourNameToRGB "rosybrown"
ticks = ticks or 5
tick_colour = tick_colour or ColourNameToRGB "silver"
frame_colour = frame_colour or ColourNameToRGB "lightgrey"
-- shadow
if shadow_colour then
WindowRectOp (win, miniwin.rect_fill, left + 2, top + 2, left + width + 2, top + height + 2, shadow_colour)
end -- if
-- background colour - for un-filled part
WindowRectOp (win, miniwin.rect_fill, left, top, left + width, top + height, bg_colour) -- fill entire box
-- how big filled part is
local gauge_width = (width - 2) * Fraction
-- box size must be > 0 or WindowGradient fills the whole thing
if math.floor (gauge_width) > 0 then
if no_gradient then
WindowRectOp (win, miniwin.rect_fill, left+1, top, left+1+gauge_width, top+height, fg_colour)
else
-- top half
WindowGradient (win, left, top,
left + gauge_width, top + height / 2,
0x000000, -- black
fg_colour, 2) -- vertical top to bottom
-- bottom half
WindowGradient (win, left, top + height / 2,
left + gauge_width, top + height-1,
fg_colour,
0x000000, -- black
2) -- vertical top to bottom
end
end -- non-zero
-- draw tick marks if wanted
if ticks > 0 then
-- show ticks (if there are 5 ticks there are 6 gaps)
local ticks_at = width / (ticks + 1)
-- ticks
for i = 1, ticks do
WindowLine (win, left + (i * ticks_at), top,
left + (i * ticks_at), top + height,
tick_colour, 0, 1)
end -- for
end -- ticks wanted
-- draw a box around it (frame)
WindowRectOp (win, miniwin.rect_frame, left, top, left + width, top + height, frame_colour)
if name and #name > 0 then
-- mouse-over information: add hotspot
WindowAddHotspot (win, name, left, top, left + width, top + height,
"", "", "", "", "", "", 0, 0)
-- store numeric values in case they mouse over it
if max then
WindowHotspotTooltip(win, name, string.format ("%s\t%i / %i (%i%%)",
name, current, max, Fraction * 100) )
else
WindowHotspotTooltip(win, name, string.format ("%s\t(%i%%)",
name, Fraction * 100) )
end -- if
end -- hotspot wanted
end -- function gauge
-- find which element in an array has the largest text size
function max_text_width (win, font_id, t, utf8)
local max = 0
for _, s in ipairs (t) do
max = math.max (max, WindowTextWidth (win, font_id, s, utf8))
end -- for each item
return max
end -- max_text_width
-- get font from preferred font list
function get_preferred_font (t)
local fonts = utils.getfontfamilies ()
-- convert to upper-case
local f2 = {}
for k in pairs (fonts) do
f2 [k:upper ()] = true
end -- for
for _, s in ipairs (t) do
if f2 [s:upper ()] then
return s
end -- if
end -- for each item
return "Courier"
end -- get_preferred_font
function capitalize (x)
return string.upper (string.sub(x, 1, 1)) .. string.lower (string.sub(x, 2))
end -- capitalize
function draw_3d_box (win, left, top, width, height)
local right = left + width
local bottom = top + height
WindowCircleOp (win, miniwin.circle_round_rectangle, left, top, right, bottom, 0x505050, 0, 3, 0, 1) -- dark grey border (3 pixels)
WindowCircleOp (win, miniwin.circle_round_rectangle, left + 1, top + 1, right - 1, bottom - 1, 0x7C7C7C, 0, 1, 0, 1) -- lighter inner border
WindowCircleOp (win, miniwin.circle_round_rectangle, left + 2, top + 2, right - 2, bottom - 2, 0x000000, 0, 1, 0, 1) -- black inside that
WindowLine (win, left + 1, top + 1, right - 1, top + 1, 0xC2C2C2, 0, 1) -- light top edge
WindowLine (win, left + 1, top + 1, left + 1, bottom - 1, 0xC2C2C2, 0, 1) -- light left edge (for 3D look)
end -- draw_3d_box
function draw_text_box (win, font, left, top, text, utf8, text_colour, fill_colour, border_colour)
local width = WindowTextWidth (win, font, text, utf8)
local font_height = WindowFontInfo (win, font, 1)
WindowRectOp (win, miniwin.rect_fill, left - 3, top, left + width + 3, top + font_height + 4, fill_colour) -- fill
WindowText (win, font, text, left, top + 2, 0, 0, text_colour, utf8) -- draw text
WindowRectOp (win, miniwin.rect_frame, left - 3, top, left + width + 3, top + font_height + 4, border_colour) -- border
return width
end -- draw_text_box
-- text with a black outline
function outlined_text(win, font, text, startx, starty, endx, endy, color, utf8)
WindowText(win, font, text, startx-1, starty-1, endx, endy, 0x000000, utf8)
WindowText(win, font, text, startx-1, starty, endx, endy, 0x000000, utf8)
WindowText(win, font, text, startx-1, starty+1, endx, endy, 0x000000, utf8)
WindowText(win, font, text, startx, starty-1, endx, endy, 0x000000, utf8)
WindowText(win, font, text, startx, starty+1, endx, endy, 0x000000, utf8)
WindowText(win, font, text, startx+1, starty-1, endx, endy, 0x000000, utf8)
WindowText(win, font, text, startx+1, starty, endx, endy, 0x000000, utf8)
WindowText(win, font, text, startx+1, starty+1, endx, endy, 0x000000, utf8)
WindowText(win, font, text, startx, starty, endx, endy, color, utf8)
end

View File

@@ -0,0 +1,48 @@
-- getlines.lua
-- getlines iterator - iterates over a string and returns one item per line
function getlines (str)
local pos = 0
-- the for loop calls this for every iteration
-- returning nil terminates the loop
local function iterator (s)
if not pos then
return nil
end -- end of string, exit loop
local oldpos = pos + 1 -- step past previous newline
pos = string.find (s, "\n", oldpos) -- find next newline
if not pos then -- no more newlines, return rest of string
return string.sub (s, oldpos)
end -- no newline
return string.sub (s, oldpos, pos - 1)
end -- iterator
return iterator, str
end -- getlines
return getlines
--[=[
Example of use:
require "getlines"
test = [[
every good
boy
deserves
fruit]]
for l in getlines (test) do
print ('"' .. l .. '"')
end -- for
--]=]

View File

@@ -0,0 +1,49 @@
-- getstyle.lua
--
--[[
See forum thread: http://www.gammon.com.au/forum/?id=7818
GetStyle:
Finds a style run corresponding to a given column
Returns nil if style run not found (eg. column out of range)
If style run found returns:
* the style table (see below)
* the character at that column
* the style run number (eg. style 3)
The style table should contain the following:
t.text --> text of that (entire) style run
t.length --> length of the (entire) style run
t.textcolour --> text colour (RGB number)
t.backcolour --> background colour (RGB number)
t.style --> style bits (1=bold, 2=underline, 4=italic)
--]]
function GetStyle (styleRuns, wantedColumn)
local currentColumn = 1
-- check arguments
assert (type (styleRuns) == "table",
"First argument to GetStyle must be table of style runs")
assert (type (wantedColumn) == "number" and wantedColumn >= 1,
"Second argument to GetStyle must be column number to find")
-- go through each style
for item, style in ipairs (styleRuns) do
local position = wantedColumn - currentColumn + 1 -- where letter is in style
currentColumn = currentColumn + style.length -- next style starts here
if currentColumn > wantedColumn then -- if we are within this style
return style, string.sub (style.text, position, position), item -- done
end -- if found column
end -- for each style
-- if not found: result is nil
end -- function GetStyle

View File

@@ -0,0 +1,111 @@
-- table of worlds we couldn't open
cannot_open_world = cannot_open_world or {} -- set flag here if can't open world
-- getworld.lua
--
--[[
See forum thread: http://www.gammon.com.au/forum/?id=7991
This simplifies sending triggered lines to another, dummy, world.
get_a_world (name) - returns a world pointer to the named world, opening it if necessary
send_to_world (name, styles) - sends the style runs to the named world, calling get_a_world
to get it
--]]
-- make the named world, if necessary - adds "extra" lines to the world file (eg. plugins)
function make_world (name, extra, folder)
local filename = GetInfo (57)
if folder then
filename = filename .. folder .. "\\"
end -- if folder wanted
filename = filename .. name .. ".mcl"
local f = io.open (filename, "r")
if f then
f:close ()
return
end -- world file exists
f = io.output (filename) -- create world file
assert (f:write ([[
<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE muclient>
<!-- MUSHclient world file -->
<!-- Written by Nick Gammon -->
<!-- Home Page: http://www.mushclient.com/ -->
<!-- Generated by getworld.lua plugin -->
<muclient>
<world defaults="y"
name="]] .. name .. [["
site="0.0.0.0"
port="4000"
/>
]] .. extra .. [[
</muclient>
]]))
f:close () -- close world file now
-- and open the file ;P
Open (filename)
end -- make_world
-- open a world by name, return world object or nil if cannot
function get_a_world (name, folder)
-- try to find world
local w = GetWorld (name) -- get world
-- if not found, try to open it in worlds directory
if not cannot_open_world [name] and not w then
local filename = GetInfo (57)
if folder then
filename = filename .. folder .. "\\"
end -- if folder wanted
filename = filename .. name .. ".mcl"
Open (filename) -- get MUSHclient to open it
Activate () -- make our original world active again
w = GetWorld (name) -- try again to get the world object
if w then
w:DeleteOutput () -- delete "welcome to MUSHclient" message
else
ColourNote ("white", "red", "Can't open world file: " .. filename)
cannot_open_world [name] = true -- don't repeatedly show failure message
end -- can't find world
end -- can't find world first time around
return w
end -- get_a_world
-- send the styles (eg. from a trigger) to the named world, opening it if necessary
function send_to_world (name, styles)
local w = get_a_world (name)
if w then -- if present
for _, v in ipairs (styles) do
w:ColourTell (RGBColourToName (v.textcolour),
RGBColourToName (v.backcolour),
v.text)
end -- for each style run
w:Note ("") -- wrap up line
end -- world found
return w -- so they can check if we succeeded
end -- send_to_world

24
cosmic rage/lua/json.lua Normal file
View File

@@ -0,0 +1,24 @@
--[[
Licensed according to the included 'LICENSE' document
Author: Thomas Harning Jr <harningt@gmail.com>
]]
local decode = require("json.decode")
local encode = require("json.encode")
local util = require("json.util")
local _G = _G
local _ENV = nil
local json = {
_VERSION = "1.3.4",
_DESCRIPTION = "LuaJSON : customizable JSON decoder/encoder",
_COPYRIGHT = "Copyright (c) 2007-2014 Thomas Harning Jr. <harningt@gmail.com>",
decode = decode,
encode = encode,
util = util
}
_G.json = json
return json

View File

@@ -0,0 +1,171 @@
--[[
Licensed according to the included 'LICENSE' document
Author: Thomas Harning Jr <harningt@gmail.com>
]]
local lpeg = require("lpeg")
local error = error
local pcall = pcall
local jsonutil = require("json.util")
local merge = jsonutil.merge
local util = require("json.decode.util")
local decode_state = require("json.decode.state")
local setmetatable, getmetatable = setmetatable, getmetatable
local assert = assert
local ipairs, pairs = ipairs, pairs
local string_char = require("string").char
local type = type
local require = require
local _ENV = nil
local modulesToLoad = {
"composite",
"strings",
"number",
"others"
}
local loadedModules = {
}
local json_decode = {}
json_decode.default = {
unicodeWhitespace = true,
initialObject = false,
nothrow = false
}
local modes_defined = { "default", "strict", "simple" }
json_decode.simple = {}
json_decode.strict = {
unicodeWhitespace = true,
initialObject = true,
nothrow = false
}
for _,name in ipairs(modulesToLoad) do
local mod = require("json.decode." .. name)
if mod.mergeOptions then
for _, mode in pairs(modes_defined) do
mod.mergeOptions(json_decode[mode], mode)
end
end
loadedModules[#loadedModules + 1] = mod
end
-- Shift over default into defaultOptions to permit build optimization
local defaultOptions = json_decode.default
json_decode.default = nil
local function generateDecoder(lexer, options)
-- Marker to permit detection of final end
local marker = {}
local parser = lpeg.Ct((options.ignored * lexer)^0 * lpeg.Cc(marker)) * options.ignored * (lpeg.P(-1) + util.unexpected())
local decoder = function(data)
local state = decode_state.create(options)
local parsed = parser:match(data)
assert(parsed, "Invalid JSON data")
local i = 0
while true do
i = i + 1
local item = parsed[i]
if item == marker then break end
if type(item) == 'function' and item ~= jsonutil.undefined and item ~= jsonutil.null then
item(state)
else
state:set_value(item)
end
end
if options.initialObject then
assert(type(state.previous) == 'table', "Initial value not an object or array")
end
-- Make sure stack is empty
assert(state.i == 0, "Unclosed elements present")
return state.previous
end
if options.nothrow then
return function(data)
local status, rv = pcall(decoder, data)
if status then
return rv
else
return nil, rv
end
end
end
return decoder
end
local function buildDecoder(mode)
mode = mode and merge({}, defaultOptions, mode) or defaultOptions
for _, mod in ipairs(loadedModules) do
if mod.mergeOptions then
mod.mergeOptions(mode)
end
end
local ignored = mode.unicodeWhitespace and util.unicode_ignored or util.ascii_ignored
-- Store 'ignored' in the global options table
mode.ignored = ignored
--local grammar = {
-- [1] = mode.initialObject and (ignored * (object_type + array_type)) or value_type
--}
local lexer
for _, mod in ipairs(loadedModules) do
local new_lexer = mod.generateLexer(mode)
lexer = lexer and lexer + new_lexer or new_lexer
end
return generateDecoder(lexer, mode)
end
-- Since 'default' is nil, we cannot take map it
local defaultDecoder = buildDecoder(json_decode.default)
local prebuilt_decoders = {}
for _, mode in pairs(modes_defined) do
if json_decode[mode] ~= nil then
prebuilt_decoders[json_decode[mode]] = buildDecoder(json_decode[mode])
end
end
--[[
Options:
number => number decode options
string => string decode options
array => array decode options
object => object decode options
initialObject => whether or not to require the initial object to be a table/array
allowUndefined => whether or not to allow undefined values
]]
local function getDecoder(mode)
mode = mode == true and json_decode.strict or mode or json_decode.default
local decoder = mode == nil and defaultDecoder or prebuilt_decoders[mode]
if decoder then
return decoder
end
return buildDecoder(mode)
end
local function decode(data, mode)
local decoder = getDecoder(mode)
return decoder(data)
end
local mt = {}
mt.__call = function(self, ...)
return decode(...)
end
json_decode.getDecoder = getDecoder
json_decode.decode = decode
json_decode.util = util
setmetatable(json_decode, mt)
return json_decode

View File

@@ -0,0 +1,64 @@
--[[
Licensed according to the included 'LICENSE' document
Author: Thomas Harning Jr <harningt@gmail.com>
--]]
local lpeg = require("lpeg")
local util = require("json.decode.util")
local jsonutil = require("json.util")
local table_maxn = require("table").maxn
local unpack = unpack
module("json.decode.array")
-- Utility function to help manage slighly sparse arrays
local function processArray(array)
local max_n = table_maxn(array)
-- Only populate 'n' if it is necessary
if #array ~= max_n then
array.n = max_n
end
if jsonutil.InitArray then
array = jsonutil.InitArray(array) or array
end
return array
end
local defaultOptions = {
trailingComma = true
}
default = nil -- Let the buildCapture optimization take place
strict = {
trailingComma = false
}
local function buildCapture(options, global_options)
local ignored = global_options.ignored
-- arrayItem == element
local arrayItem = lpeg.V(util.types.VALUE)
local arrayElements = lpeg.Ct(arrayItem * (ignored * lpeg.P(',') * ignored * arrayItem)^0 + 0) / processArray
options = options and jsonutil.merge({}, defaultOptions, options) or defaultOptions
local capture = lpeg.P("[")
capture = capture * ignored
* arrayElements * ignored
if options.trailingComma then
capture = capture * (lpeg.P(",") + 0) * ignored
end
capture = capture * lpeg.P("]")
return capture
end
function register_types()
util.register_type("ARRAY")
end
function load_types(options, global_options, grammar)
local capture = buildCapture(options, global_options)
local array_id = util.types.ARRAY
grammar[array_id] = capture
util.append_grammar_item(grammar, "VALUE", lpeg.V(array_id))
end

View File

@@ -0,0 +1,116 @@
--[[
Licensed according to the included 'LICENSE' document
Author: Thomas Harning Jr <harningt@gmail.com>
--]]
local lpeg = require("lpeg")
local tostring = tostring
local pairs, ipairs = pairs, ipairs
local next, type = next, type
local error = error
local util = require("json.decode.util")
local buildCall = require("json.util").buildCall
local getmetatable = getmetatable
module("json.decode.calls")
local defaultOptions = {
defs = nil,
-- By default, do not allow undefined calls to be de-serialized as call objects
allowUndefined = false
}
-- No real default-option handling needed...
default = nil
strict = nil
local isPattern
if lpeg.type then
function isPattern(value)
return lpeg.type(value) == 'pattern'
end
else
local metaAdd = getmetatable(lpeg.P("")).__add
function isPattern(value)
return getmetatable(value).__add == metaAdd
end
end
local function buildDefinedCaptures(argumentCapture, defs)
local callCapture
if not defs then return end
for name, func in pairs(defs) do
if type(name) ~= 'string' and not isPattern(name) then
error("Invalid functionCalls name: " .. tostring(name) .. " not a string or LPEG pattern")
end
-- Allow boolean or function to match up w/ encoding permissions
if type(func) ~= 'boolean' and type(func) ~= 'function' then
error("Invalid functionCalls item: " .. name .. " not a function")
end
local nameCallCapture
if type(name) == 'string' then
nameCallCapture = lpeg.P(name .. "(") * lpeg.Cc(name)
else
-- Name matcher expected to produce a capture
nameCallCapture = name * "("
end
-- Call func over nameCallCapture and value to permit function receiving name
-- Process 'func' if it is not a function
if type(func) == 'boolean' then
local allowed = func
func = function(name, ...)
if not allowed then
error("Function call on '" .. name .. "' not permitted")
end
return buildCall(name, ...)
end
else
local inner_func = func
func = function(...)
return (inner_func(...))
end
end
local newCapture = (nameCallCapture * argumentCapture) / func * ")"
if not callCapture then
callCapture = newCapture
else
callCapture = callCapture + newCapture
end
end
return callCapture
end
local function buildCapture(options)
if not options -- No ops, don't bother to parse
or not (options.defs and (nil ~= next(options.defs)) or options.allowUndefined) then
return nil
end
-- Allow zero or more arguments separated by commas
local value = lpeg.V(util.types.VALUE)
local argumentCapture = (value * (lpeg.P(",") * value)^0) + 0
local callCapture = buildDefinedCaptures(argumentCapture, options.defs)
if options.allowUndefined then
local function func(name, ...)
return buildCall(name, ...)
end
-- Identifier-type-match
local nameCallCapture = lpeg.C(util.identifier) * "("
local newCapture = (nameCallCapture * argumentCapture) / func * ")"
if not callCapture then
callCapture = newCapture
else
callCapture = callCapture + newCapture
end
end
return callCapture
end
function load_types(options, global_options, grammar)
local capture = buildCapture(options, global_options)
if capture then
util.append_grammar_item(grammar, "VALUE", capture)
end
end

View File

@@ -0,0 +1,190 @@
--[[
Licensed according to the included 'LICENSE' document
Author: Thomas Harning Jr <harningt@gmail.com>
]]
local pairs = pairs
local type = type
local lpeg = require("lpeg")
local util = require("json.decode.util")
local jsonutil = require("json.util")
local rawset = rawset
local assert = assert
local tostring = tostring
local error = error
local getmetatable = getmetatable
local _ENV = nil
local defaultOptions = {
array = {
trailingComma = true
},
object = {
trailingComma = true,
number = true,
identifier = true,
setObjectKey = rawset
},
calls = {
defs = nil,
-- By default, do not allow undefined calls to be de-serialized as call objects
allowUndefined = false
}
}
local modeOptions = {
default = nil,
strict = {
array = {
trailingComma = false
},
object = {
trailingComma = false,
number = false,
identifier = false
}
}
}
local function BEGIN_ARRAY(state)
state:push()
state:new_array()
end
local function END_ARRAY(state)
state:end_array()
state:pop()
end
local function BEGIN_OBJECT(state)
state:push()
state:new_object()
end
local function END_OBJECT(state)
state:end_object()
state:pop()
end
local function END_CALL(state)
state:end_call()
state:pop()
end
local function SET_KEY(state)
state:set_key()
end
local function NEXT_VALUE(state)
state:put_value()
end
local function mergeOptions(options, mode)
jsonutil.doOptionMerge(options, true, 'array', defaultOptions, mode and modeOptions[mode])
jsonutil.doOptionMerge(options, true, 'object', defaultOptions, mode and modeOptions[mode])
jsonutil.doOptionMerge(options, true, 'calls', defaultOptions, mode and modeOptions[mode])
end
local isPattern
if lpeg.type then
function isPattern(value)
return lpeg.type(value) == 'pattern'
end
else
local metaAdd = getmetatable(lpeg.P("")).__add
function isPattern(value)
return getmetatable(value).__add == metaAdd
end
end
local function generateSingleCallLexer(name, func)
if type(name) ~= 'string' and not isPattern(name) then
error("Invalid functionCalls name: " .. tostring(name) .. " not a string or LPEG pattern")
end
-- Allow boolean or function to match up w/ encoding permissions
if type(func) ~= 'boolean' and type(func) ~= 'function' then
error("Invalid functionCalls item: " .. name .. " not a function")
end
local function buildCallCapture(name)
return function(state)
if func == false then
error("Function call on '" .. name .. "' not permitted")
end
state:push()
state:new_call(name, func)
end
end
local nameCallCapture
if type(name) == 'string' then
nameCallCapture = lpeg.P(name .. "(") * lpeg.Cc(name) / buildCallCapture
else
-- Name matcher expected to produce a capture
nameCallCapture = name * "(" / buildCallCapture
end
-- Call func over nameCallCapture and value to permit function receiving name
return nameCallCapture
end
local function generateNamedCallLexers(options)
if not options.calls or not options.calls.defs then
return
end
local callCapture
for name, func in pairs(options.calls.defs) do
local newCapture = generateSingleCallLexer(name, func)
if not callCapture then
callCapture = newCapture
else
callCapture = callCapture + newCapture
end
end
return callCapture
end
local function generateCallLexer(options)
local lexer
local namedCapture = generateNamedCallLexers(options)
if options.calls and options.calls.allowUndefined then
lexer = generateSingleCallLexer(lpeg.C(util.identifier), true)
end
if namedCapture then
lexer = lexer and lexer + namedCapture or namedCapture
end
if lexer then
lexer = lexer + lpeg.P(")") * lpeg.Cc(END_CALL)
end
return lexer
end
local function generateLexer(options)
local ignored = options.ignored
local array_options, object_options = options.array, options.object
local lexer =
lpeg.P("[") * lpeg.Cc(BEGIN_ARRAY)
+ lpeg.P("]") * lpeg.Cc(END_ARRAY)
+ lpeg.P("{") * lpeg.Cc(BEGIN_OBJECT)
+ lpeg.P("}") * lpeg.Cc(END_OBJECT)
+ lpeg.P(":") * lpeg.Cc(SET_KEY)
+ lpeg.P(",") * lpeg.Cc(NEXT_VALUE)
if object_options.identifier then
-- Add identifier match w/ validation check that it is in key
lexer = lexer + lpeg.C(util.identifier) * ignored * lpeg.P(":") * lpeg.Cc(SET_KEY)
end
local callLexers = generateCallLexer(options)
if callLexers then
lexer = lexer + callLexers
end
return lexer
end
local composite = {
mergeOptions = mergeOptions,
generateLexer = generateLexer
}
return composite

View File

@@ -0,0 +1,100 @@
--[[
Licensed according to the included 'LICENSE' document
Author: Thomas Harning Jr <harningt@gmail.com>
]]
local lpeg = require("lpeg")
local tonumber = tonumber
local jsonutil = require("json.util")
local merge = jsonutil.merge
local util = require("json.decode.util")
local _ENV = nil
local digit = lpeg.R("09")
local digits = digit^1
-- Illegal octal declaration
local illegal_octal_detect = #(lpeg.P('0') * digits) * util.denied("Octal numbers")
local int = (lpeg.P('-') + 0) * (lpeg.R("19") * digits + illegal_octal_detect + digit)
local frac = lpeg.P('.') * digits
local exp = lpeg.S("Ee") * (lpeg.S("-+") + 0) * digits
local nan = lpeg.S("Nn") * lpeg.S("Aa") * lpeg.S("Nn")
local inf = lpeg.S("Ii") * lpeg.P("nfinity")
local ninf = lpeg.P('-') * lpeg.S("Ii") * lpeg.P("nfinity")
local hex = (lpeg.P("0x") + lpeg.P("0X")) * lpeg.R("09","AF","af")^1
local defaultOptions = {
nan = true,
inf = true,
frac = true,
exp = true,
hex = false
}
local modeOptions = {}
modeOptions.strict = {
nan = false,
inf = false
}
local nan_value = 0/0
local inf_value = 1/0
local ninf_value = -1/0
--[[
Options: configuration options for number rules
nan: match NaN
inf: match Infinity
frac: match fraction portion (.0)
exp: match exponent portion (e1)
DEFAULT: nan, inf, frac, exp
]]
local function mergeOptions(options, mode)
jsonutil.doOptionMerge(options, false, 'number', defaultOptions, mode and modeOptions[mode])
end
local function generateLexer(options)
options = options.number
local ret = int
if options.frac then
ret = ret * (frac + 0)
else
ret = ret * (#frac * util.denied("Fractions", "number.frac") + 0)
end
if options.exp then
ret = ret * (exp + 0)
else
ret = ret * (#exp * util.denied("Exponents", "number.exp") + 0)
end
if options.hex then
ret = hex + ret
else
ret = #hex * util.denied("Hexadecimal", "number.hex") + ret
end
-- Capture number now
ret = ret / tonumber
if options.nan then
ret = ret + nan / function() return nan_value end
else
ret = ret + #nan * util.denied("NaN", "number.nan")
end
if options.inf then
ret = ret + ninf / function() return ninf_value end + inf / function() return inf_value end
else
ret = ret + (#ninf + #inf) * util.denied("+/-Inf", "number.inf")
end
return ret
end
local number = {
int = int,
mergeOptions = mergeOptions,
generateLexer = generateLexer
}
return number

View File

@@ -0,0 +1,103 @@
--[[
Licensed according to the included 'LICENSE' document
Author: Thomas Harning Jr <harningt@gmail.com>
--]]
local lpeg = require("lpeg")
local util = require("json.decode.util")
local merge = require("json.util").merge
local tonumber = tonumber
local unpack = unpack
local print = print
local tostring = tostring
local rawset = rawset
module("json.decode.object")
-- BEGIN LPEG < 0.9 SUPPORT
local initObject, applyObjectKey
if not (lpeg.Cg and lpeg.Cf and lpeg.Ct) then
function initObject()
return {}
end
function applyObjectKey(tab, key, val)
tab[key] = val
return tab
end
end
-- END LPEG < 0.9 SUPPORT
local defaultOptions = {
number = true,
identifier = true,
trailingComma = true
}
default = nil -- Let the buildCapture optimization take place
strict = {
number = false,
identifier = false,
trailingComma = false
}
local function buildItemSequence(objectItem, ignored)
return (objectItem * (ignored * lpeg.P(",") * ignored * objectItem)^0) + 0
end
local function buildCapture(options, global_options)
local ignored = global_options.ignored
local string_type = lpeg.V(util.types.STRING)
local integer_type = lpeg.V(util.types.INTEGER)
local value_type = lpeg.V(util.types.VALUE)
options = options and merge({}, defaultOptions, options) or defaultOptions
local key = string_type
if options.identifier then
key = key + lpeg.C(util.identifier)
end
if options.number then
key = key + integer_type
end
local objectItems
local objectItem = (key * ignored * lpeg.P(":") * ignored * value_type)
-- BEGIN LPEG < 0.9 SUPPORT
if not (lpeg.Cg and lpeg.Cf and lpeg.Ct) then
local set_key = applyObjectKey
if options.setObjectKey then
local setObjectKey = options.setObjectKey
set_key = function(tab, key, val)
setObjectKey(tab, key, val)
return tab
end
end
objectItems = buildItemSequence(objectItem / set_key, ignored)
objectItems = lpeg.Ca(lpeg.Cc(false) / initObject * objectItems)
-- END LPEG < 0.9 SUPPORT
else
objectItems = buildItemSequence(lpeg.Cg(objectItem), ignored)
objectItems = lpeg.Cf(lpeg.Ct(0) * objectItems, options.setObjectKey or rawset)
end
local capture = lpeg.P("{") * ignored
capture = capture * objectItems * ignored
if options.trailingComma then
capture = capture * (lpeg.P(",") + 0) * ignored
end
capture = capture * lpeg.P("}")
return capture
end
function register_types()
util.register_type("OBJECT")
end
function load_types(options, global_options, grammar)
local capture = buildCapture(options, global_options)
local object_id = util.types.OBJECT
grammar[object_id] = capture
util.append_grammar_item(grammar, "VALUE", lpeg.V(object_id))
end

View File

@@ -0,0 +1,62 @@
--[[
Licensed according to the included 'LICENSE' document
Author: Thomas Harning Jr <harningt@gmail.com>
]]
local lpeg = require("lpeg")
local jsonutil = require("json.util")
local merge = jsonutil.merge
local util = require("json.decode.util")
-- Container module for other JavaScript types (bool, null, undefined)
local _ENV = nil
-- For null and undefined, use the util.null value to preserve null-ness
local booleanCapture =
lpeg.P("true") * lpeg.Cc(true)
+ lpeg.P("false") * lpeg.Cc(false)
local nullCapture = lpeg.P("null")
local undefinedCapture = lpeg.P("undefined")
local defaultOptions = {
allowUndefined = true,
null = jsonutil.null,
undefined = jsonutil.undefined
}
local modeOptions = {}
modeOptions.simple = {
null = false, -- Mapped to nil
undefined = false -- Mapped to nil
}
modeOptions.strict = {
allowUndefined = false
}
local function mergeOptions(options, mode)
jsonutil.doOptionMerge(options, false, 'others', defaultOptions, mode and modeOptions[mode])
end
local function generateLexer(options)
-- The 'or nil' clause allows false to map to a nil value since 'nil' cannot be merged
options = options.others
local valueCapture = (
booleanCapture
+ nullCapture * lpeg.Cc(options.null or nil)
)
if options.allowUndefined then
valueCapture = valueCapture + undefinedCapture * lpeg.Cc(options.undefined or nil)
else
valueCapture = valueCapture + #undefinedCapture * util.denied("undefined", "others.allowUndefined")
end
return valueCapture
end
local others = {
mergeOptions = mergeOptions,
generateLexer = generateLexer
}
return others

View File

@@ -0,0 +1,189 @@
--[[
Licensed according to the included 'LICENSE' document
Author: Thomas Harning Jr <harningt@gmail.com>
]]
local setmetatable = setmetatable
local jsonutil = require("json.util")
local assert = assert
local type = type
local next = next
local unpack = require("table").unpack or unpack
local _ENV = nil
local state_ops = {}
local state_mt = {
__index = state_ops
}
function state_ops.pop(self)
self.previous_set = true
self.previous = self.active
local i = self.i
-- Load in this array into the active item
self.active = self.stack[i]
self.active_state = self.state_stack[i]
self.active_key = self.key_stack[i]
self.stack[i] = nil
self.state_stack[i] = nil
self.key_stack[i] = nil
self.i = i - 1
end
function state_ops.push(self)
local i = self.i + 1
self.i = i
self.stack[i] = self.active
self.state_stack[i] = self.active_state
self.key_stack[i] = self.active_key
end
function state_ops.put_object_value(self, trailing)
local object_options = self.options.object
if trailing and object_options.trailingComma then
if not self.active_key then
return
end
end
assert(self.active_key, "Missing key value")
object_options.setObjectKey(self.active, self.active_key, self:grab_value())
self.active_key = nil
end
function state_ops.put_array_value(self, trailing)
-- Safety check
if trailing and not self.previous_set and self.options.array.trailingComma then
return
end
local new_index = self.active_state + 1
self.active_state = new_index
self.active[new_index] = self:grab_value()
end
function state_ops.put_value(self, trailing)
if self.active_state == 'object' then
self:put_object_value(trailing)
else
self:put_array_value(trailing)
end
end
function state_ops.new_array(self)
local new_array = {}
if jsonutil.InitArray then
new_array = jsonutil.InitArray(new_array) or new_array
end
self.active = new_array
self.active_state = 0
self.active_key = nil
self:unset_value()
end
function state_ops.end_array(self)
if self.previous_set or self.active_state ~= 0 then
-- Not an empty array
self:put_value(true)
end
if self.active_state ~= #self.active then
-- Store the length in
self.active.n = self.active_state
end
end
function state_ops.new_object(self)
local new_object = {}
self.active = new_object
self.active_state = 'object'
self.active_key = nil
self:unset_value()
end
function state_ops.end_object(self)
if self.previous_set or next(self.active) then
-- Not an empty object
self:put_value(true)
end
end
function state_ops.new_call(self, name, func)
-- TODO setup properly
local new_call = {}
new_call.name = name
new_call.func = func
self.active = new_call
self.active_state = 0
self.active_key = nil
self:unset_value()
end
function state_ops.end_call(self)
if self.previous_set or self.active_state ~= 0 then
-- Not an empty array
self:put_value(true)
end
if self.active_state ~= #self.active then
-- Store the length in
self.active.n = self.active_state
end
local func = self.active.func
if func == true then
func = jsonutil.buildCall
end
self.active = func(self.active.name, unpack(self.active, 1, self.active.n or #self.active))
end
function state_ops.unset_value(self)
self.previous_set = false
self.previous = nil
end
function state_ops.grab_value(self)
assert(self.previous_set, "Previous value not set")
self.previous_set = false
return self.previous
end
function state_ops.set_value(self, value)
assert(not self.previous_set, "Value set when one already in slot")
self.previous_set = true
self.previous = value
end
function state_ops.set_key(self)
assert(self.active_state == 'object', "Cannot set key on array")
local value = self:grab_value()
local value_type = type(value)
if self.options.object.number then
assert(value_type == 'string' or value_type == 'number', "As configured, a key must be a number or string")
else
assert(value_type == 'string', "As configured, a key must be a string")
end
self.active_key = value
end
local function create(options)
local ret = {
options = options,
stack = {},
state_stack = {},
key_stack = {},
i = 0,
active = nil,
active_key = nil,
previous = nil,
active_state = nil
}
return setmetatable(ret, state_mt)
end
local state = {
create = create
}
return state

View File

@@ -0,0 +1,133 @@
--[[
Licensed according to the included 'LICENSE' document
Author: Thomas Harning Jr <harningt@gmail.com>
]]
local lpeg = require("lpeg")
local jsonutil = require("json.util")
local util = require("json.decode.util")
local merge = jsonutil.merge
local tonumber = tonumber
local string_char = require("string").char
local floor = require("math").floor
local table_concat = require("table").concat
local error = error
local _ENV = nil
local function get_error(item)
local fmt_string = item .. " in string [%q] @ %i:%i"
return lpeg.P(function(data, index)
local line, line_index, bad_char, last_line = util.get_invalid_character_info(data, index)
local err = fmt_string:format(bad_char, line, line_index)
error(err)
end) * 1
end
local bad_unicode = get_error("Illegal unicode escape")
local bad_hex = get_error("Illegal hex escape")
local bad_character = get_error("Illegal character")
local bad_escape = get_error("Illegal escape")
local knownReplacements = {
["'"] = "'",
['"'] = '"',
['\\'] = '\\',
['/'] = '/',
b = '\b',
f = '\f',
n = '\n',
r = '\r',
t = '\t',
v = '\v',
z = '\z'
}
-- according to the table at http://da.wikipedia.org/wiki/UTF-8
local function utf8DecodeUnicode(code1, code2)
code1, code2 = tonumber(code1, 16), tonumber(code2, 16)
if code1 == 0 and code2 < 0x80 then
return string_char(code2)
end
if code1 < 0x08 then
return string_char(
0xC0 + code1 * 4 + floor(code2 / 64),
0x80 + code2 % 64)
end
return string_char(
0xE0 + floor(code1 / 16),
0x80 + (code1 % 16) * 4 + floor(code2 / 64),
0x80 + code2 % 64)
end
local function decodeX(code)
code = tonumber(code, 16)
return string_char(code)
end
local doSimpleSub = lpeg.C(lpeg.S("'\"\\/bfnrtvz")) / knownReplacements
local doUniSub = lpeg.P('u') * (lpeg.C(util.hexpair) * lpeg.C(util.hexpair) + bad_unicode)
local doXSub = lpeg.P('x') * (lpeg.C(util.hexpair) + bad_hex)
local defaultOptions = {
badChars = '',
additionalEscapes = false, -- disallow untranslated escapes
escapeCheck = #lpeg.S('bfnrtv/\\"xu\'z'), -- no check on valid characters
decodeUnicode = utf8DecodeUnicode,
strict_quotes = false
}
local modeOptions = {}
modeOptions.strict = {
badChars = '\b\f\n\r\t\v',
additionalEscapes = false, -- no additional escapes
escapeCheck = #lpeg.S('bfnrtv/\\"u'), --only these chars are allowed to be escaped
strict_quotes = true
}
local function mergeOptions(options, mode)
jsonutil.doOptionMerge(options, false, 'strings', defaultOptions, mode and modeOptions[mode])
end
local function buildCaptureString(quote, badChars, escapeMatch)
local captureChar = (1 - lpeg.S("\\" .. badChars .. quote)) + (lpeg.P("\\") / "" * escapeMatch)
-- During error, force end
local captureString = captureChar^0 + (-#lpeg.P(quote) * bad_character + -1)
return lpeg.P(quote) * lpeg.Cs(captureString) * lpeg.P(quote)
end
local function generateLexer(options)
options = options.strings
local quotes = { '"' }
if not options.strict_quotes then
quotes[#quotes + 1] = "'"
end
local escapeMatch = doSimpleSub
escapeMatch = escapeMatch + doXSub / decodeX
escapeMatch = escapeMatch + doUniSub / options.decodeUnicode
if options.escapeCheck then
escapeMatch = options.escapeCheck * escapeMatch + bad_escape
end
if options.additionalEscapes then
escapeMatch = options.additionalEscapes + escapeMatch
end
local captureString
for i = 1, #quotes do
local cap = buildCaptureString(quotes[i], options.badChars, escapeMatch)
if captureString == nil then
captureString = cap
else
captureString = captureString + cap
end
end
return captureString
end
local strings = {
mergeOptions = mergeOptions,
generateLexer = generateLexer
}
return strings

View File

@@ -0,0 +1,121 @@
--[[
Licensed according to the included 'LICENSE' document
Author: Thomas Harning Jr <harningt@gmail.com>
]]
local lpeg = require("lpeg")
local select = select
local pairs, ipairs = pairs, ipairs
local tonumber = tonumber
local string_char = require("string").char
local rawset = rawset
local jsonutil = require("json.util")
local error = error
local setmetatable = setmetatable
local table_concat = require("table").concat
local merge = require("json.util").merge
local _ENV = nil
local function get_invalid_character_info(input, index)
local parsed = input:sub(1, index)
local bad_character = input:sub(index, index)
local _, line_number = parsed:gsub('\n',{})
local last_line = parsed:match("\n([^\n]+.)$") or parsed
return line_number, #last_line, bad_character, last_line
end
local function build_report(msg)
local fmt = msg:gsub("%%", "%%%%") .. " @ character: %i %i:%i [%s] line:\n%s"
return lpeg.P(function(data, pos)
local line, line_index, bad_char, last_line = get_invalid_character_info(data, pos)
local text = fmt:format(pos, line, line_index, bad_char, last_line)
error(text)
end) * 1
end
local function unexpected()
local msg = "unexpected character"
return build_report(msg)
end
local function denied(item, option)
local msg
if option then
msg = ("'%s' denied by option set '%s'"):format(item, option)
else
msg = ("'%s' denied"):format(item)
end
return build_report(msg)
end
-- 09, 0A, 0B, 0C, 0D, 20
local ascii_space = lpeg.S("\t\n\v\f\r ")
local unicode_space
do
local chr = string_char
local u_space = ascii_space
-- \u0085 \u00A0
u_space = u_space + lpeg.P(chr(0xC2)) * lpeg.S(chr(0x85) .. chr(0xA0))
-- \u1680 \u180E
u_space = u_space + lpeg.P(chr(0xE1)) * (lpeg.P(chr(0x9A, 0x80)) + chr(0xA0, 0x8E))
-- \u2000 - \u200A, also 200B
local spacing_end = ""
for i = 0x80,0x8b do
spacing_end = spacing_end .. chr(i)
end
-- \u2028 \u2029 \u202F
spacing_end = spacing_end .. chr(0xA8) .. chr(0xA9) .. chr(0xAF)
u_space = u_space + lpeg.P(chr(0xE2, 0x80)) * lpeg.S(spacing_end)
-- \u205F
u_space = u_space + lpeg.P(chr(0xE2, 0x81, 0x9F))
-- \u3000
u_space = u_space + lpeg.P(chr(0xE3, 0x80, 0x80))
-- BOM \uFEFF
u_space = u_space + lpeg.P(chr(0xEF, 0xBB, 0xBF))
unicode_space = u_space
end
local identifier = lpeg.R("AZ","az","__") * lpeg.R("AZ","az", "__", "09") ^0
local hex = lpeg.R("09","AF","af")
local hexpair = hex * hex
local comments = {
cpp = lpeg.P("//") * (1 - lpeg.P("\n"))^0 * lpeg.P("\n"),
c = lpeg.P("/*") * (1 - lpeg.P("*/"))^0 * lpeg.P("*/")
}
local comment = comments.cpp + comments.c
local ascii_ignored = (ascii_space + comment)^0
local unicode_ignored = (unicode_space + comment)^0
-- Parse the lpeg version skipping patch-values
-- LPEG <= 0.7 have no version value... so 0.7 is value
local DecimalLpegVersion = lpeg.version and tonumber(lpeg.version():match("^(%d+%.%d+)")) or 0.7
local function setObjectKeyForceNumber(t, key, value)
key = tonumber(key) or key
return rawset(t, key, value)
end
local util = {
unexpected = unexpected,
denied = denied,
ascii_space = ascii_space,
unicode_space = unicode_space,
identifier = identifier,
hex = hex,
hexpair = hexpair,
comments = comments,
comment = comment,
ascii_ignored = ascii_ignored,
unicode_ignored = unicode_ignored,
DecimalLpegVersion = DecimalLpegVersion,
get_invalid_character_info = get_invalid_character_info,
setObjectKeyForceNumber = setObjectKeyForceNumber
}
return util

View File

@@ -0,0 +1,161 @@
--[[
Licensed according to the included 'LICENSE' document
Author: Thomas Harning Jr <harningt@gmail.com>
]]
local type = type
local assert, error = assert, error
local getmetatable, setmetatable = getmetatable, setmetatable
local ipairs, pairs = ipairs, pairs
local require = require
local output = require("json.encode.output")
local util = require("json.util")
local util_merge, isCall = util.merge, util.isCall
local _ENV = nil
--[[
List of encoding modules to load.
Loaded in sequence such that earlier encoders get priority when
duplicate type-handlers exist.
]]
local modulesToLoad = {
"strings",
"number",
"calls",
"others",
"array",
"object"
}
-- Modules that have been loaded
local loadedModules = {}
local json_encode = {}
-- Configuration bases for client apps
local modes_defined = { "default", "strict" }
json_encode.default = {}
json_encode.strict = {
initialObject = true -- Require an object at the root
}
-- For each module, load it and its defaults
for _,name in ipairs(modulesToLoad) do
local mod = require("json.encode." .. name)
if mod.mergeOptions then
for _, mode in pairs(modes_defined) do
mod.mergeOptions(json_encode[mode], mode)
end
end
loadedModules[name] = mod
end
-- NOTE: Nested not found, so assume unsupported until use case arises
local function flattenOutput(out, value)
assert(type(value) ~= 'table')
out = out or {}
out[#out + 1] = value
return out
end
-- Prepares the encoding map from the already provided modules and new config
local function prepareEncodeMap(options)
local map = {}
for _, name in ipairs(modulesToLoad) do
local encodermap = loadedModules[name].getEncoder(options[name])
for valueType, encoderSet in pairs(encodermap) do
map[valueType] = flattenOutput(map[valueType], encoderSet)
end
end
return map
end
--[[
Encode a value with a given encoding map and state
]]
local function encodeWithMap(value, map, state, isObjectKey)
local t = type(value)
local encoderList = assert(map[t], "Failed to encode value, unhandled type: " .. t)
for _, encoder in ipairs(encoderList) do
local ret = encoder(value, state, isObjectKey)
if false ~= ret then
return ret
end
end
error("Failed to encode value, encoders for " .. t .. " deny encoding")
end
local function getBaseEncoder(options)
local encoderMap = prepareEncodeMap(options)
if options.preProcess then
local preProcess = options.preProcess
return function(value, state, isObjectKey)
local ret = preProcess(value, isObjectKey or false)
if nil ~= ret then
value = ret
end
return encodeWithMap(value, encoderMap, state)
end
end
return function(value, state, isObjectKey)
return encodeWithMap(value, encoderMap, state)
end
end
--[[
Retreive an initial encoder instance based on provided options
the initial encoder is responsible for initializing state
State has at least these values configured: encode, check_unique, already_encoded
]]
function json_encode.getEncoder(options)
options = options and util_merge({}, json_encode.default, options) or json_encode.default
local encode = getBaseEncoder(options)
local function initialEncode(value)
if options.initialObject then
local errorMessage = "Invalid arguments: expects a JSON Object or Array at the root"
assert(type(value) == 'table' and not isCall(value, options), errorMessage)
end
local alreadyEncoded = {}
local function check_unique(value)
assert(not alreadyEncoded[value], "Recursive encoding of value")
alreadyEncoded[value] = true
end
local outputEncoder = options.output and options.output() or output.getDefault()
local state = {
encode = encode,
check_unique = check_unique,
already_encoded = alreadyEncoded, -- To unmark encoding when moving up stack
outputEncoder = outputEncoder
}
local ret = encode(value, state)
if nil ~= ret then
return outputEncoder.simple and outputEncoder.simple(ret) or ret
end
end
return initialEncode
end
-- CONSTRUCT STATE WITH FOLLOWING (at least)
--[[
encoder
check_unique -- used by inner encoders to make sure value is unique
already_encoded -- used to unmark a value as unique
]]
function json_encode.encode(data, options)
return json_encode.getEncoder(options)(data)
end
local mt = {}
mt.__call = function(self, ...)
return json_encode.encode(...)
end
setmetatable(json_encode, mt)
return json_encode

View File

@@ -0,0 +1,110 @@
--[[
Licensed according to the included 'LICENSE' document
Author: Thomas Harning Jr <harningt@gmail.com>
]]
local jsonutil = require("json.util")
local type = type
local pairs = pairs
local assert = assert
local table = require("table")
local math = require("math")
local table_concat = table.concat
local math_floor, math_modf = math.floor, math.modf
local jsonutil = require("json.util")
local util_IsArray = jsonutil.IsArray
local _ENV = nil
local defaultOptions = {
isArray = util_IsArray
}
local modeOptions = {}
local function mergeOptions(options, mode)
jsonutil.doOptionMerge(options, false, 'array', defaultOptions, mode and modeOptions[mode])
end
--[[
Utility function to determine whether a table is an array or not.
Criteria for it being an array:
* ExternalIsArray returns true (or false directly reports not-array)
* If the table has an 'n' value that is an integer >= 1 then it
is an array... may result in false positives (should check some values
before it)
* It is a contiguous list of values with zero string-based keys
]]
local function isArray(val, options)
local externalIsArray = options and options.isArray
if externalIsArray then
local ret = externalIsArray(val)
if ret == true or ret == false then
return ret
end
end
-- Use the 'n' element if it's a number
if type(val.n) == 'number' and math_floor(val.n) == val.n and val.n >= 1 then
return true
end
local len = #val
for k,v in pairs(val) do
if type(k) ~= 'number' then
return false
end
local _, decim = math_modf(k)
if not (decim == 0 and 1<=k) then
return false
end
if k > len then -- Use Lua's length as absolute determiner
return false
end
end
return true
end
--[[
Cleanup function to unmark a value as in the encoding process and return
trailing results
]]
local function unmarkAfterEncode(tab, state, ...)
state.already_encoded[tab] = nil
return ...
end
local function getEncoder(options)
options = options and jsonutil.merge({}, defaultOptions, options) or defaultOptions
local function encodeArray(tab, state)
if not isArray(tab, options) then
return false
end
-- Make sure this value hasn't been encoded yet
state.check_unique(tab)
local encode = state.encode
local compositeEncoder = state.outputEncoder.composite
local valueEncoder = [[
for i = 1, (composite.n or #composite) do
local val = composite[i]
PUTINNER(i ~= 1)
val = encode(val, state)
val = val or ''
if val then
PUTVALUE(val)
end
end
]]
return unmarkAfterEncode(tab, state, compositeEncoder(valueEncoder, '[', ']', ',', tab, encode, state))
end
return { table = encodeArray }
end
local array = {
mergeOptions = mergeOptions,
isArray = isArray,
getEncoder = getEncoder
}
return array

View File

@@ -0,0 +1,68 @@
--[[
Licensed according to the included 'LICENSE' document
Author: Thomas Harning Jr <harningt@gmail.com>
]]
local table = require("table")
local table_concat = table.concat
local select = select
local getmetatable, setmetatable = getmetatable, setmetatable
local assert = assert
local jsonutil = require("json.util")
local isCall, decodeCall = jsonutil.isCall, jsonutil.decodeCall
local _ENV = nil
local defaultOptions = {
}
-- No real default-option handling needed...
local modeOptions = {}
local function mergeOptions(options, mode)
jsonutil.doOptionMerge(options, false, 'calls', defaultOptions, mode and modeOptions[mode])
end
--[[
Encodes 'value' as a function call
Must have parameters in the 'callData' field of the metatable
name == name of the function call
parameters == array of parameters to encode
]]
local function getEncoder(options)
options = options and jsonutil.merge({}, defaultOptions, options) or defaultOptions
local function encodeCall(value, state)
if not isCall(value) then
return false
end
local encode = state.encode
local name, params = decodeCall(value)
local compositeEncoder = state.outputEncoder.composite
local valueEncoder = [[
for i = 1, (composite.n or #composite) do
local val = composite[i]
PUTINNER(i ~= 1)
val = encode(val, state)
val = val or ''
if val then
PUTVALUE(val)
end
end
]]
return compositeEncoder(valueEncoder, name .. '(', ')', ',', params, encode, state)
end
return {
table = encodeCall,
['function'] = encodeCall
}
end
local calls = {
mergeOptions = mergeOptions,
getEncoder = getEncoder
}
return calls

View File

@@ -0,0 +1,58 @@
--[[
Licensed according to the included 'LICENSE' document
Author: Thomas Harning Jr <harningt@gmail.com>
]]
local tostring = tostring
local assert = assert
local jsonutil = require("json.util")
local huge = require("math").huge
local _ENV = nil
local defaultOptions = {
nan = true,
inf = true
}
local modeOptions = {}
modeOptions.strict = {
nan = false,
inf = false
}
local function mergeOptions(options, mode)
jsonutil.doOptionMerge(options, false, 'number', defaultOptions, mode and modeOptions[mode])
end
local function encodeNumber(number, options)
if number ~= number then
assert(options.nan, "Invalid number: NaN not enabled")
return "NaN"
end
if number == huge then
assert(options.inf, "Invalid number: Infinity not enabled")
return "Infinity"
end
if number == -huge then
assert(options.inf, "Invalid number: Infinity not enabled")
return "-Infinity"
end
return tostring(number)
end
local function getEncoder(options)
options = options and jsonutil.merge({}, defaultOptions, options) or defaultOptions
return {
number = function(number, state)
return encodeNumber(number, options)
end
}
end
local number = {
mergeOptions = mergeOptions,
getEncoder = getEncoder
}
return number

View File

@@ -0,0 +1,77 @@
--[[
Licensed according to the included 'LICENSE' document
Author: Thomas Harning Jr <harningt@gmail.com>
]]
local pairs = pairs
local assert = assert
local type = type
local tostring = tostring
local table_concat = require("table").concat
local jsonutil = require("json.util")
local _ENV = nil
local defaultOptions = {
}
local modeOptions = {}
local function mergeOptions(options, mode)
jsonutil.doOptionMerge(options, false, 'object', defaultOptions, mode and modeOptions[mode])
end
--[[
Cleanup function to unmark a value as in the encoding process and return
trailing results
]]
local function unmarkAfterEncode(tab, state, ...)
state.already_encoded[tab] = nil
return ...
end
--[[
Encode a table as a JSON Object ( keys = strings, values = anything else )
]]
local function encodeTable(tab, options, state)
-- Make sure this value hasn't been encoded yet
state.check_unique(tab)
local encode = state.encode
local compositeEncoder = state.outputEncoder.composite
local valueEncoder = [[
local first = true
for k, v in pairs(composite) do
local ti = type(k)
assert(ti == 'string' or ti == 'number' or ti == 'boolean', "Invalid object index type: " .. ti)
local name = encode(tostring(k), state, true)
if first then
first = false
else
name = ',' .. name
end
PUTVALUE(name .. ':')
local val = encode(v, state)
val = val or ''
if val then
PUTVALUE(val)
end
end
]]
return unmarkAfterEncode(tab, state, compositeEncoder(valueEncoder, '{', '}', nil, tab, encode, state))
end
local function getEncoder(options)
options = options and jsonutil.merge({}, defaultOptions, options) or defaultOptions
return {
table = function(tab, state)
return encodeTable(tab, options, state)
end
}
end
local object = {
mergeOptions = mergeOptions,
getEncoder = getEncoder
}
return object

View File

@@ -0,0 +1,66 @@
--[[
Licensed according to the included 'LICENSE' document
Author: Thomas Harning Jr <harningt@gmail.com>
]]
local tostring = tostring
local assert = assert
local jsonutil = require("json.util")
local type = type
local _ENV = nil
-- Shortcut that works
local encodeBoolean = tostring
local defaultOptions = {
allowUndefined = true,
null = jsonutil.null,
undefined = jsonutil.undefined
}
local modeOptions = {}
modeOptions.strict = {
allowUndefined = false
}
local function mergeOptions(options, mode)
jsonutil.doOptionMerge(options, false, 'others', defaultOptions, mode and modeOptions[mode])
end
local function getEncoder(options)
options = options and jsonutil.merge({}, defaultOptions, options) or defaultOptions
local function encodeOthers(value, state)
if value == options.null then
return 'null'
elseif value == options.undefined then
assert(options.allowUndefined, "Invalid value: Unsupported 'Undefined' parameter")
return 'undefined'
else
return false
end
end
local function encodeBoolean(value, state)
return value and 'true' or 'false'
end
local nullType = type(options.null)
local undefinedType = options.undefined and type(options.undefined)
-- Make sure that all of the types handled here are handled
local ret = {
boolean = encodeBoolean,
['nil'] = function() return 'null' end,
[nullType] = encodeOthers
}
if undefinedType then
ret[undefinedType] = encodeOthers
end
return ret
end
local others = {
encodeBoolean = encodeBoolean,
mergeOptions = mergeOptions,
getEncoder = getEncoder
}
return others

View File

@@ -0,0 +1,91 @@
--[[
Licensed according to the included 'LICENSE' document
Author: Thomas Harning Jr <harningt@gmail.com>
]]
local type = type
local assert, error = assert, error
local table_concat = require("table").concat
local loadstring = loadstring or load
local io = require("io")
local setmetatable = setmetatable
local output_utility = require("json.encode.output_utility")
local _ENV = nil
local tableCompositeCache = setmetatable({}, {__mode = 'v'})
local TABLE_VALUE_WRITER = [[
ret[#ret + 1] = %VALUE%
]]
local TABLE_INNER_WRITER = ""
--[[
nextValues can output a max of two values to throw into the data stream
expected to be called until nil is first return value
value separator should either be attached to v1 or in innerValue
]]
local function defaultTableCompositeWriter(nextValues, beginValue, closeValue, innerValue, composite, encode, state)
if type(nextValues) == 'string' then
local fun = output_utility.prepareEncoder(defaultTableCompositeWriter, nextValues, innerValue, TABLE_VALUE_WRITER, TABLE_INNER_WRITER)
local ret = {}
fun(composite, ret, encode, state)
return beginValue .. table_concat(ret, innerValue) .. closeValue
end
end
-- no 'simple' as default action is just to return the value
local function getDefault()
return { composite = defaultTableCompositeWriter }
end
-- BEGIN IO-WRITER OUTPUT
local IO_INNER_WRITER = [[
if %WRITE_INNER% then
state.__outputFile:write(%INNER_VALUE%)
end
]]
local IO_VALUE_WRITER = [[
state.__outputFile:write(%VALUE%)
]]
local function buildIoWriter(output)
if not output then -- Default to stdout
output = io.output()
end
local function ioWriter(nextValues, beginValue, closeValue, innerValue, composite, encode, state)
-- HOOK OUTPUT STATE
state.__outputFile = output
if type(nextValues) == 'string' then
local fun = output_utility.prepareEncoder(ioWriter, nextValues, innerValue, IO_VALUE_WRITER, IO_INNER_WRITER)
local ret = {}
output:write(beginValue)
fun(composite, ret, encode, state)
output:write(closeValue)
return nil
end
end
local function ioSimpleWriter(encoded)
if encoded then
output:write(encoded)
end
return nil
end
return { composite = ioWriter, simple = ioSimpleWriter }
end
local function getIoWriter(output)
return function()
return buildIoWriter(output)
end
end
local output = {
getDefault = getDefault,
getIoWriter = getIoWriter
}
return output

View File

@@ -0,0 +1,54 @@
--[[
Licensed according to the included 'LICENSE' document
Author: Thomas Harning Jr <harningt@gmail.com>
]]
local setmetatable = setmetatable
local assert, loadstring = assert, loadstring or load
local _ENV = nil
-- Key == weak, if main key goes away, then cache cleared
local outputCache = setmetatable({}, {__mode = 'k'})
-- TODO: inner tables weak?
local function buildFunction(nextValues, innerValue, valueWriter, innerWriter)
local putInner = ""
if innerValue and innerWriter then
-- Prepare the lua-string representation of the separator to put in between values
local formattedInnerValue = ("%q"):format(innerValue)
-- Fill in the condition %WRITE_INNER% and the %INNER_VALUE% to actually write
putInner = innerWriter:gsub("%%WRITE_INNER%%", "%%1"):gsub("%%INNER_VALUE%%", formattedInnerValue)
end
-- Template-in the value writer (if present) and its conditional argument
local functionCode = nextValues:gsub("PUTINNER(%b())", putInner)
-- %VALUE% is to be filled in by the value-to-write
valueWriter = valueWriter:gsub("%%VALUE%%", "%%1")
-- Template-in the value writer with its argument
functionCode = functionCode:gsub("PUTVALUE(%b())", valueWriter)
functionCode = [[
return function(composite, ret, encode, state)
]] .. functionCode .. [[
end
]]
return assert(loadstring(functionCode))()
end
local function prepareEncoder(cacheKey, nextValues, innerValue, valueWriter, innerWriter)
local cache = outputCache[cacheKey]
if not cache then
cache = {}
outputCache[cacheKey] = cache
end
local fun = cache[nextValues]
if not fun then
fun = buildFunction(nextValues, innerValue, valueWriter, innerWriter)
cache[nextValues] = fun
end
return fun
end
local output_utility = {
prepareEncoder = prepareEncoder
}
return output_utility

View File

@@ -0,0 +1,88 @@
--[[
Licensed according to the included 'LICENSE' document
Author: Thomas Harning Jr <harningt@gmail.com>
]]
local string_char = require("string").char
local pairs = pairs
local jsonutil = require("json.util")
local util_merge = jsonutil.merge
local _ENV = nil
local normalEncodingMap = {
['"'] = '\\"',
['\\'] = '\\\\',
['/'] = '\\/',
['\b'] = '\\b',
['\f'] = '\\f',
['\n'] = '\\n',
['\r'] = '\\r',
['\t'] = '\\t',
['\v'] = '\\v' -- not in official spec, on report, removing
}
local xEncodingMap = {}
for char, encoded in pairs(normalEncodingMap) do
xEncodingMap[char] = encoded
end
-- Pre-encode the control characters to speed up encoding...
-- NOTE: UTF-8 may not work out right w/ JavaScript
-- JavaScript uses 2 bytes after a \u... yet UTF-8 is a
-- byte-stream encoding, not pairs of bytes (it does encode
-- some letters > 1 byte, but base case is 1)
for i = 0, 255 do
local c = string_char(i)
if c:match('[%z\1-\031\128-\255]') and not normalEncodingMap[c] then
-- WARN: UTF8 specializes values >= 0x80 as parts of sequences...
-- without \x encoding, do not allow encoding > 7F
normalEncodingMap[c] = ('\\u%.4X'):format(i)
xEncodingMap[c] = ('\\x%.2X'):format(i)
end
end
local defaultOptions = {
xEncode = false, -- Encode single-bytes as \xXX
processor = nil, -- Simple processor for the string prior to quoting
-- / is not required to be quoted but it helps with certain decoding
-- Required encoded characters, " \, and 00-1F (0 - 31)
encodeSet = '\\"/%z\1-\031',
encodeSetAppend = nil -- Chars to append to the default set
}
local modeOptions = {}
local function mergeOptions(options, mode)
jsonutil.doOptionMerge(options, false, 'strings', defaultOptions, mode and modeOptions[mode])
end
local function getEncoder(options)
options = options and util_merge({}, defaultOptions, options) or defaultOptions
local encodeSet = options.encodeSet
if options.encodeSetAppend then
encodeSet = encodeSet .. options.encodeSetAppend
end
local encodingMap = options.xEncode and xEncodingMap or normalEncodingMap
local encodeString
if options.processor then
local processor = options.processor
encodeString = function(s, state)
return '"' .. processor(s:gsub('[' .. encodeSet .. ']', encodingMap)) .. '"'
end
else
encodeString = function(s, state)
return '"' .. s:gsub('[' .. encodeSet .. ']', encodingMap) .. '"'
end
end
return {
string = encodeString
}
end
local strings = {
mergeOptions = mergeOptions,
getEncoder = getEncoder
}
return strings

View File

@@ -0,0 +1,152 @@
--[[
Licensed according to the included 'LICENSE' document
Author: Thomas Harning Jr <harningt@gmail.com>
]]
local type = type
local print = print
local tostring = tostring
local pairs = pairs
local getmetatable, setmetatable = getmetatable, setmetatable
local select = select
local _ENV = nil
local function foreach(tab, func)
for k, v in pairs(tab) do
func(k,v)
end
end
local function printValue(tab, name)
local parsed = {}
local function doPrint(key, value, space)
space = space or ''
if type(value) == 'table' then
if parsed[value] then
print(space .. key .. '= <' .. parsed[value] .. '>')
else
parsed[value] = key
print(space .. key .. '= {')
space = space .. ' '
foreach(value, function(key, value) doPrint(key, value, space) end)
end
else
if type(value) == 'string' then
value = '[[' .. tostring(value) .. ']]'
end
print(space .. key .. '=' .. tostring(value))
end
end
doPrint(name, tab)
end
local function clone(t)
local ret = {}
for k,v in pairs(t) do
ret[k] = v
end
return ret
end
local function inner_merge(t, remaining, from, ...)
if remaining == 0 then
return t
end
if from then
for k,v in pairs(from) do
t[k] = v
end
end
return inner_merge(t, remaining - 1, ...)
end
--[[*
Shallow-merges tables in order onto the first table.
@param t table to merge entries onto
@param ... sequence of 0 or more tables to merge onto 't'
@returns table 't' from input
]]
local function merge(t, ...)
return inner_merge(t, select('#', ...), ...)
end
-- Function to insert nulls into the JSON stream
local function null()
return null
end
-- Marker for 'undefined' values
local function undefined()
return undefined
end
local ArrayMT = {}
--[[
Return's true if the metatable marks it as an array..
Or false if it has no array component at all
Otherwise nil to get the normal detection component working
]]
local function IsArray(value)
if type(value) ~= 'table' then return false end
local meta = getmetatable(value)
local ret = meta == ArrayMT or (meta ~= nil and meta.__is_luajson_array)
if not ret then
if #value == 0 then return false end
else
return ret
end
end
local function InitArray(array)
setmetatable(array, ArrayMT)
return array
end
local CallMT = {}
local function isCall(value)
return CallMT == getmetatable(value)
end
local function buildCall(name, ...)
local callData = {
name = name,
parameters = {n = select('#', ...), ...}
}
return setmetatable(callData, CallMT)
end
local function decodeCall(callData)
if not isCall(callData) then return nil end
return callData.name, callData.parameters
end
local function doOptionMerge(options, nested, name, defaultOptions, modeOptions)
if nested then
modeOptions = modeOptions and modeOptions[name]
defaultOptions = defaultOptions and defaultOptions[name]
end
options[name] = merge(
{},
defaultOptions,
modeOptions,
options[name]
)
end
local json_util = {
printValue = printValue,
clone = clone,
merge = merge,
null = null,
undefined = undefined,
IsArray = IsArray,
InitArray = InitArray,
isCall = isCall,
buildCall = buildCall,
decodeCall = decodeCall,
doOptionMerge = doOptionMerge
}
return json_util

292
cosmic rage/lua/ltn12.lua Normal file
View File

@@ -0,0 +1,292 @@
-----------------------------------------------------------------------------
-- LTN12 - Filters, sources, sinks and pumps.
-- LuaSocket toolkit.
-- Author: Diego Nehab
-- RCS ID: $Id: ltn12.lua,v 1.31 2006/04/03 04:45:42 diego Exp $
-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
-- Declare module
-----------------------------------------------------------------------------
local string = require("string")
local table = require("table")
local base = _G
module("ltn12")
filter = {}
source = {}
sink = {}
pump = {}
-- 2048 seems to be better in windows...
BLOCKSIZE = 2048
_VERSION = "LTN12 1.0.1"
-----------------------------------------------------------------------------
-- Filter stuff
-----------------------------------------------------------------------------
-- returns a high level filter that cycles a low-level filter
function filter.cycle(low, ctx, extra)
base.assert(low)
return function(chunk)
local ret
ret, ctx = low(ctx, chunk, extra)
return ret
end
end
-- chains a bunch of filters together
-- (thanks to Wim Couwenberg)
function filter.chain(...)
local n = table.getn(arg)
local top, index = 1, 1
local retry = ""
return function(chunk)
retry = chunk and retry
while true do
if index == top then
chunk = arg[index](chunk)
if chunk == "" or top == n then return chunk
elseif chunk then index = index + 1
else
top = top+1
index = top
end
else
chunk = arg[index](chunk or "")
if chunk == "" then
index = index - 1
chunk = retry
elseif chunk then
if index == n then return chunk
else index = index + 1 end
else base.error("filter returned inappropriate nil") end
end
end
end
end
-----------------------------------------------------------------------------
-- Source stuff
-----------------------------------------------------------------------------
-- create an empty source
local function empty()
return nil
end
function source.empty()
return empty
end
-- returns a source that just outputs an error
function source.error(err)
return function()
return nil, err
end
end
-- creates a file source
function source.file(handle, io_err)
if handle then
return function()
local chunk = handle:read(BLOCKSIZE)
if not chunk then handle:close() end
return chunk
end
else return source.error(io_err or "unable to open file") end
end
-- turns a fancy source into a simple source
function source.simplify(src)
base.assert(src)
return function()
local chunk, err_or_new = src()
src = err_or_new or src
if not chunk then return nil, err_or_new
else return chunk end
end
end
-- creates string source
function source.string(s)
if s then
local i = 1
return function()
local chunk = string.sub(s, i, i+BLOCKSIZE-1)
i = i + BLOCKSIZE
if chunk ~= "" then return chunk
else return nil end
end
else return source.empty() end
end
-- creates rewindable source
function source.rewind(src)
base.assert(src)
local t = {}
return function(chunk)
if not chunk then
chunk = table.remove(t)
if not chunk then return src()
else return chunk end
else
table.insert(t, chunk)
end
end
end
function source.chain(src, f)
base.assert(src and f)
local last_in, last_out = "", ""
local state = "feeding"
local err
return function()
if not last_out then
base.error('source is empty!', 2)
end
while true do
if state == "feeding" then
last_in, err = src()
if err then return nil, err end
last_out = f(last_in)
if not last_out then
if last_in then
base.error('filter returned inappropriate nil')
else
return nil
end
elseif last_out ~= "" then
state = "eating"
if last_in then last_in = "" end
return last_out
end
else
last_out = f(last_in)
if last_out == "" then
if last_in == "" then
state = "feeding"
else
base.error('filter returned ""')
end
elseif not last_out then
if last_in then
base.error('filter returned inappropriate nil')
else
return nil
end
else
return last_out
end
end
end
end
end
-- creates a source that produces contents of several sources, one after the
-- other, as if they were concatenated
-- (thanks to Wim Couwenberg)
function source.cat(...)
local src = table.remove(arg, 1)
return function()
while src do
local chunk, err = src()
if chunk then return chunk end
if err then return nil, err end
src = table.remove(arg, 1)
end
end
end
-----------------------------------------------------------------------------
-- Sink stuff
-----------------------------------------------------------------------------
-- creates a sink that stores into a table
function sink.table(t)
t = t or {}
local f = function(chunk, err)
if chunk then table.insert(t, chunk) end
return 1
end
return f, t
end
-- turns a fancy sink into a simple sink
function sink.simplify(snk)
base.assert(snk)
return function(chunk, err)
local ret, err_or_new = snk(chunk, err)
if not ret then return nil, err_or_new end
snk = err_or_new or snk
return 1
end
end
-- creates a file sink
function sink.file(handle, io_err)
if handle then
return function(chunk, err)
if not chunk then
handle:close()
return 1
else return handle:write(chunk) end
end
else return sink.error(io_err or "unable to open file") end
end
-- creates a sink that discards data
local function null()
return 1
end
function sink.null()
return null
end
-- creates a sink that just returns an error
function sink.error(err)
return function()
return nil, err
end
end
-- chains a sink with a filter
function sink.chain(f, snk)
base.assert(f and snk)
return function(chunk, err)
if chunk ~= "" then
local filtered = f(chunk)
local done = chunk and ""
while true do
local ret, snkerr = snk(filtered, err)
if not ret then return nil, snkerr end
if filtered == done then return 1 end
filtered = f(done)
end
else return 1 end
end
end
-----------------------------------------------------------------------------
-- Pump stuff
-----------------------------------------------------------------------------
-- pumps one chunk from the source to the sink
function pump.step(src, snk)
local chunk, src_err = src()
local ret, snk_err = snk(chunk, src_err)
if chunk and ret then return 1
else return nil, src_err or snk_err end
end
-- pumps all data from a source to a sink, using a step function
function pump.all(src, snk, step)
base.assert(src and snk)
step = step or pump.step
while true do
local ret, err = step(src, snk)
if not ret then
if err then return nil, err
else return 1 end
end
end
end

1001
cosmic rage/lua/luacom5.lua Normal file

File diff suppressed because it is too large Load Diff

1633
cosmic rage/lua/mapper.lua Normal file

File diff suppressed because it is too large Load Diff

87
cosmic rage/lua/mime.lua Normal file
View File

@@ -0,0 +1,87 @@
-----------------------------------------------------------------------------
-- MIME support for the Lua language.
-- Author: Diego Nehab
-- Conforming to RFCs 2045-2049
-- RCS ID: $Id: mime.lua,v 1.29 2007/06/11 23:44:54 diego Exp $
-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
-- Declare module and import dependencies
-----------------------------------------------------------------------------
local base = _G
local ltn12 = require("ltn12")
local mime = require("mime.core")
local io = require("io")
local string = require("string")
module("mime")
-- encode, decode and wrap algorithm tables
encodet = {}
decodet = {}
wrapt = {}
-- creates a function that chooses a filter by name from a given table
local function choose(table)
return function(name, opt1, opt2)
if base.type(name) ~= "string" then
name, opt1, opt2 = "default", name, opt1
end
local f = table[name or "nil"]
if not f then
base.error("unknown key (" .. base.tostring(name) .. ")", 3)
else return f(opt1, opt2) end
end
end
-- define the encoding filters
encodet['base64'] = function()
return ltn12.filter.cycle(b64, "")
end
encodet['quoted-printable'] = function(mode)
return ltn12.filter.cycle(qp, "",
(mode == "binary") and "=0D=0A" or "\r\n")
end
-- define the decoding filters
decodet['base64'] = function()
return ltn12.filter.cycle(unb64, "")
end
decodet['quoted-printable'] = function()
return ltn12.filter.cycle(unqp, "")
end
local function format(chunk)
if chunk then
if chunk == "" then return "''"
else return string.len(chunk) end
else return "nil" end
end
-- define the line-wrap filters
wrapt['text'] = function(length)
length = length or 76
return ltn12.filter.cycle(wrp, length, length)
end
wrapt['base64'] = wrapt['text']
wrapt['default'] = wrapt['text']
wrapt['quoted-printable'] = function()
return ltn12.filter.cycle(qpwrp, 76, 76)
end
-- function that choose the encoding, decoding or wrap algorithm
encode = choose(encodet)
decode = choose(decodet)
wrap = choose(wrapt)
-- define the end-of-line normalization filter
function normalize(marker)
return ltn12.filter.cycle(eol, 0, marker)
end
-- high level stuffing filter
function stuff()
return ltn12.filter.cycle(dot, 2)
end

View File

@@ -0,0 +1,410 @@
-- movewindow.lua
--[[
Miniwindow drag-to-move functions.
Author: Nick Gammon
Date: 15th July 2009
Modified: 16th November 2010 to add preprocessing
Modified: 29th November 2010 by Fiendish to improve dragging offscreen
Modified: 8th February 2018 by Nick to remember the flags setting (eg. absolute position)
This module is intended to make it easier to add drag handlers for miniwindows.
It implements the following:
-- find previous location
nocheck: if true, don't check if offscreen (boolean)
friends: other windows to move with this one (table)
preprocess: preprocessing for mousedown, mouseup etc. (table)
Handler names for preprocess table:
mousedown
mouseup
mouseover
cancelmouseover
cancelmousedown
dragmove
dragrelease
If any preprocess handler returns true (that is, neither nil nor false) then the default handler
in this module is not used. (The miniwindow name is the third argument)
function mousedown (flags, id, win)
if bit.band (flags, miniwin.hotspot_got_rh_mouse) then
-- do something different here
return true
end -- if
return false -- take normal movewindow behaviour
end -- mousedown
windowinfo = movewindow.install (win, default_position, default_flags, nocheck, friends, preprocess)
movewindow.add_drag_handler (win, left, top, right, bottom, cursor) -- add a drag handler for the nominated rectangle
movewindow.save_state (win) -- saves the miniwindow location to the appropriate variables
It also installs a position-checker that moves the miniwindow into view after 5 seconds, in case
you resize the main world window, and the window is no longer visible. The 5 seconds are to give
the main world window's position and size time to stabilize. (Unless nocheck is true)
Example of use:
require "movewindow" -- pull in this module
-- CREATE WINDOW in OnPluginInstall
win = GetPluginID () -- miniwindow ID
windowinfo = movewindow.install (win, miniwin.pos_center_right, 0) -- default position / flags
-- make miniwindow (use locations returned from last time we saved the state)
-- note that the width and height are not part of the window position info, and can thus change as required
WindowCreate (win,
windowinfo.window_left,
windowinfo.window_top,
WINDOW_WIDTH,
WINDOW_HEIGHT,
windowinfo.window_mode,
windowinfo.window_flags,
ColourNameToRGB "slategray")
-- INSTALL DRAG HANDLER when required (eg. when drawing stuff to window)
-- in this case we use 0,0,0,0 as the rectangle (ie. the whole window)
-- typically the height would be the size of the title bar
movewindow.add_drag_handler (win, 0, 0, 0, 0, miniwin.cursor_both_arrow)
-- SAVE STATE in OnPluginSaveState
movewindow.save_state (win)
The module makes one global variable (table) when installed. This is named:
mw_<window_id>_movewindow_info
This contains handler functions (the table is an upvalue to the functions)
"check_map_position"=function: 023D9368 -- the position checker
"dragmove"=function: 01AD1158 -- the dragmove handler
"dragrelease"=function: 023E4238 -- the dragrelease handler
"margin"=20 -- margin for dragging offscreen
"mousedown"=function: 01AD1108 -- the mousedown handler
"origx"=648 -- used during dragging
"origy"=39
"startx"=88
"starty"=8
"win"="23c3c91af0a26790c625f5d1" -- the supplied window ID
"window_flags"=2 -- flags (eg. 2, absolute position)
"window_left"=652 -- current left location
"window_mode"=0 -- window mode
"window_top"=31 -- current top location
This table is returned from movewindow.install so you can find where to put the
window the first time it is created.
--]]
movewindow = {} -- table to hold functions like movewindow.install
-- make a mouse-down handler with the movement information as an upvalue
local function make_mousedown_handler (mwi)
return function (flags, hotspot_id)
local win = mwi.win
-- see if other action wanted
if mwi.preprocess.mousedown then
if mwi.preprocess.mousedown (flags, hotspot_id, win) then
return
end -- if handled already
end -- if handler
-- find where mouse is so we can adjust window relative to mouse
mwi.startx = WindowInfo (win, 14)
mwi.starty = WindowInfo (win, 15)
-- find where window is in case we drag it offscreen
mwi.origx = WindowInfo (win, 10)
mwi.origy = WindowInfo (win, 11)
-- find where the friends are relative to the window
for i, v in ipairs (mwi.window_friends) do
if v then
mwi.window_friend_deltas [i] =
{
WindowInfo (v, 10) - mwi.origx,
WindowInfo (v, 11) - mwi.origy
}
end -- if
end -- for
end -- mousedown
end -- make_mousedown_handler
-- make a mouse drag-move handler with the movement information as an upvalue
local function make_dragmove_handler (mwi)
return function (flags, hotspot_id)
local win = mwi.win
-- see if other action wanted
if mwi.preprocess.dragmove then
if mwi.preprocess.dragmove (flags, hotspot_id, win) then
return
end -- if handled already
end -- if handler
-- find where it is now
local posx, posy = WindowInfo (win, 17) - mwi.startx,
WindowInfo (win, 18) - mwi.starty
-- change the mouse cursor shape appropriately
if posx < 0 or
posx > GetInfo (281) - mwi.margin or
posy < 0 or -- don't drag title out of view
posy > GetInfo (280) - mwi.margin then
SetCursor (miniwin.cursor_x) -- X cursor
else
SetCursor (miniwin.cursor_hand) -- hand cursor
end -- if
if posx < 0 then
posx = 0
elseif posx > GetInfo (281) - mwi.margin then
posx = GetInfo(281) - mwi.margin
end
if posy < 0 then
posy = 0
elseif posy > GetInfo(280) - mwi.margin then
posy = GetInfo(280) - mwi.margin
end
-- move the window to the new location - offset by how far mouse was into window
WindowPosition(win, posx, posy, 0, miniwin.create_absolute_location);
-- move the friends if they still exist
for i, v in ipairs(mwi.window_friends) do
if v then
WindowPosition (v, posx + mwi.window_friend_deltas [i] [1],
posy + mwi.window_friend_deltas [i] [2],
0,
WindowInfo (v, 8))
end -- if
end -- for
mwi.window_left = posx -- remember for saving state
mwi.window_top = posy
mwi.window_mode = 0
mwi.window_flags = miniwin.create_absolute_location -- absolute position
end -- dragmove
end -- make_dragmove_handler
-- make a mouse drag-release handler with the movement information as an upvalue
local function make_dragrelease_handler (mwi)
return function (flags, hotspot_id)
local win = mwi.win
-- see if other action wanted
if mwi.preprocess.dragrelease then
if mwi.preprocess.dragrelease (flags, hotspot_id, win) then
return
end -- if handled already
end -- if handler
Repaint () -- update window location
end -- dragrelease
end -- make_dragrelease_handler
-- make other handler with the movement information as an upvalue
local function make_other_handler (mwi, name)
return function (flags, hotspot_id)
-- send to supplied handler
if mwi.preprocess [name] then
mwi.preprocess [name] (flags, hotspot_id, mwi.win)
end -- if handler
end -- other
end -- make_other_handler
-- make a mouse position-checking function with the movement information as an upvalue
local function make_check_map_position_handler (mwi)
return function ()
local win = mwi.win
if not WindowInfo (win, 1) then
ColourNote ("white", "red", "Error in make_check_map_position_handler: no window named: " .. win)
return
end -- no such window
-- check miniwindow visible
if mwi.window_left < 0 or
mwi.window_left > GetInfo (281) - mwi.margin or
mwi.window_top < 0 or -- don't drag title out of view
mwi.window_top > GetInfo (280) - mwi.margin then
mwi.window_mode = miniwin.pos_center_right
mwi.window_flags = 0
end -- if not visible
WindowPosition (win,
mwi.window_left,
mwi.window_top,
mwi.window_mode,
mwi.window_flags)
end -- check_map_position
end -- make_check_map_position_handler
-- call movewindow.install in OnPluginInstall to find the position of the window, before creating it
-- - it also creates the handler functions ready for use later
function movewindow.install (win, default_position, default_flags, nocheck, friends, preprocess, start_position)
win = win or GetPluginID () -- default to current plugin ID
assert (not string.match (win, "[^A-Za-z0-9_]"), "Invalid window name in movewindow.install: " .. win)
default_position = default_position or miniwin.pos_center_right -- on right, center top/bottom
default_flags = default_flags or 0
-- set up handlers and where window should be shown (from saved state, if any)
local movewindow_info = {
win = win, -- save window ID
-- save current position in table (obtained from state file)
window_left = tonumber (GetVariable ("mw_" .. win .. "_windowx")) or (start_position and start_position.x) or 0,
window_top = tonumber (GetVariable ("mw_" .. win .. "_windowy")) or (start_position and start_position.y) or 0,
window_mode = default_position,
window_flags = tonumber (GetVariable ("mw_" .. win .. "_windowflags")) or default_flags,
window_friends = friends or {},
window_friend_deltas = {},
margin = 20, -- how close we can put to the edge of the window
preprocess = preprocess or {},
}
-- check valid
for k, v in pairs (movewindow_info.preprocess) do
assert (type (v) == "function", "Handler '" .. k .. "' is not a function")
end -- for
-- handler to reposition window
movewindow_info.check_map_position = make_check_map_position_handler (movewindow_info) -- for startup
-- mouse handlers
movewindow_info.mousedown = make_mousedown_handler (movewindow_info)
movewindow_info.mouseup = make_other_handler (movewindow_info, "mouseup")
movewindow_info.mouseover = make_other_handler (movewindow_info, "mouseover")
movewindow_info.cancelmouseover = make_other_handler (movewindow_info, "cancelmouseover")
movewindow_info.cancelmousedown = make_other_handler (movewindow_info, "cancelmousedown")
movewindow_info.dragmove = make_dragmove_handler (movewindow_info)
movewindow_info.dragrelease = make_dragrelease_handler (movewindow_info)
-- save table in global namespace
_G ["mw_" .. win .. "_movewindow_info"] = movewindow_info
-- give main world window time to stabilize its size and position
-- eg. this might be: mw_23c3c91af0a26790c625f5d1_movewindow_info.check_map_position ()
if not nocheck then -- if wanted
DoAfterSpecial (5, "mw_" .. win .. "_movewindow_info.check_map_position ()" , sendto.script)
end -- if
return movewindow_info -- the caller might appreciate access to this table
end -- movewindow.install
-- call movewindow.add_drag_handler after creating the window, and after deleting hotspots where applicable
-- to add a drag hotspot
function movewindow.add_drag_handler (win, left, top, right, bottom, cursor)
win = win or GetPluginID () -- default to current plugin ID
-- the zz puts it under other hotspots on the drag area
local hotspot_id = "zz_mw_" .. win .. "_movewindow_hotspot"
if not WindowInfo (win, 1) then
ColourNote ("white", "red", "Error in movewindow.add_drag_handler: no window named: " .. win)
return
end -- no such window
-- make a hotspot
WindowAddHotspot(win, hotspot_id,
left or 0, top or 0, right or 0, bottom or 0, -- rectangle
"mw_" .. win .. "_movewindow_info.mouseover", -- MouseOver
"mw_" .. win .. "_movewindow_info.cancelmouseover", -- CancelMouseOver
"mw_" .. win .. "_movewindow_info.mousedown", -- MouseDown
"mw_" .. win .. "_movewindow_info.cancelmousedown", -- CancelMouseDown
"mw_" .. win .. "_movewindow_info.mouseup", -- MouseUp
"Drag to move window", -- tooltip text
cursor or miniwin.cursor_hand, -- cursor
0) -- flags
WindowDragHandler (win, hotspot_id,
"mw_" .. win .. "_movewindow_info.dragmove",
"mw_" .. win .. "_movewindow_info.dragrelease",
0) -- flags
end -- movewindow.add_drag_handler
-- call movewindow.save_state in OnPluginSaveState
function movewindow.save_state (win)
win = win or GetPluginID () -- default to current plugin ID
-- get movewindow variable from global namespace
local mwi = _G ["mw_" .. win .. "_movewindow_info"]
if not mwi then
ColourNote ("white", "red", "Error in movewindow.save_state: no window movement info for: " .. win)
return
end -- no such window
-- remember where the window was
-- use actual last specified position, not where we happen to think it is, in case another plugin moves it
-- suggested by Fiendish, 27 August 2012.
if WindowInfo (win, 1) then
mwi.window_left = WindowInfo(win, 1)
end
if WindowInfo (win, 2) then
mwi.window_top = WindowInfo(win, 2)
end
SetVariable ("mw_" .. win .. "_windowx", mwi.window_left)
SetVariable ("mw_" .. win .. "_windowy", mwi.window_top)
SetVariable ("mw_" .. win .. "_windowflags", mwi.window_flags)
end -- movewindow.save_state

480
cosmic rage/lua/mw.lua Normal file
View File

@@ -0,0 +1,480 @@
-- mw.lua
-- Helper functions for miniwindows
--
-- Author: Nick Gammon - 8th September 2008
--[[
Exposed functions are:
mw.colourtext - show a string with imbedded colour codes
mw.colour_conversion - table with colour codes in it - add more if you want
mw.strip_colours - remove colour codes from a string
mw.popup - make a popup window
mw.tooltip - make a tooltip window
EXAMPLE OF MAKING A POPUP WINDOW:
require "mw"
-- SET UP FOR POPUP WINDOWS - define colours, add fonts, make window id
-- (DO THIS ONCE ONLY, eg. in OnPluginInstall)
-- our window frame/background colours
border_colour = 0xCCD148
background_colour = 0x222222
-- a unique ID
infowin = GetPluginID () .. ":info"
-- font IDs
font_id = "popup_font"
heading_font_id = "popup_heading_font"
font_size = 8
-- use 8 pt Dina or 10 pt Courier
local fonts = utils.getfontfamilies ()
-- choose a font that exists
if fonts.Dina then
font_name = "Dina"
elseif fonts ["Lucida Sans Unicode"] then
font_name = "Lucida Sans Unicode"
else
font_size = 10
font_name = "Courier"
end -- if
-- load fonts
WindowCreate (infowin, 0, 0, 0, 0, 0, 0, 0) -- make initial window
-- install the fonts
WindowFont (infowin, font_id, font_name, font_size, false, false, false, false,
miniwin.font_charset_ansi,
miniwin.font_family_modern + miniwin.font_pitch_fixed)
WindowFont (infowin, heading_font_id, font_name, font_size + 2, false, false, false, false,
miniwin.font_charset_ansi,
miniwin.font_family_modern + miniwin.font_pitch_fixed)
-- NOW DISPLAY A WINDOW
-- what to say - one line per table entry, with imbedded colour codes
info = { "@Ctesting 1 2 3",
"@GThis is a heading",
"Line @Mwith @Bmultiple @Rcolours",
}
heading = "@MHello, @Yworld"
left, top = 40, 50
align_right = false
align_bottom = false
capitalize = true
-- show it
mw.popup (infowin, -- window name to use
heading_font_id, -- font to use for the heading
font_id, -- font to use for each line
heading, -- heading text
info, -- table of lines to show (with colour codes)
left, top, -- where to put it
border_colour, -- colour for round rectangle line
background_colour, -- colour for background
capitalize, -- if true, force the first letter to upper case
align_right, -- if true, align right side on "Left" parameter
align_bottom) -- if true, align bottom side on "Top" parameter
EXAMPLE OF MAKING A TOOLTIP WINDOW:
-- SET UP FOR TOOLTIP WINDOWS - define colours, add fonts, make window id
-- (DO THIS ONCE ONLY, eg. in OnPluginInstall)
-- Example setup code:
require "mw"
-- get ready for tooltips
win_tooltip = GetPluginID () .. "_tooltip"
font_tooltip = "tf"
bold_font_tooltip = "tfb"
-- make the window
WindowCreate (win_tooltip, 0, 0, 0, 0, 0, 0, 0)
-- load some fonts into it
WindowFont (win_tooltip, font_tooltip, "Tahoma", 8)
WindowFont (win_tooltip, bold_font_tooltip, "Tahoma", 8, true)
-- NOW DISPLAY A tooltip (Put bold lines inside asterisks)
mw.tooltip (win_tooltip, -- window name to use
font_tooltip, -- font to use for each line
bold_font_tooltip, -- bold font
"*Info*\nHello, world!\nHave fun.", -- tooltip text
45, 75, -- where to put it (x, y)
0, -- colour for text (black)
0, -- colour for border (black)
ColourNameToRGB ("#FFFFE1")) -- colour for background
--]]
module (..., package.seeall)
DEFAULT_COLOUR = "@w"
TRANSPARENCY_COLOUR = 0x080808
BORDER_WIDTH = 2
local BLACK = 1
local RED = 2
local GREEN = 3
local YELLOW = 4
local BLUE = 5
local MAGENTA = 6
local CYAN = 7
local WHITE = 8
-- colour styles (eg. @r is normal red, @R is bold red)
-- @- is shown as ~
-- @@ is shown as @
-- This table uses the colours as defined in the MUSHclient ANSI tab, however the
-- defaults are shown on the right if you prefer to use those.
colour_conversion = {
k = GetNormalColour (BLACK) , -- 0x000000
r = GetNormalColour (RED) , -- 0x000080
g = GetNormalColour (GREEN) , -- 0x008000
y = GetNormalColour (YELLOW) , -- 0x008080
b = GetNormalColour (BLUE) , -- 0x800000
m = GetNormalColour (MAGENTA) , -- 0x800080
c = GetNormalColour (CYAN) , -- 0x808000
w = GetNormalColour (WHITE) , -- 0xC0C0C0
K = GetBoldColour (BLACK) , -- 0x808080
R = GetBoldColour (RED) , -- 0x0000FF
G = GetBoldColour (GREEN) , -- 0x00FF00
Y = GetBoldColour (YELLOW) , -- 0x00FFFF
B = GetBoldColour (BLUE) , -- 0xFF0000
M = GetBoldColour (MAGENTA) , -- 0xFF00FF
C = GetBoldColour (CYAN) , -- 0xFFFF00
W = GetBoldColour (WHITE) , -- 0xFFFFFF
-- add custom colours here
} -- end conversion table
-- displays text with colour codes imbedded
--
-- win: window to use
-- font_id : font to use
-- Text : what to display
-- Left, Top, Right, Bottom : where to display it
-- Capitalize : if true, turn the first letter into upper-case
function colourtext (win, font_id, Text, Left, Top, Right, Bottom, Capitalize, utf8)
if Text:match ("@") then
local x = Left -- current x position
local need_caps = Capitalize
Text = Text:gsub ("@%-", "~") -- fix tildes
Text = Text:gsub ("@@", "\0") -- change @@ to 0x00
-- make sure we start with @ or gsub doesn't work properly
if Text:sub (1, 1) ~= "@" then
Text = DEFAULT_COLOUR .. Text
end -- if
for colour, text in Text:gmatch ("@(%a)([^@]+)") do
text = text:gsub ("%z", "@") -- put any @ characters back
if need_caps then
local count
text, count = text:gsub ("%a", string.upper, 1)
need_caps = count == 0 -- if not done, still need to capitalize yet
end -- if
if #text > 0 then
x = x + WindowText (win, font_id, text, x, Top, Right, Bottom,
colour_conversion [colour] or GetNormalColour (WHITE), utf8)
end -- some text to display
end -- for each colour run
return x
end -- if
if Capitalize then
Text = Text:gsub ("%a", string.upper, 1)
end -- if leading caps wanted
return WindowText (win, font_id, Text, Left, Top, Right, Bottom,
colour_conversion [DEFAULT_COLOUR] or GetNormalColour (WHITE))
end -- colourtext
-- converts text with colour styles in it into style runs
function ColoursToStyles (Text)
if Text:match ("@") then
astyles = {}
Text = Text:gsub ("@%-", "~") -- fix tildes
Text = Text:gsub ("@@", "\0") -- change @@ to 0x00
-- make sure we start with @ or gsub doesn't work properly
if Text:sub (1, 1) ~= "@" then
Text = DEFAULT_COLOUR .. Text
end -- if
for colour, text in Text:gmatch ("@(%a)([^@]+)") do
text = text:gsub ("%z", "@") -- put any @ characters back
if #text > 0 then
table.insert (astyles, { text = text,
length = #text,
textcolour = colour_conversion [colour] or GetNormalColour (WHITE),
backcolour = GetNormalColour (BLACK) })
end -- if some text
end -- for each colour run.
return astyles
end -- if any colour codes at all
-- No colour codes, create a single style.
return { { text = Text,
length = #Text,
textcolour = GetNormalColour (WHITE),
backcolour = GetNormalColour (BLACK) } }
end -- function ColoursToStyles
-- take a string, and remove colour codes from it (eg. "@Ghello" becomes "hello"
function strip_colours (s)
s = s:gsub ("@%-", "~") -- fix tildes
s = s:gsub ("@@", "\0") -- change @@ to 0x00
s = s:gsub ("@%a([^@]*)", "%1")
return (s:gsub ("%z", "@")) -- put @ back
end -- strip_colours
function popup (win, -- window name to use
heading_font_id, -- font to use for the heading
font_id, -- font to use for each line
heading, -- heading text
info, -- table of lines to show (with colour codes)
Left, Top, -- where to put it
border_colour, -- colour for round rectangle line
background_colour, -- colour for background
capitalize, -- if true, force the first letter to be upper case
align_right, -- if true, align right side on "Left" parameter
align_bottom) -- if true, align bottom side on "Top" parameter
assert (WindowInfo (win, 1), "Window " .. win .. " must already exist")
assert (WindowFontInfo (win, heading_font_id, 1), "No font " .. heading_font_id .. " in " .. win)
assert (WindowFontInfo (win, font_id, 1), "No font " .. font_id .. " in " .. win)
local font_height = WindowFontInfo (win, font_id, 1)
local font_leading = WindowFontInfo (win, font_id, 4) + WindowFontInfo (win, font_id, 5)
local heading_font_height = WindowFontInfo (win, heading_font_id, 1)
-- find text width - minus colour codes
local infowidth = 0
local infoheight = 0
-- calculate heading width and height
if heading and #heading > 0 then
infowidth = WindowTextWidth (win, heading_font_id, strip_colours (heading))
infoheight = heading_font_height
end -- have a heading
-- calculate remaining width and height
for _, v in ipairs (info) do
infowidth = math.max (infowidth, WindowTextWidth (win, font_id, strip_colours (v)))
infoheight = infoheight + font_height
end -- for
infowidth = infowidth + (2 * BORDER_WIDTH) + -- leave room for border
WindowFontInfo (win, font_id, 6) -- one character width extra
infoheight = infoheight + (2 * BORDER_WIDTH) + -- leave room for border
font_leading + -- plus leading below bottom line,
10 -- and 5 pixels top and bottom
if align_right then
Left = Left - infowidth
end -- if align_right
if align_bottom then
Top = Top - infoheight
end -- if align_bottom
WindowCreate (win,
Left, Top, -- where
infowidth, -- width (gap of 5 pixels per side)
infoheight, -- height
miniwin.pos_top_left, -- position mode: can't be 0 to 3
miniwin.create_absolute_location + miniwin.create_transparent,
TRANSPARENCY_COLOUR) -- background (transparent) colour
WindowCircleOp (win, miniwin.circle_round_rectangle,
BORDER_WIDTH, BORDER_WIDTH, -BORDER_WIDTH, -BORDER_WIDTH, -- border inset
border_colour, miniwin.pen_solid, BORDER_WIDTH, -- line
background_colour, miniwin.brush_solid, -- fill
5, 5) -- diameter of ellipse
local x = BORDER_WIDTH + WindowFontInfo (win, font_id, 6) / 2 -- start 1/2 character in
local y = BORDER_WIDTH + 5 -- skip border, and leave 5 pixel gap
-- heading if wanted
if heading and #heading > 0 then
colourtext (win, heading_font_id, heading, x, y, 0, 0, capitalize)
y = y + heading_font_height
end -- have a heading
-- show each line
for _, v in ipairs (info) do
colourtext (win, font_id, v, x, y, 0, 0, capitalize)
y = y + font_height
end -- for
-- display popup window
WindowShow (win, true)
end -- popup
-- --------------------------------------------------------------
-- Displays a tooltip (small box with "arrow" pointing to something of interest)
-- Bold lines are surrounded by asterisks (eg. *Info*)
-- --------------------------------------------------------------
function tooltip (win, -- window name to use
font_id, -- font to use for each line
bold_font_id, -- font to use for bold lines
text, -- tooltip text
Left, Top, -- where to put it (x, y)
text_colour, -- colour for text
border_colour, -- colour for border
background_colour) -- colour for background
assert (WindowInfo (win, 1), "Window " .. win .. " must already exist")
assert (WindowFontInfo (win, font_id, 1), "No font " .. font_id .. " in " .. win)
assert (WindowFontInfo (win, bold_font_id, 1), "No font " .. bold_font_id .. " in " .. win)
local font_height = WindowFontInfo (win, font_id, 1)
local bold_font_height = WindowFontInfo (win, bold_font_id, 1)
local MARGIN = 8
local TIPSIZE = 12
-- break text into lines
local t = utils.split (text, "\n")
-- tooltip height
local height = MARGIN * 2 -- margin at top and bottom
-- tooltip width
local width = TIPSIZE * 2 -- must be at least large enough for the tip part
for k, v in ipairs (t) do
-- bold lines start and end with an asterisk
local boldText = string.match (v, "^%*(.*)%*$")
if boldText then
width = math.max (width, WindowTextWidth (win, bold_font_id, boldText, true))
height = height + bold_font_height
else
width = math.max (width, WindowTextWidth (win, font_id, v, true))
height = height + font_height
end -- if
end -- for
width = width + (MARGIN * 2) -- margin per side
-- the tooltip pointer starts TIPSIZE pixels to the right and descends TIPSIZE pixels
WindowCreate (win,
Left - TIPSIZE, Top - height - TIPSIZE,
width + 2, height + TIPSIZE + 2, -- 2 pixels margin to allow for border + TIPSIZE downwards for the tip
miniwin.pos_top_left, -- position
miniwin.create_absolute_location + miniwin.create_transparent + miniwin.create_ignore_mouse,
TRANSPARENCY_COLOUR)
-- mucking around here to get rounded rectangle
local points = {
-- top LH corner
1, 6,
2, 6,
2, 4,
3, 4,
3, 3,
4, 3,
4, 2,
6, 2,
6, 1,
-- top RH corner
width - 5, 1,
width - 5, 2,
width - 3, 2,
width - 3, 3,
width - 2, 3,
width - 2, 4,
width - 1, 4,
width - 1, 6,
width, 6,
-- bottom RH corner
width, height - 5,
width - 1, height - 5,
width - 1, height - 3,
width - 2, height - 3,
width - 2, height - 2,
width - 3, height - 2,
width - 3, height - 1,
width - 5, height - 1,
width - 5, height,
(TIPSIZE * 2) + 1, height, -- RH side of tip
TIPSIZE + 1, height + TIPSIZE, -- bottom of tip
TIPSIZE + 1, height, -- LH side of tip
-- bottom LH corner
6, height,
6, height - 1,
4, height - 1,
4, height - 2,
3, height - 2,
3, height - 3,
2, height - 3,
2, height - 5,
1, height - 5,
}
-- make the tooltip polygon
WindowPolygon(win, table.concat (points, ","),
border_colour, -- pen colour
miniwin.pen_solid, 1, -- pen (1 pixel wide)
background_colour, -- brush colour
miniwin.brush_solid, -- brush
true) -- close it
-- put the text into it
local top = MARGIN + 1
for _, v in ipairs (t) do
-- bold lines start and end with an asterisk
local boldText = string.match (v, "^%*(.*)%*$")
if boldText then
WindowText (win, bold_font_id, boldText, MARGIN + 1, top, 0, 0, text_colour, true)
top = top + bold_font_height
else
WindowText (win, font_id, v, MARGIN + 1, top, 0, 0, text_colour, true)
top = top + font_height
end -- if
end -- for
WindowShow (win, true)
end -- tooltip

View File

@@ -0,0 +1,43 @@
-- pairsbykeys.lua
-- From Programming in Lua book, 2nd edition
-- Gives you an iterator that moves through an ordinary table (eg. string keys)
-- but sorted into key sequence.
-- It does that by copying the table keys into a temporary table and sorting that.
-- If you need to sort keys other than strings, see:
-- See: http://lua-users.org/wiki/SortedIteration
function pairsByKeys (t, f)
local a = {}
-- build temporary table of the keys
for n in pairs (t) do
table.insert (a, n)
end
table.sort (a, f) -- sort using supplied function, if any
local i = 0 -- iterator variable
return function () -- iterator function
i = i + 1
return a[i], t[a[i]]
end -- iterator function
end -- pairsByKeys
return pairsByKeys
--[[
-- This prints the math functions in random order
for k, v in pairs (math) do
print (k, v)
end -- for
require "pairsbykeys"
-- This prints the math functions in key order
for k, v in pairsByKeys (math) do
print (k, v)
end -- for
--]]

170
cosmic rage/lua/ppi.lua Normal file
View File

@@ -0,0 +1,170 @@
--[[
PLUGIN-to-PLUGIN-INTERFACE (PPI)
Author: Twisol
Date: 3rd January 2010
Amendments: Nick Gammon
Date: 8th January 2010
Example of use:
SERVICE
-- require PPI module
require "ppi"
-- exposed function
function SomeMethodHere (a, b, c, d)
-- do something with a, b, c, d
return 1, 2, 3, 4
end
-- notify PPI of this function
ppi.Expose "SomeMethodHere"
-- Or, for anonymous functions:
ppi.Expose ("DoSomethingElse", function () print "hello" end)
CLIENT
-- require PPI module
require "ppi"
-- resolve dependencies
function OnPluginListChanged ()
-- get PPI entries for all exposed function in this plugin
my_service = ppi.Load ("15783160bde378741f9652d1") -- plugin ID of service plugin
if not my_service then
Note ("Dependency plugin not installed!")
end
end -- OnPluginListChanged
-- later on in plugin ...
-- call SomeMethodHere in other plugin, passing various data types, getting results
if my_service then
w, x, y, z = my_service.SomeMethodHere (42, "Nick", true, { a = 63, b = 22 } )
end -- if service installed
NOTES
-----
ppi.Load returns a table with various values in it about the target plugin (see below
for what they are). For example, _name is the plugin name of the target plugin, and
_version is the version number of that plugin.
If ppi.Load returns no value (effectively, nil) then the target plugin was not installed.
Provided a non-nil result was returned, you can then call any exposed function in the
target plugin. There is currently no mechanism for finding what functions are exposed, for
simplicity's sake. However it would be possible to make a service function that returned all
exposed functions. If service plugins evolve in functionality, checking the target plugin's
version (the _version variable) should suffice for making sure plugins are synchronized.
To avoid clashes in variable names, you cannot expose a function starting with an underscore.
Communication with the target plugin is by global variables set up by the Expose function, along
the lines of:
PPI_function_name_PPI_ (one for each exposed function)
Also:
PPI__returns__PPI_ is used for storing the returned values.
--]]
-- hide all except non-local variables
module (..., package.seeall)
-- for transferring variables
require "serialize"
-- PPI version
local V_MAJOR, V_MINOR, V_PATCH = 1, 1, 0
local VERSION = string.format ("%d.%d.%d", V_MAJOR, V_MINOR, V_PATCH)
-- called plugin uses this variable to store returned values
local RETURNED_VALUE_VARIABLE = "PPI__returns__PPI_"
-- For any function in our PPI table, try to call that in the target plugin
local PPI_meta = {
__index = function (tbl, idx)
if (idx:sub (1, 1) ~= "_") then
return function(...)
-- Call the method in the target plugin
local status = CallPlugin (tbl._id, "PPI_" .. idx .. "_PPI_", serialize.save_simple {...})
-- explain a bit if we failed
if status ~= error_code.eOK then
ColourNote ("white", "red", "Error calling " .. idx ..
" in plugin " .. tbl._name ..
" using PPI from " .. GetPluginName () ..
" (" .. error_desc [status] .. ")")
check (status)
end -- if
-- call succeeded, get any returned values
local returns = {} -- table of returned values
local s = GetPluginVariable(tbl._id, RETURNED_VALUE_VARIABLE) or "{}"
local f = assert (loadstring ("t = " .. s)) -- convert serialized data back
setfenv (f, returns) () -- load the returned values into 'returns'
-- unpack returned values to caller
return unpack (returns.t)
end -- generated function
end -- not starting with underscore
end -- __index function
} -- end PPI_meta table
-- PPI request resolver
local function PPI_resolver (func)
return function (s) -- calling plugin serialized parameters into a single string argument
local params = {} -- table of parameters
local f = assert (loadstring ("t = " .. s)) -- convert serialized data back
setfenv (f, params) () -- load the parameters into 'params'
-- call target function, get return values, serialize back into variable
SetVariable(RETURNED_VALUE_VARIABLE, serialize.save_simple {func(unpack (params.t))})
end -- generated function
end -- PPI_resolver
-- EXPOSED FUNCTIONS
-- We "load" a plugin by checking it exists, and creating a table saving the
-- target plugin ID etc., and have a metatable which will handle function calls
function Load (plugin_id)
if IsPluginInstalled (plugin_id) then
return setmetatable (
{ _id = plugin_id, -- so we know which plugin to call
_name = GetPluginInfo (plugin_id, 1),
_author = GetPluginInfo (plugin_id, 2),
_filename = GetPluginInfo (plugin_id, 6),
_enabled = GetPluginInfo (plugin_id, 17),
_version = GetPluginInfo (plugin_id, 18),
_required_version = GetPluginInfo (plugin_id, 19),
_directory = GetPluginInfo (plugin_id, 20),
_PPI_V_MAJOR = V_MAJOR, -- version info
_PPI_V_MINOR = V_MINOR,
_PPI_V_PATCH = V_PATCH,
_PPI_VERSION = VERSION,
},
PPI_meta) -- everything except the above will generate functions
else
return nil, "Plugin ID " .. plugin_id .. " not installed" -- in case you assert
end -- if
end -- function Load
-- Used by a plugin to expose methods to other plugins
-- Each exposed function will be added to global namespace as PPI_<name>_PPI_
function Expose (name, func)
assert (type (func or _G [name]) == "function", "Function " .. name .. " does not exist.")
_G ["PPI_" .. name .. "_PPI_"] = PPI_resolver (func or _G [name])
end -- function Expose

259
cosmic rage/lua/re.lua Normal file
View File

@@ -0,0 +1,259 @@
-- $Id: re.lua,v 1.44 2013/03/26 20:11:40 roberto Exp $
-- imported functions and modules
local tonumber, type, print, error = tonumber, type, print, error
local setmetatable = setmetatable
local m = require"lpeg"
-- 'm' will be used to parse expressions, and 'mm' will be used to
-- create expressions; that is, 're' runs on 'm', creating patterns
-- on 'mm'
local mm = m
-- pattern's metatable
local mt = getmetatable(mm.P(0))
-- No more global accesses after this point
local version = _VERSION
if version == "Lua 5.2" then _ENV = nil end
local any = m.P(1)
-- Pre-defined names
local Predef = { nl = m.P"\n" }
local mem
local fmem
local gmem
local function updatelocale ()
mm.locale(Predef)
Predef.a = Predef.alpha
Predef.c = Predef.cntrl
Predef.d = Predef.digit
Predef.g = Predef.graph
Predef.l = Predef.lower
Predef.p = Predef.punct
Predef.s = Predef.space
Predef.u = Predef.upper
Predef.w = Predef.alnum
Predef.x = Predef.xdigit
Predef.A = any - Predef.a
Predef.C = any - Predef.c
Predef.D = any - Predef.d
Predef.G = any - Predef.g
Predef.L = any - Predef.l
Predef.P = any - Predef.p
Predef.S = any - Predef.s
Predef.U = any - Predef.u
Predef.W = any - Predef.w
Predef.X = any - Predef.x
mem = {} -- restart memoization
fmem = {}
gmem = {}
local mt = {__mode = "v"}
setmetatable(mem, mt)
setmetatable(fmem, mt)
setmetatable(gmem, mt)
end
updatelocale()
local I = m.P(function (s,i) print(i, s:sub(1, i-1)); return i end)
local function getdef (id, defs)
local c = defs and defs[id]
if not c then error("undefined name: " .. id) end
return c
end
local function patt_error (s, i)
local msg = (#s < i + 20) and s:sub(i)
or s:sub(i,i+20) .. "..."
msg = ("pattern error near '%s'"):format(msg)
error(msg, 2)
end
local function mult (p, n)
local np = mm.P(true)
while n >= 1 do
if n%2 >= 1 then np = np * p end
p = p * p
n = n/2
end
return np
end
local function equalcap (s, i, c)
if type(c) ~= "string" then return nil end
local e = #c + i
if s:sub(i, e - 1) == c then return e else return nil end
end
local S = (Predef.space + "--" * (any - Predef.nl)^0)^0
local name = m.R("AZ", "az", "__") * m.R("AZ", "az", "__", "09")^0
local arrow = S * "<-"
local seq_follow = m.P"/" + ")" + "}" + ":}" + "~}" + "|}" + (name * arrow) + -1
name = m.C(name)
-- a defined name only have meaning in a given environment
local Def = name * m.Carg(1)
local num = m.C(m.R"09"^1) * S / tonumber
local String = "'" * m.C((any - "'")^0) * "'" +
'"' * m.C((any - '"')^0) * '"'
local defined = "%" * Def / function (c,Defs)
local cat = Defs and Defs[c] or Predef[c]
if not cat then error ("name '" .. c .. "' undefined") end
return cat
end
local Range = m.Cs(any * (m.P"-"/"") * (any - "]")) / mm.R
local item = defined + Range + m.C(any)
local Class =
"["
* (m.C(m.P"^"^-1)) -- optional complement symbol
* m.Cf(item * (item - "]")^0, mt.__add) /
function (c, p) return c == "^" and any - p or p end
* "]"
local function adddef (t, k, exp)
if t[k] then
error("'"..k.."' already defined as a rule")
else
t[k] = exp
end
return t
end
local function firstdef (n, r) return adddef({n}, n, r) end
local function NT (n, b)
if not b then
error("rule '"..n.."' used outside a grammar")
else return mm.V(n)
end
end
local exp = m.P{ "Exp",
Exp = S * ( m.V"Grammar"
+ m.Cf(m.V"Seq" * ("/" * S * m.V"Seq")^0, mt.__add) );
Seq = m.Cf(m.Cc(m.P"") * m.V"Prefix"^0 , mt.__mul)
* (#seq_follow + patt_error);
Prefix = "&" * S * m.V"Prefix" / mt.__len
+ "!" * S * m.V"Prefix" / mt.__unm
+ m.V"Suffix";
Suffix = m.Cf(m.V"Primary" * S *
( ( m.P"+" * m.Cc(1, mt.__pow)
+ m.P"*" * m.Cc(0, mt.__pow)
+ m.P"?" * m.Cc(-1, mt.__pow)
+ "^" * ( m.Cg(num * m.Cc(mult))
+ m.Cg(m.C(m.S"+-" * m.R"09"^1) * m.Cc(mt.__pow))
)
+ "->" * S * ( m.Cg((String + num) * m.Cc(mt.__div))
+ m.P"{}" * m.Cc(nil, m.Ct)
+ m.Cg(Def / getdef * m.Cc(mt.__div))
)
+ "=>" * S * m.Cg(Def / getdef * m.Cc(m.Cmt))
) * S
)^0, function (a,b,f) return f(a,b) end );
Primary = "(" * m.V"Exp" * ")"
+ String / mm.P
+ Class
+ defined
+ "{:" * (name * ":" + m.Cc(nil)) * m.V"Exp" * ":}" /
function (n, p) return mm.Cg(p, n) end
+ "=" * name / function (n) return mm.Cmt(mm.Cb(n), equalcap) end
+ m.P"{}" / mm.Cp
+ "{~" * m.V"Exp" * "~}" / mm.Cs
+ "{|" * m.V"Exp" * "|}" / mm.Ct
+ "{" * m.V"Exp" * "}" / mm.C
+ m.P"." * m.Cc(any)
+ (name * -arrow + "<" * name * ">") * m.Cb("G") / NT;
Definition = name * arrow * m.V"Exp";
Grammar = m.Cg(m.Cc(true), "G") *
m.Cf(m.V"Definition" / firstdef * m.Cg(m.V"Definition")^0,
adddef) / mm.P
}
local pattern = S * m.Cg(m.Cc(false), "G") * exp / mm.P * (-any + patt_error)
local function compile (p, defs)
if mm.type(p) == "pattern" then return p end -- already compiled
local cp = pattern:match(p, 1, defs)
if not cp then error("incorrect pattern", 3) end
return cp
end
local function match (s, p, i)
local cp = mem[p]
if not cp then
cp = compile(p)
mem[p] = cp
end
return cp:match(s, i or 1)
end
local function find (s, p, i)
local cp = fmem[p]
if not cp then
cp = compile(p) / 0
cp = mm.P{ mm.Cp() * cp * mm.Cp() + 1 * mm.V(1) }
fmem[p] = cp
end
local i, e = cp:match(s, i or 1)
if i then return i, e - 1
else return i
end
end
local function gsub (s, p, rep)
local g = gmem[p] or {} -- ensure gmem[p] is not collected while here
gmem[p] = g
local cp = g[rep]
if not cp then
cp = compile(p)
cp = mm.Cs((cp / rep + 1)^0)
g[rep] = cp
end
return cp:match(s)
end
-- exported names
local re = {
compile = compile,
match = match,
find = find,
gsub = gsub,
updatelocale = updatelocale,
}
if version == "Lua 5.1" then _G.re = re end
return re

163
cosmic rage/lua/sandbox.lua Normal file
View File

@@ -0,0 +1,163 @@
-- sandbox.lua
--[[
MUSHclient sandbox (taken from versions 4.11 to 4.57)
To enable the sandbox for all Lua scripting add to:
File menu -> Global Preferences -> Lua -> Preliminary Code
... this line:
require "sandbox"
See: http://mushclient.com/security
Note that this sandbox only affects Lua, not other scripting languages.
--]]
trust_all_worlds = false -- change to true to trust all the worlds
trust_all_plugins = false -- change to true to trust all the plugins
warn_if_not_trusted = false -- change to true to show warnings
--[[
-- Lua initialization (sandbox) --> please read comments carefully.
Use this to create a "sandbox" for safe execution of non-trusted scripts.
If you only run your own scripts or plugins then you may not need this.
The code in this area is executed after each Lua script space is created
but before any of your scripts are done. This can be used to initialise things
(eg. load DLLs, load files, set up variables) or to disable things as shown below.
By setting a function name to nil you effectively make it unavailable.
You can remove some functions from a library rather than all of them, eg.
os.execute = nil -- no operating system calls
os.remove = nil -- no deleting files
os.rename = nil -- no renaming files
This script will automatically be replaced if you completely delete it from
the Global Preferences, and restart MUSHclient. To avoid this, leave a comment
in (if you don't want any further action taken).
--]]
-- Example sandbox --
function MakeSandbox ()
local function ReportDisabled (pkg, func)
return function ()
error (string.format (
"Function '%s.%s' disabled in Lua sandbox - see MUSHclient global preferences",
pkg, func), 2)
end -- function
end -- ReportDisabled
package.loadlib = ReportDisabled ("package", "loadlib") -- disable loadlib function
package.loaders [3] = nil -- disable DLL loader
package.loaders [4] = nil -- disable all-in-one loader
for k, v in pairs (io) do
if type (v) == "function" then
io [k] = ReportDisabled ("io", k)
end -- type is function
end -- for
local orig_os = os -- so we know names of disabled ones
-- replace 'os' table with one containing only safe functions
os = {
date = os.date,
time = os.time,
setlocale = os.setlocale,
clock = os.clock,
difftime = os.difftime,
}
for k, v in pairs (orig_os) do
if not os [k] and type (v) == "function" then
os [k] = ReportDisabled ("os", k)
end -- not still active
end -- for
if warn_if_not_trusted then
ColourNote ("yellow", "black",
"Lua sandbox created, some functions disabled.")
end -- if warn_if_not_trusted
end -- end of function MakeSandbox
-- default is to sandbox everything --
-- To trust individual worlds or plugins, add them to the lists below.
-- To find your current world ID, do this: /print (GetWorldID ())
-- Plugin IDs are mentioned near the start of every plugin.
-- You can limit the behaviour to specific worlds, or specific plugins
-- by doing something like this:
do
-- World IDs of worlds we trust - replace with your world IDs
-- (and remove comment from start of line)
local trusted_worlds = {
-- ["a4a1cc1801787ba88cd84f3a"] = true, -- example world A
-- ["cdc8552d1b251e449b874b9a"] = true, -- example world B
-- ["1ec5aac3265e472b97f0c103"] = true, -- example world C
} -- end of trusted_worlds
-- Plugin IDs of plugins we trust - add your plugins to the table
local trusted_plugins = {
[""] = "", -- trust main script (ie. if no plugin running)
["03ca99c4e98d2a3e6d655c7d"] = "Chat",
["982581e59ab42844527eec80"] = "Random_Socials",
["4a267cd69ba59b5ecefe42d8"] = "Installer_sumcheck",
["83beba4e37b3d0e7f63cedbc"] = "Reconnecter",
} -- end of trusted_plugins
-- check worlds
if not trust_all_worlds then
if not trusted_worlds [GetWorldID ()] then
if warn_if_not_trusted then
ColourNote ("yellow", "black", "Untrusted world " .. WorldName () ..
", ID: " .. GetWorldID ())
end -- if warn_if_not_trusted
MakeSandbox ()
end -- not trusted world or plugin
end -- not trusting all worlds
-- check plugins - check name *and* plugin ID
if not trust_all_plugins then
if trusted_plugins [GetPluginID ()] ~= GetPluginName () then
if warn_if_not_trusted then
ColourNote ("yellow", "black", "Untrusted plugin " .. GetPluginName () ..
", ID: " .. GetPluginID ())
end -- if warn_if_not_trusted
MakeSandbox ()
end -- not trusted world or plugin
end -- if not trusting all plugins
end -- local block
-- warn if we can't load DLLs (checkbox might be unchecked)
if not package.loadlib and warn_if_not_trusted then
local by_this_plugin = ""
if GetPluginID () ~= "" then
by_this_plugin = " by this plugin"
end -- this is a plugin
ColourNote ("yellow", "black",
"Loading of DLLs" .. by_this_plugin .. " is disabled.")
end -- if

View File

@@ -0,0 +1,237 @@
-- serialize.lua
module (..., package.seeall)
-- ----------------------------------------------------------
-- serializer
-- See "Programming In Lua" chapter 12.1.2.
-- Also see forum thread:
-- http://www.gammon.com.au/forum/?id=4960
-- ----------------------------------------------------------
--[[
Example of use:
require "serialize"
SetVariable ("mobs", serialize.save ("mobs")) --> serialize mobs table
loadstring (GetVariable ("mobs")) () --> restore mobs table
If you need to serialize two tables where subsequent ones refer to earlier ones
you can supply your own "saved tables" variable, like this:
require "serialize"
result, t = serialize.save ("mobs")
result = result .. "\n" .. serialize.save ("quests", nil, t)
In this example the serializing of "quests" also knows about the "mobs" table
and will use references to it where necessary.
You can also supply the actual variable if the variable to be serialized does
not exist in the global namespace (for instance, if the variable is a local
variable to a function). eg.
require "serialize"
do
local myvar = { 1, 2, 8, 9 }
print (serialize.save ("myvar", myvar))
end
In this example, without supplying the location of "myvar" the serialize would fail
because it would not be found in the _G namespace.
----- Added on 19 July 2007:
You can now do a "simple save" which is intended for tables without cycles. That is,
tables, that do not refer to other tables. This is appropriate for "simple" data, like
a straight table of keys/values, including subtables.
For a simple save, all you need to do is supply the value, like this:
print (serialize.save_simple ({ foo = 22, bar = "hi", t = { s = 9, k = 22 } }))
This produces:
{
t = {
s = 9,
k = 22,
},
bar = "hi",
foo = 22,
}
--]]
local save_item -- forward declaration, function appears near the end
local save_item_simple
function save (what, v, saved)
saved = saved or {} -- initial table of tables we have already done
v = v or _G [what] -- default to "what" in global namespace
assert (type (what) == "string",
"1st argument to serialize.save should be the *name* of a variable")
assert (v, "Variable '" .. what .. "' does not exist")
assert (type (saved) == "table" or saved == nil,
"3rd argument to serialize.save should be a table or nil")
local out = {} -- output to this table
save_item (what, v, out, 0, saved) -- do serialization
return table.concat (out, "\n"), saved -- turn into a string (also return our table)
end -- serialize.save
function save_simple (v)
local out = {} -- output to this table
save_item_simple (v, out, 2) -- do serialization
return table.concat (out) -- turn into a string
end -- serialize.save_simple
--- below are local functions for this module -------------
local function basicSerialize (o)
if type(o) == "number" or type(o) == "boolean" then
return tostring(o)
else -- assume it is a string
return string.format("%q", o)
end
end -- basicSerialize
--
-- Lua keywords might look OK to not be quoted as keys but must be.
-- So, we make a list of them.
--
local lua_reserved_words = {}
for _, v in ipairs ({
"and", "break", "do", "else", "elseif", "end",
"for", "function", "if", "in", "local", "nil", "not", "or",
"repeat", "return", "then", "until", "while"
}) do lua_reserved_words [v] = true end
-- ----------------------------------------------------------
-- save one variable (calls itself recursively)
--
-- Modified on 23 October 2005 to better handle keys (like table keys)
-- ----------------------------------------------------------
function save_item (name, value, out, indent, saved) -- important! no "local" keyword
local iname = string.rep (" ", indent) .. name -- indented name
-- numbers, strings, and booleans can be simply serialized
if type (value) == "number" or
type (value) == "string" or
type (value) == "boolean" then
table.insert (out, iname .. " = " .. basicSerialize(value))
-- tables need to be constructed, unless we have already done it
elseif type (value) == "table" then
if saved[value] then -- value already saved?
table.insert (out, iname .. " = " .. saved[value]) -- use its previous name
else
-- remember we have created this table so we don't do it twice
saved [value] = name -- save name for next time
-- make the table constructor, and recurse to save its contents
table.insert (out, iname .. " = {}") -- create a new table
for k, v in pairs (value) do -- save its fields
local fieldname
-- if key is a Lua variable name which is not a reserved word
-- we can express it as tablename.keyname
if type (k) == "string"
and string.find (k, "^[_%a][_%a%d]*$")
and not lua_reserved_words [k] then
fieldname = string.format("%s.%s", name, k)
-- if key is a table itself, and we know its name then we can use that
-- eg. tablename [ tablekeyname ]
elseif type (k) == "table" and saved[k] then
fieldname = string.format("%s[%s]", name, saved [k])
-- if key is an unknown table, we have to raise an error as we cannot
-- deduce its name
elseif type (k) == "table" then
error ("Key table entry " .. tostring (k) ..
" in table " .. name .. " is not known")
-- if key is a number or a boolean it can simply go in brackets,
-- like this: tablename [5] or tablename [true]
elseif type (k) == "number" or type (k) == "boolean" then
fieldname = string.format("%s[%s]", name, tostring (k))
-- now key should be a string, otherwise an error
elseif type (k) ~= "string" then
error ("Cannot serialize table keys of type '" .. type (k) ..
"' in table " .. name)
-- if key is a non-variable name (eg. "++") then we have to put it
-- in brackets and quote it, like this: tablename ["keyname"]
else
fieldname = string.format("%s[%s]", name,
basicSerialize(k))
end
-- now we have finally worked out a suitable name for the key,
-- recurse to save the value associated with it
save_item(fieldname, v, out, indent + 2, saved)
end
end
-- cannot serialize things like functions, threads
else
error ("Cannot serialize '" .. name .. "' (" .. type (value) .. ")")
end -- if type of variable
end -- save_item
-- saves tables *without* cycles (produces smaller output)
function save_item_simple (value, out, indent)
-- numbers, strings, and booleans can be simply serialized
if type (value) == "number" or
type (value) == "string" or
type (value) == "boolean" then
table.insert (out, basicSerialize(value))
elseif type (value) == "table" then
table.insert (out, "{\n")
for k, v in pairs (value) do -- save its fields
table.insert (out, string.rep (" ", indent))
if not string.find (k, '^[_%a][_%a%d]*$')
or lua_reserved_words [k] then
table.insert (out, "[" .. basicSerialize (k) .. "] = ")
else
table.insert (out, k .. " = ")
end -- if
save_item_simple (v, out, indent + 2)
table.insert (out, ",\n")
end -- for each table item
table.insert (out, string.rep (" ", indent) .. "}")
-- cannot serialize things like functions, threads
else
error ("Cannot serialize " .. type (value))
end -- if type of variable
end -- save_item_simple

View File

@@ -0,0 +1,25 @@
-- show_loaded.lua
--[[
Shows each plugin as it is loaded.
To enable this, add to:
File menu -> Global Preferences -> Lua -> Preliminary Code
... this line:
require "show_loaded"
--]]
if GetPluginID () == "" then
ColourNote ("gray", "", "Initializing main world script ...")
else
ColourNote ("gray", "", string.format ("Loading plugin '%s' (%s) version %0.2f ...",
GetPluginInfo ( GetPluginID (), 7),
GetPluginInfo ( GetPluginID (), 1),
GetPluginInfo ( GetPluginID (), 19)))
end -- if

133
cosmic rage/lua/socket.lua Normal file
View File

@@ -0,0 +1,133 @@
-----------------------------------------------------------------------------
-- LuaSocket helper module
-- Author: Diego Nehab
-- RCS ID: $Id: socket.lua,v 1.22 2005/11/22 08:33:29 diego Exp $
-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
-- Declare module and import dependencies
-----------------------------------------------------------------------------
local base = _G
local string = require("string")
local math = require("math")
local socket = require("socket.core")
module("socket")
-----------------------------------------------------------------------------
-- Exported auxiliar functions
-----------------------------------------------------------------------------
function connect(address, port, laddress, lport)
local sock, err = socket.tcp()
if not sock then return nil, err end
if laddress then
local res, err = sock:bind(laddress, lport, -1)
if not res then return nil, err end
end
local res, err = sock:connect(address, port)
if not res then return nil, err end
return sock
end
function bind(host, port, backlog)
local sock, err = socket.tcp()
if not sock then return nil, err end
sock:setoption("reuseaddr", true)
local res, err = sock:bind(host, port)
if not res then return nil, err end
res, err = sock:listen(backlog)
if not res then return nil, err end
return sock
end
try = newtry()
function choose(table)
return function(name, opt1, opt2)
if base.type(name) ~= "string" then
name, opt1, opt2 = "default", name, opt1
end
local f = table[name or "nil"]
if not f then base.error("unknown key (".. base.tostring(name) ..")", 3)
else return f(opt1, opt2) end
end
end
-----------------------------------------------------------------------------
-- Socket sources and sinks, conforming to LTN12
-----------------------------------------------------------------------------
-- create namespaces inside LuaSocket namespace
sourcet = {}
sinkt = {}
BLOCKSIZE = 2048
sinkt["close-when-done"] = function(sock)
return base.setmetatable({
getfd = function() return sock:getfd() end,
dirty = function() return sock:dirty() end
}, {
__call = function(self, chunk, err)
if not chunk then
sock:close()
return 1
else return sock:send(chunk) end
end
})
end
sinkt["keep-open"] = function(sock)
return base.setmetatable({
getfd = function() return sock:getfd() end,
dirty = function() return sock:dirty() end
}, {
__call = function(self, chunk, err)
if chunk then return sock:send(chunk)
else return 1 end
end
})
end
sinkt["default"] = sinkt["keep-open"]
sink = choose(sinkt)
sourcet["by-length"] = function(sock, length)
return base.setmetatable({
getfd = function() return sock:getfd() end,
dirty = function() return sock:dirty() end
}, {
__call = function()
if length <= 0 then return nil end
local size = math.min(socket.BLOCKSIZE, length)
local chunk, err = sock:receive(size)
if err then return nil, err end
length = length - string.len(chunk)
return chunk
end
})
end
sourcet["until-closed"] = function(sock)
local done
return base.setmetatable({
getfd = function() return sock:getfd() end,
dirty = function() return sock:dirty() end
}, {
__call = function()
if done then return nil end
local chunk, err, partial = sock:receive(socket.BLOCKSIZE)
if not err then return chunk
elseif err == "closed" then
sock:close()
done = 1
return partial
else return nil, err end
end
})
end
sourcet["default"] = sourcet["until-closed"]
source = choose(sourcet)

View File

@@ -0,0 +1,35 @@
-- strict.lua
-- by Roberto Ierusalimschy
-- checks uses of undeclared global variables
-- All global variables must be 'declared' through a regular assignment
-- (even assigning nil will do) in a main chunk before being used
-- anywhere or assigned to inside a function.
--
local mt = getmetatable(_G)
if mt == nil then
mt = {}
setmetatable(_G, mt)
end
mt.__declared = {}
mt.__newindex = function (t, n, v)
if not mt.__declared[n] then
local w = debug.getinfo(2, "S").what
if w ~= "main" and w ~= "C" then
error("assign to undeclared variable '"..n.."'", 2)
end
mt.__declared[n] = true
end
rawset(t, n, v)
end
mt.__index = function (t, n)
if not mt.__declared[n] then
error("variable '"..n.."' is not declared", 2)
end
return rawget(t, n)
end

View File

@@ -0,0 +1,24 @@
--[[
string_indexing.lua
If you install this, then you can index into strings, like this:
require "string_indexing"
a = "nick"
print (a [3]) --> c
--]]
getmetatable ("").__index = function (str, i)
if (type (i) == "number") then
return string.sub (str, i, i) -- index into str
end -- if
return string [i] -- fallback (eg. string.match)
end -- function

View File

@@ -0,0 +1,313 @@
-- tabbed_window.lua
--[[
Author: Nick Gammon
Date: 7th February 2018
Generic tabbed miniwindow drawer.
Exposed functions:
init (context) -- call once (from OnPluginInstall), supply:
context.win -- window ID for this tabbed window
context.tabfont -- table of font info for non-active tabs (name, size, unicode, bold, italic)
context.activefont -- table of font info for the selected tab (name, size, unicode, bold, italic)
context.titlefont -- table of font info for the title bar (name, size, unicode, bold, italic)
context.colours -- table of assorted colours (see below for examples)
context.window -- table (width and height) of the desired window size
context.tab_filler -- gap between tabs
context.can_move -- can window be dragged around?
context.active_tab -- the currently active tab
context.tabs -- table of tables - one per tab (name, handler)
draw_window (context, which_tab) -- draws the window with which_tab as the active tab
hide_window (context) -- hides this window
save_state (context) -- for saving the window position (call from OnPluginSaveState)
Exposed variables:
VERSION -- version of this module
--]]
module (..., package.seeall)
VERSION = 1.1 -- -- for querying by plugins
require "movewindow" -- load the movewindow.lua module
local default_context = {
-- window ID
win = "tabbed_window_" .. GetUniqueID (),
-- font for inactive tab text
tabfont = {
id = "fn",
name = "Lucida Console",
size = 10, -- points
unicode = false, -- Unicode?
bold = false, -- make bold?
italic = false, -- make italic?
}, -- end of tabfont
-- font for active tab text
activefont = {
id = "fa",
name = "Lucida Console",
size = 10, -- points
unicode = false, -- Unicode?
bold = false, -- make bold?
italic = false, -- make italic?
}, -- end of activefont
-- font for title text
titlefont = {
id = "ft",
name = "Lucida Console",
size = 12, -- points
unicode = false, -- Unicode?
bold = true, -- make bold?
italic = false, -- make italic?
}, -- end of titlefont
colours = {
background = ColourNameToRGB ("lightskyblue"), -- entire window background
frame = ColourNameToRGB ("mediumorchid"), -- frame around window
title_bar = ColourNameToRGB ("palegoldenrod"), -- behind the title
title = ColourNameToRGB ("mediumblue"), -- title text
tab = ColourNameToRGB ("green"), -- tab background
tab_text = ColourNameToRGB ("white"), -- inactive tab text
active_tab_text = ColourNameToRGB ("lightgreen"), -- active tab text
upper_line = ColourNameToRGB ("lightgray"), -- line above tab text
lower_line = ColourNameToRGB ("lightgray"), -- line below tab text
vertical_line = ColourNameToRGB ("lightgray"), -- line between tabs
}, -- end of colours
-- miniwindow size
window = {
width = 300,
height = 200,
}, -- end of window info
-- gap between text in tabs
tab_filler = 10,
-- can it be moved?
can_move = true,
-- which tab is active
active_tab = 1,
-- tabs
-- table of tabs. For each one supply a table containing a name and a handler,
-- eg. { name = "Circle", handler = DrawCircle },
tabs = { },
} -- end of default_context
-- -------------------------------------------------------------------------------
-- init - initialize this module ready for use (do once)
-- -------------------------------------------------------------------------------
function init (context)
-- make copy of colours, sizes etc.
assert (type (context) == "table", "No 'context' table supplied to tabbed_window.")
-- force some context defaults if not supplied
for k, v in pairs (default_context) do
-- some context items are tables so copy the table entries
if type (v) == 'table' then
if type (context [k]) ~= 'table' then
assert (context [k] == nil, "tabbed window context item '" .. k .. "' should be a table or nil")
context [k] = { } -- make table if required
end -- not having a table there
for k1, v1 in pairs (v) do
context [k] [k1] = context [k] [k1] or v1
end -- for each sub-table item
else
context [k] = context [k] or v
end -- if table or not
end -- for
if next (context.tabs) == nil then
ColourNote ("orange", "", "Warning: no tabs define for tabbed window")
end -- if
-- install the window movement handler, get back the window position
context.windowinfo = movewindow.install (context.win, miniwin.pos_top_left, 0) -- default to top left
-- save a bit of typing
local windowinfo = context.windowinfo
local win = context.win
local tabfont = context.tabfont
local activefont = context.activefont
local titlefont = context.titlefont
local window = context.window
local colours = context.colours
-- make window so I can grab the font info
WindowCreate (win,
windowinfo.window_left,
windowinfo.window_top,
window.width,
window.height,
windowinfo.window_mode,
windowinfo.window_flags,
colours.background)
WindowFont (win, tabfont.id, tabfont.name, tabfont.size, tabfont.bold, tabfont.italic, false, false, 0, 0)
tabfont.height = WindowFontInfo (win, tabfont.id, 1) -- height
WindowFont (win, activefont.id, activefont.name, activefont.size, activefont.bold, activefont.italic, false, false, 0, 0)
activefont.height = WindowFontInfo (win, activefont.id, 1) -- height
WindowFont (win, titlefont.id, titlefont.name, titlefont.size, titlefont.bold, titlefont.italic, false, false, 0, 0)
titlefont.height = WindowFontInfo (win, titlefont.id, 1) -- height
context.client_bottom = context.window.height - context.tabfont.height - 8
end -- init
function save_state (context)
-- save window current location for next time
movewindow.save_state (context.win)
end -- save_state
-- -------------------------------------------------------------------------------
-- draw_window - draw the tabbed window
-- -------------------------------------------------------------------------------
function draw_window (context, whichTab)
context.active_tab = whichTab
-- save a bit of typing
local windowinfo = context.windowinfo
local win = context.win
local tabfont = context.tabfont
local activefont = context.activefont
local titlefont = context.titlefont
local window = context.window
local colours = context.colours
local tabs = context.tabs
local client_bottom = context.client_bottom
local tab_filler = context.tab_filler
-- clear window
WindowRectOp (win, miniwin.rect_fill, 0, 0, 0, 0, colours.background)
WindowDeleteAllHotspots (win)
-- draw drag bar rectangle
WindowRectOp (win, miniwin.rect_fill, 1, 1, 0, titlefont.height + 2, colours.title_bar)
-- add the drag handler so they can move the window around
if context.can_move then
movewindow.add_drag_handler (win, 0, 0, 0, titlefont.height)
end -- if can move the window
local thisTab = tabs [whichTab]
if not thisTab then
ColourNote ("orange", "", "Tab " .. whichTab .. " does not exist")
return
end -- no such tab
-- find title width so we can center it
local title_width = WindowTextWidth (win, titlefont.id, thisTab.name, titlefont.unicode)
-- draw title
WindowText(win, titlefont.id, thisTab.name, (window.width - title_width )/ 2 + 1, 1, 0, 0, colours.title, titlefont.unicode)
-- frame window
WindowRectOp (win, miniwin.rect_frame, 0, 0, 0, 0, colours.frame)
-- draw tabs
local left = 1
-- we have to make a function to handle each tabbed window, so its
-- name must include the window ID
local mouse_down_function_name = "tabbed_window_mouse_down_handler_" .. win
-- the function will have the context as a closure
getfenv () [mouse_down_function_name] = function (flags, hotspot_id)
local whichTab = string.match (hotspot_id, "^hs(%d)$")
if not whichTab then -- tab unknown for some reason
ColourNote ("orange", "", "Mousedown tab unknown " .. hotspot_id)
return
end -- if
draw_window (context, tonumber (whichTab))
end -- mouse_down_function_name
for k, v in ipairs (tabs) do
local tab_width
if k == whichTab then
tab_width = WindowTextWidth (win, activefont.id, v.name, activefont.unicode)
else
tab_width = WindowTextWidth (win, tabfont.id, v.name, tabfont.unicode)
end -- if
-- tab background
WindowRectOp (win, miniwin.rect_fill, left, client_bottom, left + tab_width + tab_filler, window.height - 1, colours.tab)
-- tab text
if k == whichTab then
WindowText(win, activefont.id, v.name, left + tab_filler / 2, client_bottom + 4, 0, 0, colours.active_tab_text, activefont.unicode)
else
WindowText(win, tabfont.id, v.name, left + tab_filler / 2, client_bottom + 4, 0, 0, colours.tab_text, tabfont.unicode)
end -- if
-- draw upper line if not active tab
if k ~= whichTab then
WindowLine(win, left, client_bottom + 2, left + tab_width + tab_filler, client_bottom + 2,
colours.upper_line, miniwin.pen_solid, 2)
end -- if not active
-- draw lower line
WindowLine(win, left, window.height - 1, left + tab_width + tab_filler, window.height - 1,
colours.lower_line, miniwin.pen_solid, 1)
-- draw vertical lines
WindowLine(win, left, client_bottom + 2, left, window.height - 1,
colours.vertical_line, miniwin.pen_solid, 1)
WindowLine(win, left + tab_width + tab_filler, client_bottom + 2, left + tab_width + tab_filler, window.height - 1,
colours.vertical_line, miniwin.pen_solid, 1)
-- now add a hotspot for this tab
WindowAddHotspot(win, "hs" .. k,
left, client_bottom, left + tab_width + tab_filler, window.height - 1, -- rectangle
"", -- mouseover
"", -- cancelmouseover
"tabbed_window." .. mouse_down_function_name,
"", -- cancelmousedown
"", -- mouseup
"Show " .. v.name .. " tab", -- tooltip text
miniwin.cursor_hand, 0) -- hand cursor
left = left + tab_width + tab_filler
end -- for each tab
-- call handler to draw rest of window
local handler = thisTab.handler
if handler then
handler (win, 1, titlefont.height + 2, window.width - 1, client_bottom, context)
else
ColourNote ("orange", "", "No tab handler for " .. thisTab.name)
end -- if
WindowShow (win, true)
end -- draw_window
-- -------------------------------------------------------------------------------
-- hide_window - hide the tabbed window
-- -------------------------------------------------------------------------------
function hide_window (context)
WindowShow (context.win, false)
end -- hide_window

View File

@@ -0,0 +1,47 @@
--
-- tprint.lua
--[[
For debugging what tables have in them, prints recursively
See forum thread: http://www.gammon.com.au/forum/?id=4903
eg.
require "tprint"
tprint (GetStyleInfo (20))
--]]
function tprint (t, indent, done)
-- in case we run it standalone
local Note = Note or print
local Tell = Tell or io.write
-- show strings differently to distinguish them from numbers
local function show (val)
if type (val) == "string" then
return '"' .. val .. '"'
else
return tostring (val)
end -- if
end -- show
-- entry point here
done = done or {}
indent = indent or 0
for key, value in pairs (t) do
Tell (string.rep (" ", indent)) -- indent it
if type (value) == "table" and not done [value] then
done [value] = true
Note (show (key), ":");
tprint (value, indent + 2, done)
else
Tell (show (key), "=")
print (show (value))
end
end
end
return tprint

48
cosmic rage/lua/var.lua Normal file
View File

@@ -0,0 +1,48 @@
-- var.lua
-- ----------------------------------------------------------
-- Accessing MUSHclient variables through the 'var' table.
-- See forum thread:
-- http://www.gammon.com.au/forum/?id=4904
--[[
* Set a variable by assigning something to it.
* Delete a variable by assigning nil to it.
* Get a variable by retrieving its value, will return nil if the variable does not exist.
Examples:
var.target = "kobold" -- set MUSHclient variable 'target' to kobold
print (var.target) -- print contents of MUSHclient variable
--]]
-- ----------------------------------------------------------
var = {} -- variables table
setmetatable (var,
{
-- called to access an entry
__index =
function (t, name)
return GetVariable (name)
end,
-- called to change or delete an entry
__newindex =
function (t, name, val)
local result
if val == nil then -- nil deletes it
result = DeleteVariable (name)
else
result = SetVariable (name, tostring (val))
end
-- warn if they are using bad variable names
if result == error_code.eInvalidObjectLabel then
error ("Bad variable name '" .. name .. "'", 2)
end
end
})
return var

180
cosmic rage/lua/wait.lua Normal file
View File

@@ -0,0 +1,180 @@
-- wait.lua
-- ----------------------------------------------------------
-- 'wait' stuff - lets you build pauses into scripts.
-- See forum thread:
-- http://www.gammon.com.au/forum/?id=4957
-- ----------------------------------------------------------
--[[
Example of an alias 'send to script':
require "wait"
wait.make (function () --- coroutine below here
repeat
Send "cast heal"
line, wildcards =
wait.regexp ("^(You heal .*|You lose your concentration)$")
until string.find (line, "heal")
-- wait a second for luck
wait.time (1)
end) -- end of coroutine
--]]
require "check"
module (..., package.seeall)
-- ----------------------------------------------------------
-- table of outstanding threads that are waiting
-- ----------------------------------------------------------
local threads = {}
-- ----------------------------------------------------------
-- wait.timer_resume: called by a timer to resume a thread
-- ----------------------------------------------------------
function timer_resume (name)
local thread = threads [name]
if thread then
threads [name] = nil
local ok, err = coroutine.resume (thread)
if not ok then
ColourNote ("deeppink", "black", "Error raised in timer function (in wait module).")
ColourNote ("darkorange", "black", debug.traceback (thread))
error (err)
end -- if
end -- if
end -- function timer_resume
-- ----------------------------------------------------------
-- wait.trigger_resume: called by a trigger to resume a thread
-- ----------------------------------------------------------
function trigger_resume (name, line, wildcards, styles)
local thread = threads [name]
if thread then
threads [name] = nil
local ok, err = coroutine.resume (thread, line, wildcards, styles)
if not ok then
ColourNote ("deeppink", "black", "Error raised in trigger function (in wait module)")
ColourNote ("darkorange", "black", debug.traceback (thread))
error (err)
end -- if
end -- if
end -- function trigger_resume
-- ----------------------------------------------------------
-- convert x seconds to hours, minutes, seconds (for AddTimer)
-- ----------------------------------------------------------
local function convert_seconds (seconds)
local hours = math.floor (seconds / 3600)
seconds = seconds - (hours * 3600)
local minutes = math.floor (seconds / 60)
seconds = seconds - (minutes * 60)
return hours, minutes, seconds
end -- function convert_seconds
-- ----------------------------------------------------------
-- wait.time: we call this to wait in a script
-- ----------------------------------------------------------
function time (seconds)
local id = "wait_timer_" .. GetUniqueNumber ()
threads [id] = assert (coroutine.running (), "Must be in coroutine")
local hours, minutes, seconds = convert_seconds (seconds)
check (AddTimer (id, hours, minutes, seconds, "",
bit.bor (timer_flag.Enabled,
timer_flag.OneShot,
timer_flag.Temporary,
timer_flag.ActiveWhenClosed,
timer_flag.Replace),
"wait.timer_resume"))
return coroutine.yield ()
end -- function time
-- ----------------------------------------------------------
-- wait.regexp: we call this to wait for a trigger with a regexp
-- ----------------------------------------------------------
function regexp (regexp, timeout, flags)
local id = "wait_trigger_" .. GetUniqueNumber ()
threads [id] = assert (coroutine.running (), "Must be in coroutine")
check (AddTriggerEx (id, regexp,
"-- added by wait.regexp",
bit.bor (flags or 0, -- user-supplied extra flags, like omit from output
trigger_flag.Enabled,
trigger_flag.RegularExpression,
trigger_flag.Temporary,
trigger_flag.Replace,
trigger_flag.OneShot),
custom_colour.NoChange,
0, "", -- wildcard number, sound file name
"wait.trigger_resume",
12, 100)) -- send to script (in case we have to delete the timer)
-- if timeout specified, also add a timer
if timeout and timeout > 0 then
local hours, minutes, seconds = convert_seconds (timeout)
-- if timer fires, it deletes this trigger
check (AddTimer (id, hours, minutes, seconds,
"DeleteTrigger ('" .. id .. "')",
bit.bor (timer_flag.Enabled,
timer_flag.OneShot,
timer_flag.Temporary,
timer_flag.ActiveWhenClosed,
timer_flag.Replace),
"wait.timer_resume"))
check (SetTimerOption (id, "send_to", "12")) -- send to script
-- if trigger fires, it should delete the timer we just added
check (SetTriggerOption (id, "send", "DeleteTimer ('" .. id .. "')"))
end -- if having a timeout
return coroutine.yield () -- return line, wildcards
end -- function regexp
-- ----------------------------------------------------------
-- wait.match: we call this to wait for a trigger (not a regexp)
-- ----------------------------------------------------------
function match (match, timeout, flags)
return regexp (MakeRegularExpression (match), timeout, flags)
end -- function match
-- ----------------------------------------------------------
-- wait.make: makes a coroutine and resumes it
-- ----------------------------------------------------------
function make (f)
assert (type (f) == "function", "wait.make requires a function")
-- More friendly failure, suggested by Fiendish
local errors = {}
if GetOption ("enable_timers") ~= 1 then
table.insert (errors, "TIMERS")
end -- if timers disabled
if GetOption ("enable_triggers") ~= 1 then
table.insert (errors, "TRIGGERS")
end -- if triggers disabled
if #errors ~= 0 then
ColourNote("white", "red",
"One of your scripts (in '" ..
(GetPluginInfo(GetPluginID(), 1) or "World") ..
"') just did something that requires " ..
table.concat (errors, " and ") ..
" to be enabled, but they aren't. " ..
"Please check your configuration settings.")
return nil, "Trigger/Timers not enabled" -- bad return
end -- if have errors
coroutine.wrap (f) () -- make coroutine, resume it
return true -- good return
end -- make

View File

@@ -0,0 +1,298 @@
-- Convert a number to words
-- Author: Nick Gammon
-- Date: 18th March 2010
-- See: http://www.gammon.com.au/forum/?id=10155
-- Examples of use:
-- words = convert_numbers_to_words ("94921277802687490518")
-- number = convert_words_to_numbers ("one hundred eight thousand three hundred nine")
-- Both functions return nil and an error message so you can check for failure,
-- or assert, eg. words = assert (convert_numbers_to_words ("2687490518"))
-- Units, must be in inverse order!
-- The trailing space is required as the space between words
local inverse_units = {
"vigintillion ", -- 10^63
"novemdecillion ", -- 10^60
"octodecillion ", -- 10^57
"septendecillion ", -- 10^54
"sexdecillion ", -- 10^51
"quindecillion ", -- 10^48
"quattuordecillion ",-- 10^45
"tredecillion ", -- 10^42
"duodecillion ", -- 10^39
"undecillion ", -- 10^36
"decillion ", -- 10^33
"nonillion ", -- 10^30
"octillion ", -- 10^27
"septillion ", -- 10^24
"sextillion ", -- 10^21
"quintillion ", -- 10^18
"quadrillion ", -- 10^15
"trillion ", -- 10^12
"billion ", -- 10^9
"million ", -- 10^6
"thousand ", -- 10^3
} -- inverse_units
local inverse_numbers = {
"one ",
"two ",
"three ",
"four ",
"five ",
"six ",
"seven ",
"eight ",
"nine ",
"ten ",
"eleven ",
"twelve ",
"thirteen ",
"fourteen ",
"fifteen ",
"sixteen ",
"seventeen ",
"eighteen ",
"nineteen ",
"twenty ",
[30] = "thirty ",
[40] = "forty ",
[50] = "fifty ",
[60] = "sixty ",
[70] = "seventy ",
[80] = "eighty ",
[90] = "ninety ",
} -- inverse_numbers
local function convert_up_to_999 (n)
if n <= 0 then
return ""
end -- if zero
local hundreds = math.floor (n / 100)
local tens = math.floor (n % 100)
local result = ""
-- if over 99 we need to say x hundred
if hundreds > 0 then
result = inverse_numbers [hundreds] .. "hundred "
if tens == 0 then
return result
end -- if only a digit in the hundreds column
-- to have "and" between things like "hundred and ten"
-- uncomment the next line
-- result = result .. "and "
end -- if
-- up to twenty it is then just five hundred (and) fifteen
if tens <= 20 then
return result .. inverse_numbers [tens]
end -- if
-- otherwise we need: thirty (something)
result = result .. inverse_numbers [math.floor (tens / 10) * 10]
-- get final digit (eg. thirty four)
local digits = math.floor (n % 10)
-- to put a hyphen between things like "forty-two"
-- uncomment the WITH HYPHEN line and
-- comment out the NO HYPHEN line
if digits > 0 then
result = result .. inverse_numbers [digits] -- NO HYPHEN
-- result = string.sub (result, 1, -2) .. "-" .. inverse_numbers [digits] -- WITH HYPHEN
end -- if
return result
end -- convert_up_to_999
-- convert a number to words
-- See: http://www.gammon.com.au/forum/?id=10155
function convert_numbers_to_words (n)
local s = tostring (n)
-- preliminary sanity checks
local c = string.match (s, "%D")
if c then
return nil, "Non-numeric digit '" .. c .. "' in number"
end -- if
if #s == 0 then
return nil, "No number supplied"
elseif #s > 66 then
return nil, "Number too big to convert to words"
end -- if
-- make multiple of 3
while #s % 3 > 0 do
s = "0" .. s
end -- while
local result = ""
local start = #inverse_units - (#s / 3) + 2
for i = start, #inverse_units do
local group = tonumber (string.sub (s, 1, 3))
if group > 0 then
result = result .. convert_up_to_999 (group) .. inverse_units [i]
end -- if not zero
s = string.sub (s, 4)
end -- for
result = result .. convert_up_to_999 (tonumber (s))
if result == "" then
result = "zero"
end -- if
return (string.gsub (result, " +$", "")) -- trim trailing spaces
end -- convert_numbers_to_words
-- Convert words to a number
-- Author: Nick Gammon
-- Date: 18th March 2010
-- Does NOT handle decimal places (eg. four point six)
local numbers = {
zero = bc.number (0),
one = bc.number (1),
two = bc.number (2),
three = bc.number (3),
four = bc.number (4),
five = bc.number (5),
six = bc.number (6),
seven = bc.number (7),
eight = bc.number (8),
nine = bc.number (9),
ten = bc.number (10),
eleven = bc.number (11),
twelve = bc.number (12),
thirteen = bc.number (13),
fourteen = bc.number (14),
fifteen = bc.number (15),
sixteen = bc.number (16),
seventeen = bc.number (17),
eighteen = bc.number (18),
nineteen = bc.number (19),
twenty = bc.number (20),
thirty = bc.number (30),
forty = bc.number (40),
fifty = bc.number (50),
sixty = bc.number (60),
seventy = bc.number (70),
eighty = bc.number (80),
ninety = bc.number (90),
} -- numbers
local units = {
hundred = bc.number ("100"),
thousand = bc.number ("1" .. string.rep ("0", 3)),
million = bc.number ("1" .. string.rep ("0", 6)),
billion = bc.number ("1" .. string.rep ("0", 9)),
trillion = bc.number ("1" .. string.rep ("0", 12)),
quadrillion = bc.number ("1" .. string.rep ("0", 15)),
quintillion = bc.number ("1" .. string.rep ("0", 18)),
sextillion = bc.number ("1" .. string.rep ("0", 21)),
septillion = bc.number ("1" .. string.rep ("0", 24)),
octillion = bc.number ("1" .. string.rep ("0", 27)),
nonillion = bc.number ("1" .. string.rep ("0", 30)),
decillion = bc.number ("1" .. string.rep ("0", 33)),
undecillion = bc.number ("1" .. string.rep ("0", 36)),
duodecillion = bc.number ("1" .. string.rep ("0", 39)),
tredecillion = bc.number ("1" .. string.rep ("0", 42)),
quattuordecillion = bc.number ("1" .. string.rep ("0", 45)),
quindecillion = bc.number ("1" .. string.rep ("0", 48)),
sexdecillion = bc.number ("1" .. string.rep ("0", 51)),
septendecillion = bc.number ("1" .. string.rep ("0", 54)),
octodecillion = bc.number ("1" .. string.rep ("0", 57)),
novemdecillion = bc.number ("1" .. string.rep ("0", 60)),
vigintillion = bc.number ("1" .. string.rep ("0", 63)),
} -- units
-- convert a number in words to a numeric form
-- See: http://www.gammon.com.au/forum/?id=10155
-- Thanks to David Haley
function convert_words_to_numbers (s)
local stack = {}
local previous_type
for word in string.gmatch (s:lower (), "[%a%d]+") do
if word ~= "and" then -- skip "and" (like "hundred and fifty two")
local top = #stack
-- If the current word is a number (English or numeric),
-- and the previous word was also a number, pop the previous number
-- from the stack and push the addition of the two numbers.
-- Otherwise, push the new number.
local number = tonumber (word) -- try for numeric (eg. 22 thousand)
if number then
number = bc.number (number) -- turn into "big number"
else
number = numbers [word]
end -- if a number-word "like: twenty"
if number then
if previous_type == "number" then -- eg. forty three
local previous_number = table.remove (stack, top) -- get the forty
number = number + previous_number -- add three
end -- if
table.insert (stack, number)
previous_type = "number"
else
-- If the current word is a unit, multiply the number on the top of the stack by the unit's magnitude.
local unit = units [word]
if not unit then
return nil, "Unexpected word: " .. word
end -- not unit
previous_type = "unit"
-- It is an error to get a unit before a number.
if top == 0 then
return nil, "Cannot have unit before a number: " .. word
end -- starts of with something like "thousand"
-- pop until we get something larger on the stack
local interim_result = bc.number (0)
while top > 0 and stack [top] < unit do
interim_result = interim_result + table.remove (stack, top)
top = #stack
end -- while
table.insert (stack, interim_result * unit)
end -- if number or not
end -- if 'and'
end -- for each word
if #stack == 0 then
return nil, "No number found"
end -- nothing
-- When the input has been parsed, sum all numbers on the stack.
local result = bc.number (0)
for _, item in ipairs (stack) do
result = result + item
end -- for
return result
end -- function convert_words_to_numbers