From 72e13dff9ff4fde91b84054167da91a5d27cb952 Mon Sep 17 00:00:00 2001
From: sanine <sanine.not@pm.me>
Date: Tue, 4 Jan 2022 22:28:43 -0600
Subject: add working rss:

---
 src/lua-script/cify.lua   |   2 +
 src/lua-script/script.h   | 204 ++++++++++++++++++++++++++++++++++++++++++++--
 src/lua-script/script.lua | 204 ++++++++++++++++++++++++++++++++++++++++++++--
 3 files changed, 394 insertions(+), 16 deletions(-)

(limited to 'src')

diff --git a/src/lua-script/cify.lua b/src/lua-script/cify.lua
index a2fbcbe..1c6a16b 100755
--- a/src/lua-script/cify.lua
+++ b/src/lua-script/cify.lua
@@ -8,6 +8,8 @@ output_file = io.open('script.h', 'w')
 output_file:write('const char *argent_script =\n')
 
 for line in io.lines('script.lua') do
+   line = string.gsub(line, '\\', '\\\\')
+   line = string.gsub(line, '"', '\\"')
    output_file:write('   "'..line..'\\n"\n')
 end
 
diff --git a/src/lua-script/script.h b/src/lua-script/script.h
index 9406097..0b58f11 100644
--- a/src/lua-script/script.h
+++ b/src/lua-script/script.h
@@ -1,4 +1,134 @@
 const char *argent_script =
+   "local RssChannel = {}\n"
+   "RssChannel.new = function(title, link, description, settings)\n"
+   "   local settings = settings or {}\n"
+   "\n"
+   "   local channel = {\n"
+   "      title=title,\n"
+   "      link=link,\n"
+   "      description=description,\n"
+   "   }\n"
+   "\n"
+   "   channel.language = settings.language\n"
+   "   channel.copyright = settings.copyright\n"
+   "   channel.managingEditor = settings.managingEditor\n"
+   "   channel.webMaster = settings.webMaster\n"
+   "   channel.lastBuildDate = settings.lastBuildDate\n"
+   "   channel.category = settings.category\n"
+   "   channel.generator = 'argent v0.1.0'\n"
+   "   channel.docs = 'https://www.rssboard.org/rss-specification'\n"
+   "\n"
+   "   channel.items = {}\n"
+   "\n"
+   "   setmetatable(\n"
+   "      channel,\n"
+   "      {\n"
+   "	 __index=RssChannel,\n"
+   "	 __tostring=RssChannel.tostring,\n"
+   "      }\n"
+   "   )\n"
+   "   return channel\n"
+   "end\n"
+   "\n"
+   "\n"
+   "RssChannel.addItem = function(self, tbl)\n"
+   "   local to_rfc822 = function(datestring, zone)\n"
+   "      if not string.match(datestring, '^%d%d%d%d%-%d%d%-%d%d$') then\n"
+   "	 error(string.format('%q is not a valid date string', datestring))\n"
+   "      end\n"
+   "      \n"
+   "      local year = string.match(datestring, '^%d%d%d%d')\n"
+   "      local month = string.match(datestring, '-(%d%d)-')\n"
+   "      local day = string.match(datestring, '%d%d$')\n"
+   "\n"
+   "      local month_names = {'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'}\n"
+   "      month = month_names[tonumber(month)]\n"
+   "\n"
+   "      return string.format('%s %s %s 00:00:00 %s', day, month, year, zone)\n"
+   "   end\n"
+   "   \n"
+   "   if not (tbl.title or tbl.description) then\n"
+   "      error('items must specify either a title or description!')\n"
+   "   end\n"
+   "\n"
+   "   if not tbl.pubDate then\n"
+   "      error('items must specify a pubDate!')\n"
+   "   else\n"
+   "      tbl.pubDate = to_rfc822(tbl.pubDate, 'Z')\n"
+   "      print(tbl.pubDate)\n"
+   "   end\n"
+   "\n"
+   "   table.insert(self.items, tbl)\n"
+   "end\n"
+   "\n"
+   "\n"
+   "local tag_builder = function(source_table, indent_level)\n"
+   "   local str = ''\n"
+   "   local indent = string.rep('  ', indent_level)\n"
+   "   local add_tag = function(tag)\n"
+   "      if source_table[tag] then\n"
+   "	 str = str ..\n"
+   "	    string.format('%s<%s>%s</%s>\\n',\n"
+   "			  indent, tag, source_table[tag], tag)\n"
+   "      end\n"
+   "   end\n"
+   "   local content = function()\n"
+   "      return str\n"
+   "   end\n"
+   "   return add_tag, content\n"
+   "end\n"
+   "\n"
+   "local render_item = function(item_tbl)\n"
+   "   local add_tag, content = tag_builder(item_tbl, 2)\n"
+   "   add_tag('title')\n"
+   "   add_tag('link')\n"
+   "   add_tag('description')\n"
+   "   add_tag('author')\n"
+   "   add_tag('category')\n"
+   "   add_tag('comments')\n"
+   "   add_tag('guid')\n"
+   "   add_tag('pubDate')\n"
+   "\n"
+   "   local indent = string.rep('  ', 1)\n"
+   "   return string.format('%s<item>\\n%s%s</item>',\n"
+   "			indent, content(), indent)\n"
+   "end\n"
+   "\n"
+   "RssChannel.tostring = function(self)\n"
+   "   local add_tag, content = tag_builder(self, 1)\n"
+   "   add_tag('title')\n"
+   "   add_tag('link')\n"
+   "   add_tag('description')\n"
+   "\n"
+   "   add_tag('language')\n"
+   "   add_tag('copyright')\n"
+   "   add_tag('managingEditor')\n"
+   "   add_tag('webMaster')\n"
+   "   add_tag('pubDate')\n"
+   "   add_tag('lastBuildDate')\n"
+   "   add_tag('category')\n"
+   "   add_tag('generator')\n"
+   "   add_tag('docs')\n"
+   "\n"
+   "   items_str = ''\n"
+   "\n"
+   "   for _, item in ipairs(self.items) do\n"
+   "      items_str = items_str .. render_item(item) .. '\\n'\n"
+   "   end\n"
+   "\n"
+   "   return string.format(\n"
+   "      '<rss version=\"2.0\">\\n<channel>\\n%s\\n%s</channel>\\n</rss>',\n"
+   "      content(), items_str)\n"
+   "end\n"
+   "\n"
+   "\n"
+   "RssChannel.save = function(self, filename)\n"
+   "   file = io.open(filename, 'w')\n"
+   "   file:write(self:tostring())\n"
+   "   file:close()\n"
+   "end\n"
+   "\n"
+   "\n"
    "return function(config)\n"
    "   local fmt = string.format\n"
    "\n"
