gemwriter/gemwriter.lua

392 lines
13 KiB
Lua

local env = require("env")
local lang = require("lang.en")
local util = require("util")
local writer = {}
writer.docs = {}
writer.conf = {}
writer.posts = {}
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
writer.docs.parse_config = [[
Read the config and template files, 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
-- 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)
-- Custom templates override lang defaults (ignore config)
for name, file in pairs(env.defaults.config_files) do
if name ~= "config" then
local template = util.read_file(writer.conf.config_dir .. "/" .. file)
if template ~= "" then lang[name] = template end
end
end
end
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
-- 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
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.cap.index_page then
n = n + 1
posts[n] = { files_list[f], util.read_file(writer.conf.cap.gemlog_dir ..
"/" .. files_list[f]) }
end
end
return posts
end
writer.docs.get_posts_meta = [[
Extract gemlog posts metadata and return them as an associative table.
]]
function writer.get_posts_meta()
local meta = {}
local posts = writer.get_posts()
for e = 1, #posts do
meta[e] = {}
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(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(posts[e][2], "author: ") == nil then
meta[e]["author"] = writer.conf.cap.author
else
meta[e]["author"] = util.extract_str(posts[e][2], "author: ", "\n")
end
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.post_date_format)
end
else
meta[e]["date"] = util.extract_str(posts[e][2], "date: ", "\n")
end
if string.find(posts[e][2], "summary: ") == nil then
meta[e]["summary"] = meta[e]["title"]
else
meta[e]["summary"] = util.extract_str(posts[e][2], "summary: ", "\n")
end
if string.find(posts[e][2], "tags: ") == nil then
meta[e]["tags"] = ""
else
meta[e]["tags"] = util.extract_str(posts[e][2], "tags: ", "\n")
end
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 = 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, 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.post_date_format),
url = writer.conf.cap.url .. "/" .. writer.conf.cap.atom_feed,
}
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
writer.posts[e]["content"] = util.replace_feed_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 .. lang.atom_footer
feed_text = util.replace_feed_entities(feed_text, "post")
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()
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] ~= lang.opts.help) and (args[1] ~= lang.opts.version) then
writer.load_config(args[2])
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] == 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
file_name = writer.add_gemtext(args[2], args[1])
end
print(lang.msgs.add_gemtext .. file_name)
elseif args[1] == lang.opts.index then
print(lang.msgs.index)
elseif args[1] == lang.opts.publish then
writer.publish()
print(lang.msgs.publish)
elseif args[1] == lang.opts.help then
print(env.app.exec_name .. lang.msgs.help)
elseif args[1] == lang.opts.version then
print(env.app.name .. " " .. env.app.version ..
" (" .. env.app.last_updated .. ")")
end
end
cli.handle_args(arg)