Skip to content

Commit 934cba5

Browse files
authored
Overload hook.Add with hook names and callback types (#39) (#84)
Resolve #39 Overload hook.Add with hook names and callback types
1 parent 4cc8e27 commit 934cba5

File tree

4 files changed

+121
-10
lines changed

4 files changed

+121
-10
lines changed

__tests__/api-writer/plugins.spec.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// import { GluaApiWriter } from '../../src/api-writer/glua-api-writer';
2+
// import { LibraryFunction } from '../../src/scrapers/wiki-page-markup-scraper';
3+
4+
describe('plugins', () => {
5+
it('should write plugin annotations', async () => {
6+
expect(true).toBe(true);
7+
// TODO: This test is commented since it requires the wiki to have been scraped so ./output/gm is filled, which isn't the case for the CI
8+
// const writer = new GluaApiWriter();
9+
// const api = writer.writePage(<LibraryFunction>{
10+
// name: 'Add',
11+
// address: 'hook.Add',
12+
// parent: 'hook',
13+
// dontDefineParent: true,
14+
// description: '',
15+
// realm: 'shared',
16+
// type: 'libraryfunc',
17+
// url: 'na',
18+
// arguments: [
19+
// {
20+
// args: [{
21+
// name: 'intensity',
22+
// type: 'number',
23+
// description: 'The intensity of the explosion.',
24+
// default: '1000',
25+
// }]
26+
// }
27+
// ],
28+
// returns: [
29+
// {
30+
// type: 'number',
31+
// description: 'The amount of damage done.',
32+
// },
33+
// ],
34+
// });
35+
36+
// expect(api).toContain('---@overload fun(eventName: "Move", identifier: any, func: fun(ply: Player, mv: CMoveData):(boolean?))');
37+
});
38+
});

custom/plugins/hook-add.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { GluaApiWriter } from "../../src/api-writer/glua-api-writer";
2+
import { Function, HookFunction } from "../../src/scrapers/wiki-page-markup-scraper";
3+
import path from 'path';
4+
import fs from 'fs';
5+
6+
export default function plugin(writer: GluaApiWriter, func: Function) {
7+
// let hookAnnotations = '---@overload fun(eventName: "Move", identifier: any, func: fun(ply: Player, mv: CMoveData): boolean?)\n';
8+
let hookAnnotations = '';
9+
10+
// Iterate writer.outputDirectory to find all hooks in gm/ that have type "hook"
11+
const hookFiles = path.join(writer.outputDirectory, 'gm');
12+
const hookFilesList = fs.readdirSync(hookFiles, { withFileTypes: true })
13+
.filter(dirent => dirent.isFile() && dirent.name.endsWith('.json'))
14+
.map(dirent => dirent.name);
15+
16+
// Iterate all files and parse them as JSON, only those with type "hook" are considered
17+
for (const file of hookFilesList) {
18+
const filePath = path.join(writer.outputDirectory, 'gm', file);
19+
const fileContent = fs.readFileSync(filePath, 'utf8');
20+
const fileJson = JSON.parse(fileContent)[0] as HookFunction;
21+
22+
// Check if the function is a hook
23+
if (fileJson.type !== 'hook') {
24+
continue;
25+
}
26+
27+
// Add the hook annotation to the hookAnnotations string
28+
let args = '';
29+
30+
if (fileJson.arguments) {
31+
for (const arg of fileJson.arguments) {
32+
if (arg.args) {
33+
for (const argItem of arg.args) {
34+
const argType = GluaApiWriter.transformType(argItem.type);
35+
args += `${argItem.name}: ${argType}, `;
36+
}
37+
}
38+
}
39+
40+
// Remove the last comma and space
41+
args = args.slice(0, -2);
42+
}
43+
44+
let returns = '';
45+
if (fileJson.returns) {
46+
for (const ret of fileJson.returns) {
47+
const retType = GluaApiWriter.transformType(ret.type);
48+
returns += `${retType}, `;
49+
}
50+
51+
// Remove the last comma and space
52+
returns = returns.slice(0, -2);
53+
54+
if (returns !== '') {
55+
// We force the return type to be optional, since hooks should only return a value if they want to
56+
returns = `:(${returns}?)`;
57+
}
58+
}
59+
60+
// Create the overload
61+
hookAnnotations += `---@overload fun(eventName: "${fileJson.name}", identifier: any, func: fun(${args})${returns})\n`;
62+
}
63+
64+
return hookAnnotations;
65+
}

src/api-writer/glua-api-writer.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
isStruct,
1111
isEnum,
1212
} from '../scrapers/wiki-page-markup-scraper.js';
13+
import customPluginHookAdd from '../../custom/plugins/hook-add.js';
1314
import fs from 'fs';
1415

