commit e1400e1ed8f197d105e0d4553e2249672c4519fb Author: mio Date: Mon Jul 25 18:49:12 2022 +0000 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..87b605f --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +log +config.lua +config.ini +template.ini diff --git a/config.sample.lua b/config.sample.lua new file mode 100644 index 0000000..f76e33c --- /dev/null +++ b/config.sample.lua @@ -0,0 +1,10 @@ +local config = { + author = "Gem", + title = "gemlog", + subtitle = "a Gemini log", + url = "gemini://", + output_dir = "/path/to/local/log", + rsync_remote = "/path/to/remote/log", +} + +return config diff --git a/gemwriter.lua b/gemwriter.lua new file mode 100644 index 0000000..1fe80dd --- /dev/null +++ b/gemwriter.lua @@ -0,0 +1,247 @@ +local conf = require("config") +local tpl = require("template") +local util = require("util") + + +local writer = {} + +writer.app = { + exec_name = "gw", + last_updated = "2022-07-24", + name = "gemwriter", + version = "0.1", +} + +writer.conf = {} +writer.defaults = { + atom_file = "atom.xml", + author = "Gem", + -- Atom feed date format + entry_date_format = "%Y-%m-%dT%X+00:00", + entry_ext = ".gmi", + entry_slug = os.date("%Y-%m-%d-%H%M%S"), + entry_file_pattern = "[%/](%d%d%d%d%-%d%d%-%d%d)(.*)[%.](%a*)", + gen_atom_feed = true, + gen_index_page = true, + index_file = "index.gmi", + page_slug = "untitled-" .. os.date("%Y%m%d%H%M%S"), + title = "gemlog", + subtitle = "a Gemini log", + url = "", + output_dir = "log", + rsync_exec = "/usr/bin/rsync", + rsync_options = "-avz", + rsync_remote = "", +} + +writer.entries = {} + +writer.msg = { + add_entry = "Created ", + help = writer.app.exec_name .. [[ [options] [slug] + +Options: + +page [slug] Add a new page with the given name +post [slug] Add a new entry with the given name +index Generate an index page and feed of entries +publish Index and copy posts to a remote server (requires rsync) +help Show this help message +version Print version info + ]], + index = "Created index page and feed.", + publish = "Published log.", +} + + +function writer.load_config(config) + for k, v in pairs(writer.defaults) do + if config[k] == nil then + writer.conf[k] = writer.defaults[k] + else + writer.conf[k] = config[k] + end + end +end + + +function writer.replace_vars(str, vars, vals) + local text = str + for k, v in pairs(vars) do + text = string.gsub(text, v, vals[k]) + end + return text +end + + +function writer.add_entry(slug) + local text = tpl.log_gmi + text = string.gsub(text, tpl.vars.entry.date, + os.date(writer.conf.entry_date_format)) + text = string.gsub(text, tpl.vars.entry.author, writer.conf.author) + + local entry_name = slug .. writer.conf.entry_ext + os.execute("test -d " .. writer.conf.output_dir .. " || mkdir -p " .. + writer.conf.output_dir) + util.write_file(writer.conf.output_dir .. "/" .. entry_name, text) +end + + +function writer.get_entries() + local ls_cmd = io.popen("ls " .. writer.conf.output_dir .. " | grep " .. + writer.conf.entry_ext) + local ls = ls_cmd:read("*a") + local entries = {} + local n = 0 + local files_list = util.split_str(ls) + for f = 1, #files_list do + if files_list[f] ~= writer.conf.index_file then + n = n + 1 + entries[n] = { files_list[f], util.read_file(writer.conf.output_dir .. + "/" .. files_list[f]) } + end + end + return entries +end + + +function writer.get_entries_meta() + local meta = {} + local entries = writer.get_entries() + for e = 1, #entries do + meta[e] = {} + if string.find(entries[e][2], "# ") == nil then + meta[e]["title"] = entries[e][1] + meta[e]["content"] = entries[e][2] + else + meta[e]["title"] = util.extract_str(entries[e][2], "# ", "\n") + hi1, hi2 = string.find(entries[e][2], "# ") + meta[e]["content"] = string.sub(entries[e][2], hi1, + string.len(entries[e][2])) + end + if string.find(entries[e][2], "author: ") == nil then + meta[e]["author"] = writer.conf.author + else + meta[e]["author"] = util.extract_str(entries[e][2], "author: ", "\n") + end + if string.find(entries[e][2], "date: ") == nil then + meta[e]["date"] = util.extract_file_date(writer.conf.output_dir, + entries[e][1]) + if (meta[e]["date"] == "") or (meta[e]["date"] == nil) then + meta[e]["date"] = os.date(writer.conf.entry_date_format) + end + else + meta[e]["date"] = util.extract_str(entries[e][2], "date: ", "\n") + end + if string.find(entries[e][2], "summary: ") == nil then + meta[e]["summary"] = meta[e]["title"] + else + meta[e]["summary"] = util.extract_str(entries[e][2], "summary: ", "\n") + end + if string.find(entries[e][2], "tags: ") == nil then + meta[e]["tags"] = "" + else + meta[e]["tags"] = util.extract_str(entries[e][2], "tags: ", "\n") + end + meta[e]["url"] = writer.conf.url .. "/" .. entries[e][1] + end + return meta +end + + +function writer.gen_index_page() + local index_text = writer.replace_vars(tpl.index_gmi, tpl.vars.log, + writer.conf) + local entries_text = "" + -- Reverse insert links to log entries, newest first + for e = #writer.entries, 1, -1 do + local entry_date = util.split_str(writer.entries[e]["date"], "(.*)[T]")[1] + -- Get entry filename from the url + local fi1, fi2 = string.find(writer.entries[e]["url"], + writer.conf.entry_file_pattern) + entries_text = entries_text .. "\n=> " .. + string.sub(writer.entries[e]["url"], fi1 + 1, fi2) .. " " .. + entry_date .. " " .. writer.entries[e]["title"] + end + index_text = string.gsub(index_text, tpl.vars.index.entries, entries_text) + util.write_file(writer.conf.output_dir .. "/" .. writer.conf.index_file, + index_text) +end + + +function writer.gen_atom_feed() + local feed_meta = { + date = os.date(writer.conf.entry_date_format), + url = writer.conf.url .. "/" .. writer.conf.atom_file, + } + local feed_text = writer.replace_vars(tpl.atom_header, tpl.vars.log, + writer.conf) + feed_text = writer.replace_vars(feed_text, tpl.vars.feed, feed_meta) + local feed_entry = "" + -- Reverse insert log entries, newest first + for e = #writer.entries, 1, -1 do + feed_entry = tpl.atom_entry + feed_entry = writer.replace_vars(feed_entry, tpl.vars.entry, + writer.entries[e]) + feed_text = feed_text .. feed_entry + end + feed_text = feed_text .. tpl.atom_footer + util.write_file(writer.conf.output_dir .. "/" .. writer.conf.atom_file, + feed_text) +end + + +function writer.publish() + os.execute(writer.conf.rsync_exec .. " " .. writer.conf.rsync_options .. + " " .. writer.conf.output_dir .. " " .. writer.conf.rsync_remote) +end + + + +local cli = {} + +function cli.handle_args(args) + if (args[1] ~= "help") and (args[1] ~= "version") then + writer.load_config(conf) + end + + if (args[1] == "index") or (args[1] == "publish") then + writer.entries = writer.get_entries_meta() + if writer.conf.gen_index_page then writer.gen_index_page() end + if writer.conf.gen_atom_feed then writer.gen_atom_feed() end + end + + if args[1] == "post" then + local slug = args[2] + if slug == "" then + slug = writer.conf.entry_slug + else + slug = os.date("%Y-%m-%d") .. "-" .. slug + end + writer.add_entry(slug) + print(writer.msg.add_entry .. slug .. writer.conf.entry_ext) + + elseif args[1] == "page" then + local slug = args[2] + if slug == "" then slug = writer.conf.page_slug end + writer.add_entry(slug) + print(writer.msg.add_entry .. slug .. writer.conf.entry_ext) + + elseif args[1] == "index" then + print(writer.msg.index) + + elseif args[1] == "publish" then + writer.publish() + print(writer.msg.publish) + + elseif args[1] == "help" then + print(writer.msg.help) + + elseif args[1] == "version" then + print(writer.app.name .. " " .. writer.app.version .. + " (" .. writer.app.last_updated .. ")") + end +end + + +cli.handle_args(arg) diff --git a/template.lua b/template.lua new file mode 100644 index 0000000..f003cf4 --- /dev/null +++ b/template.lua @@ -0,0 +1,96 @@ +local tpl = {} + +tpl.vars = { + entry = { + author = "{{ entry_author }}", + content = "{{ entry_content }}", + date = "{{ entry_date }}", + summary = "{{ entry_summary }}", + tags = "{{ entry_tags }}", + title = "{{ entry_title }}", + url = "{{ entry_url }}", + }, + feed = { + date = "{{ feed_date }}", + url = "{{ feed_url }}", + }, + index = { + entries = "{{ entries }}", + }, + log = { + author = "{{ log_author }}", + subtitle = "{{ log_subtitle }}", + title = "{{ log_title }}", + url = "{{ log_url }}", + }, +} + +tpl.atom_header = [[ + + {{ log_url }} + {{ log_title }} + {{ log_subtitle }} + {{ feed_date }} + + {{ log_author }} + + + ]] + +tpl.atom_entry = [[ + {{ entry_url }} + + <![CDATA[{{ entry_title }}]] .. "]]>" .. [[ + + + {{ entry_date }} + + {{ entry_author }} + + + + " .. [[ + + + + " .. [[ + + + +]] + +tpl.atom_footer = [[]] + +tpl.log_gmi = [[--- +date: {{ entry_date }} +--- + +# {{ entry_title }} + +# Heading 1 +## Heading 2 +### Heading 3 + +List: + +* +* +* + +``` +Preformatted text +``` + + +## Links + +=> gemini:// link +=> gemini:// link (img) +=> https:// link (https)]] + +tpl.index_gmi = [[ +# {{ log_title }} +{{ entries }} +]] + +return tpl diff --git a/util.lua b/util.lua new file mode 100644 index 0000000..bc27496 --- /dev/null +++ b/util.lua @@ -0,0 +1,47 @@ +local util = {} + +function util.split_str(str, sep) + local tbl = {} + local n = 0 + if sep == nil then sep = "%S+" end + for sub in string.gmatch(str, sep) do + n = n + 1 + tbl[n] = sub + end + return tbl +end + + +function util.extract_str(full_str, find_str, end_str) + fi1, fi2 = string.find(full_str, find_str, 1) + ei1, ei2 = string.find(full_str, end_str, fi2) + return string.sub(full_str, fi2 + 1, ei1 - 1) +end + + +function util.extract_file_date(dir, file) + -- Atom feed date format: 2022-01-15T00:00:00+00:00 + local ls_cmd = io.popen("ls -l --time-style=\"+%Y-%m-%dT%H:%M:%S%z\" " .. + dir .. " | grep " .. file .. " | awk '{print $6}'") + return ls_cmd:read("*a") +end + + +function util.read_file(file) + local fh = io.open(file, "r") + local text = "" + io.input(fh) + text = io.read("*a") + io.close(fh) + return text +end + + +function util.write_file(file, str) + local fh = io.open(file, "w") + io.output(fh) + io.write(str) + io.close(fh) +end + +return util