Differences From
Artifact [eb2c53bff4]:
1 1 -- [ʞ] sirsem.lua
2 2 -- ~ lexi hale <lexi@hale.su>
3 3 -- glowpelt (hsl conversion)
4 4 -- ? utility library with functionality common to
5 5 -- cortav.lua and its extensions
6 --- from Ranuir "software utility"
6 +-- \ from Ranuir "software utility"
7 7 -- > local ss = require 'sirsem.lua'
8 8
9 9 local ss
10 10 do -- pull ourselves up by our own bootstraps
11 11 local package = _G.package
12 12 -- prevent namespace from being broken by env shenanigans
13 13 local function namespace(name, tbl)
................................................................................
473 473 bestmatch = k
474 474 bestlen = #kt
475 475 end
476 476 end
477 477 ::skip::end
478 478 return tbl[bestmatch] or tbl[true], bestmatch
479 479 end
480 +
481 +function ss.str.b64e(str)
482 + local bytes = {}
483 + local n = 1
484 + for i=1, #str, 3 do
485 + local triple = {string.byte(str, i, i+2)}
486 + local T = function(q)
487 + return triple[q] or 0
488 + end
489 + local B = function(q)
490 + print(q)
491 + if q <= 25 then
492 + return string.char(0x41 + q)
493 + elseif q <= 51 then
494 + return string.char(0x61 + (q-26))
495 + elseif q <= 61 then
496 + return string.char(0x30 + (q-52))
497 + elseif q == 62 then
498 + return '+'
499 + elseif q == 63 then
500 + return '/'
501 + else error('base64 algorithm broken') end
502 + end
503 + local quads = {
504 + ((T(1) & 0xFC) >> 2);
505 + ((T(1) & 0x03) << 4) | ((T(2) & 0xF0) >> 4);
506 + ((T(2) & 0x0F) << 2) | ((T(3) & 0xC0) >> 6);
507 + ((T(3) & 0x3F));
508 + }
509 +
510 + bytes[n + 0] = B(quads[1])
511 + bytes[n + 1] = B(quads[2])
512 + if triple[2] then
513 + bytes[n + 2] = B(quads[3])
514 + if triple[3] then
515 + bytes[n + 3] = B(quads[4])
516 + else
517 + bytes[n + 3] = '='
518 + end
519 + else
520 + bytes[n + 2] = '='
521 + bytes[n + 3] = '='
522 + end
523 +
524 + n = n + 4
525 + end
526 +
527 + return table.concat(bytes)
528 +end
529 +
530 +function ss.str.b64d(str)
531 +end
480 532
481 533 ss.math = {}
482 534
483 535 function ss.math.lerp(t, a, b)
484 536 return (1-t)*a + (t*b)
485 537 end
486 538
................................................................................
1110 1162 -- versions at least can launch programs in a sane and secure
1111 1163 -- way.
1112 1164 else
1113 1165 return s
1114 1166 end
1115 1167 end, ...))
1116 1168 end
1169 +
1170 +local fetchexn = ss.exnkind 'fetch'
1171 +local fetchableProtocols = {
1172 + http = {
1173 + proto = {
1174 + {'http'};
1175 + {'https'};
1176 + {'http', 'tls'};
1177 + };
1178 + fetch = function(uri)
1179 + fetchexn('cortav must be compiled with the C shim and libcurl support to use http fetch'):throw()
1180 + end;
1181 + };
1182 + file = {
1183 + proto = {
1184 + {'file'};
1185 + {'file', 'txt'};
1186 + {'file', 'bin'};
1187 + {'asset'};
1188 + {'asset', 'txt'};
1189 + {'asset', 'bin'};
1190 + };
1191 + fetch = function(uri, env)
1192 + local assetDir = env.asset_base or '.'
1193 + if uri.namespace then
1194 + fetchexn('authority (hostname) segment is not supported in file: URIs'):throw()
1195 + end
1196 + if uri.svc then
1197 + fetchexn('service segment is not supported in file: URIs'):throw()
1198 + end
1199 + local mode = 'r'
1200 + local path = uri.path
1201 + if uri.class[1] == 'asset' then path = assetDir ..'/'.. path end
1202 + if uri.class[2] == 'bin' then mode = 'rb' end
1203 + local fd,e = io.open(path, mode)
1204 + if not fd then
1205 + fetchexn('IO error fetching URI “%s” (%s)', tostring(uri), e):throw()
1206 + end
1207 + local data = fd:read '*a'
1208 + fd:close()
1209 + return data
1210 + end;
1211 + };
1212 +}
1213 +
1214 +function ss.match(a,b, eq)
1215 + if #a ~= #b then return false end
1216 + eq = eq or function(p,q) return p == q end
1217 + for i = 1, #a do
1218 + if not eq(a[i],b[i]) then return false end
1219 + end
1220 + return true
1221 +end
1222 +
1223 +ss.uri = ss.declare {
1224 + ident = 'uri';
1225 + mk = function() return {
1226 + class = nil;
1227 + namespace = nil;
1228 + path = nil;
1229 + query = nil;
1230 + frag = nil;
1231 + auth = nil;
1232 + } end;
1233 + construct = function(me, str)
1234 + local enc = ss.str.enc.utf8
1235 + -- URIs must be either ASCII or utf8, so we read and
1236 + -- store as UTF8. to use a URI in another encoding, it
1237 + -- must be manually converted to and fro using the
1238 + -- appropriate functions, such as encodeUCS
1239 + if not str then return end
1240 + me.raw = str
1241 + local rem = str
1242 + local s_class do
1243 + local s,r = rem:match '^([^:]+):(.*)$'
1244 + s_class, rem = s,r
1245 + end
1246 + if not rem then
1247 + ss.uri.exn('invalid URI “%s”', str):throw()
1248 + end
1249 + local s_ns do
1250 + local s,r = rem:match '^//([^/]*)(.*)$'
1251 + if s then s_ns, rem = s,r end
1252 + end
1253 + local h_query
1254 + local s_frag
1255 + local s_path if rem ~= '' then
1256 + local s,q,r = rem:match '^([^?#]*)([?#]?)(.*)$'
1257 + if s == '' then s = nil end
1258 + s_path, rem = s,r
1259 +
1260 + if q == '#' then
1261 + s_frag = rem
1262 + elseif q == '?' then
1263 + h_query = true
1264 + end
1265 + else s_path = '' end
1266 +
1267 + local s_query if h_query then
1268 + local s,q,r = rem:match '^([^#]*)(#?)(.*)$'
1269 + s_query, rem = s,r
1270 + if q~='' then s_frag = rem end
1271 + end
1272 +
1273 + local function dec(str)
1274 + if not str then return end
1275 + return str:gsub('%%([0-9A-Fa-f][0-9A-Fa-f])', function(hex)
1276 + return string.char(tonumber(hex,16))
1277 + end)
1278 + end
1279 +
1280 + local s_auth if s_ns then
1281 + local s,r = s_ns:match('^([^@]*)@(.*)$')
1282 + if s then
1283 + s_ns = r
1284 + if s ~= '' then
1285 + s_auth = s
1286 + end
1287 + end
1288 + end
1289 +
1290 + local s_svc if s_ns then
1291 + local r,s = s_ns:match('^(.*):(.-)$')
1292 + if r then
1293 + s_ns = r
1294 + if s and s ~= '' then
1295 + s_svc = s
1296 + end
1297 + end
1298 + end
1299 +
1300 + me.class = ss.str.split(enc, s_class, '+', {keep_empties=true})
1301 + for i,v in ipairs(me.class) do me.class[i] = dec(v) end
1302 + me.auth = dec(s_auth)
1303 + me.svc = dec(s_svc)
1304 + me.namespace = dec(s_ns)
1305 + me.path = dec(s_path)
1306 + me.query = dec(s_query)
1307 + me.frag = dec(s_frag)
1308 + end;
1309 + cast = {
1310 + string = function(me)
1311 + local function san(str, chars)
1312 + -- TODO IRI support
1313 + chars = chars or ''
1314 + local ptn = '-a-zA-Z0-9_.,;'
1315 + ptn = ptn .. chars
1316 + return (str:gsub('[^'..ptn..']', function(c)
1317 + if c == ' ' then return '+' end
1318 + return string.format('%%%02X', string.byte(c))
1319 + end))
1320 + end
1321 + if me.class == nil or next(me.class) == nil then
1322 + return 'none:'
1323 + end
1324 + local parts = {
1325 + table.concat(ss.map(san,me.class), '+') .. ':';
1326 + }
1327 + if me.namespace or me.auth or me.svc then
1328 + table.insert(parts, '//')
1329 + if me.auth then
1330 + table.insert(parts, san(me.auth,':') .. '@')
1331 + end
1332 + if me.namespace then
1333 + table.insert(parts, san(me.namespace))
1334 + end
1335 + if me.svc then
1336 + table.insert(parts, ':' .. san(me.svc))
1337 + end
1338 + if me.path and not ss.str.begins(me.path, '/') then
1339 + table.insert(parts, '/')
1340 + end
1341 + end
1342 + if me.path then
1343 + table.insert(parts, san(me.path,'+/=&'))
1344 + end
1345 + if me.query then
1346 + table.insert(parts, '?' .. san(me.query,'?+/=&'))
1347 + end
1348 + if me.frag then
1349 + table.insert(parts, '#' .. san(me.frag,'+/=&'))
1350 + end
1351 + return table.concat(parts)
1352 + end;
1353 + };
1354 + fns = {
1355 + canfetch = function(me)
1356 + for id, pr in pairs(fetchableProtocols) do
1357 + for _, p in ipairs(pr.proto) do
1358 + if ss.match(me.class, p) then return id end
1359 + end
1360 + end
1361 + return false
1362 + end;
1363 + fetch = function(me, env)
1364 + local pid = me:canfetch()
1365 + if (not pid) or fetchableProtocols[pid].fetch == nil then
1366 + ss.uri.exn("URI “%s” is unfetchable", tostring(me)):throw()
1367 + end
1368 + local proto = fetchableProtocols[pid]
1369 + return proto.fetch(me, env or {})
1370 + end;
1371 + };
1372 +}
1373 +ss.uri.exn = ss.exnkind 'URI'
1117 1374
1118 1375 ss.mime = ss.declare {
1119 1376 ident = 'mime-type';
1120 1377 mk = function() return {
1121 1378 class = nil;
1122 1379 kind = nil;
1123 1380 opts = {};