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:

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':
Some example delta strings:
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:
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:

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