@@ -22,6 +152,7 @@ const char *argent_script =
    "\n"
    "   function set_tostring(set)\n"
    "      local str = '[empty]'\n"
+   "      if not set then return str end\n"
    "      for element in pairs(set) do\n"
    "	 if str == '[empty]' then\n"
    "	    str = tostring(element)\n"
@@ -83,17 +214,30 @@ const char *argent_script =
    "\n"
    "   function setup(config)\n"
    "      argent.log('debug', 'begin setup')\n"
-   "      \n"
+   "\n"
+   "      if not config.site_name then\n"
+   "	 error('site_name MUST be set!')\n"
+   "      end\n"
+   "\n"
    "      argent.config = {\n"
+   "	 site_name = config.site_name,\n"
+   "	 site_address = add_end_slash(config.site_address) or nil,\n"
+   "	 site_description = config.site_description or '',\n"
+   "	 site_language = config.site_language,\n"
+   "	 site_copyright = config.site_copyright,\n"
+   "	 \n"
    "	 site_directory = add_end_slash(config.site_directory) or 'site/',\n"
    "	 output_directory = add_end_slash(config.output_directory) or 'public/',\n"
    "	 layout_directory = add_end_slash(config.layout_directory) or nil,\n"
    "	 plugin_directory = add_end_slash(config.plugin_directory) or nil,\n"
+   "	 \n"
    "	 exclude = Set(config.exclude, strip_end_slash) or {},\n"
    "	 include = Set(config.include, strip_end_slash) or {},\n"
    "	 keep = Set(config.keep, strip_end_slash) or {},\n"
    "	 noprocess = Set(config.noprocess, strip_end_slash) or {},\n"
