💾 Archived View for gemini.conman.org › extensions › GLV-1 › handlers › blog.lua captured on 2020-10-31 at 02:18:43.
-=-=-=-=-=-=-
-- *********************************************************************** -- -- Copyright 2020 by Sean Conner. -- -- This program is free software: you can redistribute it and/or modify it -- under the terms of the GNU General Public License as published by the -- Free Software Foundation, either version 3 of the License, or (at your -- option) any later version. -- -- This program is distributed in the hope that it will be useful, but -- WITHOUT ANY WARRANTY; without even the implied warranty of -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General -- Public License for more details. -- -- You should have received a copy of the GNU General Public License along -- with this program. If not, see <http://www.gnu.org/licenses/>. -- -- Comments, questions and criticisms can be sent to: sean@conman.org -- -- ======================================================================= -- -- Code to handle blog requests. -- -- *********************************************************************** -- luacheck: globals init handler last_link -- luacheck: ignore 611 local syslog = require "org.conman.syslog" local fsys = require "org.conman.fsys" local date = require "org.conman.date" local ENTITIES = require "org.conman.const.entity" local lpeg = require "lpeg" local io = require "io" local os = require "os" local table = require "table" local string = require "string" local utf8 = require "utf8" local readfile = require "GLV-1.handlers.file" local MSG = require "GLV-1.MSG" local uurl = require "GLV-1.url-util" local html = require "org.conman.app.port70.handlers.blog.html" local format = require "org.conman.app.GLV-1.handlers.blog.format" local loadfile = loadfile local tonumber = tonumber local ipairs = ipairs local blog = { require = setmetatable( {}, { __index = function(s) return s end, __call = function(s) return s end, } ) } _ENV = {} local deent do local char = lpeg.P"&#" * lpeg.C(lpeg.R"09"^1) * lpeg.P";" / utf8.char + lpeg.P"&" * lpeg.C(lpeg.R("az","AZ","09")^1) * lpeg.P";" / ENTITIES + lpeg.S" \t\r\n"^1 / " " + lpeg.P(1) deent = lpeg.Cs(char^0) end -- *********************************************************************** -- usage: date = read_date(fname) -- desc: Read the blog start and end date files. -- input: fname (string) either '.first' or '.last' -- return: date (table) -- * year -- * month -- * day -- *********************************************************************** local number = lpeg.R"09"^1 / tonumber local dateparse = lpeg.Ct( lpeg.Cg(number,"year") * lpeg.P"/" * lpeg.Cg(number,"month") * lpeg.P"/" * lpeg.Cg(number,"day") * lpeg.P"." * lpeg.Cg(number,"part") ) local function read_date(fname) fname = blog.basedir .. "/" .. fname local f = io.open(fname,"r") local d = f:read("*l") f:close() return dateparse:match(d) end -- *********************************************************************** -- usage: titles = get_days_titles(when) -- desc: Retreive the titles of the posts of a given day -- input: when (table) -- * year -- * month -- * day -- * part -- return: titles (string/array) titles for each post -- *********************************************************************** local function get_days_titles(when) local res = {} local fname = string.format("%s/%d/%02d/%02d/titles",blog.basedir,when.year,when.month,when.day) if fsys.access(fname,"r") then for title in io.lines(fname) do table.insert(res,deent:match(title)) end end return res end -- *********************************************************************** -- usage: collect_day(when) -- desc: Create gopher links for a day's entry -- input: when (table) -- * year -- * month -- * day -- *********************************************************************** local function collect_day(base,when) when.part = 1 local acc = {} local fname = string.format("%s/%d/%02d/%02d/titles",blog.basedir,when.year,when.month,when.day) if fsys.access(fname,"r") then for title in io.lines(fname) do local link = string.format("%s%d/%02d/%02d.%d",base,when.year,when.month,when.day,when.part) table.insert(acc,string.format("=> %s %s\n",link,deent:match(title))) when.part = when.part + 1 end end return acc end -- *********************************************************************** -- usage: collect_month(when) -- desc: Create gopher links for a month's worth of entries -- input: acc (table) table for accumulating links -- input: when (table) -- * year -- * month -- * day -- *********************************************************************** local function collect_month(base,when) when.day = 1 local acc = {} local d = os.time(when) table.insert(acc,os.date("%B, %Y\n",d)) local maxday = date.daysinmonth(when) for day = 1 , maxday do when.day = day local posts = collect_day(base,when) if #posts > 0 then local title = string.format("%d/%02d/%02d",when.year,when.month,when.day) local link = string.format("%s%d/%02d/%02d",base,when.year,when.month,when.day) table.insert(acc,string.format("=> %s %s\n",link,title)) for _,post in ipairs(posts) do table.insert(acc,post) end end end return 20,'text/gemini',table.concat(acc) end -- *********************************************************************** -- LPEG code to parse a request. tumber() will parse the request and return -- a table with the following fields: -- -- * year - year of request -- * month - month of request -- * day - day of request -- * part - part of day -- * file - file reference -- * unit - one of 'none', 'year', 'month' , 'day' , 'part' , 'file' -- indicating how much of a request was made. -- *********************************************************************** local Ct = lpeg.Ct local Cg = lpeg.Cg local Cc = lpeg.Cc local P = lpeg.P local file = P"/" * Cg(P(1)^1,"file") * Cg(Cc('file'), "unit") local part = P"." * Cg(number,"part") * Cg(Cc('part'), "unit") local day = P"/" * Cg(number,"day") * Cg(Cc('day'), "unit") local month = P"/" * Cg(number,"month") * Cg(Cc('month'),"unit") local year = Cg(number,"year") * Cg(Cc('year'), "unit") local redir = P"/" * Cg(Cc(true),'redirect') local tumbler = Ct( year * month * day * file + year * month * day * part + year * month * day * redir^-1 + year * month * redir^-1 + year * redir^-1 + Cg(Cc('none'),"unit") ) * P(-1) -- *********************************************************************** -- usage: links = display(request) -- desc: Return a list of gopher links for a given request -- input: request (string) requested entry/ies -- return: links (array) array of gopher links -- *********************************************************************** local function display_part(base,what,fpath) local f = io.open(blog.basedir .. fpath,"r") if not f then return 51,MSG[51],"" end local doc = html(f) f:close() local txt = format(doc,base,what,blog.affiliate) return 20,'text/gemini',txt end -- *********************************************************************** function handler(_,_,loc,match) local what = tumbler:match(match[2]) if not what then return 51,MSG[51],"" end if what.redirect then loc.path = loc.path:sub(1,-2) local u = uurl.toa(loc) return 31,u,"" end local last = read_date(".last") if what.unit == 'none' then local years = {} -- xluacheck: ignore for i = last.year , blog._first.year , -1 do table.insert(years,string.format("=> %s%04d\n",match[1],i)) end return 20,'text/gemini',table.concat(years) elseif what.unit == 'year' then local months = {} for i = 1 , 12 do if what.year == blog._first.year and i >= blog._first.month or what.year == last.year and i <= last.month or what.year > blog._first.year and what.year < last.year then table.insert(months,string.format("=> %s%04d/%02d\n",match[1],what.year,i)) end end return 20,'text/gemini',table.concat(months) elseif what.unit == 'month' then return collect_month(match[1],what) elseif what.unit == 'day' then return 20,'text/gemini',table.concat(collect_day(match[1],what)) elseif what.unit == 'part' then local fpath = string.format("/%04d/%02d/%02d/%d",what.year,what.month,what.day,what.part) local titles = get_days_titles(what) if #titles > 0 then local status,mime,data = display_part(match[1],what,fpath) if status ~= 20 then return status,mime,data else return status,mime,'# ' .. titles[what.part] .. '\n\n' .. data end else return 51,MSG[51],"" end elseif what.unit == 'file' then local fpath = string.format("/%04d/%02d/%02d/%s",what.year,what.month,what.day,what.file) if fpath:match("%.x%-html$") then return display_part(match[1],what,fpath) else local conf = { file = blog.basedir .. fpath } readfile.init(conf) return readfile.handler(conf) end else syslog('error',"Um ... what now?") return 51,MSG[51],"" end end -- *********************************************************************** -- usage: init() -- desc: Intialize the handler module -- *********************************************************************** function init(conf) local f,err = loadfile(conf.config,"t",blog) if not f then syslog('error',"%s: %s",conf.config,err) return false,err end f() blog._first = read_date(".first") return true end -- *********************************************************************** return _ENV