From 25b94de01e2d00bcba46dc835f97d56ee7c916c4 Mon Sep 17 00:00:00 2001 From: Jordan Irwin Date: Sat, 28 Aug 2021 20:16:01 -0700 Subject: [PATCH] LDoc: add scripts for creating versioned docs --- .ldoc/auxf.ld | 133 +++++++++++++++++ .ldoc/build_versioned_docs.sh | 117 +++++++++++++++ .ldoc/config.ld | 261 +++++++++++++++++++++++++++++++++- .ldoc/gendoc.sh | 66 ++++++++- .ldoc/parse_readme.py | 179 +++++++++++++++++++++++ .ldoc/tags.ld | 80 +++++++++++ 6 files changed, 826 insertions(+), 10 deletions(-) create mode 100644 .ldoc/auxf.ld create mode 100755 .ldoc/build_versioned_docs.sh mode change 100644 => 100755 .ldoc/gendoc.sh create mode 100755 .ldoc/parse_readme.py create mode 100644 .ldoc/tags.ld diff --git a/.ldoc/auxf.ld b/.ldoc/auxf.ld new file mode 100644 index 0000000..c0c9f59 --- /dev/null +++ b/.ldoc/auxf.ld @@ -0,0 +1,133 @@ + +-- Place this file in mod's ".ldoc" directory + + +local package = import("package") +local require = import("require") + +-- START: string + +string.ltrim = function(st, delim) + delim = delim or " " + + while st:find(delim) == 1 do + st = st:sub(2) + end + + return st +end + +string.rtrim = function(st, delim) + delim = delim or " " + + while st:sub(#st) == delim do + st = st:sub(1, #st-1) + end + + return st +end + +string.trim = function(st, delim) + return string.rtrim(string.ltrim(st, delim), delim) +end + +string.split = function(st, delim) + delim = delim or " " + + -- trim up + st = string.trim(st, delim) + + local dd = delim .. delim + while st:find(dd) do + st:gsub(dd, delim) + end + + local new_table = {} + local idx = st:find(delim) + while idx do + table.insert(new_table, st:sub(1, idx-1)) + st = st:sub(idx+#delim) + idx = st:find(delim) + end + + if st ~= "" then + table.insert(new_table, st) + end + + return new_table +end + +-- END: string + + +-- START: system + +sys = { + platform = "unix", + path_delim = package.config:sub(1, 1) +} + +if sys.path_delim == "\\" then + sys.platform = "win" +end + +-- END: system + + +-- START: filesystem + +local normpath = function(path) + local retval + if sys.platform == "win" then + retval = path:gsub("^/c", "C:"):gsub("/", sys.path_delim) + else + retval = path:gsub("\\", sys.path_delim):gsub("//", sys.path_delim) + end + + return retval +end + +local path = require("pl.path") +fs = { + copy = require("pl.file").copy, + attr = path.attrib, + exists = path.exists, + isfile = path.isfile, + isdir = path.isdir, + mkdir = path.mkdir, + --normpath = path.normpath, + normpath = normpath, +} + +local mkdirs = function(dir) + dir = fs.normpath(dir) + + local path_root = dir:find(sys.path_delim) == 1 + local parts = string.split(dir, sys.path_delim) + + local path = "" + if sys.platform == "unix" and path_root then + path = sys.path_delim + end + + for _, d in ipairs(parts) do + path = path .. d + + if fs.isfile(path) then + print("ERROR: [mkdir] file exists: " .. path) + return false + end + + if not fs.isdir(path) then + fs.mkdir(path) + end + + path = path .. sys.path_delim + end + + return fs.isdir(dir) +end + +fs.mkdirs = mkdirs + +-- END: filesystem diff --git a/.ldoc/build_versioned_docs.sh b/.ldoc/build_versioned_docs.sh new file mode 100755 index 0000000..2ddd73c --- /dev/null +++ b/.ldoc/build_versioned_docs.sh @@ -0,0 +1,117 @@ +#!/usr/bin/env bash + +# place this file in mod ".ldoc" directory + + +d_ldoc="$(dirname $(readlink -f $0))" + +cd "${d_ldoc}/.." + +d_root="$(pwd)" +d_export="${d_export:-${d_root}/docs/reference}" + +cmd_ldoc="${d_root}/../ldoc/ldoc.lua" +if test -f "${cmd_ldoc}"; then + if test ! -x "${cmd_ldoc}"; then + chmod +x "${cmd_ldoc}" + fi +else + cmd_ldoc="ldoc" +fi + +# clean old files +rm -rf "${d_export}" + +# store current branch +main_branch="$(git branch --show-current)" + +html_out="\n\n\n\n\n\n\n" + +cd "${d_root}" +git checkout ${main_branch} + +echo -e "${html_out}" > "${d_export}/index.html" + +echo -e "\nDone!" diff --git a/.ldoc/config.ld b/.ldoc/config.ld index d0eb2c1..0987f7b 100644 --- a/.ldoc/config.ld +++ b/.ldoc/config.ld @@ -1,9 +1,260 @@ -project = "wdata" -title = "World Data Manager" + +-- Place this file in mod's ".ldoc" directory + + +local env = { + import = import, +} + +local loadfile = import("loadfile") +local type = import("type", env) +local string = import("string", env) +local tostring = import("tostring", env) +local tonumber = import("tonumber", env) +local table = import("table", env) +local pairs = import("pairs", env) +local ipairs = import("ipairs", env) + +local dofile = function(f) + return loadfile(f, "t", env)() +end +env["dofile"] = dofile + +dofile(".ldoc/auxf.ld") + +local args = import("args") +local d_data = args.dir .. "/data" + + +project = "World Data Manager" format = "markdown" -not_luadoc = true boilerplate = false +not_luadoc = true +favicon = "https://www.minetest.net/media/icon.svg" +readme = ".ldoc/README.md" -file = "api.lua" +local version = "1.1" -new_type("falias", "Aliases") +file = {"api.lua"} + +new_type("setting", "Settings") +new_type("node", "Nodes") +new_type("chatcmd", "Chat Commands") + +local function video_frame(src) + return '' +end + +local tags +tags, custom_tags = dofile(".ldoc/tags.ld") + + +-- START: handling items to prevent re-parsing + +local registered_items = {} + +local function is_registered(item) + if not registered_items[item.type] then return false end + + for _, tbl in ipairs(registered_items[item.type]) do + if item == tbl then + return true + end + end + + return false +end + +local function register(item) + if not registered_items[item.type] then + registered_items[item.type] = {} + end + + if not is_registered(item) then + table.insert(registered_items[item.type], item) + end +end + +-- END: + + +local function format_string(value, flags) + local st = '' .. value:gsub("_", "\\_") .. '' +end + +local format_setting_tag = function(desc, value) + return "\n- " .. format_string("`" .. desc .. ":`", {size="80%"}) .. " " .. value +end + + +local setting_handler = function(item) + local tags = { + {"settype", "type"}, + {"default"}, + {"min", "minimum value"}, + {"max", "maximum value"}, + } + + local def = { + ["settype"] = format_setting_tag("type", "string"), + } + + for _, t in ipairs(tags) do + local name = t[1] + local desc = t[2] + if not desc then desc = name end + + local value = item.tags[name] + if type(value) == "table" then + if #value > 1 then + local msg = item.file.filename .. " (line " .. item.lineno + .. "): multiple instances of tag \"" .. name .. "\" found" + if error then + error(msg) + elseif print then + print("WARNING: " .. msg) + end + end + + if value[1] then + def[name] = format_setting_tag(desc, value[1]) + end + end + end + + item.description = item.description .. "\n\n**Definition:**\n" .. def.settype + for _, t in ipairs({def.default, def.min, def.max}) do + if t then + item.description = item.description .. t + end + end + + return item +end + +local chatcmd_handler = function(item) + for _, p in ipairs(item.params) do + if item.modifiers.param[p].opt then + item.name = item.name .. " [" .. p .. "]" + else + item.name = item.name .. " <" .. p .. ">" + end + end + + return item +end + +function custom_display_name_handler(item, default_handler) + if not is_registered(item) then + if item.type == "setting" then + item = setting_handler(item) + elseif item.type == "chatcmd" then + item = chatcmd_handler(item) + end + + local parse_tags = {"priv", "note"} + for _, pt in ipairs(parse_tags) do + local tvalues = item.tags[pt] + if tvalues then + local tstring = "" + + local title = tags.get_title(pt) + if title then + tstring = tstring .. "\n\n### " .. title .. ":\n" + end + + for _, tv in ipairs(tvalues) do + tstring = tstring .. "\n- " .. tags.format(pt, tv) + end + + item.description = item.description .. tstring + end + end + end + + register(item) + return default_handler(item) +end + + +local custom_see_links = { + ["ObjectRef"] = "https://minetest.gitlab.io/minetest/class-reference/#objectref", + ["PlayerMetaRef"] = "https://minetest.gitlab.io/minetest/class-reference/#playermetaref", + ["ItemDef"] = "https://minetest.gitlab.io/minetest/definition-tables/#item-definition", + ["ItemStack"] = "https://minetest.gitlab.io/minetest/class-reference/#itemstack", + ["groups"] = "https://minetest.gitlab.io/minetest/groups/", + ["entity_damage_mechanism"] = "https://minetest.gitlab.io/minetest/entity-damage-mechanism/", + ["vector"] = "https://minetest.gitlab.io/minetest/representations-of-simple-things/#positionvector", + ["SoundParams"] = "https://minetest.gitlab.io/minetest/sounds/", + ["currency"] = "https://content.minetest.net/packages/VanessaE/currency/", +} + +local function format_custom_see(name, section) + local url = custom_see_links[name] + if not url then + url = "" + end + + if not name then + name = "" + end + + return name, url +end + + +custom_see_handler("^(ObjectRef)$", function(name, section) + return format_custom_see(name, section) +end) + +custom_see_handler("^(PlayerMetaRef)$", function(name, section) + return format_custom_see(name, section) +end) + +custom_see_handler("^(ItemDef)$", function(name, section) + return format_custom_see(name, section) +end) + +custom_see_handler("^(groups)$", function(name, section) + return format_custom_see(name, section) +end) + +custom_see_handler("^(entity_damage_mechanism)$", function(name, section) + return format_custom_see(name, section) +end) + +custom_see_handler("^(ItemStack)$", function(name, section) + return format_custom_see(name, section) +end) + +custom_see_handler("^(vector)$", function(name, section) + return format_custom_see(name, section) +end) + +custom_see_handler("^(SoundParams)$", function(name, section) + return format_custom_see(name, section) +end) + +custom_see_handler("^(currency)$", function(name, section) + return format_custom_see(name, section) +end) + +-- reference items from separate modules +custom_see_handler("^(.*) (.*)$", function(name, section) + return section, name .. ".html#" .. section +end) + +-- reference external pages +custom_see_handler("^(.*) (http.*)$", function(name, section) + return name, section +end) diff --git a/.ldoc/gendoc.sh b/.ldoc/gendoc.sh old mode 100644 new mode 100755 index fea78e5..43636bb --- a/.ldoc/gendoc.sh +++ b/.ldoc/gendoc.sh @@ -2,13 +2,69 @@ d_ldoc="$(dirname $(readlink -f $0))" d_root="$(dirname ${d_ldoc})" -d_docs="${d_root}/docs" f_config="${d_ldoc}/config.ld" +d_export="${d_export:-${d_root}/docs/reference}" cd "${d_root}" -# Clean old files -rm -rf "${d_docs}/reference" +# clean old files +rm -rf "${d_export}" -# Create new files -ldoc -c "${f_config}" -d "${d_docs}/reference" "${d_root}" +rm -f "${d_ldoc}/README.md" + +if test ! -x "${d_ldoc}/parse_readme.py"; then + chmod +x "${d_ldoc}/parse_readme.py" +fi + +"${d_ldoc}/parse_readme.py" + +vinfo="v$(grep "^version = " "${d_root}/mod.conf" | head -1 | sed -e 's/version = //')" +d_data="${d_export}/${vinfo}/data" + +# use temp config so sound previews can be linked to master branch +f_config_tmp="${d_ldoc}/config_tmp.ld" +cp "${f_config}" "${f_config_tmp}" +sed -i -e 's/local version = .*$/local version = master/' "${f_config_tmp}" + +# create new files +ldoc --UNSAFE_NO_SANDBOX -c "${f_config_tmp}" -d "${d_export}/${vinfo}" "${d_root}" +retval=$? + +# check exit status +if test ${retval} -ne 0; then + echo -e "\nan error occurred (ldoc return code: ${retval})" + exit ${retval} +fi + +# show version info +echo -e "\nfinding ${vinfo}..." +for html in $(find "${d_export}/${vinfo}" -type f -name "*.html"); do + sed -i -e "s|^

