aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/libraries
diff options
context:
space:
mode:
Diffstat (limited to 'libraries')
-rw-r--r--libraries/skang.lua1686
1 files changed, 1686 insertions, 0 deletions
diff --git a/libraries/skang.lua b/libraries/skang.lua
new file mode 100644
index 0000000..23549c3
--- /dev/null
+++ b/libraries/skang.lua
@@ -0,0 +1,1686 @@
1--[[ TODO - This should be in C, but so far development has been quite rapid doing it in Lua.
2
3C will let us -
4 Actually do the widget stuff.
5 Slap meta tables on all value types.
6 Which lets us put the meta table on the variable, instead of on the table, which I think is cleaner.
7 Figure out the directory separator.
8 Network stuff. No need to look at Lua socket stuff, we have Ecore_Con.
9 Database stuff. No need to look at Lua SQL stuff, we have esskyuehl. Maybe.
10
11 Actually, we could have the best of both worlds, since it is currently a C / Lua hybrid. B-)
12]]
13
14
15--[[ Skang package
16
17In here should live all the internals of matrix-RAD that don't
18specifically relate to widgets. This would include the "window" though.
19
20skang.module(Evas)
21skang.module(Elementary)
22skang.skang('some/skang/file.skang')
23
24This package is also what "apps" that use the system should "inherit"
25from, in the same way matrix-RAD apps did. Skang "apps" could be Lua
26modules. They could also be C code, like the extantz modules are likely
27to be. Skang "apps" would automatically be associated with skang files
28of the same name.
29
30For a .skang file, the skang command (written in C) would strip off the
31first line, add the two implied lines, then run it as Lua. The
32skang.load() command would do the same. So that skang C comand would
33just pass the file name to skang.load() in this library. B-)
34
35The old skang argument types are -
36
37{"name", "java.lang.String"},
38{"action", "java.lang.String"},
39{"type", "java.lang.String"},
40{"data", "java.lang.String"},
41{"URL", "java.lang.String"},
42{"file", "java.lang.String"},
43{"method", "java.lang.String"},
44{"lx", "java.lang.String"},
45{"ly", "java.lang.String"},
46{"lw", "java.lang.String"},
47{"lh", "java.lang.String"},
48{"normal", "java.lang.String"},
49{"ghost", "java.lang.String"},
50{"active", "java.lang.String"},
51{"toggle", "java.lang.String"},
52{"boolean","java.lang.Boolean"},
53{"number", "java.lang.Integer"},
54{"int", "java.lang.Integer"},
55{"x", "java.lang.Integer"},
56{"y", "java.lang.Integer"},
57{"w", "java.lang.Integer"},
58{"h", "java.lang.Integer"},
59{"r", "java.lang.Integer"},
60{"g", "java.lang.Integer"},
61{"b", "java.lang.Integer"},
62{"alpha", "java.lang.Integer"},
63{"acl", "net.matrix_rad.security.ACL"},
64]]
65
66
67-- Wrapping the entire module in do .. end helps if people just join a bunch of modules together, which apparently is popular.
68-- By virtue of the fact we are stuffing our result into package.loaded[], just plain running this works as "loading the module".
69do -- Only I'm not gonna indent this.
70
71mainSkin = {}
72
73-- TODO - This needs to be expanded a bit to cover things like 1.42
74local versions = {
75 '0%.0', 'unwritten', 'Just a stub, no code at all, or completely non-existant.',
76 '0%.1', 'prototype', 'Stuff that has only been prototyped, or partly written. There is some code, and it may even work, but it is not even close to the finished product.',
77 '%d%.3', 'written', 'Stuff that has already been written. It may not be perfect, but it is considered an aproximation of the finished product.',
78 '%d%.5', 'alpha', 'Version being tested by official alpha testers.',
79 '%d%.9', 'beta', 'Version passed alpha testing, but not ready for final release.',
80 '1%.0', 'final', 'Version ready for final release, fully tested.',
81 '3%.0', 'poetry', 'Near perfection has been acheived.',
82 '5%.0', 'nirvana', 'Perfection has been acheived.',
83 '9%.0', 'bible', 'This is the Whord of Ghod.',
84}
85
86-- Trying to capture best practices here for creating modules, especially since module() is broken and deprecated.
87-- TODO - Should parse in license type to.
88moduleBegin = function (name, author, copyright, version, timestamp, skin, isLua)
89 local _M = {} -- This is what we return to require().
90 local level = 2
91
92 if 'nil' == type(isLua) then isLua = true end
93
94 package.loaded[name] = _M -- Stuff the result into where require() can find it, instead of returning it at the end.
95 -- Returning it at the end does the same thing.
96 -- This is so that we can have all the module stuff at the top, in this function.
97 -- Should do this before any further require(), so that circular references don't blow out.
98
99 -- Save the callers environment.
100 local savedEnvironment
101 if isLua then
102 savedEnvironment = getfenv(level)
103 else
104 -- While the above works fine for test_c, it doesn't for GuiLua. Odd.
105 savedEnvironment = getfenv(1)
106 end
107
108 -- Clone the environment into _M, so the module can access everything as usual after the setfenv() below.
109 --[[ TODO - Check if this also clones _G or _ENV. And see if it leaks stuff in either direction.
110 local _G = _G -- Only sets a local _G for this function.
111 _M._G = _G -- This clone loop might do this, but we don't want to be able to access the old _G from outside via this leak.
112 In Lua 5.1 at least, _G was special. In 5.2, _ENV sorta replaces setfenv(), but no idea if this clone loop stomps on that.
113 ]]
114 for k, v in pairs(savedEnvironment) do
115 _M[k] = v
116 end
117
118 _M._M = _M -- So that references to _M below the setfenv() actually go to the real _M.
119 _M._NAME = name
120 _M._PACKAGE = string.gsub(_M._NAME, "[^.]*$", "") -- Strip the name down to the package name.
121 _M.isLua = isLua
122
123 -- Parse in an entire copyright message, and strip that down into bits, to put back together.
124 local date, owner = string.match(copyright, '[Cc]opyright (%d%d%d%d) (.*)')
125 _M.AUTHOR = author or owner
126 _M.COPYRIGHT = 'Copyright ' .. date .. ' ' .. _M.AUTHOR
127 -- Translate the version number into a version string.
128 local versionName, versionDesc = ' ', ''
129 for i = 1, #versions / 3 do
130 if 1 == string.find(version, versions[i]) then
131 versionName = ' ' .. versions[i + 1] .. ' '
132 versionDesc = versions[i + 2]
133 break
134 end
135 end
136 _M.VERSION = version .. versionName .. timestamp
137 _M.VERSION_DESC = versionDesc
138 -- If there is a .skang file, read that in and override the passed in skin.
139 local f = io.open(name .. '.skang')
140 if f then
141 skin = f:read('*l')
142 if '#' == string.sub(skin, 1, 1) then skin = '' end
143 skin = skin .. f:read('*a')
144 f:close()
145 end
146 if skin then
147 skin = "local skang = require 'skang'\nlocal " .. name .. " = require '" .. name .. "'\n" .. skin
148 if nil == mainSkin._NAME then mainSkin = _M end
149 end
150 _M.DEFAULT_SKANG = skin
151
152 --_G[_M._NAME] = _M -- Stuff it into a global of the same name.
153 -- Not such a good idea to stomp on global name space.
154 -- It's also redundant coz we get stored in package.loaded[_M._NAME] anyway.
155 -- This is why module() is broken.
156 _M.savedEnvironment = savedEnvironment
157 -- NOTE - setfenv() wont work if the environment it refers to is a C function. Worse, getfenv() returns the global environment, so we can't tell.
158 if isLua then
159 -- setfenv() sets the environment for the FUNCTION, stack level deep.
160 -- The number is the stack level -
161 -- 0 running thread, 1 current function, 2 function that called this function, etc
162 setfenv(level, _M) -- Use the result for the modules internal global environment, so they don't need to qualify internal names.
163 -- Dunno if this causes problems with the do ... end style of joining modules. It does. So we need to restore in moduleEnd().
164 -- Next question, does this screw with the environment of the skang module? No it doesn't, coz that's set up at require 'skang' time.
165 end
166
167 print('Loaded module ' .. _M._NAME .. ' version ' .. _M.VERSION .. ', ' .. _M.COPYRIGHT .. '.\n ' .. _M.VERSION_DESC)
168
169 return _M
170end
171
172
173--[[ Parse command line parameters.
174
175This is done in two parts. Skang will do an initial scan and tokenise,
176then each module gets a chance to pull it's own Things from the result.
177
178Make the command line parameter getting MUCH more intelligent, try to support the common
179command line interfaces -
180
181arg value
182a value
183/arg value
184/a value
185--arg value
186--a value
187-a value
188-ab ('a' and 'b' are both shortcuts.)
189arg=value
190a=value
191arg1=value1&arg2=value2
192arg1=value1|arg2=value2
193a=value1&a=value2
194+arg/-arg (Can't support this generically.)
195
196Ignore /,-,--,& except as arg introducers. Use = as value introducer. Expect
197arg or a. If type is String, expect a value. If type is integer, and next token is
198not an integer, increment current value, otherwise expect integer value. If type is
199boolean, value beginning with T, t, F, f, etc is true, otherwise value is false, unless
200next token starts with an introducer, then value is true.
201
202TODO - Finish supporting all of the above.
203 These all need deeper parsing, but we dunno if they might have been inside quoted strings from the shell.
204 arg=value Shell.
205 arg1=value1&arg2=value2 For URLs.
206 arg1=value1|arg2=value2 Can't remember why, probably the old skang multivalue syntax.
207 Test it all.
208 Skang command line should have standardish stuff, like --version, --help, --help module.thing.
209 Lua does these already, might be no need to do them ourselves -
210 -e 'some code'.
211 -i go interactive after running the script.
212 -v version.
213 - read from stdin non interactively.
214 LuaJIT also has this -
215 -- stop processing options.
216]]
217
218ARGS = {}
219lua = ''
220command = ''
221
222
223-- Do an initial scan and tokenise of the command line arguments.
224scanArguments = function (args)
225 if args then
226 lua = args[-1]
227 command = args[0]
228 for i, v in ipairs(args) do
229 local pre = ''
230 if '--' == string.sub(v, 1, 2) then pre = '--'; v = string.sub(v, 3, -1) end
231 if '-' == string.sub(v, 1, 1) then pre = '-'; v = string.sub(v, 2, -1) end
232 if '+' == string.sub(v, 1, 1) then pre = '+'; v = string.sub(v, 2, -1) end
233 -- TODO - Make this the opposite of the directory separator for what ever platform we are running on.
234 -- Which Lua can't figure out I think.
235 if '/' == string.sub(v, 1, 1) then pre = '/'; v = string.sub(v, 2, -1) end
236 if '=' == string.sub(v, 1, 1) then pre = '='; v = string.sub(v, 2, -1) end
237 if '&' == string.sub(v, 1, 1) then pre = '&'; v = string.sub(v, 2, -1) end
238 if '|' == string.sub(v, 1, 1) then pre = '|'; v = string.sub(v, 2, -1) end
239 if '' ~= v then ARGS[i] = {pre, v} end
240 end
241 end
242end
243
244parseType = function (module, thingy, v, value)
245 if 'string' == thingy.types[1] then
246 if value then
247 module[v[2] ] = value[2]
248 value[2] = nil -- Mark it as used.
249 else
250 print('ERROR - Expected a string value for ' .. thingy.names[1])
251 end
252 end
253
254 if 'number' == thingy.types[1] then
255 if value then
256 -- If the introducer is '-', then this should be a negative number.
257 if '-' == value[1] then value[1] = ''; value[2] = '-' .. value[2] end
258 -- Only parse the next value as a number if it doesn't have an introducer.
259 if ('' == value[1]) or ('=' == value[1]) then
260 value[2] = tonumber(value[2])
261 if value[2] then
262 module[v[2] ] = value[2]
263 value[2] = nil -- Mark it as used.
264 else
265 print('ERROR - Expected a number value for ' .. thingy.names[1])
266 end
267 else
268 module[v[2] ] = module[v[2] ] + 1
269 end
270 else
271 print('ERROR - Expected a number value for ' .. thingy.names[1])
272 end
273 end
274
275 if 'function' == thingy.types[1] then
276 local args = {}
277 -- TODO - Should allow more than one argument, but would need to pass in ARGS and i.
278 if 2 == #thingy.types then
279 if value then
280 -- TODO - Should check the type of the arguments.
281 args[#args + 1] = value[2]
282 module[v[2] ](args[1])
283 value[2] = nil -- Mark it as used.
284 else
285 print('ERROR - Expected an argument for ' .. thingy.names[1])
286 end
287 else
288 module[v[2] ]()
289 end
290 end
291
292 if 'boolean' == thingy.types[1] then
293 if value then
294 -- Only parse the next value as a boolean if it doesn't have an introducer.
295 if ('' == value[1]) or ('=' == value[1]) then
296 module[v[2] ] = isBoolean(value[2])
297 value[2] = nil -- Mark it as used.
298 else
299 module[v[2] ] = true
300 end
301 else
302 print('ERROR - Expected a boolean value for ' .. thingy.names[1])
303 end
304 end
305end
306
307pullArguments = function (module)
308 -- Look for our command line arguments.
309 local metaMum = getmetatable(module)
310 if metaMum and metaMum.__self then
311 for i, v in ipairs(ARGS) do
312 if v[2] then
313 local thingy = metaMum.__self.stuff[v[2] ]
314 -- Did we find one of ours?
315 if thingy then
316 parseType(module, thingy, v, ARGS[i + 1])
317 v[2] = nil -- Mark it as used.
318 else
319 -- Didn't find one directly, check for single letter matches in '-abc'.
320 for k, w in pairs(metaMum.__self.stuff) do
321 if 1 == #w.names[1] then
322 for j = 1, #v[2] do
323 if string.sub(v[2], j, 1) == w.names[1] then
324 if 1 == j then
325 v[2] = string.sub(v[2], 2, -1)
326 if 'boolean' == w.types[1] then module[v[2] ] = true end
327 elseif #v[2] == j then
328 v[2] = string.sub(v[2], 1, j - 1)
329 -- The one at the end is the only one that could have a following value.
330 parseType(module, w, v, ARGS[i + 1])
331 else
332 v[2] = string.sub(v[2], 1, j - 1) .. string.sub(v[2], j + 1, -1)
333 if 'boolean' == w.types[1] then module[v[2] ] = true end
334 end
335 if '' == v[2] then v[2] = nil end -- Mark it as used.
336 end
337 end
338 end
339 end
340 end
341 end
342 end
343 end
344end
345
346-- Restore the environment, and grab paramateres from standard places.
347moduleEnd = function (module)
348 -- See if there is a properties file, and run it in the modules environment.
349 local properties, err = loadfile(module._NAME .. '.properties')
350 if properties then
351 setfenv(properties, getfenv(2))
352 properties()
353 elseif 'cannot open ' ~= string.sub(err, 1, 12) then
354 print("ERROR - " .. err)
355 end
356
357 pullArguments(module)
358
359 -- Run the main skin, which is the first skin that is defined. In theory, the skin from the main module.
360 if mainSkin == module then
361 print("RUNNING SKIN FOR " .. module._NAME)
362 local skin, err = loadstring(module.DEFAULT_SKANG)
363 if skin then
364 setfenv(skin, getfenv(2))
365 skin()
366 else
367 print("ERROR - " .. err)
368 end
369 end
370
371 if module.isLua then setfenv(2, module.savedEnvironment) end
372end
373
374
375-- Call this now so that from now on, this is like any other module.
376local _M = moduleBegin('skang', 'David Seikel', 'Copyright 2014 David Seikel', '0.1', '2014-03-27 02:57:00')
377
378-- This works coz LuaJIT automatically loads the jit module.
379if type(jit) == 'table' then
380 print('Skang is being run by ' .. jit.version .. ' under ' .. jit.os .. ' on a ' .. jit.arch)
381else
382 print('Skang is being run by Lua version ' .. _VERSION)
383end
384
385scanArguments(arg)
386
387
388function printTableStart(table, space, name)
389 print(space .. name .. ": ")
390 print(space .. "{")
391 printTable(table, space .. " ")
392 print(space .. "}")
393 if '' == space then print('') end
394end
395
396function printTable(table, space)
397 if nil == table then return end
398 for k, v in pairs(table) do
399 if type(v) == "table" then
400 if v._NAME then
401 print(space .. "SKANG module " .. v._NAME .. ";")
402 else
403 printTableStart(v, space, k)
404 end
405 elseif type(v) == "string" then
406 print(space .. k .. ': "' .. v .. '";')
407 elseif type(v) == "function" then
408 print(space .. "function " .. k .. "();")
409 elseif type(v) == "userdata" then
410 print(space .. "userdata " .. k .. ";")
411 elseif type(v) == "boolean" then
412 if (v) then
413 print(space .. "boolean " .. k .. " TRUE ;")
414 else
415 print(space .. "boolean " .. k .. " FALSE ;")
416 end
417 else
418 print(space .. k .. ": " .. v .. ";")
419 end
420 end
421end
422
423
424csv2table = function (csv)
425 local result = {}
426 local i = 1
427
428 for v in string.gmatch(csv, ' *([^,]+)') do
429 result[i] = v
430 i = i + 1
431 end
432 return result
433end
434
435
436shiftLeft = function (tab)
437 local result = tab[1]
438 table.remove(tab, 1)
439 return result
440end
441
442
443-- My clever boolean check, this is the third language I've written this in. B-)
444-- true 1 yes ack ok one positive absolutely affirmative 'ah ha' 'shit yeah' 'why not'
445local isTrue = 't1aopswy'
446-- false 0 no nack nope zero negative nah 'no way' 'get real' 'uh uh' 'fuck off' 'bugger off'
447local isFalse = 'f0bgnuz'
448isBoolean = function (aBoolean)
449 local result = false
450
451 if type(aBoolean) ~= 'nil' then
452 -- The default case, presence of a value means it's true.
453 result = true
454 if type(aBoolean) == 'boolean' then result = aBoolean
455 elseif type(aBoolean) == 'function' then result = aBoolean()
456 elseif type(aBoolean) == 'number' then result = (aBoolean ~= 0)
457 elseif type(aBoolean) == 'string' then
458 if '' == aBoolean then
459 result = false
460 else
461 if 1 == string.find(string.lower(aBoolean), '^[' .. isTrue .. ']') then result = true end
462 if 1 == string.find(string.lower(aBoolean), '^[' .. isFalse .. ']') then result = false end
463 end
464 end
465 end
466 return result
467end
468
469
470--[[ Thing Java package
471
472matrix-RAD had Thing as the base class of everything.
473
474Each "users session" (matrix-RAD term that came from Java
475applets/servlets) has a ThingSpace, which is a tree that holds
476everything else. It holds the class cache, commands, loaded modules,
477variables and their values, widgets and their states. In matrix-RAD I
478built BonsiaTree and LeafLike, for the old FDO system I built dumbtrees.
479
480Other Thing things are -
481 get/set The getter and setter.
482 number No idea how this was useful.
483 skang The owning object, a Skang (actually got this, called module for now).
484 owner The owning object, a String (module._NAME).
485 clas Class of the Thing, a Class. (pointless)
486 type Class of the Thing, a String. (pointless)
487 realType Real Class of the Thing, a String. (pointless)
488 myRoot ThingSpace we are in, a ThingSpace.
489
490 Also various functions to wrap checking the security, like canDo, canRead, etc.
491]]
492
493
494--[[ Stuff Java package
495
496In matrix-RAD Stuff took care of multi value Things, like database rows.
497
498Stuff is an abstract class that gets extended by other classes, like
499SquealStuff, which was the only thing extending it. It dealt with the
500basic "collection of things" stuff. Each individual thing was called a
501stufflet. A final fooStuff would extend SquealStuff, and include an
502array of strings called "stufflets" that at least named the stufflets,
503but could also include metadata and links to other Stuffs.
504
505There was various infrastructure for reading and writing Stuff, throwing
506rows of Stuff into grids, having choices of Stuff, linking stufflets to
507individual widgets, having default Stuffs for windows, validating
508Stuffs, etc.
509
510In Lua, setting up stuff has been folded into the general Thing stuff.
511
512]]
513
514
515--[[ Thing structure -
516
517In the following, meta(table) is short for getmetatable(table).
518
519In Lua everything is supposed to be a first class value, and variables are just places to store values.
520All variables are in fact stored in tables, even if it's just the local environment table.
521Any variable that has a value of nil, doesn't actually exist. That's the definition.
522While non table things can have metatables, Lua can only set metatables on tables, C has no such restriction.
523meta(table).__index and __newindex only work on table entries that don't exist.
524 __index(table, key) is called if table.key is nil.
525 Though if __index is a table, then try __index[key].
526 __newindex(table, key, value) is called if table.key is nil.
527 Though if __newindex is a table, then try __newindex[key] = value.
528Using both __index and __newindex, and keeping the actual values elsewhere, is called a proxy table.
529meta(table).__call(table, ...) is called when trying to access table as a function - table(...).
530
531It's worth repeating -
532All variables in Lua are in some table somewhere, even if it's just the global environment table.
533Metatables are only associated vith values, not variables.
534Lua can only attach metatables to values that are tables, but C can attach metatables to any value.
535
536
537A Thing is a managed variable stored in a parent proxy table, which is usually empty.
538So the values stored in this Thing are actually stored in meta(parent)__values[thing].
539 parent[thing] -> __index (parent, thing) -> meta(parent).__values[thing]
540 parent[thing] = value -> __newindex(parent, thing, value) -> meta(parent).__values[thing] = value
541
542
543Each Thing has a description table that includes -
544 names - An array of names, the first one is the "official" name.
545 types - An array of types, the first one is the "official" type.
546 help - A descriptive text for humans to read.
547 default - The default value.
548 widget - A default widget definition.
549 required - If the Thing is required.
550 isValid - A function that tests if the Thing is valid.
551 errors - Any errors related to the Thing.
552 isKeyed - Is this a parent for Things that are stored under an arbitrary key.
553 stuff - An array of descriptions for sub Things, so Things that are tables can have their own Things.
554 and other things that aren't actually used yet.
555All things that a description doesn't have should be inherited from the Thing table.
556 setmetatable(aStuff, {__index = Thing})
557Descriptions should be able to be easily shared by various Things.
558
559
560A parent's metatable has __self, which is it's own description.
561A parent is free to use it's own name space for anything it wants.
562Only the variables it specifies as managed Things are dealt with here.
563
564
565Things that are tables are treated differently, in two different ways even.
566Ordinary table Things are basically treated recursively, the table is a parent, and it gets it's own Things.
567There is also 'Keyed' type table Things, where the keys to this table are arbitrary, but we still want to store Things in it.
568In this case, when a table is assigned to this Keyed Thing, via a new key, a new table Thing is created by copying the parent Thing description.
569
570
571TODO -
572 test.foo -> test.__index(test, 'foo') -> test.__values[foo]; if that's nil, and test.stuff[foo], then return an empty table instead?
573 Do we still need a parent pointer?
574 Should be in __values I guess.
575 __values[key].value
576 __values[key].parent
577 Weak references might help in here somewhere.
578 Maybe try looking in the skang table for Things that are not found?
579 Maybe put Things in the skang table that are unique from modules?
580 I think this is what matrix-RAD Collisions was all about.
581]]
582
583-- There is no ThingSpace, or Stuff, now it's all just in this meta table.
584local Thing =
585{
586-- Default Thing values.
587 names = {'unknown'},
588 help = 'No description supplied.', -- help text describing this Thing.
589 default = '', -- The default value. This could be a funcion, making this a command.
590 types = {}, -- A list of types. The first is the type of the Thing itself, the rest are for multi value Things. Or argument types for commands.
591 required = false, -- Is this thing is required. TODO - Maybe fold this into types somehow, or acl?
592 widget = '', -- Default widget command arguments for creating this Thing as a widget.
593-- acl = '', -- Access Control List defining security restrains.
594-- boss = '', -- The Thing or person that owns this Thing, otherwise it is self owned.
595
596 action = 'nada', -- An optional action to perform.
597 tell = '', -- The skang command that created this Thing.
598 pattern = '.*', -- A pattern to restrict values.
599
600 isKeyed = false, -- Is this thing an arbitrarily Keyed table?
601 isReadOnly = false, -- Is this Thing read only?
602 isServer = false, -- Is this Thing server side?
603 isStub = false, -- Is this Thing a stub?
604 isStubbed = false, -- Is this Thing stubbed elsewhere?
605
606 hasCrashed = 0, -- How many times this Thing has crashed.
607
608 append = function (self,data) -- Append to the value of this Thing.
609 end,
610
611 stuff = {}, -- The sub things this Thing has, for modules, tables, and Keyed tables.
612 errors = {}, -- A list of errors returned by isValid().
613
614 isValid = function (self, parent) -- Check if this Thing is valid, return resulting error messages in errors.
615 -- Anything that overrides this method, should call this super method first.
616 local name = self.names[1]
617 local metaMum = getmetatable(parent)
618 local value = metaMum.__values[name]
619 local mum = metaMum.__self.names[1]
620
621 local t = type(value) or 'nil'
622 self.errors = {}
623 -- TODO - Naturally there should be formatting functions for stuffing Thing stuff into strings, and overridable output functions.
624 if 'nil' == t then
625 if self.required then table.insert(self.errors, mum .. '.' .. name .. ' is required!') end
626 else
627 if 'widget' == self.types[1] then
628 -- TODO - Should validate any attached Thing.
629 elseif self.types[1] ~= t then table.insert(self.errors, mum .. '.' .. name .. ' should be a ' .. self.types[1] .. ', but it is a ' .. t .. '!')
630 else
631 if 'number' == t then value = '' .. value end
632 if ('number' == t) or ('string' == t) then
633 if 1 ~= string.find(value, '^' .. self.pattern .. '$') then table.insert(self.errors, mum .. '.' .. name .. ' does not match pattern "' .. self.pattern .. '"!') end
634 end
635 end
636 end
637
638 for k, v in pairs(self.stuff) do
639 if not v:isValid(value) then
640 for i, w in ipairs(v.errors) do
641 table.insert(self.errors, w)
642 end
643 end
644 end
645
646 return #(self.errors) == 0
647 end,
648
649 remove = function (self) -- Delete this Thing.
650 end,
651}
652
653
654getStuffed = function (parent, key)
655 local metaMum = getmetatable(parent)
656 local thingy
657
658 if metaMum and metaMum.__self then
659 thingy = metaMum.__self.stuff[key]
660
661 if not thingy then
662 -- Deal with getting a table entry.
663 if metaMum.__values[key] then
664 thingy = getmetatable(metaMum.__values[key]).__self
665 end
666 end
667 end
668 return metaMum, thingy
669end
670
671local Mum =
672{
673
674__index = function (parent, key)
675 -- This only works for keys that don't exist. By definition a value of nil means it doesn't exist.
676
677 -- First see if this is a Thing.
678 local metaMum, thingy = getStuffed(parent, key)
679
680 if thingy then
681 return metaMum.__values[thingy.names[1] ] or thingy.default
682 end
683
684 -- Then see if we can inherit it from Thing.
685 return Thing[key]
686end,
687
688__newindex = function (parent, key, value)
689 -- This only works for keys that don't exist. By definition a value of nil means it doesn't exist.
690
691 -- First see if this is a Thing.
692 local metaMum, thingy = getStuffed(parent, key)
693
694 if not thingy then
695 if metaMum.__self.isKeyed then
696 -- Deal with setting a new Keyed table entry.
697 local newThing = copy(parent, key)
698 local newSelf = getmetatable(newThing).__self
699 rawset(metaMum.__values, key, newThing)
700 thingy = {}
701 for k, v in pairs(newSelf) do
702 thingy[k] = v
703 end
704 thingy.names={key}
705 thingy.types={'table'}
706 setmetatable(thingy, {__index = Thing}) -- To pick up isValid, pattern, and the other stuff by default.
707 end
708 end
709
710 if thingy then
711 local name = thingy.names[1]
712 local oldMum
713
714 if 'table' == type(value) then
715 -- Coz setting it via metaMum screws with the __index stuff somehow.
716 local oldValue = metaMum.__values[name]
717 if 'table' == type(oldValue) then
718 oldMum = getmetatable(oldValue)
719 if oldMum then
720 -- TODO - This SHOULD work, but doesn't -
721 --setmetatable(value, oldMum)
722 -- Instead we do this -
723 -- Clear out any values in the old table.
724 for k, v in pairs(oldMum.__values) do
725 oldMum.__values[k] = nil
726 end
727 for k, v in pairs(value) do
728 local newK = oldMum.__self.stuff[k]
729 if newK then newK = newK.names[1] else newK = k end
730 oldMum.__values[newK] = v
731 end
732 end
733 end
734 end
735 if nil == oldMum then metaMum.__values[name] = value end
736 -- NOTE - invalid values are still stored, this is by design.
737 if not thingy:isValid(parent) then
738 for i, v in ipairs(thingy.errors) do
739 print('ERROR - ' .. v)
740 end
741 end
742 -- TODO - Go through it's linked things and set them to.
743 -- Done, don't fall through to the rawset()
744 return
745 end
746
747 rawset(parent, key, value) -- Stuff it normally.
748end,
749
750__call = function (func, ...)
751 return thingasm(func, ...)
752end,
753
754}
755
756newMum = function ()
757 local result = {}
758--[[ From an email by Mike Pall -
759"Important: create the metatable and its metamethods once and reuse
760the _same_ metatable for _every_ instance."
761
762This is for LuaJIT, he's the author, and concerns performance.
763
764TODO - Which is the exact opposite of what we are doing. Perhaps we can fix that?
765]]
766 for k, v in pairs(Mum) do
767 result[k] = v
768 end
769 result.__self = {stuff={}}
770 result.__values = {}
771 return result
772end
773
774
775-- skang.thingasm() Creates a new Thing, or changes an existing one.
776--[[ It can be called in many different ways -
777
778It can be called with positional arguments - (names, help, default, types, widget, required, acl, boss)
779Or it can be called with a table - {names, help, pattern='.*', acl='rwx'}
780
781The first argument can be another Thing (the parent), or a string list of names (see below).
782
783It can be called by itself, with no parent specified -
784 thingasm('foo', 'help text)
785In this case the surrounding Lua environment becomes the parent of foo.
786 If the first argument (or first in the table) is a string, then it's this form.
787All others include the parent as the first argument, which would be a table.
788
789It can be called by calling the parent as a function -
790 foo('bar', 'some help', types='table') -- ___call(foo, 'bar', ...) And now foo is the parent.
791 foo.bar{'baz', types='Keyed'} -- thingasm({foo.bar, 'baz', ...})
792 foo.bar.baz{'field0'} -- thingasm({foo.bar.baz, 'field0'})
793 foo.bar.baz{'field1'}
794]]
795
796-- names - a comma seperated list of names, aliases, and shortcuts. The first one is the official name.
797-- If this is not a new thing, then only the first one is used to look it up.
798-- So to change names, use skang.thingasm{'oldName', names='newName,otherNewName'}
799thingasm = function (names, ...)
800 local params = {...}
801 local new = false
802 local parent
803 local set = true
804
805 -- Check how we were called, and re arrange stuff to match.
806 if 0 == #params then
807 if ('table' == type(names)) then -- thingasm{...}
808 params = names
809 names = shiftLeft(params)
810 if 'table' == type(names) then -- thingasm{parent, 'foo', ...}
811 parent = names
812 names = shiftLeft(params)
813 end
814 end -- thingasm("foo") otherwise
815 else
816 if 'table' == type(names) then
817 parent = names
818 if 'string' == type(...) then params = {...} -- C or __call(table, string, ..)
819 elseif 'table' == type(...) then params = ... -- __call(table, table)
820 end
821 names = shiftLeft(params)
822 end -- thingasm('foo', ...) otherwise
823 end
824
825 -- Break out the names.
826 names = csv2table(names)
827 local name = names[1]
828 local oldNames = {}
829
830 -- TODO - Double check this comment - No need to bitch and return if no names, this will crash for us.
831
832 -- Grab the environment of the calling function if no parent was passed in.
833 parent = parent or getfenv(2)
834 local metaMum = getmetatable(parent)
835 -- Coz at module creation time, Thing is an empty table, or in case this is for a new parent.
836 if nil == metaMum then
837 metaMum = newMum()
838 metaMum.__self.names = {parent._NAME or 'NoName'}
839 if parent._NAME then metaMum.__self.types = {'Module'} end
840 setmetatable(parent, metaMum)
841 end
842
843 local thingy = metaMum.__self.stuff[name]
844 if not thingy then -- This is a new Thing.
845 new = true
846 thingy = {}
847 thingy.names = names
848 thingy.stuff = {}
849 setmetatable(thingy, {__index = Thing}) -- To pick up isValid, pattern, and the other stuff by default.
850 end
851
852 -- Pull out positional arguments.
853 thingy.help = params[1] or thingy.help
854 thingy.default = params[2] or thingy.default
855 local types = params[3] or table.concat(thingy.types or {}, ',')
856
857 -- Pull out named arguments.
858 for k, v in pairs(params) do
859 if 'string' == type(k) then
860 if 'types' == k then types = v
861 elseif 'names' == k then
862 oldNames = thingy.names
863 thingy.names = cvs2table(v)
864 elseif 'required' == k then
865 if isBoolean(v) then thingy.required = true end
866 else thingy[k] = v
867 end
868 end
869 end
870
871 -- Find type, default to string, then break out the other types.
872 local typ = type(thingy.default)
873 if 'nil' == typ then typ = 'string' end
874 if 'function' == typ then types = typ .. ',' .. types end
875 if '' == types then types = typ end
876 thingy.types = csv2table(types)
877
878 if 'widget' == thingy.types[1] then
879 set = false
880 local args, err = loadstring('return ' .. thingy.widget)
881 if args then
882 setfenv(args, parent)
883 thingy.Cwidget = widget(args())
884print('\nNO IDEA WHY this does isValid() three times on the action, and the first one being a string.')
885 parent.W[name] = thingy
886 else
887 print("ERROR - " .. err)
888 end
889 end
890
891 -- Deal with Keyed and tables.
892 if 'Keyed' == thingy.types[1] then
893 set = false
894 thingy.types[1] = 'table'
895 thingy.isKeyed = true
896 end
897 if 'table' == thingy.types[1] then
898 -- Default in this case becomes a parent.
899 if '' == thingy.default then thingy.default = {} end
900 local thisMum = newMum()
901 thisMum.__self = thingy
902 setmetatable(thingy.default, thisMum)
903 end
904
905 if 'userdata' == thingy.types[1] then
906 set = false
907 end
908
909 -- Remove old names, then stash the Thing under all of it's new names.
910 for i, v in ipairs(oldNames) do
911 metaMum.__self.stuff[v] = nil
912 end
913 for i, v in ipairs(thingy.names) do
914 metaMum.__self.stuff[v] = thingy
915 end
916
917 -- This triggers the Mum.__newindex metamethod above. If nothing else, it triggers thingy.isValid()
918 if new and set then parent[name] = thingy.default end
919end
920
921
922fixNames = function (module, name)
923 local stuff = getmetatable(module)
924 if stuff then
925 stuff.__self.names[1] = name
926 for k, v in pairs(stuff.__self.stuff) do
927 if 'table' == v.types[1] then
928 local name = v.names[1]
929 print(name .. ' -> ' .. name)
930 fixNames(stuff.__values[name], name)
931 end
932 end
933 end
934end
935
936
937copy = function (parent, name)
938 local result = {}
939 local stuff = getmetatable(parent).__self.stuff
940
941 for k, v in pairs(stuff) do
942 local temp = {}
943 for l, w in pairs(v) do
944 temp[l] = w
945 end
946 temp[1] = table.concat(temp.names, ',')
947 temp.names = nil
948 temp.types = table.concat(temp.types, ',')
949 temp.errors = nil
950 thingasm(result, temp)
951 end
952 getmetatable(result).__self.names = {name}
953
954-- TODO - Should we copy values to?
955
956 return result
957end
958
959module = function (name)
960 _G[name] = require(name)
961 return _G[name]
962end
963
964stuff = function (aThingy, aStuff)
965 return getmetatable(aThingy).__self.stuff[aStuff]
966end
967
968
969get = function (stuff, key, name)
970 local result
971 if name then
972 local thingy = getmetatable(stuff)
973 if thingy then
974 local this = thingy.__self.stuff[key]
975 if this then result = this[name] end
976 end
977 else
978 result = stuff[key]
979 end
980 return result
981end
982
983
984reset = function (stuff, key, name)
985 if name then
986 local thingy = getmetatable(stuff)
987 if thingy then
988 local this = thingy.__self.stuff[key]
989 if this then this[name] = nil end
990 end
991 else
992 stuff[key] = nil
993 end
994end
995
996
997set = function (stuff, key, name, value)
998 if 'nil' ~= type(value) then
999 local thingy = getmetatable(stuff)
1000 if thingy then
1001 local this = thingy.__self.stuff[key]
1002 if this then this[name] = value end
1003 end
1004 else
1005 -- In this case, value isn't there, so we are reusing the third argument as the value.
1006 stuff[key] = name
1007 end
1008end
1009
1010
1011-- Get our C functions installed into skang.
1012-- This has to be after thingasm is defined.
1013package.cpath = package.cpath .. ';../../libraries/lib?.so'
1014local GuiLua = require 'GuiLua'
1015
1016
1017thingasm('module,l', 'Load a module.', module, 'file')
1018thingasm('get', 'Get the current value of an existing Thing or metadata.', get, 'thing,key,name')
1019thingasm('reset', 'Reset the current value of an existing Thing or metadata.', reset, 'thing,key,name')
1020thingasm('set', 'Set the current value of an existing Thing or metadata.', set, 'thing,key,name,data')
1021
1022thingasm('nada', 'Do nothing.', function () --[[ This function intentionally left blank. ]] end)
1023
1024
1025
1026-- Widget wrappers.
1027-- TODO - Fix this up so we don't need .W
1028local widgets = {}
1029--thingasm{widgets, 'window', 'The window.', types='userdata'}
1030thingasm{widgets, 'W', 'Holds all the widgets', types='Keyed'}
1031widgets.W{'Cwidget', 'The widget.', types='userdata'}
1032widgets.W{'action,a', 'The action for the widget.', 'nada', types='string'}
1033local aIsValid = function (self, parent)
1034 local result = Thing.isValid(self, parent)
1035
1036 if result then
1037 local value = parent[self.names[1] ]
1038print('NEW ACTION - ' .. self.names[1] .. ' = ' .. value .. ' ' .. type(parent.Cwidget))
1039 action(parent.Cwidget, value)
1040 end
1041 return result
1042end
1043
1044widgets.W{'look,l', 'The look of the widget.', types='string'}
1045--[[
1046widgets.W{'colour,color,c', 'The colours of the widget.', types='table'}
1047widgets.W.c{'red,r', 'The red colour of the widget.', 255, types='number'}
1048widgets.W.c{'green,g', 'The green colour of the widget.', 255, types='number'}
1049widgets.W.c{'blue,b', 'The blue colour of the widget.', 255, types='number'}
1050widgets.W.c{'alpha,a', 'The alpha colour of the widget.', 255, types='number'}
1051-- At this point we want to change widgets.W.c() to go to a different __call, coz right now it's going to the Mum.__call, which wraps thingasm.
1052-- TODO - Keep an eye on this if we change to a single Mum, instead of the shallow copy we use now.
1053local wMum = getmetatable(widgets.W.c)
1054wMum.__call = function (func, ...)
1055 return Colour(func, ...)
1056end
1057]]
1058
1059window = function(w, h, title, name)
1060 name = name or 'myWindow'
1061 local win = {}
1062 win = copy(widgets, name)
1063 local wMum, wThingy = getStuffed(win.W, 'a')
1064 wThingy.isValid = aIsValid
1065 win.window = Cwindow(w, h, title, name)
1066 return win
1067end
1068
1069thingasm{'window', 'Specifies the size and title of the application Frame.', window, 'number,number,string', acl="GGG"}
1070
1071
1072-- TODO - Some function stubs, for now. Fill them up later.
1073skang = function (name)
1074end
1075
1076thingasm('skang', 'Parse the contents of a skang file or URL.', skang, 'URL')
1077
1078
1079moduleEnd(_M)
1080
1081end
1082
1083-- Boss is the person that owns a Thing.
1084
1085--[[ The original Skang parameters and commands.
1086 public final static String MY_USAGE[][] =
1087 {
1088 {"skinURL", "skinURL", "Y", "s", null, "URL of skin file.", "", "RI-"},
1089 {"debug", "debug", "N", "", "0", "Set debugging level to :\n\t-1 - errors and warnings only (-q)\n\t0 - basic information\n\t1 - advanced information (-v)\n\t2 - trace functions\n\t3 - trace protocol\n\t4 - dump packets + stuff\n\t5 - detail", "", ""},
1090 {"browser", "browser", "N", "", "mozilla %f", "Browser to run.", "", ""},
1091 {"downloaddir", "downloadDir", "N", "", "download", "Download directory.", "", ""},
1092 {"sessionID", "sessionID", "N", "", null, "SessionID from servlet.", "", ""},
1093 {"JSESSIONID", "JSESSIONID", "N", "", null, "JSESSIONID from servlet engine.", "", ""},
1094 {"servletpath", "servletPath", "N", "", "matrix_rad", "Servlet path.", "", ""},
1095 {"servletport", "servletPort", "N", "", null, "Servlet port.", "", ""},
1096 {"servletsslport", "servletSSLPort", "N", "", null, "Servlet SSL port.", "", ""},
1097 {"HTML", "HTML", "N", "", "false", "Output to HTML?", "", ""},
1098 {"PHP", "PHP", "N", "", "false", "Output though the PHP wrapper", "", ""},
1099 {"inbrowser", "inBrowser", "N", "", "true", "Run in browser window?", "", ""},
1100 {"SSL", "SSL", "N", "", null, "Dummy to avoid a web server bug.", "", ""},
1101 {"NOSSL", "NOSSL", "N", "", null, "Dummy to avoid a web server bug.", "", ""},
1102 {"corporate", "corporate", "N", "", null, "Are we doing corporate shit?", "", ""},
1103 {"", "", "", "", "", "", "", ""}
1104 };
1105 public final static String MY_SKANG[][] =
1106 {
1107-- {"module", "addModule", "file,data", "Load a module.", "", ""},
1108 {"append", "appendThing", "name,data", "Append to the current value of a Thing.", "", ""},
1109 {"#!java", "bash", "name,name,name,name,name,name,name", "A not so clever unix script compatability hack.", "", ""},
1110 {"pending", "pendingDoThing", "action", "Do an action when you are ready.", "", ""},
1111 {"applet", "doIfApplet", "action", "Only do this if we are an applet.", "", ""},
1112 {"application", "doIfApplication", "action", "Only do this if we are an application.", "", ""},
1113 {"corporateshit", "doIfCorporateShit", "action", "Only do this if we are doing corporate shit.", "", ""},
1114 {"realworld", "doIfRealWorld", "action", "Only do this if we are in the real world.", "", ""},
1115 {"servlet", "doIfServlet", "action", "Only do this if we are a servlet.", "", ""},
1116 {"do", "doThing", "action", "Do this action.", "", ""},
1117 {"grab", "getFile", "URL", "Grab a file from a URL.", "", ""},
1118-- {"get", "getThing", "name", "Get the current value of an existing thing.", "", ""},
1119 {"gimmeskin", "gimmeSkin", "", "Returns the modules default skin.", "", ""},
1120 {"help", "helpThing", "file", "Show help page.", "", ""},
1121-- {"nada", "nothing", "data", "Does nothing B-).", "", ""},
1122 {"postshow", "postShowThings", "URL,name", "POST the values of all Things to the URL, show the returned content.", "", ""},
1123 {"post", "postThings", "URL", "POST the values of all Things to the URL, return the content.", "", ""},
1124 {"postparse", "postParseThings", "URL", "POST the values of all Things to the URL, parse the returned content.", "", ""},
1125 {"quiet", "quiet", "", "Output errors and warnings only.", "", ""},
1126 {"remove", "removeThing", "name", "Remove an existing thing.", "", ""},
1127 {"sethelp", "setHelp", "name,data", "Change the help for something.", "", ""},
1128-- {"set", "setThing", "name,data", "Set the current value of an existing Thing.", "", ""},
1129-- {"skang", "skangRead", "URL", "Parse the contents of a skang file or URL.", "", ""},
1130-- {"quit", "startQuit", "", "Quit, exit, remove thyself.", "", ""},
1131 {"stopwhinging", "stopWhinging", "", "Clear all messages.", "", ""},
1132 {"tell", "tellThing", "name", "Returns details of an existing Thing.", "", ""},
1133 {"togglebug", "toggleIgnoreBrowserBug", "", "Toggle ignorance of a certain browser bug.", "", ""},
1134 {"verbose", "verbose", "", "Output advanced information.", "", ""},
1135 {"", "", "", "", "", ""}
1136]]
1137
1138--[[ The original SkangAWT parameters and commands.
1139 public final static String MY_USAGE[][] =
1140 {
1141 {"", "", "", "", "", "", "", ""}
1142 };
1143 public final static String MY_SKANG[][] =
1144 {
1145 {"taction", "tactionWidget", "name,action", "Set the alternative action for a widget.", "", ""},
1146 {"action", "actionWidget", "name,action", "Set the action for a widget.", "", ""},
1147 {"pane", "addPane", "name,x,y,w,h,data", "Add a pane to the current module.", "", ""},
1148 {"widget", "addWidget", "name,type,lx,ly,lw,lh,data,data", "Add a widget to the current skin.", "", ""},
1149 {"checkboxgroup", "checkBoxGroup", "number", "Make the next 'number' Checkboxes part of a check box group.", "", ""},
1150-- {"clear", "clearWidgets", "", "The current skin is cleared of all widgets.", "", ""},
1151 {"colour", "colourWidget", "name,r,g,b,alpha,r,g,b,alpha", "Set widget's background and foreground colour.", "", "GGG"},
1152 {"doaction", "doWidget", "name", "Do a widgets action.", "", "GGG"},
1153 {"disable", "disableWidget", "name", "Disable a widget.", "", "GGG"},
1154 {"enable", "enableWidget", "name", "Enable a widget.", "", "GGG"},
1155 {"hide", "hideWidget", "name", "Hide a widget.", "", "GGG"},
1156 {"hideall", "hideAllWidgets", "name,lx,ly,lw,lh", "Hide all widgets.", "", "GGG"},
1157 {"look", "lookWidget", "name,normal,ghost,active,toggle", "Set the current look of an existing widget.", "", "GGG"},
1158 {"mask", "maskWidget", "name,data", "Set the mask for a widget.", "", ""},
1159 {"onmouse", "onMouse", "name,data", "Do something on mouse hover.", "", ""},
1160 {"offmouse", "offMouse", "name,data", "Do something off mouse hover.", "", ""},
1161 {"popup", "popupWidget", "name,data,data,data,data", "Create a popup.", "", "GGG"},
1162 {"readonly", "readOnlyWidget", "name", "Make a widget read only.", "", "GGG"},
1163 {"writeonly", "writeOnlyWidget", "name", "Make a widget write only.", "", "GGG"},
1164 {"satori", "satori", "x,y", "Give me the developers menu.", "", "GGG"},
1165 {"showloginwindow", "showLoginWindow", "", "Show user login window.", "", "GGG"},
1166 {"show", "showWidget", "name", "Show a widget.", "", "GGG"},
1167-- {"window", "setSkangFrame", "x,y,name", "Specifies the size and title of the application Frame.", "", "GGG"},
1168 {"stuff", "stuffWidget", "name,data", "Set the stuff for a widget's pane.", "", ""},
1169 {"stufflet", "stuffWidget", "name,data,data", "Set the stufflet for a widget.", "", ""},
1170 {"stufflist", "stuffListWidget", "name,data", "List the stuff in this widget.", "", ""},
1171 {"stuffload", "stuffLoadWidget", "name,data,data", "Load the stuff for a widget.", "", ""},
1172 {"stuffsave", "stuffSaveWidget", "name,data,data", "Save the stuff for a widget.", "", ""},
1173 {"stuffdelete", "stuffDeleteWidget", "name,data,data", "Delete the stuff for a widget.", "", "SSS"},
1174 {"stuffclear", "stuffClearWidget", "name,data", "Clear the stuff for a widget.", "", "SSS"},
1175 {"rowtowidgets", "rowToWidgets", "name", "Copy Grid row to matching widgets.", "", ""},
1176 {"widgetstorow", "widgetsToRow", "name,data", "Copy matching widgets to Grid row.", "", ""},
1177 {"clearrow", "clearRow", "name", "Clear Grid row and matching widgets.", "", ""},
1178 {"clearrowwidgets", "clearRowWidgets", "name", "Clear only the Grid row matching widgets.", "", ""},
1179 {"", "", "", "", "", ""}
1180 };
1181]]
1182
1183
1184--[[ security package
1185
1186Java skang could run as a stand alone applicion, as an applet in a web
1187page, or as a servlet on a web server. This was pretty much all
1188transparent to the user. The security system reflected that. Lua skang
1189wont run in web pages, but can still have client / server behaviour.
1190The general idea was, and still is, that the GUI is the client side (in
1191web page, in extantz GUI) that sends values back to the server side
1192(servlet, actual Lua package running as a separate process, or the world
1193server for in world scripts). Client side can request that server side
1194runs commands. Serevr side can send values and commands back to the
1195client. Mostly it all happenes automatically through the ACLs.
1196
1197Bouncer is the Java skang security manager, it extended the Java
1198SecurityManager. Lua has no such thing, though C code running stuff in
1199a sandbox does a similar job. Fascist is the Java security supervisor,
1200again should go into the C sandbox.
1201
1202Human is used for authenticating a human, Puter for authenticating a
1203computer, Suits for corporate style authentication, and they all
1204extended Who, the base authentication module.
1205
1206For now, I have no idea how this all translates into Lua, but putting
1207this here for a reminder to think about security during the design
1208stage.
1209
1210
1211This is the old Java ACL definition -
1212 acl - access control list.
1213Owner is usually the person running the Thingspace.
1214RWX~,---,Rwxgroup1,r--group2,r-xgroup3,rw-group4,--X~user1
1215rwx~ is for the owner. The second one is the default. The rest are per group or per user.
1216Capital letters mean that they get access from the network to.
1217--- No access at all.
1218RWX Full access.
1219R-- Read only access.
1220r-x Read and execute, but only locally.
1221rw- Read and write a field, but don't execute a method.
1222-w- A password.
1223-a- An append only log file.
1224-A- An append only log file on the server.
1225Ri- read, but only set from init (ei. skinURL not set from properties or skang files).
1226RI- As above, but applet.init() can set it too.
1227--x Thing is both method and field, only execution of the method is allowed.
1228--p Run as owner (Pretend).
1229--P Run across the network as owner (can run in applet triggered by server).
1230s-- Read only, but not even visible to applets.
1231sss Only visible to servlets and applications.
1232--S Send to servlet to execute if applet, otherwise execute normally.
1233S-- Read only, but ignore local version and get it from server.
1234ggg GUI Thing, only visible to Applets and applications.
1235GGG GUI Thing, but servlets can access them across the net.
1236
1237For servlet only modules from an applet, the applet only loads the skanglet class, using it for all
1238access to the module.
1239
1240
1241Lua Security best practices -
1242
1243 From an email by Mike Pall -
1244
1245"The only reasonably safe way to run untrusted/malicious Lua scripts is
1246to sandbox it at the process level. Everything else has far too many
1247loopholes."
1248
1249http://lua-users.org/lists/lua-l/2011-02/msg01595.html
1250http://lua-users.org/lists/lua-l/2011-02/msg01609.html
1251http://lua-users.org/lists/lua-l/2011-02/msg01097.html
1252http://lua-users.org/lists/lua-l/2011-02/msg01106.html
1253
1254So that's processes, not threads like LuaProc does. B-(
1255
1256However, security in depth is good, so still worthwhile looking at it from other levels as well.
1257
1258General solution is to create a new environment, which we are doing
1259anyway, then whitelist the safe stuff into it, instead of blacklisting
1260unsafe stuff. Coz you never know if new unsafe stuff might exist.
1261
1262Different between 5.1 (setfenv) and 5.2 (_ENV)
1263
1264http://lua-users.org/wiki/SandBoxes -
1265
1266------------------------------------------------------
1267-- make environment
1268local env = -- add functions you know are safe here
1269{
1270 ipairs = ipairs,
1271 next = next,
1272 pairs = pairs,
1273 pcall = pcall,
1274 tonumber = tonumber,
1275 tostring = tostring,
1276 type = type,
1277 unpack = unpack,
1278 coroutine = { create = coroutine.create, resume = coroutine.resume,
1279 running = coroutine.running, status = coroutine.status,
1280 wrap = coroutine.wrap },
1281 string = { byte = string.byte, char = string.char, find = string.find,
1282 format = string.format, gmatch = string.gmatch, gsub = string.gsub,
1283 len = string.len, lower = string.lower, match = string.match,
1284 rep = string.rep, reverse = string.reverse, sub = string.sub,
1285 upper = string.upper },
1286 table = { insert = table.insert, maxn = table.maxn, remove = table.remove,
1287 sort = table.sort },
1288 math = { abs = math.abs, acos = math.acos, asin = math.asin,
1289 atan = math.atan, atan2 = math.atan2, ceil = math.ceil, cos = math.cos,
1290 cosh = math.cosh, deg = math.deg, exp = math.exp, floor = math.floor,
1291 fmod = math.fmod, frexp = math.frexp, huge = math.huge,
1292 ldexp = math.ldexp, log = math.log, log10 = math.log10, max = math.max,
1293 min = math.min, modf = math.modf, pi = math.pi, pow = math.pow,
1294 rad = math.rad, random = math.random, sin = math.sin, sinh = math.sinh,
1295 sqrt = math.sqrt, tan = math.tan, tanh = math.tanh },
1296 os = { clock = os.clock, difftime = os.difftime, time = os.time },
1297}
1298
1299-- run code under environment [Lua 5.1]
1300local function run(untrusted_code)
1301 if untrusted_code:byte(1) == 27 then return nil, "binary bytecode prohibited" end
1302 local untrusted_function, message = loadstring(untrusted_code)
1303 if not untrusted_function then return nil, message end
1304 setfenv(untrusted_function, env)
1305 return pcall(untrusted_function)
1306end
1307
1308-- run code under environment [Lua 5.2]
1309local function run(untrusted_code)
1310 local untrusted_function, message = load(untrusted_code, nil, 't', env)
1311 if not untrusted_function then return nil, message end
1312 return pcall(untrusted_function)
1313end
1314------------------------------------------------------
1315
1316Also includes a table of safe / unsafe stuff.
1317
1318
1319While whitelisting stuff, could also wrap unsafe stuff to make them more safe.
1320
1321print() -> should reroute to our output widgets.
1322rawget/set() -> don't bypass the metatables, but gets tricky and recursive.
1323require -> don't allow bypassing the sandbox to get access to restricted modules
1324package.loaded -> ditto
1325
1326
1327Other things to do -
1328
1329debug.sethook() can be used to call a hook every X lines, which can help with endless loops and such.
1330Have a custom memory allocater, like edje_lua2 does.
1331
1332------------------------------------------------------
1333------------------------------------------------------
1334
1335The plan -
1336
1337 Process level -
1338 Have a Lua script runner C program / library.
1339 It does the LuaProc thing, and the edje_lua2 memory allocater thing.
1340 Other code feeds scripts to it via a pipe.
1341 Unless they are using this as a library.
1342 It can be chrooted, ulimited, LXC containered, etc.
1343 It has an internal watchdog thread.
1344 The truly paranoid can have a watchdog timer process watch it.
1345 Watches for a "new Lua state pulled off the queue" signal.
1346 This could be done from the App that spawned it.
1347
1348 It runs a bunch of worker threads, with a queue of ready Lua states.
1349 Each Lua state being run has lua_sethook() set to run each X lines, AND a watchdog timer set.
1350 If either is hit, then the Lua state is put back on the queue.
1351 (Currently LuaProc states go back an the queue when waiting for a "channel message", which does a lua_yeild().)
1352 NOTE - apparently "compiled code" bypasses hooks, though there's an undocumented LuaJIT compile switch for that. http://lua-users.org/lists/lua-l/2011-02/msg01106.html
1353 EFL is event based.
1354 LSL is event based.
1355 LuaSL is event based.
1356 Java skang is event based, with anything that has long running stuff overriding runBit().
1357 Coz Java AWT is event based, the "events" are over ridden methods in each widget class.
1358 Should all work if we keep this as event based.
1359 An "event" is a bit of Lua script in a string, at Input trust level usually.
1360 Configurable for this script runner is -
1361 IP address & port, or pipe name.
1362 Security level to run at, defaults to Network.
1363 Number of worker threads, defaults to number of CPUs.
1364 Memory limit per Lua state.
1365 Lines & time per tick for Lua states.
1366
1367 Different levels of script trust -
1368 System - the local skang and similar stuff.
1369 -> No security at all.
1370 App - Lua scripts and C from the running application.
1371 -> Current module level security.
1372 Each has it's own environment, with no cross environment leakage.
1373 Runs in the Apps process, though that might be the script runner as a library.
1374 Or could be skang's main loop.
1375 Local - Lua scripts and skang files sent from the client apps running on the same system.
1376 -> As per App.
1377 Runs in a script runner, maybe? Or just the Apps script runner.
1378 Limit OS and file stuff, the client app can do those itself.
1379 Network - Lua scripts and skang files sent from the network.
1380 -> Runs in a script runner.
1381 Option to chroot it, ulimit it, etc.
1382 Heavily Lua sandboxed via environment.
1383 It can access nails, but via network derived credentials.
1384 Can have very limited local storage, not direct file access though, but via some sort of security layer.
1385 Can have network access.
1386 Can have GUI access, but only to it's own window.
1387 Config - Lua scripts run as configuration files.
1388 -> Almost empty local environment, can really only do math and set Things.
1389 Input - Lua scripts run as a result of hitting skang widgets on the other end of a socket.
1390 -> As per Config, but can include function calls.
1391 Also would need sanitizing, as this can come from the network.
1392 Microsoft - Not to be trusted at all.
1393 Apple - Don't expect them to trust us.
1394
1395 NOTE - edje_lua2 would be roughly Local trust level.
1396
1397 So we need to be able to pass Lua between processes -
1398 Have to be able to do it from C, from Lua, and from Lua embedded in C.
1399 edje_lua2 - uses Edje messages / signals.
1400 LuaSL - uses Ecore_Con, in this case a TCP port, even though it's only local.
1401 LuaSL mainloop for it's scripts is to basically wait for these messages from LuaProc.
1402 Which yield's until we get one.
1403 Eventually gets Lua code as a string -> loadstring() -> setfenv() -> pcall()
1404 The pcall returns a string that we send back as a LuaProc message.
1405 Extantz - we want to use stdin/stdout for the pipe, but otherwise LuaSL style semantics.
1406
1407 Hmm, Extantz could run external skang modules in two ways -
1408 Run the module as a separate app, with stdin/stdout.
1409 Require the module, and run it internally.
1410 Stuff like the in world editor and radar would be better as module, since they are useless outside Extantz?
1411 Radar is useless outside Extantz, editor could be used to edit a local file.
1412 Stuff like woMan would be better off as a separate application, so it can start and stop extantz.
1413
1414]]
1415
1416
1417--[[
1418The main loop.
1419 A Lua skang module is basically a table, with skang managed fields.
1420 Once it calls moduleEnd(), it's expected to have finished.
1421 test.lua is currently an exception, it runs test stuff afterwards.
1422 So their code just registers Things and local variables.
1423 Some of those Things might be functions for manipulating internal module state.
1424 A C module has it's variables managed outside of it by Lua.
1425 So would have to go via LUA_GLOBALSINDEX to get to them.
1426 Unless they are userdata, which are C things anyway.
1427 Though it's functions are obviously inside the C module.
1428 Normal Lua module semantics expect them to return to the require call after setting themselves up.
1429 So test.lua is really an aberation.
1430
1431 Soooo, where does the main loop go?
1432 The skang module itself could define the main loop.
1433 Should not actually call it though, coz skang is itself a module.
1434
1435 In Java we had different entry points depending on how it was called.
1436 If it was called as an applet or application, it got it's own thread with a mainloop.
1437 That main loop just slept and called runBit() on all registered modules.
1438 If it was loaded as a module, it bypassed that stuff.
1439 I don't think Lua provides any such mechanism.
1440 In theory, the first call to moduleBegin would be the one that was started as an application.
1441 So we could capture that, and test against it in moduleEnd to find when THAT module finally got to the end.
1442 THEN we can start the main loop (test.lua being the exception that fucks this up).
1443 Though test.lua could include a runBits() that quits the main loop, then starts the main loop at the very end once more?
1444 The problem with this is applications that require skang type modules.
1445 The first one they require() isn't going to return.
1446 Eventually skang itself will get loaded. It looks at the "arg" global, which SHOULD be the command line.
1447 Including the name of the application, which we could use to double check.
1448 Extantz / script runner would set this arg global itself.
1449
1450 Skang applications have one main loop per app.
1451 Skang modules use the main loop of what ever app called them.
1452 Non skang apps have the option of calling skangs main loop.
1453 Extantz starts many external skang apps, which each get their own main loop.
1454 Extantz has it's own Ecore main loop.
1455 LuaSL still has one main loop per script.
1456 LSL scripts get their own main loop, but LSL is stupid and doesn't have any real "module" type stuff.
1457
1458What does skang main loop actually do?
1459 In Java it just slept for a bit, then called runBit() from each module, and the only module that had one was watch.
1460 Actually better off using Ecore timers for that sort of thing.
1461 Skang main loop has nothing to do? lol
1462 Well, except for the "wait for LuaProc channel messages" thing.
1463 Widget main loop would be Ecore's main loop.
1464
1465 Extantz loads a skang module.
1466 Module runs in extantz script runner.
1467 Module runs luaproc message main loop from skang.
1468 Evas / Elm widgets send signals, call C callbacks.
1469 Extantz sends Lua input scripts via luaproc messages to module.
1470 Extantz runs separate skang module.
1471 Module runs in it's own process.
1472 Module runs it's own message loop on the end of stdin.
1473 Evas / Elm widgets send signals, call C callbacks.
1474 Extantz sends Lua Input scripts to module via stdout.
1475 Module runs stand alone.
1476 Module runs in it's own process.
1477 Module has to have widget start Ecore's main loop.
1478 Module runs it's own message loop, waiting for widget to send it messages.
1479 Evas / Elm widgets send signals, call C callbacks.
1480 Widget sends Lua Input scripts to module.
1481
1482Alternate plan -
1483 Skang has no main loop, modules are just tables.
1484 OK, so sometimes skang has to start up an Ecore main loop.
1485 With either Ecore_Con, or Evas and Elm.
1486 skang.message(string)
1487 Call it to pass a bit of Lua to skang, which is likely Input.
1488 Widget twiddles happen in Ecore's main loop, via signals and call backs.
1489 Eventually these call backs hit the widgets action string.
1490 Send that action string to skang.message().
1491
1492 Extantz loads a skang module.
1493 Extantz has a skang Lua state.
1494 Module is just another table in skang.
1495 Widget -> callback -> action string -> skang.message()
1496 Extantz runs separate skang module.
1497 Skang module C code runs an Ecore main loop.
1498 Module is just another table in skang.
1499 Skang C uses Ecore_Con to get messages from Extantz' Ecore_Con.
1500 Widget -> callback -> action string -> Ecore_Con -> skang.message()
1501 OOORRRR ....
1502 Skang runs a main loop reading stdin.
1503 Widget -> callback -> action string -> stdout -> skang.message()
1504 Module runs stand alone.
1505 Skang module C code runs an Ecore main loop.
1506 Module is just another table in skang.
1507 Widget -> callback -> action string -> skang.message()
1508 Extantz loads a skang module from the network.
1509 Skang module runs on the server with it's own Ecore main loop somehow.
1510 Module is just another table in skang.
1511 Extantz uses Ecore_Con to get messages from Extantz' Ecore_Con, via TCP.
1512 Widget -> callback -> action string -> Ecore_Con -> skang.message()
1513 OOORRRR ....
1514 Remember, these are untrusted, so that's what the script runner is for.
1515 Skang module runs in the script runner, with some sort of luaproc interface.
1516 Widget -> callback -> action string -> Ecore_Con -> luaproc -> skang.message()
1517
1518 Skang running as a web server.
1519 Skang runs an Ecore main loop.
1520 Skang has an Ecore_Con server attached to port 80.
1521 Skang loads modules as usual.
1522 Skang responds to specific URLs with HTML forms generated from Skang files.
1523 Skang gets post data back, which kinda looks like Input. B-)
1524
1525 Extantz runs a LuaSL / LSL script from the network.
1526 Send this one to the LuaSL script runner.
1527 Coz LSL scripts tend to have lots of copies with the same name in different objects.
1528 Could get too huge to deal with via "just another table".
1529 In this case, compiling the LSL might be needed to.
1530
1531]]
1532
1533
1534--[[ TODO
1535 NOTE that skang.thingasm{} doesn't care what other names you pass in, they all get assigned to the Thing.
1536
1537
1538 Widget -
1539 Should include functions for actually dealing with widgets, plus a way
1540 of creating widgets via introspection. Should also allow access to
1541 widget internals via table access. Lua code could look like this -
1542
1543 foo = widget('label', 0, "0.1", 0.5, 0, 'Text goes here :")
1544 -- Method style.
1545 foo:colour(255, 255, 255, 0, 0, 100, 255, 0)
1546 foo:hide()
1547 foo:action("skang.load(some/skang/file.skang)")
1548 -- Table style.
1549 foo.action = "skang.load('some/skang/file.skang')"
1550 foo.colour.r = 123
1551 foo.look('some/edje/file/somewhere.edj')
1552 foo.help = 'This is a widget for labelling some foo.'
1553
1554 Widgets get a type as well, which would be label, button, edit, grid, etc.
1555 A grid could even have sub types - grid,number,string,button,date.
1556 A required widget might mean that the window HAS to have one.
1557 Default for a widget could be the default creation arguments - '"Press me", 1, 1, 10, 50'.
1558
1559 skang.thingasm{'myButton', types='Button', rectangle={1, 1, 10, 50}, title='Press me', ...}
1560
1561 skang.thingasm('foo,s,fooAlias', 'Foo is a bar, not the drinking type.', function () print('foo') end, '', '"button", "The foo :"' 1, 1, 10, 50')
1562 myButton = skang.widget('foo') -- Gets the default widget creation arguments.
1563 myButton:colour(1, 2, 3, 4)
1564 -- Use generic positional / named arguments for widget to, then we can do -
1565 myEditor = skang.widget{'foo', "edit", "Edit foo :", 5, 15, 10, 100, look='edit.edj', colour={blue=20}, action='...'}
1566 -- Using the Thing alias stuff, maybe we can do the "first stage tokenise" step after all -
1567 myEditor = skang.widget{'foo', "edit", "Edit foo :", 5, 15, 10, 100, l='edit.edj', c={b=20}, a='...'}
1568 myEditor:colour(1, 2, 3, 4, 5, 6, 7, 8)
1569 myButton = 'Not default' -- myEditor and foo change to. Though now foo is a command, not a parameter, so maybe don't change that.
1570 -- Though the 'quit' Thing could have a function that does quitting, this is just an example of NOT linking to a Thing.
1571 -- If we had linked to this theoretical 'quit' Thing, then pushing that Quit button would invoke it's Thing function.
1572 quitter = skang.widget(nil, 'button', 'Quit', 0.5, 0.5, 0.5, 0.5)
1573 quitter:action('quit')
1574
1575 For widgets with "rows", which was handled by Stuff in skang, we could
1576 maybe use the Lua concat operator via metatable. I think that works by
1577 having the widget (a table) on one side of the concat or the other, and
1578 the metatable function gets passed left and right sides, then must
1579 return the result. Needs some experimentation, but it might look like
1580 this -
1581
1582 this.bar = this.bar .. 'new choice'
1583 this.bar = 'new first choice' .. this.bar
1584
1585
1586 coordinates and sizes -
1587
1588 Originally skang differentiated between pixels and character cells,
1589 using plain integers to represent pixels, and _123 to represent
1590 character cells. The skang TODO wanted to expand that to percentages
1591 and relative numbers. We can't use _123 in Lua, so some other method
1592 needs to be used. Should include those TODO items in this new design.
1593
1594 Specifying character cells should be done as strings - "123"
1595
1596 Percentages can be done as small floating point numbers between 0 and 1,
1597 which is similar to Edje. Since Lua only has a floating point number
1598 type, both 0 and 1 should still represent pixels / character cells -
1599
1600 0.1, 0.5, "0.2", "0.9"
1601
1602 Relative numbers could be done as strings, with the widget to be
1603 relative to, a + or -, then the number. This still leaves the problem
1604 of telling if the number is pixels or character cells. Also, relative
1605 to what part of the other widget? Some more thought needs to be put
1606 into this.
1607
1608 Another idea for relative numbers could be to have a coord object with
1609 various methods, so we could have something like -
1610
1611 button:bottom(-10):right(5) -- 10 pixels below the bottom of button, 5 pixels to the right of the right edge of button.
1612 button:width("12") -- 12 characters longer than the width of button.
1613
1614 But then how do we store that so that things move when the thing they are
1615 relative to moves?
1616
1617
1618 Squeal -
1619 Squeal was the database driver interface for SquealStuff, the database
1620 version of Stuff. Maybe we could wrap esskyuehl? Not really in need of
1621 database stuff for now, but should keep it in mind.
1622 For SquealStuff, the metadata would be read from the SQL database automatically.
1623
1624 squeal.database('db', 'host', 'someDb', 'user', 'password') -> Should return a Squeal Thing.
1625 local db = require 'someDbThing' -> Same as above, only the database details are encodode in the someDbThing source, OR come from someDbThing.properties.
1626 db:getTable('stuff', 'someTable') -> Grabs the metadata, but not the rows.
1627 db:read('stuff', 'select * from someTable'} -> Fills stuff up with several rows, including setting up the metadata for the columns.
1628 stuff[1].field1 -> Is a Thing, with a proper metatable, that was created automatically from the database meta data.
1629 stuff:read('someIndex') -> Grabs a single row that has the key 'someIndex', or perhaps multiple rows since this might have SQL under it.
1630 stuff = db:read('stuff', 'select * from someTable where key='someIndex')
1631
1632 stuff:write() -> Write all rows to the database table.
1633 stuff:write(1) -> Write one row to the database table.
1634 stuff:stuff('field1').isValid = someFunction -- Should work, all stuff[key] share the same Thing description.
1635 stuff:isValid(db) -> Validate the entire Thing against it's metadata at least.
1636 window.stuff = stuff -> Window gets stuff as it's default 'Keyed' table, any widgets with same names as the table fields get linked.
1637 grid.stuff = stuff -> Replace contents of this grid widget with data from all the rows in stuff.
1638 choice.stuff = stuff -> As in grid, but only using the keys.
1639 widget.stuff = stuff:stuff('field1') -> This widget gets a particular stufflet.
1640 widget would have to look up getmetatable(window.stuff).parent. Or maybe this should work some other way?
1641
1642 In all these cases above, stuff is a 'Keyed' table that has a Thing metatable, so it has sub Things.
1643 Should include some way of specifyings details like key name, where string, etc.
1644 getmetatable(stuff).__keyName
1645 getmetatable(stuff).__squeal.where
1646 And a way to link this database table to others, via the key of the other, as a field in this Stuff.
1647 stuff:stuff('field0').__link = {parent, key, index}
1648 In Java we had this -
1649
1650public class PersonStuff extends SquealStuff
1651{
1652
1653...
1654
1655 public final static String FULLNAME = "fullname";
1656
1657 public static final String keyField = "ID"; // Name of key field/s.
1658 public static final String where = keyField + "='%k'";
1659 public static final String listName = "last";
1660 public static final String tables = "PEOPLE";
1661 public static final String select = null;
1662 public static final String stufflets[] =
1663 {
1664 keyField,
1665 "PASSWD_ID|net.matrix_rad.squeal.PasswdStuff|,OTHER",
1666 "QUALIFICATION_IDS|net.matrix_rad.people.QualificationStuff|,OTHER",
1667 "INTERESTING_IDS|net.matrix_rad.people.InterestingStuff|,OTHER",
1668 "title",
1669 "first",
1670 "middle",
1671 "last",
1672 "suffix",
1673
1674...
1675
1676 FULLNAME + "||,VARCHAR,512"
1677 };
1678}
1679
1680]]
1681
1682
1683-- Gotta check out this _ENV thing, 5.2 only. Seems to replace the need for setfenv(). Seems like setfenv should do what we want, and is more backward compatible.
1684-- "_ENV is not supported directly in 5.1, so its use can prevent a module from remaining compatible with 5.1.
1685-- Maybe you can simulate _ENV with setfenv and trapping gets/sets to it via __index/__newindex metamethods, or just avoid _ENV."
1686-- LuaJIT doesn't support _ENV anyway.