Index: cortav.lua ================================================================== --- cortav.lua +++ cortav.lua @@ -143,18 +143,20 @@ end) end local function checkFromSec(sec,doc) - if sec and not id:find'%.' then - local rid = sec.refs[id] - if rid then - return rid, id, sec + if not id:find'%.' then + if sec then + local rid = sec.refs[id] + if rid then + return rid, id, sec + end end - if doc.sections[rid] then - return nil, id, doc.sections[rid] + if doc.sections[id] then + return nil, id, doc.sections[id] end else local secid, ref = string.match(id, "(.-)%.(.+)") local s s = s or doc.sections[secid] @@ -198,11 +200,11 @@ return rid, id, self.invocation.origin.sec end end end - o,i,s = scanParents(doc) + o,i,s = scanParents(self.doc) if o or s then return o,i,s end self:fail("ID “%s” does not name an object or section", id) end }; Index: render/html.lua ================================================================== --- render/html.lua +++ render/html.lua @@ -613,19 +613,49 @@ function span_renderers.raw(v,b,s) return htmlSpan(v.spans, b, s) end function span_renderers.link(sp,b,s) + local dest_o, _, dest_s = b.origin:ref(sp.ref) local href - if b.origin.doc.sections[sp.ref] then - href = '#' .. getSafeID(sp) + if dest_o == nil then + -- link is to the section itself + href = '#' .. getSafeID(dest_s) else - if sp.addr then href = sp.addr else - local r = b.origin:ref(sp.ref) - if type(r) == 'table' then - href = '#' .. getSafeID(r) - else href = r end +-- if sp.addr then href = sp.addr else + if type(dest_o) == 'table' then + href = '#' .. getSafeID(dest_o) + else -- URI in reference + local uri = ss.uri(dest_o) + if uri.class[1] == 'file' + or uri.class[1] == 'asset' then + if uri.namespace == 'localhost' then + -- emit an actual file url + href = 'file://' .. uri:construct('path','frag') + elseif uri.namespace == nil then + -- this is gonna be tricky. first we establish the location + -- of the CWD/asset base relative to the output file (if any; + -- assume equivalent otherwise) then express the difference + -- as a directory prefix. + -- jk tho for now we just emit the path+frag sadlol TODO + href = uri:construct('path','frag') + else + b.origin:fail('file: URI namespace must be empty or “localhost” for HTML links; others are not meaningful (offending URI: “%s”)', dest_o) + end + elseif uri:canfetch() == 'http' then + local sc = 'http' + if uri.class[1] == 'https' or uri.class[2] == 'tls' then + sc = 'https' + end + if uri.namespace == nil and uri.auth == nil and uri.svc == nil then + -- omit the scheme so we can use a relative path + href = uri:construct('path','query','frag') + else + uri.class = {sc} + href = tostring(uri) + end + else href = tostring(uri) end end end return tag('a',{href=href},next(sp.spans) and htmlSpan(sp.spans,b,s) or href) end Index: sirsem.lua ================================================================== --- sirsem.lua +++ sirsem.lua @@ -763,13 +763,13 @@ }; } function ss.classinstance(o) local g = getmetatable(o) - if not o then return nil end + if not g then return nil end local mm = getmetatable(g) - if not o then return nil end + if not mm then return nil end if mm.__name == 'class' then return g else return nil end @@ -1216,10 +1216,121 @@ for i = 1, #a do if not eq(a[i],b[i]) then return false end end return true end + +ss.os = {} +function ss.os.getcwd() + return os.getenv 'PWD' -- :((( HAX +end + +ss.path = ss.declare { + ident = 'path'; + mk = function() return { + relative = true; + elts = {}; + } end; + construct = function(me, o, rel) + if type(o) == 'string' then + if o:sub(1,1) == '/' then + me.relative = false + end + me.elts = ss.str.split(ss.str.enc.ascii, o, '/') + elseif type(o) == 'table' then + me.elts = o + me.relative = rel + end + end; + clonesetup = function(me) + me.elts = ss.clone(me.elts) + end; + cast = { + string = function(me) + return (me.relative and '' or '/') .. + table.concat(me.elts, '/') + end; + }; + op = { + sub = function(a,b) + if a.relative ~= b.relative then + return nil + end + + local np = ss.path({}, true) + local brk = false + for i=1, math.max(#a.elts,#b.elts) do + if not brk then + if a.elts[i] ~= b.elts[i] then + brk = true + end + end + if brk then + table.insert(np.elts, b.elts[i]) + end + end + + return np + end; + sum = function(a,b) + if b.relative == false then + return nil + end + local n = a:clone() + local i = #n.elts + for j, v in ipairs(b.elts) do + n.elts[i+j] = v + end + return n + end; + lt = function(a,b) + -- '/a/b/c' < '/a/b/c/d' + -- 'q/f/g' < 'q/f/g/p/d' + -- '/a' !< '/b', '/a' !< 'a' + if a.relative ~= b.relative then + return false + end + if #a.elts > #b.elts then return false end + for i=1, #a.elts do + if a.elts[i] ~= b.elts[i] then + return false + end + end + return true + end; + }; + fns = { + dir = function(me) + local n = ss.copy(me.elts) + n[#n] = nil + local p = ss.path(n, me.relative) + end; + normalize = function(me) + local np = ss.path({}, me.relative) + for i, e in ipairs(me.elts) do + if e == '..' then + if me.relative and ( + next(np.elts) == nil or + np.elts[#np.elts] == '..' + ) then + table.insert(np.elts, '..') + else + table.remove(np.elts) + end + elseif e ~= '.' then + table.insert(np.elts, e) + end + end + return np + end + }; + cfns = { + cwd = function() + return ss.path(ss.os.getcwd()) + end; + }; +} ss.uri = ss.declare { ident = 'uri'; mk = function() return { class = nil; @@ -1244,11 +1355,11 @@ end if not rem then ss.uri.exn('invalid URI “%s”', str):throw() end local s_ns do - local s,r = rem:match '^//([^/]*)(.*)$' + local s,r = rem:match '^//([^/?#]*)(.*)$' if s then s_ns, rem = s,r end end local h_query local s_frag local s_path if rem ~= '' then @@ -1305,10 +1416,33 @@ me.query = dec(s_query) me.frag = dec(s_frag) end; cast = { string = function(me) + return me:construct('class','namespace','path','query','frag') + end; + }; + fns = { + canfetch = function(me) + for id, pr in pairs(fetchableProtocols) do + for _, p in ipairs(pr.proto) do + if ss.match(me.class, p) then return id end + end + end + return false + end; + construct = function(me, ...) + local parts = {} + local function add(n, ...) + if n == nil then return end + table.insert(parts, me:part(n)) + add(...) + end + add(...) + return table.concat(parts) + end; + part = function(me, p) local function san(str, chars) -- TODO IRI support chars = chars or '' local ptn = '-a-zA-Z0-9_.,;' ptn = ptn .. chars @@ -1315,51 +1449,43 @@ return (str:gsub('[^'..ptn..']', function(c) if c == ' ' then return '+' end return string.format('%%%02X', string.byte(c)) end)) end - if me.class == nil or next(me.class) == nil then - return 'none:' - end - local parts = { - table.concat(ss.map(san,me.class), '+') .. ':'; - } - if me.namespace or me.auth or me.svc then - table.insert(parts, '//') - if me.auth then - table.insert(parts, san(me.auth,':') .. '@') + if p == 'class' then + if me.class == nil or next(me.class) == nil then + return 'none:' end - if me.namespace then - table.insert(parts, san(me.namespace)) - end - if me.svc then - table.insert(parts, ':' .. san(me.svc)) - end - if me.path and not ss.str.begins(me.path, '/') then - table.insert(parts, '/') + return table.concat(ss.map(san,me.class), '+') .. ':'; + else + if me[p] == nil then return '' end + if p == 'namespace' then + local parts = {} + if me.namespace or me.auth or me.svc then + table.insert(parts, '//') + if me.auth then + table.insert(parts, san(me.auth,':') .. '@') + end + if me.namespace then + table.insert(parts, san(me.namespace)) + end + if me.svc then + table.insert(parts, ':' .. san(me.svc)) + end + if me.path and not ss.str.begins(me.path, '/') then + table.insert(parts, '/') + end + end + return table.concat(parts) + elseif p == 'path' then + return san(me.path,'+/=&') + elseif p == 'query' then + return '?' .. san(me.query,'?+/=&') + elseif p == 'frag' then + return '#' .. san(me.frag,'+/=&') end end - if me.path then - table.insert(parts, san(me.path,'+/=&')) - end - if me.query then - table.insert(parts, '?' .. san(me.query,'?+/=&')) - end - if me.frag then - table.insert(parts, '#' .. san(me.frag,'+/=&')) - end - return table.concat(parts) - end; - }; - fns = { - canfetch = function(me) - for id, pr in pairs(fetchableProtocols) do - for _, p in ipairs(pr.proto) do - if ss.match(me.class, p) then return id end - end - end - return false end; fetch = function(me, env) local pid = me:canfetch() if (not pid) or fetchableProtocols[pid].fetch == nil then ss.uri.exn("URI “%s” is unfetchable", tostring(me)):throw()