Skip to content

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.

ActionWhat it doesExample
camel-caseConverts to camelCase$c = camel-case "Hello World"
pascal-caseConverts to PascalCase$c = pascal-case "Hello World"
kebab-caseConverts to kebab-case$c = kebab-case "Hello World"
snake-caseConverts to snake_case$c = snake-case "Hello World"
constant-caseConverts to CONSTANT_CASE$c = constant-case "Hello World"
title-caseConverts 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 first

capitalize 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 rest

Trimming, repeating, reversing, padding

ActionWhat it doesExample
trimRemoves leading/trailing whitespace$t = trim " hello "
reverse-textReverses the characters$r = reverse-text "hello"
repeat-textRepeats text N times$r = repeat-text 3 "-"
pad-leftPads to a length on the left$p = pad-left 8 "0" "42"
pad-rightPads 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

ActionWhat it doesExample
add-textAppends text to a base string$s = add-text "World" to "Hello"
add-lineAppends text on a new line$s = add-line "line2" to "line1"
replace-textReplaces an occurrence of a substring$s = replace-text "hello" "goodbye" "hello world hello"
remove-textRemoves all occurrences of a substring$s = remove-text "hello" "hello world hello"
mask-textMasks 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 gone

mask-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

ActionWhat it doesExample
characterGets the character at a position$c = character 1 of "abc"
word-atGets the word at a position$w = word-at 2 of "quick brown fox"
extract-textExtracts a substring by range$s = extract-text 0 5 "hello world"
length-ofLength of the text$n = length-of "hello world"
count-wordsNumber of words$n = count-words "one two three"
split-textSplits 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.

ActionWhat it doesExample
starts-withWhether text starts with a prefix$b = starts-with "hello" "hello world"
ends-withWhether text ends with a suffix$b = ends-with "world" "hello world"
contains-textWhether 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 false

truncate-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 "..." suffix

URL encoding

ActionWhat it doesExample
encode-uriURI-encodes text$e = encode-uri "hello world"
decode-uriDecodes 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

MistakeFix
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-textThe tag is actually spelled -seperator (typo in the action itself)
Assuming replace-text replaces every matchIt only replaces the first by default — add -all for every occurrence
Assuming remove-text needs an -all tag like replace-textIt already removes every occurrence with no tag
Expecting capitalize to force-lowercase the rest of the textIt 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 variablereverse-text returns "" for missing input, not “missing” — check for an empty string instead
Assuming extract-text uses the same indexing as character/word-atcharacter 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}"
$masked-email = mask-text 4 $email
.text "Signed in as {$masked-email}"
$username = "ada_lovelace99"
$is-reserved = starts-with "admin" $username
if $is-reserved:
.text "Reserved username" #red
:else
.text "Username OK" #green
:end