This file documents key information about the project architecture, build commands, code style, and security practices.
Project overview
A Java library providing ASCII-safe Charset SPI
implementations that transliterate Unicode to ASCII subsets rather than
simply rejecting non-ASCII input. Published to GitHub Packages as
com.maybeitssquid:ascii-safe-charsets.
Commands
Build and test:
./gradlew build # compile, test, spotless check
./gradlew test # run all tests
./gradlew test --tests "*CacheTest" # run tests by class name
./gradlew test --tests "*CacheTest.test*" # run tests by method patternCode quality:
./gradlew spotlessApply # auto-format (required before commit)
./gradlew dependencyCheckAnalyze # OWASP vulnerability scan (slow; fails at CVSS ≥ 7)NVD key (optional):
dependencyCheckAnalyze reads the key from the
NVD_API_KEY environment variable or the
nvd.apiKey Gradle property. With a key, it updates the NVD
data live. Without one, it uses the local NVD cache and logs a warning.
CI runs without a key, so it also uses the cache.
External dependencies: Standalone library; no runtime dependencies beyond Java.
Build uses Java 25 toolchain, compiles to Java 17 bytecode
(release = "17"). CI tests on Java 17, 21, and 25.
Key Entry Points
TransliteratingASCIIProvider— mainCharsetProviderSPI entry point; provides charset instancesCharset.forName("X-Transliterating")— retrieve a charset by name (registered via SPI)
Versioning and Releases
Versions are derived from git tags using gradle-git-version:
- On a tag (e.g.,
v1.0.0) → version =1.0.0 - After a tag → version = tag + distance + commit
hash (e.g.,
1.0.1-3-gABC1234= 3 commits after v1.0.0) - No tags yet → version synthesized from git history
(e.g.,
0.0.1-dev-88-gXYZ)
To create a release:
# Ensure all commits are pushed
git push origin main
# Create and push the tag (triggers automatic version picking in build)
git tag -a v1.0.0 -m "Release 1.0.0"
git push origin v1.0.0
# Publish the GitHub Release; gradle-publish.yml publishes to GitHub Packages
gh release create v1.0.0 --title "v1.0.0" --generate-notesTo delete a release tag:
git tag -d v1.0.0 # Delete locally
git push origin :v1.0.0 # Delete from remoteConfiguration cache is enabled
(org.gradle.configuration-cache=true in
gradle.properties).
Architecture
The library wires together two subsystems: a Charset
implementation and a configurable transliteration pipeline.
Module and package layout
The library is a named JPMS module (module-info.java,
module com.maybeitssquid.safeascii). Only the charset API
package com.maybeitssquid.safeascii is exported. The
transliteration pipeline lives in
com.maybeitssquid.safeascii.internal, which is
not exported — those classes are implementation
details, not public API. The provider is declared two ways so it works
on both paths:
provides java.nio.charset.spi.CharsetProvider with … in
module-info.java (module path) and
META-INF/services/java.nio.charset.spi.CharsetProvider
(classpath).
Charset layer
TransliteratingASCIIProvider—CharsetProviderSPI entry point in the exported package, registered viasrc/main/resources/META-INF/services/java.nio.charset.spi.CharsetProvider(classpath) and theprovidesdirective inmodule-info.java(module path). Provides four charsets lazily:X-ASCII-Printable(aliasASCII-Printable) — strict printable ASCII (0x20–0x7E only, controls blocked)X-ASCII-Plain(aliasASCII-Plain) — same but allows LF; CR is unmappable so CRLF normalises to LF underIGNOREX-ASCII-Formatted(aliasASCII-Formatted) — same as X-ASCII-Plain but also allows TAB (0x09)X-Transliterating— aggressive Unicode-to-ASCII transliterationX-Transliterating-Single-Byte(aliasACH) — same but guarantees 1:1 character output
TransliteratingASCII— extendsjava.nio.charset.Charset. Takes anIntFunction<CharSequence>transliterator at construction; the encoder/decoder delegate all codepoint mapping to it.
Transliterator pipeline
Pipeline classes live in the non-exported
com.maybeitssquid.safeascii.internal package:
- ASCII-Printable / ASCII-Plain:
Cache → ASCIIFilter - X-Transliterating:
Cache → Decompose → Name → ASCIIFilter - X-Transliterating-Single-Byte:
Cache → SingleCharacterFilter → Decompose → Name → ASCIIFilter
Shared build sections
build.gradle and settings.gradle wrap the
build logic this project shares with the other Java/Gradle projects in
// shared:begin NAME and // shared:end NAME
markers. The canonical copies live in
claude-skills/init-java-project/shared/. The
shared-build CI job checks out that directory and fails if
a wrapped section differs from its canonical copy. Indentation, blank
lines and comment lines are ignored. To change a shared section, change
the canonical copy and the same section in every project in one change
set. Run the check locally with
bash <claude-skills>/init-java-project/shared/check-shared-sections.sh . <claude-skills>/init-java-project/shared.
Code style
Spotless enforces Google Java Format. Run
./gradlew spotlessApply before committing. The formatter
excludes module-info.java.
Security patches
For CVE patch management, see the gradle-security-patch
skill. Use /gradle-security-patch to pin a CVE fix in the
version catalog.