Dumps emmy lua to markdown documentation files and a single API file
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

310 lines
7.9 KiB

2 years ago
const fs = require("fs");
const path = require("path");
// Extract emmy lua classes from a file
// "---@class Bastion" -> "Bastion"
/*
"---@class Bastion" -> "Bastion"
Bastion = {
Test = function(self, name, name2)
return 5
end,
isTest = true,
}
-> ["Bastion", variables: [{key: 'Test', value: 'function(self, name, name2)'} ,
{key: 'isTest', value: 'true'}]
*/
2 years ago
function extractClasses(file) {
const classes = [];
const lines = file.split("\n");
let className = "";
let variables = [];
2 years ago
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
2 years ago
if (line.startsWith("---@class ")) {
// We have reached the start of a class documentation
className = line.replace("---@class ", "");
} else if (line.startsWith("}")) {
if (className != "") {
// console.log("end of class", className, "variables", variables);
classes.push({
name: className,
variables,
});
className = "";
variables = [];
}
// if the line contains both { and } then the table is empty and we can just add it to the classes array
} else if (className != "" && line.includes("{") && line.includes("}")) {
classes.push({
name: className,
variables: [],
});
className = "";
variables = [];
} else if (className != "" && !line.includes("{")) {
// if the line contains a = then it is a variable declaration
if (line.includes("=")) {
const [key, value] = line.split("=");
variables.push({
key: key.trim(),
value: value.trim(),
});
}
2 years ago
}
}
// console.log(classes);
2 years ago
return classes;
}
// console.log(
// extractClasses(fs.readFileSync(path.join(__dirname, "Bastion.lua"), "utf8"))
// );
// Extract emmy lua functions from a file
/*
"---@param name string" -> "name", "string"
"---@param name2 string|nil" -> "name2", "string|nil"
"---@return string" -> "string"
"function Bastion:Test(name, name2)" -> "Bastion", "Test"
Every function documentation starts with a ---@param line or a ---@return line and ends with a function line, we should ignore any
other lines in between these lines
return {
name: "Bastion",
function: "Test",
params: [
{
name: "name",
type: "string"
},
{
name: "name2",
type: "string|nil"
},
],
returns: [
{
type: "string"
}
]
}
*/
function extractFunctions(file) {
const functions = [];
const lines = file.split("\n");
let params = [];
let returns = [];
let description = "";
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (line.startsWith("---@param ")) {
const paramLine = line.replace("---@param ", "");
// get the name (everything before the first space) and the types (everything after the first space allowing for spaces in the types)
const name = paramLine.split(" ")[0];
const type = paramLine.replace(name + " ", "");
params.push({
name,
type,
});
} else if (line.startsWith("---@return ")) {
const returnLine = line.replace("---@return ", "");
const type = returnLine;
returns.push({
type,
});
} else if (line.startsWith("---") && !line.startsWith("---@")) {
// This is a description line
description += line.replace("---", "") + "\n";
} else if (line.startsWith("function ")) {
// We have reached the end of the function documentation, now we can extract the function name and class name
if (params.length === 0 && returns.length === 0) {
// There is no documentation for this function
continue;
}
const functionLine = line.replace("function ", "");
const [className, functionName] = functionLine.split(":");
functions.push({
className: className,
function: functionName,
params,
returns,
description: description,
});
// Reset the params and returns array
params = [];
returns = [];
description = "";
}
}
return functions;
}
let globalFunctions = [];
let globalClasses = [];
2 years ago
// dump a file to markdown
function Dump(filePath) {
const fileName = path.basename(filePath);
const functions = extractFunctions(fs.readFileSync(filePath, "utf8"));
globalFunctions = [...globalFunctions, ...functions];
const classes = extractClasses(fs.readFileSync(filePath, "utf8")).map(
(classData) => {
const classFunctions = functions.filter(
(func) => func.className === classData.name
);
2 years ago
return {
...classData,
functions: classFunctions,
2 years ago
};
}
);
globalClasses = [...globalClasses, ...classes];
2 years ago
// console.log(classes);
2 years ago
// write the data to disk as a markdown file
const markdown = classes
.map((classData) => {
const classMarkdown = `# ${classData.name}
${classData.variables ? "## Variables" : ""}
${classData.variables
.map((variable) => {
return `\`${variable.key} = ${variable.value}\``;
})
.join("\n")}
2 years ago
${classData.functions
.map((func) => {
return `## ${func.className}:${func.function}
${func.description}
${func.params.length > 0 ? "### Parameters" : ""}
${func.params
.map((param) => {
return `\`${param.name} (${param.type})\``;
})
.join("\n")}
${func.returns.length > 0 ? "### Returns" : ""}
${func.returns
.map((ret) => {
return `\`-> ${ret.type}\``;
})
.join("\n")}`;
})
.join("\n")}`;
return classMarkdown;
})
.join("\n");
const p = path.join(filePath.replace("input", "output")) + ".md";
// create the output directory if it doesn't exist
if (!fs.existsSync(path.dirname(p))) {
fs.mkdirSync(path.dirname(p), {
recursive: true,
});
}
fs.writeFileSync(p, markdown);
}
// Dump(path.join(__dirname, "Bastion.lua"));
function DumpDirectory(directory) {
// get all the files in the directory
const files = fs.readdirSync(directory);
// filter out the files that are not lua files
const luaFiles = files.filter((file) => file.endsWith(".lua"));
// dump each file
luaFiles.forEach((file) => {
Dump(path.join(directory, file));
});
// dump the files in the subdirectories
const subDirectories = files.filter((file) =>
fs.lstatSync(path.join(directory, file)).isDirectory()
);
subDirectories.forEach((subDirectory) => {
DumpDirectory(path.join(directory, subDirectory));
});
}
function DumpAPIFile(_classes, _funcs) {
// console.log(_classes);
const luaClasses = _classes.map((classData) => {
let str = "";
if (classData.description && classData.description !== "") {
str += `--- ${classData.description}\n`;
}
str += `---@class ${classData.name}\n`;
if (classData.variables.length > 0) {
classData.variables.map((variable) => {
str += `---@field ${variable.key} ${variable.value}\n`;
});
}
return str;
});
2 years ago
// console.log(_funcs);
const lua = _funcs
.map((func) => {
let str = "";
if (func.description && func.description !== "") {
2 years ago
str += `--- ${func.description}\n`;
}
func.params.map((param) => {
str += `---@param ${param.name} ${param.type}\n`;
});
func.returns.map((ret) => {
str += `---@return ${ret.type}\n`;
});
str += `function ${func.className}:${func.function} end`;
return str;
})
.join("\n\n");
let output = [...luaClasses, lua].join("\n\n");
fs.writeFileSync(path.join(__dirname, "output", "API.lua"), output);
2 years ago
}
// Wipe the output directory
fs.rmdirSync(path.join(__dirname, "output"), { recursive: true });
fs.mkdirSync(path.join(__dirname, "output"));
DumpDirectory(path.join(__dirname, "input"));
DumpAPIFile(globalClasses, globalFunctions);