2024-03-16 19:45:43 +02:00
|
|
|
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'
|
2024-03-17 00:05:55 +02:00
|
|
|
let custom_types: string[] = []
|
2024-03-16 19:45:43 +02:00
|
|
|
|
2024-03-17 01:14:31 +02:00
|
|
|
export_from_godot()
|
2024-03-16 19:45:43 +02:00
|
|
|
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() {
|
2024-03-17 00:05:55 +02:00
|
|
|
custom_types = (await readdir(REFERENCE_PATH)).map(file => {
|
|
|
|
file = file.replace('.xml', '').replace(/--/g, '/')
|
|
|
|
|
|
|
|
if (file.includes('--')) {
|
|
|
|
return `"${file}.gd"`
|
|
|
|
}
|
|
|
|
|
|
|
|
return file
|
|
|
|
})
|
|
|
|
|
|
|
|
|
2024-03-16 19:45:43 +02:00
|
|
|
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}
|
2024-03-17 00:05:55 +02:00
|
|
|
`
|
2024-03-16 19:45:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (json.class.description) {
|
|
|
|
description = endent`
|
|
|
|
## Description
|
|
|
|
|
|
|
|
${json.class.description}
|
2024-03-17 00:05:55 +02:00
|
|
|
`
|
|
|
|
}
|
|
|
|
|
|
|
|
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')
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-03-17 01:14:31 +02:00
|
|
|
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')
|
|
|
|
}
|
|
|
|
|
2024-03-17 00:05:55 +02:00
|
|
|
let constant_descriptions = ''
|
2024-03-17 01:14:31 +02:00
|
|
|
let constants_list = to_array(json.class.constants?.constant).filter(constant => ('_enum' in constant) === false)
|
2024-03-17 00:05:55 +02:00
|
|
|
|
|
|
|
if (constants_list.length > 0) {
|
|
|
|
constant_descriptions = '## Constants\n\n' +
|
|
|
|
constants_list.map(constant => {
|
|
|
|
const name = constant._name
|
|
|
|
|
2024-03-17 01:14:31 +02:00
|
|
|
return endent`
|
|
|
|
### ${name} = \`${constant._value}\` ${'{#const-' + name_to_anchor(name) + '}'}
|
2024-03-17 00:05:55 +02:00
|
|
|
|
2024-03-17 01:14:31 +02:00
|
|
|
${constant['#text'] || 'No description provided yet.'}
|
|
|
|
`
|
2024-03-17 00:05:55 +02:00
|
|
|
}).join('\n\n')
|
2024-03-16 19:45:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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 [
|
2024-03-17 01:14:31 +02:00
|
|
|
`[${name}](#prop-${name_to_anchor(name)})`,
|
2024-03-16 19:45:43 +02:00
|
|
|
link_godot_type(member._type),
|
|
|
|
handle_default(member._default)
|
|
|
|
]
|
|
|
|
})
|
2024-03-17 00:05:55 +02:00
|
|
|
])
|
|
|
|
}
|
|
|
|
`
|
2024-03-16 19:45:43 +02:00
|
|
|
|
|
|
|
member_descriptions = '## Property Descriptions\n\n' +
|
|
|
|
members_list.map(member => {
|
|
|
|
const name = member._name
|
|
|
|
|
|
|
|
return endent`
|
2024-03-17 01:14:31 +02:00
|
|
|
### ${name}: ${link_godot_type(member._type)} ${'{#prop-' + name_to_anchor(name) + '}'}
|
2024-03-16 19:45:43 +02:00
|
|
|
|
2024-03-17 01:14:31 +02:00
|
|
|
${member['#text'] || 'No description provided yet.'}
|
2024-03-17 00:05:55 +02:00
|
|
|
`
|
2024-03-16 19:45:43 +02:00
|
|
|
}).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} )`
|
|
|
|
]
|
|
|
|
})
|
2024-03-17 00:05:55 +02:00
|
|
|
])
|
|
|
|
}
|
|
|
|
`
|
2024-03-16 19:45:43 +02:00
|
|
|
|
|
|
|
method_descriptions = '## Method Descriptions\n\n' +
|
|
|
|
methods_list.map(method => {
|
|
|
|
const name = method._name
|
|
|
|
|
|
|
|
let params = to_array(method?.param).map(param => {
|
2024-03-17 00:05:55 +02:00
|
|
|
return `${param._name}: ${link_godot_type(param._type)} `
|
2024-03-16 19:45:43 +02:00
|
|
|
}).join(', ')
|
|
|
|
|
2024-03-17 01:14:31 +02:00
|
|
|
let qualifiers = to_array(method?._qualifiers).join(', ')
|
|
|
|
|
2024-03-16 19:45:43 +02:00
|
|
|
return endent`
|
2024-03-17 01:14:31 +02:00
|
|
|
### ${qualifiers} ${name} (${params} ) -> ${link_godot_type(method.return._type)} ${'{#' + name_to_anchor(name) + '}'}
|
2024-03-16 19:45:43 +02:00
|
|
|
|
|
|
|
${method.description || 'No description provided yet.'}
|
2024-03-17 00:05:55 +02:00
|
|
|
`
|
2024-03-16 19:45:43 +02:00
|
|
|
}).join('\n\n')
|
|
|
|
}
|
|
|
|
|
|
|
|
let markdown = endent`
|
|
|
|
# ${getTitle(json.class._name)}
|
2024-03-17 00:05:55 +02:00
|
|
|
${inherits}
|
2024-03-16 19:45:43 +02:00
|
|
|
|
|
|
|
${description}
|
|
|
|
|
|
|
|
${members}
|
|
|
|
|
|
|
|
${methods}
|
|
|
|
|
2024-03-17 00:05:55 +02:00
|
|
|
${signal_descriptions}
|
|
|
|
|
2024-03-17 01:14:31 +02:00
|
|
|
${enum_descriptions}
|
|
|
|
|
2024-03-17 00:05:55 +02:00
|
|
|
${constant_descriptions}
|
|
|
|
|
2024-03-16 19:45:43 +02:00
|
|
|
${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)`
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-03-17 00:05:55 +02:00
|
|
|
if (custom_types.includes(type)) {
|
|
|
|
return `[${getTitle(type)}](/reference/${type}.html)`
|
|
|
|
}
|
|
|
|
|
2024-03-16 19:45:43 +02:00
|
|
|
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}\``
|
|
|
|
}
|