KoalaRainbow Query Language :: Functions
Disclaimer: Functions is perhaps the most likely place for
expansion in KRQL. No doubt as you read through the list of these
functions you can probably think of 5 times as many functions that you
would also like to have (like sorting, although that could go in the
KRQL engine itself). This is the first release of KoalaRainbow,
so rest assured that more will be coming. The system has been
designed to allow other plugins to register functions, but the
interface for doing so has not yet been stabilized or tested, so if
anyone does create any functions, they may need to be updated before
they will work in subsequent versions. Also, one might say that
the simplest thing would be to allow a function that would call perl's
eval. I doubt I will ever do that as that seems rather dangerous,
hard to debug, and rather defeats the purpose of a lot of this.
Math Functions
Coercion
int(val-to-coerce)
This function is mainly to force undef values and empty strings to turn
into 0. ALL OTHER MATH FUNCTIONS ALREADY HANDLE THE UNDEF
CASE. The problem is if you're not using a math function, and the
value needs to go to some other code, it's possible that code will
become unhappy and output warnings if you don't do this. In
summary, this function is basically a hack.
Simple, Simple, Simple Math
(Arguably these do not need to be separate, but hey.)
add(a, b, ...)
a + b.
sub(a, b, ...)
a - b.
mul(a, b, ...)
a * b.
div(a, b)
a / b.
Range Support
min(a, b, ...)
Returns the smallest of the supplied arguments. You must provide
at least one argument, but there is no maximum number of arguments.
min(1, 2) => 1
min(-1, 1) => -1
min(5, 4, 5) => 4
max(a, b, ...)
Returns the largest of the supplied arguments. You must provide
at least one argument, but there is no maximum number of arguments.
max(1, 2) => 2
max(-1, 1) => 1
max(5, 4, 5) => 5
clamp(user-value, min-value, max-value)
Takes a user value, and a min and max value defining a valid
range. If the user value is less than the min value, the min
value is returned. If the user value is greater than the max
value, the max value is returned. Otherwise, the user value is
returned.
clamp(5, 0, 10) => 5
clamp(-3, 1, 5) => 1
clamp(99, 0, 10) => 10
lerp(user-domain-value, user-domain-min, user-domain-max,
output-domain-min, output-domain-max)
Takes a user-domain value, clamps it to the provided user-domain range,
then scales the value from the user-domain range to the output-domain
range.
lerp($foo, 0, 5, 10, 60), for $foo values of:
-1 => 10
0 => 10
1 => 20
4 => 50
5 => 60
6 => 60
Color Functions
Create Colors
color(some-color-name) OR color(some-hex-color-code)
Takes as string-constant that is the name of a color known to the
rendering engine, or a hex-color code prefixed by the # sign.
color('red')
color('#ff0000')
color('aquamarine')
color('#7fffd4')
rgb(red-part, green-part, blue-part)
Creates a color from three numeric components constituting the red,
green, and blue components of the color.
rgb(255, 0, 0)
rgb(127, 255, 212)
hsv(hue-part, saturation-part, value-part)
Creates a color from three numeric components constituting the hue,
saturation, and "value" components of the color.
gray(level) or grey(level)
Generates a grey color using the provided level. This is
equivalent to calling rgb with all 3 arguments being the value you pass
for level.
String Functions
There's no end to the amount of neat string processing that could be
done. But we've got two functions. Doh. See
disclaimer up top.
len(string)
Returns the number of characters in the given string.
concat(string-a, string-b, ...)
Concatenates all the provided strings together. No whitespace is
inserted, so be sure to include that yourself if you want whitespace.
Date Functions
Okay, so it is important that you know that the actual date objects we
throw around here are the same string timestamp objects MovableType
throws around everywhere. These timestamps have the format
'YYYYMMDDhhmmss'. Example, June 3th, 2004 at 6:17pm and 19
seconds would be: '20040603181719'. These are just strings, but
do observe that we're using 24 hour time. If you want to use a
specific date, just use a string literal. Note that date ranges
are an entirely different bucket of pickles; but we have a date_range
function for that purpose.
Date Creation
now()
Returns a timestamp representing the current time.
Now-Relative Date Comparison Functions
minutes_old(date-to-see-how-many-minutes-old-it-is)
Returns the number of minutes since the date in question. (Aka
now - argument represented in minutes)
hours_old(date-to-see-how-many-hours-old-it-is)
Returns the number of hours since the date in question. (Aka now -
argument represented in hours)
days_old(date-to-see-how-many-days-old-it-is)
Returns the number of days since the date in question (Aka now -
argument represented in days)
Date Processing
date_thresh(date, threshold-unit-which-is-a-string)
This function is equivalent to floor() for floating point values, but
instead for dates and where you can specify which unit it should
'floor' to. For example, if you have a date Jun 6, 2004, 12:41pm,
thresholding to the day gives you Jun 6, 2004, 12:00am. Likewise,
thresholding to the month gives you Jun 1, 2004, 12:00am. And so
on. The point of this function is to make it easier create
intuitive date ranges and bin data into dates effectively. Note
that in most cases you will want to use date_range or date_range_set,
which integrate this functionality into more complex tasks.
The supported threshold units and their behaviors are:
- 'y': Year. Returns a date at 12:00am on Jan 1st of the
given date's year.
- 'mo': Month. Returns a date at 12:00am on the 1st of the
given date's month.
- 'w': Week. Returns a date at 12:00am on the sunday prior to the
given date... or whatever day of the week MT thinks the week starts on.
- 'd': Day. Returns a date at 12:00am on the given date.
- 'h': Hours. Returns a date at the top of the given date's
hour.
- 'm': Minutes. Returns a date at the top of the given date's
minute.
- anything else just leaves things as they are.
date_manip(date, delta-to-apply-in-custom-string-format)
This function takes the provided date, applies the delta specified in
the second argument and then returns the new date. The function
takes special care to compensate for leap years and what have you.
The manipulation string is made up of one or more of the following
tokens preceded by a number (positive) or negative. The tokens
must be sequenced in the order you see here, but you only include those
you need. Note that just include a token without a number does
nothing. If you want 1 day, you must say '1d', not just 'd':
- 'y': A year.
- 'mo': A month
- 'w': A week, in the 7 days sense. This doesn't have the
alignment semantics that it does in thresholding, multiplying the value
by 7 and using days would achieve the same effect.
- 'd': A day
- 'h': An hour
- 'm': A minute
- 's': A second
Some example delta strings:
- '1d': Returns the date one day after the given date.
- '-1s': Returns the date corresponding to the second imemdiately
before the given date.
- '1d-1s': Returns the date 23h59m59s after the given date.
- '1y1mo1d': Returns the date 1 year, 1 month, and 1 day before the
given date.
To reiterate, you need to maintain the ordering in the list;
'1s1y' is not a valid manipulation string. Invalid manipulation
strings are equivalent to empty strings, which will return the date you
passed in. That might not be the end of the world, but it's
certainly not what you want.
Date Ranges
dates_to_range(start-date, end-date)
Creates an inclusive date range using the given start-date and end-date.
date_in_rage(test-date, date-range)
Returns true if the test-date falls on either of the provided dates or
in the range between them.
date_range(date, thresholding, delta, [tweak_delta='-1s'])
Creates a date range by taking the provided date and thresholding it to
create the start date in the date range. The end date is then
created by applying the delta manip-string, which is followed by
applying the tweak_delta manip-string. Tweak delta is optional
and defaults to -1s if not specified. The tweak_delta is required
because date-ranges are inclusive, and if you don't tweak the end date
you run the very good chance of having two date ranges that are
intended to be distinct overlap at midnight. For example, if you
use date_range to give you a date_range covering today (by using
date_range(now(), 'd', '1d')) and there was no tweak, it would include
midnight today and midnight tomorrow. This is okay until you get
a date range for tomorrow. If for some reason you don't want the
value tweaked, just pass an empty string for that.
The thresholding argument should be as described in date_thresh, and
delta and tweak_delta should be as described in date_manip. For
sanity purposes, you probably want to avoid using negative deltas, but
date_in_range is smart enough to handle the case where the end date
actually is the start date, so it's safe. (At some point this
method will normalize its output too.) Note that if the most
significant part of the delta is negative, the tweak_delta becomes
positive 1 second.)
Examples:
date_range(now(), 'd', '1d') # Today
date_range(now(), 'd', '1w') # One full week starting today.
date_range(now(), 'h', '24h') # A 24 hour range starting with the current hour
date_range(now(), 'd', '-7d') # A one week range ending with midnight today.
# (The effective start date is then actually 00:00:01 am...)
date_range_set(date, thresholding, delta, num_in_set, set_delta,
[tweak_delta='-1s'])
This function is an expanded version of date_range that returns a set
of date ranges. This is most useful when you need to 'bin' a set
of records by their creation or modification date.
The function logic works like date_range for each date_range in the
set. The difference is how we modify the start_date as we create
each new date range. Specifically for all num_in_set date ranges
that we create, we apply set_delta to the previous start_date
calculated to get our new start date. Tweak_delta is of critical
importance here. Note that it only applies to the end date in the
date range, and does not have any accumulating effect on the date
ranges.
Examples:
date_range_set(date_manip(now(), '-6d'), 'd', '1d', 7, '1d') # returns a range set containing the past 7 days (each day is a range)
Date Printing
fmtdate(format_string, some_timestamp)
This function just calls Movable Type's date formatting routine
(format_ts). Please consult the MT manual entry on
date tag formats for info on valid format strings.
Set Functions
Set Functions
count([set])
Returns the number of records in the provided set, or if none is
provided, the number of records in the current/context set.
flatten(set)
Returns a new set in which all the items that were lists have been
spliced into the list. So if the contents of the set were [1, [2,
3, 4], 5], the resulting set is [1, 2, 3, 4, 5]. This is not a
recursive behavior. This is intended for dealing with MT object
helper functions that return a list of things, such as the list of
categories that an entry is associated with. The set logic does
not automatically flatten these lists because it is very conceivable
that people might want to deal with sets as sets.
val_in_set(value, set)
Returns 1 if the given value is present in the given set, 0
otherwise. The value may be a constant or it may be a
singleton-set.
Set Context Functions
These functions rely on the current record in the current set to do
their magic.
self()
This function is described a little bit in the KRQL
documentation. Basically, it is a hack to return the 'current
set' in which the function is evaluated. This solves the problem
where you have a data type that is the current set, but it's not
something that has any edges, it's an actual data-type, and you want to
get at it. Ex, given the set (1, 2, 3) stored in $foo, if we had
a constraint on foo, the 'current set' in the constraint would be the
item that we are currently testing (ex: 1, then 2, then 3). These
numbers have no edges to evaluate, so if you use self(), you can get
that value and then do a test. At some point this function may
become obsolete if the KRQL syntax is expanded to have a magic thing to
represent self, such as '.', like I think XPath might have, but I
forget.
This function is nondeterministic, making it ineligible for partial
evaluation, thereby also precluding constraint propagation of any
clauses depending on its value.
parent([num-levels-to-climb=1])
Along the same lines of self, parent will climb the hierarchy of set
contexts, returning the one the specified number of steps up. In
a constraint, parent(0) would return the current set containing the
object we are now evaluating; in other words "parent(0)/foo" is the
same as "foo". parent(1) would return the entire set that our
constraint is filtering. parent(3) would return the set context
preceding whatever set the constraint is filtering. Kee
Let us use, for example, the following real world query (which was
later optimized to no longer exist in such a form because it took an
absurd amount of time owing to its need to filter through the global ):
$months_comments[/entries[id = parent(2)/entry_id]/category/id = $category/id]
This point of the query is to find all of the comments in the set
$months_comments that were made to posts in the category
$category. Translating it to nested english, we get:
- $months_comments[...]: Given a set of comments, $months_comments,
take only the comments where:
- /entries[...]/category/id = $category/id: The entry associated
with the comment (see below) is in the same category as our category.
- /entries[id = parent(2)/entry_id]:
Find the entry whose id is the same as the entry_id of the category in
which this snippet is being evaluated. If this were just
parent(1), it would evaluate to the set of all entries
This function is nondeterministic, making it ineligible for partial
evaluation, thereby also precluding constraint propagation of any
clauses depending on its value.
position([item, set])
If no arguments are provided, it returns the zero-based index of the
current record in the current
set. (Name chosen to match XPath.)
If arguments are provided, you must provide an item (which may be an
actual data point, like a string, or a set whose first object will be
used), and a set that you know that the item is in. This function
will then return the position of the item in the provided set.
position_percent()
If no arguments are provided, returns a percentage in the range (0.0,
1.0] of the 'distance' the
current record is through the current set. The goal is to evenly
space the values returned by this function mod 1.0, so 0.0 will never
be returned. This function is equivalent to div(add(position(),
1), count()). In a set with 1 record, the call would return
1.0. In a set with 2 records, 0.5 would be returned for the first
record, and 1.0 for the second.
If arguments are provided, you must provide an item (which may be an
actual data point, like a string, or a set whose first object will be
used), and a set that you know that the item is in. This function
will then return a percentage like in the no-argument case, but for the
provided item and set.
position_percent_zero()
Same deal as position_percent, but returns a percentage in the range
[0.0, 1.0). In other words, we are using a zero-based index when
dividing into the total count, rather than a 1-based index.
MT Interaction Support
mtenv(key-name, [put-non-arrays-in-set=1])
Retrieves an value from the MovableType Template::Context stash, and
returns it. This is intended to be used to retrieve values from
template in which the query is embedded, most specifically, to get the
values from 'entries', 'entry', or 'blog'. If the thing retrieved
is
an array, it will be automatically wrapped in a KRQL set object so that
it can be
used like any other set retrieved. If the thing is not an array,
it
will be put in a singleton set if put-non-arrays-in-set is true (which
it is by default), otherwise it is returned verbatim.
In practical terms, if you want:
- entries: use "mtenv('entries')"
- entry: use "mtenv('entry')"
- blog: use "mtenv('blog')"
mtdirify(some-string)
This function wraps a call to MT::Util's dirify function. This
function is important because it is the mechanism by which MT maps
arbitrary strings into short strings suitable for use in a
filesystem. Note that in the cases of entries, one should really
be using the 'basename' field/accessor if you want to know a good
filename for it, as that has already been mangled to avoid
collisions. However, in the cases of categories and the like,
dirify is still the thing to use.
Copyright 2004, Andrew Sutherland