1516
export const RESERVERD_KEYWORDS = new Set([
@@ -50,7 +51,9 @@ export class GluaApiWriter {
5051

5152
private readonly files: Map<string, IndexedWikiPage[]> = new Map();
5253

53-
constructor() { }
54+
constructor(
55+
public readonly outputDirectory: string = './output',
56+
) { }
5457

5558
public static safeName(name: string) {
5659
if (name.includes('/'))
@@ -527,6 +530,12 @@ export class GluaApiWriter {
527530
if (func.deprecated)
528531
luaDocComment += `---@deprecated ${removeNewlines(func.deprecated)}\n`;
529532

533+
// TODO: Write a nice API to allow customizing API output from custom/
534+
// See https://github.com/luttje/glua-api-snippets/issues/65
535+
if (func.address === 'hook.Add') {
536+
luaDocComment += customPluginHookAdd(this, func);
537+
}
538+
530539
return luaDocComment;
531540
}
532541

src/cli-scraper.ts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,11 @@ async function startScrape() {
3232
const customDirectory = options.customOverrides?.replace(/\/$/, '') ?? null;
3333
const baseUrl = options.url.replace(/\/$/, '');
3434
const pageListScraper = new WikiPageListScraper(`${baseUrl}/~pagelist?format=json`);
35-
const writer = new GluaApiWriter();
35+
const writer = new GluaApiWriter(baseDirectory);
3636

3737
const retryOptions: RequestInitWithRetry = {
3838
retries: 5,
39-
retryDelay: function(attempt, error, response) {
39+
retryDelay: function (attempt, error, response) {
4040
return Math.pow(2, attempt) * 500; // 500, 1000, 2000, 4000, 8000
4141
}
4242
}
@@ -85,7 +85,7 @@ async function startScrape() {
8585

8686
const pageIndexes = await scrapeAndCollect(pageListScraper);
8787

88-
console.log(`Took ${Math.floor((performance.now()-collect_start) / 100) / 10}s!\n`);
88+
console.log(`Took ${Math.floor((performance.now() - collect_start) / 100) / 10}s!\n`);
8989

9090
console.log('Scraping all pages...');
9191
let scrape_start = performance.now();
@@ -109,7 +109,7 @@ async function startScrape() {
109109
}
110110

111111
fileName = fileName.replace(/[^a-z0-9]/gi, '_').toLowerCase();
112-
112+
113113
// Make sure modules like Entity and ENTITY are placed in the same file.
114114
moduleName = moduleName.toLowerCase();
115115

@@ -132,10 +132,9 @@ async function startScrape() {
132132

133133
queue.push(pageMarkupScraper.scrape());
134134

135-
if (queue.length > 20)
136-
{
135+
if (queue.length > 20) {
137136
const results = await Promise.allSettled(queue);
138-
for ( const result of results) {
137+
for (const result of results) {
139138
if (result.status === "rejected") console.warn("Failed to scrape a page!", result.reason);
140139
}
141140
queue = [];
@@ -144,11 +143,11 @@ async function startScrape() {
144143

145144
// Await any after the loop exits
146145
const results = await Promise.allSettled(queue);
147-
for ( const result of results) {
146+
for (const result of results) {
148147
if (result.status === "rejected") console.warn("Failed to scrape a page!", result.reason);
149148
}
150149

151-
console.log(`Took ${Math.floor((performance.now()-scrape_start) / 100) / 10}s!`);
150+
console.log(`Took ${Math.floor((performance.now() - scrape_start) / 100) / 10}s!`);
152151

153152
writer.writeToDisk();
154153

0 commit comments

Comments
 (0)