cortav  Check-in [8c11f3b669]

Overview
Comment:add path class; add URI support for HTML link output
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 8c11f3b6696754197f3fd2223687309feab9b6a41e993d38594eb4d3f9477d9d
User & Date: lexi on 2022-09-10 01:02:12
Other Links: manifest | tags
Context
2022-09-10
03:11
add xref blocks, minor refactors check-in: 76fe4885f4 user: lexi tags: trunk
01:02
add path class; add URI support for HTML link output check-in: 8c11f3b669 user: lexi tags: trunk
2022-09-09
23:22
ref now recurses into parents properly check-in: 47905a08e6 user: lexi tags: trunk
Changes

Modified cortav.lua from [00eceed9de] to [08a1a9b804].

   141    141   						return arg
   142    142   					else return sp end
   143    143   				end)
   144    144   
   145    145   			end
   146    146   
   147    147   			local function checkFromSec(sec,doc)
   148         -				if sec and not id:find'%.' then
   149         -					local rid = sec.refs[id]
   150         -					if rid then
   151         -						return rid, id, sec
          148  +				if not id:find'%.' then
          149  +					if sec then
          150  +						local rid = sec.refs[id]
          151  +						if rid then
          152  +							return rid, id, sec
          153  +						end
   152    154   					end
   153    155   
   154         -					if doc.sections[rid] then
   155         -						return nil, id, doc.sections[rid]
          156  +					if doc.sections[id] then
          157  +						return nil, id, doc.sections[id]
   156    158   					end
   157    159   				else
   158    160   					local secid, ref = string.match(id, "(.-)%.(.+)")
   159    161   					local s
   160    162   					s = s or doc.sections[secid]
   161    163   					if s then
   162    164   						if s.refs[ref] then
................................................................................
   196    198   					rid = self.invocation.origin:ref(id)
   197    199   					if rid then
   198    200   						return rid, id, self.invocation.origin.sec
   199    201   					end
   200    202   				end
   201    203   			end
   202    204   
   203         -			o,i,s = scanParents(doc)
          205  +			o,i,s = scanParents(self.doc)
   204    206   			if o or s then return o,i,s end
   205    207   
   206    208   			self:fail("ID “%s” does not name an object or section", id)
   207    209   		end
   208    210   	};
   209    211   }
   210    212   

Modified render/html.lua from [f159ef53c2] to [78b49cf252].

   611    611   		end
   612    612   
   613    613   		function span_renderers.raw(v,b,s)
   614    614   			return htmlSpan(v.spans, b, s)
   615    615   		end
   616    616   
   617    617   		function span_renderers.link(sp,b,s)
          618  +			local dest_o, _, dest_s = b.origin:ref(sp.ref)
   618    619   			local href
   619         -			if b.origin.doc.sections[sp.ref] then
   620         -				href = '#' .. getSafeID(sp)
          620  +			if dest_o == nil then
          621  +				-- link is to the section itself
          622  +				href = '#' .. getSafeID(dest_s)
   621    623   			else
   622         -				if sp.addr then href = sp.addr else
   623         -					local r = b.origin:ref(sp.ref)
   624         -					if type(r) == 'table' then
   625         -						href = '#' .. getSafeID(r)
   626         -					else href = r end
          624  +-- 				if sp.addr then href = sp.addr else
          625  +				if type(dest_o) == 'table' then
          626  +					href = '#' .. getSafeID(dest_o)
          627  +				else -- URI in reference
          628  +					local uri = ss.uri(dest_o)
          629  +					if uri.class[1] == 'file'
          630  +					or uri.class[1] == 'asset' then
          631  +						if uri.namespace == 'localhost' then
          632  +							-- emit an actual file url
          633  +							href = 'file://' .. uri:construct('path','frag')
          634  +						elseif uri.namespace == nil then
          635  +							-- this is gonna be tricky. first we establish the location
          636  +							-- of the CWD/asset base relative to the output file (if any;
          637  +							-- assume equivalent otherwise) then express the difference
          638  +							-- as a directory prefix.
          639  +							-- jk tho for now we just emit the path+frag sadlol TODO
          640  +							href = uri:construct('path','frag')
          641  +						else
          642  +							b.origin:fail('file: URI namespace must be empty or “localhost” for HTML links; others are not meaningful (offending URI: “%s”)', dest_o)
          643  +						end
          644  +					elseif uri:canfetch() == 'http' then
          645  +						local sc = 'http'
          646  +						if uri.class[1] == 'https' or uri.class[2] == 'tls' then
          647  +							sc = 'https'
          648  +						end
          649  +						if uri.namespace == nil and uri.auth == nil and uri.svc == nil then
          650  +							-- omit the scheme so we can use a relative path
          651  +							href = uri:construct('path','query','frag')
          652  +						else
          653  +							uri.class = {sc}
          654  +							href = tostring(uri)
          655  +						end
          656  +					else href = tostring(uri) end
   627    657   				end
   628    658   			end
   629    659   			return tag('a',{href=href},next(sp.spans) and htmlSpan(sp.spans,b,s) or href)
   630    660   		end
   631    661   
   632    662   		span_renderers['line-break'] = function(sp,b,s)
   633    663   			return elt('br')

