You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
560 lines
16 KiB
560 lines
16 KiB
meta = { |
|
domain = "compudanzas.net", |
|
title = "compudanzas", |
|
description = "explorations of computing at a human scale", |
|
fecha = os.date("%Y-%m-%d") |
|
} |
|
|
|
webdir = "web" -- web output directory |
|
gemdir = "gem" -- gemini output directory |
|
templates = { |
|
webEn = { |
|
header = { path = "pageheader.html", template = ""}, |
|
footer = { path = "pagefooter.html", template = ""}, |
|
incoming = { path = "pageincoming.html", template = ""} |
|
}, |
|
webEs = { |
|
header = { path = "pageheader.html", template = ""}, |
|
footer = { path = "pagefooter_es.html", template = ""}, |
|
incoming = { path = "pageincoming_es.html", template = ""} |
|
}, |
|
gemEn = { |
|
header = { path = "pageheader.gmi", template = ""}, |
|
footer = { path = "pagefooter.gmi", template = ""}, |
|
incoming = { path = "pageincoming.gmi", template = ""} |
|
}, |
|
gemEs = { |
|
header = { path = "pageheader.gmi", template = ""}, |
|
footer = { path = "pagefooter_es.gmi", template = ""}, |
|
incoming = { path = "pageincoming_es.gmi", template = ""} |
|
}, |
|
} |
|
|
|
pages = {} |
|
|
|
function slugify( s ) |
|
return (string.gsub( s, "%s", "_" )) |
|
end |
|
|
|
function spacify( s ) -- opposite of slugify (?) |
|
return (string.gsub( s, "_", " ")) |
|
end |
|
|
|
function fillTemplate( template, pagemeta ) |
|
return ( string.gsub( template, "({.-})", function (key) |
|
local var = string.sub(key,2,-2) -- remove { } |
|
return meta[var] or pagemeta[var] |
|
end) ) |
|
end |
|
|
|
function getHeader( line ) -- returns title, level |
|
local head, title = string.match(line,"^(#+)%s*(.-)$") |
|
return title, #head |
|
end |
|
|
|
function getLink( line ) -- returns dest, text |
|
return string.match( line, "^=>%s*(%S-)%s+(.-)$") |
|
end |
|
|
|
function isImagePath( text ) |
|
return text:match(".jpg$") or text:match(".gif$") or text:match(".png$") |
|
end |
|
|
|
function sanitize( line ) -- replace relevant html entities |
|
return line:gsub("<","<"):gsub(">",">") |
|
end |
|
|
|
function closeTags() |
|
local close = "" |
|
if flags.list then |
|
close = "</ul>\n" |
|
flags.list = false |
|
elseif flags.p then |
|
close = "</p>\n" |
|
flags.p = false |
|
elseif flags.gallery then |
|
close = "</gallery>\n" |
|
flags.gallery = false |
|
end |
|
|
|
return close |
|
end |
|
|
|
function insertIncoming( pagename, incomingname ) |
|
if incomingname == "pages" then return false end |
|
|
|
local incoming = pages[pagename].incoming |
|
for i = 1, #incoming do |
|
if incoming[i] == incomingname then |
|
return false |
|
end |
|
end |
|
|
|
table.insert( incoming, incomingname) |
|
return true |
|
end |
|
|
|
function firstPass() |
|
-- load templates |
|
for _,t in pairs(templates) do |
|
-- read templates |
|
for k,subtemp in pairs( t ) do |
|
local f = assert(io.open(string.format("templates/%s",subtemp.path),"r")) |
|
subtemp.template = f:read("a") |
|
f:close() |
|
end |
|
end |
|
|
|
-- for each page: |
|
-- convert gmo to gmi and html |
|
-- and calculate incoming links |
|
for name,page in pairs(pages) do |
|
local pagemeta = pages[name] |
|
|
|
local gmopath = string.format("src/%s.gmo", pagemeta.slug) |
|
|
|
-- open file |
|
local f = assert( io.open(gmopath, "r") ) |
|
|
|
-- initialize flags |
|
flags = { list = false, pre = false , p = false, gallery = false } |
|
|
|
-- table to store the lines to write to file |
|
local lines = { web = {}, gem = {} } |
|
|
|
local navlines = { "<ul>" } |
|
|
|
-- convert one line at a time |
|
local count = 1 |
|
for line in f:lines() do |
|
-- the output line: |
|
local out = { gem = line, web = nil} |
|
|
|
if count == 1 then -- extract title |
|
pagemeta.ptitle = string.match(line, "^#+%s*(.-)$") |
|
elseif count == 2 then -- language |
|
if line == "" then |
|
pagemeta.lang = "en,es-MX" -- default |
|
else |
|
local langformat = "^lang=(%a+)" |
|
local tradformat = "%s(%a+)->{(.-)}$" |
|
pagemeta.lang = string.match(line,langformat) |
|
local trlang, trname = string.match(line, tradformat) |
|
if trlang ~= nil then |
|
local ttext = "in english:" |
|
if string.match( trlang, "^es" ) then |
|
ttext = "en español:" |
|
end |
|
local weblink = string.format( |
|
"<a href='./%s.html' rel='alternate'>%s</a>", |
|
pages[ trname ].slug, trname ) |
|
local gemlink = string.format( |
|
"=> ./%s.gmi %s", |
|
pages[ trname ].slug, trname ) |
|
pagemeta.webtranslation = string.format( |
|
"<p id='translation'>%s %s</p>", |
|
ttext, weblink ) |
|
pagemeta.gemtranslation = string.format( |
|
"%s\n%s\n", ttext, gemlink) |
|
end |
|
end |
|
elseif count == 3 then -- obtain page description |
|
pagemeta.pdescription,n = string.gsub(line,"[{}]","") |
|
end |
|
|
|
if count <=2 then goto nextline end -- skip normal processing for the first lines |
|
|
|
-- CONVERT LINES |
|
if string.match( line, "^```") then -- preformated |
|
if flags.pre then |
|
out.web = "</pre>" |
|
else |
|
out.web = "<pre>" |
|
end |
|
flags.pre = not flags.pre |
|
goto insert -- skip more checks |
|
end |
|
|
|
if flags.pre then |
|
out.web = sanitize( line ) |
|
goto insert |
|
end |
|
|
|
if string.match( line, "^%+") then -- + append html |
|
out.gem = nil |
|
out.web = string.sub(line,3) -- remove "+ " |
|
|
|
elseif string.match( line, "^&") then -- & append gemtext |
|
out.gem = string.sub(line,3) -- remove "& " |
|
|
|
elseif string.match( line, "^$") then -- empty line |
|
out.web = closeTags() |
|
|
|
elseif string.match( line, "^#+") then -- HEADERS |
|
local title, level = getHeader( line ) |
|
|
|
-- add h1 to navigation |
|
if level==1 then |
|
table.insert(navlines,string.format("<li><a href='#%s'>%s</a></li>",title,title)) |
|
end |
|
|
|
local close = closeTags() |
|
out.web = string.format("%s<h%d id='%s'>%s</h%d>",close,level,title,title,level) |
|
|
|
elseif string.match( line, "^>") then -- BLOCKQUOTE |
|
local close = closeTags() |
|
out.web = string.format("%s<blockquote>%s</blockquote>", close, string.sub(line,3)) |
|
|
|
elseif string.match( line, "^%*") then -- LIST ITEMS |
|
-- TODO reuse {wikilinks} code from paragraphs |
|
-- TODO apply the sanitization to everything? |
|
-- web |
|
local strippedline = sanitize(string.sub(line, 3)) -- remove "* " |
|
local webtext = string.gsub( strippedline, "{(.-)}", function (wikiname) |
|
return string.format("<a href='./%s.html'>%s</a>", pages[wikiname].slug, wikiname) |
|
end) |
|
|
|
local li = string.format("<li>%s</li>", webtext) |
|
if not flags.list then -- start list |
|
out.web = string.format("<ul>\n%s", li) |
|
flags.list = true |
|
else -- append list element |
|
out.web = li |
|
end |
|
|
|
-- gem |
|
local gemlinks = {} |
|
local gemline = string.gsub( line, "{(.-)}", function (wikiname) |
|
table.insert(gemlinks, string.format("=> ./%s.gmi %s",pages[wikiname].slug, wikiname)) |
|
return wikiname |
|
end) |
|
|
|
out.gem = gemline |
|
-- append links if there were any |
|
if #gemlinks > 0 then |
|
out.gem = out.gem.."\n"..table.concat(gemlinks,"\n") |
|
end |
|
|
|
elseif string.match( line, "^=>") then -- LINKS |
|
local dest, text = getLink( line ) |
|
if string.match( dest, "^%./") then --local links |
|
local cleantext = string.match( text, "^{(.-)}$" ) -- see if wikilink and clean |
|
local htmldest = string.gsub( dest, "gmi", "html" ) |
|
if cleantext ~= nil then -- wikilink |
|
local close = closeTags() |
|
out.web = string.format("%s<p><a href='%s' class='arrow'>%s</a></p>",close,htmldest,cleantext) |
|
out.gem = string.format("=> %s %s",dest,cleantext) |
|
|
|
elseif isImagePath( dest ) then -- images |
|
local img = string.format('<img src="%s" alt="%s" loading="lazy"/>',dest,text) |
|
if not flags.gallery then |
|
local close = closeTags() |
|
out.web = string.format("%s<gallery>\n%s", close, img) |
|
flags.gallery = true |
|
else |
|
out.web = img |
|
end |
|
|
|
else -- other files |
|
local close = closeTags() |
|
out.web = string.format("%s<p><a href='%s'>%s</a></p>",close,dest,text) |
|
end |
|
|
|
else -- external links |
|
out.web = string.format('<p><a href="%s" rel="external noreferrer noopener" target="_blank" class="arrow">%s</a></p>', dest, text) |
|
end |
|
|
|
else -- PARAGRAPHS |
|
-- web |
|
-- search and replace wikilinks |
|
local webline = string.gsub( line, "{(.-)}", function (wikiname) |
|
return string.format("<a href='./%s.html'>%s</a>", pages[wikiname].slug, wikiname) |
|
end) |
|
|
|
|
|
if not flags.p then |
|
out.web = string.format("<p>%s", webline) |
|
flags.p = true |
|
else |
|
out.web = string.format("<br/>\n%s",webline) |
|
end |
|
|
|
-- gem |
|
local gemlinks = {} |
|
local gemline = string.gsub( line, "{(.-)}", function (wikiname) |
|
table.insert(gemlinks, string.format("=> ./%s.gmi %s",pages[wikiname].slug, wikiname)) |
|
return wikiname |
|
end) |
|
|
|
out.gem = gemline |
|
-- append links if there were any |
|
if #gemlinks > 0 then |
|
out.gem = out.gem.."\n"..table.concat(gemlinks,"\n") |
|
end |
|
end -- paragraphs |
|
|
|
::insert:: -- insert line in table |
|
for k,l in pairs(out) do |
|
if l ~= nil then |
|
table.insert( lines[k], l ) |
|
end |
|
end |
|
|
|
|
|
-- skip incoming links calculation if <pre> mode |
|
if flags.pre then goto nextline end |
|
|
|
-- calculate incoming links from outgoing |
|
for outname in string.gmatch( line, "{([^ ]?.-[^ ]?)}" ) do |
|
insertIncoming( outname, name ) |
|
end |
|
|
|
::nextline:: |
|
count = count + 1 |
|
end -- end for line in f:lines() |
|
|
|
-- finalize html |
|
local close = closeTags() |
|
if isPre then |
|
table.insert( lines.web, "</pre>" ) |
|
elseif close~="" then |
|
table.insert( lines.web, close ) |
|
end |
|
|
|
table.insert( navlines, "</ul>" ) |
|
pagemeta.navcontent = table.concat( navlines, "\n" ) |
|
|
|
-- set templates |
|
if string.match( pagemeta.lang, "^en" ) then |
|
pagemeta.outs.web.templates = templates.webEn |
|
pagemeta.outs.gem.templates = templates.gemEn |
|
else |
|
pagemeta.outs.web.templates = templates.webEs |
|
pagemeta.outs.gem.templates = templates.gemEs |
|
end |
|
|
|
|
|
-- fill templates and write results |
|
for key, out in pairs(pagemeta.outs) do |
|
local fo = assert( io.open( out.path, "w" ) ) |
|
fo:write( fillTemplate(out.templates.header.template, pagemeta) ) |
|
fo:write( table.concat( lines[key],"\n" )) -- content |
|
fo:write( "\n" ) |
|
fo:close() |
|
end |
|
|
|
-- close source file |
|
f:close() |
|
end |
|
end |
|
|
|
function secondPass() -- write incoming links and footer |
|
|
|
for name,page in pairs(pages) do |
|
local pagemeta = page |
|
|
|
local doIncoming = true |
|
if #pagemeta.incoming > 0 then |
|
local gemlines = {} |
|
local weblines = { "<ul>" } |
|
|
|
-- format list of incoming links |
|
for i = 1, #pagemeta.incoming do |
|
name = pagemeta.incoming[i] |
|
slug = pages[name].slug |
|
table.insert( gemlines, string.format("=> ./%s.gmi %s",slug,name) ) |
|
table.insert( weblines, string.format("<li><a href='./%s.html'>%s</a></li>",slug,name) ) |
|
end |
|
|
|
table.insert(weblines,"</ul>") |
|
|
|
page.gemincoming = table.concat( gemlines, "\n" ) |
|
page.webincoming = table.concat( weblines ) |
|
|
|
else |
|
doIncoming = false |
|
print( string.format("{%s} is an orphan!", name) ) |
|
end |
|
|
|
-- fill incoming and footer templates and write results |
|
for key, out in pairs(pagemeta.outs) do |
|
local fo = assert( io.open( out.path, "a" ) ) |
|
if doIncoming then |
|
fo:write( fillTemplate(out.templates.incoming.template, pagemeta) ) |
|
end |
|
fo:write( fillTemplate(out.templates.footer.template, pagemeta) ) |
|
fo:close() |
|
end |
|
end |
|
end |
|
|
|
function initPageMetadata( name, date ) -- return a table with metadata |
|
local meta = { |
|
incoming = {}, |
|
slug = slugify(name), name = name, |
|
ptitle = "", pdescription = "", |
|
updatedate = date, |
|
navcontent = "", |
|
gemincoming = "", webincoming = "", |
|
gemtranslation = "", webtranslation = "" |
|
} |
|
meta.outs = { |
|
web = { path = string.format("%s/%s.html", webdir, meta.slug) }, |
|
gem = { path = string.format("%s/%s.gmi", gemdir, meta.slug) } |
|
} |
|
return meta |
|
end |
|
|
|
function genIndex() |
|
local indexpath = "src/pages.gmo" |
|
|
|
-- initialize and open index file |
|
os.execute(string.format("cp templates/index.gmi %s",indexpath)) |
|
local index = assert( io.open(indexpath,"a" ) ) |
|
|
|
-- get gmo files in chronological order |
|
local gmofiles = io.popen("ls -to --time-style=+%F src/*gmo") |
|
local count = 0 |
|
for filename in gmofiles:lines() do |
|
local date,basename = string.match(filename,"^.+%s(.-)%ssrc/(.-).gmo$") |
|
local name = spacify(basename) |
|
-- create an entry for each file |
|
if name ~= "pages" then |
|
local entry = string.format("=> ./%s.gmi {%s}\n", basename, name) |
|
index:write(entry) |
|
end |
|
-- also initialize each name in the pages table |
|
-- initialize page metadata |
|
pages[ name ] = initPageMetadata( name, date ) |
|
count = count + 1 |
|
end |
|
gmofiles:close() |
|
index:close() |
|
return count |
|
end |
|
|
|
function genLog() |
|
-- generate the following log files: |
|
-- * twtxt gemini and web |
|
-- * atom feed gemini and web |
|
-- * gmo log |
|
-- * gmisub |
|
|
|
local logsrc = assert( io.open( "src/log.txt", "r" ) ) |
|
|
|
local logs = { |
|
gemtw = { path = string.format("%s/tw.txt",gemdir), head="templates/twheader.txt" }, |
|
webtw = { path = string.format("%s/tw.txt",webdir), head="templates/twheader.txt" }, |
|
gmolog = { path = "src/log.gmo", head="templates/logheader.gmi" }, |
|
gematom = { path = string.format("%s/atom.xml",gemdir), head="templates/gematomheader.txt" }, |
|
webatom = { path = string.format("%s/atom.xml",webdir), head="templates/webatomheader.txt" }, |
|
gemfeed = { path = string.format("%s/feed.gmi",gemdir), head="templates/feedheader.gmi" } |
|
} |
|
|
|
-- create and open logs |
|
for _,log in pairs(logs) do |
|
os.execute(string.format("cp %s %s",log.head,log.path)) |
|
log.f = assert( io.open( log.path, "a" ) ) |
|
end |
|
|
|
logs.gematom.f:write(string.format("<updated>%sT12:00:00Z</updated>\n",meta.fecha)) |
|
logs.webatom.f:write(string.format("<updated>%sT12:00:00Z</updated>\n",meta.fecha)) |
|
|
|
|
|
-- process each line of log.txt |
|
for line in logsrc:lines() do |
|
local pattern = "^(.-)%s(.-)%s{(.-)}$" |
|
local timestamp, text, wikilink = string.match(line,pattern) |
|
local slug = slugify(wikilink) |
|
local date = string.sub(timestamp,1,10) -- YYYY-MM-DD |
|
|
|
-- gmo log |
|
local gmoformat = "## %s\n%s\n=> ./%s.gmi {%s}\n\n" |
|
logs.gmolog.f:write(string.format(gmoformat, date, text, slug, wikilink)) |
|
|
|
-- gmi sub |
|
local gmisubformat = "=> ./%s.gmi %s %s\n" |
|
logs.gemfeed.f:write(string.format(gmisubformat, slug, date, text)) |
|
|
|
-- twtxt |
|
local weburl = string.format("https://%s/%s.html",meta.domain,slug) |
|
local gemurl = string.format("gemini://%s/%s.gmi",meta.domain,slug) |
|
local twtxtformat = "%s\t%s | %s\n" |
|
logs.webtw.f:write(string.format(twtxtformat, timestamp, text, weburl)) |
|
logs.gemtw.f:write(string.format(twtxtformat, timestamp, text, gemurl)) |
|
|
|
-- atom |
|
local weburldate = string.format("https://%s/%s.html#%s",meta.domain,slug,date) |
|
local atomformat = [[ |
|
|
|
<entry> |
|
<title>%s</title> |
|
<updated>%s</updated> |
|
<link rel='alternate' href='%s'/> |
|
<id>%s</id> |
|
</entry> |
|
]] |
|
logs.webatom.f:write(string.format(atomformat, text, timestamp, weburldate, weburldate)) |
|
logs.gematom.f:write(string.format(atomformat, text, timestamp, gemurl, gemurl)) |
|
end |
|
|
|
|
|
--close logs |
|
logs.webatom.f:write("</feed>\n") |
|
logs.gematom.f:write("</feed>\n") |
|
for _,log in pairs(logs) do |
|
log.f:close() |
|
end |
|
|
|
logsrc:close() |
|
end |
|
|
|
|
|
-- THE SCRIPT |
|
|
|
-- create directories |
|
os.execute(string.format("mkdir -p %s/static %s/img %s/img",webdir,webdir,gemdir)) |
|
|
|
print(meta.fecha) |
|
|
|
-- clean up |
|
print("limpiando archivos previos...") |
|
os.execute(string.format('find %s %s -regex ".+.\\(gmi\\|html\\)$" -exec rm {} +',webdir,gemdir)) |
|
|
|
-- copy images |
|
print("copiando imágenes y otros archivos...") |
|
os.execute( string.format("find src/img/ -type f -exec cp -vut %s/img/ {} +",webdir)) |
|
os.execute( string.format("find src/img/ -type f -exec cp -vut %s/img/ {} +",gemdir)) |
|
|
|
-- copy stylesheet and misc files |
|
os.execute(string.format("cp -vu templates/estilo.css %s/static/",webdir)) |
|
|
|
local miscfiles = { "src/llave_sejo.asc", "src/biosejo.xml" } |
|
for k = 1, #miscfiles do |
|
os.execute(string.format("cp -vu %s %s/", miscfiles[k], webdir)) |
|
os.execute(string.format("cp -vu %s %s/", miscfiles[k], gemdir)) |
|
end |
|
|
|
|
|
-- generate log files |
|
print("generando log...") |
|
genLog() |
|
|
|
-- generate index |
|
print("generando índice de páginas...") |
|
local n = genIndex() |
|
|
|
-- start first pass |
|
-- converting from gmo to gmi and html and calculating incoming links |
|
print(string.format("convirtiendo %d páginas...",n)) |
|
firstPass() |
|
|
|
-- second pass, write incoming links |
|
print("escribiendo incoming links...") |
|
secondPass() |
|
|
|
-- update index files with home |
|
os.execute("cp web/home.html web/index.html") |
|
os.execute("cp gem/home.gmi gem/index.gmi") |
|
|
|
print("listx!")
|
|
|