From 1844a492b32246afbc560da011dcc8a56c5dcaeb Mon Sep 17 00:00:00 2001 From: ctcpip Date: Fri, 25 Aug 2023 10:09:25 -0500 Subject: [PATCH] =?UTF-8?q?=E2=9C=85=20add=20test=20for=20ideal=20TLAs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/check-delegates-test.mjs | 4 ++ scripts/check-delegates.mjs | 79 ++++++++++++++++++++++++++++++-- 2 files changed, 80 insertions(+), 3 deletions(-) diff --git a/scripts/check-delegates-test.mjs b/scripts/check-delegates-test.mjs index b3265b12..3a727116 100644 --- a/scripts/check-delegates-test.mjs +++ b/scripts/check-delegates-test.mjs @@ -8,6 +8,8 @@ const twoLetter = `Chris de Almeida (CDA)\nRob Palmer (RP)\nUjjwal Sharma (USA)` const threeLetter = `Chris de Almeida (CDA)\nRob Palmer (ROBPALMER)\nUjjwal Sharma (USA)`; const duplicate = `Chris de Almeida (CDA)\nRob Palmer (RPR)\nUjjwal Sharma (USA)\nUjjwal Sharma (USA)`; const valid = `Chris de Almeida (CDA)\nMichael Ficarra (MF)\nRob Palmer (RPR)\nUjjwal Sharma (USA)`; +const mononymous = `Chris de Almeida (CDA)\nYee (YEE)\nRob Palmer (RPR)\nUjjwal Sharma (USA)`; +const idealTLA = `Chris de Almeida (CAA)\nMichael Ficarra (MF)\nRob Palmer (RPR)\nUjjwal Sharma (USA)`; assert.throws(() => checkDelegates(lex), { message: 'Line 3: Not in lexicographic order.' }); // also validates expected line number assert.throws(() => checkDelegates(missing), { message: /Missing abbreviation for/ }); @@ -15,5 +17,7 @@ assert.throws(() => checkDelegates(uppercaseLatin), { message: /Abbreviations mu assert.throws(() => checkDelegates(twoLetter), { message: /not in allowlist. New delegate abbreviations must be three letters/ }); assert.throws(() => checkDelegates(threeLetter), { message: /New delegate abbreviations must be three letters/ }); assert.throws(() => checkDelegates(duplicate), { message: /Conflicting usage on line/ }); +assert.throws(() => checkDelegates(mononymous), { message: /Unexpected mononymous delegate/ }); +assert.throws(() => checkDelegates(idealTLA), { message: /Should be using ideal TLA \(CDA\)/ }); assert.doesNotThrow(() => checkDelegates(valid)); diff --git a/scripts/check-delegates.mjs b/scripts/check-delegates.mjs index 862bbe5e..51e0f58e 100755 --- a/scripts/check-delegates.mjs +++ b/scripts/check-delegates.mjs @@ -2,7 +2,16 @@ import fs from 'fs'; -export function checkDelegates(contents = fs.readFileSync('./delegates.txt', 'utf8').toString()) { +export function checkDelegates(contents) { + + const DELEGATES_FILE_PATH = './delegates.txt'; + + let isContentsDelegatesFile = false; + + if (!contents) { + contents = fs.readFileSync(DELEGATES_FILE_PATH, 'utf8').toString(); + isContentsDelegatesFile = true; + } console.debug('checking delegates...'); @@ -19,8 +28,36 @@ export function checkDelegates(contents = fs.readFileSync('./delegates.txt', 'ut 'VM', 'WH', 'YK', 'ZB', ]); + // list of abbreviations that are not ideal. + // most of these are here because they are grandfathered in. + // new elements should only be false positives. + const NON_IDEAL_ABBRS = new Set([ + 'ABU', 'ACB', 'AEC', 'AEH', 'ALH', 'ARB', 'ASH', 'AVC', 'AVK', + 'AVP', 'AWB', 'AYS', 'BNG', 'BRK', 'CCN', 'CHU', 'CJI', 'CJR', + 'CJT', 'CZW', 'DAS', 'DDC', 'DEN', 'DFS', 'DFV', 'DHC', 'DJF', + 'DJW', 'DLM', 'DMM', 'DMP', 'DTL', 'DVE', 'EDB', 'FED', 'FRT', + 'FYT', 'GCW', 'GKZ', 'GPT', 'GRS', 'HUG', 'HUX', 'IOA', 'IVH', + 'JAN', 'JAZ', 'JBS', 'JDD', 'JFP', 'JHJ', 'JHL', 'JMN', 'JPB', + 'JPG', 'JRB', 'JSL', 'JSW', 'JTO', 'JXF', 'JXZ', 'JZY', 'KBK', + 'KCD', 'KGO', 'KHG', 'KOT', 'KZM', 'LCA', 'LCP', 'LEO', 'LFP', + 'LGY', 'LIU', 'LWT', 'LWW', 'LZH', 'LZJ', 'LZM', 'MAG', 'MAR', + 'MCM', 'MCW', 'MED', 'MGR', 'MHA', 'MJN', 'MJS', 'MLS', 'MPC', + 'MQW', 'MWS', 'MYC', 'MZG', 'NLY', 'PFC', 'PFM', 'PLH', 'PMD', + 'PZE', 'REK', 'ROF', 'RTM', 'SFC', 'SJL', 'SJY', 'SMK', 'SNS', + 'SRK', 'SRL', 'SSA', 'STH', 'SYH', 'SYP', 'SZH', 'SZT', 'TAB', + 'TEK', 'TJC', 'TOC', 'TVC', 'WES', 'WMM', 'WWW', 'WXK', 'WYJ', + 'XAX', 'XTY', 'XWC', 'YIY', 'YJM', 'YKL', 'YKZ', 'YRL', 'YTX', + 'YYC', 'ZJL', 'ZRJ', 'ZYY', + ]); + + // delegates with only one name. this list should be as close to zero as possible... + const MONONYMOUS = new Set([ + 'Surma', + ]); + + const allAbbrs = new Set(contents.match(/(?<=\()[^)]*(?=\))/g)); - const re = /^(?[^(]+)(?: \((?[^)]*)\))?$/; + const re = /^(?[^( ]+) ?(?[^(]+)?(?: \((?[^)]*)\))?$/; const abbrs = new Map; const lines = contents.split('\n'); @@ -30,7 +67,7 @@ export function checkDelegates(contents = fs.readFileSync('./delegates.txt', 'ut for (const line of lines) { if (line.length === 0) continue; const match = re.exec(line); - const { abbr } = match.groups; + const { abbr, firstName, lastName } = match.groups; if (previousLine.localeCompare(line, 'en') > 0) { throw new Error(`Line ${lineNumber}: Not in lexicographic order.`); @@ -57,9 +94,45 @@ export function checkDelegates(contents = fs.readFileSync('./delegates.txt', 'ut } abbrs.set(abbr, lineNumber); + const idealTLA = getIdealTLA(firstName, lastName); + + if (idealTLA){ + if (!allAbbrs.has(idealTLA) && !NON_IDEAL_ABBRS.has(abbr) && !TWO_LETTER_ABBRS.has(abbr)){ // duplicates the 2-letter check, but helpful to distinguish these issues + throw new Error(`Line ${lineNumber}: Should be using ideal TLA (${idealTLA}). Note: because code cannot distinguish between a middle name vs a two-part last name, this may be a false positive.`); + } + } else if (!MONONYMOUS.has(firstName)) { + throw new Error(`Line ${lineNumber}: Unexpected mononymous delegate.`); + } + previousLine = line; ++lineNumber; } + if (isContentsDelegatesFile) { + + for (const abbr of TWO_LETTER_ABBRS) { + if (!allAbbrs.has(abbr)){ + throw new Error(`abbreviation ${abbr} is included in TWO_LETTER_ABBRS, but does not exist in ${DELEGATES_FILE_PATH}`); + } + } + + for (const abbr of NON_IDEAL_ABBRS) { + if (!allAbbrs.has(abbr)){ + throw new Error(`abbreviation ${abbr} is included in NON_IDEAL_ABBRS, but does not exist in ${DELEGATES_FILE_PATH}`); + } + } + + } + console.debug('...delegates are valid\n'); } + +function getIdealTLA(firstName, lastName) { + + if (lastName) { + return `${firstName.slice(0, 1)}${lastName.slice(0, 1)}${lastName.slice(lastName.length - 1)}`.toUpperCase(); + } + + return null; + +}