From c9b3e82e6671594e61441bad1134aeab882402e1 Mon Sep 17 00:00:00 2001 From: "Davide P. Cervone" Date: Mon, 11 Dec 2023 10:47:38 -0500 Subject: [PATCH 1/3] Add ability to do typechecking and conversion on the values of key-value options --- ts/input/tex/ParseUtil.ts | 81 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 79 insertions(+), 2 deletions(-) diff --git a/ts/input/tex/ParseUtil.ts b/ts/input/tex/ParseUtil.ts index 364158ba7..dccbc8018 100644 --- a/ts/input/tex/ParseUtil.ts +++ b/ts/input/tex/ParseUtil.ts @@ -105,6 +105,71 @@ function muReplace([value, unit, length]: [string, string, number]): [string, st return [em.slice(0, -2), 'em', length]; } + +/** + * The data needed for checking the value of a key-value pair. + */ +export type KeyValueType = { + name: string; + verify: (value: string) => boolean; + convert: (value: string) => T; +} + +/** + * A function for creating a key-value type checker + */ +export function KeyValueTypeDefinition( + name: string, + verify: (value: string) => boolean, + convert: (value: string) => T, +) { + return {name, verify, convert} as KeyValueType; +} + +/** + * Predefined value types that can be used to create the list of allowed types. E.g. + * + * const allowed = { + * compact: KeyValueTypes.boolean, + * direction: KeyValueTypes.oneof('up', 'down'), + * 'open-brace': KeyValueType.string + * }; + * + * ParseUtil.keyvalueOptions(options, allowed, true); + */ +export const KeyValueTypes: {[name: string]: KeyValueType | ((data: any) => KeyValueType)} = { + boolean: KeyValueTypeDefinition( + 'boolean', + (value) => value === 'true' || value === 'false', + (value) => value === 'true' + ), + number: KeyValueTypeDefinition( + 'number', + (value) => !!value.match(/^[-+]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[-+]?\d+)?$/), + (value) => parseFloat(value) + ), + integer: KeyValueTypeDefinition( + 'integer', + (value) => !!value.match(/^[-+]?\d+$/), + (value) => parseInt(value) + ), + string: KeyValueTypeDefinition( + 'string', + (_value) => true, + (value) => value + ), + oneof: (...values: string[]) => KeyValueTypeDefinition( + 'oneof', + (value) => values.indexOf(value) >= 0, + (value) => value + ), + dimen: KeyValueTypeDefinition( + 'dimen', + (value) => ParseUtil.matchDimen(value)[0] !== null, + (value) => value + ) +}; + /** * Implementation of the keyval function from https://www.ctan.org/pkg/keyval * @param {string} text The optional parameter string for a package or @@ -785,13 +850,25 @@ export const ParseUtil = { * @return {EnvList} The attribute list. */ keyvalOptions: function(attrib: string, - allowed: {[key: string]: number} = null, + allowed: {[key: string]: number | KeyValueType} = null, error: boolean = false, l3keys: boolean = false): EnvList { let def: EnvList = readKeyval(attrib, l3keys); if (allowed) { for (let key of Object.keys(def)) { - if (!allowed.hasOwnProperty(key)) { + if (allowed.hasOwnProperty(key)) { + // + // If allowed[key] is a type definition, check the key value against that + // + if (typeof allowed[key] === 'object') { + const type = allowed[key] as KeyValueType; + const value = String(def[key]); + if (!type.verify(value)) { + throw new TexError('InvalidValue', 'Value for key \'%1\' is not of the expected type', key); + } + def[key] = type.convert(value); + } + } else { if (error) { throw new TexError('InvalidOption', 'Invalid option: %1', key); } From 1e21798e641abb2db0a0407867f0e1f374b2e26b Mon Sep 17 00:00:00 2001 From: "Davide P. Cervone" Date: Thu, 18 Jan 2024 17:34:23 -0500 Subject: [PATCH 2/3] Use an object rather than a creator function, as per Volker's review --- ts/input/tex/ParseUtil.ts | 35 ++++++++++++++--------------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/ts/input/tex/ParseUtil.ts b/ts/input/tex/ParseUtil.ts index dccbc8018..b72143312 100644 --- a/ts/input/tex/ParseUtil.ts +++ b/ts/input/tex/ParseUtil.ts @@ -109,22 +109,14 @@ function muReplace([value, unit, length]: [string, string, number]): [string, st /** * The data needed for checking the value of a key-value pair. */ -export type KeyValueType = { - name: string; - verify: (value: string) => boolean; - convert: (value: string) => T; +export class KeyValueType { + constructor( + public name: string, + public verify: (value: string) => boolean, + public convert: (value: string) => T + ) {} } -/** - * A function for creating a key-value type checker - */ -export function KeyValueTypeDefinition( - name: string, - verify: (value: string) => boolean, - convert: (value: string) => T, -) { - return {name, verify, convert} as KeyValueType; -} /** * Predefined value types that can be used to create the list of allowed types. E.g. @@ -138,38 +130,39 @@ export function KeyValueTypeDefinition( * ParseUtil.keyvalueOptions(options, allowed, true); */ export const KeyValueTypes: {[name: string]: KeyValueType | ((data: any) => KeyValueType)} = { - boolean: KeyValueTypeDefinition( + boolean: new KeyValueType( 'boolean', (value) => value === 'true' || value === 'false', (value) => value === 'true' ), - number: KeyValueTypeDefinition( + number: new KeyValueType( 'number', (value) => !!value.match(/^[-+]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[-+]?\d+)?$/), (value) => parseFloat(value) ), - integer: KeyValueTypeDefinition( + integer: new KeyValueType( 'integer', (value) => !!value.match(/^[-+]?\d+$/), (value) => parseInt(value) ), - string: KeyValueTypeDefinition( + string: new KeyValueType( 'string', (_value) => true, (value) => value ), - oneof: (...values: string[]) => KeyValueTypeDefinition( + oneof: (...values: string[]) => new KeyValueType( 'oneof', (value) => values.indexOf(value) >= 0, (value) => value ), - dimen: KeyValueTypeDefinition( + dimen: new KeyValueType( 'dimen', (value) => ParseUtil.matchDimen(value)[0] !== null, (value) => value ) }; + /** * Implementation of the keyval function from https://www.ctan.org/pkg/keyval * @param {string} text The optional parameter string for a package or @@ -860,7 +853,7 @@ export const ParseUtil = { // // If allowed[key] is a type definition, check the key value against that // - if (typeof allowed[key] === 'object') { + if (allowed[key] instanceof KeyValueType) { const type = allowed[key] as KeyValueType; const value = String(def[key]); if (!type.verify(value)) { From 32fde4e842490e3df6965ddf7679e2b4023dd076 Mon Sep 17 00:00:00 2001 From: "Davide P. Cervone" Date: Thu, 18 Jan 2024 17:36:51 -0500 Subject: [PATCH 3/3] Try to remove merge conflict --- ts/input/tex/ParseUtil.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ts/input/tex/ParseUtil.ts b/ts/input/tex/ParseUtil.ts index b72143312..a7f5c0a70 100644 --- a/ts/input/tex/ParseUtil.ts +++ b/ts/input/tex/ParseUtil.ts @@ -842,10 +842,10 @@ export const ParseUtil = { * @param {boolean?} l3keys If true, use l3key-style parsing (only remove one set of braces) * @return {EnvList} The attribute list. */ - keyvalOptions: function(attrib: string, - allowed: {[key: string]: number | KeyValueType} = null, - error: boolean = false, - l3keys: boolean = false): EnvList { + keyvalOptions(attrib: string, + allowed: {[key: string]: number | KeyValueType} = null, + error: boolean = false, + l3keys: boolean = false): EnvList { let def: EnvList = readKeyval(attrib, l3keys); if (allowed) { for (let key of Object.keys(def)) {