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