From 71810c1fee88b665891b7b39152e022f1c18a598 Mon Sep 17 00:00:00 2001 From: Lexi Robinson Date: Sun, 15 Jun 2025 16:13:42 +0100 Subject: [PATCH] Support class/library fields I could have refactored a lot of other things to use isValidDescription too but I decided not to because I'm lazy Fixes #10 --- src/types.ts | 10 +++++ src/wiki-scraper.test.ts | 64 +++++++++++++++++++++++++++++ src/wiki-scraper.ts | 88 +++++++++++++++++++++++++++++++++------- 3 files changed, 147 insertions(+), 15 deletions(-) diff --git a/src/types.ts b/src/types.ts index 9cc059e..2177b37 100644 --- a/src/types.ts +++ b/src/types.ts @@ -38,6 +38,7 @@ export interface Class { parent?: string; description?: string; functions?: Array; + fields?: Array; } export interface Panel { @@ -80,6 +81,15 @@ export interface Function { overloads?: Array; } +export interface ClassField { + name: string; + type: string; + parent: string; + source?: FunctionSource; + description?: string; + realms: Array; +} + export interface Type { name: string; description?: string; diff --git a/src/wiki-scraper.test.ts b/src/wiki-scraper.test.ts index a20d1cb..ac4d8ab 100644 --- a/src/wiki-scraper.test.ts +++ b/src/wiki-scraper.test.ts @@ -66,6 +66,70 @@ describe("WikiScraper", () => { ]); }); + it("parses a field page", () => { + const mathPiPageContent = + '\r\n' + + "\t\r\n" + + "A variable containing the mathematical constant pi. (`3.1415926535898`)\r\n" + + "\r\n" + + "See also: Trigonometry\r\n" + + "\r\n" + + "It should be noted that due to the nature of floating point numbers, results of calculations with `math.pi` may not be what you expect. See second example below.\r\n" + + "\r\n" + + "\tShared and Menu\r\n" + + "\t\r\n" + + '\t\tThe mathematical constant, Pi.\r\n' + + "\t\r\n" + + "\r\n" + + "\r\n" + + "\r\n" + + "\r\n" + + "print( math.cos( math.pi ) )\r\n" + + "\r\n" + + "\r\n" + + "```\r\n" + + "-1\r\n" + + "```\r\n" + + "\r\n" + + "\r\n" + + "\r\n" + + "\r\n" + + "\r\n" + + "\r\n" + + "`sin(π) = 0`, but because floating point precision is not unlimited it cannot be calculated as exactly `0`.\r\n" + + "\r\n" + + "\r\n" + + "print( math.sin( math.pi ), math.sin( math.pi ) == 0 )\r\n" + + "\r\n" + + "\r\n" + + "```\r\n" + + "1.2246467991474e-16 false\r\n" + + "```\r\n" + + "\r\n" + + "\r\n"; + + const isField = wikiScraper.isClassFieldPage(mathPiPageContent); + expect(isField).toBeTruthy() + + const mathPiField = wikiScraper.parseFieldPage(mathPiPageContent); + + expect(mathPiField.name).toBe("pi"); + expect(mathPiField.parent).toBe("math"); + expect(mathPiField.realms).toEqual( + expect.arrayContaining(["client", "server", "menu"]), + ); + expect(mathPiField.type).toBe("number"); + const expectedDesc = ` +A variable containing the mathematical constant pi. (\`3.1415926535898\`) + +See also: Trigonometry + +It should be noted that due to the nature of floating point numbers, results of calculations with \`math.pi\` may not be what you expect. See second example below. +` + expect(mathPiField.description).toBe(expectedDesc.trim()); + }); + + it("parses a panel page", () => { const dbuttonPageContent = "\r\n" + diff --git a/src/wiki-scraper.ts b/src/wiki-scraper.ts index 983578c..61b0c2d 100644 --- a/src/wiki-scraper.ts +++ b/src/wiki-scraper.ts @@ -8,6 +8,7 @@ import { FunctionArgument, FunctionReturnValue, Realm, + ClassField, Class, Panel, WikiPage, @@ -269,6 +270,11 @@ export class WikiScraper { if (type.description) { _class.description = type.description; } + } else if (this.isClassFieldPage(wikiPage.content)) { + const field = this.parseFieldPage(wikiPage.content); + + _class.fields = _class.fields ?? []; + _class.fields.push(field); } else if (this.isFunctionPage(wikiPage.content)) { const _function = this.parseFunctionPage(wikiPage.content); @@ -286,6 +292,43 @@ export class WikiScraper { return Array.from(classes.values()); } + public parseFieldPage(pageContent: string): ClassField { + const $ = this.parseContent(pageContent); + const name = $("function").attr().name; + const parent = $("function").attr().parent; + let rawDescription = $("function > description").html(); + const $sourceFile = $("function > file"); + const realmsRaw = this.trimMultiLineString($("function > realm").text()); + const realms = this.parseRealms(realmsRaw); + + const typeEl = $("function > rets") + .children() + .filter((_, el) => el.type == "tag") + .first(); + + const type = typeEl.attr("type") ?? "nil" + + // Currently all the fields define both a normal description and a + // return description, but since the return description seems to mostly + // be useless, only pick it if there is no real description + if (!this.isValidDescription(rawDescription)) { + rawDescription = $(typeEl).html(); + } + let description: string | undefined = undefined + if (this.isValidDescription(rawDescription)) { + description = this.trimMultiLineString(rawDescription); + } + + return { + name, + type, + description, + parent, + realms, + source: this.parseSourceFile($sourceFile) + }; + } + public parseFunctionPage(pageContent: string): Function { const $ = this.parseContent(pageContent); const name = $("function").attr().name; @@ -339,6 +382,7 @@ export class WikiScraper { name: name, parent: parent, realms: realms, + source: this.parseSourceFile($sourceFile) }; if (description && description !== "") { @@ -362,27 +406,31 @@ export class WikiScraper { }); } - if ($sourceFile.length > 0) { - const file = $sourceFile.text(); + return _function; + } + + private parseSourceFile($sourceFile: cheerio.Cheerio): FunctionSource | undefined { + if ($sourceFile.length == 0) { + return undefined + } - const line = $sourceFile.attr().line.replace("L", ""); - const lines = line.split("-"); - const lineStart = lines[0]; - const lineEnd = lines[1]; + const file = $sourceFile.text(); - const source: FunctionSource = { - file: file, - lineStart: Number(lineStart), - }; + const line = $sourceFile.attr().line.replace("L", ""); + const lines = line.split("-"); + const lineStart = lines[0]; + const lineEnd = lines[1]; - if (lineEnd) { - source.lineEnd = Number(lineEnd); - } + const source: FunctionSource = { + file: file, + lineStart: Number(lineStart), + }; - _function.source = source; + if (lineEnd) { + source.lineEnd = Number(lineEnd); } - return _function; + return source; } public parsePanelPage(pageContent: string): Panel { @@ -535,6 +583,12 @@ export class WikiScraper { return $("panel").length > 0; } + public isClassFieldPage(pageContent: string): boolean { + const $ = this.parseContent(pageContent); + + return $("function[type$=field]").length > 0; + } + public isFunctionPage(pageContent: string): boolean { const $ = this.parseContent(pageContent); @@ -591,6 +645,10 @@ export class WikiScraper { return cheerio.load(content, { decodeEntities: false }); } + private isValidDescription(str: string | null): str is string { + return str != null && str != "" + } + private trimMultiLineString(str: string) { return str .split("\n")