Working with text
This page covers the textfn actions: manipulating and transforming string values. It’s a different set from Displaying text, which covers .text/.fancytext — how text looks on screen. This page is about changing the text itself, before you ever display it.
Every action here returns a value — assign it with $result = (or feed it straight into another action or .text).
Case conversion
These reshape text into a specific casing convention. They share smart word-splitting logic: they can decompose camelCase, kebab-case, and snake_case input into separate words before recasing, not just re-case existing whitespace-separated words.
| Action | What it does | Example |
|---|---|---|
camel-case | Converts to camelCase | $c = camel-case "Hello World" |
pascal-case | Converts to PascalCase | $c = pascal-case "Hello World" |
kebab-case | Converts to kebab-case | $c = kebab-case "Hello World" |
snake-case | Converts to snake_case | $c = snake-case "Hello World" |
constant-case | Converts to CONSTANT_CASE | $c = constant-case "Hello World" |
title-case | Converts to Title Case | $c = title-case "camelCaseText" |
$name = "Hello World"$camel = camel-case $name// $camel is "helloWorld"$snake = snake-case $name// $snake is "hello_world"
$titled = title-case "camelCaseText"// $titled is "Camel Case Text" — camelCase input gets split into words firstcapitalize looks similar but works differently — it only respects existing whitespace, and won’t force-lowercase text that’s already uppercase:
$upper = uppercase "hello world"// $upper is "HELLO WORLD"$lower = lowercase "HELLO WORLD"// $lower is "hello world"$cap = capitalize "hello world"// $cap is "Hello world"$cap-unchanged = capitalize "HELLO WORLD"// $cap-unchanged is still "HELLO WORLD" — capitalize doesn't lowercase the restTrimming, repeating, reversing, padding
| Action | What it does | Example |
|---|---|---|
trim | Removes leading/trailing whitespace | $t = trim " hello " |
reverse-text | Reverses the characters | $r = reverse-text "hello" |
repeat-text | Repeats text N times | $r = repeat-text 3 "-" |
pad-left | Pads to a length on the left | $p = pad-left 8 "0" "42" |
pad-right | Pads to a length on the right | $p = pad-right 10 "." "Hello" |
$trimmed = trim " hello world "// $trimmed is "hello world"
$reversed = reverse-text "hello"// $reversed is "olleh"
$repeated = repeat-text 3 "-"// $repeated is "---"
$padded = pad-left 8 "0" "42"// $padded is "00000042"$padded-right = pad-right 10 "." "Hello"// $padded-right is "Hello....."reverse-text on a missing/undefined variable returns an empty string (""), rather than leaving the result undefined the way most other textfn actions do — worth remembering if you’re checking if missing after it.
Combining and editing text
| Action | What it does | Example |
|---|---|---|
add-text | Appends text to a base string | $s = add-text "World" to "Hello" |
add-line | Appends text on a new line | $s = add-line "line2" to "line1" |
replace-text | Replaces an occurrence of a substring | $s = replace-text "hello" "goodbye" "hello world hello" |
remove-text | Removes all occurrences of a substring | $s = remove-text "hello" "hello world hello" |
mask-text | Masks characters (e.g. for display) | $s = mask-text 4 "1234567890" |
$joined = add-text "World" to "Hello"// $joined is "HelloWorld"$joined-sep = add-text "World" to "Hello" -seperator=" "// $joined-sep is "Hello World"add-text’s separator tag is spelled -seperator (not -separator) — that’s a typo baked into the action itself, so it has to be written that way for it to work.
replace-text and remove-text look symmetric but default differently: replace-text only replaces the first match unless you add -all; remove-text removes every match with no tag needed.
$first-only = replace-text "hello" "goodbye" "hello world hello"// $first-only is "goodbye world hello"$all = replace-text "hello" "goodbye" "hello world hello" -all// $all is "goodbye world goodbye"
$removed = remove-text "hello" "hello world hello"// $removed is " world " — both occurrences gonemask-text hides part of a string, useful for displaying sensitive values like emails or card numbers. It takes a value to mask on its own (uses sensible defaults), or you can specify how many trailing characters to reveal, and even a custom mask character:
$masked = mask-text "secret"// $masked is "******" — every character masked by default$masked-tail = mask-text 4 "1234567890"// $masked-tail is "******7890" — last 4 characters shown$masked-custom = mask-text 4 "*" "1234567890"Extraction and analysis
| Action | What it does | Example |
|---|---|---|
character | Gets the character at a position | $c = character 1 of "abc" |
word-at | Gets the word at a position | $w = word-at 2 of "quick brown fox" |
extract-text | Extracts a substring by range | $s = extract-text 0 5 "hello world" |
length-of | Length of the text | $n = length-of "hello world" |
count-words | Number of words | $n = count-words "one two three" |
split-text | Splits text into an array of words | $arr = split-text "hello world" |
$char = character 1 of "abc"// $char is "a" — character/word-at positions are 1-indexed$word = word-at 2 of "quick brown fox"// $word is "brown"$sub = extract-text 0 5 "hello world"// $sub is "hello" — extract-text's start/end range is 0-indexed, unlike character/word-at$len = length-of "hello world"// $len is 11$wc = count-words "one two three"// $wc is 3$parts = split-text "hello world"// $parts is ["hello", "world"]Text queries
These all return true/false, handy in if conditions.
| Action | What it does | Example |
|---|---|---|
starts-with | Whether text starts with a prefix | $b = starts-with "hello" "hello world" |
ends-with | Whether text ends with a suffix | $b = ends-with "world" "hello world" |
contains-text | Whether text contains a substring | $b = contains-text "world" "hello world" |
$starts = starts-with "hello" "hello world"// $starts is true$ends = ends-with "world" "hello world"// $ends is true$contains = contains-text "xyz" "hello world"// $contains is falsetruncate-text shortens long text to a maximum length, useful for previews and summaries:
$snippet = truncate-text 5 "Hello World"// $snippet is "He..." — the max length includes the "..." suffixURL encoding
| Action | What it does | Example |
|---|---|---|
encode-uri | URI-encodes text | $e = encode-uri "hello world" |
decode-uri | Decodes a URI-encoded string | $d = decode-uri "hello%20world" |
$encoded = encode-uri "hello world"// $encoded is "hello%20world"$decoded = decode-uri "hello%20world"// $decoded is "hello world"Pluralize and initials
pluralize picks between a singular and plural form based on a count. The count must be a variable, not a bare literal number:
$item-count = 3$label = pluralize $item-count "item" "items"// $label is "3 items"
$item-count = 1$label = pluralize $item-count "item" "items"// $label is "1 item"initials extracts initials from a name, handy for avatars:
$avatar = initials "Jane Doe"// $avatar is "JD"A note on slug/convert-to-slug
The textfn set also ships a convert-to-slug action (invocable as slug) for turning text into a URL-safe slug, e.g. slug "Hello World!" → "hello-world". We hit a compiler bug validating it — see below — so its behavior here is sourced directly from the action’s own test assertions rather than a live compile, and it’s left out of the validated example files. Treat it as accurate but unverified through our usual pipeline until the underlying bug is fixed.
Common mistakes
| Mistake | Fix |
|---|---|
Passing a bare literal to pluralize’s count: pluralize 3 "item" "items" | Assign it to a variable first: $n = 3 then pluralize $n "item" "items" |
Writing -separator on add-text | The tag is actually spelled -seperator (typo in the action itself) |
Assuming replace-text replaces every match | It only replaces the first by default — add -all for every occurrence |
Assuming remove-text needs an -all tag like replace-text | It already removes every occurrence with no tag |
Expecting capitalize to force-lowercase the rest of the text | It only capitalizes the first letter of each word at whitespace boundaries — it doesn’t touch existing casing |
Checking if missing $result after reverse-text on a missing variable | reverse-text returns "" for missing input, not “missing” — check for an empty string instead |
Assuming extract-text uses the same indexing as character/word-at | character and word-at are 1-indexed; extract-text’s start/end range is 0-indexed |
Complete example
$full-name = " ada lovelace "$clean-name = trim $full-name$display-name = title-case $clean-name$avatar = initials $display-name
.text "{$display-name} ({$avatar})"
$article = "Rhappsody makes it easy to build data-driven apps without writing code."$snippet = truncate-text 40 $article$word-count = count-words $article
.text "{$snippet}".text "{$word-count} words"
$cart-count = 3$cart-label = pluralize $cart-count "item" "items".text "Cart: {$cart-label}"
$email = "[email protected]"$masked-email = mask-text 4 $email.text "Signed in as {$masked-email}"
$username = "ada_lovelace99"$is-reserved = starts-with "admin" $usernameif $is-reserved: .text "Reserved username" #red:else .text "Username OK" #green:end