World Data Manager

$|

World Data Manager (${vinfo})

|" "${html}" +done + +# copy screenshot +screenshot="${d_root}/screenshot.png" +if test -f "${screenshot}"; then + cp "${d_root}/screenshot.png" "${d_export}/${vinfo}" +fi + +# cleanup +rm -f "${d_ldoc}/README.md" "${f_config_tmp}" + +# copy textures to data directory +if test -d "${d_root}/textures"; then + printf "\ncopying textures ..." + mkdir -p "${d_data}" + texture_count=0 + for png in $(find "${d_root}/textures" -maxdepth 1 -type f -name "*.png"); do + if test -f "${d_data}/$(basename ${png})"; then + echo "WARNING: not overwriting existing file: ${png}" + else + cp "${png}" "${d_data}" + texture_count=$((texture_count + 1)) + printf "\rcopied ${texture_count} textures" + fi + done +fi + +echo -e "\n\nDone!" diff --git a/.ldoc/parse_readme.py b/.ldoc/parse_readme.py new file mode 100755 index 0000000..c693053 --- /dev/null +++ b/.ldoc/parse_readme.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python + +import sys, os, codecs, errno, shutil + + +f_script = os.path.realpath(__file__) +d_ldoc = os.path.dirname(f_script) +d_root = os.path.dirname(d_ldoc) + +vinfo = "master" +if len(sys.argv) > 1: + vinfo = sys.argv[1] + +f_readme_src = os.path.join(d_root, "README.md") +f_readme_tgt = os.path.join(d_ldoc, "README.md") + +if not os.path.isfile(f_readme_src): + print("ERROR: source README.md does not exists") + sys.exit(errno.ENOENT) + +print("\nparsing {}".format(f_readme_src)) + +buffer = codecs.open(f_readme_src, "r", "utf-8") +if not buffer: + print("ERROR: could not open source README.md for reading") + sys.exit(1) + +r_data = buffer.read() +buffer.close() + +r_data = r_data.replace("\r\n", "\n").replace("\r", "\n") +r_lines = r_data.split("\n") +r_lines_pre = [] +r_lines_post = [] +table = [] + +links = {} + +for line in r_lines: + if line.startswith("[") and "]: " in line: + link = line.lstrip("[").split("]: ") + links[link[0]] = link[1] + +def escape_underscore(li, in_code=False): + if in_code: + return li + + characters = [] + for c in li: + if c == "`": + if not in_code: + in_code = True + else: + in_code = False + + if c == "_" and not in_code: + c = "\\_" + + characters.append(c) + + return "".join(characters) + + +mid = False +post = False +indent = False +in_code = False +for line in r_lines: + if line.startswith("###"): + line = line.rstrip(":") + elif line.startswith("```"): + if not in_code: + in_code = True + else: + in_code = False + + ''' + if line.count("_") > 1: + line = line.replace("_", "\\_") + ''' + + if line == "![screenshot](screenshot.png)": + line = '' + elif line == "See [sources.md](sources.md)": + line = "See sources.md".format(vinfo) + elif line.startswith("|"): + mid = True + + if mid: + if line.startswith("|"): + if line.startswith("| Filename"): + line = "{}\n".format(line.lstrip("| ").rstrip(" |")).replace("_", "\\_") + else: + #if line.replace("-", "").replace("|", "").strip() == "": + if line.strip(" -|") == "": + continue + + #line = "- {}".format(line.lstrip("|").rstrip(" |")) + + line = line.lstrip("| ").rstrip(" |") + cols = line.split("|") + for idx in range(len(cols)): + cols[idx] = cols[idx].strip() + + url = None + sname = cols[0] + lname = None + author = cols[1] + lic = cols[2] + notes = None + if len(cols) > 3: + notes = cols[3] + + if "][]" in sname: + sname = sname.strip("[]") + lname = sname + elif "][" in sname: + tmp = sname.split("][") + sname = tmp[0].strip("[]") + lname = tmp[1].strip("[]") + + if lname and lname in links: + url = links[lname] + + sname = sname.replace("_", "\_") + author = author.replace("_", "\_").replace("↓", "🡇") + + line = "- " + if url: + line = '{} '.format(line, url) + line = "{}{}".format(line, sname) + if url: + line = "{}".format(line) + line = "{} by {} ({})".format(line, author, lic) + if notes: + line = "{} ({})".format(line, notes) + + while " " in line: + line = line.replace(" ", " ") + + if line.startswith("#####"): + line = "
\n{}".format(line) + + # authors cont. + if not indent and line.startswith("\t- "): + line = line.replace("\t- ", "\n{}".format(line) + else: + line = line.replace("\t- ", "
  • ").replace("**", "", 1).replace("**", "", 1) + ''' + line = "\n{}".format(line) + indent = False + ''' + + table.append(line) + + if line == "### Usage:": + post = True + mid = False + continue + + if post: + r_lines_post.append(escape_underscore(line, in_code)) + elif not mid: + r_lines_pre.append(escape_underscore(line, in_code)) + +buffer = codecs.open(f_readme_tgt, "w", "utf-8") +if not buffer: + print("ERROR: could not open target README.md for writing") + sys.exit(1) + +buffer.write("{}\n\n{}\n\n{}".format("\n".join(r_lines_pre), "\n".join(table), "\n".join(r_lines_post))) +buffer.close() + +print("Exported README.md to {}\n".format(f_readme_tgt)) diff --git a/.ldoc/tags.ld b/.ldoc/tags.ld new file mode 100644 index 0000000..1a27c25 --- /dev/null +++ b/.ldoc/tags.ld @@ -0,0 +1,80 @@ + +local tags = {} +local tag_list = {} +local custom_tags = {} + +local register_tag = function(name, tag) + local new_tag = {name, title=tag.title, hidden=tag.hidden, format=tag.format} + table.insert(custom_tags, new_tag) + tag_list[name] = {title=tag.title, format=tag.format} +end + +tags.get_title = function(tname) + local t = tag_list[tname] + if t then + return t.title + end +end + +tags.format = function(tname, value) + local t = tag_list[tname] + if t then + if type(t.format) == "function" then + value = t.format(value) + end + end + + return value +end + + +local new_tags = { + ["priv"] = { + title = "Required Privileges", + hidden = true, + }, + ["note"] = { + title = "Notes", + hidden = true, + format = function(value) + return "*" .. value .. "*" + end, + }, + ["video"] = { + title = "Video", + format = video_frame, + }, + ["youtube"] = { + title = "Video", + format = function(value) + return video_frame("https://www.youtube.com/embed/" .. value) + end, + }, + -- settings + ["settype"] = { + title = "Setting Type", + hidden = true, + }, + ["default"] = { + title = "Default Value", + hidden = true, + }, + -- craft items/tools + ["img"] = { + title = "Image", + format = function(value) + return "" + end, + }, + -- sounds + ["snd"] = { + hidden = true, + }, +} + +for k, v in pairs(new_tags) do + register_tag(k, v) +end + + +return tags, custom_tags