#!/bin/sh
_rem=--[=[
# POSIX shell wrapper to call correct version of Lua or Lunacy

LUNACY=""
if command -v lunacy64 >/dev/null 2>&1 ; then
  LUNACY=lunacy64
elif command -v lua5.1 >/dev/null 2>&1 ; then
  LUNACY=lua5.1
elif command -v lua-5.1 >/dev/null 2>&1 ; then
  LUNACY=lua-5.1
elif command -v lunacy >/dev/null 2>&1 ; then
  LUNACY=lunacy
fi
if [ -z "$LUNACY" ] ; then
  echo Please install Lunacy or Lua 5.1
  echo Either the version included with MaraDNS -or- the version at
  echo https://github.com/samboy/lunacy
  echo To compile and install the version of Lunacy with MaraDNS:
  echo
  echo     cd MaraDNS/coLunacyDNS/lunacy
  echo     make
  echo     sudo cp lunacy /usr/local/bin/
  exit 1
fi

exec $LUNACY $0 "$@"

# ]=]1
-- This script is written in Lua 5.1

-- This script has been donated to the public domain in 2022 by Sam Trenholme
-- If, for some reason, a public domain declation is not acceptable, it
-- may be licensed under the following terms:

-- Copyright 2022 Sam Trenholme
-- Permission to use, copy, modify, and/or distribute this software for
-- any purpose with or without fee is hereby granted.
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
-- OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-- ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-- ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

-- Convert an ej-formatted doc in to a *ROFF man page (an macro)
-- Input: First argument or standard input
-- Output: Standard output

-- mc() converts a string in to a case insensitive regex; mc('th') is [Tt][Hh]
function mc(i) 
  local out = ""
  local afterPercent = false
  for a=1,i:len() do
    seek = i:sub(a,a) 
    -- Do not mangle anything right after a %
    if seek == "%" and not afterPercent then
      out = out .. seek
      afterPercent = true
    -- Only letters not after a % get mangled
    elseif seek:find("%a") and not afterPercent then 
      out = out .. "[" .. seek:upper() .. seek:lower() .. "]"
    -- Other stuff is copied as is
    elseif seek and not afterPercent then
      out = out .. seek
    -- Everything right after a % is passed as-is
    elseif afterPercent then
      out = out .. seek
      afterPercent = false
    end
  end
  return out
end

-- Get a string representing today’s date
function get_timestamp_string()
  local timestamp = "Time unknown"
  if lunacy then
    local year, mon, day = lunacy.today()
    if year then
      timestamp = string.format("%d-%02d-%02d",year, mon, day)
    else
      -- lunacy.today() returns nil if time_t is 32-bit
      -- Linux has had 64-bit time support on 32-bit systems since 2020
      -- Alpine Linux, for example, has a 64-bit time_t on 32-bit x86
      -- Another option is to patch the Lunacy source using the
      -- code at https://github.com/evalEmpire/y2038 and making a
      -- non-portable syscall() to get the undelying 64-bit timestamp
      timestamp = "If your time_t is 32-bit, please upgrade"
    end
  elseif os.date then
    local a = os.date("*t")
    local year = a.year
    local mon = a.month
    local day = a.day
    timestamp = string.format("%d-%02d-%02d",year, mon, day)
  elseif os.time then
    timestamp = string.format("Unix timestamp %d",os.time())
  end
  return timestamp
end

-- Count the number of UTF-8 codepoints in a string.  This function
-- is used to determine how wide a given string will be on the terminal.
-- The correct way to determine terminal string width is to get the
-- toolkit at https://github.com/unicode-org/icu then use
-- u_getIntPropertyValue() or to use the functions at
-- https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c, which I have mirrored
-- at https://github.com/samboy/wcwidth/ 
-- The way I do it is to assume that all UTF-8 code points have the
-- same width.  If this isn’t true for your language, put <NOFMT> in
-- the head of the document and use a version of `fmt` that uses a
-- proper Unicode library.
function UTF8count(from)
  local count = 0
  if not from:find("[\128-\255]") then return from:len() end  
  from = from:gsub("[\194-\223][\128-\191]","@")
  from = from:gsub("[\224-\239][\128-\191][\128-\191]","#")
  from = from:gsub("[\240-\247][\128-\191][\128-\191][\128-\191]","$")
  return from:len()
end
-- Add linebreaks to a long string to make it look nice on a 80-column
-- (or whatever) terminal
-- Input: The string we will add newlines to (from)
--        What we will put on lines after adding newlines (this is
--        so we can use this with lists and have the lines all start
--        to the right of the bullet) (prefix)
--        The maximum length of a line before adding a newline (should
--        there be a word with space longer than len, it will not be
--        broken up)
-- Output: The string with newlines added
NoFmt = false -- This will be true for non-LGC languages
function fmt(from, prefix, len)
  if NoFmt then return from end -- NOFMT option for non-LGC languages
  if not prefix then prefix = "" end -- Usually for bullet lists
  if not len then len = 72 end
  local out = ""
  local index = 1
  local thisLineStart = 1
  local thisLineLength = 0
  while index < from:len() do
    lastIndex = index
    index = from:find("%s",index + 1)
    if not index then
      return out .. from:sub(thisLineStart,-1) .. "\n"
    end 
    thisLineLength = thisLineLength + UTF8count(from:sub(lastIndex,index-1))
    if from:sub(index,index) == "\n" then 
      out = out .. from:sub(thisLineStart,index)
      thisLineStart = index + 1
      thisLineLength = 0
    end
    if thisLineLength > len then
      thisLineLength = UTF8count(from:sub(lastIndex,index-1))
      out = out .. from:sub(thisLineStart,lastIndex) .. "\n" .. prefix
      if thisLineStart == 1 then len = len - prefix:len() end -- Bullet lists
      thisLineStart = lastIndex + 1
    end
  end
  return out .. from:sub(thisLineStart,-1) .. "\n"
end
----- END FUNCTIONS -----

-- If they give a filename as an argument try to open that file
if arg[1] then 
 fh = io.open(arg[1],"rb")
 if not fh then
   print("Error opening file " .. arg[1])
   os.exit(1)
 end
 io.input(fh)
 fhSave = fh
else
 fhSave = io.stdin
end
inInclude = false

-- Read the file, making it a single line, unless we’re in a <PRE>
-- tag

out = ""
l = io.read()
inPre = false
while l do

  l = l:gsub(mc('<hinclude[^>]*>[ \t]*'),"") -- HINCLUDE used only by ej2html
  -- handle <include "filename"> (doesn’t nest)
  if l:find(mc('<include%s+"')) and not inInclude then
    includeFileName=l:gsub(mc('.*<include%s+"([^"]+)".*'),"%1")
    l=l:gsub(mc('<include%s+"([^"]+)"%s*>'),"")
    fh = io.open(includeFileName,"rb")
    if not fh then
      print("Error opening file " .. includeFileName)
      os.exit(1)
    end
    io.input(fh)
    inInclude = true
  end

  -- Grab input file line by line, make it one long line
  out = out .. l .. " "
  if l:find(mc("<pre>")) then
    inPre = true
  elseif l:find(mc("</pre>")) then
    inPre = false
  end
  if inPre then 
    out = out .. "\n"
  end
  l = io.read()

  -- Handle end of included filename (go back to parent)
  if not l and inInclude then
    inInclude = false
    io.input(fhSave)
    l = io.read()
  end

end

-- Collapse white space except in <pre> blocks
start = out:find(mc("<pre>"))
lastEnd = 1
while start do
  local top = ""
  if lastEnd > 1 then top = out:sub(1,lastEnd-1) end
  -- Extract the part of the document we will alter
  local toAlter = out:sub(lastEnd,start + 4)
  local initial = toAlter:len()

  -- Get rid of multiple spaces; nroff (unlike EJ) honors them
  toAlter = toAlter:gsub("%s+"," ")
  toAlter = toAlter:gsub("\n(%s*)\n","\n")

  -- Now that we have collapsed whitespace in a non-PRE block
  -- stitch that altered block with the rest of the document again
  local offset = toAlter:len() - initial
  local preend = out:find(mc("</pre>"),start + 1)
  local mid = out:sub(start + 5,preend - 1)
  mid = mid:gsub("<","\192") -- Allow HTML in PRE, 192 never in valid UTF-8
  mid = mid:gsub(">","\193") -- Allow HTML in PRE, 193 never in valid UTF-8
  local tail = out:sub(preend)
  out = top .. toAlter .. mid .. tail
  -- Now, see if there’s another PRE block
  lastEnd = preend + offset
  start = out:find(mc("<pre>"),preend + offset)
end
local top = out:sub(1,lastEnd)
local tail = out:sub(lastEnd + 1)
-- We need to alter the document after the final PRE block
tail = tail:gsub("%s+"," ")
tail = tail:gsub("\n(%s*)\n","\n")
out = top .. tail

-- Step two: Now that the file is a single line (except for PRE blocks), 
-- process EJ (HTML) tags
if out:find(mc("<nofmt>.*<body>")) then NoFmt = true end
out = out:gsub(mc("<nofmt>"),"")

if out:find(mc("<dtwidth>([^<]*)</dtwidth>")) then
  DTROFF = out:gsub(mc(".*<dtwidth>([^<]*)</dtwidth>.*"),".TP %1")
else
  DTROFF = ".TP 4"
end

-- Create heading for man page
print('.\\" Do *not* edit this file; it was automatically generated by ej2man')
print('.\\" Look for a name.ej file with the same name as this filename')
print('.\\"')
print('.\\" Process this file with the following (replace filename.1)')
print('.\\" ' .. "preconv < filename.1 | nroff -man -Tutf8")
print('.\\"')
print('.\\" Last updated ' .. get_timestamp_string())
print('.\\"')
-- The TH heading is placed here
TH = nil
if out:find(mc('.*<th>([^<]*)</th>.*')) then
  TH = out:gsub(mc('.*<th>([^<]*)</th>.*'),'%1')
end
if TH then 
  print(".TH " .. TH)
else
  print(".TH ")
end
print('.\\" We don\'t want hyphenation (it\'s too ugly)')
print('.\\" We also disable justification when using nroff')
print('.\\" Due to the way the -mandoc macro works, this needs to be placed')
print('.\\" after the .TH heading')
print(".hy 0")
print(".if n .na")
print('.\\"')
print('.\\" We need the following stuff so that we can have single quotes')
print('.\\" In both groff and other UNIX *roff processors')
print('.if \\n(.g .mso www.tmac')
print('.ds aq \\(aq')
print('.if !\\n(.g .if \'\\(aq\'\' .ds aq \\\'')
print("")

out = out:gsub("<!%-%-.-%-%->","") -- Remove HTML comments
out=out:gsub(mc('<head>.*</head>'),"") -- Scrub HEAD section
out=out:gsub(mc('</?body>'),"") -- Scrub BODY tag

-- The old behavior from the early 2000s to have no non-ASCII in man
-- pages no longer applies here in 2022.  Now we have `preconv` and
-- the man program which comes with Ubuntu 22.04 uses it.
--out=out:gsub(mc('<hibit alt=%"([^"]*)%">[^<]*</hibit>'),"%1") -- HIBIT (old)
out=out:gsub(mc('<hibit[^>]*>([^<]*)</hibit>'),"%1") -- HIBIT (2022)

out=out:gsub(mc("</?hibit[^>]*>"),"") -- HIBIT part 2
out=out:gsub("\\","\\\\") -- escape backslashes
out=out:gsub("'",'\\(aq') -- escape quotes
out=out:gsub(mc("<pre>[ \t]*"),"\n\n.nf\n"); -- PRE opener
out=out:gsub(mc("</pre>%s*"),"\n.fi\n\n"); -- PRE closer
out=out:gsub(mc('&nbsp;'),' ') -- &nbsp; becomes literal space
out=out:gsub(mc("<h1>([^<]*)</h1>%s*"),'\n.SH "%1"\n.PP\n') -- H1

-- H2 tag
-- No quotes in H2 sections
while out:find(mc("<h2>([^<]*)[\'\"]([^<]*)</h2>")) do
  out = out:gsub(mc("<h2>([^<]*)[\'\"]([^<]*)</h2>"),"<h2>%1%2</h2>")
end
out=out:gsub(mc("<h2>([^<]*)</h2>%s*"),'\n.PP\n.in -3\n\\fB%1\\fR\n.PP\n')

out=out:gsub(mc("</?a[^<]+>"),"") -- A tag part 1
out=out:gsub(mc("</?a>"),"") -- A tag part 2
out=out:gsub(mc("</?tt>"),"") -- TT tag
out=out:gsub(mc("<hr>"),'\n.PP\n.RS 28\n* * *\n.RE\n.PP\n') -- HR tag
out=out:gsub(mc("<hinclude[^>]+>"),"") -- HINCLUDE non-HTML tag
out=out:gsub(mc("<blockquote>"),'\n.PP\n.RS 4\n') -- BLOCKQUOTE open
out=out:gsub(mc("</blockquote>"),'\n.RE\n.PP\n') -- BLOCKQUOTE close

-- The B tag
while out:find(mc("<b>([^<]*)[\'\"]([^<]*)</b>")) do
  out = out:gsub(mc("<b>([^<]*)[\'\"]([^<]*)</b>"),"<b>%1%2</b>")
end
out=out:gsub(mc("<b>([^<]*)</b>([^%s\'\"]+)"),'\n.BR "%1" "%2"\n')
out=out:gsub(mc("<b>([^<]*)</b>%s*"),'\n.B "%1"\n')

-- The I tag
while out:find(mc("<i>([^<]*)[\'\"]([^<]*)</i>")) do
  out = out:gsub(mc("<i>([^<]*)[\'\"]([^<]*)</i>"),"<i>%1%2</i>")
end
out=out:gsub(mc("<i>([^<]*)</i>([^%s\"\']+)"),'\n.IR "%1" "%2"\n')
out=out:gsub(mc("<i>([^<]*)</i>%s*"),'\n.I "%1"\n')

-- The P tag
out=out:gsub(mc("<p>%s*"),'\n.PP\n')

out=out:gsub("<[DdOoUu][Ll]>","") -- DL, OL, and UL tags removed
out=out:gsub(mc("<li>\n?"),'\n.TP 2\n*\n') -- The LI tag
out=out:gsub(mc("<dt>([^<]*)<dd>\n?"),'\n' .. DTROFF .. '\n%1\n') -- DT tag
out=out:gsub("</[Dd][TtDd]>","") -- Remove closing DT and DD tags
out=out:gsub("</[DdOoUu][Ll]>",'\n.PP\n') -- /UL, /OL, and /DL not ignored
out=out:gsub("\n+(\n%.[TP]P)","%1") -- Zap empty lines before .TP or .PP roff
out=out:gsub("(\n%.RE)","\n%1") -- Make RE tags look nice
-- Get rid of empty lines after a .RE flag; this does not look nice
out=out:gsub("(\n%.RE[^\n]+\n)\n+","%1") 
-- Put a newline before the .in flag; this looks nicer
out=out:gsub("(\n%.in)","\n%1")
-- Get rid of empty lines after a .TP or .PP flag; this never looks nice
out=out:gsub("(\n%,[TP]P[^\n]+\n)\n+","%1")
-- Tables, used by csv1.ej
out=out:gsub(mc("<table>"),'\n.ta +8 +8 +8\n')
out=out:gsub(mc("<td>"),'\t')
out=out:gsub('<[TtBb][Rr]>\n?','\n.br\n')
out=out:gsub(mc("</table>"),"")
out=out:gsub("\n+\n.RE\n.PP",'\n.RE\n.PP\n') -- Remove lines at BLOCKQUOTE end

-- fmt: Add line breaks to fit on an 80-column terminal
-- Do not fmt pre sections, fmt everything else
start = out:find("\n.nf\n")
lastEnd = 1
while start do
  local top = ""
  if lastEnd > 1 then top = out:sub(1,lastEnd-1) end
  local toFmt = out:sub(lastEnd,start - 1)
  local initial = toFmt:len()
  toFmt=toFmt:gsub("\n[ \t]+","\n") -- Remove leading space; confuses nroff
  toFmt=toFmt:gsub(mc("&lt;"),"<") -- HTML entities only outside PRE blocks
  toFmt=toFmt:gsub(mc("&gt;"),">") -- HTML entities only outside PRE blocks
  toFmt=toFmt:gsub(mc("&amp;"),"&") -- HTML entities only outside PRE blocks
  toFmt = fmt(toFmt)
  local offset = toFmt:len() - initial
  local preend = out:find("\n.fi\n",start + 1)
  local mid = out:sub(start,preend)
  local tail = out:sub(preend + 1)
  out = top .. toFmt .. mid .. tail
  lastEnd = preend + offset
  start = out:find("\n.nf\n",preend + offset)
end
local top = out:sub(1,lastEnd)
local tail = out:sub(lastEnd + 1)
tail=tail:gsub("\n[ \t]+","\n") -- Remove leading space; confuses nroff
tail=tail:gsub(mc("&lt;"),"<") -- HTML entities only outside PRE blocks
tail=tail:gsub(mc("&gt;"),">") -- HTML entities only outside PRE blocks
tail=tail:gsub(mc("&amp;"),"&") -- HTML entities only outside PRE blocks
tail=fmt(tail)
out = top .. tail
-- Remove blank lines at top and bottom of <pre> blocks
out = out:gsub('\n.nf\n+','\n.nf\n')
out = out:gsub('\n+.fi\n','\n.fi\n')

out = out:gsub("[ ]*\n[ ]*\n[ ]*\n[ ]*\n[ ]*\n","\n\n")
out = out:gsub("[ ]*\n[ ]*\n[ ]*\n[ ]*\n","\n\n")
out = out:gsub("[ ]*\n[ ]*\n[ ]*\n","\n\n")
out = out:gsub("\n\n\n\n\n\n\n","\n\n")
out = out:gsub("\n\n\n\n\n\n","\n\n")
out = out:gsub("\n\n\n\n\n","\n\n")
out = out:gsub("\n\n\n\n","\n\n")
out = out:gsub("\n\n\n","\n\n")
out = out:gsub("([^\n])\n%.nf","%1\n\n.nf") -- <pre> fix 
out = out:gsub("\n%.fi\n([^\n])","\n.fi\n\n%1") -- </pre> fix 1
out = out:gsub("\n%.fi\n\n%.","\n.fi\n.") -- </pre> fix 2
out = out:gsub("\192","<") -- Allow HTML in PRE, 192 never in valid UTF-8
out = out:gsub("\193",">") -- Allow HTML in PRE, 193 never in valid UTF-8

print(out)
