SMOLNET PORTAL home about changes
-- ***********************************************************************
--
-- 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: 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
.
Response: text/plain
Original URLgopher://gopher.conman.org/0Obsolete%3AGopher%3ASrc%3Ablog.lua
Content-Typetext/plain; charset=utf-8