-   "	 rss_include = Set(config.rss_include, strip_end_slash) or {},\n"
+   "	 \n"
+   "	 rss_include = Set(config.rss_include, strip_end_slash) or nil,\n"
+   "	 webmaster_email = config.webmaster_email,\n"
    "      }\n"
    "\n"
    "      argent.log('info', fmt('site directory: %s', argent.config.site_directory))\n"
@@ -119,6 +263,22 @@ const char *argent_script =
    "	    add_end_slash(argent.currentWorkingDirectory())\n"
    "	    ..argent.config.plugin_directory..'?.lua;'..package.path\n"
    "      end\n"
+   "\n"
+   "      if argent.config.rss_include then\n"
+   "	 if not argent.config.site_address then\n"
+   "	    error('rss_include is set, but site_address is not!')\n"
+   "	 end\n"
+   "	 argent.rss_channel = RssChannel.new(\n"
+   "	    argent.config.site_name,\n"
+   "	    argent.config.site_address,\n"
+   "	    argent.config.site_description,\n"
+   "	    { language = argent.config.site_language,\n"
+   "	      copyright = argent.config.site_copyright,\n"
+   "	      webMaster = argent.config.webmaster_email,\n"
+   "	    }\n"
+   "	 )\n"
+   "      end\n"
+   "      \n"
    "      argent.log('debug', 'end setup')\n"
    "   end\n"
    "\n"
@@ -176,11 +336,11 @@ const char *argent_script =
    "      end\n"
    "\n"
    "      argent.log('debug', fmt('obliterating files in %q', parent..dir))\n"
-   "      return obliterate_directory(dir, parent)\n"
+   "      return obliterate(dir, parent)\n"
    "   end\n"
    "\n"
    "\n"
-   "   function obliterate_directory(dirname, parent)\n"
+   "   function obliterate(dirname, parent)\n"
    "      local dirname = add_end_slash(dirname) or ''\n"
    "      local parent = parent or ''\n"
    "\n"
@@ -325,6 +485,30 @@ const char *argent_script =
    "      local output_file = io.open(argent.config.output_directory..parent..output_name, 'w')\n"
    "      output_file:write(output_data)\n"
    "      output_file:close()\n"
+   "\n"
+   "      for pattern in pairs(argent.config.rss_include) do\n"
+   "	 if string.match(filename, pattern)\n"
+   "	    or string.match(parent..filename, pattern)\n"
+   "	 then -- add to the RSS feed!\n"
+   "	    if not result.date then\n"
+   "	       argent.log(\n"
+   "		  'warn',\n"
+   "		  fmt(\n"
+   "		     '%q did not specify a date; it will not be included in rss.xml!',\n"
+   "		     parent..filename\n"
+   "		  )\n"
+   "	       )\n"
+   "	       return\n"
+   "	    end\n"
+   "	    \n"
+   "	    argent.rss_channel:addItem{\n"
+   "	       title=result.title,\n"
+   "	       link=argent.config.site_address..parent..output_name,\n"
+   "	       description = result.description,\n"
+   "	       pubDate = result.date,\n"
+   "	    }\n"
+   "	 end\n"
+   "      end\n"
    "   end\n"
    "\n"
    "\n"
@@ -337,11 +521,11 @@ const char *argent_script =
    "      if not directory_exists(directory, parent) then\n"
    "	 argent.createDirectory(argent.config.output_directory..parent..directory)\n"
    "      end\n"
-   "      process_directory(directory, parent)\n"
+   "      process(directory, parent)\n"
    "   end\n"
    "\n"
    "\n"
-   "   function process_directory(directory, parent)\n"
+   "   function process(directory, parent)\n"
    "      local directory = add_end_slash(directory) or ''\n"
    "      local parent = add_end_slash(parent) or ''\n"
    "      local dirs, files = argent.scanDirectory(argent.config.site_directory..parent..directory)\n"
@@ -367,8 +551,12 @@ const char *argent_script =
    "   if not output_directory_exists() then\n"
    "      argent.createDirectory(argent.config.output_directory)\n"
    "   else\n"
-   "      obliterate_directory()\n"
+   "      obliterate()\n"
+   "   end\n"
+   "   process()\n"
+   "\n"
+   "   if argent.rss_channel then\n"
+   "      argent.rss_channel:save(argent.config.output_directory..'rss.xml')\n"
    "   end\n"
