String Utilities

Luma provides comprehensive string manipulation methods that are immutable, safe, and easy to chain.

Declaration

text: str = "Hello World"

Quick Reference

Method Description Returns
s.upper() Uppercase str
s.lower() Lowercase str
s.title() Title Case Each Word str
s.trim() Strip leading/trailing whitespace str
s.trim_left() Strip leading whitespace str
s.trim_right() Strip trailing whitespace str
s.pad_left(width) Left-pad with spaces to width str
s.pad_left(width, char) Left-pad with custom character str
s.pad_right(width) Right-pad with spaces to width str
s.pad_right(width, char) Right-pad with custom character str
s.contains(sub) Check if substring exists bool
s.starts_with(prefix) Check prefix bool
s.ends_with(suffix) Check suffix bool
s.is_empty() Check if string is empty bool
s.index_of(sub) First occurrence index ?int
s.last_index_of(sub) Last occurrence index ?int
s.split(delimiter) Split by delimiter [str]
s.split() Split by whitespace [str]
s.chars() Split into characters [str]
s.replace(old, new) Replace all occurrences str
s.repeat(n) Repeat n times str
s.reverse() Reverse the string str
s.len() Length of string int
words.join(sep) Join a [str] list str
s.regex_match(pattern) Test if string matches regex bool
s.regex_find(pattern) First regex match or nil ?str
s.regex_find_all(pattern) All regex matches [str]
s.regex_replace(pattern, replacement) Replace all regex matches str
s.regex_split(pattern) Split by regex pattern [str]
s.from_hex() Decode hex string to bytes [byte] or ?[byte]
s.from_base64() Decode base64 to bytes [byte] or ?[byte]
s.from_base64url() Decode URL-safe base64 to bytes [byte] or ?[byte]

Methods

Case Conversion

text.upper()        // "HELLO WORLD"
text.lower()        // "hello world"
text.title()        // "Hello World"

Whitespace Handling

"  hello  ".trim()       // "hello"
"  hello  ".trim_left()  // "hello  "
"  hello  ".trim_right() // "  hello"

Padding & Alignment

Pad a string to a fixed width. If the string is already equal to or longer than the width, it’s returned unchanged.

"42".pad_left(5)          // "   42"
"42".pad_left(5, "0")     // "00042"
"hi".pad_right(10)        // "hi        "
"hi".pad_right(10, ".")   // "hi........"

pad_left adds padding before the string (right-aligns the content).
pad_right adds padding after the string (left-aligns the content).

The second argument is optional — defaults to a space. When provided, it must be a single character.

// Align numbers in a column
price: str = "9.99"
price.pad_left(10)        // "      9.99"

// Fixed-width labels
"Name".pad_right(15)      // "Name           "
"Age".pad_right(15)       // "Age            "

Useful for terminal tables, columnar output, and formatted reports.

Search & Validation

text.contains("World")     // true
text.starts_with("Hello")  // true
text.ends_with("World")    // true
text.is_empty()            // false
text.index_of("World")     // 6
text.last_index_of("l")    // 9

index_of and last_index_of return ?int — they produce nil when the substring is not found:

pos: ?int = "hello".index_of("xyz")   // nil

Length

"hello".len()    // 5
"".len()         // 0

Splitting & Joining

"a,b,c".split(",")              // ["a", "b", "c"]
"one two".split()               // ["one", "two"]
"hello".chars()                 // ["h", "e", "l", "l", "o"]

.join() is called on a [str] list to combine elements with a separator:

words: [str] = "a,b,c".split(",")
words.join(" - ")                    // "a - b - c"

Replacement & Repetition

text.replace("World", "Luma")   // "Hello Luma"
"ha".repeat(3)                  // "hahaha"

Reverse & Characters

"Hello".reverse()       // "olleH"
"Hi!".chars()           // ["H", "i", "!"]

Regex

All regex methods share the regex_ prefix, so they’re easy to find and clearly distinct from plain string operations. Patterns use standard regular expression syntax.

Testing a match

regex_match returns true if the pattern matches anywhere in the string:

"hello123".regex_match("[0-9]+")       // true
"hello".regex_match("[0-9]+")          // false

Use it for validation:

email: str = "user@example.com"
if email.regex_match("^[^@]+@[^@]+\\.[^@]+$") {
    print("valid email format")
}

Finding the first match

regex_find returns the first match or nil if nothing matches:

result: ?str = "order-4521-confirmed".regex_find("[0-9]+")
// result = "4521"

This pairs naturally with pattern matching:

match "invoice #8832".regex_find("#[0-9]+") {
    nil -> print("no invoice number")
    id -> print("found: ${id}")
}

