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" " 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\\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\\n%s%s',\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" " '\\n\\n%s\\n%s\\n',\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" " --------------------------------\n" " --\n" " -- basics\n" " --\n" " --------------------------------\n" "\n" " function Set(array, transform)\n" " if type(array) ~= 'table' then return nil end\n" "\n" " local transform = transform or function(x) return x end\n" "\n" " local set = {}\n" " for _, element in ipairs(array) do\n" " set[transform(element)] = true\n" " end\n" " return set\n" " end\n" "\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" " else\n" " str = str .. '; '.. tostring(element)\n" " end\n" " end\n" "\n" " return str\n" " end\n" "\n" "\n" " function add_end_slash(str)\n" " if type(str) ~= 'string' then return nil end\n" " if string.match(str, '/$') then return str end\n" " return str .. '/'\n" " end\n" "\n" "\n" " function strip_end_slash(str)\n" " if string.match(str, '/$') then return string.sub(str, 1, #str-1) end\n" " return str\n" " end\n" "\n" "\n" " function load_layouts(directory, parent, layouts)\n" " local directory = add_end_slash(directory) or ''\n" " local parent = parent or ''\n" " local layouts = layouts or {}\n" "\n" " local fullpath = parent..directory\n" " local dirs, files = argent.scanDirectory(argent.config.layout_directory..fullpath)\n" "\n" " for _, file in ipairs(files) do\n" " if string.match(file, '%.lua$') then\n" " local basename = string.gsub(file, '%.lua$', '')\n" " local id = string.gsub(fullpath..basename, '/', '.')\n" " argent.log('debug', fmt('loading layout %q', id))\n" " success, result = pcall(loadfile(argent.config.layout_directory..fullpath..file), 0, 1)\n" " if not success then\n" " argent.log('error', result)\n" " argent.log('warn', fmt('layout %q will not be available', id))\n" " else\n" " layouts[id] = result\n" " end\n" " end\n" " end\n" "\n" " for _, dir in ipairs(dirs) do\n" " if dir ~= '.' and dir ~= '..' then\n" " argent.log('debug', fmt('loading layouts from %q', parent..directory))\n" " load_layouts(dir, parent..directory, layouts)\n" " end\n" " end\n" "\n" " return layouts\n" " end\n" "\n" "\n" " function setup(config)\n" " argent.log('debug', 'begin setup')\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" " \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" " argent.log('info', fmt('output directory: %s', argent.config.output_directory))\n" " argent.log('info', fmt('layout directory: %s', tostring(argent.config.layout_directory)))\n" " argent.log('info', fmt('plugin directory: %s', tostring(argent.config.plugin_directory)))\n" "\n" " argent.log('info', 'exclusions: '..set_tostring(argent.config.exclude))\n" " argent.log('info', 'inclusions: '..set_tostring(argent.config.include))\n" " argent.log('info', 'files to keep: '..set_tostring(argent.config.keep))\n" " argent.log('info', 'noprocess: '..set_tostring(argent.config.noprocess))\n" " argent.log('info', 'rss files: '..set_tostring(argent.config.rss_include))\n" "\n" " if argent.config.layout_directory then\n" " argent.layouts = load_layouts()\n" " else\n" " argent.layouts = {}\n" " end\n" " argent.log('info', fmt('available layouts: %s', set_tostring(argent.layouts)))\n" " \n" " if argent.config.plugin_directory then\n" " package.path =\n" " 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" "\n" " function is_dotfile(filename)\n" " if string.match(filename, '^%.') then return true end\n" " return false\n" " end\n" "\n" "\n" " function directory_exists(directory, parent)\n" " local directory = directory or ''\n" " local parent = parent or ''\n" " local dirs = argent.scanDirectory(argent.config.output_directory..parent)\n" " for _, dir in ipairs(dirs) do\n" " if strip_end_slash(directory) == dir then return true end\n" " end\n" " return false\n" " end\n" "\n" "\n" " function output_directory_exists()\n" " local dirs = argent.scanDirectory('./')\n" " for _, dir in ipairs(dirs) do\n" " if add_end_slash(dir) == argent.config.output_directory then\n" " return true\n" " end\n" " end\n" " return false\n" " end\n" "\n" "\n" " --------------------------------\n" " --\n" " -- obliterating\n" " --\n" " --------------------------------\n" "\n" " function obliterate_file(file, parent)\n" " if argent.config.keep[file] then\n" " argent.log('debug', fmt('retaining file %q', parent..file))\n" " return true\n" " end\n" "\n" " argent.log('debug', fmt('removing file %q', parent..file))\n" " os.remove(argent.config.output_directory..parent..file)\n" " return false\n" " end\n" "\n" "\n" " function obliterate_dir(dir, parent)\n" " if argent.config.keep[strip_end_slash(dir)] then\n" " argent.log('debug', fmt('retaining directory %q', parent..dir))\n" " return true\n" " end\n" "\n" " argent.log('debug', fmt('obliterating files in %q', parent..dir))\n" " return obliterate(dir, parent)\n" " end\n" "\n" "\n" " function obliterate(dirname, parent)\n" " local dirname = add_end_slash(dirname) or ''\n" " local parent = parent or ''\n" "\n" " if argent.config.keep[dirname] then return true end\n" "\n" " local keep_self = (dirname == '')\n" "\n" " local dirs, files = argent.scanDirectory(argent.config.output_directory..parent..dirname)\n" " for _, file in ipairs(files) do\n" " keep_self = obliterate_file(file, parent..dirname) or keep_self\n" " end\n" " for _, dir in ipairs(dirs) do\n" " if dir ~= '.' and dir ~= '..' then\n" " keep_self = obliterate_dir(dir, parent..dirname) or keep_self\n" " end\n" " end\n" "\n" " if not keep_self then\n" " os.remove(argent.config.output_directory..parent..dirname)\n" " return false\n" " else\n" " return true\n" " end\n" " end\n" "\n" "\n" " --------------------------------\n" " --\n" " -- processing\n" " --\n" " --------------------------------\n" "\n" " function should_include(filename, parent)\n" " for pattern in pairs(argent.config.include) do\n" " if string.match(filename, pattern) or string.match(parent..filename, pattern) then\n" " return true\n" " end\n" " end\n" " return false\n" " end\n" "\n" " function should_ignore(filename, parent)\n" " if is_dotfile(filename) and not should_include(filename, parent) then\n" " return false\n" " end\n" "\n" " for pattern in pairs(argent.config.exclude) do\n" " if string.match(filename, pattern)\n" " or string.match(parent..filename, pattern) then\n" " return true\n" " end\n" " end\n" "\n" " return false\n" " end\n" "\n" " function should_process(filename, parent)\n" " if not string.match(filename, '%.lua$') then\n" " return false\n" " end\n" " \n" " if should_ignore(filename, parent) then\n" " return false\n" " end\n" "\n" " for pattern in pairs(argent.config.noprocess) do\n" " if string.match(filename, pattern)\n" " or string.match(parent..filename, pattern) then\n" " return false\n" " end\n" " end\n" "\n" " return true\n" " end\n" "\n" " function process_file(filename, parent)\n" " if should_ignore(filename, parent) then\n" " argent.log('debug', fmt('will not process file %q', parent..filename))\n" " return\n" " end\n" "\n" " if should_process(filename, parent) then\n" " argent.log('debug', fmt('processing %q as lua file', parent..filename))\n" " process_lua_file(filename, parent)\n" " else\n" " argent.log('debug', fmt('processing %q as regular file', parent..filename))\n" " argent.copyFile(\n" " argent.config.site_directory..parent..filename,\n" " argent.config.output_directory..parent..filename\n" " )\n" " end\n" " end\n" "\n" "\n" " function process_lua_file(filename, parent)\n" " local success, result = pcall(loadfile(argent.config.site_directory..parent..filename))\n" " if not success then\n" " argent.log('error', result)\n" " argent.log('warn', fmt('%q will not be processed for output!', parent..filename))\n" " return\n" " end\n" "\n" " if not type(result) == 'table' then\n" " argent.log('error', fmt('%q returned %q instead of %q', filename, type(result), 'table'))\n" " argent.log('warn', fmt('%q will not result in file output!', filename))\n" " return\n" " end\n" "\n" " local html\n" " if result.html then\n" " html = result.html\n" " elseif result.markdown then\n" " html = argent.markdown(result.markdown)\n" " else\n" " argent.log('error', fmt('%q did not specify any content!', filename))\n" " argent.log('warn', fmt('%q will not result in file output!', filename))\n" " return\n" " end\n" "\n" " if not result.title then\n" " result.title = string.gsub(filename, '%.lua$', '')\n" " argent.log('warn', fmt('%q did not specify a title; using default title of %q',\n" " filename, result.title))\n" " end\n" "\n" " local layout = function(html, tbl) return html end\n" " if result.layout then\n" " if not argent.layouts[result.layout] then\n" " argent.log('error', fmt('%q requested nonexistent layout %q', filename, result.layout))\n" " argent.log('warn', fmt('%q will not result in file output!', filename))\n" " return\n" " else\n" " layout = argent.layouts[result.layout]\n" " end\n" " else -- no layout specified\n" " argent.log('warn', fmt(\n" " '%q did not specify a layout; output will be naked content!',\n" " filename))\n" " end\n" "\n" " local output_data = layout(html, result)\n" " local output_name = string.gsub(filename, '%.lua$', '.html')\n" " 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" " if argent.rss_channel then\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" " end\n" "\n" "\n" " function process_dir(directory, parent)\n" " if should_ignore(directory, parent) then\n" " argent.log('debug', fmt('will not process directory %q', parent..directory))\n" " return\n" " end\n" "\n" " if not directory_exists(directory, parent) then\n" " argent.createDirectory(argent.config.output_directory..parent..directory)\n" " end\n" " process(directory, parent)\n" " end\n" "\n" "\n" " function process(directory, parent)\n" " local directory = add_end_slash(directory) or ''\n" " local parent = parent or ''\n" " local dirs, files = argent.scanDirectory(argent.config.site_directory..parent..directory)\n" " for _, file in ipairs(files) do\n" " process_file(file, parent..directory)\n" " end\n" "\n" " for _, dir in ipairs(dirs) do\n" " if dir ~= '.' and dir ~= '..' then\n" " process_dir(dir, parent..directory)\n" " end\n" " end\n" " end\n" "\n" "\n" " --------------------------------\n" " --\n" " -- putting it all together\n" " --\n" " --------------------------------\n" "\n" " setup(config)\n" " if not output_directory_exists() then\n" " argent.createDirectory(argent.config.output_directory)\n" " else\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" "end\n" ;