unity-atoms/scripts/generateDocs.js
2019-10-15 21:02:41 +02:00

245 lines
9.3 KiB
JavaScript

const child_process = require('child_process');
const fs = require('fs');
const xml2js = require('xml2js');
const rimraf = require('rimraf');
const path = require('path');
const argv = require('yargs').argv
const run = async () => {
// Extract dlls
let dlls = [];
const parser = new xml2js.Parser();
const assemblyFile = fs.readFileSync(path.join(process.cwd(), 'Examples', 'Assembly-CSharp.csproj'));
const assemblyXml = await parser.parseStringPromise(assemblyFile);
assemblyXml.Project.ItemGroup.forEach((itemGroup) => {
if (itemGroup.Reference) {
itemGroup.Reference.forEach((ref) => {
dlls = dlls.concat(ref.HintPath);
})
}
});
const packages = ['Tags'];
// Compile code
const apiXmlName = `api.xml`;
const assemblyName = `Packages.dll`;
const cmd = `csc -recurse:${path.join(process.cwd(), 'Packages', '/*.cs')} /doc:${path.join(process.cwd(), apiXmlName)} -t:library -out:${path.join(process.cwd(), assemblyName)} -r:${dlls.join(',')}`;
const compileStdout = child_process.execSync(cmd);
if (argv.verbose) {
console.log("Stdout from source code compilation:");
console.log(compileStdout.toString());
}
// Remove generated assembly
rimraf.sync(path.join(process.cwd(), assemblyName));
// Parse docs xml
const docsXmlFile = fs.readFileSync(path.join(process.cwd(), apiXmlName));
const docsXml = await parser.parseStringPromise(docsXmlFile);
const NAMESPACES = [
'UnityAtoms.',
'UnityAtoms.Editor.',
'UnityAtoms.Tags.',
'UnityAtoms.Tags.Editor.',
'UnityAtoms.Mobile.',
'UnityAtoms.Mobile.Editor.',
'UnityAtoms.UI.',
'UnityAtoms.UI.Editor.',
'UnityAtoms.SceneMgmt.',
'UnityAtoms.SceneMgmt.Editor.'
];
const getNamespace = (name) => {
const matches = NAMESPACES.filter((ns) => name.includes(ns));
const namespace = matches.sort((a, b) => (b.match(/./g) || []).length - (a.match(/./g) || []).length)[0];
return namespace.substring(0, namespace.length - 1);
}
const extractFromName = (name) => {
const [type, ...restOfName] = name.split(':');
const namespace = getNamespace(name);
const toReturn = { type, namespace };
if (type === 'T') {
toReturn.className = restOfName[0].substring(namespace.length + 1);
} else if (type === 'M' || type === 'P' || type === 'F') {
const rest = restOfName[0].substring(namespace.length + 1);
const indexOfFirstParenthesis = rest.indexOf('(');
let className, restName;
if (indexOfFirstParenthesis !== -1) {
const indexOfMethodNameStart = rest.substring(0, indexOfFirstParenthesis).lastIndexOf('.') + 1;
className = rest.substring(0, indexOfMethodNameStart - 1);
restName = rest.substring(indexOfMethodNameStart, rest.length);
} else {
const splitName = restOfName[0].substring(namespace.length + 1).split('.');
restName = splitName.pop();
className = splitName.join('.');
}
toReturn.className = className;
toReturn.name = restName;
}
return toReturn;
}
// EXAMPLE FORMAT:
// const prettifiedAndGroupedJsonExample = [
// {
// namespace: 'UnityAtoms',
// classes: [{
// id: 'AtomListener',
// name: 'AtomListener'
// summary: '12312312',
// methods: [{}]
// properties: [{}]
// variables: [{}]]
// }],
// }
// ];
const prettifiedAndGroupedJson = [];
// Prettify
const prettifiedXml = docsXml.doc.members[0].member.map((cur) => {
const summary = cur.summary && cur.summary[0];
const params = cur.param && cur.param.length > 0 && cur.param.map((p) => ({ name: p['$'].name, description: p['_'] }));
const returns = cur.returns && cur.returns.length > 0 && cur.returns[0];
const value = cur.value && cur.value.length > 0 && cur.value[0];
const examples = cur.example && cur.example.length > 0 && cur.example.map((ex) => ex.code[0]);
const typeparams = cur.typeparam && cur.typeparam.length > 0 && cur.typeparam.map((tp) => ({ name: tp['$'].name, description: tp['_'] }));
const extractedFromName = extractFromName(cur['$'].name);
// Add namespace and classes
let namespaceGroup = prettifiedAndGroupedJson.find((n) => n.namespace === extractedFromName.namespace);
if (!namespaceGroup) {
namespaceGroup = { namespace: extractedFromName.namespace, classes: [] };
prettifiedAndGroupedJson.push(namespaceGroup);
}
if (extractedFromName.type === 'T') {
namespaceGroup.classes.push({
id: extractedFromName.className,
name: extractedFromName.className.includes('`') && typeparams ? extractedFromName.className.replace(/`\d/, `<${typeparams.map((tp) => tp.name).join(',')}>`) : extractedFromName.className,
typeparams,
summary,
methods: [],
properties: [],
variables: [],
});
}
return { summary, params, returns, value, examples, typeparams, ...extractedFromName };
}, []);
// Add all methods, properties and variables
prettifiedXml.filter((cur) => ['M', 'F', 'P'].includes(cur.type)).forEach((cur) => {
const classGroup = prettifiedAndGroupedJson.find((n) => n.namespace === cur.namespace).classes.find((n) => n.id === cur.className);
if (classGroup) {
if (cur.type === 'M') {
if (cur.name.includes('Do(')) {
}
let name = cur.name;
if (name.includes('``') && cur.typeparams) {
name = name.replace(/``\d/, `<${cur.typeparams.map((tp) => tp.name).join(',')}>`);
}
if (name.includes('`') && cur.params) {
name = name.replace(/\(([^\)]+)\)/, `(${cur.params.map((p) => p.name).join(',')})`);
}
classGroup.methods.push({
...cur,
name,
});
} else if (cur.type === 'F') {
classGroup.variables.push(cur);
} else if (cur.type === 'P') {
classGroup.properties.push(cur);
}
}
});
const printExamples = (examples) => {
if (!examples) return '';
return `##### Examples\n\n${examples.map((example) => {
const exampleSplitOnNewline = example.split('\n');
const numSpacesFirstRow = exampleSplitOnNewline[1].search(/\S/);
const trimmedExample = exampleSplitOnNewline.map((line) => line.substring(numSpacesFirstRow)).join('\n');
return `\`\`\`cs${trimmedExample}\`\`\``;
}).join('\n\n')}\n\n`;
}
const printValues = (values = '') => {
const trimmedValues = values.replace(/\s+/g, ' ').trim();
if (!trimmedValues) return '';
return `##### Values\n\n${trimmedValues}\n\n`;
}
const printReturns = (returns = '') => {
const trimmedReturns = returns.replace(/\s+/g, ' ').trim();
if (!trimmedReturns) return '';
return `##### Returns\n\n${trimmedReturns}\n\n`;
}
const printSummary = (summary = '') => {
const trimmedSummary = summary.replace(/\s+/g, ' ').trim();
if (!trimmedSummary) return '';
return(`${trimmedSummary}\n\n`);
}
const printTypeParams = (typeparams) => {
if (!typeparams || typeparams.length <= 0) return '';
return(`#### Type Parameters\n\n${typeparams.map((tp) => `- \`${tp.name}\` - ${tp.description}`).join('\n')}\n\n`);
}
const printParameters = (params) => {
if (!params || params.length <= 0) return '';
return (`##### Parameters\n\n${params.map((param) => `- \`${param.name}\` - ${param.description}`).join('\n')}\n\n`);
}
const printVariablesSection = (variables) => {
if (!variables || variables.length <= 0) return '';
return `### Variables\n\n${variables.map((v) => {
return `#### \`${v.name}\`\n\n${printSummary(v.summary)}`;
}).join('---\n\n')}`;
}
const printPropertiesSection = (properties) => {
if (!properties || properties.length <= 0) return '';
return `### Properties\n\n${properties.map((prop) => {
return `#### \`${prop.name}\`\n\n${printSummary(prop.summary)}${printValues(prop.values)}${printExamples(prop.examples)}`
}).join('---\n\n')}`;
}
const printMethodsSection = (methods) => {
if (!methods || methods.length <= 0) return '';
return `### Methods\n\n${methods.map((method) => {
return `#### \`${method.name}\`\n\n${printSummary(method.summary)}${printTypeParams(method.typeparams)}${printParameters(method.params)}${printReturns(method.returns)}${printExamples(method.examples)}`
}).join('---\n\n')}`;
}
const printClasses = (classes) => {
if (!classes || classes.length <= 0) return '';
return classes.map((c) => {
return `## \`${c.name}\`\n\n${printTypeParams(c.typeparams)}${printSummary(c.summary)}${printVariablesSection(c.variables)}${printPropertiesSection(c.properties)}${printMethodsSection(c.methods)}---\n\n`
}).join('');
}
const printNamespace = (namespace) => `# Namespace - \`${namespace.namespace}\`\n\n${printClasses(namespace.classes)}`;
const printPageMeta = (namespace) => `---\nid: ${namespace.toLowerCase()}\ntitle: ${namespace}\nhide_title: true\nsidebar_label: ${namespace}\n---\n\n`;
// Create one MD file per namespace
prettifiedAndGroupedJson.forEach((namespace) => {
const mdPath = path.join(process.cwd(), 'docs', 'api', `${namespace.namespace.toLowerCase()}.md`);
const mdFile = `${printPageMeta(namespace.namespace)}${printNamespace(namespace)}`;
fs.writeFileSync(mdPath, mdFile.substring(0, mdFile.length - 1)); // Trim last new line
});
// Remove generated xml
rimraf.sync(path.join(process.cwd(), apiXmlName));
}
run();