diff --git a/.gitignore b/.gitignore
index 87b605f..6268f27 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,2 @@
-log
-config.lua
-config.ini
-template.ini
+gemwriter
+gemwriter.luastatic.c
diff --git a/config.sample.lua b/config.sample.lua
deleted file mode 100644
index f76e33c..0000000
--- a/config.sample.lua
+++ /dev/null
@@ -1,10 +0,0 @@
-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/env.lua b/env.lua
new file mode 100644
index 0000000..3a3d08a
--- /dev/null
+++ b/env.lua
@@ -0,0 +1,85 @@
+local env = {}
+
+-- App information
+env.app = {
+ name = "gemwriter",
+ exec_name = "gemwriter",
+ version = "0.2",
+ last_updated = "2022-08-05",
+}
+
+env.defaults = {
+ config_dir = "$HOME/.config/" .. env.app.exec_name,
+ config_files = {
+ atom_entry = "atom.entry.xml",
+ atom_header = "atom.header.xml",
+ config = "config.toml",
+ index = "index.gmi",
+ page = "page.gmi",
+ post = "post.gmi",
+ },
+ post_slug = "untitled",
+ post_slug_date_format = "%Y-%m-%d",
+ -- Atom feed date format
+ post_date_format = "%Y-%m-%dT%H:%M:%SZ",
+ post_ext = ".gmi",
+ post_file_pattern = "[%/](%d%d%d%d%-%d%d%-%d%d)(.*)[%.](%a*)",
+ page_slug = os.date("%Y%m%d-%H%M%S"),
+}
+
+-- Configurable settings
+env.toml_vars = {
+ general = "general",
+ capsules = "capsules",
+}
+env.general = {
+ app_lang = "en",
+ main_capsule = "main",
+}
+env.capsules = {
+ main = {
+ author = "Gem",
+ title = "Gemlog",
+ subtitle = "A Gemini log",
+ url = "gemini://domain.tld",
+ log_url = "gemini://domain.tld/log",
+ capsule_dir = env.defaults.config_dir .. "/capsule",
+ gemlog_dir = env.defaults.config_dir .. "/capsule/log",
+ gen_index_page = true,
+ index_page = "index.gmi",
+ gen_atom_feed = true,
+ atom_feed = "atom.xml",
+ transfer_mode = "scp",
+ scp_exec = "/usr/bin/scp",
+ scp_target = "/path/to/remote/capsule",
+ rsync_exec = "/usr/bin/rsync",
+ rsync_options = "-avz",
+ rsync_dest = "/path/to/remote/capsule",
+ },
+}
+env.defaults_toml = [[
+[]] .. env.toml_vars.general .. [[]
+app_lang = "]] .. env.general.app_lang .. [["
+main_capsule = "]] .. env.general.main_capsule .. [["
+
+[]] .. env.toml_vars.capsules .. "." .. env.general.main_capsule .. [[]
+author = "]] .. env.capsules.main.author .. [["
+title = "]] .. env.capsules.main.title .. [["
+subtitle = "]] .. env.capsules.main.subtitle .. [["
+url = "]] .. env.capsules.main.url .. [["
+log_url = "]] .. env.capsules.main.log_url .. [["
+capsule_dir = "]] .. env.capsules.main.capsule_dir .. [["
+gemlog_dir = "]] .. env.capsules.main.gemlog_dir .. [["
+gen_index_page = "]] .. tostring(env.capsules.main.gen_index_page) .. [["
+index_page = "]] .. env.capsules.main.index_page .. [["
+gen_atom_feed = "]] .. tostring(env.capsules.main.gen_atom_feed) .. [["
+atom_feed = "]] .. env.capsules.main.atom_feed .. [["
+transfer_mode = "]] .. env.capsules.main.transfer_mode .. [["
+scp_exec = "]] .. env.capsules.main.scp_exec .. [["
+scp_target = "]] .. env.capsules.main.scp_target .. [["
+rsync_exec = "]] .. env.capsules.main.rsync_exec .. [["
+rsync_options = "]] .. env.capsules.main.rsync_options .. [["
+rsync_dest = "]] .. env.capsules.main.rsync_dest .. [["
+]]
+
+return env
diff --git a/gemwriter.lua b/gemwriter.lua
index 1fe80dd..5e0d2c2 100644
--- a/gemwriter.lua
+++ b/gemwriter.lua
@@ -1,245 +1,382 @@
-local conf = require("config")
-local tpl = require("template")
+local env = require("env")
+local lang = require("lang.en")
local util = require("util")
local writer = {}
-
-writer.app = {
- exec_name = "gw",
- last_updated = "2022-07-24",
- name = "gemwriter",
- version = "0.1",
-}
-
+writer.docs = {}
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.posts = {}
-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]
+writer.docs.gen_config = [[
+ Generate a default config directory.
+ ]]
+function writer.gen_config()
+ writer.conf.config_dir = util.replace_shell_vars(env.defaults.config_dir)
+ util.make_dir(writer.conf.config_dir)
+ for name, file in pairs(env.defaults.config_files) do
+ -- Check each file individually anyway to avoid overwriting existing
+ -- files
+ if util.read_file(writer.conf.config_dir .. "/" .. file) == "" then
+ if file == env.defaults.config_files.config then
+ util.write_file(writer.conf.config_dir .. "/" .. file,
+ env.defaults_toml)
+ else
+ util.write_file(writer.conf.config_dir .. "/" .. file, lang[name])
+ end
end
end
+ -- Create capsule and gemlog directories
+ util.make_dir(env.capsules.main.capsule_dir)
+ util.make_dir(env.capsules.main.gemlog_dir)
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])
+writer.docs.parse_config = [[
+ Read a config file in TOML format and load the values into a table.
+ ]]
+function writer.parse_config(config_file)
+ local lines = util.split_lines(config_file)
+ local config_group = env.toml_vars.general
+ local line_cap = ""
+ writer.conf.capsules = {}
+ for l = 1, #lines do
+ -- Config group
+ if string.sub(lines[l], 1, 1) == "[" then
+ for tv, label in pairs(env.toml_vars) do
+ if string.find(lines[l], label) ~= nil then config_group = label end
+ end
+ if config_group == env.toml_vars.capsules then
+ local _, lb_sep = string.find(lines[l], "%[" .. config_group .. "%.")
+ local rb_sep, _ = string.find(lines[l], "%]")
+ if lb_sep ~= nil then
+ line_cap = string.sub(lines[l], lb_sep + 1, rb_sep - 1)
+ writer.conf.capsules[line_cap] = {}
+ end
+ end
+
+ -- Config variables (ignore comments)
+ elseif (string.sub(lines[l], 1, 1) ~= "#") and
+ (string.find(lines[l], "=") ~= nil) then
+ local eq_sep, _ = string.find(lines[l], " = ")
+ local qt_sep, _ = string.find(lines[l], "\"")
+ local k = string.sub(lines[l], 1, eq_sep - 1)
+ local val = string.sub(lines[l], eq_sep + 4, string.len(lines[l]) - 1)
+ if qt_sep == nil then
+ val = string.sub(lines[l], eq_sep + 3, string.len(lines[l]))
+ end
+ -- Load settings by key and their values to the config table
+ if config_group == env.toml_vars.general then
+ writer.conf[k] = val
+ elseif config_group == env.toml_vars.capsules then
+ -- Convert boolean values
+ if (val == "\"true\"") or (val == "\"false\"") then
+ writer.conf.capsules[line_cap][k] = util.to_bool(val)
+ else
+ writer.conf.capsules[line_cap][k] = val
+ end
+ end
+ end
end
- return text
+
+ -- If the main capsule id is not the app default, update the id
+ if (writer.conf.cap_id == env.general.main_capsule) and
+ (writer.conf.main_capsule ~= env.general.main_capsule) then
+ writer.conf.cap_id = writer.conf.main_capsule
+ end
+ -- Add an alias to the selected capsule's table
+ writer.conf.cap = writer.conf.capsules[writer.conf.cap_id]
+
+ -- Fill in the remaining defaults
+ for k, v in pairs(env.defaults) do
+ if writer.conf[k] == nil then
+ writer.conf[k] = env.defaults[k]
+ end
+ end
+
+ -- Replace shell variables in paths to make them usable
+ for c_id, _ in pairs(writer.conf.capsules) do
+ writer.conf.capsules[c_id]["capsule_dir"] = util.replace_shell_vars(
+ writer.conf.capsules[c_id]["capsule_dir"])
+ writer.conf.capsules[c_id]["gemlog_dir"] = util.replace_shell_vars(
+ writer.conf.capsules[c_id]["gemlog_dir"])
+ -- Create capsule and gemlog directories
+ util.make_dir(writer.conf.capsules[c_id]["capsule_dir"])
+ util.make_dir(writer.conf.capsules[c_id]["gemlog_dir"])
+ end
+
+ -- Set lang
+ lang = require("lang." .. writer.conf.app_lang)
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)
+writer.docs.load_config = [[
+ Check whether there is an existing config and load it if found. Exit if a
+ capsule is not found.
+ ]]
+function writer.load_config(cap_id)
+ writer.conf.config_dir = util.replace_shell_vars(env.defaults.config_dir)
+ local config_file = util.read_file(writer.conf.config_dir .. "/" ..
+ env.defaults.config_files.config)
+ local config_new = false
- 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)
+ -- No config found
+ if (config_file == "") and (not config_new) then
+ writer.gen_config()
+ config_new = true
+ end
+
+ -- Config found, read the configuration and load the values to an
+ -- associative table, writer.conf.
+ if (config_file ~= "") and (not config_new) then
+ -- Check capsule id exists, abort if no valid capsule found
+ if (cap_id == nil) or (string.find(cap_id, " ") ~= nil) then
+ writer.conf.cap_id = env.general.main_capsule
+ else
+ writer.conf.cap_id = cap_id
+ end
+ local cap_label = "%[" .. env.toml_vars.capsules .. "%." ..
+ tostring(cap_id) .. "%]"
+ if (string.find(config_file, cap_label) == nil) and
+ (writer.conf.cap_id ~= env.general.main_capsule) then
+ print(lang.errs.invalid_cap_id)
+ os.exit()
+ end
+
+ writer.parse_config(config_file)
+ end
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 = {}
+writer.docs.add_gemtext = [[
+ Given a title and mode, create a new gemtext file. Supported modes: page,
+ post.
+ ]]
+function writer.add_gemtext(title, mode)
+ local file_name = title
+ local post_title = title
+ if ((title == "") or (title == nil)) and (mode == "post") then
+ file_name = os.date(writer.conf.post_slug_date_format) .. "-" ..
+ writer.conf.post_slug .. writer.conf.post_ext
+ post_title = writer.conf.post_slug
+ elseif ((title == "") or (title == nil)) and (mode == "page") then
+ file_name = writer.conf.page_slug .. writer.conf.post_ext
+ post_title = writer.conf.page_slug
+ else
+ -- Strip punctuation from the title and convert to file name
+ local delim = "delim-" .. tostring(math.random(90000000000))
+ file_name = string.gsub(file_name, " ", delim)
+ if mode == "post" then
+ file_name = os.date(writer.conf.post_slug_date_format) .. "-" ..
+ string.gsub(file_name, "%p", "") .. writer.conf.post_ext
+ else
+ file_name = string.gsub(file_name, "%p", "") .. writer.conf.post_ext
+ end
+ file_name = string.lower(string.gsub(file_name, delim, "-"))
+ end
+
+ local text = lang.post
+ local file_path = writer.conf.cap.gemlog_dir .. "/" .. file_name
+ if mode == "page" then
+ text = lang.page
+ file_path = writer.conf.cap.capsule_dir .. "/" .. file_name
+ end
+ text = string.gsub(text, lang.tpl_vars.post.date,
+ os.date(writer.conf.post_date_format))
+ text = string.gsub(text, lang.tpl_vars.post.author, writer.conf.cap.author)
+ text = string.gsub(text, lang.tpl_vars.post.title, post_title)
+
+ util.write_file(file_path, text)
+ return file_name
+end
+
+
+writer.docs.get_posts = [[
+ Get a list of gemlog posts and return the file names and their contents in a
+ table.
+ ]]
+function writer.get_posts()
+ local ls = util.ls_grep(writer.conf.cap.gemlog_dir, writer.conf.post_ext)
+ local posts = {}
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
+ if files_list[f] ~= writer.conf.cap.index_page then
n = n + 1
- entries[n] = { files_list[f], util.read_file(writer.conf.output_dir ..
+ posts[n] = { files_list[f], util.read_file(writer.conf.cap.gemlog_dir ..
"/" .. files_list[f]) }
end
end
- return entries
+ return posts
end
-function writer.get_entries_meta()
+writer.docs.get_posts_meta = [[
+ Extract gemlog posts metadata and return them as an associative table.
+ ]]
+function writer.get_posts_meta()
local meta = {}
- local entries = writer.get_entries()
- for e = 1, #entries do
+ local posts = writer.get_posts()
+ for e = 1, #posts do
meta[e] = {}
- if string.find(entries[e][2], "# ") == nil then
- meta[e]["title"] = entries[e][1]
- meta[e]["content"] = entries[e][2]
+ if string.find(posts[e][2], "# ") == nil then
+ meta[e]["title"] = posts[e][1]
+ meta[e]["content"] = posts[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]))
+ meta[e]["title"] = util.extract_str(posts[e][2], "# ", "\n")
+ hi1, hi2 = string.find(posts[e][2], "# ")
+ meta[e]["content"] = string.sub(posts[e][2], hi1,
+ string.len(posts[e][2]))
end
- if string.find(entries[e][2], "author: ") == nil then
- meta[e]["author"] = writer.conf.author
+ if string.find(posts[e][2], "author: ") == nil then
+ meta[e]["author"] = writer.conf.cap.author
else
- meta[e]["author"] = util.extract_str(entries[e][2], "author: ", "\n")
+ meta[e]["author"] = util.extract_str(posts[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 string.find(posts[e][2], "date: ") == nil then
+ meta[e]["date"] = util.extract_file_date(writer.conf.cap.gemlog_dir,
+ posts[e][1])
if (meta[e]["date"] == "") or (meta[e]["date"] == nil) then
- meta[e]["date"] = os.date(writer.conf.entry_date_format)
+ meta[e]["date"] = os.date(writer.conf.post_date_format)
end
else
- meta[e]["date"] = util.extract_str(entries[e][2], "date: ", "\n")
+ meta[e]["date"] = util.extract_str(posts[e][2], "date: ", "\n")
end
- if string.find(entries[e][2], "summary: ") == nil then
+ if string.find(posts[e][2], "summary: ") == nil then
meta[e]["summary"] = meta[e]["title"]
else
- meta[e]["summary"] = util.extract_str(entries[e][2], "summary: ", "\n")
+ meta[e]["summary"] = util.extract_str(posts[e][2], "summary: ", "\n")
end
- if string.find(entries[e][2], "tags: ") == nil then
+ if string.find(posts[e][2], "tags: ") == nil then
meta[e]["tags"] = ""
else
- meta[e]["tags"] = util.extract_str(entries[e][2], "tags: ", "\n")
+ meta[e]["tags"] = util.extract_str(posts[e][2], "tags: ", "\n")
end
- meta[e]["url"] = writer.conf.url .. "/" .. entries[e][1]
+ meta[e]["url"] = writer.conf.cap.log_url .. "/" .. posts[e][1]
end
return meta
end
+writer.docs.gen_index_page = [[
+ Generate a gemlog index page listing all gemlog posts.
+ ]]
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"]
+ local index_text = util.replace_vars(lang.index, lang.tpl_vars.log,
+ writer.conf.cap)
+ local posts_text = ""
+ -- Reverse insert links to log posts, newest first
+ for e = #writer.posts, 1, -1 do
+ local post_date = util.split_str(writer.posts[e]["date"], "(.*)[T]")[1]
+ -- If post date cannot be extracted, revert to current date
+ if post_date == nil then
+ post_date = os.date(writer.conf.post_date_format)
+ end
+ -- Get post filename from the url
+ local fi1, fi2 = string.find(writer.posts[e]["url"],
+ writer.conf.post_file_pattern)
+ posts_text = posts_text .. "\n=> " ..
+ string.sub(writer.posts[e]["url"], fi1 + 1, fi2) .. " " ..
+ post_date .. " " .. writer.posts[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)
+ index_text = string.gsub(index_text, lang.tpl_vars.index.posts,
+ posts_text)
+ util.write_file(writer.conf.cap.gemlog_dir .. "/" ..
+ writer.conf.cap.index_page, index_text)
end
+writer.docs.gen_atom_feed = [[
+ Generate an Atom feed of gemlog posts.
+ ]]
function writer.gen_atom_feed()
local feed_meta = {
- date = os.date(writer.conf.entry_date_format),
- url = writer.conf.url .. "/" .. writer.conf.atom_file,
+ date = os.date(writer.conf.post_date_format),
+ url = writer.conf.cap.url .. "/" .. writer.conf.cap.atom_feed,
}
- 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
+ local feed_text = util.replace_vars(lang.atom_header, lang.tpl_vars.log,
+ writer.conf.cap)
+ feed_text = util.replace_vars(feed_text, lang.tpl_vars.feed, feed_meta)
+ local feed_post = ""
+ -- Reverse insert log posts, newest first
+ for e = #writer.posts, 1, -1 do
+ feed_post = lang.atom_entry
+ -- Escape html entities in post contents. "%" is also a special character
+ -- for string.gsub() and similar functions, and will cause an error if not
+ -- escaped.
+ writer.posts[e]["content"] =
+ util.replace_html_entities(writer.posts[e]["content"])
+ feed_post = util.replace_vars(feed_post, lang.tpl_vars.post,
+ writer.posts[e])
+ feed_text = feed_text .. feed_post
end
- feed_text = feed_text .. tpl.atom_footer
- util.write_file(writer.conf.output_dir .. "/" .. writer.conf.atom_file,
- feed_text)
+ feed_text = feed_text .. lang.atom_footer
+ util.write_file(writer.conf.cap.gemlog_dir .. "/" ..
+ writer.conf.cap.atom_feed, feed_text)
end
+writer.docs.publish = [[
+ Transfer a capsule's contents to a remote location.
+ ]]
function writer.publish()
- os.execute(writer.conf.rsync_exec .. " " .. writer.conf.rsync_options ..
- " " .. writer.conf.output_dir .. " " .. writer.conf.rsync_remote)
+ if writer.conf.cap.transfer_mode == "rsync" then
+ os.execute(writer.conf.cap.rsync_exec .. " " ..
+ writer.conf.cap.rsync_options .. " " .. writer.conf.cap.capsule_dir ..
+ "/ " .. writer.conf.cap.rsync_dest .. "/")
+ else
+ os.execute(writer.conf.cap.scp_exec .. " -r " ..
+ writer.conf.cap.capsule_dir .. "/* " .. writer.conf.cap.scp_target)
+ end
end
local cli = {}
+cli.docs = {}
+
+cli.docs.handle_args = [[
+ Match command-line options to the respective application functions, passing
+ any additional arguments to the functions.
+ ]]
function cli.handle_args(args)
- if (args[1] ~= "help") and (args[1] ~= "version") then
- writer.load_config(conf)
+ if (args[1] ~= lang.opts.help) and (args[1] ~= lang.opts.version) then
+ writer.load_config(args[2])
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
+ if (args[1] == lang.opts.index) or (args[1] == lang.opts.publish) then
+ writer.posts = writer.get_posts_meta()
+ if writer.conf.cap.gen_index_page then writer.gen_index_page() end
+ if writer.conf.cap.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
+ if args[1] == lang.opts.config then
+ writer.load_config()
+ print(lang.msgs.load_config .. writer.conf.config_dir)
+
+ elseif (args[1] == lang.opts.post) or (args[1] == lang.opts.page) then
+ local file_name = ""
+ if args[3] ~= nil then
+ file_name = writer.add_gemtext(args[3], args[1])
else
- slug = os.date("%Y-%m-%d") .. "-" .. slug
+ file_name = writer.add_gemtext(args[2], args[1])
end
- writer.add_entry(slug)
- print(writer.msg.add_entry .. slug .. writer.conf.entry_ext)
+ print(lang.msgs.add_gemtext .. file_name)
- 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] == lang.opts.index then
+ print(lang.msgs.index)
- elseif args[1] == "index" then
- print(writer.msg.index)
-
- elseif args[1] == "publish" then
+ elseif args[1] == lang.opts.publish then
writer.publish()
- print(writer.msg.publish)
+ print(lang.msgs.publish)
- elseif args[1] == "help" then
- print(writer.msg.help)
+ elseif args[1] == lang.opts.help then
+ print(env.app.exec_name .. lang.msgs.help)
- elseif args[1] == "version" then
- print(writer.app.name .. " " .. writer.app.version ..
- " (" .. writer.app.last_updated .. ")")
+ elseif args[1] == lang.opts.version then
+ print(env.app.name .. " " .. env.app.version ..
+ " (" .. env.app.last_updated .. ")")
end
end
diff --git a/lang/en.lua b/lang/en.lua
new file mode 100644
index 0000000..745f421
--- /dev/null
+++ b/lang/en.lua
@@ -0,0 +1,145 @@
+local en = {}
+
+-- Templates
+en.tpl_vars = {
+ post = {
+ author = "{{ post_author }}",
+ content = "{{ post_content }}",
+ date = "{{ post_date }}",
+ summary = "{{ post_summary }}",
+ tags = "{{ post_tags }}",
+ title = "{{ post_title }}",
+ url = "{{ post_url }}",
+ },
+ feed = {
+ date = "{{ feed_date }}",
+ url = "{{ feed_url }}",
+ },
+ index = {
+ posts = "{{ posts }}",
+ },
+ log = {
+ author = "{{ log_author }}",
+ subtitle = "{{ log_subtitle }}",
+ title = "{{ log_title }}",
+ log_url = "{{ log_url }}",
+ },
+}
+
+en.atom_header = [[
+
+ {{ log_url }}
+ {{ log_title }}
+ {{ log_subtitle }}
+ {{ feed_date }}
+
+ {{ log_author }}
+
+
+
+]]
+
+en.atom_entry = [[
+
+ {{ post_url }}
+
+ " .. [[
+
+
+ {{ post_date }}
+
+ {{ post_author }}
+
+
+
+ " .. [[
+
+
+
+ " .. [[
+
+
+
+]]
+
+en.atom_footer = [[]]
+
+en.post = [[---
+date: {{ post_date }}
+---
+
+# {{ post_title }}
+
+
+
+
+## Links
+
+=> gemini:// link
+=> gemini:// link (img)
+=> https:// link (https)]]
+
+en.index = [[
+# {{ log_title }}
+{{ posts }}
+]]
+
+en.page = [[
+# {{ post_title }}
+
+## Heading 2
+### Heading 3
+
+List:
+
+*
+*
+*
+
+```
+Preformatted text
+```
+
+
+## Links
+
+=> gemini:// link
+=> gemini:// link (img)
+=> https:// link (https)]]
+
+-- App command options and messages output
+en.opts = {
+ config = "config",
+ page = "page",
+ post = "post",
+ index = "index",
+ publish = "publish",
+ help = "help",
+ version = "version",
+}
+
+en.msgs = {
+ add_gemtext = "Created ",
+ help = [[ [options] [capsule] [title]
+
+Options:
+
+config Generate a config directory
+page [capsule] [title] Add a new page with the given title
+post [capsule] [title] Add a new gemlog post with the given title
+index Generate an index page and feed of posts
+publish Index and copy posts remotely using scp
+help Show this help message
+version Print version info]],
+ index = "Created index page and feed.",
+ load_config = [[Created config files. Please edit them with the correct
+details before proceeding. The config files can be found at:
+]],
+ publish = "Published capsule.",
+}
+
+en.errs = {
+ invalid_cap_id = "Error: unknown capsule id.",
+}
+
+return en
diff --git a/readme.md b/readme.md
new file mode 100644
index 0000000..45a2dfd
--- /dev/null
+++ b/readme.md
@@ -0,0 +1,63 @@
+# Gemwriter
+
+A little command-line helper for publishing [Gemini] sites or "capsules".
+
+
+```
+Options:
+
+config Generate a config directory
+page [capsule] [title] Add a new page with the given title
+post [capsule] [title] Add a new gemlog post with the given title
+index Generate an index page and feed of posts
+publish Index and copy posts remotely using scp
+help Show this help message
+version Print version info
+```
+
+
+## Requirements
+
+- Linux or Unix-based OS
+- [Lua] 5.4 (other versions >= 5.1 will probably be fine but are untested)
+- [scp] or [rsync], to transfer files remotely
+
+
+## Build
+
+- Install Lua and [luastatic].
+
+- Clone this repository and change into the directory. Run:
+
+ ```
+ luastatic gemwriter.lua env.lua util.lua lang/en.lua /usr/lib/liblua.so -I/usr/include -o gemwriter
+ ```
+
+ The paths to `liblua.so` and the development headers (i.e.
+ `/usr/include/lua.h`) may need to be adjusted for your distribution.
+
+- Move the `gemwriter` executable to a location in your `$PATH`.
+
+
+## Quick start
+
+1. Generate a new config: `gemwriter config`
+
+2. Edit `~/.config/gemwriter/config.toml` with the correct details about your
+ capsule and gemlog.
+
+3. Create a new gemlog post: `gemwriter post "Hello World!"`
+
+4. Publish your capsule: `gemwriter publish`
+
+
+## License
+
+BSD-3-Clause
+
+
+[Gemini]: https://gemini.circumlunar.space/
+[Lua]: https://www.lua.org/
+[scp]: https://www.openssh.com/
+[rsync]: https://rsync.samba.org/
+[luastatic]: https://github.com/ers35/luastatic
diff --git a/template.lua b/template.lua
deleted file mode 100644
index f003cf4..0000000
--- a/template.lua
+++ /dev/null
@@ -1,96 +0,0 @@
-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 }}
-
- " .. [[
-
-
- {{ 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
index bc27496..4fae930 100644
--- a/util.lua
+++ b/util.lua
@@ -12,10 +12,114 @@ function util.split_str(str, sep)
end
+function util.split_lines(str)
+ local tbl = {}
+ local si = 1
+ for i = 1, #str do
+ if string.find(string.sub(str, i, i), "\n") ~= nil then
+ tbl[#tbl + 1] = string.sub(str, si, i - 1)
+ si = i + 1
+ end
+ 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)
+ local fi1, fi2 = string.find(full_str, find_str, 1)
+ local ei1, ei2 = string.find(full_str, end_str, fi2)
+ if end_str == "\n" then
+ return string.sub(full_str, fi2 + 1, ei1 - 2)
+ else
+ return string.sub(full_str, fi2 + 1, ei1 - 1)
+ end
+end
+
+
+function util.to_bool(str)
+ local bool = { ["true"] = true, ["false"] = false }
+ return bool(str)
+end
+
+
+function util.table_keys(tbl)
+ local keys = {}
+ local n = 0
+ for k, v in pairs(tbl) do
+ n = n + 1
+ keys[n] = k
+ end
+ return keys
+end
+
+
+function util.has_key(tbl, str)
+ local keys = util.table_keys(tbl)
+ for k = 1, #keys do
+ if str == keys[k] then
+ do return true end
+ end
+ end
+ return false
+end
+
+
+function util.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 util.replace_html_entities(str)
+ local html_ents = {
+ percent = { "%%", "%" },
+ less_than = { "<", "<" },
+ greater_than = { ">", ">" },
+ left_bracket = { "%[", "[" },
+ right_bracket = { "%]", "]" },
+ }
+ local text = str
+ for e, c in pairs(html_ents) do
+ text = string.gsub(text, c[1], c[2])
+ end
+ return text
+end
+
+
+function util.make_dir(dir)
+ os.execute("test -d " .. dir .. " || mkdir -p " .. dir)
+end
+
+
+function util.ls_grep(dir, str)
+ local ls_cmd = io.popen("ls " .. dir .. " | grep " .. str)
+ return ls_cmd:read("*a")
+end
+
+
+function util.get_shell_var(str)
+ local var = os.getenv(str)
+ if var == "" then
+ return nil
+ else
+ return var
+ end
+end
+
+
+function util.replace_shell_vars(str)
+ -- Replace common shell variable paths
+ if string.find(str, "%$") ~= nil then
+ local shell_vars = { "HOME", "USER" }
+ for v = 1, #shell_vars do
+ str = string.gsub(str, "$" .. shell_vars[v],
+ util.get_shell_var(shell_vars[v]))
+ end
+ end
+ return str
end
@@ -30,18 +134,18 @@ end
function util.read_file(file)
local fh = io.open(file, "r")
local text = ""
- io.input(fh)
- text = io.read("*a")
- io.close(fh)
+ if fh ~= nil then
+ text = fh:read("*a")
+ fh:close()
+ end
return text
end
function util.write_file(file, str)
local fh = io.open(file, "w")
- io.output(fh)
- io.write(str)
- io.close(fh)
+ fh:write(str)
+ fh:close()
end
return util