1from nameparser.config._invariants import assert_normalized
2
3CONJUNCTIONS = frozenset({
4 '&',
5 'and',
6 'et',
7 'e',
8 'of',
9 'the',
10 'und',
11 'y',
12 # #269: Cyrillic (ru/uk/bg) "and": и, і, та. Ukrainian writes і and
13 # й for the same conjunction, alternating on the surrounding
14 # vowel/consonant for euphony ("Олесь і Олена", "Марія й Петро"),
15 # so real data carries both spellings and neither alone suffices.
16 'и',
17 'і',
18 'й',
19 'та',
20 # #269: Greek "and": και.
21 'και',
22 # #269 follow-up: Arabic "and". Formal script attaches و to the
23 # following word (وفاطمة), so a standalone و token appears only in
24 # informal spacing -- common in real data. Single-character like
25 # 'y'/'и': the single-letter carve-out (Google Code issue 11,
26 # the "john e smith" bug) protects short names (joins
27 # only with enough rootname pieces).
28 'و',
29})
30"""
31Pieces that should join to their neighboring pieces, e.g. "and", "y" and "&".
32"of" and "the" are also include to facilitate joining multiple titles,
33e.g. "President of the United States".
34"""
35
36CONJUNCTIONS_AMBIGUOUS = frozenset({
37 # #383/#479: single letters that read as an INITIAL rather than a
38 # connective when the input carries no case evidence -- a name
39 # written wholly in one case, upper or lower alike. A letter
40 # outside this set joins there, bare capital included.
41 #
42 # 'e' and not 'y', measured: a bare E initial is common (Edward,
43 # Elizabeth) and an 'e' between two surnames is rare outside couple
44 # listings; a bare Y initial is rare and 'y' between two surnames is
45 # the commonest Hispanic compound and this library's oldest fixture
46 # ('Velasquez y Garcia'). Cyrillic и/і/й follow y, not e: #267
47 # blessed their joining and nothing here narrows it.
48 #
49 # 'i' (Catalan) is NOT here because it is not conjunction vocabulary
50 # at all yet; it ships in this subset if #397 adds it, a bare I
51 # initial being as common as a bare E.
52 #
53 # A caller edits the vocabulary rather than a switch: remove 'e' to
54 # restore joining for Portuguese data, add 'y' for a Dutch-style
55 # "every single letter is an initial".
56 'e',
57})
58"""
59Single-letter entries of :data:`CONJUNCTIONS` that read as an initial,
60not a connective, in a name written wholly in one case.
61"""
62
63
64assert_normalized("CONJUNCTIONS", CONJUNCTIONS)
65
66# Guard the invariant the docstring promises, so a future edit that
67# breaks it fails at import time (same rationale as suffixes.py).
68# This holds the SHIPPED constant and nothing else, and two things
69# follow. `assert` is stripped under `python -O`, so under -O even
70# that much is gone. And unlike the other marker subsets, the pair is
71# deliberately absent from Lexicon's subset checks, so a CALLER'S
72# orphan is never rejected -- it is inert instead, the classify fork
73# and its emitter both requiring the base entry before they read the
74# marker (see decisions.md#P3, the 2026-09-13 entries).
75assert CONJUNCTIONS_AMBIGUOUS <= CONJUNCTIONS, \
76 "CONJUNCTIONS_AMBIGUOUS must stay a subset of CONJUNCTIONS"
77assert all(len(w) == 1 and w.upper() != w.lower()
78 for w in CONJUNCTIONS_AMBIGUOUS), \
79 "CONJUNCTIONS_AMBIGUOUS holds CASED single letters; the fork only " \
80 "reads a cased single-letter token, so a caseless letter (Arabic " \
81 "و) or a multi-letter entry would be silently inert"
82assert_normalized("CONJUNCTIONS_AMBIGUOUS", CONJUNCTIONS_AMBIGUOUS)