feat: just printaffixes prints letters free for new flags (#1540)

* feat: `just printaffixes` prints letters free for new flags

I've also renamed it to `just annotations` but kept the old name as an alias.

Annotations that have comment fields will also be in the printout now.

* fix: remove comment

* feat: `suggestannotation`

Pass in a term you want to make a new annotation for and it will check if any of the letters are not already used, or are OK to reuse.
This commit is contained in:
Andrew Dunbar 2025-07-14 22:59:52 +07:00 committed by GitHub
parent f612cf1c33
commit ffc88736cf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 98 additions and 17 deletions

View file

@ -12,7 +12,8 @@
}
],
"target": [],
"base_metadata": {}
"base_metadata": {},
"rename_ok": true
},
"L": {
"#": "'-ment' suffix",
@ -26,7 +27,8 @@
}
],
"target": [],
"base_metadata": {}
"base_metadata": {},
"rename_ok": true
},
"E": {
"#": "'dis-' prefix",
@ -40,7 +42,8 @@
}
],
"target": [],
"base_metadata": {}
"base_metadata": {},
"rename_ok": true
},
"Y": {
"#": "'-ly' suffix",
@ -259,7 +262,8 @@
}
],
"target": [],
"base_metadata": {}
"base_metadata": {},
"rename_ok": true
},
"p": {
"#": "'-(i)ness' suffix",
@ -283,10 +287,12 @@
}
],
"target": [],
"base_metadata": {}
"base_metadata": {},
"rename_ok": true
},
"g": {
"#": "-'s possessive suffix; contraction of 'has' and 'is",
"//": "mnemonic: 'genitive' is a similar concept to 'possessive'",
"kind": "suffix",
"cross_product": true,
"replacements": [
@ -321,7 +327,8 @@
}
],
"target": [],
"base_metadata": {}
"base_metadata": {},
"rename_ok": true
},
"B": {
"#": "'-able' suffix",
@ -481,7 +488,8 @@
}
],
"target": [],
"base_metadata": {}
"base_metadata": {},
"rename_ok": true
},
"f": {
"#": "'-ful' suffix",
@ -533,7 +541,8 @@
}
],
"target": [],
"base_metadata": {}
"base_metadata": {},
"rename_ok": true
},
"z": {
"#": "'-ings' suffix",
@ -552,7 +561,8 @@
}
],
"target": [],
"base_metadata": {}
"base_metadata": {},
"rename_ok": true
}
},
"properties": {
@ -593,6 +603,7 @@
},
"x": {
"#": "swear word property",
"//": "'xxx' used to be a placeholder for swear words",
"propagate": true,
"metadata": {
"swear": true
@ -632,7 +643,8 @@
"#": "common word property",
"metadata": {
"common": true
}
},
"rename_ok": true
},
"P": {
"#": "preposition property",
@ -716,6 +728,7 @@
},
".": {
"#": "singular pronoun property",
"//": "mnemonic: one dot",
"metadata": {
"pronoun": {
"is_singular": true
@ -724,6 +737,7 @@
},
":": {
"#": "plural pronoun property",
"//": "mnemonic: multiple dots",
"metadata": {
"pronoun": {
"is_plural": true
@ -744,17 +758,20 @@
"propagate": true,
"metadata": {
"dialects": "AMERICAN"
}
},
"rename_ok": true
},
"!": {
"#": "GB property",
"propagate": true,
"metadata": {
"dialects": "BRITISH"
}
},
"rename_ok": true
},
"@": {
"#": "CA property",
"//": "mnemonic: at symbol resembles an 'a' inside a 'C'",
"propagate": true,
"metadata": {
"dialects": "CANADIAN"
@ -762,6 +779,7 @@
},
"_": {
"#": "AU property",
"//": "mnemonic: down under",
"propagate": true,
"metadata": {
"dialects": "AUSTRALIAN"
@ -785,6 +803,7 @@
},
"5": {
"#": "possessive determiner property",
"//": "mnemonic: 5 looks like an 's'",
"metadata": {
"determiner": {
"is_possessive": true

View file

@ -438,19 +438,39 @@ registerlinter module name:
sed -i "/insert_expr_rule!(ChockFull, true);/a \ \ \ \ insert_struct_rule!({{name}}, true);" "$D/lint_group.rs"
just format
# Print affixes and their descriptions from annotations.json
printaffixes:
# Print annotations and their descriptions from annotations.json
alias printaffixes := printannotations
printannotations:
#! /usr/bin/env node
const affixesData = require('{{justfile_directory()}}/harper-core/annotations.json');
const allAffixes = {
const allEntries = {
...affixesData.affixes || {},
...affixesData.properties || {}
};
Object.entries(allAffixes).sort((a, b) => a[0].localeCompare(b[0])).forEach(([affix, fields]) => {
Object.entries(allEntries).sort((a, b) => a[0].localeCompare(b[0])).forEach(([flag, fields]) => {
const description = fields['#'] || '';
description && console.log(affix + ': ' + description);
const comment = fields['//'] || null;
description && console.log(flag + ': ' + description + (comment ? '\t\t// ' + comment : ''));
});
console.log('Available letters for new flags:', [...Array.from({length: 26}, (_, i) =>
[String.fromCharCode(65 + i), String.fromCharCode(97 + i)]
).flat()].filter(letter => !Object.keys(allEntries).includes(letter)).sort().join(' '));
console.log('Available digits for new flags:', [...Array.from({length: 10}, (_, i) =>
String(i)
)].filter(digit => !Object.keys(allEntries).includes(digit)).sort().join(' '));
console.log('Available symbols for new flags:',
[...Array.from('!"#$%&\'()*+,-./:;<=>?@\[\\\]\^_`{|}~')]
.filter(symbol => !Object.keys(allEntries).includes(symbol)).sort().join(' '));
console.log('Available Latin-1 characters for new flags:');
[...Array.from({length: 256-160}, (_, i) => String.fromCharCode(160 + i))]
.filter(char => !Object.keys(allEntries).includes(char) && char.charCodeAt(0) !== 160 && char.charCodeAt(0) !== 173)
.sort()
.join(' ')
.match(/.{1,64}/g)
.forEach(line => console.log(' ' + line));
# Get the most recent changes to the curated dictionary. Includes an optional argument to specify the number of commits to look back. Defaults to 1.
newest-dict-changes *numCommits:
#! /usr/bin/env node
@ -553,3 +573,45 @@ newest-dict-changes *numCommits:
});
});
});
# Suggest annotations for a potential new property annotation
suggestannotation input:
#! /usr/bin/env node
const affixesData = require('{{justfile_directory()}}/harper-core/annotations.json');
const allEntries = {
...affixesData.affixes || {},
...affixesData.properties || {}
};
// Get all used flags
const usedFlags = new Set(Object.keys(allEntries));
// Process input string and check both cases
const input = '{{input}}';
const normalizedInput = input.replace(/\s/g, '');
const uniqueChars = [...new Set(normalizedInput.toUpperCase() + normalizedInput.toLowerCase())];
console.log(`Checking input: "${input}"\n${'='.repeat(50)}`);
// Check each character in input
const availableChars = [...new Set(uniqueChars)]
.filter(char => !usedFlags.has(char));
if (availableChars.length > 0) {
console.log(`These characters of "${input}" are available to use for new annotations:`);
availableChars.forEach(char => console.log(` '${char}' (${char.charCodeAt(0)})`));
} else {
const inputChars = new Set(normalizedInput.toLowerCase() + normalizedInput.toUpperCase());
const renamable = Object.entries(allEntries)
.filter(([flag, entry]) => entry.rename_ok && inputChars.has(flag))
.sort((a, b) => a[0].localeCompare(b[0]));
if (renamable.length > 0) {
console.log(`None of the characters of "${input}" are available to use for new annotations, but these ones are OK to be moved to make way for new annotations:`);
renamable.forEach(([flag, entry]) => {
console.log(` '${flag}': ${entry['#'] || 'No description'}${entry['//'] ? ` (${entry['//']})` : ''}`);
});
} else {
console.log(`None of the characters of "${input}" are available to use for new annotations, and none of them are OK to be moved to make way for new annotations.`);
}
}