-   "   process_directory()\n"
    "end\n"
 ;
\ No newline at end of file
diff --git a/src/lua-script/script.lua b/src/lua-script/script.lua
index e44ece5..65bf0cf 100644
--- a/src/lua-script/script.lua
+++ b/src/lua-script/script.lua
@@ -1,3 +1,133 @@
+local RssChannel = {}
+RssChannel.new = function(title, link, description, settings)
+   local settings = settings or {}
+
+   local channel = {
+      title=title,
+      link=link,
+      description=description,
+   }
+
+   channel.language = settings.language
+   channel.copyright = settings.copyright
+   channel.managingEditor = settings.managingEditor
+   channel.webMaster = settings.webMaster
+   channel.lastBuildDate = settings.lastBuildDate
+   channel.category = settings.category
+   channel.generator = 'argent v0.1.0'
+   channel.docs = 'https://www.rssboard.org/rss-specification'
+
+   channel.items = {}
+
+   setmetatable(
+      channel,
+      {
+	 __index=RssChannel,
+	 __tostring=RssChannel.tostring,
+      }
+   )
+   return channel
+end
+
+
+RssChannel.addItem = function(self, tbl)
+   local to_rfc822 = function(datestring, zone)
+      if not string.match(datestring, '^%d%d%d%d%-%d%d%-%d%d$') then
+	 error(string.format('%q is not a valid date string', datestring))
+      end
+      
+      local year = string.match(datestring, '^%d%d%d%d')
+      local month = string.match(datestring, '-(%d%d)-')
+      local day = string.match(datestring, '%d%d$')
+
+      local month_names = {'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'}
+      month = month_names[tonumber(month)]
+
+      return string.format('%s %s %s 00:00:00 %s', day, month, year, zone)
+   end
+   
+   if not (tbl.title or tbl.description) then
+      error('items must specify either a title or description!')
+   end
+
+   if not tbl.pubDate then
+      error('items must specify a pubDate!')
+   else
+      tbl.pubDate = to_rfc822(tbl.pubDate, 'Z')
+      print(tbl.pubDate)
+   end
+
+   table.insert(self.items, tbl)
+end
+
+
+local tag_builder = function(source_table, indent_level)
+   local str = ''
+   local indent = string.rep('  ', indent_level)
+   local add_tag = function(tag)
+      if source_table[tag] then
+	 str = str ..
+	    string.format('%s<%s>%s</%s>\n',
+			  indent, tag, source_table[tag], tag)
+      end
+   end
+   local content = function()
+      return str
+   end
+   return add_tag, content
+end
+
+local render_item = function(item_tbl)
+   local add_tag, content = tag_builder(item_tbl, 2)
+   add_tag('title')
+   add_tag('link')
+   add_tag('description')
+   add_tag('author')
+   add_tag('category')
+   add_tag('comments')
+   add_tag('guid')
+   add_tag('pubDate')
+
+   local indent = string.rep('  ', 1)
+   return string.format('%s<item>\n%s%s</item>',
+			indent, content(), indent)
+end
+
+RssChannel.tostring = function(self)
+   local add_tag, content = tag_builder(self, 1)
+   add_tag('title')
+   add_tag('link')
+   add_tag('description')
+
+   add_tag('language')
+   add_tag('copyright')
+   add_tag('managingEditor')
+   add_tag('webMaster')
+   add_tag('pubDate')
+   add_tag('lastBuildDate')
+   add_tag('category')
+   add_tag('generator')
+   add_tag('docs')
+
+   items_str = ''
+
+   for _, item in ipairs(self.items) do
+      items_str = items_str .. render_item(item) .. '\n'
+   end
+
+   return string.format(
+      '<rss version="2.0">\n<channel>\n%s\n%s</channel>\n</rss>',
+      content(), items_str)
+end
+
+
+RssChannel.save = function(self, filename)
+   file = io.open(filename, 'w')
+   file:write(self:tostring())
+   file:close()
+end
+
+
 return function(config)
    local fmt = string.format
 
@@ -21,6 +151,7 @@ return function(config)
 
    function set_tostring(set)
       local str = '[empty]'
+      if not set then return str end
       for element in pairs(set) do
 	 if str == '[empty]' then
 	    str = tostring(element)
