lua generator

This commit is contained in:
sejo 2022-03-27 17:55:43 -06:00
parent b931ab0070
commit c5fe6f2833
14 changed files with 687 additions and 0 deletions

488
generasitio.lua Normal file
View File

@ -0,0 +1,488 @@
meta = {
domain = "compudanzas.net",
title = "compudanzas",
description = "explorations of computing at a human scale",
fecha = os.date("%Y-%m-%d")
}
webdir = "webtest" -- web output directory
gemdir = "gemtest" -- gemini output directory
templates = {
webEn = {
header = { path = "pageheader.html", template = ""},
footer = { path = "pagefooter.html", template = ""},
incoming = { path = "pageincoming.html", template = ""}
},
gemEn = {
header = { path = "pageheader.gmi", template = ""},
footer = { path = "pagefooter.gmi", template = ""},
incoming = { path = "pageincoming.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("<","&lt;"):gsub(">","&gt;")
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 = {} }
-- 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
pagemeta.lang, pagemeta.trlang, pagemeta.trname = string.match("^lang=(.-)%s(.-)->(.-)$")
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
-- TODO create nav
local title, level = getHeader( line )
local close = closeTags()
out.web = string.format("%s<h%d>%s</h%d>",close,level,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
local li = string.format("<li>%s</li>", string.sub(line,3)) -- remove "* "
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
elseif string.match( line, "^=>") then -- LINKS
local dest, text = getLink( line )
if string.match( dest, "^%./") then --local links
local t = string.match( text, "^{(.-)}$" )
if t ~= nil then -- wikilink
local close = closeTags()
out.web = string.format("%s<p><a href='%s' class='arrow'>%s</a></p>",close,dest,t)
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
-- set templates
pagemeta.outs.web.templates = templates.webEn
pagemeta.outs.gem.templates = templates.gemEn
-- 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 = { "<p>" }
-- 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("<a href='./%s.html'>%s</a> ",slug,name) )
end
table.insert(weblines,"</p>")
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 ) -- return a table with metadata
local meta = {
incoming = {},
slug = slugify(name), name = name,
ptitle = "", pdescription = "",
updatedate = "",
navcontent = "",
gemincoming = "", webincoming = "",
}
meta.outs = {
web = { path = string.format("%s/%s.html", webdir, meta.slug) },
gem = { path = string.format("%s/%s.gmi", gemdir, meta.slug) }
}
-- get update date for file
local stat = io.popen(string.format("stat -c %%y src/%s.gmo", meta.slug),"r")
local dat = stat:read("l")
stat:close()
meta.updatedate = string.sub(dat, 1, 10) -- YYYY-MM-DD
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 -t src/*gmo")
for filename in gmofiles:lines() do
local basename = string.sub(filename, 5, -5) -- remove src/ and .gmo
local name = spacify(basename)
-- create an entry for each file
local entry = string.format("=> ./%s.gmi {%s}\n", basename, name)
index:write(entry)
-- also initialize each name in the pages table
-- initialize page metadata
pages[ name ] = initPageMetadata( name )
end
gmofiles:close()
index:close()
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",fecha))
logs.webatom.f:write(string.format("<updated>%sT12:00:00Z</updated>\n",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))
-- twtxt
local weburl = string.format("https://%s/%s.html",domain,slug)
local gemurl = string.format("gemini://%s/%s.gmi",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",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 tmp",webdir,webdir,gemdir))
print(meta.fecha)
-- clean up
print("limpiando archivos previos...")
os.execute(string.format("find %s -iname *.html -exec rm {} \\;", webdir))
os.execute(string.format("find %s -iname *.gmi -exec rm {} \\;", gemdir))
os.execute("find tmp -iname *.gmo -exec rm {} \\;")
-- copy images
print("copiando imágenes y otros archivos...")
local imgs = io.popen("ls src/img/")
for img in imgs:lines() do
os.execute(string.format("cp -vu src/img/%s %s/img/", img, webdir))
os.execute(string.format("cp -vu src/img/%s %s/img/", img, gemdir))
end
imgs:close()
-- copy stylesheet and misc files
os.execute(string.format("cp -vu templates/estilo.css %s/static/",webdir))
local miscfiles = { "src/llave_sejo.asc" }
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...")
genIndex()
-- start first pass
-- converting from gmo to gmi and html and calculating incoming links
print("convirtiendo páginas...")
firstPass()
-- second pass, write incoming links
print("escribiendo incoming links...")
secondPass()
print("listx!")

100
templates/estilo.css Normal file
View File

@ -0,0 +1,100 @@
/* links externos */
a[rel^=external]:after{ content: "↗"; }
a{
color: #857;
}
a:visited{
color: #404;
}
a.icon{
background-color: #fff;
}
/* gemtext-like format */
/* h1:before{ content: "# "; }
h2:before{ content: "## "; }
h3:before{ content: "### "; }*/
a.arrow:before{ content: "⇒ "; }
body{
color: #100;
max-width:36em;
margin:0 auto;
font-size:18px;
padding: 1em;
font-family:sans-serif;
}
footer{
font-size: x-small;
padding-top:2em;
}
footer img{
margin:0;
}
pre{
background-color: #edd;
color: #433;
padding:1em;
overflow-x: auto;
}
blockquote{
font-style: italic;
}
main h1{
margin-top: 1.5em;
font-size: xx-large;
}
main h2{
font-size: x-large;
margin-left:0.3em;
}
main h3{
font-size: larger;
margin-left:1em;
}
gallery{
display:flex;
flex-direction:row;
flex-wrap:wrap;
justify-content:space-evenly;
align-content:center;
}
img{
margin:10px;
border-radius:5px;
max-width: 320px;
}
nav{ font-size:smaller; }
nav li{ padding:0.2em; }
main li{ padding:0.5em; }
table {
font-size: smaller;
max-width: 90%;
border-collapse: collapse;
margin: 1em auto;
text-align:left;
}
table th {
border-bottom: 1px solid black;
padding: 0.1em 1em;
}
table td {
padding: 0.1em 1em;
}
td.num{
text-align:right;
}
/* desktop */
@media screen and (min-width: 600px) {
nav li{
display:inline;
}
img{ max-width: 100%; }
}

9
templates/feedheader.gmi Normal file
View File

@ -0,0 +1,9 @@
# compudanzas
our gemsub feed
see also:
=> ./log.gmi log
# entries

View File

@ -0,0 +1,9 @@
<?xml version='1.0' encoding='UTF-8'?>
<feed xmlns='http://www.w3.org/2005/Atom'>
<id>gemini://compudanzas.net/</id>
<title>compudanzas</title>
<author><name>sejo</name></author>
<generator>genlog</generator>
<link href='gemini://compudanzas.net/atom.xml' rel='self'/>
<link href='gemini://compudanzas.net/log.gmi' rel='alternate'/>

4
templates/index.gmi Normal file
View File

@ -0,0 +1,4 @@
# index of pages
most recently modified, above:

9
templates/logheader.gmi Normal file
View File

@ -0,0 +1,9 @@
# compudanzas log
updates of the project
=> ./tw.txt [twtxt]
=> ./atom.xml [atom feed]
& => ./feed.gmi [gemsub feed]
# entries

5
templates/pagefooter.gmi Normal file
View File

@ -0,0 +1,5 @@
# meta
=> ./home.gmi {title}
=> ./contact.gmi contact
=> https://wiki.p2pfoundation.net/Peer_Production_License text, images, and code are shared with the peer production license

15
templates/pagefooter.html Normal file
View File

@ -0,0 +1,15 @@
</main>
<footer>
<p>
<a href='https://tildegit.org/sejo/compudanzas/src/branch/main/src/{slug}.gmo' rel='external noreferrer noopener' target=_blank>source file</a> &mdash;
most recent update:
<time datetime='{updatedate}'>1{updatedate}</time> &mdash;
<a href='gemini://{domain}/{slug}.gmi' rel='external noreferrer noopener' target=_blank>view in gemini://</a>
</p>
<hr/>
<p><a href='./about.html'>about</a> <a href='./contact.html'>contact</a> <a href='./support.html'>support</a></p>
<p><a class='icon' href='./home.html' title='compudanzas'><img src='./img/iconocompudanzas_32.png' width=32 height=32 alt='icono de compudanzas'/></a>
<a href='https://webring.xxiivv.com/#random' target='_blank' title='webring' class='icon'><img src='./img/icon.black.svg' width='32' height='32' alt='xxiivv webring'/></a></p>
<p>texts, images, and code are shared with the <a href='https://wiki.p2pfoundation.net/Peer_Production_License' target=_blank rel='external noreferrer noopener' title='Peer Production License'>peer production license</a></p>
</footer>
</body>

2
templates/pageheader.gmi Normal file
View File

@ -0,0 +1,2 @@
# {ptitle}

29
templates/pageheader.html Normal file
View File

@ -0,0 +1,29 @@
<!DOCTYPE html>
<html xmlns='http://www.w3.org/1999/xhtml' lang='{lang}'>
<head>
<meta http-equiv='Content-Type' content='text/html; charset=utf-8' />
<meta content='initial-scale=1.0' name='viewport'/>
<link rel='me' href='https://sunbeam.city/@compudanzas'>
<link rel='me' href='https://post.lurk.org/@compudanzas'>
<link rel='me' href='https://merveilles.town/@sejo'>
<link rel='stylesheet' href='./static/estilo.css'>
<link rel='icon' href='./img/iconocompudanzas_16.png' type='image/png' sizes='16x16'>
<link rel='icon' href='./img/iconocompudanzas_32.png' type='image/png' sizes='32x32'>
<link rel='icon' href='./img/iconocompudanzas_64.png' type='image/png' sizes='64x64'>
<meta property='og:type' content='website' />
<meta property='og:image' content='https://compudanzas.net/img/iconocompudanzas_128_w.png'>
<meta property='og:title' content='{title} &mdash; {ptitle}'>
<meta property='og:description' content='{pdescription}'>
<meta name='description' content='{pdescription}'>
<meta property='og:url' content='https://compudanzas.net/{slug}.html'>
<title>{title} &mdash; {ptitle}</title>
</head>
<body>
<header>
<p><a class='icon' href='./home.html' title='{title}'><img src='./img/iconocompudanzas_32.png' style='margin:0' alt='icono de compudanzas'/></a></p>
<h1>{ptitle}</h1>
</header>
<nav>
{navcontent}
</nav>
<main>

View File

@ -0,0 +1,3 @@
# incoming links
{gemincoming}

View File

@ -0,0 +1,4 @@
<section id='incoming'>
<h1>incoming links</h1>
{webincoming}
</section>

1
templates/twheader.txt Normal file
View File

@ -0,0 +1 @@
# nick = compudanzas

View File

@ -0,0 +1,9 @@
<?xml version='1.0' encoding='UTF-8'?>
<feed xmlns='http://www.w3.org/2005/Atom'>
<id>https://compudanzas.net/</id>
<title>compudanzas</title>
<author><name>sejo</name></author>
<generator>genlog</generator>
<link href='https://compudanzas.net/atom.xml' rel='self'/>
<link href='https://compudanzas.net/log.html' rel='alternate'/>