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)