@@ -82,17 +213,30 @@ return function(config)
 
    function setup(config)
       argent.log('debug', 'begin setup')
-      
+
+      if not config.site_name then
+	 error('site_name MUST be set!')
+      end
+
       argent.config = {
+	 site_name = config.site_name,
+	 site_address = add_end_slash(config.site_address) or nil,
+	 site_description = config.site_description or '',
+	 site_language = config.site_language,
+	 site_copyright = config.site_copyright,
+	 
 	 site_directory = add_end_slash(config.site_directory) or 'site/',
 	 output_directory = add_end_slash(config.output_directory) or 'public/',
 	 layout_directory = add_end_slash(config.layout_directory) or nil,
 	 plugin_directory = add_end_slash(config.plugin_directory) or nil,
+	 
 	 exclude = Set(config.exclude, strip_end_slash) or {},
 	 include = Set(config.include, strip_end_slash) or {},
 	 keep = Set(config.keep, strip_end_slash) or {},
 	 noprocess = Set(config.noprocess, strip_end_slash) or {},
-	 rss_include = Set(config.rss_include, strip_end_slash) or {},
+	 
+	 rss_include = Set(config.rss_include, strip_end_slash) or nil,
+	 webmaster_email = config.webmaster_email,
       }
 
       argent.log('info', fmt('site directory: %s', argent.config.site_directory))
@@ -118,6 +262,22 @@ return function(config)
 	    add_end_slash(argent.currentWorkingDirectory())
 	    ..argent.config.plugin_directory..'?.lua;'..package.path
       end
+
+      if argent.config.rss_include then
+	 if not argent.config.site_address then
+	    error('rss_include is set, but site_address is not!')
+	 end
+	 argent.rss_channel = RssChannel.new(
+	    argent.config.site_name,
+	    argent.config.site_address,
+	    argent.config.site_description,
+	    { language = argent.config.site_language,
+	      copyright = argent.config.site_copyright,
+	      webMaster = argent.config.webmaster_email,
+	    }
+	 )
+      end
+      
       argent.log('debug', 'end setup')
    end
 
@@ -175,11 +335,11 @@ return function(config)
       end
 
       argent.log('debug', fmt('obliterating files in %q', parent..dir))
-      return obliterate_directory(dir, parent)
+      return obliterate(dir, parent)
    end
 
 
-   function obliterate_directory(dirname, parent)
+   function obliterate(dirname, parent)
       local dirname = add_end_slash(dirname) or ''
       local parent = parent or ''
 
@@ -324,6 +484,30 @@ return function(config)
       local output_file = io.open(argent.config.output_directory..parent..output_name, 'w')
       output_file:write(output_data)
       output_file:close()
+
+      for pattern in pairs(argent.config.rss_include) do
+	 if string.match(filename, pattern)
+	    or string.match(parent..filename, pattern)
+	 then -- add to the RSS feed!
+	    if not result.date then
+	       argent.log(
+		  'warn',
+		  fmt(
+		     '%q did not specify a date; it will not be included in rss.xml!',
+		     parent..filename
+		  )
+	       )
+	       return
+	    end
+	    
+	    argent.rss_channel:addItem{
+	       title=result.title,
+	       link=argent.config.site_address..parent..output_name,
+	       description = result.description,
+	       pubDate = result.date,
+	    }
+	 end
+      end
    end
 
 
@@ -336,11 +520,11 @@ return function(config)
       if not directory_exists(directory, parent) then
 	 argent.createDirectory(argent.config.output_directory..parent..directory)
       end
-      process_directory(directory, parent)
+      process(directory, parent)
    end
 
 
-   function process_directory(directory, parent)
+   function process(directory, parent)
       local directory = add_end_slash(directory) or ''
       local parent = add_end_slash(parent) or ''
       local dirs, files = argent.scanDirectory(argent.config.site_directory..parent..directory)
@@ -366,7 +550,11 @@ return function(config)
    if not output_directory_exists() then
       argent.createDirectory(argent.config.output_directory)
    else
-      obliterate_directory()
+      obliterate()
+   end
+   process()
+
+   if argent.rss_channel then
+      argent.rss_channel:save(argent.config.output_directory..'rss.xml')
    end
-   process_directory()
 end
-- 
cgit v1.2.1