mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-09 20:12:57 +00:00
FU.5: craft-proof id, ru — the cased-letter rule for words in capitals (craft-libs#485): a caseless part is no abbreviation; shared in_capitals / capitals_part
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FypgCX8LsYYE8fsj4byBm2
This commit is contained in:
1 parent
86345ebcf5
commit
1f345ea809
4 files changed
+47
-6
No files matched your search
@@ -76,7 +76,7 @@ fn check(d: &AffixDict, word: &str, opts: &crate::Options) -> bool {
|
||||
|| w.split('-').all(|p| {
|
||||
p.chars().count() <= 1
|
||||
|| CLITICS.iter().any(|c| p.eq_ignore_ascii_case(c))
|
||||
|| (opts.ignore_uppercase && p.chars().all(|c| !c.is_lowercase()) && p.chars().count() <= 6)
|
||||
|| (opts.ignore_uppercase && crate::spelling::capitals_part(p, 6))
|
||||
|| known(d, p)
|
||||
});
|
||||
}
|
||||
@@ -141,6 +141,19 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// Review follow-up (craft-libs#485's cased-letter rule): a part in a caseless script is no
|
||||
/// abbreviation, so `buku-日本` is checked part by part; a part in capitals or without letters
|
||||
/// still is one (`KTP-nya`, `COVID-19` with numbers checked).
|
||||
#[test]
|
||||
fn caseless_parts_are_not_abbreviations() {
|
||||
test_dict::install();
|
||||
let d = dictionary();
|
||||
let defaults = crate::Options::default();
|
||||
assert!(!check(&d, "buku-日本", &defaults) && !check(&d, "buku-ไทย", &defaults));
|
||||
let numbers = crate::Options { ignore_numbers: false, ..defaults };
|
||||
assert!(check(&d, "KTP-nya", &defaults) && check(&d, "buku-19", &numbers));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn forbidden_words_are_rejected_in_every_case() {
|
||||
test_dict::install();
|
||||
|
||||
@@ -669,7 +669,7 @@ pub(crate) fn accepted_by_options(word: &str, opts: &Options) -> bool {
|
||||
|| !w.chars().any(char::is_alphabetic)
|
||||
|| (opts.ignore_numbers && w.chars().any(|c| c.is_ascii_digit()))
|
||||
// Only a word with a cased letter is in capitals: a Hebrew or Thai word has none.
|
||||
|| (opts.ignore_uppercase && w.chars().any(char::is_uppercase) && w.chars().all(|c| !c.is_lowercase()))
|
||||
|| (opts.ignore_uppercase && spelling::in_capitals(w))
|
||||
// The whole word: the languages split a hyphenated word into its parts, and an added
|
||||
// `Unigranrio-RJ` is none of them (#2082).
|
||||
|| in_user_dictionary(&w.to_lowercase())
|
||||
|
||||
@@ -105,7 +105,7 @@ fn check(d: &AffixDict, word: &str, opts: &crate::Options) -> bool {
|
||||
if n <= 1 || (opts.ignore_numbers && w.chars().any(char::is_numeric)) || !w.chars().any(char::is_alphabetic) {
|
||||
return true;
|
||||
}
|
||||
if opts.ignore_uppercase && w.chars().all(|c| !c.is_lowercase()) && n <= ABBREVIATION_LETTERS {
|
||||
if opts.ignore_uppercase && crate::spelling::in_capitals(w) && n <= ABBREVIATION_LETTERS {
|
||||
return true;
|
||||
}
|
||||
// Camel case (`ВКонтакте`, `МирКрасок`): a brand or an identifier, not a dictionary word.
|
||||
@@ -333,7 +333,7 @@ fn hyphenated(d: &AffixDict, w: &str, opts: &crate::Options) -> bool {
|
||||
let last = parts.len() - 1;
|
||||
parts.iter().enumerate().all(|(i, p)| {
|
||||
// An abbreviation part (`ИИ-консультант`, `SMS-рассылка`) or a known word.
|
||||
if known(d, p) || (opts.ignore_uppercase && p.chars().all(|c| !c.is_lowercase()) && p.chars().count() <= ABBREVIATION_LETTERS) {
|
||||
if known(d, p) || (opts.ignore_uppercase && crate::spelling::capitals_part(p, ABBREVIATION_LETTERS)) {
|
||||
return true;
|
||||
}
|
||||
// A first part with a linking vowel: the stem (`северо` → `север`) or its adjective
|
||||
@@ -485,5 +485,7 @@ mod tests {
|
||||
assert!(!crate::is_correct_in_with("м²", lang, &check_digits), "a superscript is a number too");
|
||||
assert!(crate::is_correct_in_with("2026", lang, &check_digits), "numbers alone stay correct");
|
||||
assert!(is_correct("ПРЕВЕТ"), "direct checks keep the defaults");
|
||||
// The cased-letter rule (craft-libs#485): capitals and letterless parts are abbreviations.
|
||||
assert!(crate::is_correct_in_with("ИИ-2", lang, &check_digits) && crate::is_correct_in_with("ТУ-154М", lang, &check_digits));
|
||||
}
|
||||
}
|
||||
@@ -32,17 +32,43 @@ pub(crate) fn word_to_check_in<'a>(d: &AffixDict, word: &'a str, opts: &Options)
|
||||
if n <= 1 || (opts.ignore_numbers && w.chars().any(|c| c.is_ascii_digit())) || !w.chars().any(char::is_alphabetic) {
|
||||
return None;
|
||||
}
|
||||
// Only a word with a cased letter can be in capitals: Hebrew, Arabic, Thai… words have none.
|
||||
if opts.ignore_uppercase && w.chars().any(char::is_uppercase) && w.chars().all(|c| !c.is_lowercase()) && n <= 6 {
|
||||
if opts.ignore_uppercase && in_capitals(w) && n <= 6 {
|
||||
return None;
|
||||
}
|
||||
Some(w)
|
||||
}
|
||||
|
||||
/// Is `w` in capitals: a cased letter and no lower-case one? Only a word with a cased letter can
|
||||
/// be: Hebrew, Arabic, Thai, Chinese… words have none, so they are never taken for acronyms.
|
||||
pub(crate) fn in_capitals(w: &str) -> bool {
|
||||
w.chars().any(char::is_uppercase) && w.chars().all(|c| !c.is_lowercase())
|
||||
}
|
||||
|
||||
/// Is `part` of a hyphenated word an abbreviation Ignore words in UPPERCASE skips: at most `max`
|
||||
/// characters, [`in_capitals`] or without letters (`SMS` in `SMS-ка`, `19` in `COVID-19`)? A
|
||||
/// part in a caseless script is no abbreviation.
|
||||
pub(crate) fn capitals_part(part: &str, max: usize) -> bool {
|
||||
part.chars().count() <= max && (in_capitals(part) || !part.chars().any(char::is_alphabetic))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// craft-libs#485's cased-letter rule, shared by every language: a word or part is in
|
||||
/// capitals only with a cased letter; a part without letters counts as an abbreviation.
|
||||
#[test]
|
||||
fn capitals_need_a_cased_letter() {
|
||||
for w in ["PKO", "SMS", "ÉTÉ", "ВКП", "B2B"] {
|
||||
assert!(in_capitals(w) && capitals_part(w, 6), "{w}");
|
||||
}
|
||||
for w in ["日本", "שלום", "ไทย", "Pko", "pko", "123", ""] {
|
||||
assert!(!in_capitals(w), "{w}");
|
||||
}
|
||||
assert!(capitals_part("19", 6) && capitals_part("", 6));
|
||||
assert!(!capitals_part("日本", 6) && !capitals_part("ไทย", 6) && !capitals_part("ABCDEFG", 6));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn words_needing_no_lookup() {
|
||||
let d = AffixDict::from_hunspell("", "1\nkot\n", &[]).unwrap();
|
||||
|
||||
Reference in new issue
Block a user