STR

Pipeline-First String Toolkit for Go

Every function is func(string) string. Compose them with Pipe. Zero dependencies. 36 functions. One import.

CI Go Reference Go Report Card
go get github.com/schigh/str@latest
// Build reusable string pipelines normalize := str.Pipe( str.TrimSpace, str.CollapseWhitespace, str.ToSnakeCase, str.Truncate(64, "..."), ) slug := normalize(" HelloWorld ") // "hello_world"

Compose

The core of str. Build reusable transformation pipelines from simple functions. Every transformer is func(string) string, and Pipe chains them left to right.

Pipe compositor
func Pipe(fns ...func(string) string) func(string) string
Compose multiple transformers into one. Zero functions = identity.
format := str.Pipe(str.TrimSpace, str.PadLeft("0", 8)) format(" 42 ") // "00000042"
PipeErr compositor
func PipeErr(fns ...func(string) (string, error)) func(string) (string, error)
Like Pipe, but for fallible transformers. Short-circuits on first error.
fn := str.PipeErr(validate, transform) result, err := fn("input")
PipeIf conditional
func PipeIf(pred func(string) bool, fn func(string) string) func(string) string
Apply a transformation only when the predicate is true. Otherwise, passthrough.
isLong := func(s string) bool { return len([]rune(s)) > 100 } str.PipeIf(isLong, str.Truncate(100, "..."))
PipeUnless conditional
func PipeUnless(pred func(string) bool, fn func(string) string) func(string) string
Inverse of PipeIf. Apply when predicate is false.
isEmpty := func(s string) bool { return s == "" } str.PipeUnless(isEmpty, str.SlugifyASCII)
PipeMap mapper
func PipeMap(split func(string) []string, join string, fn func(string) string) func(string) string
Split a string, transform each part, rejoin. Use with Lines or Words.
trimLines := str.PipeMap(str.Lines, "\n", str.TrimSpace) trimLines(" hello \n world ") // "hello\nworld"
Lines / Words splitters
func Lines(s string) []string • func Words(s string) []string
Built-in splitters for PipeMap. Lines splits on \n. Words splits on whitespace.
str.Lines("a\nb\nc") // ["a", "b", "c"] str.Words("hello world") // ["hello", "world"]

Transform

Case conversion with acronym awareness, reversal, whitespace normalization, and slug generation. All func(string) string, all pipeline-composable.

ToSnakeCase pipe
func(string) string
Converts to snake_case. Handles acronyms (HTML, URL, ID, API, etc.), digit boundaries, and Unicode.
str.ToSnakeCase("HTMLParser") // "html_parser" str.ToSnakeCase("getHTTPSURL") // "get_https_url" str.ToSnakeCase("UserID") // "user_id"
ToCamelCase pipe
func(string) string
Converts to camelCase. First word lowercase.
str.ToCamelCase("hello_world") // "helloWorld" str.ToCamelCase("HTMLParser") // "htmlParser"
ToPascalCase pipe
func(string) string
Converts to PascalCase. All words capitalized.
str.ToPascalCase("hello_world") // "HelloWorld"
ToKebabCase pipe
func(string) string
Converts to kebab-case. Hyphen separated.
str.ToKebabCase("HelloWorld") // "hello-world"
ToTitleCase pipe
func(string) string
Capitalizes every word. Language-agnostic.
str.ToTitleCase("hello_world") // "Hello World"
ToTitleCaseEnglish pipe
func(string) string
English-aware title case. Skips articles, conjunctions, prepositions mid-sentence.
str.ToTitleCaseEnglish("a tale of two cities") // "A Tale of Two Cities"
ToScreamingSnake pipe
func(string) string
SCREAMING_SNAKE_CASE. All uppercase.
str.ToScreamingSnake("helloWorld") // "HELLO_WORLD"
*CaseWith + WithAcronyms pipe
func To*CaseWith(opts ...CaseOption) func(string) string
All 6 case functions have a *With variant for custom domain acronyms.
toSnake := str.ToSnakeCaseWith(str.WithAcronyms("DAO", "NFT")) toSnake("DAOVoting") // "dao_voting"
Reverse pipe
func(string) string
Reverses runes. Safe for multibyte UTF-8.
str.Reverse("hello") // "olleh"
CollapseWhitespace pipe
func(string) string
All Unicode whitespace collapsed to a single space. Trimmed.
str.CollapseWhitespace("hello \t\n world") // "hello world"
TrimSpace pipe
func(string) string
Removes leading and trailing whitespace. Pipeline convenience re-export.
str.TrimSpace(" hello ") // "hello"
SlugifyASCII pipe
func(string) string
URL-safe ASCII slug. Lowercase, hyphens, strips non-ASCII.
str.SlugifyASCII("Hello, World!") // "hello-world"
NaturalSortKey pipe
func(string) string
Returns a key for natural sorting. "file2" sorts before "file10".
str.NaturalSortKey("file2") // sorts before "file10"

