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- \\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"
" print('WARNING: '..result)\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"
" 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"
" 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 = add_end_slash(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"
;