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 { 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 === '') { return '' } return `\`${value}\`` }