Skip to content

Commit

Permalink
CLDR-16836 kbd: ebnf: also test XML files
Browse files Browse the repository at this point in the history
  • Loading branch information
srl295 committed Jan 9, 2025
1 parent 4696e50 commit 001036f
Show file tree
Hide file tree
Showing 7 changed files with 165 additions and 18 deletions.
105 changes: 105 additions & 0 deletions tools/scripts/keyboard-abnf-tests/lib/index.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// Copyright (c) 2025 Unicode, Inc.
// For terms of use, see http://www.unicode.org/copyright.html
// SPDX-License-Identifier: Unicode-3.0

import { XMLParser } from "fast-xml-parser";
import { readFileSync } from "node:fs";
import { join } from "node:path";
import * as abnf from "abnf";
import peggy from "peggy";

/** relative path to ABNF */
export const ABNF_DIR = "../../../keyboards/abnf";

/**
* @param {string} abnfPath path to .abnf file
* @returns the raw parser
*/
export async function getAbnfParser(abnfPath) {
const parsed = await abnf.parseFile(abnfPath);
const opts = {
grammarSource: abnfPath,
trace: false,
};
const text = parsed.toFormat({ format: "peggy" });
const parser = peggy.generate(text, opts);

return parser;
}

/**
* @param {string} abnfPath path to .abnf file
* @param {Object} parser parser from getAbnfParser
* @returns function taking a string and returning results (or throwing)
*/
export async function getParseFunction(abnfPath) {
const parser = await getAbnfParser(abnfPath);
const opts = {
grammarSource: abnfPath,
trace: false,
};
const fn = (str) => parser.parse(str, opts);
return fn;
}

/** @returns true if OK,otherwise throws */
export async function checkXml(path) {
const parseFrom = await getParseFunction(
join(ABNF_DIR, "transform-from-required.abnf")
);
const parseTo = await getParseFunction(
join(ABNF_DIR, "transform-to-required.abnf")
);

const text = readFileSync(path);
const parser = new XMLParser({
ignoreAttributes: false,
trimValues: false,
htmlEntities: true,
});
const r = parser.parse(text, false);

let transforms = r?.keyboard3?.transforms;

if (!transforms) return true; // no transforms

if (!Array.isArray(transforms)) {
transforms = [transforms];
}

for (const transformSet of transforms) {
let transformGroups = transformSet?.transformGroup;

if (!transformGroups) continue; // no transforms

if (!Array.isArray(transformGroups)) {
// there was only one transformGroup
transformGroups = [transformGroups];
}

for (const transformGroup of transformGroups) {
let transforms = transformGroup?.transform;
if (!transforms) continue;
if (!Array.isArray(transforms)) {
transforms = [transforms];
}
for (const transform of transforms) {
const fromStr = transform["@_from"];
try {
parseFrom(fromStr);
} catch (e) {
throw Error(`Bad from="${fromStr}"`, { cause: e });
}
const toStr = transform["@_to"];
if (toStr) {
try {
parseTo(toStr);
} catch (e) {
throw Error(`Bad to="${toStr}"`, { cause: e });
}
}
}
}
}
return true;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@

import { readFileSync, readdirSync } from "node:fs";
import { join } from "node:path";

export const ABNF_DIR = "../../../keyboards/abnf";
import { ABNF_DIR } from "./index.mjs";

/**
*
Expand Down
27 changes: 27 additions & 0 deletions tools/scripts/keyboard-abnf-tests/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions tools/scripts/keyboard-abnf-tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"private": true,
"dependencies": {
"abnf": "^4.3.1",
"fast-xml-parser": "^4.5.1",
"peggy": "^4.2.0"
}
}
2 changes: 1 addition & 1 deletion tools/scripts/keyboard-abnf-tests/test/abnf-valid.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import * as abnf from "abnf";
import { test } from "node:test";
import * as assert from "node:assert";
import { forEachAbnf } from "./util.mjs";
import { forEachAbnf } from "../lib/util.mjs";

function check_refs(parsed) {
const errs = abnf.checkRefs(parsed);
Expand Down
23 changes: 8 additions & 15 deletions tools/scripts/keyboard-abnf-tests/test/datadriven.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,27 @@
// For terms of use, see http://www.unicode.org/copyright.html
// SPDX-License-Identifier: Unicode-3.0

import * as abnf from "abnf";
import { existsSync, readFileSync, readdirSync } from "node:fs";
import { test } from "node:test";
import { basename, join } from "node:path";
import * as assert from "node:assert";
import { forEachAbnf } from "./util.mjs";
import peggy from "peggy";
import { forEachAbnf } from "../lib/util.mjs";
import { getParseFunction } from "../lib/index.mjs";

async function assertTest({ t, abnfPath, testText, expect }) {
const parsed = await abnf.parseFile(abnfPath);
const opts = {
grammarSource: abnfPath,
trace: false,
};
const text = parsed.toFormat({ format: "peggy" });
const parser = peggy.generate(text, opts);
const parser = await getParseFunction(abnfPath);
for (const str of testText
.trim()
.split("\n")
.filter((l) => !/^#/.test(l))) {
await t.test(`"${str}"`, async (t) => {
const fn = () => parser.parse(str, opts);
if (!expect) {
assert.throws(fn, `Expected this expression to fail parsing`);
assert.throws(
() => parser(str),
`Expected this expression to fail parsing`
);
} else {
const results = fn();
const results = parser(str);
assert.ok(results);
}
});
Expand Down Expand Up @@ -64,7 +59,5 @@ await forEachAbnf(async ({ abnfFile, abnfText, abnfPath }) => {
});
} else throw Error(`Unknown testFile ${testFile}`);
}
// const parsed = await abnf.parseFile(abnfPath);
// assert.equal(check_refs(parsed), 0);
});
});
22 changes: 22 additions & 0 deletions tools/scripts/keyboard-abnf-tests/test/xml.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (c) 2025 Unicode, Inc.
// For terms of use, see http://www.unicode.org/copyright.html
// SPDX-License-Identifier: Unicode-3.0

import { join } from "node:path";
import { readdirSync } from "node:fs";
import { test } from "node:test";
import { checkXml } from "../lib/index.mjs";

const KBD_DIR = "../../../keyboards/3.0";

await test("Testing Keyboard XML files for valid transform from/to attributes", async (t) => {
// keyboards, excluding -test.xml files
const kbds = readdirSync(KBD_DIR).filter((f) =>
/^.*(?<!-test)\.xml$/.test(f)
);
for (const kbd of kbds) {
await t.test(`Testing ${kbd}`, async (t) => {
await checkXml(join(KBD_DIR, kbd));
});
}
});

0 comments on commit 001036f

Please sign in to comment.