296 lines
11 KiB
Plaintext
296 lines
11 KiB
Plaintext
|
(var lfs (require :lfs))
|
||
|
(var fennel (require :fennel))
|
||
|
|
||
|
(global pp (fn [x]
|
||
|
(print (fennel.view x))))
|
||
|
|
||
|
; config variables
|
||
|
(var gemini-baseurl "gemini://tilde.town/~nihilazo")
|
||
|
(var html-baseurl "https://itwont.work")
|
||
|
(var gemini-outdir "public_gemini")
|
||
|
(var html-outdir "public_html")
|
||
|
(var content-dir "content")
|
||
|
;; TODO header/footer files instead of variables?
|
||
|
(var gemini-blog-header "# lipu pi jan Niko\n\n")
|
||
|
(var gemini-footer "\n=> gemini://tilde.town/~nihilazo Go Home\n")
|
||
|
(var html-blog-header "<h1>lipu pi jan Niko</h1>\n\n")
|
||
|
(var html-header "<!DOCTYPE html><head><link rel=stylesheet href='https://itwont.work/style.css'><title>lipu pi jan Niko</title><body><article>")
|
||
|
(var html-footer "</article><footer>
|
||
|
<a href=https://itwont.work>Go Home</a><br>
|
||
|
<a href='https://webring.xxiivv.com/#random' target='_blank'><img id='webring' src='https://webring.xxiivv.com/icon.white.svg'/></a></footer></body>")
|
||
|
(var ass-feed-header "# Actually Simple Syndication - https://tilde.town/~dzwdz/ass/\n" )
|
||
|
|
||
|
(var rss-feed-header "<?xml version=\"1.0\" encoding=\"utf-8\"?>
|
||
|
<feed xmlns=\"http://www.w3.org/2005/Atom\">
|
||
|
<title>lipu pi jan Niko</title>
|
||
|
<author><name>jan Niko</name></author>\n")
|
||
|
|
||
|
; working variables
|
||
|
(var dirs [])
|
||
|
(var files [])
|
||
|
(var posts [])
|
||
|
|
||
|
; converts a gemini link line to a html link line, with the correct changes to function
|
||
|
(fn html-link [line]
|
||
|
(var url "")
|
||
|
(let [(u name) (line:match "=> ([^%s]+) (.+)")] ; get the url and name
|
||
|
(set url u) ; if the link isn't an offsite gemini link, change .gmi extensions to .html
|
||
|
(if (and (string.match url ".%.gmi") (not (string.match url "gemini://.+")))
|
||
|
(set url (string.gsub url "%.gmi" ".html")))
|
||
|
(if (= (string.sub url 1 1) "/")
|
||
|
; if it's an absolute link, add the base url
|
||
|
(string.format "<a href=\"%s%s\">%s</a><br>\n" html-baseurl url name)
|
||
|
(string.format "<a href=\"%s\">%s</a><br>\n" url name))))
|
||
|
|
||
|
; generates log roll page for gemini
|
||
|
(fn generate-gemini-log []
|
||
|
(var gemini-index gemini-blog-header)
|
||
|
(each [_ p (ipairs posts)]
|
||
|
(let [{: date : title : path} p
|
||
|
url (.. gemini-baseurl path)]
|
||
|
(set gemini-index
|
||
|
(.. gemini-index (string.format "=> %s %s %s\n" url date title)))))
|
||
|
(set gemini-index (.. gemini-index gemini-footer))
|
||
|
(let [f (io.open (.. gemini-outdir "/log/index.gmi") :w+)]
|
||
|
(f:write gemini-index)
|
||
|
(f:close)))
|
||
|
|
||
|
; generate rss/atom feeds
|
||
|
(fn generate-rss-feeds []
|
||
|
(var gemini-rss-feed (.. rss-feed-header "<id>" gemini-baseurl "</id>\n"))
|
||
|
(var html-rss-feed (.. rss-feed-header "<id>" html-baseurl "</id>\n"))
|
||
|
; add last updated info to rss feeds
|
||
|
(let [d (. posts 1 :date) ]
|
||
|
(set gemini-rss-feed (.. gemini-rss-feed "<updated>" d "T12:00:00Z</updated>\n"))
|
||
|
(set html-rss-feed (.. html-rss-feed "<updated>" d "T12:00:00Z</updated>\n")))
|
||
|
; add posts to rss feeds
|
||
|
(each [_ p (ipairs posts)]
|
||
|
(let [ {: date : title : path} p
|
||
|
entry "\n<entry>
|
||
|
<id>%s%s</id>
|
||
|
<title>%s</title>
|
||
|
<updated>%sT12:00:00Z</updated>
|
||
|
<link href=\"%s\" rel=\"alternate\" />
|
||
|
</entry>\n" ]
|
||
|
(set html-rss-feed (.. html-rss-feed
|
||
|
(entry:format html-baseurl (path:gsub ".gmi" ".html") title date (path:gsub ".gmi" ".html"))))
|
||
|
(set gemini-rss-feed (.. gemini-rss-feed
|
||
|
(entry:format gemini-baseurl path title date path)))))
|
||
|
(set html-rss-feed (.. html-rss-feed "</feed>"))
|
||
|
(set gemini-rss-feed (.. gemini-rss-feed "</feed>"))
|
||
|
; write out rss feeds
|
||
|
(let [f (io.open (.. gemini-outdir "/atom.xml") :w+)]
|
||
|
(f:write gemini-rss-feed)
|
||
|
(f:close))
|
||
|
(let [f (io.open (.. html-outdir "/atom.xml") :w+)]
|
||
|
(f:write html-rss-feed)
|
||
|
(f:close)))
|
||
|
|
||
|
; generate ass (https://tilde.town/~dzwdz/ass) feeds
|
||
|
(fn generate-ass-feeds []
|
||
|
(var gemini-ass-feed ass-feed-header)
|
||
|
(var html-ass-feed ass-feed-header)
|
||
|
; add posts to ass feeds
|
||
|
(each [_ p (ipairs posts)]
|
||
|
(let [ {: date : title : path} p]
|
||
|
(set html-ass-feed (.. html-ass-feed
|
||
|
(string.format "%s\t%s%s\t%s\n" date html-baseurl (path:gsub ".gmi" ".html") title)))
|
||
|
(set gemini-ass-feed (.. gemini-ass-feed
|
||
|
(string.format "%s\t%s%s\t%s\n" date gemini-baseurl path title)))))
|
||
|
; write out ass feeds
|
||
|
(let [f (io.open (.. gemini-outdir "/feed.ass") :w+)]
|
||
|
(f:write gemini-ass-feed)
|
||
|
(f:close))
|
||
|
(let [f (io.open (.. html-outdir "/feed.ass") :w+)]
|
||
|
(f:write html-ass-feed)
|
||
|
(f:close)))
|
||
|
|
||
|
; generates log roll page for html
|
||
|
(fn generate-html-log []
|
||
|
(var html-index (.. html-header html-blog-header :<ul>))
|
||
|
(each [_ p (ipairs posts)]
|
||
|
(let [{: date : title : path} p
|
||
|
url (.. html-baseurl path)]
|
||
|
(set html-index (.. html-index "<li>"
|
||
|
(html-link (string.format "=> %s %s %s\n" url date
|
||
|
title))
|
||
|
"</li>"))))
|
||
|
(set html-index (.. html-index "</ul>" html-footer))
|
||
|
(let [f (io.open (.. html-outdir "/log/index.html") :w+)]
|
||
|
(f:write html-index)
|
||
|
(f:close)))
|
||
|
|
||
|
; generate-log generates the log pages and feeds from the posts index
|
||
|
(fn generate-logs []
|
||
|
(table.sort posts (fn [a b]
|
||
|
(> (. a :date) (. b :date))))
|
||
|
(generate-gemini-log)
|
||
|
(generate-html-log)
|
||
|
(generate-ass-feeds)
|
||
|
(generate-rss-feeds))
|
||
|
|
||
|
; index-post adds a log post to the post index
|
||
|
(fn index-post [f]
|
||
|
(io.input (.. content-dir f)) ; open file
|
||
|
(let [post {}]
|
||
|
(tset post :path f)
|
||
|
(let [line (io.read) ; read first line, match title
|
||
|
title (line:match "^#%s*([^\n]+)%s*$")]
|
||
|
(tset post :title title))
|
||
|
(let [line (io.read) ; read second line, match date
|
||
|
date (line:match "^%s*(.+)%s*")]
|
||
|
(tset post :date date))
|
||
|
(table.insert posts post))
|
||
|
(io.close))
|
||
|
|
||
|
; HTML-escapes and trims a string
|
||
|
(fn html-clean [l]
|
||
|
(let [s (l:match "%s*(.+)%s*")]
|
||
|
(-> s
|
||
|
(string.gsub "&" "&")
|
||
|
(string.gsub "<" "<")
|
||
|
(string.gsub ">" ">")
|
||
|
(string.gsub "\"" """)
|
||
|
(string.gsub "''" "'"))))
|
||
|
|
||
|
; converts a gemtext line to an html line. This could probably be done far better. TODO am I overusing string matching here? Maybe could write an lpeg or something similar?
|
||
|
|
||
|
(var state :normal)
|
||
|
|
||
|
; either :normal or :pre depending on the current position in the file.
|
||
|
(var listing false)
|
||
|
|
||
|
; true if we are in a list, false otherwise
|
||
|
(var blockquoting false)
|
||
|
(fn to-html [line]
|
||
|
(var prefix "")
|
||
|
(if (and listing (not (line:match "^%*%s")))
|
||
|
(do
|
||
|
(set listing false)
|
||
|
(set prefix "</ul>")))
|
||
|
(if (and blockquoting (not (line:match "^>")))
|
||
|
(do
|
||
|
(set blockquoting false)
|
||
|
(set prefix "</blockquote>")))
|
||
|
(if (= state :pre)
|
||
|
(if (line:match "^```") (do
|
||
|
(set state :normal)
|
||
|
"</pre>") line)
|
||
|
(= state :normal)
|
||
|
(if (line:match "^```")
|
||
|
(do
|
||
|
(set state :pre)
|
||
|
(.. prefix "<pre>")) ; open pre
|
||
|
(line:match "^#[^#]")
|
||
|
(.. prefix "<h1>" (html-clean (line:match "^#([^#].+)")) "</h1>")
|
||
|
(line:match "^##[^#]")
|
||
|
(.. prefix "<h2>" (html-clean (line:match "^##([^#].+)")) "</h2>")
|
||
|
(line:match "^###[^#]")
|
||
|
(.. prefix "<h3>" (html-clean (line:match "^###([^#].+)")) "</h3>")
|
||
|
(line:match "^=>")
|
||
|
(.. prefix (html-link line)) ; link
|
||
|
(line:match "^%s*$")
|
||
|
prefix ; blank line
|
||
|
(line:match "^%*%s")
|
||
|
(do
|
||
|
(if (not listing)
|
||
|
(do
|
||
|
(set prefix "<ul>")
|
||
|
(set listing true)))
|
||
|
(.. prefix "<li>" (html-clean (line:match "^%*(.+)")) "</li>"))
|
||
|
(line:match "^>%s")
|
||
|
(do
|
||
|
(if (not blockquoting)
|
||
|
(do
|
||
|
(set prefix "<blockquote>\n")
|
||
|
(set blockquoting true)))
|
||
|
(.. prefix (html-clean (line:match "^>(.+)"))))
|
||
|
(.. prefix "<p>" (html-clean line) "</p>"))))
|
||
|
|
||
|
; processes, converts, and writes out an input gemini page in html
|
||
|
(fn process-html-page [f]
|
||
|
(let [infile (.. content-dir f)
|
||
|
outfile (io.open (.. html-outdir (string.gsub f "%.gmi" ".html")) :w+)]
|
||
|
(outfile:write html-header)
|
||
|
(set state :normal)
|
||
|
(each [line (io.lines infile)]
|
||
|
(let [h (to-html line)]
|
||
|
(outfile:write h "\n")))
|
||
|
(outfile:write html-footer)
|
||
|
(outfile:close)))
|
||
|
|
||
|
; processes and writes out an input gemini page into an output one
|
||
|
(fn process-gemini-page [f]
|
||
|
(let [infile (.. content-dir f)
|
||
|
outfile (io.open (.. gemini-outdir f) :w+)]
|
||
|
(each [l (io.lines infile)]
|
||
|
(if (string.match l "^=>.+") ; if the line is a link
|
||
|
(let [(url name) (string.match l "=> ([^%s]+) (.+)")] ; get the url and name
|
||
|
(if (= (string.sub url 1 1) "/")
|
||
|
; if it's an absolute link, add the base url
|
||
|
(outfile:write (string.format "=> %s%s %s\n" gemini-baseurl url
|
||
|
name))
|
||
|
(outfile:write (string.format "=> %s %s\n" url name))))
|
||
|
(outfile:write l "\n")))
|
||
|
(outfile:write gemini-footer)
|
||
|
(outfile:close)))
|
||
|
|
||
|
; process-file runs for every input file to process t
|
||
|
(fn process-file [f]
|
||
|
(if (string.match f ".+log/.+%.gmi")
|
||
|
(do
|
||
|
(index-post f)
|
||
|
(process-gemini-page f)
|
||
|
(process-html-page f)) ; add log posts to index
|
||
|
(string.match f ".+%.gmi")
|
||
|
(do
|
||
|
(process-gemini-page f)
|
||
|
(process-html-page f))
|
||
|
(do
|
||
|
(os.execute (.. "cp " content-dir f " " gemini-outdir f))
|
||
|
(os.execute (.. "cp " content-dir f " " html-outdir f)))))
|
||
|
|
||
|
; else copy file
|
||
|
|
||
|
; process-dir is run for every input directory to copy the tree into output directories
|
||
|
(fn process-dir [f]
|
||
|
(lfs.mkdir (.. gemini-outdir f))
|
||
|
(lfs.mkdir (.. html-outdir f)))
|
||
|
|
||
|
; walk the input directory tree recursively, processing every file and directory using the process-file and process-dir functions. "base" is the directory that is being walked. "position" is the current position in the directory tree.
|
||
|
|
||
|
(fn walk [base position] ; for each file in the current directory
|
||
|
(each [file (lfs.dir (.. base position))]
|
||
|
(let [fullpath (.. base position "/" file)]
|
||
|
(let [{:mode filemode} (lfs.attributes fullpath)] ; get the mode (file or directory)
|
||
|
(if (and (not= file "..") (not= file ".")) ; skip . and ..
|
||
|
(if (= filemode :directory)
|
||
|
(do
|
||
|
; recurse
|
||
|
(walk base (.. position "/" file))
|
||
|
(table.insert dirs (.. position "/" file)))
|
||
|
(= filemode :file) ; process the file
|
||
|
(table.insert files (.. position "/" file))))))))
|
||
|
|
||
|
; do everything
|
||
|
(os.execute (.. "rm -rf " gemini-outdir))
|
||
|
(os.execute (.. "rm -rf " html-outdir))
|
||
|
(lfs.mkdir gemini-outdir)
|
||
|
(lfs.mkdir html-outdir)
|
||
|
|
||
|
(print "getting files")
|
||
|
(walk content-dir "")
|
||
|
|
||
|
(print "processing directory structure")
|
||
|
(each [_ d (ipairs dirs)]
|
||
|
(process-dir d))
|
||
|
|
||
|
(print "processing files")
|
||
|
(each [_ f (ipairs files)]
|
||
|
(process-file f))
|
||
|
|
||
|
(print "generating feeds")
|
||
|
(generate-logs)
|
||
|
|
||
|
(print "done!")
|