Modified sirsem.lua from [dec9af930b] to [9b28e1fde4].

   761    761   			return str
   762    762   		end
   763    763   	};
   764    764   }
   765    765   
   766    766   function ss.classinstance(o)
   767    767   	local g = getmetatable(o)
   768         -	if not o then return nil end
          768  +	if not g then return nil end
   769    769   	local mm = getmetatable(g)
   770         -	if not o then return nil end
          770  +	if not mm then return nil end
   771    771   	if mm.__name == 'class' then
   772    772   		return g
   773    773   	else
   774    774   		return nil
   775    775   	end
   776    776   end
   777    777   
................................................................................
  1214   1214   	if #a ~= #b then return false end
  1215   1215   	eq = eq or function(p,q) return p == q end
  1216   1216   	for i = 1, #a do
  1217   1217   		if not eq(a[i],b[i]) then return false end
  1218   1218   	end
  1219   1219   	return true
  1220   1220   end
         1221  +
         1222  +ss.os = {}
         1223  +function ss.os.getcwd()
         1224  +	return os.getenv 'PWD' -- :((( HAX
         1225  +end
         1226  +
         1227  +ss.path = ss.declare {
         1228  +	ident = 'path';
         1229  +	mk = function() return {
         1230  +		relative = true;
         1231  +		elts = {};
         1232  +	} end;
         1233  +	construct = function(me, o, rel)
         1234  +		if type(o) == 'string' then
         1235  +			if o:sub(1,1) == '/' then
         1236  +				me.relative = false
         1237  +			end
         1238  +			me.elts = ss.str.split(ss.str.enc.ascii, o, '/')
         1239  +		elseif type(o) == 'table' then
         1240  +			me.elts = o
         1241  +			me.relative = rel
         1242  +		end
         1243  +	end;
         1244  +	clonesetup = function(me)
         1245  +		me.elts = ss.clone(me.elts)
         1246  +	end;
         1247  +	cast = {
         1248  +		string = function(me)
         1249  +			return (me.relative and '' or '/') ..
         1250  +				table.concat(me.elts, '/')
         1251  +		end;
         1252  +	};
         1253  +	op = {
         1254  +		sub = function(a,b)
         1255  +			if a.relative ~= b.relative then
         1256  +				return nil
         1257  +			end
         1258  +
         1259  +			local np = ss.path({}, true)
         1260  +			local brk = false
         1261  +			for i=1, math.max(#a.elts,#b.elts) do
         1262  +				if not brk then
         1263  +					if a.elts[i] ~= b.elts[i] then
         1264  +						brk = true
         1265  +					end
         1266  +				end
         1267  +				if brk then
         1268  +					table.insert(np.elts, b.elts[i])
         1269  +				end
         1270  +			end
         1271  +
         1272  +			return np
         1273  +		end;
         1274  +		sum = function(a,b)
         1275  +			if b.relative == false then
         1276  +				return nil
         1277  +			end
         1278  +			local n = a:clone()
         1279  +			local i = #n.elts
         1280  +			for j, v in ipairs(b.elts) do
         1281  +				n.elts[i+j] = v
         1282  +			end
         1283  +			return n
         1284  +		end;
         1285  +		lt = function(a,b)
         1286  +			-- '/a/b/c' < '/a/b/c/d'
         1287  +			-- 'q/f/g' < 'q/f/g/p/d'
         1288  +			-- '/a' !< '/b', '/a' !< 'a'
         1289  +			if a.relative ~= b.relative then
         1290  +				return false
         1291  +			end
         1292  +			if #a.elts > #b.elts then return false end
         1293  +			for i=1, #a.elts do
         1294  +				if a.elts[i] ~= b.elts[i] then
         1295  +					return false
         1296  +				end
         1297  +			end
         1298  +			return true
         1299  +		end;
         1300  +	};
         1301  +	fns = {
         1302  +		dir = function(me)
         1303  +			local n = ss.copy(me.elts)
         1304  +			n[#n] = nil
         1305  +			local p = ss.path(n, me.relative)
         1306  +		end;
         1307  +		normalize = function(me)
         1308  +			local np = ss.path({}, me.relative)
         1309  +			for i, e in ipairs(me.elts) do
         1310  +				if e == '..' then
         1311  +					if me.relative and (
         1312  +						next(np.elts) == nil or
         1313  +						np.elts[#np.elts] == '..'
         1314  +					) then
         1315  +						table.insert(np.elts, '..')
         1316  +					else
         1317  +						table.remove(np.elts)
         1318  +					end
         1319  +				elseif e ~= '.' then
         1320  +					table.insert(np.elts, e)
         1321  +				end
         1322  +			end
         1323  +			return np
         1324  +		end
         1325  +	};
         1326  +	cfns = {
         1327  +		cwd = function()
         1328  +			return ss.path(ss.os.getcwd())
         1329  +		end;
         1330  +	};
         1331  +}
  1221   1332   
  1222   1333   ss.uri = ss.declare {
  1223   1334   	ident = 'uri';
  1224   1335   	mk = function() return {
  1225   1336   		class = nil;
  1226   1337   		namespace = nil;
  1227   1338   		path = nil;
................................................................................
  1242   1353   			local s,r = rem:match '^([^:]+):(.*)$'
  1243   1354   			s_class, rem = s,r
  1244   1355   		end
  1245   1356   		if not rem then
  1246   1357   			ss.uri.exn('invalid URI “%s”', str):throw()
  1247   1358   		end
  1248   1359   		local s_ns do
  1249         -			local s,r = rem:match '^//([^/]*)(.*)$'
         1360  +			local s,r = rem:match '^//([^/?#]*)(.*)$'
  1250   1361   			if s then s_ns, rem = s,r end
  1251   1362   		end
  1252   1363   		local h_query
  1253   1364   		local s_frag
  1254   1365   		local s_path if rem ~= '' then
  1255   1366   			local s,q,r = rem:match '^([^?#]*)([?#]?)(.*)$'
  1256   1367   			if s == '' then s = nil end
................................................................................
  1303   1414   		me.namespace = dec(s_ns)
  1304   1415   		me.path = dec(s_path)
  1305   1416   		me.query = dec(s_query)
  1306   1417   		me.frag = dec(s_frag)
  1307   1418   	end;
  1308   1419   	cast = {
  1309   1420   		string = function(me)
         1421  +			return me:construct('class','namespace','path','query','frag')
         1422  +		end;
         1423  +	};
         1424  +	fns = {
         1425  +		canfetch = function(me)
         1426  +			for id, pr in pairs(fetchableProtocols) do
         1427  +				for _, p in ipairs(pr.proto) do
         1428  +					if ss.match(me.class, p) then return id end
         1429  +				end
         1430  +			end
         1431  +			return false
         1432  +		end;
         1433  +		construct = function(me, ...)
         1434  +			local parts = {}
         1435  +			local function add(n, ...)
         1436  +				if n == nil then return end
         1437  +				table.insert(parts, me:part(n))
         1438  +				add(...)
         1439  +			end
         1440  +			add(...)
         1441  +			return table.concat(parts)
         1442  +		end;
         1443  +		part = function(me, p)
  1310   1444   			local function san(str, chars)
  1311   1445   				-- TODO IRI support
  1312   1446   				chars = chars or ''
  1313   1447   				local ptn = '-a-zA-Z0-9_.,;'
  1314   1448   				ptn = ptn .. chars
  1315   1449   				return (str:gsub('[^'..ptn..']', function(c)
  1316   1450   					if c == ' ' then return '+' end
  1317   1451   					return string.format('%%%02X', string.byte(c))
  1318   1452   				end))
  1319   1453   			end
  1320         -			if me.class == nil or next(me.class) == nil then
  1321         -				return 'none:'
  1322         -			end
  1323         -			local parts = {
  1324         -				table.concat(ss.map(san,me.class), '+') .. ':';
  1325         -			}
  1326         -			if me.namespace or me.auth or me.svc then
  1327         -				table.insert(parts, '//')
  1328         -				if me.auth then
  1329         -					table.insert(parts, san(me.auth,':') .. '@')
         1454  +			if p == 'class' then
         1455  +				if me.class == nil or next(me.class) == nil then
         1456  +					return 'none:'
  1330   1457   				end
  1331         -				if me.namespace then
  1332         -					table.insert(parts, san(me.namespace))
  1333         -				end
  1334         -				if me.svc then
  1335         -					table.insert(parts, ':' .. san(me.svc))
  1336         -				end
  1337         -				if me.path and not ss.str.begins(me.path, '/') then
  1338         -					table.insert(parts, '/')
         1458  +				return table.concat(ss.map(san,me.class), '+') .. ':';
         1459  +			else
         1460  +				if me[p] == nil then return '' end
         1461  +				if p == 'namespace' then
         1462  +					local parts = {}
         1463  +					if me.namespace or me.auth or me.svc then
         1464  +						table.insert(parts, '//')
         1465  +						if me.auth then
         1466  +							table.insert(parts, san(me.auth,':') .. '@')
         1467  +						end
         1468  +						if me.namespace then
         1469  +							table.insert(parts, san(me.namespace))
         1470  +						end
         1471  +						if me.svc then
         1472  +							table.insert(parts, ':' .. san(me.svc))
         1473  +						end
         1474  +						if me.path and not ss.str.begins(me.path, '/') then
         1475  +							table.insert(parts, '/')
         1476  +						end
         1477  +					end
         1478  +					return table.concat(parts)
         1479  +				elseif p == 'path' then
         1480  +					return san(me.path,'+/=&')
         1481  +				elseif p == 'query' then
         1482  +					return '?' .. san(me.query,'?+/=&')
         1483  +				elseif p == 'frag' then
         1484  +					return '#' .. san(me.frag,'+/=&')
  1339   1485   				end
  1340   1486   			end
  1341         -			if me.path then
  1342         -				table.insert(parts, san(me.path,'+/=&'))
  1343         -			end
  1344         -			if me.query then
  1345         -				table.insert(parts, '?' .. san(me.query,'?+/=&'))
  1346         -			end
  1347         -			if me.frag then
  1348         -				table.insert(parts, '#' .. san(me.frag,'+/=&'))
  1349         -			end
  1350         -			return table.concat(parts)
  1351         -		end;
  1352         -	};
  1353         -	fns = {
  1354         -		canfetch = function(me)
  1355         -			for id, pr in pairs(fetchableProtocols) do
  1356         -				for _, p in ipairs(pr.proto) do
  1357         -					if ss.match(me.class, p) then return id end
  1358         -				end
  1359         -			end
  1360         -			return false
  1361   1487   		end;
  1362   1488   		fetch = function(me, env)
  1363   1489   			local pid = me:canfetch()
  1364   1490   			if (not pid) or fetchableProtocols[pid].fetch == nil then
  1365   1491   				ss.uri.exn("URI “%s” is unfetchable", tostring(me)):throw()
  1366   1492   			end
  1367   1493   			local proto = fetchableProtocols[pid]