LDoc: add scripts for creating versioned docs

This commit is contained in:
Jordan Irwin 2021-08-28 20:16:01 -07:00
parent 40bab2a311
commit 25b94de01e
6 changed files with 826 additions and 10 deletions

133
.ldoc/auxf.ld Normal file

@ -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

117
.ldoc/build_versioned_docs.sh Executable file

@ -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="<html>\n<head></head>\n\n<body>\n\n<ul>\n"
# generate new doc files
mkdir -p "${d_export}"
for vinfo in $(git tag -l --sort=-v:refname | grep "^v[0-9]"); do
echo -e "\nbuilding ${vinfo} docs ..."
git checkout ${vinfo}
d_temp="${d_ldoc}/temp"
mkdir -p "${d_temp}"
f_config="${d_ldoc}/config.ld"
if test ! -f "${f_config}"; then
# backward compat
f_config="${d_root}/docs/config.ld"
fi
if test ! -f "${f_config}"; then
echo -e "\nLDoc config not found, skipping build for ${vinfo} ..."
continue
fi
parse_readme="${d_ldoc}/parse_readme.py"
if test -f "${parse_readme}"; then
if test ! -x "${parse_readme}"; then
chmod +x "${parse_readme}"
fi
"${parse_readme}" "${vinfo}"
else
echo -e "\nparse_readme.py not found, skipping README.md parsing ..."
fi
echo
"${cmd_ldoc}" --UNSAFE_NO_SANDBOX --multimodule -c "${f_config}" -d "${d_temp}" "${d_root}"; retval=$?
if test ${retval} -ne 0; then
echo -e "\nERROR: doc build for ${vinfo} failed!"
rm -rf "${d_temp}"
continue
fi
# show version info
for html in $(find "${d_temp}" -type f -name "*.html"); do
sed -i -e "s|^<h1>World Data Manager</h1>$|<h1>World Data Manager <span style=\"font-size:12pt;\">(${vinfo})</span></h1>|" "${html}"
done
# copy screenshot
screenshot="${d_root}/screenshot.png"
if test -f "${screenshot}"; then
cp "${d_root}/screenshot.png" "${d_temp}"
fi
rm -f "${d_ldoc}/README.md"
if test -d "${d_root}/textures"; then
# copy textures to data directory
echo -e "\ncopying textures ..."
d_data="${d_temp}/data"
mkdir -p "${d_data}"
texture_count=0
for png in $(find "${d_root}/textures" -maxdepth 1 -type f -name "*.png"); do
t_png="${d_data}/$(basename ${png})"
if test -f "${t_png}"; then
echo "WARNING: not overwriting existing file: ${t_png}"
else
cp "${png}" "${d_data}"
texture_count=$((texture_count + 1))
printf "\rcopied ${texture_count} textures"
fi
done
fi
mv "${d_temp}" "${d_export}/${vinfo}"
if test -z ${vcur+x}; then
vcur="${vinfo}"
ln -s "${d_export}/${vinfo}" "${d_export}/current"
ln -s "${d_export}/${vinfo}" "${d_export}/latest"
html_out="${html_out} <li><a href=\"current/\">current</a></li>\n"
html_out="${html_out} <li><a href=\"latest/\">latest</a></li>\n"
fi
html_out="${html_out} <li><a href=\"${vinfo}/\">${vinfo}</a></li>\n"
done
html_out="${html_out}</ul>\n\n</body></html>"
cd "${d_root}"
git checkout ${main_branch}
echo -e "${html_out}" > "${d_export}/index.html"
echo -e "\nDone!"

@ -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 '<iframe width="560" height="315" src="' .. src
.. '" title="Video Player" frameborder="0"'
.. ' allow="fullscreen;"></iframe>'
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 = '<span style="'
if flags.color then
st = st .. 'color:' .. flags.color .. ';'
end
if flags.size then
st = st .. 'font-size:' .. flags.size .. ';'
end
return st .. '">' .. value:gsub("_", "\\_") .. '</span>'
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 .. " &lt;" .. p .. "&gt;"
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)

66
.ldoc/gendoc.sh Normal file → Executable file

@ -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|^<h1>World Data Manager</h1>$|<h1>World Data Manager <span style=\"font-size:12pt;\">(${vinfo})</span></h1>|" "${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!"

179
.ldoc/parse_readme.py Executable file

@ -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 = '<img src="../screenshot.png" width="700" />'
elif line == "See [sources.md](sources.md)":
line = "See <a href=\"https://github.com/AntumMT/mod-sounds/blob/{}/sources.md\">sources.md</a>".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 = '{} <a href="{}">'.format(line, url)
line = "{}{}".format(line, sname)
if url:
line = "{}</a>".format(line)
line = "{} by {} ({})".format(line, author, lic)
if notes:
line = "{} ({})".format(line, notes)
while " " in line:
line = line.replace(" ", " ")
if line.startswith("#####"):
line = "<br/>\n{}".format(line)
# authors cont.
if not indent and line.startswith("\t- "):
line = line.replace("\t- ", "<ul>\n<li>").replace("**", "<b>", 1).replace("**", "</b>", 1)
indent = True
elif indent:
if not line.startswith("\t- "):
indent = False
line = "</ul>\n{}".format(line)
else:
line = line.replace("\t- ", "<li>").replace("**", "<b>", 1).replace("**", "</b>", 1)
'''
line = "</ul>\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))

80
.ldoc/tags.ld Normal file

@ -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 "<img src=\"../data/" .. value .. "\" style=\"width:32px;\" />"
end,
},
-- sounds
["snd"] = {
hidden = true,
},
}
for k, v in pairs(new_tags) do
register_tag(k, v)
end
return tags, custom_tags