Documentation
¶
Overview ¶
Package namefold is the one rule for when two spellings of a name are the same text: what a search compares, and the part of a tag's identity that is about letters rather than whitespace.
Two things make one name look like two, and both are folded here, in this order, for both sides of every comparison:
How the letters are ENCODED. A keyboard types `ü` as one code point (Unicode normalisation form C); macOS hands a filename over decomposed, `u` followed by U+0308 COMBINING DIAERESIS (form D). filex stores the name it is given — the storage needs those bytes — so a catalogue holds both. Measured on a production catalogue on 2026-09-24: 71 388 of 169 471 names were decomposed, about nine in ten of the names with a Turkish letter in them (GitHub PR #46). Canonical composes.
How the letters are CASED. Go's default case mapping is wrong for Turkish in exactly the letters a Turkish user types most: `I` lowers to `i`, not `ı`, and `ı` has no default partner at all, so "IŞIK" and "ışık" — one word — are different text to strings.ToLower. Turkish casing is wrong the other way: "INVOICE" lowers to "ınvoıce". So the four Latin i's — I, ı, İ, i — are ONE letter here, and everything else is lower-cased the default way. That is the rule internal/tagname has used for tags since v0.43.0; a search that disagreed with it would call a tag and the file name it was copied from two different words.
The price of (2) is that two Turkish words spelt apart only by the dot — "ılık" and "ilik" — are one word to a search. The opposite rule fails every capitalised Turkish name typed on an English keyboard and every English name typed on a Turkish one.
Accents stay significant: "müşteri" and "musteri" are different words. The stored name is never changed; this is how names are COMPARED.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Canonical ¶
Canonical returns s composed (NFC), with a dot above dropped where it sits on a letter that already has one or is about to lose it: any of the four i's, or j.
The dot: lower-casing `İ` the full Unicode way — JavaScript's toLowerCase(), Python's lower() — gives `i` + U+0307, which composition leaves as it is, so a client or an agent that lower-cases a query before sending it would never meet the `i` on the other side (found verifying PR #46 against the production catalogue, where 4 of 10 sampled names carried an `İ`).
A string that is already composed and holds no U+0307 — ASCII among them — is returned as it is, without allocating: this runs on every candidate of every search. ⚠ QuickSpanString, not IsNormalString: the latter allocates on every call, fast path included (two allocations per candidate in BenchmarkScorerPerCandidate, which is otherwise flat zero). U+0307 can compose, so a string holding it never takes the fast path.
func Loose ¶ added in v0.54.0
Loose is String with the accents dropped as well: "Güvenlik" and "guvenlik", "şifre" and "sifre" are one word to it.
⚠ It is NOT how a file name is searched - accents count there (the package comment: "müşteri" is not "musteri"), and the search, the explorer's folder filter (search.NameMatcher) and every store comparison use String. Loose is the rule for words a person types to find a LABEL - a page of the admin panel, a setting, a colleague's name - on whatever keyboard they have (the admin panel's search, handlers/panel_search.go). It lives here, beside String, so the two rules are one package and one definition each (filex 0.54, audit D5: the panel had its own copy).
func Plain ¶
Plain reports whether r, an ASCII letter or digit, is stored as itself in every spelling of a name that folds to it — so a database can look for it in the stored bytes as they are, with nothing but its own ASCII case folding, and cheaply. Every ASCII letter and digit is, except three:
- `i`: `ı`, `I` and `İ` fold to it, and a stored i may carry a dot above that Canonical drops;
- `j`: a stored j may carry a dot above that Canonical drops;
- `k`: U+212A KELVIN SIGN composes to `K`.
TestPlain_NothingElseFoldsIntoPlainASCII walks every code point to keep this list honest.
func PlainPrefix ¶
PlainPrefix is the longest prefix of s made of Plain letters and digits: all of s when AllPlain(s). A folded word that is all Plain is stored as it is in every spelling of a name that folds to it, so a database finds it with its own case-insensitive LIKE — no normaliser needed, and none of its cost; a word that is not can still be narrowed by its plain prefix.
func Rune ¶
Rune is the case fold of one rune: the four Latin i's are one letter, `i`; everything else is lower-cased the Unicode default way. One rune in, one rune out, so a caller that compares position by position (the scorer) keeps its positions.
Types ¶
This section is empty.