💾 Archived View for gemini.conman.org › extensions › GLV-1 › handlers › bible.lua captured on 2023-11-14 at 10:06:34.

View Raw

More Information

⬅️ Previous capture (2022-06-04)

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

-- ************************************************************************
--
--    Bible module
--    Copyright 2019 by Sean Conner.  All Rights Reserved.
--
--    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 init fini handler
-- luacheck: ignore 611

local soundex   = require "org.conman.parsers.soundex"
local metaphone = require "org.conman.string".metaphone
local wrapt     = require "org.conman.string".wrapt
local uurl      = require "GLV-1.url-util"
local lpeg      = require "lpeg"
local io        = require "io"
local math      = require "math"
local string    = require "string"
local table     = require "table"

local tonumber = tonumber

local CONF
local ABBR      = {}
local BOOKS     = {}
local SOUNDEX   = {}
local METAPHONE = {}

_ENV = {}

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

local bible_parse do
  local Cb = lpeg.Cb
  local Cc = lpeg.Cc
  local Cg = lpeg.Cg
  local Ct = lpeg.Ct
  local P  = lpeg.P
  local R  = lpeg.R
  
  -- G                  G.1:1-999:999
  -- G.a                G.a:1-a:999
  -- G.a:b              G.a:b-a:b
  -- G.a:b-x            G.a:b-a:x
  -- G.a-c              G.a:1-c:999
  -- G.a-c:d            G.a:1-c:d
  -- G.a:b-c:d          G.a:b-c:d
  
  local num   = R"09"^1 / tonumber
  
  local book  = Cg(R("AZ","az","09")^1,'book')
              * Cg(Cc(  1),'cb') * Cg(Cc(  1),'vb') -- starting chapter/book
              * Cg(Cc(999),'ce') * Cg(Cc(999),'ve') -- ending chapter/book
              
  local start = P"." * Cg(num,'cb')
              * Cg(Cc(true),'fcb')
              * Cg(Cb'cb','ce')
              * (
                  P":" * Cg(num,'vb') * Cg(Cc(true),'fvb')
                  * (
                      (#(P"-" * R"09"^1 * P(-1)) * P"-" * Cg(num,'ve'))
                      + Cg(Cb'vb','ve')
                    )
                )^-1
                
  local stop  =  P"-" * Cg(num,'ce') * Cg(Cc(true),'fce')
              * (P":" * Cg(num,'ve') * Cg(Cc(true),'fve'))^-1
              
  bible_parse = Ct(book * (start * stop^-1)^-1) * P(-1)
end

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

function init(conf)
  local entry = lpeg.C(lpeg.R("AZ","az","09")^1)
              * lpeg.S" \t"^0 * lpeg.P"," * lpeg.S" \t"^0
              * lpeg.C(lpeg.R("AZ","az","09")^1)
              
  for line in io.lines(conf.books) do
    local abbr,book = entry:match(line)
    local s         = soundex:match(book)
    local m         = metaphone(book)
    ABBR[abbr]      = book
    BOOKS[book]     = true
    SOUNDEX[s]      = book
    METAPHONE[m]    = book
  end
  
  CONF = conf
  return true
end

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

local function bible_request(query)
  local r = bible_parse:match(query)
  if not r then return end
  
  if ABBR[r.book] and ABBR[r.book] ~= r.book then
    r.book = ABBR[r.book]
    return r,true
  end
  
  if not BOOKS[r.book] then
    local s = soundex:match(r.book)
    if SOUNDEX[s] then
      r.book = SOUNDEX[s]
      return r,true
    end
    
    local m = metaphone(r.book)
    if METAPHONE[m] then
      r.book = METAPHONE[m]
      return r,true
    end
  else
    return r,false
  end
end

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

function handler(conf,_,loc,pathinfo,ios)

  local function redirect_here(u,book)
    local selector = { book.book }
    
    if book.fcb then
      table.insert(selector,string.format(".%d",book.cb))
    end
    if book.fvb then
      table.insert(selector,string.format(":%d",book.vb))
    end
    if book.fce then
      table.insert(selector,string.format("-%d",book.ce))
    end
    if book.fve then
      table.insert(selector,string.format(":%d",book.ve))
    end
    
    return uurl.merge(u,{ path = table.concat(selector) })
  end
  
  -- ================================================
  
  if pathinfo == "" then
    loc.path = loc.path .. "/"
    ios:write("31 ",uurl.toa(loc),"\r\n")
    return 31
  end
  
  if not pathinfo:match "^/" then
    ios:write("51\r\n")
    return 51
  end
  
  if pathinfo == "/" then
    ios:write("31 ",conf.index,"\r\n")
    return 31
  end
  
  pathinfo = pathinfo:sub(2,-1)
  local r,redirect = bible_request(pathinfo)
  
  if not r then
    ios:write("51\r\n")
    return 51
  end
  
  if redirect then
    local here = redirect_here(loc,r)
    ios:write("31 ",uurl.toa(here),"\r\n")
    return 31
  end
  
  -- ================================================
  
  local function write(fmt,...)
    ios:write(string.format(fmt,...),"\r\n")
  end
  
  -- ================================================
  
  local function readint(file)
    local c = file:read(1) -- trigger EOF
    if c then
      return c:byte()
           + file:read(1):byte() * 2^8
           + file:read(1):byte() * 2^16
           + file:read(1):byte() * 2^24
    end
  end
  
  -- ================================================
  
  local function show_chapter(chapter,low,high)
    local index = io.open(string.format("%s/%s/%d.index",CONF.verses,r.book,chapter),"rb")
    if not index then return end
    local verse = io.open(string.format("%s/%s/%d",CONF.verses,r.book,chapter),"r")
    local max   = math.min(high,readint(index))
    
    high = math.min(high,max)
    
    index:seek('set',low * 4)
    local start = readint(index)
    verse:seek('set',start)
    
    local hdr = string.format("Chapter %d",chapter)
    write("\n%s%s\n\n",string.rep(" ",40 - #hdr // 2),hdr)
    
    for v = low , high do
      local stop = readint(index)
      local len  = stop - start
      local text = verse:read(len)
      local wt   = wrapt(text,60)
      start      = stop
      write("%3d. %s\n",v,table.concat(wt,"\n     "))
    end
    
    index:close()
    verse:close()
  end
  
  -- ================================================
  
  ios:write("20 text/plain\r\n")
  write("%s%s\n",string.rep(" ",40 - #r.book // 2),r.book)
  
  for chapter = r.cb , r.ce do
    local vb
    local ve
    
    if chapter == r.cb then
      vb = r.vb
    else
      vb = 1
    end
    
    if chapter == r.ce then
      ve = r.ve
    else
      ve = 999
    end
    
    show_chapter(chapter,vb,ve)
  end
  
  return 20
end

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

return _ENV