Skip to content

Overload hook.Add with hook names and callback types (#39) #84

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions __tests__/api-writer/plugins.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// import { GluaApiWriter } from '../../src/api-writer/glua-api-writer';
// import { LibraryFunction } from '../../src/scrapers/wiki-page-markup-scraper';

describe('plugins', () => {
it('should write plugin annotations', async () => {
expect(true).toBe(true);
// 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
// const writer = new GluaApiWriter();
// const api = writer.writePage(<LibraryFunction>{
// name: 'Add',
// address: 'hook.Add',
// parent: 'hook',
// dontDefineParent: true,
// description: '',
// realm: 'shared',
// type: 'libraryfunc',
// url: 'na',
// arguments: [
// {
// args: [{
// name: 'intensity',
// type: 'number',
// description: 'The intensity of the explosion.',
// default: '1000',
// }]
// }
// ],
// returns: [
// {
// type: 'number',
// description: 'The amount of damage done.',
// },
// ],
// });

// expect(api).toContain('---@overload fun(eventName: "Move", identifier: any, func: fun(ply: Player, mv: CMoveData):(boolean?))');
});
});
65 changes: 65 additions & 0 deletions custom/plugins/hook-add.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { GluaApiWriter } from "../../src/api-writer/glua-api-writer";
import { Function, HookFunction } from "../../src/scrapers/wiki-page-markup-scraper";
import path from 'path';
import fs from 'fs';

export default function plugin(writer: GluaApiWriter, func: Function) {
// let hookAnnotations = '---@overload fun(eventName: "Move", identifier: any, func: fun(ply: Player, mv: CMoveData): boolean?)\n';
let hookAnnotations = '';

// Iterate writer.outputDirectory to find all hooks in gm/ that have type "hook"
const hookFiles = path.join(writer.outputDirectory, 'gm');
const hookFilesList = fs.readdirSync(hookFiles, { withFileTypes: true })
.filter(dirent => dirent.isFile() && dirent.name.endsWith('.json'))
.map(dirent => dirent.name);

// Iterate all files and parse them as JSON, only those with type "hook" are considered
for (const file of hookFilesList) {
const filePath = path.join(writer.outputDirectory, 'gm', file);
const fileContent = fs.readFileSync(filePath, 'utf8');
const fileJson = JSON.parse(fileContent)[0] as HookFunction;

// Check if the function is a hook
if (fileJson.type !== 'hook') {
continue;
}

// Add the hook annotation to the hookAnnotations string
let args = '';

if (fileJson.arguments) {
for (const arg of fileJson.arguments) {
if (arg.args) {
for (const argItem of arg.args) {
const argType = GluaApiWriter.transformType(argItem.type);
args += `${argItem.name}: ${argType}, `;
}
}
}

// Remove the last comma and space
args = args.slice(0, -2);
}

let returns = '';
if (fileJson.returns) {
for (const ret of fileJson.returns) {
const retType = GluaApiWriter.transformType(ret.type);
returns += `${retType}, `;
}

// Remove the last comma and space
returns = returns.slice(0, -2);

if (returns !== '') {
// We force the return type to be optional, since hooks should only return a value if they want to
returns = `:(${returns}?)`;
}
}

// Create the overload
hookAnnotations += `---@overload fun(eventName: "${fileJson.name}", identifier: any, func: fun(${args})${returns})\n`;
}

return hookAnnotations;
}
11 changes: 10 additions & 1 deletion src/api-writer/glua-api-writer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
isStruct,
isEnum,
} from '../scrapers/wiki-page-markup-scraper.js';
import customPluginHookAdd from '../../custom/plugins/hook-add.js';
import fs from 'fs';

export const RESERVERD_KEYWORDS = new Set([
Expand Down Expand Up @@ -50,7 +51,9 @@ export class GluaApiWriter {

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

constructor() { }
constructor(
public readonly outputDirectory: string = './output',
) { }

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

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

return luaDocComment;
}

Expand Down
17 changes: 8 additions & 9 deletions src/cli-scraper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ async function startScrape() {
const customDirectory = options.customOverrides?.replace(/\/$/, '') ?? null;
const baseUrl = options.url.replace(/\/$/, '');
const pageListScraper = new WikiPageListScraper(`${baseUrl}/~pagelist?format=json`);
const writer = new GluaApiWriter();
const writer = new GluaApiWriter(baseDirectory);

const retryOptions: RequestInitWithRetry = {
retries: 5,
retryDelay: function(attempt, error, response) {
retryDelay: function (attempt, error, response) {
return Math.pow(2, attempt) * 500; // 500, 1000, 2000, 4000, 8000
}
}
Expand Down Expand Up @@ -85,7 +85,7 @@ async function startScrape() {

const pageIndexes = await scrapeAndCollect(pageListScraper);

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

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

fileName = fileName.replace(/[^a-z0-9]/gi, '_').toLowerCase();

// Make sure modules like Entity and ENTITY are placed in the same file.
moduleName = moduleName.toLowerCase();

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

queue.push(pageMarkupScraper.scrape());

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

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

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

writer.writeToDisk();

Expand Down