// Filters applied via the `|` operator, e.g. `{{ name | upper }}`.
//
// Common filters implemented by the portable core.
///|
/// Uppercase an ASCII string.
fn str_upper(s : String) -> String {
let chars = s.to_array()
let out : Array[Char] = []
for c in chars {
if c >= 'a' && c <= 'z' {
match (c.to_int() - 32).to_char() {
Some(u) => out.push(u)
None => out.push(c)
}
} else {
out.push(c)
}
}
String::from_iter(out.iter())
}
///|
/// Lowercase an ASCII string.
fn str_lower(s : String) -> String {
let chars = s.to_array()
let out : Array[Char] = []
for c in chars {
if c >= 'A' && c <= 'Z' {
match (c.to_int() + 32).to_char() {
Some(u) => out.push(u)
None => out.push(c)
}
} else {
out.push(c)
}
}
String::from_iter(out.iter())
}
///|
/// Apply a named filter to a value with already-evaluated arguments.
/// Unknown filters pass the input through unchanged.
pub fn apply_filter(name : String, input : Value, args : Array[Value]) -> Value {
match name {
"upper" =>
match input {
Str(s) => Str(str_upper(s))
_ => input
}
"lower" =>
match input {
Str(s) => Str(str_lower(s))
_ => input
}
"trim" =>
match input {
Str(s) => Str(s.trim().to_owned())
_ => input
}
"length" =>
match input {
Str(s) => Int(s.char_length())
Array(a) => Int(a.length())
Object(o) => Int(o.length())
_ => Int(0)
}
"default" => {
let missing = match input {
Null => true
_ => false
}
if missing && args.length() > 0 {
args[0]
} else {
input
}
}
"first" =>
match input {
Array(a) => if a.length() > 0 { a[0] } else { Null }
_ => input
}
"last" =>
match input {
Array(a) => if a.length() > 0 { a[a.length() - 1] } else { Null }
_ => input
}
"abs" =>
match input {
Int(i) => Int(if i < 0 { -i } else { i })
_ => input
}
"reverse" =>
match input {
Array(a) => {
let r : Array[Value] = []
for k = a.length() - 1; k >= 0; k = k - 1 {
r.push(a[k])
}
Array(r)
}
_ => input
}
"nth" =>
match input {
Array(a) =>
match args[0] {
Int(i) => if i >= 0 && i < a.length() { a[i] } else { Null }
_ => Null
}
_ => input
}
"int" =>
match input {
Int(i) => Int(i)
Float(f) => Int(f.to_int())
_ => input
}
"round" =>
match input {
Float(f) => Float(f.round())
_ => input
}
"keys" =>
match input {
Object(o) => {
let ks : Array[Value] = []
for entry in o {
let (k, _) = entry
ks.push(Str(k))
}
Array(ks)
}
_ => input
}
"values" =>
match input {
Object(o) => {
let vs : Array[Value] = []
for entry in o {
let (_, v) = entry
vs.push(v)
}
Array(vs)
}
_ => input
}
"json_encode" => Str(value_to_json(input))
"escape_html" | "e" =>
match input {
Str(s) => Str(escape_html(s))
_ => input
}
"capitalize" =>
match input {
Str(s) => Str(capitalize_str(s))
_ => input
}
"concat" =>
match input {
Array(a) =>
match args[0] {
Array(b) => {
let r : Array[Value] = []
for x in a {
r.push(x)
}
for x in b {
r.push(x)
}
Array(r)
}
_ => input
}
_ => input
}
"unique" =>
match input {
Array(a) => {
let out : Array[Value] = []
for x in a {
let mut found = false
for y in out {
if value_eq(x, y) {
found = true
}
}
if !found {
out.push(x)
}
}
Array(out)
}
_ => input
}
"slice" =>
match input {
Array(a) =>
match (args[0], args[1]) {
(Int(s), Int(e)) => {
let r : Array[Value] = []
let mut j = s
while j < e && j < a.length() {
if j >= 0 {
r.push(a[j])
}
j = j + 1
}
Array(r)
}
_ => input
}
_ => input
}
"replace" =>
match input {
Str(s) =>
match (args[0], args[1]) {
(Str(old), Str(new)) => Str(replace_str(s, old, new))
_ => input
}
_ => input
}
"split" =>
match input {
Str(s) =>
match args[0] {
Str(sep) => {
let parts : Array[Value] = []
let buf = StringBuilder::new()
let sc = sep.to_array()
let delim = if sc.length() > 0 { sc[0] } else { ' ' }
for c in s.to_array() {
if c == delim {
parts.push(Str(buf.to_string()))
buf.reset()
} else {
buf.write_char(c)
}
}
parts.push(Str(buf.to_string()))
Array(parts)
}
_ => input
}
_ => input
}
"join" =>
match input {
Array(a) =>
match args[0] {
Str(sep) => {
let parts : Array[String] = []
for x in a {
parts.push(render_value(x))
}
Str(join_str(parts, sep))
}
_ => input
}
_ => input
}
"sort" =>
match input {
Array(a) => {
let r : Array[Value] = []
for x in a {
r.push(x)
}
let n = r.length()
for i = 0; i < n; i = i + 1 {
for j = 0; j < n - 1 - i; j = j + 1 {
if value_lt(r[j + 1], r[j]) {
let tmp = r[j]
r[j] = r[j + 1]
r[j + 1] = tmp
}
}
}
Array(r)
}
_ => input
}
"title" =>
match input {
Str(s) => Str(title_str(s))
_ => input
}
"striptags" =>
match input {
Str(s) => Str(striptags_str(s))
_ => input
}
"get" =>
match (input, args[0]) {
(Object(_), Str(k)) => lookup(k, input)
_ => input
}
"pairs" =>
match input {
Object(o) => {
let r : Array[Value] = []
for entry in o {
let (k, v) = entry
r.push(array_value([str_value(k), v]))
}
Array(r)
}
_ => input
}
"safe" => input // marks output as pre-escaped (no-op until autoescape lands)
"as_float" =>
match input {
Int(i) => Float(i.to_double())
Float(f) => Float(f)
_ => input
}
"trim_start" =>
match input {
Str(s) => Str(trim_start_str(s))
_ => input
}
"truncate" =>
match (input, args[0]) {
(Str(s), Int(n)) => Str(truncate_str(s, n))
_ => input
}
"max" =>
match input {
Array(a) => {
let mut best : Value = Null
let mut seen = false
for x in a {
if !seen {
best = x
seen = true
} else if value_lt(best, x) {
best = x
}
}
best
}
_ => input
}
"min" =>
match input {
Array(a) => {
let mut best : Value = Null
let mut seen = false
for x in a {
if !seen {
best = x
seen = true
} else if value_lt(x, best) {
best = x
}
}
best
}
_ => input
}
"sum" =>
match input {
Array(a) => {
let mut total = 0
for x in a {
match x {
Int(i) => total = total + i
_ => ()
}
}
Int(total)
}
_ => input
}
"lines" =>
match input {
Str(s) => {
let r : Array[Value] = []
let buf = StringBuilder::new()
for c in s.to_array() {
if c == '\n' {
r.push(Str(buf.to_string()))
buf.reset()
} else {
buf.write_char(c)
}
}
r.push(Str(buf.to_string()))
Array(r)
}
_ => input
}
"floor" =>
match input {
Float(f) => Float(f.floor())
_ => input
}
"ceil" =>
match input {
Float(f) => Float(f.ceil())
_ => input
}
"urlencode" =>
match input {
Str(s) => Str(urlencode_str(s))
_ => input
}
"indent" =>
match (input, args[0]) {
(Str(s), Int(n)) => Str(indent_str(s, n))
_ => input
}
"repeat" =>
match (input, args[0]) {
(Str(s), Int(n)) => Str(repeat_str(s, n))
_ => input
}
"pluralize" =>
match (input, args[0], args[1]) {
(Int(n), Str(singular), Str(plural)) =>
Str(if n == 1 { singular } else { plural })
_ => input
}
"count" =>
match input {
Str(s) => Int(s.char_length())
Array(a) => Int(a.length())
Object(o) => Int(o.length())
_ => Int(0)
}
"wordcount" =>
match input {
Str(s) => Int(wordcount_str(s))
_ => input
}
"spaceless" =>
match input {
Str(s) => Str(spaceless_str(s))
_ => input
}
"center" =>
match (input, args[0]) {
(Str(s), Int(n)) => Str(center_str(s, n))
_ => input
}
"ljust" =>
match (input, args[0]) {
(Str(s), Int(n)) => Str(ljust_str(s, n))
_ => input
}
"rjust" =>
match (input, args[0]) {
(Str(s), Int(n)) => Str(rjust_str(s, n))
_ => input
}
"as_str" => Str(render_value(input))
_ => input // unknown filter: pass through
}
}
///|
/// Count whitespace-separated words.
fn wordcount_str(s : String) -> Int {
let mut count = 0
let mut in_word = false
for c in s.to_array() {
if c == ' ' || c == '\t' || c == '\n' {
in_word = false
} else if !in_word {
in_word = true
count = count + 1
}
}
count
}
///|
/// Remove whitespace between `>` and `<` (HTML spaceless).
fn spaceless_str(s : String) -> String {
let chars = s.to_array()
let buf = StringBuilder::new()
let mut i = 0
while i < chars.length() {
let c = chars[i]
if c == '>' {
buf.write_char(c)
let mut j = i + 1
while j < chars.length() &&
(chars[j] == ' ' || chars[j] == '\n' || chars[j] == '\t') {
j = j + 1
}
i = j
} else {
buf.write_char(c)
i = i + 1
}
}
buf.to_string()
}
///|
/// Pad `s` with spaces on the right to width `n`.
fn ljust_str(s : String, n : Int) -> String {
let len = s.char_length()
if len >= n {
s
} else {
s + repeat_str(" ", n - len)
}
}
///|
/// Pad `s` with spaces on the left to width `n`.
fn rjust_str(s : String, n : Int) -> String {
let len = s.char_length()
if len >= n {
s
} else {
repeat_str(" ", n - len) + s
}
}
///|
/// Center `s` in a field of width `n`.
fn center_str(s : String, n : Int) -> String {
let len = s.char_length()
if len >= n {
s
} else {
let total = n - len
let left = total / 2
let right = total - left
repeat_str(" ", left) + s + repeat_str(" ", right)
}
}
///|
/// Percent-encode a string for URLs (ASCII subset).
fn urlencode_str(s : String) -> String {
let buf = StringBuilder::new()
for c in s.to_array() {
if (c >= 'a' && c <= 'z') ||
(c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9') ||
c == '-' ||
c == '_' ||
c == '.' ||
c == '~' {
buf.write_char(c)
} else {
let hex = "0123456789ABCDEF".to_array()
let v = c.to_int()
buf.write_string("%")
buf.write_char(hex[v / 16])
buf.write_char(hex[v % 16])
}
}
buf.to_string()
}
///|
/// Indent every line of `s` by `n` spaces (except the first).
fn indent_str(s : String, n : Int) -> String {
let pad = repeat_str(" ", n)
let buf = StringBuilder::new()
let mut first = true
for c in s.to_array() {
if first {
first = false
}
if c == '\n' {
buf.write_char(c)
buf.write_string(pad)
} else {
buf.write_char(c)
}
}
buf.to_string()
}
///|
/// Repeat a string `n` times.
fn repeat_str(s : String, n : Int) -> String {
let buf = StringBuilder::new()
for i = 0; i < n; i = i + 1 {
buf.write_string(s)
}
buf.to_string()
}
///|
/// Trim leading whitespace.
fn trim_start_str(s : String) -> String {
let chars = s.to_array()
let mut i = 0
while i < chars.length() && (chars[i] == ' ' || chars[i] == '\t') {
i = i + 1
}
s[i:chars.length()].to_owned()
}
///|
/// Truncate a string to at most `n` characters, appending "..." if cut.
fn truncate_str(s : String, n : Int) -> String {
let len = s.char_length()
if len <= n {
s
} else {
s[0:n].to_owned() + "..."
}
}
///|
/// Title-case each whitespace-separated word (ASCII).
fn title_str(s : String) -> String {
let chars = s.to_array()
let out : Array[Char] = []
let mut prev_space = true
for c in chars {
let nc = if prev_space && c >= 'a' && c <= 'z' {
match (c.to_int() - 32).to_char() {
Some(u) => u
None => c
}
} else {
c
}
out.push(nc)
prev_space = c == ' '
}
String::from_iter(out.iter())
}
///|
/// Strip HTML-like `<...>` tags from a string.
fn striptags_str(s : String) -> String {
let buf = StringBuilder::new()
let mut in_tag = false
for c in s.to_array() {
if c == '<' {
in_tag = true
} else if c == '>' {
in_tag = false
} else if !in_tag {
buf.write_char(c)
}
}
buf.to_string()
}
///|
/// HTML-escape a string.
fn escape_html(s : String) -> String {
let buf = StringBuilder::new()
for c in s.to_array() {
match c {
'&' => buf.write_string("&")
'<' => buf.write_string("<")
'>' => buf.write_string(">")
'"' => buf.write_string(""")
'\'' => buf.write_string("'")
_ => buf.write_char(c)
}
}
buf.to_string()
}
///|
/// Capitalize the first ASCII character (rest unchanged).
fn capitalize_str(s : String) -> String {
let chars = s.to_array()
if chars.length() == 0 {
return ""
}
let out : Array[Char] = []
for idx = 0; idx < chars.length(); idx = idx + 1 {
let c = chars[idx]
if idx == 0 && c >= 'a' && c <= 'z' {
match (c.to_int() - 32).to_char() {
Some(u) => out.push(u)
None => out.push(c)
}
} else {
out.push(c)
}
}
String::from_iter(out.iter())
}
///|
/// Replace all occurrences of `old` with `new` in `s`.
fn replace_str(s : String, old : String, new : String) -> String {
if old == "" {
return s
}
let chars = s.to_array()
let oldchars = old.to_array()
let n = chars.length()
let m = oldchars.length()
let buf = StringBuilder::new()
let mut i = 0
while i < n {
let mut matches = true
if i + m > n {
matches = false
}
let mut k = 0
while matches && k < m {
if chars[i + k] != oldchars[k] {
matches = false
}
k = k + 1
}
if matches {
buf.write_string(new)
i = i + m
} else {
buf.write_char(chars[i])
i = i + 1
}
}
buf.to_string()
}
///|
fn join_str(arr : Array[String], sep : String) -> String {
let buf = StringBuilder::new()
for k = 0; k < arr.length(); k = k + 1 {
if k > 0 {
buf.write_string(sep)
}
buf.write_string(arr[k])
}
buf.to_string()
}
///|
/// Render a Value as deterministic JSON.
fn value_to_json(v : Value) -> String {
v.to_json_string()
}