📐Format

Padding, truncation, substring extraction, word wrapping, and pluralization. All return func(string) string via partial application.

PadLeft / PadRight pipe
func PadLeft(pad string, width int) func(string) string
Pad to target width (runes). Multi-char pads truncated to exact width.
str.PadLeft("0", 8)("1234") // "00001234" str.PadRight(".", 10)("hello") // "hello....."
Truncate pipe
func Truncate(maxLen int, ellipsis string) func(string) string
Truncate to max runes. Length includes ellipsis. Multibyte-safe.
str.Truncate(8, "...")("hello world") // "hello..."
Substring pipe
func Substring(start, length int) func(string) string
Rune-based substring. Negative start counts from end (Python-style).
str.Substring(0, 3)("hello") // "hel" str.Substring(-3, 2)("abcde") // "cd"
Wrap pipe
func Wrap(width int, opts ...WrapOption) func(string) string
Word wrap with WrapBeforeWord (default), WrapAfterWord, or WrapHardBreak. Custom line breaks and indentation.
str.Wrap(40)("long text...") str.Wrap(76, str.WrapHardBreak)("base64...")
Pluralize pipe
func Pluralize(count int, plural string) func(string) string
Selects singular (input) or plural form. Count 1 = singular, everything else = plural.
str.Pluralize(1, "items")("item") // "item" str.Pluralize(5, "items")("item") // "items"

🎲Generate

Cryptographically secure random string generation with crypto/rand.

RandomToken standalone
func RandomToken(opts ...TokenOption) string
Generate random strings. Bias-free character selection. Configurable length, charset, and prefix. Default: 20 alphanumeric characters.
str.RandomToken() // "7wyxEyzgvsrEGdHcpbiU" str.RandomToken(str.WithLength(32)) // 32 random alphanumeric chars str.RandomToken(str.WithCharset("0123456789")) // 20 random digits str.RandomToken(str.WithPrefix("tok_"), str.WithLength(16)) // "tok_" + 16 chars

📏Measure

String distance and similarity algorithms.

Levenshtein standalone
func Levenshtein(a, b string) int
Edit distance. O(min(m,n)) space with 2-row rolling array.
str.Levenshtein("kitten", "sitting") // 3 str.Levenshtein("", "hello") // 5
JaroWinkler standalone
func JaroWinkler(a, b string) float64
Similarity 0.0 to 1.0. Winkler prefix bonus p=0.1, max prefix 4.
str.JaroWinkler("martha", "marhta") // ~0.96 str.JaroWinkler("hello", "hello") // 1.0

🎨Pipeline Patterns

Real-world composition recipes. Build once, reuse everywhere.

Input Normalization

Clean user input before storage

var clean = str.Pipe(str.TrimSpace, str.CollapseWhitespace)

URL Slug Generation

Turn any string into a URL-safe slug

var slugify = str.Pipe(str.TrimSpace, str.CollapseWhitespace, str.SlugifyASCII)

Log Field Formatting

Fixed-width, truncated log columns

var fmt = str.Pipe(str.TrimSpace, str.Truncate(50, "..."), str.PadRight(" ", 50))

Conditional Truncation

Only shorten strings that are too long

isLong := func(s string) bool { return len([]rune(s)) > 100 } var c = str.Pipe(str.TrimSpace, str.PipeIf(isLong, str.Truncate(100, "...")))

Per-Line Processing

Transform every line independently

var tidy = str.PipeMap(str.Lines, "\n", str.TrimSpace)

Domain Acronyms

Custom acronyms for your industry

var s = str.ToSnakeCaseWith(str.WithAcronyms("DAO", "NFT"))

🛡Edge Case Guarantees

No function panics for any input (except RandomToken on OS entropy failure). Empty in, empty out. Degenerate parameters return reasonable defaults.

FunctionEdge InputBehavior
Pipe()zero functionsIdentity (returns input unchanged)
PipeIf(nil, fn)nil predicateNo-op (passthrough)
PipeUnless(nil, fn)nil predicateAlways applies fn
PipeMap(nil, j, fn)nil splitPassthrough
PadLeft("", N)empty padReturns input unchanged
Truncate(0, "...")zero lengthReturns empty string
Substring(-3, 2)negative startCounts from end
Wrap(0)zero widthReturns input unchanged
Pluralize(0, pl)count zeroReturns plural
RandomToken(WithLength(0))zero lengthReturns prefix only
All case functionsempty stringReturns empty string
Levenshtein("", "")both emptyReturns 0
JaroWinkler("", "")both emptyReturns 1.0
FuzzyMatch("", hay)empty needleReturns nil