Finding all matches

regex_find_all returns every match as a list:

numbers: [str] = "a1 b22 c333".regex_find_all("[0-9]+")
// ["1", "22", "333"]

Returns an empty list when nothing matches:

"hello".regex_find_all("[0-9]+")   // []

Replacing matches

regex_replace replaces all occurrences of the pattern:

clean: str = "  too   many   spaces  ".regex_replace("\\s+", " ")
// " too many spaces "

redacted: str = "call 555-1234 or 555-5678".regex_replace("[0-9]{3}-[0-9]{4}", "***-****")
// "call ***-**** or ***-****"

For literal replacement (no regex), use .replace() instead — it’s simpler and faster.

Splitting by pattern

regex_split splits a string using a regex as the delimiter:

parts: [str] = "one, two;  three".regex_split("[,;]\\s*")
// ["one", "two", "three"]

For splitting on a fixed delimiter, use .split() instead.

Regex summary

Method Returns Description
s.regex_match(pattern) bool Does the pattern match anywhere?
s.regex_find(pattern) ?str First match, or nil
s.regex_find_all(pattern) [str] All matches
s.regex_replace(pattern, repl) str Replace all matches
s.regex_split(pattern) [str] Split by pattern

Error handling

An invalid regex pattern causes a panic at runtime:

"test".regex_match("[invalid")   // panic: invalid regex

Always use valid patterns. Regex patterns are compiled each time a method is called — for hot loops, this is a known cost.

Practical examples

Log line parsing

line: str = "2026-02-14 ERROR: connection refused"

match line.regex_find("^[0-9]{4}-[0-9]{2}-[0-9]{2}") {
    nil -> print("no date found")
    date -> print("log date: ${date}")
}

level: str = match line.regex_find("(ERROR|WARN|INFO|DEBUG)") {
    nil -> "UNKNOWN"
    l -> l
}

Input sanitization

fn sanitize(input: str) -> str {
    return input
        .regex_replace("[<>\"']", "")
        .trim()
}

Tokenizing

source: str = "name=alice age=30 role=admin"
pairs: [str] = source.regex_find_all("[a-z]+=\\w+")
// ["name=alice", "age=30", "role=admin"]

Encoding

Strings can decode encoded text back to [byte].

Hex Decoding

data: [byte] = "48656c6c6f".from_hex()
data: ?[byte] = user_input.from_hex()     // nil for invalid hex

Decodes a hexadecimal string to bytes. Panics on invalid input (odd length, non-hex characters).
Use ?[byte] for safe decoding that returns nil instead.

Base64 Decoding

data: [byte] = "SGVsbG8=".from_base64()
data: ?[byte] = user_input.from_base64()  // nil for invalid base64

Decodes standard base64 (RFC 4648) back to bytes.

URL-Safe Base64 Decoding

data: [byte] = "SGVsbG8".from_base64url()
data: ?[byte] = user_input.from_base64url()  // nil for invalid

Decodes URL-safe base64 (no padding, - and _ instead of + and /).

Encoding Summary

Method Returns Description
s.from_hex() [byte] or ?[byte] Hex string to bytes
s.from_base64() [byte] or ?[byte] Base64 string to bytes
s.from_base64url() [byte] or ?[byte] URL-safe base64 to bytes

To encode bytes as text, use the corresponding methods on [byte] — see Lists.

// Decode a hex-encoded key
key: [byte] = "deadbeef".from_hex()

// Safe decode from user input
input: str = "not valid hex!!"
data: ?[byte] = input.from_hex()
if data == nil {
    print("Invalid hex string")
}

Chaining

All string methods that return str can be chained:

name: str = "  john doe  "
clean_name = name.trim().title()       // "John Doe"
loud = "  hello world  ".trim().upper() // "HELLO WORLD"

Examples

Validation

email: str = "user@example.com"

// Simple check
if !email.contains("@") {
    error("Invalid email")
}

// Regex check
if !email.regex_match("^[^@]+@[^@]+\\.[^@]+$") {
    error("Invalid email format")
}

CSV Parsing

data: str = "apple,123,true"
parts = data.split(",")
name = parts[0]                    // "apple"
count = parts[1].to_int(0)         // 123
active = parts[2].to_bool(false)   // true

Building Output

tags: [str] = "rust,go,luma".split(",")
output: str = tags.join(", ")     // "rust, go, luma"

Notes

  • All string methods return new strings (strings are immutable)
  • Methods like index_of and last_index_of return ?int (nullable)
  • .join() is a list method that works on [str] lists
  • Conversion methods (to_int, to_float, to_bool) require default values for error cases
Last updated on