Skip to content

feat(ucase): add default case mapping package#15

Merged
SCKelemen merged 1 commit into
mainfrom
SCKelemen/add-ucase-default-case
Jun 19, 2026
Merged

feat(ucase): add default case mapping package#15
SCKelemen merged 1 commit into
mainfrom
SCKelemen/add-ucase-default-case

Conversation

@SCKelemen

Copy link
Copy Markdown
Owner

Summary

Adds ucase, implementing the Unicode default (language-insensitive) case algorithms from The Unicode Standard, Section 3.13 "Default Case Algorithms".

Case mapping is specified in the core standard (not a numbered UAX/UTS annex), so the package is named for its function — ucase — rather than following the uaxNN/utsNN pattern. The rationale is documented in the package README.

Public API

  • Strings (full, 1:many): ToFold, ToLower (incl. Final_Sigma), ToUpper, ToTitle
  • Runes (simple, 1:1): SimpleFold, SimpleLower, SimpleUpper, SimpleTitle
  • Properties / matching: IsCased (D135), IsCaseIgnorable (D136), CaselessMatch (D144)
  • Metadata: UnicodeVersion ("17.0.0")

Data & generation

Tables are generated by generate_ucase.go from Unicode 17.0.0 UCD data (UnicodeData.txt, SpecialCasing.txt, CaseFolding.txt, DerivedCoreProperties.txt), which are committed as test vectors per repo idiom. The generator is version-pinned, deterministic (code-point-sorted), go/format-formatted, and has zero external dependencies.

Tests

  • TestDataFileConsistency — verifies every default mapping and both properties against the committed UCD files (full code-space sweep for Cased/Case_Ignorable).
  • TestOfficialConformance — re-verifies against files downloaded live from unicode.org (skipped offline / in -short).
  • Spot checks (ßSS, ligatures, final sigma, titlecasing), CaselessMatch, idempotency, and runnable examples.

Scope / non-goals

  • Default algorithm only. Locale tailorings (Turkish/Azeri tr/az, Lithuanian lt) are intentionally out of scope; the only context condition exercised by the default algorithm is Final_Sigma. Documented as future work.
  • CaselessMatch implements the default caseless match (D144), not the canonical (D145) match (no NFD normalization).

gofmt -l, go vet, and go test ./... all pass.

Implement the Unicode default (language-insensitive) case algorithms
from The Unicode Standard, Section 3.13 'Default Case Algorithms':
full case folding, lower/upper/titlecasing, the Cased and
Case_Ignorable properties, and default caseless matching.

Tables are generated from Unicode 17.0.0 UCD data. Tests verify every
default mapping and both properties against the committed UCD files and,
when online, against the official files downloaded from unicode.org.
@SCKelemen
SCKelemen merged commit 5443a88 into main Jun 19, 2026
5 checks passed
@SCKelemen
SCKelemen deleted the SCKelemen/add-ucase-default-case branch June 19, 2026 07:51

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 50f6b32994

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread ucase/ucase.go
// Case-ignorable characters do not change word state.
out = appendMapping(out, runes, i, kindLower)
default:
inWord = false

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve UAX #29 word spans in ToTitle

When ToTitle sees any character that is neither Cased nor Case_Ignorable, this branch ends the current word. That splits Unicode word spans at digits and connector punctuation even though the default titlecase algorithm is defined over UAX #29 word boundaries; the repository's UAX #29 implementation keeps Numeric × AHLetter and _ (ExtendNumLet) inside the same word (uax29/word.go:165-197, uax29/word_data.go:22-26). As a result ToTitle("version2BETA") currently returns Version2Beta instead of Version2beta, and FOO_BAR becomes Foo_Bar, so common identifiers/version strings are visibly over-titlecased.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant