💾 Archived View for gemini.conman.org › extensions › mod_blog.lua captured on 2024-07-09 at 02:35:17.

View Raw

More Information

⬅️ Previous capture (2023-09-28)

🚧 View Differences

-=-=-=-=-=-=-

-- ***********************************************************************
--
-- Module to display the King James Bible
-- Copyright 2021 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
-- ***********************************************************************
-- luacheck: globals CONF tumbler init bookend read_entry entries
-- luacheck: globals filename day_titles
-- luacheck: ignore 611

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 utf8     = require "utf8"
local string   = require "string"
local math     = require "math"
local table    = require "table"

local loadfile     = loadfile
local require      = require
local setmetatable = setmetatable
local type         = type
local assert       = assert

local _ENV = {}

tumbler = require "org.conman.app.mod_blog.tumbler"
CONF    =
{
  require = setmetatable(
        {},
        {
          __index = function(s) return s end,
          __call  = function(s) return s end,
        }
  ),
}

-- ***********************************************************************

local function date_cmp(d1,d2)
  local rc
  rc = d1.year  - d2.year  if rc ~= 0 then return rc end
  rc = d1.month - d2.month if rc ~= 0 then return rc end
  rc = d1.day   - d2.day   if rc ~= 0 then return rc end
  
  return d1.part - d2.part
end

-- ***********************************************************************

local function swap_endpoints(range)
  local start = {}
  local stop  = {}
  
  start.year  = range.stop.year
  start.month = range.stop.month
  stop.year   = range.start.year
  stop.month  = range.start.month
  
  if range.ustart == 'month' then
    stop.day  = 31
    stop.part = 23
  elseif range.ustart == 'day' then
    stop.day  = range.start.day
    stop.part = 23
  elseif range.ustart == 'part' then
    stop.day  = range.start.day
    stop.part = range.start.part
  elseif range.ustart == 'year' then
    assert(false)
  end
  
  if range.ustop == 'month' then
    start.day  = 1
    start.part = 1
  elseif range.ustop == 'day' then
    start.day  = range.stop.day
    start.part = 1
  elseif range.ustop == 'part' then
    start.day  = range.stop.day
    start.part = range.stop.part
  elseif range.ustop == 'year' then
    assert(false)
  end
  
  range.start = stop
  range.stop  = start
end

-- ***********************************************************************

local function when_to_filename(w)
  return string.format("%s/%4d/%02d/%02d/%d",CONF.basedir,w.year,w.month,w.day,w.part)
end

-- ***********************************************************************

local function when_to_meta(w,meta)
  return string.format("%s/%4d/%02d/%02d/%s",CONF.basedir,w.year,w.month,w.day,meta)
end

-- ***********************************************************************

local parse_date = lpeg.Ct(
    lpeg.Cg(lpeg.R"09"^1 / math.tointeger,"year")  * lpeg.P"/"
  * lpeg.Cg(lpeg.R"09"^1 / math.tointeger,"month") * lpeg.P"/"
  * lpeg.Cg(lpeg.R"09"^1 / math.tointeger,"day")   * lpeg.P"."
  * lpeg.Cg(lpeg.R"09"^1 / math.tointeger,"part")
)

-- ***********************************************************************

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

-- ***********************************************************************

function init(config)
  config = config or os.getenv("BLOG_CONFIG")
  if not config then return end
  local f = loadfile(config,"t",CONF)
  if not f then return end
  f()
  return CONF
end

-- ***********************************************************************

function bookend(which)
  local file = io.open(CONF.basedir .. "/." .. which)
  local line = file:read("l")
  local when = parse_date:match(line)
  file:close()
  return when
end

-- ***********************************************************************

function read_entry(when)
  local function get_meta(meta)
    local f = io.open(when_to_meta(when,meta),"r")
    local l = 0
    local line
    
    if not f then
      return ""
    end
    
    repeat
      line = f:read("l")
      l    = l + 1
    until l == when.part
    f:close()
    return line and deent:match(line) or ""
  end
  
  local filename = when_to_filename(when)
  if fsys.access(filename,"r") then
    local entry     = {}
    entry.when      = { year = when.year , month = when.month , day = when.day , part = when.part }
    entry.title     = get_meta("titles")
    entry.class     = get_meta("class")
    entry.author    = get_meta("authors")
    entry.status    = get_meta("status")
    entry.adtag     = get_meta("adtag")
    entry._filename = filename
    
    local f = io.open(filename,"r")
    entry.body = f:read("a")
    f:close()
    return entry
  end
end

-- ***********************************************************************

function entries(range)
  if not range then
    local f = bookend("first")
    local l = bookend("last")
    range   = string.format("%04d/%02d/%02d.%d-%04d/%02d/%02d.%d",
                l.year,l.month,l.day,l.part,
                f.year,f.month,f.day,f.part
              )
  end
  
  if type(range) == 'string' then
    range = tumbler.new(range,bookend "first", bookend "last")
  end
  
  if date_cmp(range.start,range.stop) <= 0 then
    local function nup(state)
      if date_cmp(state.start,state.stop) > 0 then
        return
      end
      
      local entry = read_entry(state.start)
      if entry then
        state.start.part = state.start.part + 1
        return entry
      end
      
      state.start.part = 1
      state.start.day  = state.start.day + 1
      if state.start.day > date.daysinmonth(state.start) then
        state.start.day = 1
        state.start.month = state.start.month + 1
        if state.start.month == 13 then
          state.start.month = 1
          state.start.year = state.start.year + 1
        end
      end
      
      return nup(state)
    end
    
    return nup,range
    
  else
    swap_endpoints(range)
    range.start.part = range.start.part + 1
    
    local function ndown(state)
      state.start.part = state.start.part - 1
      if state.start.part == 0 then
        state.start.part = 23
        state.start.day = state.start.day - 1
        if state.start.day == 0 then
          state.start.day = 31
          state.start.month = state.start.month - 1
          if state.start.month == 0 then
            state.start.month = 12
            state.start.year = state.start.year - 1
          end
        end
      end
      
      if date_cmp(state.start,state.stop) < 0 then
        return
      end
      
      local entry = read_entry(state.start)
      if entry then
        return entry
      end
      
      return ndown(state)
    end
    
    return ndown,range
  end
end

-- ***********************************************************************

function filename(tuple)
  return string.format(
        "%s/%04d/%02d/%02d/%s",
        CONF.basedir,
        tuple.start.year,
        tuple.start.month,
        tuple.start.day,
        tuple.filename
  )
end

-- ***********************************************************************

function day_titles(when)
  local res      = {}
  local filename = when_to_meta(when,"titles")
  
  if filename and fsys.access(filename,"r") then
    for title in io.lines(filename) do
      table.insert(res,deent:match(title))
    end
  end
  
  return res
end

-- ***********************************************************************

return _ENV