-- ***********************************************************************
--
-- 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 <
http://www.gnu.org/licenses/>.
--
-- Comments, questions and criticisms can be sent to:
[email protected]
--
-- =======================================================================
--
-- 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