cortav  Diff

Differences From Artifact [eb2c53bff4]:

To Artifact [aed49421db]:


     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 = {};