Functions
Datacore comes with a default set of functions, which can be used in expressions for various purposes.
Usage
To use a function in an expression, simply use it's name and provide a list of arguments:
func(argument1, argument2, argument3)
// i.e.,:
lower("YES") // -> "yes"
replace("yes", "e", "a") // -> "yas"
Datacore also supports 'postfix' function calling style, allowing you to chain them instead. When using postfix function style, the object you are calling the function on implicitly becomes the first argument:
"YES".lower() // Same as lower("YES")
"YES".lower().replace("e", "a") // Same as replace(lower("YES"), "e", "a")
Datacore functions support "vectorization", meaning you can also replace any argument with a list of arguments instead; the function will return a list of results instead of a single value:
lower(["YES", "NO"]) // -> ["yes", "no"]
["YES", "NO"].lower() // vectorization + postfix calling style.
Constructors
Constructors which create values.
object(key1, value1, ...)
Creates a new object with the given keys and values. Keys and values should alternate in the call, and keys should always be strings/text.
object() => empty object
object("a", 6) => object which maps "a" to 6
object("a", 4, "c", "yes") => object which maps a to 4, and c to "yes"
Objects can also be created using object literal syntax, which is usually preferred:
{} => // empty object
{ a: 6 } // object which maps "a" to 6
{ a: 4, c: "yes" } // a => 4, and c => "yes"
list(value1, value2, ...)
Creates a new list with the given values in it. array
can be used an alias for list
.
list() => empty list
list(1, 2, 3) => list with 1, 2, and 3
array("a", "b", "c") => list with "a", "b", and "c"
Note that you can also make lists directly with list syntax, which is generally preferred:
[] // => empty list
[1, 2, 3] // => list with 1, 2, and 3
date(any)
Parses a date from the provided string, date, or link object, if possible, returning null otherwise.
date("2020-04-18") // = <date object representing April 18th, 2020>
date([[2021-04-16]]) // = <date object for the given page, referring to file.day>
// Postfix style:
"2020-04-18".date()
date(text, format)
Parses a date from text to luxon DateTime
with the specified format. Note localized formats might not work.
Uses Luxon tokens.
date("12/31/2022", "MM/dd/yyyy") => DateTime for December 31th, 2022
date("210313", "yyMMdd") => DateTime for March 13th, 2021
date("946778645000", "x") => DateTime for "2000-01-02T03:04:05"
// Postfix style:
"210313".date("yyMMdd") = DateTime for March 13th, 2021
dur(any)
Parses a duration from the provided string or duration, returning null on failure.
dur(8 minutes) = <8 minutes>
dur("8 minutes, 4 seconds") = <8 minutes, 4 seconds>
dur(dur(8 minutes)) = dur(8 minutes) = <8 minutes>
// Postfix style:
"8 minutes, 4 seconds".dur() = <8 minutes, 4 seconds>
number(string)
Pulls the first number out of the given string, returning it if possible. Returns null if there are no numbers in the string.
number("18 years") = 18
number(34) = 34
number("hmm") = null
// Postfix style.
"18 years".number() = 18
string(any)
Converts any value into a "reasonable" string representation. This sometimes produces less pretty results than just directly using the value in a query - it is mostly useful for coercing dates, durations, numbers, and so on into strings for manipulation.
string(18) = "18"
string(dur(8 hours)) = "8 hours"
string(date(2021-08-15)) = "August 15th, 2021"
// Postfix style:
18.string() = "18"
link(path, [display])
Construct a link object from the given file path or name. If provided with two arguments, the second argument is the display name for the link.
link("Hello") // => link to page named 'Hello'
link("Hello", "Goodbye") // => link to page named 'Hello', displays as 'Goodbye'
// Postfix style:
"Hello".link() // => link to page named 'Hello'
embed(link, [embed?])
Convert a link object into an embedded link; practically speaking, this affects how Datacore will try to render the link in various views. Embedded links will be rendered as an Obsidian embed where possible, where as unembedded link just render as a clickable, textual link.
embed(link("Hello.png")) => embedded link to the "Hello.png" image, which will render as an actual image.
// Postfix style:
link("Hello.png").embed()
elink(url, [display])
Construct a link to an external url (like www.google.com
). If provided with two arguments, the second
argument is the display name for the link.
elink("www.google.com") => link element to google.com
elink("www.google.com", "Google") => link element to google.com, displays as "Google"
// Postfix style:
"www.google.com".elink()
typeof(any)
Get the type of any object for inspection. Can be used in conjunction with other operators to change behavior based on type.
typeof(8) => "number"
typeof("text") => "string"
typeof([1, 2, 3]) => "array"
typeof({ a: 1, b: 2 }) => "object"
typeof(date(2020-01-01)) => "date"
typeof(dur(8 minutes)) => "duration"
Numeric Operations
round(number, [digits])
Round a number to a given number of digits. If the second argument is not specified, rounds to the nearest whole number; otherwise, rounds to the given number of digits.
round(16.555555) = 17
round(16.555555, 2) = 16.56
trunc(number)
Truncates ("cuts off") the decimal point from a number.
trunc(12.937) = 12
trunc(-93.33333) = -93
trunc(-0.837764) = 0
floor(number)
Always rounds down and returns the largest integer less than or equal to a given number. This means that negative numbers become more negative.
floor(12.937) = 12
floor(-93.33333) = -94
floor(-0.837764) = -1
ceil(number)
Always rounds up and returns the smallest integer greater than or equal to a given number. This means negative numbers become less negative.
ceil(12.937) = 13
ceil(-93.33333) = -93
ceil(-0.837764) = 0
min(a, b, ..)
Compute the minimum value of a list of arguments, or an array.
min(1, 2, 3) = 1
min([1, 2, 3]) = 1
min("a", "ab", "abc") = "a"
max(a, b, ...)
Compute the maximum value of a list of arguments, or an array.
max(1, 2, 3) = 3
max([1, 2, 3]) = 3
max("a", "ab", "abc") = "abc"
sum(array)
Sums all numeric values in the array. If you have null values in your sum, you can eliminate them via the nonnull
function.
sum([1, 2, 3]) = 6
sum([]) = null
sum(nonnull([null, 1, 8])) = 9
// Postfix style:
[1, 2, 3].sum() = 6
product(array)
Calculates the product of a list of numbers. If you have null values in your average, you can eliminate them via the nonnull
function.
product([1,2,3]) = 6
product([]) = null
product(nonnull([null, 1, 2, 4])) = 8
// Postfix style:
[1, 2, 3].product() = 6
reduce(array, operand)
A generic function to reduce a list into a single value, valid operands are "+"
, "-"
, "*"
, "/"
and the boolean operands "&"
and "|"
. Note that using "+"
and "*"
equals the sum()
and product()
functions, and using "&"
and "|"
matches all()
and any()
.
reduce([100, 20, 3], "-") = 77
reduce([200, 10, 2], "/") = 10
reduce(values, "*") = Multiplies every element of values, same as product(values)
reduce(values, this.operand) = Applies the local field operand to each of the values
reduce(["⭐", 3], "*") = "⭐⭐⭐", same as "⭐" * 3
reduce([1]), "+") = 1, has the side effect of reducing the list into a single element
// Postfix style.
[1].reduce("+")
Reduce can also take a lambda function, which it will apply element by element, left to right:
reduce([100, 20, 3], (accum, curr) => accum * curr) = 6000 // multiplies elements.
// Postfix style.
[100, 200, 300, 100].reduce((accum, curr) => max(accum, curr)) = 300 // max of an array via reduce.
average(array)
Computes the numeric average of numeric values. If you have null values in your average, you can eliminate them via the
nonnull
function.
average([1, 2, 3]) = 2
average([]) = null
average(nonnull([null, 1, 2])) = 1.5
// Postfix style.
[1, 2, 3].average() = 2
minby(array, function)
Compute the minimum value of an array, using the provided function.
minby([1, 2, 3], (k) => k) = 1
minby([1, 2, 3], (k) => 0 - k) => 3
minby(this.file.tasks, (k) => k.due) => (earliest due)
// Postfix style.
this.file.tasks.minby((k) => k.due) => (earliest due)
maxby(array, function)
Compute the maximum value of an array, using the provided function.
maxby([1, 2, 3], (k) => k) = 3
maxby([1, 2, 3], (k) => 0 - k) => 1
maxby(this.file.tasks, (k) => k.due) => (latest due)
// Postfix style.
this.file.tasks.maxby((k) => k.due) => (latest due)
--
Objects, Arrays, and String Operations
Operations that manipulate values inside of container objects.
contains()
and friends
There are three distinct variants of contains
, whose behavior mostly matters on how they treat strings and lists.
contains(object|list|string, value)
Checks if the given container type has the given value in it. This function behave slightly differently based on whether the first argument is an object, a list, or a string. This function is case-sensitive.
- For objects, checks if the object has a key with the given name. For example,
file.contains("$ctime") = true
file.contains("day") = true (if file has a 'date' field, false otherwise) - For lists, checks if any of the array elements equals the given value. For example,
[1, 2, 3].contains(3) = true
[].contains(1) = false
```js - For strings, checks if the given value is a substring (i.e., inside) the string.
"hello".contains("lo") = true
"yes".contains("no") = false
icontains(object|list|string, value)
Case insensitive version of contains()
.
econtains(object|list|string, value)
"Exact contains" checks if the exact match is found in the string/list. This function is case sensitive.
-
For strings, it behaves exactly like
contains()
."Hello".econtains("Lo") = false
"Hello".econtains("lo") = true -
For lists, it checks if the exact word is in the list.
["These", "are", "words"].econtains("word") = false
["These", "are", "words"].econtains("words") = true -
For objects, it checks if the exact key name is present in the object. It does not do recursive checks.
{key:"value", pairs:"here"}.econtains("here") = false
:key:"value", pairs:"here"}.econtains("key") = true
{key:"value", recur:{recurkey: "val"}}.econtains("value") = false
{key:"value", recur:{recurkey: "val"}}.econtains("Recur") = false
{key:"value", recur:{recurkey: "val"}}.econtains("recurkey") = false
containsword(list|string, value)
Checks if value
has an exact word match in a string.
"word".containsword("word") = true
"word".containsword("Word") = true
"words".containsword("Word") = false
"Hello there!".containsword("hello") = true
"Hello there!".containsword("HeLLo") = true
"Hello there chaps!".containsword("chap") = false
"Hello there chaps!".containsword("chaps") = true
Note that containsword
does not work directly on lists - passing a list argument will apply the functiont to each element in the list:
["hello", "hello there", "no"].containsword("hello") = [true, true, false]
If you want to look for a word in any element in the list, combine containsword
with any
:
["hello", "hello there", "no"].containsword("hello").any() = true
extract(object, key1, key2, ...)
Pulls multiple fields out of an object, creating a new object with just those fields.
extract(file, "$ctime", "$mtime") = { "$ctime": file.ctime, "$mtime": file.mtime }
// Postfix style.
file.extract("$ctime", "$mtime") = { "$ctime": file.ctime, "$mtime": file.mtime }
sort(list)
Sorts a list, returning a new list in sorted order.
sort([3, 2, 1]) = [1, 2, 3]
sort(["a", "b", "aa"]) = ["a", "b", "aa"]
// Postfix style.
["a", "b", "aa"].sort() = ["a", "b", "aa"]
reverse(list)
Reverses a list, returning a new list in reversed order.
reverse([1, 2, 3]) = [3, 2, 1]
reverse(["a", "b", "c"]) = ["c", "b", "a"]
// Postfix style.
["a", "b", "c"].reverse() = ["c", "b", "a"]
length(object|array)
Returns the number of fields in an object, or the number of entries in an array.
length([]) = 0
length([1, 2, 3]) = 3
length({"hello": 1, "goodbye": 2}) = 2
// Postfix style.
[1, 2, 3].length() = 3
nonnull(array)
Return a new array with all null values removed.
nonnull([]) = []
nonnull([null, false]) = [false]
nonnull([1, 2, 3]) = [1, 2, 3]
// Postfix style.
[null, false].nonnull() = [false]
all(array)
Returns true
only if ALL values in the array are truthy. You can also pass multiple arguments to this function, in
which case it returns true
only if all arguments are truthy.
all([1, 2, 3]) = true
all([true, false]) = false
all(true, false) = false
all(true, true, true) = true
// Postfix style.
[true, true, true].all() = true
You can pass a function as second argument to return only true if all elements in the array matches the predicate.
all([1, 2, 3], (x) => x > 0) = true
all([1, 2, 3], (x) => x > 1) = false
all(["apple", "pie", 3], (x) => typeof(x) = "string") = false
// Postfix style.
["apple", "pie", 3].all((x) => typeof(x) = "string") = false
any(array)
Returns true
if ANY of the values in the array are truthy. You can also pass multiple arguments to this function, in
which case it returns true
if any of the arguments are truthy.
any([1, 2, 3]) = true
any([true, false]) = true
any([false, false, false]) = false
any(true, false) = true
any(false, false) = false
// Postfix style.
[true, false].any() = true
You can pass a function as second argument to return only true if any element in the array matches the predicate.
any([1, 2, 3], (x) => x > 2) = true
any([1, 2, 3], (x) => x = 0) = false
// Postfix style.
[1, 2, 3].any((x) => x = 0) = false
none(array)
Returns true
if NONE of the values in the array are truthy.
none([]) = true
none([false, false]) = true
none([false, true]) = false
none([1, 2, 3]) = false
// Postfix style.
[1, 2, 3].none() = false
You can pass a function as second argument to return only true if none of the elements in the array matches the predicate.
none([1, 2, 3], (x) => x = 0) = true
none([true, true], (x) => x = false) = true
none(["Apple", "Pi", "Banana"], (x) => startswith(x, "A")) = false
// Postfix style.
["Apple", "Pi", "Banana"].none((x) => startswith(x, "A")) = false
join(array, [delimiter])
Joins elements in an array into a single string (i.e., rendering them all on the same line). If provided with a second argument, then each element will be separated by the given separator.
join([1, 2, 3]) = "1, 2, 3"
join([1, 2, 3], " ") = "1 2 3"
join(6) = "6"
join([]) = ""
// Postfix style.
[1, 2, 3].join(" ") = "1 2 3"
filter(array, predicate)
Filters elements in an array according to the predicate, returning a new list of the elements which matched.
filter([1, 2, 3], (x) => x >= 2) = [2, 3]
filter(["yes", "no", "yas"], (x) => startswith(x, "y")) = ["yes", "yas"]
// Postfix style.
["yes", "no", "yas"].filter((x) => startswith(x, "y")) = ["yes", "yas"]
map(array, func)
Applies the function to each element in the array, returning a list of the mapped results.
map([1, 2, 3], (x) => x + 2) = [3, 4, 5]
map(["yes", "no"], (x) => x + "?") = ["yes?", "no?"]
// Postfix style.
["yes", "no"].map((x) => x + "?") = ["yes?", "no?"]
flat(array, [depth])
Concatenates sub-levels of the array to the desired depth. Default is 1 level, but it can
concatenate multiple levels. E.g. Can be used to reduce array depth on rows
lists after
doing GROUP BY
.
flat([1, 2, 3, [4, 5], 6]) => [1, 2, 3, 4, 5, 6]
flat([1, [21, 22], [[311, 312, 313]]], 4) => list(1, 21, 22, 311, 312, 313)
flat(rows.file.outlinks)) => All the file outlinks at first level in output
// Postfix style.
rows.file.outlinks.flat() => All the file outlinks at first level in output
slice(array, [start, [end]])
Returns a shallow copy of a portion of an array into a new array object selected from start
to end
(end
not included) where start
and end
represents the index of items in that array.
slice([1, 2, 3, 4, 5], 3) = [4, 5] => All items from given position, 0 as first
slice(["ant", "bison", "camel", "duck", "elephant"], 0, 2) = ["ant", "bison"] => First two items
slice([1, 2, 3, 4, 5], -2) = [4, 5] => counts from the end, last two items
slice(someArray) => a copy of someArray
// Postfix style.
[1, 2, 3, 4, 5].slice(-2) = [4, 5] => counts from the end, last two items
String Operations
regextest(pattern, string)
Checks if the given regex pattern can be found in the string (using the JavaScript regex engine).
regextest("\w+", "hello") = true
regextest(".", "a") = true
regextest("yes|no", "maybe") = false
regextest("what", "what's up dog?") = true
regexmatch(pattern, string)
Checks if the given regex pattern matches the entire string, using the JavaScript regex engine.
This differs from regextest
in that regextest can match just parts of the text.
regexmatch("\w+", "hello") = true
regexmatch(".", "a") = true
regexmatch("yes|no", "maybe") = false
regexmatch("what", "what's up dog?") = false
regexreplace(string, pattern, replacement)
Replaces all instances where the regex pattern
matches in string
, with replacement
. This uses the JavaScript
replace method under the hood, so you can use special characters like $1
to refer to the first capture group, and so on.
regexreplace("yes", "[ys]", "a") = "aea"
regexreplace("Suite 1000", "\d+", "-") = "Suite -"
replace(string, pattern, replacement)
Replace all instances of pattern
in string
with replacement
.
replace("what", "wh", "h") = "hat"
replace("The big dog chased the big cat.", "big", "small") = "The small dog chased the small cat."
replace("test", "test", "no") = "no"
// Postfix style.
"The big dog chased the big cat.".replace("big", "small") = "The small dog chased the small cat."
lower(string)
Convert a string to all lower case.
lower("Test") = "test"
lower("TEST") = "test"
// Postfix style.
"Test".lower() = "test"
upper(string)
Convert a string to all upper case.
upper("Test") = "TEST"
upper("test") = "TEST"
// Postfix style.
"Test".upper() = "TEST"
split(string, delimiter, [limit])
Split a string on the given delimiter string. If a third argument is provided, it limits the number of splits that occur. The delimiter string is interpreted as a regular expression. If there are capture groups in the delimiter, matches are spliced into the result array, and non-matching captures are empty strings.
split("hello world", " ") = ["hello", "world"]
split("hello world", "\s") = ["hello", "world"]
split("hello there world", " ", 2) = ["hello", "there"]
split("hello there world", "(t?here)") = ["hello ", "there", " world"]
split("hello there world", "( )(x)?") = ["hello", " ", "", "there", " ", "", "world"]
// Postfix style.
"hello there world".split("( )(x)?") = ["hello", " ", "", "there", " ", "", "world"]
startswith(string, prefix)
Checks if a string starts with the given prefix.
startswith("yes", "ye") = true
startswith("path/to/something", "path/") = true
startswith("yes", "no") = false
// Postfix style.
"yes".startswith("no") = false
endswith(string, suffix)
Checks if a string ends with the given suffix.
endswith("yes", "es") = true
endswith("path/to/something", "something") = true
endswith("yes", "ye") = false
// Postfix style.
"yes".endswith("ye") = false