Transform Files
Transform files are used to customize how files are presented on the web interface.
Technically, their purpose is to set attribute values to files and directories.
For example, you can change the display name of a file by setting the name attribute.
Transform files are the tool PFS provides to allow customization of the web interface for various use cases.
A Transform file always refers to a file or directory.
The purpose of this Transform file is to set attributes for that file or directory.
All Transform files are located in the data volumes and should be placed next to the files they refer to.
If a directory contains a file named transform.txt, it will be executed as the Transform file for that directory.
If a file named <file>.transform.txt exists in the same directory as a file named <file>, it will be run as the Transform file for that file.
Basic Syntax
The purpose of a Transform file is to set attributes.
The basic syntax to set attributes of the file or directory that this Transform file refers to is <attribute> = <value>.
Each instruction in a Transform file must be on a separate line.
A Transform file can look like this:
Some attributes have a special meaning and change how files are displayed or behave. These attributes are described in the File Customizations documentation.
Setting Attributes for Files
It is also possible to set attributes for other files in the same directory. Files are referenced by writing their file name in single quotes.
For example, the following Transform file sets the display names of multiple files and hides file2.txt.
If file1.txt or file2.txt does not exist, the respective line will be ignored.
It is also possible to read attributes and use these values to set other attributes.
Comments
Lines in a Transform file that contain // outside of a string are treated as comments, and everything after that // is ignored.
Expressions
It is possible to use expressions to combine values.
Complex expressions can be constructed by combining values using operators.
The operator priority can be controlled by enclosing expressions in parentheses ().
Each operator can have a different effect depending on the data types.
Data Types
There are various data types for values.
Values can be combined using operators. The effect of an operator depends on the data types of the values.
String
Strings are text values enclosed in double quotes. For example, "Hello" is a string value.
If special characters like " or newlines (\n) need to be included in the string, they can be escaped using backslashes (\).
String values are immutable. That means a string cannot be modified. New strings have to be created instead.
Operators
If the + operator has at least one string value, the string representations of the values are concatenated.
value = "Hello" + "World" // "HelloWorld"
value = "Hello " + 2 // "Hello 2"
value = 2 + " Hello" // "2 Hello"
String Operations
Here are some operations that can be performed on strings:
string.length-
The length of the string.
Example:
"Test".lengthis4. string.uppercase-
The string in uppercase.
Example:
"Test".uppercaseis"TEST". string.lowercase-
The string in lowercase.
Example:
"Test".lowercaseis"test". string.trim-
Removes whitespace from the beginning and end of the string.
Example:
" Test ".trimis"Test". string.startsWith(string, index?)-
Checks whether the string begins with the given string. Optionally, you can specify the index where to start searching.
Examples:
"Test".startsWith("Te")istrue"Test".startsWith("e")isfalse"Test".startsWith("e", 1)istrue
string.endsWith(string)-
Checks whether the string ends with the given string.
Examples:
"Test".endsWith("Te")isfalse"Test".endsWith("st")istrue
string.slice(startIndex, endIndex?)-
Creates a substring from the start index (inclusive) to the end index (exclusive). If no end index is specified, the substring goes until the end of the original string.
Examples:
"Test".slice(1, 3)is"es""Test".slice(1)is"est"
string.indexOf(string, index?)-
Returns the start index of the first substring that begins with the given string. Optionally, you can specify the index where to start searching.
Examples:
"Hello".indexOf("l")is2"Hello".indexOf("l", 3)is3
string.lastIndexOf(string, index?)-
Returns the start index of the last substring that begins with the given string. Optionally, you can specify the index where to start searching.
Examples:
"Hello".lastIndexOf("l")is3"Hello".lastIndexOf("l", 2)is2
string.split(regex)-
Splits the string at each occurrence of the specified regex string. Returns an array containing the resulting substrings. The split operation behaves like Java's
String.split(regex)method.Example:
"Test".split("e")is["T", "st"]
Integer
Integers are numbers without a decimal point. For example, 123 is an integer value.
Integer values can be positive or negative.
Integer values are represented as 64-bit signed integers.
Operators
| Operator | Description | Example |
|---|---|---|
value + value |
Add | 4 + 5 is 9 |
value - value |
Subtract | 10 - 4 is 6 |
value * value |
Multiply | 5 * 4 is 20 |
value / value |
Divide | 10 / 5 is 2, 17 / 5 is 3 (integer division) |
value % value |
Modulo | 10 % 4 is 2 |
value ** value |
Pow | 2 ** 5 is 32 |
- value |
Negation | -(5) is -5 |
value < value |
Less than | 5 < 8 is true |
value <= value |
Less than or equal | 5 <= 8 is true |
value > value |
Greater than | 5 > 8 is false |
value >= value |
Greater than or equal | 5 >= 8 is false |
number++- Increments the variable
numberby1and returns the previous value. ++number- Increments the variable
numberby1and returns the new value. number--- Decrements the variable
numberby1and returns the previous value. --number- Decrements the variable
numberby1and returns the new value.
Float
Floats are numbers with a decimal point. For example, 3.14 is a float value.
Float values can be positive or negative.
Float values are represented as 64-bit floating-point numbers.
Syntactically, a float is different from an integer in that it has a decimal point.
Floats support the same numerical operators as integers. If floats and integers are used together in a numerical operator, the result is a float.
Boolean
Boolean values can be either true or false.
Array
Arrays are lists of values enclosed in square brackets. For example, [1, 2, 3] is an array containing 3 integer values.
Arrays can contain any data type.
If values are separated by a line break, no additional comma is needed.
With the new [number] syntax, an array with the specified length can be created.
This array contains null values at each position.
Array Operations
Here are some operations that can be performed on arrays:
array.length-
The length of the array.
Example:
[1, 3, 5].lengthis3. array.add(value)-
Adds an element to the end of the array. If the value itself is an array, all elements from the array are added.
Example:
array.slice(startIndex, endIndex?)-
Creates a copy of a subarray. The elements in the array are not copied, i.e., a shallow copy is created. The startIndex is inclusive and the endIndex is exclusive. If no endIndex is specified, the subarray ends at the end of the array. If the startIndex is greater than the endIndex, an empty array is returned.
Example:
array.map(function)-
Applies the specified function to all elements in the array and returns a new array that contains the return values.
Example:
array.filter(function)-
Applies the specified function to all elements in the array and returns a new array that contains all elements for which the boolean representation of the return value is
true.Example:
array.sort(comparatorFunction?)-
Returns an ascendingly sorted copy of the array. If a comparatorFunction is specified, it must meet the following requirements.
- The function must accept two parameters.
- If the first parameter is smaller than the second, the function should return a negative integer.
- If the first parameter is larger than the second, the function should return a positive integer.
- If both parameters are equal, the function should return 0.
If no comparatorFunction is specified, the elements are compared to each other on a best-effort basis.
Example:
array.reverse()-
Returns a reverse copy of the array, where the order of the elements is reversed.
Example:
array.join(separator?)-
Concatenates the string representations of the elements of the array and returns the result. Optionally, a separator can be specified, which separates the elements.
Example:
Object
Objects are collections of key-value pairs enclosed in double curly braces. Object values can contain any data type.
Entries are separated by commas. If entries are separated by a line break, the comma is optional.
object1 = {{name: "value", number: 456, hidden: true}}
object2 = {{
name: "value"
number: 456
hidden: true
}}
With the . operator, you can access a key-value pair. Both read and write access is possible.
The + operator merges the attributes of two objects. If both objects contain the same attribute, the value is taken from the second object. Note that the + operator creates a new object. The input objects are not modified.
The += operator works similarly to the + operator, but no new object is created. Instead, the attributes from the second object are written into the first object.
File
The Transform file of a directory can access the files in the directory by writing their name in single quotes.
If the file does not exist, the value will be null.
Files behave like Objects. Files have attributes that can be read and written. There are special attributes that have certain effects on the appearance or behavior of the files (see File Customizations).
this
The constant this always contains the file or directory to which the currently executed Transform file belongs.
Example: Both lines are equivalent as long as hidden is not defined as a variable.
filelist
The constant filelist contains an array with all files in the directory whose Transform file is currently being executed. If the Transform file of a file is being executed, filelist is an empty array.
Null
Null represents the absence of a value. It is represented as null.
If one operand of the + operator is null, the result is always the other operand, e.g., value + null = value.
When reading attributes of null, the result is always null. When writing attributes of null, nothing happens.
TextScript
TextScript is a scripting language that can be used to generate text.
It is possible to use a TextScript as a value by enclosing it in grave accents (`).
The TextScript is able to access attributes of the file it was assigned to.
An external variable is defined for each attribute of the file.
name = `@custom; @number;`
custom = "Name"
number = 123456
'file1.txt'.name = `@custom;`
'file1.txt'.custom = "File 1"
In this example, the name of this directory will be Name 123456 and the name of file1.txt will be File 1.
TextScript values are resolved to strings after the Transform files have been run.
TextScript values are also resolved when they are converted to a string.
This happens, for example, when a TextScript value is concatenated with a string using the + operator.
The external variables then have the value of the corresponding attribute at the time of resolution.
Multiline TextScript
It is possible to specify a multi-line TextScript. This must be enclosed with three grave accents.
Standard TextScript Functions
In addition to the functions that are available by default in the standard library of TextScript, there are some additional functions that can be used in TextScript values. These functions can only be used within a TextScript. These functions are not valid in other places in Transform Files. However, for some functions there are equivalents in the standard library for Transform Files.
escapeHTML(string)- Escapes all special characters to the
�syntax. Newlines are replaced by<br/>. Tabs and spaces may be replaced by or . toTimeString(int)-
Returns a string representation of the specified amount of seconds. The format of the resulting string is
h:mm:ss,mm:ss, orm:ssdepending on the amount of seconds.
Range
Ranges represent a range of numbers. There are integer ranges and float ranges. A range has a minimum and a maximum value. The range may contain parts of the values in between.
Ranges can be specified by using a tilde (~) between two numbers. These ranges contain all numbers between those two numbers.
For example, 10 ~ 20 represents the range of numbers from 10 to 20.
Multiple ranges can be combined by using the + operator. For example, 10~20 + 30~40 represents the range of numbers from 10 to 20 and from 30 to 40, but does not include 21 to 29.
Ranges can be converted to a string using the str() function.
The name will be 2005-2010, 2020-2023.
Function
Functions are values that contain instructions. A function can be called, which executes the instructions of the function.
Functions can have parameters. Parameters are named variables that can be used in a function. When calling the function, concrete values are specified for the parameters.
Functions are created with the keyword function, followed by the list of parameter names in parentheses. The instructions are written in curly braces.
A function can be called with the () operator. Values for the parameters must be specified in the parentheses.
Return
With the return statement, a function can return a value.
The return value is delivered to the caller of the function.
If a return statement is executed, the function call ends immediately. Instructions after the return are not executed anymore.
If a return is executed without a value, or the function ends without return, the function call returns the value null.
If a function consists of a single return statement, it can also be defined with the => syntax.
Combining Functions
Functions can be combined with all operators and all types of values. If a function is specified as an operand of an operator, the operator returns a function that executes this function and applies the operator to the return value and the other value. If both operands are functions, both functions are executed and the operator is applied to their return values. The resulting function requires the same parameters as the original function. If both values are functions, the parameters must match.
var add = function(a, b) => a + b
var multiply = function(a, b) => a * b
// equivalent to function(a, b) => (a + b) + 3
var addPlus3 = add + 3
// equivalent to function(a, b) => (a * b) - (a + b)
var func = multiply - add
Additional Operators
In addition to the datatype-dependent operators, there are also operators that can be applied to any values.
Equality Operators
The == operator compares two values and returns a boolean indicating whether they are equal. != checks whether the values are not equal.
5 == 3 // false
5 == 5 // true
3 == "3" // false
"example" == "example" // true
"a" == "b" // false
"a" != "b" // true
NOT Operator
!value applies a NOT operation to the boolean representation of value.
Boolean representation
The boolean representation of a value is false if and only if the value is null or the boolean value false.
The boolean representation is true for all non-null values, except false.
AND and OR Operators
The AND operator value1 && value2 returns value2 if the boolean representation of value1 is true.
Otherwise, value2 is not evaluated and value1 is returned.
If value1 and value2 are both boolean values, this behavior results in a logical AND operation.
true && false // false
false && true // false
null && "example" // null
"example" && null // null
0 && "example" // "example"
false && "example" // false
The OR operator value1 || value2 returns value1 if the boolean representation of value1 is true.
In this case, value2 is not evaluated.
Otherwise, value2 is evaluated and returned.
If value1 and value2 are boolean values, this behavior results in a logical OR operation.
true || true // true
false || true // true
false || "example" // "example"
"a" || "b" // "a"
null || "b" // "b"
Strict AND and OR Operators
The operators & and | behave identically to && and ||, but instead of the regular boolean representation, the strict boolean representation is used.
In the strict boolean representation, in addition to null and the boolean false, the following values evaluate to false:
- The integer
0resolves tofalse - The float
0.0resolves tofalse - Empty strings resolve to
false - Empty arrays resolve to
false - Empty ranges resolve to
false
true & "example" // "example"
false & "example" // false
"" & "example" // ""
0 & true // 0
null | "a" // "a"
0 | "a" // "a"
1 | "a" // 1
Combining Comparators
The | operator can be very useful when combining separate comparator functions for sorting.
A comparator function returns a positive integer if the first parameter is greater than the second parameter,
a negative integer if the first parameter is less than the second parameter and 0 if both parameters are equal.
Separate comparator functions, for example byName and byDate, can be combined as byName | -byDate, which returns a new comparator function that primarily sorts elements ascending by their name, and secondary by their date descending.
Type Checking Operator
With value is type, you can check whether a value has a certain type.
Valid types are null, int, float, boolean, string, textscript, object, array, int_range, float_range, and function.
Ternary Operator
cond ? value1 : value2 returns value1 if the boolean representation of cond is true.
Otherwise, value2 is returned.
Compound Assignment Operator
The += operator is a shorthand to combine the current value of a variable with a value using the + operator and write the result back into the variable.
Variables
With the var keyword, temporary variables can be declared. These variables can store values.
Variables vs. File attributes
If a value is set that was not previously declared as a variable, an attribute with the corresponding name is set for the current file. Attributes of files are stored in the main volume and can change the behavior of the files, while variables are deleted after the rebuild process ends.
Public Variables
By default, a variable is accessible in the Transform files of a file or directory.
If a variable is declared with the keyword public, it is also accessible from all Transform files of the files within the directory.
If a public variable is defined in the Transform file of a directory, it can be read by the Transform files of all files in that directory, but not written.
In particular, public variables can be used to define functions that can be called in the Transform files of files in a directory.
Example:
Control Statements
With control statements, the program flow can be changed.
if Statement
With an if statement, part of the program is only executed if a condition is true.
The condition must be written in parentheses. The condition is evaluated based on the boolean representation.
After an if block, an optional else block can be specified, which is only executed if the condition is false.
It is also possible to combine if and else to else if.
while Loop
A while loop is executed as long as a condition has the boolean representation true.
The break statement can be used to exit the loop before the condition is false.
The continue statement can be used to jump directly to the next iteration.
for Loop
The for loop works similarly to the while loop but offers a more compact syntax for declaration, condition, and increment.
With the for each loop, you can iterate over all values of an array.
As with the while loop, you can use break to exit the loop prematurely and continue to jump to the next iteration.
Command Syntax
The command syntax is an alternative for calling functions.
A command consists of a command name and multiple parameters, separated by spaces.
The command name is the function to call.
The basic syntax of a command is command_name parameter1 parameter2 ....
Each command has to be on a separate line.
Commands that don't exist are ignored.
History
In PFS 1.x, the command syntax was used for almost all operations that a Transform file can perform. Functions exist since PFS 2.x. Before that, commands were hard-coded operations.
Execution Order of Transform Files
Top-Down Transform Files
Top-Down Transform Files can be named transform.txt. These are executed before the Transform Files of the files within this directory.
Usually, data is set within Top-Down Transform Files. Also, Top-Down Transform Files are particularly well suited for defining public variables so that they are already defined when the inner Transform Files are executed.
Bottom-Up Transform Files
In contrast, Bottom-Up Transform Files are executed after the Transform Files of the files within this directory have been executed. Bottom-Up Transform Files must be named transform.bottomup.txt.
With Bottom-Up Transform Files, data from the inner files can be processed programmatically.
Example:
Hooks
Hooks are functions executed for all files and directories located in a directory. Hooks are also executed for deeply nested files.
The hook is executed in the context of the respective file, i.e., this and filelist refer to the respective file or directory for which the hook is currently being executed.
In general, hooks should be defined in Top-Down Transform Files so that they are defined before the Transform Files of the inner files are executed. Defining hooks in Bottom-Up Transform Files or other hooks can lead to undesirable behavior.
Top-Down Hooks
Top-Down Hooks are executed before the Top-Down Transform Files of a folder or file are executed. Accordingly, a top-down hook is not run for the folder that defines the hook.
A top-down hook is registered using the function topdownHook(function).
Example:
This example results in the following names.
| File | Name | Reason |
|---|---|---|
dir/ |
dir | Top-down hook is not run for the defining folder |
o/ |
o | o is not inside dir |
dir/file.txt |
File: file.txt | Hook is run for files |
dir/x/ |
File: x | Hook is run for folders |
dir/x/y.txt |
File: y.txt | Hook is run for nested files |
dir/special.txt |
Special Name | Top-Down Transform File overwrites the name |
Bottom-Up Hooks
Bottom-Up Hooks are executed after the Bottom-Up Transform Files of a file or directory have been executed. Specifically, bottom-up hooks are run for directories after all Transform Files of all files in this folder have been executed. Bottom-up hooks are also executed for the directory itself that defines the hook.
A bottom-up hook is registered using the function bottomupHook(function).
Example:
This example results in the following names.
| File | Name | Reason |
|---|---|---|
dir/ |
"File: dir" | Bottom-up hook is run for the defining folder |
o/ |
o | o is not inside dir |
dir/file.txt |
File: file.txt | Hook is run for files |
dir/x/ |
File: x | Hook is run for folders |
dir/x/y.txt |
File: y.txt | Hook is run for nested files |
dir/special.txt |
File: Special Name | Bottom-up hook is run after the Transform File |
Standard Library
There are some functions and constants that are available by default.
str(value)- Converts the value into its string representation and returns it.
If the value is
null,nullis returned. toInt(value)- Converts the value into an integer and returns it.
If the value is a string, the string is parsed to an integer.
If the value is
null,nullis returned. toFloat(value)- Converts the value into a float and returns it.
If the value is a string, the string is parsed to a float.
If the value is
null,nullis returned. file(string|file)-
If a string is specified, the file with the specified name is returned.
file("name")is equivalent to the single-quote syntax to get the file:'name'. If no file with the specified name exists,nullis returned. It is not possible to access deeply nested files using this function. If a file value is specified, this file value is returned. Ifnullis specified,nullis returned.This function is useful for dynamically retrieving file objects with non-constant names. For example,
file("file_" + num + ".txt"). filename(file|string)- Returns the filename of the specified file. The filename is the part of the name before the last
., or the full name if the name does not contain a.. For example, the filename ofexample.txtisexample, the filename oftest.file.txtistest.file, and the filename oftestistest. If a string is specified, the string is interpreted as the full name. It doesn't matter whether the file exists or not. Ifnullis specified,nullis returned. fileext(file|string)- Returns the extension of the specified file. The extension is the part of the name after the last
., or the full name if the name does not contain a.. For example, the extension ofexample.txtistxt, and the extension oftestistest. If a string is specified, the string is interpreted as the full name. It doesn't matter whether the file exists or not. Ifnullis specified,nullis returned. readfile(file|string)-
Reads the specified text file and returns the file's content as a string. The charset of the file is assumed to be UTF-8. The file may be specified by its name. If the file does not exist or is a directory,
nullis returned.This function is useful for outsourcing larger amounts of data from the Transform File to other files. For example,
this.info = readfile('info.txt')outsources the description to aninfo.txtfile. escapeHTML(string)- Escapes all special characters to the
�syntax. Newlines are replaced by<br/>. Tabs and spaces may be replaced by or . Ifnullis specified,nullis returned. toTimeString(int)- Returns a string representation of the specified number of seconds.
The format of the resulting string is
h:mm:ss,mm:ss, orm:ss, depending on the number of seconds. For example,toTimeString(100)returns"1:40". Ifnullis specified,nullis returned. parseTimeString(string)- The reverse function of
toTimeString. Accepts a duration as a string in the formath:mm:ss,mm:ss, orm:ss, and returns the number of seconds. For example,parseTimeString("1:40")returns100. Ifnullis specified,nullis returned.
Logging
For debugging, the function debug(value) should be used. It outputs the specified value together with the type of the value as a debug message in the console during the rebuild process.
This requires the TomatenHTTP server to be configured to log debug messages.
To log data, use the log(logFileName, string) function. This saves the log entries in a log file located in the main volume instead of outputting them in the console. For this purpose, a line with the specified string is appended to the file with the specified name in the logs folder of the main volume.
Note that each call to the log function adds a line to the log file.
When collecting statistics, it is often desirable to generate only one line per rebuild process.
In this case, it is recommended to call the log function in a transform.bottomup.txt in the root folder.
As with all functions, debug and log can also be called with the command syntax.
Sorting
By default, the file list of a directory is sorted on a best-effort basis. Among other things, the name of the file is considered, as well as whether it is a folder or a file.
If a custom sorting is desired, the current filelist can be sorted using the sort(comparator) function. The sorting happens in-place and has a direct effect on the filelist variable, as well as the order in which files are displayed in the UI.
The sort function expects a comparator function that takes two files to compare. The return value of the comparator function is an integer that indicates which of the two files should be before the other.
- A negative integer means the first file should be before the second file.
- A positive integer means the first file should be after the second file.
0means the order of the two files does not matter.
This comparator function can be implemented arbitrarily. There are some helper functions to easily describe custom criteria for sorting.
compare(value1, value2)- A helper function that compares two arbitrary values based on their natural order.
For example,
compare(5, 7)returns a negative integer, andcompare("B", "A")returns a positive integer. Thecomparefunction cannot be used to compare objects, arrays, functions, or files, as they do not have a defined natural order. byDefault- A comparator function for files that implements the default sorting of files.
If a comparator is implemented that does not specify a unique sort order, it is recommended to call
byDefault(file1, file2)as the last comparison criteria. byAsc(attributeName|function)- A function that generates and returns a comparator function for files or objects.
If an attribute name is specified as a string,
byAscreturns a comparator function that compares the given attribute for both objects or files based on their natural order, resulting in an ascending sort order. If a function is specified, this function is applied to the objects or files to be compared to obtain the respective values, which are then compared based on their natural order. Therefore,byAsc("attribute")is equivalent tobyAsc(function (obj) => obj.attribute). byDsc(attributeName|function)- As a counterpart to
byAsc, this function compares the values in such a way that a descending sort order is created.
byAsc and byDsc can be used in combination with Combining Functions and the | operator (strict OR) to define custom comparator functions.
For example, byDsc("size") | byAsc("example") | byDefault results in a comparator function for files that sorts first by file size in descending order, then by the attr attribute in ascending order, and if both were identical, by the default sort order.
Mathematical Functions
| Usage | Description |
|---|---|
PI |
The constant PI, approximately 3.14 |
sin(float) |
Sine of a radian angle |
cos(float) |
Cosine of a radian angle |
tan(float) |
Tangent of a radian angle |
asin(float) |
Arcsine |
acos(float) |
Arccosine |
atan(float) |
Arctangent |
exp(float) |
e^x |
floor(float) |
Largest float that is less than or equal to the specified value and equal to an integer |
ceil(float) |
Smallest float that is greater than or equal to the specified value and equal to an integer |
sqrt(float) |
Square root |
log10(float) |
Base 10 logarithm |
ln(float) |
Natural logarithm (base e) |