Strings
Parsley has three distinct string types, each with different interpolation and escaping rules. Choosing the right one avoids unnecessary escaping and makes intent clear.
"Hello, World!\n" // double-quoted: escape sequences, no interpolation
`Hello, {name}!` // template: interpolation with {expr}
'C:\Users\raw \n stays' // raw: no escapes, interpolation with @{expr}
String Types
Double-Quoted Strings ("...")
Standard strings with escape sequences. No interpolation β braces are literal characters.
"Line 1\nLine 2" // newline between lines
"She said \"hello\"" // escaped quotes
"Tab\there" // tab character
| Escape | Meaning |
|---|---|
\n |
Newline |
\t |
Tab |
\r |
Carriage return |
\\ |
Literal backslash |
\" |
Literal double quote |
Template Strings (`...`)
Interpolated strings using {expression} β any valid Parsley expression works inside the braces. No escape sequences are processed.
let name = "Alice"
`Hello, {name}!` // "Hello, Alice!"
`2 + 2 = {2 + 2}` // "2 + 2 = 4"
`{name.toUpper()}` // "ALICE"
β οΈ Parsley uses
{expr}, not${expr}. The dollar sign is not part of the syntax β this is the most common mistake when coming from JavaScript.
Raw Strings ('...')
Backslashes are literal β no escape sequences. Interpolation uses @{expression}.
'C:\Users\name' // backslashes are literal
'regex: \d+\.\d+' // no escaping needed
let id = 42
'id = @{id}' // "id = 42"
Raw strings are ideal for file paths, regex patterns, SQL, and templates. Use \@ to escape a literal @ when followed by {.
Raw Strings in <script> and <style> Tags
The content inside <script> and <style> tags uses the same raw string rules β braces { and } are literal characters, and interpolation uses @{expr}. This is by design: CSS and JavaScript both use { } as core syntax (CSS rule blocks, JS code blocks), so treating them as literal avoids conflicts:
<style>
.card { border: 1px solid #ccc; }
.card:hover { background: lightblue; }
</style>
Use @{expr} when you need dynamic values:
let accent = "tomato"
<style>
.highlight { color: @{accent}; }
</style>
let endpoint = "/api/data"
<script>
fetch("@{endpoint}").then(function(r) { return r.json(); });
</script>
Choosing a String Type
| Need | Use | Why |
|---|---|---|
Static text with special chars (\n, \t) |
"..." |
Escape sequences processed |
| Dynamic text with expressions | `...` |
{expr} interpolation |
| Paths, regex, templates | '...' |
Backslashes literal, @{expr} interpolation |
Operators
| Operator | Description | Example |
|---|---|---|
+ |
Concatenation | "Hello" + " " + "World" β "Hello World" |
* |
Repetition | "ab" * 3 β "ababab" |
in |
Substring test | "ell" in "hello" β true |
not in |
Negated substring | "xyz" not in "hello" β true |
==, != |
Equality | "a" == "a" β true |
<, >, <=, >= |
Comparison (natural sort) | "file2" < "file10" β true |
~ |
Regex match (returns array or null) | "abc123" ~ /\d+/ β ["123"] |
!~ |
Regex no-match (returns boolean) | "hello" !~ /\d+/ β true |
β οΈ Natural sort order: String comparisons use natural ordering, so
"file2" < "file10"istrue(not lexicographic where"file10" < "file2"). This is almost always what you want but differs from most languages.
β οΈ
++does not concatenate strings. It wraps both sides into an array:"a" ++ "b"β["a", "b"]. Use+for string concatenation.
Indexing & Slicing
Strings support integer indexing (0-based) and slicing, including negative indices:
let s = "hello"
s[0] // "h"
s[-1] // "o" (last character)
s[1:3] // "el" (start inclusive, end exclusive)
s[:2] // "he" (first 2)
s[2:] // "llo" (from index 2)
s[?99] // null (optional access, no error)
Methods
Case & Formatting
| Method | Description |
|---|---|
.toUpper() |
Uppercase: "hello".toUpper() β "HELLO" |
.toLower() |
Lowercase: "HELLO".toLower() β "hello" |
.toTitle() |
Title case: "hello world".toTitle() β "Hello World" |
.toCamel() |
camelCase: "hello_world".toCamel() β "helloWorld" |
.toPascal() |
PascalCase: "hello_world".toPascal() β "HelloWorld" |
.toSnake() |
snake_case: "helloWorld".toSnake() β "hello_world" |
.toKebab() |
kebab-case: "helloWorld".toKebab() β "hello-world" |
.slug() |
URL-safe slug: "Hello World!".slug() β "hello-world" |
Case conversion methods handle snake_case, kebab-case, camelCase, PascalCase, spaces, and acronyms:
"hello_world".toCamel() // "helloWorld"
"hello-world".toPascal() // "HelloWorld"
"HelloWorld".toSnake() // "hello_world"
"XMLParser".toSnake() // "xml_parser" (acronyms handled)
"getAPIResponse".toKebab() // "get-api-response"
"HELLO".toCamel() // "hello"
Whitespace
| Method | Description |
|---|---|
.trim() |
Remove leading/trailing whitespace |
.collapse() |
Collapse runs of whitespace to single spaces |
.normalizeSpace() |
Collapse + trim (combines both) |
.stripSpace() |
Remove all whitespace entirely |
.indent(n) |
Add n spaces to the start of each non-blank line |
.outdent() |
Remove the common leading indent from all lines |
" hello world ".normalizeSpace() // "hello world"
" hello world ".stripSpace() // "helloworld"
Search & Transform
| Method | Description |
|---|---|
.length() |
Character count (Unicode-aware): "cafΓ©".length() β 4 |
.includes(sub) |
Contains substring: "hello".includes("ell") β true |
.split(delim) |
Split into array: "a,b,c".split(",") β ["a", "b", "c"] |
.replace(old, new) |
Replace all occurrences: "hello".replace("l", "L") β "heLLo" |
.digits() |
Extract only digits: "abc123def".digits() β "123" |
.truncate(len, suffix?) |
Truncate to length with suffix (default "..."). Unicode-aware. |
"Hello world".truncate(8) // "Hello..."
"Hello world".truncate(8, "β¦") // "Hello wβ¦"
"Hi".truncate(8) // "Hi" (no change if shorter)
"Hello world".truncate(8, "") // "Hello wo"
"γγγ«γ‘γ―δΈη".truncate(5) // "γγ..."
The replace method also accepts a regex as the first argument and a function as the second. With a regex, only the first match is replaced by default β add the g flag for global replacement:
"hello".replace("l", "L") // "heLLo" (string: replaces all)
"hello".replace(/l/, "L") // "heLlo" (regex: first match only)
"hello".replace(/l/g, "L") // "heLLo" (regex + g flag: all matches)
"hello world".replace(/\w+/g, fn(m) { m.toTitle() }) // "Hello World"
HTML
| Method | Description |
|---|---|
.htmlEncode() |
Escape <, >, &, " for safe HTML output |
.htmlDecode() |
Decode HTML entities back to characters |
.stripHtml() |
Remove all HTML tags |
.paragraphs() |
Convert blank-line-separated text to <p> tags |
.highlight(phrase, tag?) |
Wrap matches in HTML tag (default: <mark>) |
"hello & world".htmlEncode() // "hello & world"
"<b>hello</b>".stripHtml() // "hello"
Encoding
| Method | Description |
|---|---|
.toBase64() |
Encode as Base64: "hello".toBase64() β "aGVsbG8=" |
.fromBase64() |
Decode from Base64: "aGVsbG8=".fromBase64() β "hello" |
.urlEncode() |
Query-string encode (spaces β +) |
.urlDecode() |
Decode URL-encoded string |
.urlPathEncode() |
Encode path segments (/ β %2F) |
.urlQueryEncode() |
Encode query values (&, = encoded) |
Base64 round-trips work as expected. Invalid Base64 input returns an error:
"hello".toBase64() // "aGVsbG8="
"aGVsbG8=".fromBase64() // "hello"
"ζ₯ζ¬θͺ".toBase64().fromBase64() // "ζ₯ζ¬θͺ"
"!!!invalid!!!".fromBase64() // Error: invalid base64
Parsing
| Method | Returns | Description |
|---|---|---|
.parseJSON() |
any | Parse string as JSON into Parsley values |
.parseCSV(hasHeader?) |
table | Parse CSV (default: first row is header) |
.parseMarkdown(opts?) |
dictionary | Returns {html, md, raw} from Markdown source |
let data = '{"name": "Bob"}'.parseJSON()
data.name // "Bob"
let doc = "# Title\n\nBody".parseMarkdown()
doc.html // "<h1>Title</h1>\n<p>Body</p>\n"
Templating
.render(dict?)
Evaluates @{expr} placeholders in a string using values from the provided dictionary. Use double-quoted strings to hold the template (they preserve the @{...} syntax literally):
let tpl = "Hello @{name}, you have @{count} items."
tpl.render({name: "Alice", count: 3})
// "Hello Alice, you have 3 items."
Expressions inside @{...} are full Parsley β arithmetic, method calls, and conditionals all work:
"Total: @{price * qty}".render({price: 10, qty: 5})
// "Total: 50"
β οΈ Don't use raw strings (
'...') for templates you intend to.render()later β the@{expr}placeholders get interpolated immediately when the raw string is created. Use double-quoted strings instead to keep the placeholders intact.
Display & Serialization
| Method | Description |
|---|---|
.toJSON() |
JSON-encode the string (adds quotes, escapes special chars) |
.toBox(opts?) |
Render in a box with box-drawing characters |
.repr() |
Debug representation with escapes visible |
toBox options: style ("single", "double", "ascii", "rounded"), title, maxWidth, align ("left", "right", "center").
Key Differences from Other Languages
- Three string types: Most languages have one or two. Parsley's raw strings (
'...') avoid the backslash-escaping pain of regex and paths {expr}not${expr}: Template string interpolation has no dollar sign'...'is raw, not a char: Single quotes create raw strings, not character literals- Natural sort comparison:
"file2" < "file10"istrueβ string comparisons use natural ordering, not lexicographic +concatenates,++does not: Use+for string joining.++wraps into an array- No
.toString()method: Parsley coerces automatically in template strings and+concatenation. Use.type()to check a value's type
See Also
- Numbers β numeric types and formatting
- Booleans & Null β truthiness and null coalescing
- Arrays β
.split()returns arrays;.join()is on arrays - Regex β pattern matching and
.replace()with regex - Operators β complete operator reference
- Tags β string interpolation inside HTML tags
- Data Formats β parsing and generating CSV, JSON, Markdown