#!/usr/bin/env luajit --[[ Read the README file for what this is all about. If there is no README or similar, then you can find the link to the source below. Normally I define functions and globals at the top, but here I'm interleaving them. ]] local Lunamark = require("lunamark") -- https://github.com/jgm/lunamark local Lpeg = require("lpeg") -- https://www.inf.puc-rio.br/~roberto/lpeg/lpeg.html Lunamark uses this, so we can to. local RE = require("re") -- Part of lpeg. https://www.inf.puc-rio.br/~roberto/lpeg/re.html --------------------------------------------------------------------------------- -- Some global data. local GlobalMetaData = { dlr = '$', perc = '%', devuanCinnabarDark = '#310202', devuanCinnabarLight = '#510505', devuanDarkPurpyDark = '#33313b', devuanDarkPurpyLight = '#3c3a45', devuanDeepSeaDark = '#132f40', devuanDeepSeaLight = '#1a4562', devuanSaphireDark = '#004489', devuanSaphireLight = '#00509f', devuanDevuanDark = '#000000', devuanDevuanLight = '#ffffff', -- HTML link colours. Naturally HTML5 deprecated the simple version, replacing it with less simple CSS. --
has alink, link, vlink; CSS has active, link, visited, and hover. devuanDevuanalink = '#03a4ff', devuanDevuanlink = '#0076b6', devuanDevuanvlink = '#6aa4db', devuanDevuanhlink = '#03a4ff', devuanSDevuanalink = '#98c3db', devuanSDevuanlink = '#ffffff', devuanSDevuanvlink = '#ffffff', devuanSDevuanhlink = '#98c3db', karenPurple = '#8800ff', onefangPurple = '#cc00ff', PinkFloyd = '#AA00AA', DeepPurple = '#220022', -- From an ancient site of mine, which went from PinkFloyd to DeepPurple as a background gradient. favicon = 'nYAW_icon.png', logo = 'nYAW.png', footer = 'Powered by notYetAnotherWiki version 0.0. ', } local Files, Subs, xLinks = {}, {}, {} local Template = '' local h = io.open("default.template", 'r') if nil ~= h then Template = h:read('*a') h:close() else print('oops! No such file ' .. 'default.template') end --------------------------------------------------------------------------------- -- Useful functions, part 0. -- A simple table.subtable = subtable wont work, you end up with a reference so that changes to the later get applied to the former. local derefiTable = function(t, strip) local argh = {} for l, y in ipairs(t) do if (l ~= y.name) and strip then table.insert(argh, y) end end return argh end local derefTable = function(t, strip) local argh = {} for l, y in pairs(t) do argh[l] = y end return argh end local writeString = function(base, body) local a, e = io.open(base, 'w') if nil == a then print('Could not open ' .. base .. ' - ' .. e) else a:write(body) a:close() end end -- String together the bits array into a path string. Or the other way around. lol local stringBits = function(l) local bits = {} local last = 1 for j = 1, #l do if '/' == string.sub(l, j, j) then table.insert(bits, string.sub(l, last, j - 1)) last = j + 1 end end return bits, string.sub(l, last) end -- Put a value into the Files or Subs table, creating things if needed. local toFile = function(name, key, value) if nil == Files[name] then local bits, bit = stringBits(name) local path = '' Files[name] = {} Files[name].bits = bits Files[name].bit = bit for i, d in ipairs(bits) do if '' ~= path then path = path .. '/' end path = path .. d end Files[name].path = path -- Files[name].body = '' if ("Foswiki" == bits[1]) or ("PmWiki" == bits[1]) then Files[name].ogWiki = bits[1] end end if nil ~= key then Files[name][key] = value end for i, v in ipairs{'metadata', 'bits', } do if nil == Files[name][v] then Files[name][v] = {} end end -- Open the files and do the initial cleanups. local body = '' if '.md' ~= string.sub(name, -3, -1) then h = io.open(name .. '.md', 'r') if nil ~= h then -- print('Parsing ' .. name .. '.md') body = h:read('*a') ; h:close() -- Deal with my typical double spaced sentence endings, and other things. local result = RE.compile( [=[{~ ( {[.?!]{" "}} -> '%1 ' / -- ' ' gets turned into hex 0xA0 by parse(). So feed it another metadata token that gets translated to . Seems we now skip this issue. {[\\]{['"|$]}} -> '%2 ' / -- Do the same for fixing the \' \" \| etc mess pandoc left. {"[" {([^]])+} "]{.foswiki" {([^FG}])+} "FG}" } -> "%2" / {"::: {."[A-Za-z_. ]+"}"} -> '' / {":::"} -> '' / {"-noComment-"} -> ' -- ' / {"{#"[A-Za-z_]+"}"} -> '' / . )* ~}]=], { } ):match(body) body = result -- {""} -> '' / -- {"[$]"} -> '$dlr$' / -- Replace $, coz otherwise it confuses things later. body = RE.gsub(body, '{[$]}', '$dlr$') -- {"[%]"} -> '$perc$' / -- Gotta be done after the %1's above. otherwise screws things up when included above. -- body = RE.gsub(body, '{[%]}', '$perc$') -- Coz otherwise stray % trip up the capture part. end Files[name].body = body end end local toSub = function(name, key, value) if nil == Subs[name] then local bits, bit = stringBits(name) local path = '' Subs[name] = {} table.insert(bits, bit) Subs[name].bits = bits Subs[name].bit = bit for i, d in ipairs(bits) do if '' ~= path then path = path .. '/' end path = path .. d end Subs[name].path = path end if nil ~= key then Subs[name][key] = value end for i, v in ipairs{'metadata', 'bits', 'files', 'subs'} do if nil == Subs[name][v] then Subs[name][v] = {} end end end --------------------------------------------------------------------------------- -- Actually start doing things. -- Create the base of everything.md here, so it gets picked up as usual in the file scan. h = io.open('everything.md', 'w') if nil ~= h then h:close() else print("Can't open everything.md for writing.") end -- Scan the subdirectories looking for our files. local Directory = arg[1] toSub('') if nil == Directory then Directory = '.' end for l in io.popen('find -L ' .. Directory .. ' -name "*.HTM" -type f,l -printf "%P\n"'):lines() do -- print('pandoc converting ' .. l .. ' -> ' .. string.sub(l, 1, -4) .. 'md') -- Open the HTM files and do the initial cleanups, then pandoc them. h = io.open(l, 'r') if nil ~= h then local body = h:read('*a') ; h:close() if 'Foswiki' == string.sub(l, 1, 7) then -- Strip out the actual content. local beg, en = RE.find(body, [['"' .. string.sub(l, 1, -4) .. 'md') end if '.' ~= Directory then for l in io.popen('find -L . -name "*.md" -type f,l -printf "%P\n"'):lines() do toFile(string.gsub(l, '%.md$', '')) end end -- Can add in a distant directory to, for putting it's results in the current directory. for l in io.popen('find -L ' .. Directory .. ' -name "*.md" -type f,l -printf "%P\n"'):lines() do local n = string.gsub(l, '%.md$', '') if nil == Files[n] then toFile(n) end end -- Gotta figure out all the files and subs first. File and sub metadata comes along for the ride, coz we need them later. local NewMeta = {} for name, file in pairs(Files) do local bitter, path = '', '' local bits, bit = file.bits, file.bit local ln = #bits local body, metadata = '', {} -- Go through our bits, construct Subs with bits. if ln > 0 then bitter = bits[1] end if '' ~= bitter then Subs[''].subs[bitter] = bitter end -- "bitter end" was entirely by accident, I'm keeping it. B-) for i, d in ipairs(bits) do if '' ~= path then path = path .. '/' end path = path .. d toSub(path) if i < ln then Subs[path].subs[bits[i + 1]] = bits[i + 1] table.remove(Subs[path].bits, #bits) end end if '.md' == string.sub(name, -3, -1) then -- This is a metadata only file, no content, stash the matadata. local h1 = io.open(name .. '.md') if nil == h1 then print('Could not open ' .. name .. '.md') else for l in h1:lines() do for k, v in string.gmatch(l, "(%w+)%s*=%s*(.+)") do if nil == v then print(name .. ' ' .. k) else metadata[k] = v end end end end if '.md' == name then toSub(path, 'metadata', metadata) elseif '/.md' == string.sub(name, -4, -1) then toSub(path, 'metadata', metadata) -- else toFile(string.sub(name, 1, -4), 'metadata', metadata) else NewMeta[string.sub(name, 1, -4)] = metadata -- Coz we can't add to Files here. end Files[name] = nil end end -- FIXED - Lua doesn't like modifying the thing you are pair()ing, like we want to do in the last loop. for name, file in pairs(NewMeta) do if nil == Files[name] then toFile(name) end for k, v in pairs(file) do if nil == Files[name].metadata[k] then Files[name].metadata[k] = v end end end -- Fix up subs now we have all the file bits. for name, file in pairs(Files) do if '.md' ~= string.sub(name, -3, -1) then table.insert(Subs[file.path].files, file.bit) end end --------------------------------------------------------------------------------- -- These functions assume the above file and sub scan has completed. -- Which page in this directory should we show? -- NOTE - only looking for the .md files we scanned for before, any stray HTML, html, HTM, and htm files will get ignored. local whichPage = function(f) local fl = '' if nil ~= Subs[f] then if nil ~= Subs[f].whichPage then return Subs[f].whichPage end if nil ~= Subs[f].files then if 1 == #(Subs[f].files) then fl = Subs[f].files[1] else -- Standard files to search for. for i, v in ipairs{'about', 'readme', 'index', 'homepage', 'mainpage', 'webhome'} do for j, w in ipairs(Subs[f].files) do if v == string.lower(w) then fl = w break end end if '' ~= fl then break end end -- If nothing else, just grab the first one. if ('' == fl) and (nil ~= Subs[f].files[1]) then fl = Subs[f].files[1] end end end end if '' ~= fl then fl = fl .. '.HTML' ; Subs[f].whichPage = fl end return fl end -- Figure out the original title and link for the original wiki. local whichWiki = function(metadata) local title, link = '', '' if 'PmWiki' == metadata.ogWiki then title = metadata.ogBase .. '.' .. metadata.ogFile link = metadata.ogURL .. '/?n=' .. metadata.ogBase .. '.' .. metadata.ogFile end if 'Foswiki' == metadata.ogWiki then title = metadata.ogBase .. '/' .. metadata.ogFile link = metadata.ogURL .. '/' .. metadata.ogBase .. '/' .. metadata.ogFile end return title, link end -- Calculate a link from the source directory to the destination directory. local linkFrom = function(source, dest) -- Evil hacks! if 'Profiles' == dest then dest = 'PmWiki/Profiles' end if 'Onefang' == dest then dest = 'PmWiki/Onefang' end if source == dest then return '' end local depth = 0 local lnk = '' if source ~= dest then if nil == Subs[source] then -- print('!!!! No idea where to find source ' .. source) return 'DUNNO' end if nil == Subs[dest] then if dest == Subs[source].bit then return '' else -- print('!!!! No idea where to find dest ' .. dest .. ' from ' .. Subs[source].path .. ' / ' .. Subs[source].bit) return 'DUNNO' end end local s = Subs[source].bits local d = Subs[dest].bits local sl = #s local dl = #d if 0 == dl then depth = sl else for i, v in ipairs(s) do if (nil == d[i]) or (v ~= d[i]) then depth = i break end end end -- depth is where they DON'T match. local m = depth - 1 if 0 > m then m = 0 end lnk = string.rep('../', sl - m) if 0 ~= (m + 1) then lnk = lnk .. table.concat(d, '/', m + 1, dl) end end return lnk end --------------------------------------------------------------------------------- -- More of this actually doing things nonsense. -- Create an "everything" page, for URL links to every file.HTML. local Bdy = '# All the pages\n\n| page | converted | original page | last edited UTC | \n| ---- | --------- | ------- | --------------- | ' Pages = {} for name, file in pairs(Files) do local metadata = derefTable(Files[name].metadata, true) if ('everything' ~= name) then local ln, fw, pw, ts = 'DUNNO', '', '', '' local title, link = whichWiki(metadata) link = string.gsub(link, 'https://', 'HTTPS://') -- Prevent this one from being converted. if 'PmWiki' == metadata.ogWiki then pw = 'PmWiki [' .. title .. '](' .. link .. ')' end if 'Foswiki' == metadata.ogWiki then fw = 'Foswiki [' .. title .. '](' .. link .. ')' end if nil ~= metadata.timestamp then ts = metadata.timestamp end if nil ~= file.bit then ln = file.bit end table.insert(Pages, '\n| ' .. name .. ' | [' .. ln .. '](<' .. name .. '.HTML>) | ' .. fw .. ' ' .. pw .. ' | ' .. ts .. ' |') -- Track our external links. if (nil ~= metadata.ogBase) and (nil ~= metadata.ogFile) then local n = metadata.ogBase if 'PmWiki' == metadata.ogWiki then n = n .. '.' else n = n .. '/' end xLinks[n .. metadata.ogFile] = file.path end end end table.sort(Pages, function(a, b) return (string.lower(a) < string.lower(b)) end) for i, f in ipairs(Pages) do Bdy = Bdy .. f end h = io.open('everything.md', 'a+') if nil ~= h then h:write(Bdy) h:close() else print("Can't open everything.md for writing.") end toFile('everything', 'body', Bdy) -- Loop through Subs, doing whichPage and inheritance. -- It gets to testing/even/deeper BEFORE it gets to testing/even sometimes. So sort them. SUBS = {} for name, sub in pairs(Subs) do table.insert(SUBS, sub) end table.sort(SUBS, function(a, b) return (string.lower(a.path) < string.lower(b.path)) end) for n, sub in pairs(SUBS) do local name = sub.path sub.whichPage = whichPage(name) local metadata = sub.metadata for i, s in pairs(sub.subs) do local nm = i if '' ~= name then nm = name .. '/' .. i end for k, v in pairs(metadata) do if nil == Subs[nm].metadata[k] then if ('favicon' == k) or ('logo' == k) then Subs[nm].metadata[k] = linkFrom(nm, name) .. v else if 'hidden' ~= k then -- Don't inherit hidden. Subs[nm].metadata[k] = v end end end end end end -- Files inheritance. for name, file in pairs(Files) do if '' ~= file.body then local mdata = Subs[file.path].metadata for k, v in pairs(mdata) do if nil == file.metadata[k] then Files[name].metadata[k] = v end end end end --------------------------------------------------------------------------------- -- Setup the lunarmark stuff. local LunamarkOpts = { layout='compact', -- This list is copied from the lunamark source code, until I discover a way to discover it. The descriptions are useful to. containers=false, -- Put sections in containers (e.g. div or section tags) slides=false, -- Like containers, but do not nest them startnum=true, -- Start number of an ordered list is significant smart=false, -- Smart typography (quotes, dashes, ellipses) preserve_tabs=true, -- Don't expand tabs to spaces notes=true, -- Footnotes inline_notes=true, -- Inline footnotes definition_lists=true, -- Definition lists citations=true, -- Citations citation_nbsps=true, -- Turn spacing into non-breaking spaces in citations fenced_code_blocks=true, -- Fenced code blocks lua_metadata=true, -- Lua metadata pandoc_title_blocks=true, -- Pandoc style title blocks hash_enumerators=true, -- may be used as ordered list enumerator require_blank_before_blockquote=false, require_blank_before_header=false, require_blank_before_fenced_code_block=false, fancy_lists=true, -- Pandoc style fancy lists task_list=true, -- GitHub-Flavored Markdown task list strikeout=true, -- Strike-through with double tildes mark=true, -- Highlight with double equals subscript=true, -- Subscripted text between tildes superscript=true, -- Superscripted text between circumflexes bracketed_spans=true, -- Spans with attributes fenced_divs=true, -- Divs with attributes raw_attribute=true, -- Raw pass-through on code elements fenced_code_attributes=true, -- Fenced code block attributes link_attributes=true, -- Link attributes pipe_tables=true, -- PHP Markdown Extra pipe table support table_captions=true, -- Table caption syntax extension header_attributes=true, -- Header attributes line_blocks=true, -- Line blocks escaped_line_breaks=true, -- Pandoc-style escaped hard line breaks } local Writer = Lunamark.writer.html5.new(LunamarkOpts) -- Can override the various writer functions, there's something for each of the basic HTML elements. local Context = {} -- Coz can't otherwise pass context through to the deeper Lunamark functions I'm overriding. -- Fix up the links. local OgWriterLink = Writer.link -- So we can call the original from within mine, we are just changing the URL. function Writer.link(lab, url, tit) if ('https://wiki.devuan.org/' ~= url) and ('https://fos.wiki.devuan.org/' ~= url) then local label = lab local uri = url if 'string' ~= type(lab) then label = type(lab) end for i, p in ipairs{'https://wiki.devuan.org/?n=', 'https://wiki.devuan.org?n=', 'https://fos.wiki.devuan.org/'} do if p == string.sub(url, 1, #p) then local ur = string.sub(url, #p + 1) -- TODO - could probably replace some of this mess with RE.gsub() and friends. Meh, it works. local f4, f5, tk1 = string.find(ur, '?', 1, true) if fail ~= f4 then local u = string.sub(ur, 1, f4 - 1) ur = u end if nil == Context.path then url = string.gsub(ur, '%.', '/', 1) else local xlnk = xLinks[string.gsub(ur, '%..*', '', 1)] if nil == xlnk then xlnk = string.gsub(ur, '%..*', '', 1) end if '' ~= Context.path then xlnk = linkFrom(Context.path, xlnk) end if '/' == string.sub(xlnk, 1, 1) then xlnk = string.sub(xlnk, 2) end if ('' ~= xlnk) and ('/' ~= string.sub(xlnk, -1)) then xlnk = xlnk .. '/' end if 'DUNNO/' == xlnk then print('OOPS! Page not found - ' .. Context.path .. ' / ' .. Context.bit .. ' ' .. url .. ' -> ' .. xlnk .. ' ' .. string.gsub(ur, '.*%.', '', 1) .. '.HTML') end url = xlnk .. string.gsub(ur, '.*%.', '', 1) .. '.HTML' end end end end return OgWriterLink(lab, url, tit) end local Parse = Lunamark.reader.markdown.new(Writer, LunamarkOpts) --------------------------------------------------------------------------------- -- Loop through the files we found and actually create their HTML files. for name, file in pairs(Files) do local body, metadata = Files[name].body, derefTable(Files[name].metadata, true) local bits, bit = Files[name].bits, Files[name].bit local ln = #bits local result = '' if '' ~= body then -- Figure out this pages trail links. metadata.home = linkFrom(file.path, '') .. Subs[''].whichPage metadata.trail = '' for i, b in ipairs(bits) do local p = table.concat(bits, '/', 1, i) if i < #bits then metadata.trail = metadata.trail .. '' .. b .. ' 👣 ' linkFrom(file.path, table.concat(bits, '/', 1, i)) else metadata.trail = metadata.trail .. b .. ' ' end end -- Figure out this pages header links. metadata.header = '' subs = {} for i, f in pairs(Subs[file.path].subs) do table.insert(subs, f) end table.sort(subs, function(a, b) return (string.lower(a) < string.lower(b)) end) for i, f in ipairs(subs) do local pth = file.path if '' ~= file.path then pth = file.path .. '/' end if 'true' ~= Subs[pth .. f].metadata.hidden then metadata.header = metadata.header .. '' .. f .. ' ' end end -- Figure out this pages menu links. metadata.menu = '' if nil == metadata.title then metadata.title = bit end if nil ~= Subs[file.path].files then table.sort(Subs[file.path].files, function(a, b) return (string.lower(a) < string.lower(b)) end) end for i, f in ipairs(Subs[file.path].files) do local title, url = nil, nil if '' == file.path then title = Files[f].metadata.title url = Files[f].metadata.URL else title = Files[file.path .. '/' .. f].metadata.title url = Files[file.path .. '/' .. f].metadata.URL end if nil == title then title = f end if bit == f then metadata.menu = metadata.menu .. '' .. title .. '
' else if nil ~= url then metadata.menu = metadata.menu .. '' else metadata.menu = metadata.menu .. '' end end end -- Figure out this pages footer links. metadata.footer = GlobalMetaData.footer if nil ~= metadata.pagehistory then metadata.history = 'Page history
' else metadata.history = '' end if nil ~= metadata.sourcecode then metadata.footer = 'source code ' .. metadata.footer end if nil ~= metadata.feedatom then metadata.footer = 'atom feed ' .. metadata.footer end if metadata.footer ~= GlobalMetaData.footer then metadata.footer = 'Web site ' .. metadata.footer end -- Add a link to the original page. if nil ~= metadata.ogURL then local title, link = whichWiki(metadata) link = string.gsub(link, 'https://', 'HTTPS://') -- Prevent this one from being converted. metadata.footer = 'Original page, maybe you can edit it. ' .. metadata.footer end metadata.footer = '' .. metadata.footer .. '
' -- Do our own metadata replacement, it's simple and works better. local temp = Template -- Toss the body in first, so the scan can deal with it to. -- NOTE - this is where we actually parse the markup into HTML. Context = file local bd, md = Parse(body) -- The md is a table of extracted metadata, not likely to be any, and we wont do anything with it. bd = RE.gsub(bd, '{[%]}', '$perc$') -- Coz otherwise stray % trip up the capture part. temp = RE.gsub(temp, '"$body$"', bd) -- The actual metadata replacement. result = RE.compile ('{~ ({[$][A-Za-z_]+[$]} -> meta / .)* ~}', { meta = function(a) a = string.sub(a, 2, -2) local md = metadata[a] if nil == md then md = GlobalMetaData[a] if nil == md then md = a end end return md end } ):match(temp) -- Write the file. if '' ~= result then -- print('From ' .. name .. '.md -> ' .. base) writeString(name .. '.HTML', result) end end end