diff options
Diffstat (limited to 'libraries')
-rw-r--r-- | libraries/skang.lua | 1686 |
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 | |||
3 | C 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 | |||
17 | In here should live all the internals of matrix-RAD that don't | ||
18 | specifically relate to widgets. This would include the "window" though. | ||
19 | |||
20 | skang.module(Evas) | ||
21 | skang.module(Elementary) | ||
22 | skang.skang('some/skang/file.skang') | ||
23 | |||
24 | This package is also what "apps" that use the system should "inherit" | ||
25 | from, in the same way matrix-RAD apps did. Skang "apps" could be Lua | ||
26 | modules. They could also be C code, like the extantz modules are likely | ||
27 | to be. Skang "apps" would automatically be associated with skang files | ||
28 | of the same name. | ||
29 | |||
30 | For a .skang file, the skang command (written in C) would strip off the | ||
31 | first line, add the two implied lines, then run it as Lua. The | ||
32 | skang.load() command would do the same. So that skang C comand would | ||
33 | just pass the file name to skang.load() in this library. B-) | ||
34 | |||
35 | The 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". | ||
69 | do -- Only I'm not gonna indent this. | ||
70 | |||
71 | mainSkin = {} | ||
72 | |||
73 | -- TODO - This needs to be expanded a bit to cover things like 1.42 | ||
74 | local 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. | ||
88 | moduleBegin = 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 | ||
170 | end | ||
171 | |||
172 | |||
173 | --[[ Parse command line parameters. | ||
174 | |||
175 | This is done in two parts. Skang will do an initial scan and tokenise, | ||
176 | then each module gets a chance to pull it's own Things from the result. | ||
177 | |||
178 | Make the command line parameter getting MUCH more intelligent, try to support the common | ||
179 | command line interfaces - | ||
180 | |||
181 | arg value | ||
182 | a value | ||
183 | /arg value | ||
184 | /a value | ||
185 | --arg value | ||
186 | --a value | ||
187 | -a value | ||
188 | -ab ('a' and 'b' are both shortcuts.) | ||
189 | arg=value | ||
190 | a=value | ||
191 | arg1=value1&arg2=value2 | ||
192 | arg1=value1|arg2=value2 | ||
193 | a=value1&a=value2 | ||
194 | +arg/-arg (Can't support this generically.) | ||
195 | |||
196 | Ignore /,-,--,& except as arg introducers. Use = as value introducer. Expect | ||
197 | arg or a. If type is String, expect a value. If type is integer, and next token is | ||
198 | not an integer, increment current value, otherwise expect integer value. If type is | ||
199 | boolean, value beginning with T, t, F, f, etc is true, otherwise value is false, unless | ||
200 | next token starts with an introducer, then value is true. | ||
201 | |||
202 | TODO - 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 | |||
218 | ARGS = {} | ||
219 | lua = '' | ||
220 | command = '' | ||
221 | |||
222 | |||
223 | -- Do an initial scan and tokenise of the command line arguments. | ||
224 | scanArguments = 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 | ||
242 | end | ||
243 | |||
244 | parseType = 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 | ||
305 | end | ||
306 | |||
307 | pullArguments = 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 | ||
344 | end | ||
345 | |||
346 | -- Restore the environment, and grab paramateres from standard places. | ||
347 | moduleEnd = 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 | ||
372 | end | ||
373 | |||
374 | |||
375 | -- Call this now so that from now on, this is like any other module. | ||
376 | local _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. | ||
379 | if type(jit) == 'table' then | ||
380 | print('Skang is being run by ' .. jit.version .. ' under ' .. jit.os .. ' on a ' .. jit.arch) | ||
381 | else | ||
382 | print('Skang is being run by Lua version ' .. _VERSION) | ||
383 | end | ||
384 | |||
385 | scanArguments(arg) | ||
386 | |||
387 | |||
388 | function printTableStart(table, space, name) | ||
389 | print(space .. name .. ": ") | ||
390 | print(space .. "{") | ||
391 | printTable(table, space .. " ") | ||
392 | print(space .. "}") | ||
393 | if '' == space then print('') end | ||
394 | end | ||
395 | |||
396 | function 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 | ||
421 | end | ||
422 | |||
423 | |||
424 | csv2table = 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 | ||
433 | end | ||
434 | |||
435 | |||
436 | shiftLeft = function (tab) | ||
437 | local result = tab[1] | ||
438 | table.remove(tab, 1) | ||
439 | return result | ||
440 | end | ||
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' | ||
445 | local isTrue = 't1aopswy' | ||
446 | -- false 0 no nack nope zero negative nah 'no way' 'get real' 'uh uh' 'fuck off' 'bugger off' | ||
447 | local isFalse = 'f0bgnuz' | ||
448 | isBoolean = 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 | ||
467 | end | ||
468 | |||
469 | |||
470 | --[[ Thing Java package | ||
471 | |||
472 | matrix-RAD had Thing as the base class of everything. | ||
473 | |||
474 | Each "users session" (matrix-RAD term that came from Java | ||
475 | applets/servlets) has a ThingSpace, which is a tree that holds | ||
476 | everything else. It holds the class cache, commands, loaded modules, | ||
477 | variables and their values, widgets and their states. In matrix-RAD I | ||
478 | built BonsiaTree and LeafLike, for the old FDO system I built dumbtrees. | ||
479 | |||
480 | Other 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 | |||
496 | In matrix-RAD Stuff took care of multi value Things, like database rows. | ||
497 | |||
498 | Stuff is an abstract class that gets extended by other classes, like | ||
499 | SquealStuff, which was the only thing extending it. It dealt with the | ||
500 | basic "collection of things" stuff. Each individual thing was called a | ||
501 | stufflet. A final fooStuff would extend SquealStuff, and include an | ||
502 | array of strings called "stufflets" that at least named the stufflets, | ||
503 | but could also include metadata and links to other Stuffs. | ||
504 | |||
505 | There was various infrastructure for reading and writing Stuff, throwing | ||
506 | rows of Stuff into grids, having choices of Stuff, linking stufflets to | ||
507 | individual widgets, having default Stuffs for windows, validating | ||
508 | Stuffs, etc. | ||
509 | |||
510 | In Lua, setting up stuff has been folded into the general Thing stuff. | ||
511 | |||
512 | ]] | ||
513 | |||
514 | |||
515 | --[[ Thing structure - | ||
516 | |||
517 | In the following, meta(table) is short for getmetatable(table). | ||
518 | |||
519 | In Lua everything is supposed to be a first class value, and variables are just places to store values. | ||
520 | All variables are in fact stored in tables, even if it's just the local environment table. | ||
521 | Any variable that has a value of nil, doesn't actually exist. That's the definition. | ||
522 | While non table things can have metatables, Lua can only set metatables on tables, C has no such restriction. | ||
523 | meta(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. | ||
528 | Using both __index and __newindex, and keeping the actual values elsewhere, is called a proxy table. | ||
529 | meta(table).__call(table, ...) is called when trying to access table as a function - table(...). | ||
530 | |||
531 | It's worth repeating - | ||
532 | All variables in Lua are in some table somewhere, even if it's just the global environment table. | ||
533 | Metatables are only associated vith values, not variables. | ||
534 | Lua can only attach metatables to values that are tables, but C can attach metatables to any value. | ||
535 | |||
536 | |||
537 | A Thing is a managed variable stored in a parent proxy table, which is usually empty. | ||
538 | So 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 | |||
543 | Each 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. | ||
555 | All things that a description doesn't have should be inherited from the Thing table. | ||
556 | setmetatable(aStuff, {__index = Thing}) | ||
557 | Descriptions should be able to be easily shared by various Things. | ||
558 | |||
559 | |||
560 | A parent's metatable has __self, which is it's own description. | ||
561 | A parent is free to use it's own name space for anything it wants. | ||
562 | Only the variables it specifies as managed Things are dealt with here. | ||
563 | |||
564 | |||
565 | Things that are tables are treated differently, in two different ways even. | ||
566 | Ordinary table Things are basically treated recursively, the table is a parent, and it gets it's own Things. | ||
567 | There is also 'Keyed' type table Things, where the keys to this table are arbitrary, but we still want to store Things in it. | ||
568 | In 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 | |||
571 | TODO - | ||
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. | ||
584 | local 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 | |||
654 | getStuffed = 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 | ||
669 | end | ||
670 | |||
671 | local 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] | ||
686 | end, | ||
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. | ||
748 | end, | ||
749 | |||
750 | __call = function (func, ...) | ||
751 | return thingasm(func, ...) | ||
752 | end, | ||
753 | |||
754 | } | ||
755 | |||
756 | newMum = function () | ||
757 | local result = {} | ||
758 | --[[ From an email by Mike Pall - | ||
759 | "Important: create the metatable and its metamethods once and reuse | ||
760 | the _same_ metatable for _every_ instance." | ||
761 | |||
762 | This is for LuaJIT, he's the author, and concerns performance. | ||
763 | |||
764 | TODO - 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 | ||
772 | end | ||
773 | |||
774 | |||
775 | -- skang.thingasm() Creates a new Thing, or changes an existing one. | ||
776 | --[[ It can be called in many different ways - | ||
777 | |||
778 | It can be called with positional arguments - (names, help, default, types, widget, required, acl, boss) | ||
779 | Or it can be called with a table - {names, help, pattern='.*', acl='rwx'} | ||
780 | |||
781 | The first argument can be another Thing (the parent), or a string list of names (see below). | ||
782 | |||
783 | It can be called by itself, with no parent specified - | ||
784 | thingasm('foo', 'help text) | ||
785 | In 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. | ||
787 | All others include the parent as the first argument, which would be a table. | ||
788 | |||
789 | It 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'} | ||
799 | thingasm = 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()) | ||
884 | print('\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 | ||
919 | end | ||
920 | |||
921 | |||
922 | fixNames = 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 | ||
934 | end | ||
935 | |||
936 | |||
937 | copy = 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 | ||
957 | end | ||
958 | |||
959 | module = function (name) | ||
960 | _G[name] = require(name) | ||
961 | return _G[name] | ||
962 | end | ||
963 | |||
964 | stuff = function (aThingy, aStuff) | ||
965 | return getmetatable(aThingy).__self.stuff[aStuff] | ||
966 | end | ||
967 | |||
968 | |||
969 | get = 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 | ||
981 | end | ||
982 | |||
983 | |||
984 | reset = 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 | ||
994 | end | ||
995 | |||
996 | |||
997 | set = 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 | ||
1008 | end | ||
1009 | |||
1010 | |||
1011 | -- Get our C functions installed into skang. | ||
1012 | -- This has to be after thingasm is defined. | ||
1013 | package.cpath = package.cpath .. ';../../libraries/lib?.so' | ||
1014 | local GuiLua = require 'GuiLua' | ||
1015 | |||
1016 | |||
1017 | thingasm('module,l', 'Load a module.', module, 'file') | ||
1018 | thingasm('get', 'Get the current value of an existing Thing or metadata.', get, 'thing,key,name') | ||
1019 | thingasm('reset', 'Reset the current value of an existing Thing or metadata.', reset, 'thing,key,name') | ||
1020 | thingasm('set', 'Set the current value of an existing Thing or metadata.', set, 'thing,key,name,data') | ||
1021 | |||
1022 | thingasm('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 | ||
1028 | local widgets = {} | ||
1029 | --thingasm{widgets, 'window', 'The window.', types='userdata'} | ||
1030 | thingasm{widgets, 'W', 'Holds all the widgets', types='Keyed'} | ||
1031 | widgets.W{'Cwidget', 'The widget.', types='userdata'} | ||
1032 | widgets.W{'action,a', 'The action for the widget.', 'nada', types='string'} | ||
1033 | local aIsValid = function (self, parent) | ||
1034 | local result = Thing.isValid(self, parent) | ||
1035 | |||
1036 | if result then | ||
1037 | local value = parent[self.names[1] ] | ||
1038 | print('NEW ACTION - ' .. self.names[1] .. ' = ' .. value .. ' ' .. type(parent.Cwidget)) | ||
1039 | action(parent.Cwidget, value) | ||
1040 | end | ||
1041 | return result | ||
1042 | end | ||
1043 | |||
1044 | widgets.W{'look,l', 'The look of the widget.', types='string'} | ||
1045 | --[[ | ||
1046 | widgets.W{'colour,color,c', 'The colours of the widget.', types='table'} | ||
1047 | widgets.W.c{'red,r', 'The red colour of the widget.', 255, types='number'} | ||
1048 | widgets.W.c{'green,g', 'The green colour of the widget.', 255, types='number'} | ||
1049 | widgets.W.c{'blue,b', 'The blue colour of the widget.', 255, types='number'} | ||
1050 | widgets.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. | ||
1053 | local wMum = getmetatable(widgets.W.c) | ||
1054 | wMum.__call = function (func, ...) | ||
1055 | return Colour(func, ...) | ||
1056 | end | ||
1057 | ]] | ||
1058 | |||
1059 | window = 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 | ||
1067 | end | ||
1068 | |||
1069 | thingasm{'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. | ||
1073 | skang = function (name) | ||
1074 | end | ||
1075 | |||
1076 | thingasm('skang', 'Parse the contents of a skang file or URL.', skang, 'URL') | ||
1077 | |||
1078 | |||
1079 | moduleEnd(_M) | ||
1080 | |||
1081 | end | ||
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 | |||
1186 | Java skang could run as a stand alone applicion, as an applet in a web | ||
1187 | page, or as a servlet on a web server. This was pretty much all | ||
1188 | transparent to the user. The security system reflected that. Lua skang | ||
1189 | wont run in web pages, but can still have client / server behaviour. | ||
1190 | The general idea was, and still is, that the GUI is the client side (in | ||
1191 | web 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 | ||
1193 | server for in world scripts). Client side can request that server side | ||
1194 | runs commands. Serevr side can send values and commands back to the | ||
1195 | client. Mostly it all happenes automatically through the ACLs. | ||
1196 | |||
1197 | Bouncer is the Java skang security manager, it extended the Java | ||
1198 | SecurityManager. Lua has no such thing, though C code running stuff in | ||
1199 | a sandbox does a similar job. Fascist is the Java security supervisor, | ||
1200 | again should go into the C sandbox. | ||
1201 | |||
1202 | Human is used for authenticating a human, Puter for authenticating a | ||
1203 | computer, Suits for corporate style authentication, and they all | ||
1204 | extended Who, the base authentication module. | ||
1205 | |||
1206 | For now, I have no idea how this all translates into Lua, but putting | ||
1207 | this here for a reminder to think about security during the design | ||
1208 | stage. | ||
1209 | |||
1210 | |||
1211 | This is the old Java ACL definition - | ||
1212 | acl - access control list. | ||
1213 | Owner is usually the person running the Thingspace. | ||
1214 | RWX~,---,Rwxgroup1,r--group2,r-xgroup3,rw-group4,--X~user1 | ||
1215 | rwx~ is for the owner. The second one is the default. The rest are per group or per user. | ||
1216 | Capital letters mean that they get access from the network to. | ||
1217 | --- No access at all. | ||
1218 | RWX Full access. | ||
1219 | R-- Read only access. | ||
1220 | r-x Read and execute, but only locally. | ||
1221 | rw- 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. | ||
1225 | Ri- read, but only set from init (ei. skinURL not set from properties or skang files). | ||
1226 | RI- 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). | ||
1230 | s-- Read only, but not even visible to applets. | ||
1231 | sss Only visible to servlets and applications. | ||
1232 | --S Send to servlet to execute if applet, otherwise execute normally. | ||
1233 | S-- Read only, but ignore local version and get it from server. | ||
1234 | ggg GUI Thing, only visible to Applets and applications. | ||
1235 | GGG GUI Thing, but servlets can access them across the net. | ||
1236 | |||
1237 | For servlet only modules from an applet, the applet only loads the skanglet class, using it for all | ||
1238 | access to the module. | ||
1239 | |||
1240 | |||
1241 | Lua 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 | ||
1246 | to sandbox it at the process level. Everything else has far too many | ||
1247 | loopholes." | ||
1248 | |||
1249 | http://lua-users.org/lists/lua-l/2011-02/msg01595.html | ||
1250 | http://lua-users.org/lists/lua-l/2011-02/msg01609.html | ||
1251 | http://lua-users.org/lists/lua-l/2011-02/msg01097.html | ||
1252 | http://lua-users.org/lists/lua-l/2011-02/msg01106.html | ||
1253 | |||
1254 | So that's processes, not threads like LuaProc does. B-( | ||
1255 | |||
1256 | However, security in depth is good, so still worthwhile looking at it from other levels as well. | ||
1257 | |||
1258 | General solution is to create a new environment, which we are doing | ||
1259 | anyway, then whitelist the safe stuff into it, instead of blacklisting | ||
1260 | unsafe stuff. Coz you never know if new unsafe stuff might exist. | ||
1261 | |||
1262 | Different between 5.1 (setfenv) and 5.2 (_ENV) | ||
1263 | |||
1264 | http://lua-users.org/wiki/SandBoxes - | ||
1265 | |||
1266 | ------------------------------------------------------ | ||
1267 | -- make environment | ||
1268 | local 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] | ||
1300 | local 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) | ||
1306 | end | ||
1307 | |||
1308 | -- run code under environment [Lua 5.2] | ||
1309 | local 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) | ||
1313 | end | ||
1314 | ------------------------------------------------------ | ||
1315 | |||
1316 | Also includes a table of safe / unsafe stuff. | ||
1317 | |||
1318 | |||
1319 | While whitelisting stuff, could also wrap unsafe stuff to make them more safe. | ||
1320 | |||
1321 | print() -> should reroute to our output widgets. | ||
1322 | rawget/set() -> don't bypass the metatables, but gets tricky and recursive. | ||
1323 | require -> don't allow bypassing the sandbox to get access to restricted modules | ||
1324 | package.loaded -> ditto | ||
1325 | |||
1326 | |||
1327 | Other things to do - | ||
1328 | |||
1329 | debug.sethook() can be used to call a hook every X lines, which can help with endless loops and such. | ||
1330 | Have a custom memory allocater, like edje_lua2 does. | ||
1331 | |||
1332 | ------------------------------------------------------ | ||
1333 | ------------------------------------------------------ | ||
1334 | |||
1335 | The 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 | --[[ | ||
1418 | The 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 | |||
1458 | What 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 | |||
1482 | Alternate 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 | |||
1650 | public 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. | ||