-- *********************************************************************** -- -- Copyright 2016 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 . -- -- Comments, questions and criticisms can be sent to: sean@conman.org -- -- ======================================================================= -- -- Code to handle blog requests. -- -- *********************************************************************** -- luacheck: globals config display last_link init -- luacheck: ignore 611 local exit = require "org.conman.const.exit" local syslog = require "org.conman.syslog" local process = require "org.conman.process" local fsys = require "org.conman.fsys" local date = require "org.conman.date" local lpeg = require "lpeg" local io = require "io" local os = require "os" local table = require "table" local string = require "string" local setfenv = setfenv local require = require local loadfile = loadfile local tonumber = tonumber local tostring = tostring local ipairs = ipairs local config = config local _VERSION = _VERSION local blog = { require = require } if _VERSION == "Lua 5.1" then module("blog") else _ENV = {} 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) local f = io.open(fname,"r") local d = f:read("*l") 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("%d/%02d/%02d/titles",when.year,when.month,when.day) if fsys.access(fname,"r") then for title in io.lines(fname) do table.insert(res,title) end end return res end -- *********************************************************************** -- usage: collect_day(formats,when) -- desc: Create gopher links for a day's entry -- input: formats (function array) functions required: -- * INFO - format a INFO line -- * FILE - format a FILE line -- * DIR - format a DIR line -- * ERROR - format an ERROR line -- * HTML - format an HTML line -- when (table) -- * year -- * month -- * day -- *********************************************************************** local function collect_day(formats,when) local acc = {} when.part = 1 local fname = string.format("%d/%02d/%02d/titles",when.year,when.month,when.day) if fsys.access(fname,"r") then for title in io.lines(fname) do local link = string.format("Phlog:%d/%02d/%02d.%d",when.year,when.month,when.day,when.part) table.insert(acc,{ formats.FILE , title , link }) when.part = when.part + 1 end end return acc end -- *********************************************************************** -- usage: collect_month(acc,formats,when) -- desc: Create gopher links for a month's worth of entries -- input: acc (table) table for accumulating links -- formats (function array) functions required: -- * INFO - format a INFO line -- * FILE - format a FILE line -- * DIR - format a DIR line -- * ERROR - format an ERROR line -- * HTML - format an HTML line -- when (table) -- * year -- * month -- * day -- *********************************************************************** local function collect_month(acc,formats,when) when.day = 1 local d = os.time(when) table.insert(acc, { formats.INFO , os.date("%B, %Y",d) }) local maxday = date.daysinmonth(when) for day = 1 , maxday do when.day = day local posts = collect_day(formats,when) if #posts > 0 then local title = string.format("%d/%02d/%02d",when.year,when.month,when.day) local link = string.format("Phlog:%d/%02d/%02d",when.year,when.month,when.day) table.insert(acc,{ formats.DIR , title , link }) for _,post in ipairs(posts) do table.insert(acc,post) end end end 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 R = lpeg.R local P = lpeg.P local eos = P(-1) local file = P"/" * Cg(P(1)^0,"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 tumbler = Ct( year * month * day * file * eos + year * month * day * part * eos + year * month * day * eos + year * month * P"/"^-1 * eos + year * P"/"^-1 * eos + Cg(Cc('none'),"unit") * eos ) -- *********************************************************************** -- usage: links = display(formats,request) -- desc: Return a list of gopher links for a given request -- input: formats (function array) functions required: -- formats (function array) functions required: -- * INFO - format a INFO line -- * FILE - format a FILE line -- * DIR - format a DIR line -- * ERROR - format an ERROR line -- * HTML - format an HTML line -- request (string) requested entry/ies -- return: links (array) array of gopher links -- *********************************************************************** -- ----------------------------------------------------------------- -- I'm using Lynx to generate the page view, and since I'm -- referencing the file directly, any local links get a file: URL, -- which needs to change. I have the information to do that, but -- only when the blog configuration file is read in (because of the -- way LPeg works). So this is a forware reference to the code to -- fix the links, which is defined in the init() method below. -- ----------------------------------------------------------------- local fix_local_links function display(formats,request) local what = tumbler:match(request) if not what then return { { formats.ERROR , "Not found" , request } } end if what.unit == 'none' then local first = read_date(".first") local last = read_date(".last") local year = {} -- luacheck: ignore for i = last.year , first.year , -1 do table.insert(year,{ formats.DIR , tostring(i) , "Phlog:" .. i}) end return year elseif what.unit == 'year' then local first = read_date(".first") local last = read_date(".last") local months = {} local when = { year = 1999 , month = 1 , day = 1 } for i = 1 , 12 do if what.year == first.year and i >= first.month or what.year == last.year and i <= last.month or what.year > first.year and what.year < last.year then when.month = i local d = os.time(when) table.insert( months, { formats.DIR , os.date("%B",d) , string.format("Phlog:%d/%02d",what.year,i) } ) end end return months elseif what.unit == 'month' then local days = {} collect_month(days,formats,what) return days elseif what.unit == 'day' then return collect_day(formats,what) elseif what.unit == 'part' then local titles = get_days_titles(what) if #titles > 0 then local cmd = string.format( "lynx -assume_local_charset=UTF-8 -assume_charset=UTF-8 -assume_unrec_charset=UTF-8 -force_html -dump %d/%02d/%02d/%d", -- luacheck: ignore what.year, what.month, what.day, what.part ) local lynx = io.popen(cmd,"r") local data = lynx:read("*a") lynx:close() data = fix_local_links:match(data) return titles[what.part] .. "\n" .. data else return "[Apparently, there's nothing here. ---Editor]" end elseif what.unit == 'file' then local filename = string.format("%d/%02d/%02d/%s", what.year, what.month, what.day, what.file) if what.file:match "%.gif$" or what.file:match "%.jpg$" or what.file:match "%.png$" then local f,err = io.open(filename,"rb") if f then return f,false else return nil,err end else local f,err = io.open(filename,"r") if f then return f,true else return nil,err end end else syslog('error',"Um ... what now?") return "[Well, this is unexpected!]" end end -- *********************************************************************** -- usage: link = last_link() -- desc: return a gopher link for the latest blog entry -- return: link (string) gopher link -- *********************************************************************** function last_link() local last = read_date(".last") local link = string.format( "Phlog:%d/%02d/%02d.%d", last.year, last.month, last.day, last.part ) return link end -- *********************************************************************** local format_unit = { year = function(t) return string.format( "%s1Phlog:%d\r\n %s%d", config.url, t.year, blog.url, t.year ) end, month = function(t) return string.format( "%s1Phlog:%d/%02d\r\n %s%d/%02d", config.url, t.year, t.month, blog.url, t.year, t.month ) end, day = function(t) return string.format( "%s1Phlog:%d/%02d/%02d\r\n %s%d/%02d/%02d", config.url, t.year, t.month, t.day, blog.url, t.year, t.month, t.day ) end, part = function(t) return string.format( "%s0Phlog:%d/%02d/%02d.%d\r\n %s%d/%02d/%02d.%d", config.url, t.year, t.month, t.day, t.part, blog.url, t.year, t.month, t.day, t.part ) end, file = function(t) local st if t.file:match "%.gif$" or t.file:match "%.jpg$" or t.file:match "%.png$" then st = 'I' else st = '0' end return string.format( "%s%sPhlog:%d/%02d/%02d/%s\r\n %s%d/%02d/%02d/%s", config.url, st, t.year, t.month, t.day, t.file, blog.url, t.year, t.month, t.day, t.file ) end, } -- *********************************************************************** local function affiliates(list) local pattern = P(false) for _,scheme in ipairs(list) do pattern = pattern + P(scheme.proto) * P":" * lpeg.C(R("!!","#~")^1) / function(c) return string.format(scheme.link,c) end end return pattern end -- *********************************************************************** -- usage: init() -- desc: Intialize the handler module -- *********************************************************************** function init() local f,err = loadfile(config.blog,"t",blog) if not f then syslog('critical',"%s: %s",config.blog,err) process.exit(exit.CONFIG) end if _VERSION == "Lua 5.1" then setfenv(f,blog) end f() fsys.chdir(blog.basedir) -- ------------------------------------------------------------------ -- I'm using Lynx to format the entries. For local links, they come out -- looking like: -- -- file://localhost/home/spc/web/boston/journal/1999/12/15/1999/12/15.2 -- or -- file://localhost/home/spc/web/boston/journal/1999/12/15/code.txt -- -- This rather complicated looking LPeg expression does a substitution -- capture, transforming the above links to: -- -- 3. gopher://lucy.roswell.area51:7070/0Phlog:1999/12/15.2 -- http://boston.roswell.area51/1999/12/15.2 -- or -- -- 4. gopher://lucy.roswell.area51:7070/0Phlog:1999/12/15/code.txt -- http://boston.roswell.area51/1999/12/15/code.txt -- -- The first portion does #3, the next portion #4 and the final -- portion (one line) just keeps the data flowing. -- ------------------------------------------------------------------ fix_local_links = lpeg.Cs(( -- first portion ( lpeg.C(P"file://localhost" * P(blog.basedir) * P"/" * R"09"^1 * P"/" * R"09"^1 * P"/" * R"09"^1 * P"/") * Ct( Cg(Cc('none'),"unit") * Cg(R"09"^1,"year") * Cg(Cc('year'),'unit') * (P"/" * Cg(R"09"^1,"month") * Cg(Cc('month'),'unit') * (P"/" * Cg(R"09"^1,"day") * Cg(Cc('day'),'unit') * ( P"." * Cg(R"09"^1,'part') * Cg(Cc('part'),'unit') + P"/" * Cg(R"!~"^1,'file') * Cg(Cc('file'),'unit') )^-1)^-1)^-1 )) / function(_,d) return format_unit[d.unit](d) end + P"file://localhost" -- second portion * P(blog.basedir) * P"/" * Ct( Cg(R"09"^1,'year') * P"/" * Cg(R"09"^1,'month') * P"/" * Cg(R"09"^1,'day') * P"/" * Cg(R"!~"^1,'file') * Cg(Cc('file'),'unit') ) / function(d) return format_unit[d.unit](d) end + affiliates(blog.affiliate) + P(1) -- last portion )^1) end -- *********************************************************************** if _VERSION >= "Lua 5.2" then return _ENV end .