immersive-home/docs/reference/generator/index.ts
2024-03-17 00:14:31 +01:00

318 lines
8.5 KiB
TypeScript

import { execSync } from 'child_process';
import { join } from 'path';
import { readdir, unlink, readFile, writeFile, mkdir } from 'fs/promises';
import { XMLParser } from 'fast-xml-parser'
import { camelCase } from 'lodash-es'
import { markdownTable } from 'markdown-table'
import endent from "endent";
const REFERENCE_PATH = './reference/raw'
let custom_types: string[] = []
export_from_godot()
translate()
async function export_from_godot() {
try {
execSync('godot --doctool ../docs/reference/raw --gdscript-docs res://lib ', {
cwd: join(process.cwd(), '../app/'),
});
} catch (error) {
console.log('⚠️ Ignoring error exporting from Godot: ', error)
}
console.log('✅ Exported from Godot');
}
async function translate() {
custom_types = (await readdir(REFERENCE_PATH)).map(file => {
file = file.replace('.xml', '').replace(/--/g, '/')
if (file.includes('--')) {
return `"${file}.gd"`
}
return file
})
for (const file of await readdir(REFERENCE_PATH)) {
const contents = await parse_reference(join(REFERENCE_PATH, file))
const markdown = translate_reference(contents)
await save_markdown(join('./reference', file.replace('.gd', '').replace('.xml', '.md')), markdown)
}
console.log('✅ Translated references to markdown');
}
async function parse_reference(path: string): Promise<string> {
return readFile(path, 'utf-8')
}
function translate_reference(contents: string): string {
const parser = new XMLParser({
ignoreAttributes: false,
attributeNamePrefix: '_'
})
const json = parser.parse(contents)
let description = ''
if (json.class.brief_description) {
description = endent`
## Description
${json.class.brief_description}
`
}
if (json.class.description) {
description = endent`
## Description
${json.class.description}
`
}
let inherits = ''
if (json.class._inherits) {
inherits = endent`**Inherits:** ${link_godot_type(json.class._inherits)}`
}
let signal_descriptions = ''
let signals_list = to_array(json.class.signals?.signal)
if (signals_list.length > 0) {
signal_descriptions = '## Signals\n\n' +
signals_list.map(signal => {
const name = signal._name
let params = to_array(signal?.param).map(param => {
return `${param._name}: ${link_godot_type(param._type)} `
}).join(', ')
return endent`
### ${name} (${params} ) ${'{#' + name_to_anchor(name) + '}'}
${signal.description || 'No description provided yet.'}
`
}).join('\n\n')
}
let enum_descriptions = ''
let enum_list = to_array(json.class.constants?.constant).filter(constant => ('_enum' in constant) === true)
if (enum_list.length > 0) {
const enums = enum_list.reduce((acc, constant) => {
endent
if ('_enum' in constant) {
if (acc[constant._enum] === undefined) {
acc[constant._enum] = []
}
acc[constant._enum].push(constant)
}
return acc
}, {})
enum_descriptions = '## Enums\n\n' + Object.keys(enums).map(enum_name => {
return endent`
### enum ${enum_name}
${enums[enum_name].map(constant => {
const name = constant._name
return endent`
#### ${enum_name}.${name} = \`${constant._value}\` ${'{#const-' + name_to_anchor(name) + '}'}
${constant['#text'] || 'No description provided yet.'}
`
}).join('\n\n')}
`
}).join('\n\n')
}
let constant_descriptions = ''
let constants_list = to_array(json.class.constants?.constant).filter(constant => ('_enum' in constant) === false)
if (constants_list.length > 0) {
constant_descriptions = '## Constants\n\n' +
constants_list.map(constant => {
const name = constant._name
return endent`
### ${name} = \`${constant._value}\` ${'{#const-' + name_to_anchor(name) + '}'}
${constant['#text'] || 'No description provided yet.'}
`
}).join('\n\n')
}
let members = ''
let member_descriptions = ''
let members_list = to_array(json.class.members?.member)
if (members_list.length > 0) {
members = endent`
## Properties
${markdownTable([
['Name', 'Type', 'Default'],
...members_list.map(member => {
const name = member._name
return [
`[${name}](#prop-${name_to_anchor(name)})`,
link_godot_type(member._type),
handle_default(member._default)
]
})
])
}
`
member_descriptions = '## Property Descriptions\n\n' +
members_list.map(member => {
const name = member._name
return endent`
### ${name}: ${link_godot_type(member._type)} ${'{#prop-' + name_to_anchor(name) + '}'}
${member['#text'] || 'No description provided yet.'}
`
}).join('\n\n')
}
let methods = ''
let method_descriptions = ''
let methods_list = to_array(json.class.methods?.method)
if (methods_list.length > 0) {
methods = endent`
## Methods
${markdownTable([
['Returns', 'Name'],
...methods_list.map(method => {
const name = method._name
let params = to_array(method?.param).map(param => {
return `${param._name}: ${link_godot_type(param._type)}`
}).join(', ')
return [
link_godot_type(method.return._type),
`[${name}](#${name_to_anchor(name)}) ( ${params} )`
]
})
])
}
`
method_descriptions = '## Method Descriptions\n\n' +
methods_list.map(method => {
const name = method._name
let params = to_array(method?.param).map(param => {
return `${param._name}: ${link_godot_type(param._type)} `
}).join(', ')
let qualifiers = to_array(method?._qualifiers).join(', ')
return endent`
### ${qualifiers} ${name} (${params} ) -> ${link_godot_type(method.return._type)} ${'{#' + name_to_anchor(name) + '}'}
${method.description || 'No description provided yet.'}
`
}).join('\n\n')
}
let markdown = endent`
# ${getTitle(json.class._name)}
${inherits}
${description}
${members}
${methods}
${signal_descriptions}
${enum_descriptions}
${constant_descriptions}
${member_descriptions}
${method_descriptions}
` + '\n'
return markdown
}
async function save_markdown(path: string, markdown: string) {
return writeFile(path, markdown)
}
function getTitle(name: string): string {
name = name.split(/(?:--|\\|\/)/g).at(-1)
name = name.split('.').at(0)
return capitalize(camelCase(name))
}
function capitalize(string: string): string {
return string.charAt(0).toUpperCase() + string.slice(1);
}
function name_to_anchor(name: string): string {
return name.replace(/_/g, '-')
}
function link_godot_type(type: string): string {
if (!type || type === 'void') {
return 'void'
}
if (/"lib\/.*?\.gd"/g.test(type)) {
return type.replace(/"lib\/.*?\.gd"/, (match) => {
match = match.replace(/"/g, '')
const link = match.replace('.gd', '').replace(/\//g, '--')
return `[${getTitle(match)}](/reference/${link}.html)`
})
}
if (custom_types.includes(type)) {
return `[${getTitle(type)}](/reference/${type}.html)`
}
return `[${type}](https://docs.godotengine.org/de/4.x/classes/class_${type.toLowerCase()}.html)`
}
function to_array(object: any): any[] {
if (!object) {
return []
}
if (Array.isArray(object)) {
return object
} else {
return [object]
}
}
function handle_default(value: string): string {
if (!value || value === '<unknown>') {
return ''
}
return `\`${value}\``
}