TinyString is a lightweight Go library that provides comprehensive string manipulation, type conversion, formatting, and multilingual error handling with a fluid API, specifically designed for small devices and web applications using TinyGo as the target compiler.
- 🚀 Fluid and chainable API - Easy to use and readable operations
- 📝 Complete string toolkit - Transformations, conversions, formatting, and error handling
- 🌍 Multilingual error messages - Built-in dictionary system with 9 languages
- 🧵 Concurrency safe - Thread-safe operations for concurrent environments
- 📦 Zero dependencies - No
fmt,strings,strconv, orerrorsimports - 🎯 TinyGo optimized - Manual implementations for minimal binary size
- 🌐 WebAssembly-first - Designed for modern web deployment
- 🔄 Universal type support - Works with strings, numbers, booleans, and slices
- ⚡ Performance focused - Predictable allocations and custom optimizations
go get github.com/cdvelop/tinystringimport . "github.com/cdvelop/tinystring"
// Quick start - Basic conversion and transformation
text := Convert("Hóla Múndo").Tilde().ToLower().String() // out: "hola mundo"
// Working with different data types
numText := Convert(42).String() // out: "42"
boolText := Convert(true).String() // out: "true"
// Memory-efficient approach using string pointers
original := "Él Múrcielago Rápido"
Convert(&original).Tilde().CamelLow().Apply()
// original is now: "elMurcielagoRapido"
// Efficient, Unified builder and chaining example usage in loops and reuse, with accent normalization (Tilde)
items := []string{" ÁPPLE ", " banána ", " piñata "," ÑANDÚ "}
builder := Convert() // without params reused buffer = optimal performance
for i, item := range items {
processed := Convert(item).
TrimSpace(). // TrimSpace whitespace
Tilde(). // Normalize accents
ToLower(). // Convert to lowercase
Capitalize(). // Capitalize first letter
String() // Finalize the string
builder.Write(processed)
if i < len(items)-1 {
builder.Write(" - ")
}
}
out := builder.String() // Finalize the string hiding the error
out, err := builder.StringErr() // OR finalize with error handling
// out: "Apple - Banana - Piñata - Ñandu", err: nilTinyString provides two convenience helpers to escape text for HTML:
Convert(...).EscapeAttr()— escape a value for safe inclusion inside an HTML attribute value.Convert(...).EscapeHTML()— escape a value for safe inclusion inside HTML content.
Both functions perform simple string replacements and will escape the characters: &, <, >, ", and '.
Note that existing HTML entities will be escaped again (for example & -> &amp;). This library follows a simple replace-based escaping strategy — if you need entity-aware unescaping/escaping, consider using a full HTML parser.
Examples:
Convert(`Tom & Jerry's "House" <tag>`).EscapeAttr()
// -> `Tom & Jerry's "House" <tag>`
Convert(`<div>1 & 2</div>`).EscapeHTML()
// -> `<div>1 & 2</div>`Replace common strings package functions with TinyString equivalents:
| Go Standard | TinyString Equivalent |
|---|---|
strings.Builder |
c:= Convert() c.Write(a) c.Write(b) c.String() |
strings.Contains() |
Contains(s, substr) |
strings.Index() |
Index(s, substr) |
strings.LastIndex() |
LastIndex(s, substr) |
strings.Join() |
Convert(slice).Join(sep).String() |
strings.Repeat() |
Convert(s).Repeat(n).String() |
strings.Replace() |
Convert(s).Replace(old, new).String() |
strings.Split() |
Convert(s).Split(sep).String() |
strings.ToLower() |
Convert(s).ToLower().String() |
strings.ToUpper() |
Convert(s).ToUpper().String() |
strings.TrimSpace() |
Convert(s).TrimSpace().String() |
strings.TrimPrefix() |
Convert(s).TrimPrefix(prefix).String() |
strings.TrimSuffix() |
Convert(s).TrimSuffix(suffix).String() |
strings.HasPrefix() |
HasPrefix(s, prefix) |
strings.HasSuffix() |
HasSuffix(s, suffix) |
filepath.Base() |
Convert(path).PathBase().String() |
filepath.Join() |
PathJoin("a", "b", "c").String() — variadic function, zero heap allocation for ≤8 elements |
Convert("hello world").CamelLow().String() // out: "helloWorld"
Convert("hello world").CamelUp().String() // out: "HelloWorld"
Convert("hello world").SnakeLow().String() // out: "hello_world"
Convert("hello world").SnakeUp().String() // out: "HELLO_WORLD"// Search and count
pos := Index("hello world", "world") // out: 6 (first occurrence)
found := Contains("hello world", "world") // out: true
count := Count("abracadabra", "abra") // out: 2
// Prefix / Suffix checks
isPref := HasPrefix("hello", "he") // out: true
isSuf := HasSuffix("file.txt", ".txt") // out: true
// Note: this library follows the standard library semantics for prefixes/suffixes:
// an empty prefix or suffix is considered a match (HasPrefix(s, "") == true,
// HasSuffix(s, "") == true).
// Find last occurrence (useful for file extensions)
pos := LastIndex("image.backup.jpg", ".") // out: 12
if pos >= 0 {
extension := "image.backup.jpg"[pos+1:] // out: "jpg"
}
// ⚠️ Note: Index, Contains and LastIndex are global functions, not methods.
// Do NOT use: Convert(s).Contains(substr) // ❌ Incorrect, will not compile
// Use: Index(s, substr) // ✅ Correct
// Contains(s, substr) // ✅ Correct
// LastIndex(s, substr) // ✅ Correct
// PathBase (fluent API)
// Use `Convert(path).PathBase().String()` to get the last element of a path.
// Examples:
//
// Convert("/a/b/c.txt").PathBase().String() // -> "c.txt"
// Convert("folder/file.txt").PathBase().String() // -> "file.txt"
// Convert("").PathBase().String() // -> "."
// Convert(`c:\\file program\\app.exe`).PathBase().String() // -> "app.exe"
// PathJoin (cross-platform path joining)
// Standalone function with variadic string arguments.
// Returns *Conv for method chaining with transformations like ToLower().
// Uses fixed array for zero heap allocation (≤8 elements).
// Detects separator ("/" or "\\") automatically and avoids duplicates.
// Examples:
//
// PathJoin("a", "b", "c").String() // -> "a/b/c"
// PathJoin("/root", "sub", "file").String() // -> "/root/sub/file"
// PathJoin(`C:\dir`, "file").String() // -> `C:\dir\file`
// PathJoin(`\\server`, "share", "file").String() // -> `\\server\share\file`
//
// Typical use: normalize path case with ToLower() in the same chain
// PathJoin("A", "B", "C").ToLower().String() // -> "a/b/c"
// Path extension (file extension)
// Get the file extension (including the leading dot) from a path. Use the
// fluent API form `Convert(path).PathExt().String()` which reads the path
// from the Conv buffer and returns only the extension (or empty string).
// Examples:
//
// Convert("file.txt").PathExt().String() // -> ".txt"
// Convert("/path/to/archive.tar.gz").PathExt().String() // -> ".gz"
// Convert(".bashrc").PathExt().String() // -> "" (hidden file, no ext)
// Convert("noext").PathExt().String() // -> ""
// Convert(`C:\\dir\\app.exe`).PathExt().String() // -> ".exe"
//
// Typical use: normalize extension case in the same chain. For example,
// when the extension is uppercase you can lower-case it immediately:
// Convert("file.TXT").PathExt().ToLower().String() // -> ".txt"
// Replace operations
Convert("hello world").Replace("world", "Go").String() // out: "hello Go"
Convert("test 123 test").Replace(123, 456).String() // out: "test 456 test"// Split strings (always use Convert(...).Split(...))
parts := Convert("apple,banana,cherry").Split(",")
// out: []string{"apple", "banana", "cherry"}
parts := Convert("hello world new").Split() // Handles whitespace
// out: []string{"hello", "world", "new"}
// Join slices
Convert([]string{"Hello", "World"}).Join().String() // out: "Hello World"
Convert([]string{"a", "b", "c"}).Join("-").String() // out: "a-b-c"// TrimSpace operations
Convert(" hello ").TrimSpace().String() // out: "hello"
Convert("prefix-data").TrimPrefix("prefix-").String() // out: "data"
Convert("file.txt").TrimSuffix(".txt").String() // out: "file"
// Repeat strings
Convert("Go").Repeat(3).String() // out: "GoGoGo"Replace strconv package functions for type conversions:
| Go Standard | TinyString Equivalent |
|---|---|
strconv.Itoa() |
Convert(i).String() |
strconv.Atoi() |
Convert(s).Int() |
strconv.ParseFloat() |
Convert(s).Float64() |
strconv.ParseBool() |
Convert(s).Bool() |
strconv.FormatFloat() |
Convert(f).Round(n).String() |
strconv.Quote() |
Convert(s).Quote().String() |
// String to numbers => Int,Int32,Int64,Uint,Uint32,Uint64,Float32,Float64 eg:
result, err := Convert("123").Int() // out: 123, nil
result, err := Convert("456").Uint() // out: 456, nil
result, err := Convert("3.14").Float64() // out: 3.14, nil
// Numbers to string
Convert(42).String() // out: "42"
Convert(3.14159).String() // out: "3.14159"
// Boolean conversions
result, err := Convert("true").Bool() // out: true, nil
result, err := Convert(42).Bool() // out: true, nil (non-zero = true)
result, err := Convert(0).Bool() // out: false, nil
// String quoting
Convert("hello").Quote().String() // out: "\"hello\""
Convert("say \"hello\"").Quote().String() // out: "\"say \\\"hello\\\"\""// Decimal rounding: keep N decimals, round or truncate
// By default, rounds using "round half to even" (bankers rounding)
// Pass true as the second argument to truncate (no rounding), e.g.:
Convert("3.14159").Round(2).String() // "3.14" (rounded)
Convert("3.155").Round(2).String() // "3.16" (rounded)
Convert("3.14159").Round(2, true).String() // "3.14" (truncated, NOT rounded)
Convert("3.159").Round(2, true).String() // "3.15" (truncated, NOT rounded)
// Formatting with thousands separator (EU default)
Convert(2189009.00).Thousands().String() // out: "2.189.009"
// Anglo/US style (comma, dot)
Convert(2189009.00).Thousands(true).String() // out: "2,189,009"Replace fmt package functions for formatting:
| Go Standard | TinyString Equivalent |
|---|---|
fmt.Sprintf() |
Fmt(format, args...) |
fmt.Sprint() |
Convert(v).String() |
fmt.Fprintf() |
Fprintf(w, format, args...) |
fmt.Sscanf() |
Sscanf(src, format, args...) |
// Printf-style formatting
result := Fmt("Hello %s, you have %d messages", "John", 5)
// out: "Hello John, you have 5 messages"
// Multiple format specifiers
result := Fmt("Number: %d, Float: %.2f, Bool: %v", 42, 3.14159, true)
// out: "Number: 42, Float: 3.14, Bool: true"
// Advanced formatting (hex, binary, octal)
result := Fmt("Hex: %x, Binary: %b, Octal: %o", 255, 10, 8)
// out: "Hex: ff, Binary: 1010, Octal: 10"
// Write formatted output to io.Writer
var buf bytes.Buffer
Fprintf(&buf, "Hello %s, count: %d\n", "world", 42)
// Write to file
file, _ := os.Create("output.txt")
Fprintf(file, "Data: %v\n", someData)
// Parse formatted text from string (like fmt.Sscanf)
var pos int
var name string
n, err := Sscanf("!3F question", "!%x %s", &pos, &name)
// n = 2, pos = 63, name = "question", err = nil
// Parse complex formats
var code, unicode int
var word string
n, err := Sscanf("!3F U+003F question", "!%x U+%x %s", &code, &unicode, &word)
// n = 3, code = 63, unicode = 63, word = "question", err = nilReplace errors package functions for error handling with multilingual support:
| Go Standard | TinyString Equivalent |
|---|---|
errors.New() |
Err(message) |
fmt.Errorf() |
Errf(format, args...) |
// Multiple error messages and types
err := Err("invalid format", "expected number", 404)
// out: "invalid format expected number 404"
// Formatted errors (like fmt.Errorf)
err := Errf("invalid value: %s at position %d", "abc", 5)
// out: "invalid value: abc at position 5"TinyString enables multilingual error messages using reusable dictionary terms. It supports 9 languages and allows global or inline language selection.
// Set and get current language code
code := OutLang(ES) // returns "ES"
code = OutLang() // auto-detects and returns code (e.g. "EN")
err := Err(D.Format, D.Invalid)
// → "formato inválido"
// return strings
msg := Translate(FR, D.Format, D.Invalid).String()
// → "format invalide"
// Force French
err = Err(FR, D.Empty, D.String)
// → "vide chaîne"See dictionary.go for built-in words.
Combine D. (default terms) and custom dictionaries for flexible messaging.
📘 Full documentation available in docs/TRANSLATE.md
TinyString provides automatic message type classification to help identify the nature of text content. The system detects common message types like errors, warnings, success messages, and information using zero-allocation buffer-based pattern matching.
// Before: messagetype library usage
message := Translate(msgs...).String()
msgType := messagetype.DetectMessageType(message)
// After: tinystring Single operation with StringType() (zero allocations)
message, msgType := Translate(msgs...).StringType()
// Real example - Progress callback with message classification
progressCallback := func(msgs ...any) {
message, msgType := Translate(msgs...).StringType()
if msgType.IsError() {
handleError(message)
} else {
logMessage(message, msgType)
}
}
// Message type constants available via Msg struct
if msgType.IsError() {
// Handle error case
}
// Available message types:
// Msg.Normal - Default type for general content
// Msg.Info - Information messages
// Msg.Error - Error messages and failures
// Msg.Warning - Warning and caution messages
// Msg.Success - Success and completion messages
// Zero allocations - reuses existing conversion buffers
// Perfect for logging, UI status messages, and error handlingThe IDorPrimaryKey function determines if a field is an ID field and/or a primary key field based on naming conventions.
tableName: The name of the table or entity that the field belongs tofieldName: The name of the field to analyze
isID:trueif the field is an ID field (starts with "id")isPK:trueif the field is a primary key (matches specific patterns)
IDorPrimaryKey("user", "id")returns(true, true)IDorPrimaryKey("user", "iduser")returns(true, true)IDorPrimaryKey("user", "userId")returns(true, true)IDorPrimaryKey("user", "id_user")returns(true, true)IDorPrimaryKey("user", "user_ID")returns(true, true)IDorPrimaryKey("user", "idaddress")returns(true, false)IDorPrimaryKey("user", "name")returns(false, false)
// Basic truncation with ellipsis
Convert("Hello, World!").Truncate(10).String()
// out: "Hello, ..."
// Name truncation for UI display
Convert("Jeronimo Dominguez").TruncateName(3, 15).String()
// out: "Jer. Dominguez"
// Advanced name handling
Convert("Juan Carlos Rodriguez").TruncateName(3, 20).String()
// out: "Jua. Car. Rodriguez"// Key-value parsing with the new API:
value, err := Convert("user:admin").KV() // out: "admin", nil
value, err := Convert("count=42").KV("=") // out: "42", nil
// Struct tag value extraction (TagValue):
value, found := Convert(`json:"name" Label:"Nombre"`).TagValue("Label") // out: "Nombre", true
value, found := Convert(`json:"name" Label:"Nombre"`).TagValue("xml") // out: "", false