Version 0.9
Pinaret is a static website generator. It's based on a file format for defining text blocks, which is integrated with a language that serves both to interpolate and transform strings, and to script the generation of variants. Features:
These instructions assume you've downloaded and unzipped pinaret-0.9.zip.
PATH
environment variable.pinaret-0.9
directory to a permanent
location.pinaret
subdirectory of the
pinaret-0.9
directory and run the
command:raco pkg install
PATH
environment
variable.Pinaret is maintained and was developed by Josep Portella Florit. Future updates will be distributed through Pinaret's home page. Backwards compatibility is not guaranteed at this stage.
Copyright © 2016 Josep Portella Florit
Pinaret (software and manual) is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program in a file named LICENSE.html
. If
not, see the GNU
website.
Pinaret uses dictionary files, which are UTF-8 encoded text files
with the .dict
extension, and contain zero or more
key/value pairs.
A key/value pair starts with a |
character at the
beginning of a line, but the |
character may be omitted
on the first key/value pair. The key starts with the first character
that is neither whitespace nor |
, and ends at the first
whitespace character. The next character is part of the value, which
continues until a |
character is found at the beginning
of a line or until the file ends. This is an example with two
key/value pairs:
|single This is a single line value |multi This is a multi-line value
At the beginning of a line inside a value, the \
character is discarded if it's followed by the |
or
\
characters.
Different key/value pairs in the same dictionary file can't have
the same key, except if the key is #
; any key/value pairs
with #
as the key are ignored.
Values are procedures that can be called by its key and return a
string. Values may have the result of procedure calls interpolated.
A interpolation starts with the $
character, followed by
the procedure name enclosed within {
and }
.
Example:
|who World |message Hello ${who}
In this example, calling the procedure message
will
return the string Hello World
.
Whitespace characters between {
, the procedure name,
and }
are ignored. If a value must contain
${
literally, you can escape it as $\{
,
which in turn can be escaped as $\\{
and so on.
There are predefined procedures, like random
, which
returns the string of a possibly different random number on each
invocation. Procedures can receive arguments, which is mostly useful
for calling predefined procedures, but procedures defined in
dictionary files can receive arguments too, however in that case the
arguments don't correspond to any formal parameters, but are accessed
through special procedures named local
and
locals
. To pass an argument to a procedure, it must be
separated from the procedure name or other arguments by whitespace
characters, for example, ${A B C}
calls the
A
procedure with B
and C
as
arguments.
If some special character, like whitespace characters, or the
procedure application characters ({}
), must be part of an
argument or a procedure name, you must escape it with the
\
character, which in turn must also be escaped by itself
to avoid its special meaning. Alternatively, by enclosing a sequence
of characters in quotes ('
, "
or
`
) you can disable the special treatment of the
whitespace characters within the quotes. The same quote character
used to mark the beginning must be used to mark the end. Quotes must
also be escaped with the \
character to prevent their
special meaning, but within quotes you only need to escape the
character that was used to start quoting. For example, ${A B'
"C" D'}
is equivalent to ${A "B \"C\" "D}
; both
call a procedure named A
with a single argument B
"C" D
.
Procedure calls can also be interpolated inside other procedure
calls, but the $
character must be omitted for nested
procedure calls. For example, ${A{B}C}
will concatenate
A
, the result of calling B
, and
C
, and call the resulting string. If a string returned
by a nested procedure call includes a whitespace character or any
special character, it won't have any special meaning. Whitespace
characters retain their special meaning inside nested procedure calls
which are enclosed in quotes, and the same quote character that
encloses the call may be used again inside the nested call. For
example, ${"{A "B C"}"}
is equivalent to ${{A "B
C"}}
, both will call the procedure A
with a single
argument B C
, and call the procedure named as the
resulting string.
The arguments are passed unevaluated to procedures. Most procedures always evaluate their arguments, usually from left to right, however a procedure may not evaluate an argument, or may evaluate it multiple times; this allows a procedure to act as a control structure.
A procedure application that includes a |
character
after the procedure name or after an argument, is transformed by
moving the procedure name and its arguments until the |
to a nested procedure application following the first argument after
the |
, for example, {A B | C D}
is
transformed to {C {A B} D}
. There doesn't need to be any
whitespace characters around the |
for it to be
effective. The special meaning of a |
inside a procedure
application can be avoided by escaping it with \
or by
enclosing it in quotes.
When a predefined procedure receives a file or directory path, it avoids directory traversal attacks by signaling an error when necessary.
Because evaluated arguments and return values are always strings, the predefined procedures follow a set of conventions when they need to receive or return Booleans, numbers, sequences or regular expressions.
The Boolean true value corresponds to the true
string,
and the Boolean false value corresponds to the false
string. When a predefined procedure expects a Boolean, it signals an
error if the provided string isn't a valid Boolean.
Numbers can be expressed like in Racket. Refer to the Racket documentation for more information. When a predefined procedure expects a number, it signals an error if the provided string isn't a valid number.
To process a sequence, a predefined procedure like
join
or for-each
temporarily sets up an
element processing coroutine. The coroutine can then be called with
the yield
procedure; a sequence is the effect of calling
yield
zero or more times. By convention, a procedure
that is documented to produce a sequence, also returns an empty
string, although some procedures may not return an empty string, but
something more useful; in that case, it's specified in the
documentation of the procedure.
Some predefined procedures expect regular expressions. The syntax is the same as Racket's pregexp syntax; refer to the Racket documentation for more information. When a predefined procedure expects a regular expression, it signals an error if the provided string isn't a valid regular expression.
Pinaret includes the commands pinaret-init
,
pinaret
and pinaret-repl
. All Pinaret
commands accept the following options:
--version
, -v
--help
, -h
pinaret-init
With the pinaret-init
command you can create and
initialize a directory for a new project. You must pass the name of
the directory to be created as the only argument, for example:
pinaret-init my-project
pinaret
With the pinaret
command you can generate files
according to the files in the input
directory of your
project. You must run the command in your project's directory, with
no arguments or with a combination of the following options:
--quiet
, -q
--dummy
, -d
--loop
, -l
--generator <g>
, -g <g>
<g>
. This
option is allowed multiple times. The input
directory
and the .dict
extension must be omitted in
<g>
.--input <i>
, -i <i>
<i>
instead of ${input}
.
Example:--input '${input | take 1}'
--repl-on-error
, -r
pinaret-repl
command) to ease debugging.The files in the input
directory (and its
subdirectories) are either static files or generators, which are
dictionary files. The static files are copied into the
output
directory (or its corresponding subdirectory)
unless it's determined that they should be omitted. By default no
files are omitted, but this can be customized by setting the
omit-file?
parameter in the custom.rkt
file.
A static file that was already copied into the output
directory, won't be copied again until the input file is modified.
The library
directory and its subdirectories may
contain dictionary files. The procedures defined in those dictionary
files extend the set of predefined procedures. Although procedures
defined in the library may shadow predefined procedures, a library
dictionary file can't redefine a procedure defined in another library
dictionary file.
For each generator in the input
directory and its
subdirectories (or just those specified with the
--generator
command line option), the procedures defined
in them extend the set of library and predefined procedures. Then, if
a procedure named input
is defined, it's called expecting
it to produce a sequence (unless the --input
command line
option was provided, in which case the option value will be evaluated
instead to obtain the sequence). For each element in the resulting
sequence (or once, if no procedure named input
is defined
and --input
was not provided), if a procedure named
output
is defined, it's called to obtain the contents of
the output file, otherwise an error is signaled.
If a procedure named path
is defined, it's called to
obtain the path of the output file; if it isn't defined, the path of
the output file is the path of the generator excluding the
input
directory and the .dict
extension.
Obviously, when the input
procedure or the
--input
option value produces a sequence with more than
one element, you'll want a path
procedure that calls the
procedures introduced by the sequence, otherwise you'll end up with
the same output file being overwritten for each element in the
sequence. For example, this generator produces the files
0.txt
, 1.txt
and 2.txt
:
|input ${range i 3} |path ${i}.txt |output
The output file path may be either absolute or relative; anyways
it'll be appended to the output
directory to obtain the
final path. If the output file path ends with a directory separator,
if a procedure named index-file
is defined, it's called
to obtain a file name which will be appended to the output path;
otherwise an error is signaled. Directory traversal attacks are
avoided by signaling an error when necessary.
During the call to the output
procedure, the
write
procedure may be used to write directly to the
output file. You'll rarely need to use this procedure, but it's
useful if you need to write enormous files that don't fit in RAM.
Finally, the string returned by the output
procedure will
be appended to the output file.
By default, the output file will be encoded using UTF-8. If you
define a procedure named bytes?
that returns
true
, the Unicode characters will be encoded using
Latin-1, one byte per character, and an error will be signaled if
there is a character with a code point greater than 255.
You can set the output file permissions if you define a procedure
named mode
that returns a number, which may be in octal
notation, for example, #o755
.
When an error is signaled during file generation, the file
generation is halted and the error message is shown. If the
--repl-on-error
command line option was provided, the
read-eval-print-loop prompt is displayed; otherwise, or after the
interaction with the REPL is finished, the program either finishes
with an exit status of 1 or waits for files to change if the
--loop
command line option was provided.
Unless the --loop
command line option was provided,
the program finishes with an exit status of 0 when there are no more
files to generate.
With the --loop
option, any change to library
dictionary files or input files triggers the file generation process.
You may want to customize ignore-file?
to ignore certain
static files.
pinaret-repl
With the pinaret-repl
(read-eval-print-loop) command
you can test the procedures defined in your project's library and
generators. You must run the command in your project's directory.
When run with no arguments, pinaret-repl
shows the
documentation of the help
procedure (which can be
inhibited with the --quiet
/ -q
option),
displays a prompt (>
) and waits for input. As each
input line is read, it's evaluated, the result is printed and the
prompt is shown again.
If a line can't be evaluated because a procedure application isn't
finished, a different prompt is displayed (|
). When the
next line is read, it's appended to the previous unevaluated line
before evaluation is tried.
When an error is signaled during evaluation, the error message is shown, and the prompt is displayed again.
To try the procedures defined in the generator dictionary files,
you can use the extern
procedure.
If the library or generator dictionary files are modified while running the program, they'll be reloaded automatically.
The program finishes when there is no more input.
help
Procedure{help P}
returns the documentation of the procedure P
.
Example: ${help procedures}
procedures
Procedure{procedures}
returns the list of defined procedures separated by
newlines. {procedures P}
iterates the list of defined procedures; for
each procedure, temporarily defines a procedure named P
that returns
the name of the current procedure, and calls {yield}
. For example,
${procedures}
returns the same as:
${procedures p | join {p} {char 10}}
source
Procedure{source P}
returns the location of the definition of the procedure P
.
version
Procedure{version}
returns the Pinaret version.
case
Procedure{case A K V}
returns V
if A
is the same string as K
; otherwise,
signals an error. {case A K V D}
returns D
instead of signaling an
error. {case A K1 V1 K2 V2 ...}
returns V1
if A
is the same string as K1
; otherwise, is equivalent to {case A K2 V2 ...}
.
if
Procedure{if B C A}
returns C
if B
is true
; if it's false
, returns A
. {if B C}
is equivalent to {if B C ''}
.
and
Procedure{and}
returns true
. {and A}
returns true
if A
is true
; if it's
false
, returns false
. {and A1 A2 ... An}
is equivalent to {and A2
... An}
if A1
is true
; if it's false
, returns false
.
not
Procedure{not A}
returns false
if A
is true
; if it's false
, returns true
.
or
Procedure{or}
returns false
. {or A}
returns true
if A
is true
; if it's
false
, returns false
. {or A1 A2 ... An}
is equivalent to {or A2
... An}
if A1
is false
; if it's true
, returns true
.
#
Procedure{# ...}
ignores its arguments and returns an empty string. It may
be used to comment code out.
defined?
Procedure{defined? P}
returns true
if the procedure P
is defined, otherwise
returns false
.
extended
Procedure{extended A P1 V1 ... Pn Vn}
temporarily defines the procedures
named P1
... Pn
, which return the corresponding and previously
evaluated V1
... Vn
, and returns A
. Example:
${extended {+ {a} {b}} a 1 b 2}
extended*
Procedure{extended* A P1 V1 ... Pn Vn}
temporarily defines the procedures
named P1
... Pn
, which return the corresponding V1
... Vn
, and returns A
. Example: ${extended* {+ {a} {b}} a 2 b {* {a} 2}}
char
Procedure{char A}
returns the character with ordinal A
.
{char A1 A2 ... An}
is equivalent to {char A1}
{char A2 ... An}
.
Example: ${char 72 69 76 76 79}
downcase
Procedure{downcase A}
returns the downcase conversion of A
.
normalize-nfc
Procedure{normalize-nfc A}
returns the Unicode normalized form C
of A
.
normalize-nfd
Procedure{normalize-nfd A}
returns the Unicode normalized form D
of A
.
normalize-nfkc
Procedure{normalize-nfkc A}
returns the Unicode normalized form KC
of A
.
normalize-nfkd
Procedure{normalize-nfkd A}
returns the Unicode normalized form KD
of A
.
ord
Procedure{ord A}
returns the integer ordinal of the one-character string A
.
pregexp-match?
Procedure{pregexp-match? A R}
returns true
if A
matches the regular
expression R
; otherwise returns false
.
pregexp-replace
Procedure{pregexp-replace A R I}
returns A
with all the occurrences of the
regular expression R
replaced by I
. {pregexp-replace A P R I}
temporarily defines a procedure named P
before evaluating I
for each
match. {P 0}
returns the match substring. {P}
is equivalent to {P
0}
. {P N}
, where N
is greater than 0, returns the match group number N
.
pregexp-replace*
Procedure{pregexp-replace* A R I}
returns A
with the first occurrence of the
regular expression R
replaced by I
. {pregexp-replace* A P R I}
temporarily defines a procedure named P
before evaluating I
. {P 0}
returns the match substring. {P}
is equivalent to {P 0}
. {P N}
,
where N
is greater than 0, returns the match group number N
.
replace
Procedure{replace A F T}
returns A
with all the occurrences of F
replaced by T
.
replace*
Procedure{replace* A F T}
returns A
with the first occurrence of F
replaced by T
.
string<=?
Procedure{string<=?}
and {string<=? A}
return true
. {string<=? A1 A2
... An}
is equivalent to {string<=? A2 ... An}
if A1
precedes or
matches A2
in lexicographical order; otherwise, returns false
.
string<?
Procedure{string<?}
and {string<? A}
return true
. {string<? A1 A2 ... An}
is equivalent to {string<? A2 ... An}
if A1
precedes A2
in
lexicographical order; otherwise, returns false
.
string=?
Procedure{string=?}
and {string=? A}
return true
. {string=? A1 A2 ... An}
is equivalent to {string=? A2 ... An}
if A1
and A2
are the same
string; otherwise, returns false
.
string>=?
Procedure{string>=?}
and {string>=? A}
return true
. {string>=? A1 A2
... An}
is equivalent to {string>=? A2 ... An}
if A1
goes after or
matches A2
in lexicographical order; otherwise, returns false
.
string>?
Procedure{string>?}
and {string>? A}
return true
. {string>? A1 A2 ... An}
is equivalent to {string>? A2 ... An}
if A1
goes after A2
in
lexicographical order; otherwise, returns false
.
titlecase
Procedure{titlecase A}
returns the titlecase conversion of A
.
upcase
Procedure{upcase A}
returns the upcase conversion of A
.
concat
Procedure{concat A ...}
produces a sequence by concatenating the sequences
produced by its arguments, and returns the concatenation of the
strings returned by its arguments.
drop
Procedure{drop S N}
produces a sequence by discarding N
elements from the
beginning of the sequence S
, and returns the string returned by S
without the first N
characters.
length
Procedure{length A}
returns the length of the sequence produced by A
, if
it's greater than 0; otherwise, returns the length of the string
returned by A
.
random
Procedure{random}
returns a random real number between 0 and 1. {random A}
returns a random integer between 0 (included) and A
(excluded) if it
isn't an empty string, otherwise returns an empty string. It also
randomizes the order of the sequence produced by A
.
reverse
Procedure{reverse A}
reverses both the order of the sequence produced and
the string returned by A
.
take
Procedure{take S N}
produces a sequence by taking N
elements from the
beginning of the sequence S
, and returns the first N
characters of the
string returned by S
.
any?
Procedure{any? S A}
returns true
if A
is true
for any element in the
sequence S
; otherwise, returns false
.
apply
Procedure{apply S P T}
calls the procedure named P
with as many arguments as
elements in the sequence S
, each one being T
for the corresponding
element of the sequence. {apply S P A ... T}
prepends A
... to the
arguments to P
. Example: ${range i 10 | apply + -2 -1 {i}}
args
Procedure{args P A ...}
for each A
, temporarily defines a procedure named P
,
which returns A
, and calls {yield}
. Example:
${args x 1 2 3 | join {x} ,}
chars
Procedure{chars A P}
iterates the characters in A
; for each character,
temporarily defines a procedure named P
, which returns the character,
and calls {yield}
.
digest
Procedure{digest S P N F}
iterates the sequence S
; for each element,
temporarily defines a procedure named P
and evaluates N
. {P P1 V1
... Pn Vn}
temporarily defines the procedures P1
... Pn
with values V1
... Vn
, which will be available in the next evaluation of N
or F
.
Finally, F
is returned. {digest S P N F P1 V1 ... Pn Vn}
is
equivalent to {extended {digest S P N F} P1 V1 ... Pn Vn}
. Example:
${range i 10 | digest k {k j {+ {i} {j}}} {j} j 0}
drop-while
Procedure{drop-while S B}
produces a sequence by discarding the elements of
the beginning of the sequence S
while B
is true
.
every?
Procedure{every? S A}
returns true
if A
is true
for every element in the
sequence S
; otherwise, returns false
.
filter
Procedure{filter S B}
produces a sequence with the elements of the sequence S
in which B
is true
.
for-each
Procedure{for-each S A}
evaluates A
for each element of the sequence S
. It's
useful for conditionally calling {yield}
, or calling procedures
with side effects. Returns an empty string.
group-by
Procedure{group-by S P A}
groups the elements of the sequence S
by A
; for
each group, temporarily defines a procedure named P
, which produces a
sequence of the elements in the group, and calls {yield}
.
join
Procedure{join S A B}
collects A
for each element of the sequence S
, and
returns its concatenation separated by B
. {join S A}
is equivalent to
{join S A ''}
.
nth
Procedure{nth S N A}
returns A
for the N
-th element of the sequence S
.
pregexp-match
Procedure{pregexp-match A P R}
iterates the matches of the regular
expression R
in A
; for each match, temporarily defines a procedure
named P
, and calls {yield}
. {P 0}
returns the match substring. {P}
is equivalent to {P 0}
. {P N}
, where N
is greater than 0, returns the
match group number N
.
pregexp-split
Procedure{pregexp-split A P R}
splits A
into substrings by the regular
expression R
; for each substring, temporarily defines a procedure
named P
, which returns the substring, and calls {yield}
.
{pregexp-split A P}
is equivalent to {pregexp-split A P '\\s+'}
.
range
Procedure{range P A Z S}
counts from A
to Z
, increasing or decreasing by S
;
for each number, temporarily defines a procedure named P
, which
returns the current number, and calls {yield}
. {range P Z}
is
equivalent to {range P 0 Z 1}
. {range P A Z}
is equivalent to {range P A Z 1}
.
slice
Procedure{slice S P N}
splits the sequence S
into subsequences of N
elements; for each subsequence, temporarily defines a procedure named P
, which produces a sequence of the elements in the subsequence, and
calls {yield}
.
sort
Procedure{sort S A P}
produces a sequence by sorting the sequence S
;
compares each element by A
using the procedure named P
, which must
accept at least 2 arguments and return true
or false
. {sort S A}
is
equivalent to {sort S A string<?}
.
split
Procedure{split A P B}
splits A
into substrings by B
; for each substring,
temporarily defines a procedure named P
, which returns the
substring, and calls {yield}
. {split A P}
is equivalent to {split A P
' '}
.
take-while
Procedure{take-while S B}
produces a sequence by taking the elements from
the beginning of the sequence S
while B
is true
.
with-index
Procedure{with-index S P}
iterates the sequence S
; for each element,
temporarily defines a procedure named P
, which returns the index of
the element, and calls {yield}
.
yield
Procedure{yield}
calls the sequence element processing coroutine, which is
set up temporarily by procedures like join
or for-each
. Returns
an empty string.
current-timestamp
Procedure{current-timestamp}
returns the current timestamp in the ISO-8601
year-month-day-hour-minute-second-timezone format. The resulting
string reflects the time in UTC, but {current-timestamp
reflects
the time according to the local time zone.true
}
extern
Procedure{extern A G}
returns A
in the context of the generator with path G
,
which must be relative to the input
directory, and the .dict
extension
must be omitted. {extern A G P1 V1 ... Pn Vn}
is equivalent to
{extended {extern A G} P1 V1 ... Pn Vn}
.
generator
Procedure{generator}
returns the path of the current generator relative to
the input
directory and without the .dict
extension.
generators
Procedure{generators K P D}
iterates the generators in the path P
(which
must be relative to the input
directory) limited to depth D
(where 0
means no limit); for each generator, temporarily defines a procedure
named K
, and calls {yield}
. {K A}
returns A
in the context of the
current generator. {generators K}
is equivalent to {generators K ''
1}
. {generators K P}
is equivalent to {generators K P 1}
.
input-file-bytes
Procedure{input-file-bytes F}
returns the contents of the file with path F
(which must be relative to the input
directory) decoded using
Latin-1.
input-file-size
Procedure{input-file-size F}
returns the size in bytes of the file with path F
, which must be relative to the input
directory.
input-file-string
Procedure{input-file-string F}
returns the contents of the file with path F
(which must be relative to the input
directory) decoded using
UTF-8.
input-file-timestamp
Procedure{input-file-timestamp F}
returns the modification timestamp of the
file with path F
(which must be relative to the input
directory) in
the ISO-8601 year-month-day-hour-minute-second-timezone format. The
resulting string reflects the time in UTC, but {input-file-timestamp F
reflects the time according to the local time zone.true
}
input-files
Procedure{input-files K P D}
iterates the files in the path P
(which must be
relative to the input
directory) limited to depth D
(where 0 means no
limit); for each file, temporarily defines a procedure named K
that
returns the current file name, and calls {yield}
. {input-files K}
is
equivalent to {input-files K '' 1}
. {input-files K P}
is equivalent
to {input-files K P 1}
.
write
Procedure{write A}
writes A
to the file that is currently being generated.
<
Procedure{<}
and {< X}
return true
. {< X1 X2 ... Xn}
is equivalent to {< X2
... Xn}
if X1
is numerically less than X2
; otherwise, returns false
.
<=
Procedure{<=}
and {<= X}
return true
. {<= X1 X2 ... Xn}
is equivalent to
{<= X2 ... Xn}
if X1
is numerically equal or less than X2
; otherwise,
returns false
.
=
Procedure{=}
and {= X}
return true
. {= X1 X2 ... Xn}
is equivalent to {= X2
... Xn}
if X1
is numerically equal to X2
; otherwise, returns false
.
>
Procedure{>}
and {> X}
return true
. {> X1 X2 ... Xn}
is equivalent to {> X2
... Xn}
if X1
is numerically greater than X2
; otherwise, returns
false
.
>=
Procedure{>=}
and {>= X}
return true
. {>= X1 X2 ... Xn}
is equivalent to
{>= X2 ... Xn}
if X1
is numerically equal or greater than X2
;
otherwise, returns false
.
-
Procedure{- Z W}
returns the subtraction of W
from Z
. {- W}
is equivalent
to {- 0 W}
. {- Z W ...}
is equivalent to {- Z {+ W ...}}
.
/
Procedure{/ Z W}
returns the division of Z
by W
. {/ W}
is equivalent to
{/ 1 W}
. {/ Z W1 W2 ... Wn}
is equivalent to {/ {/ Z W1} W2 ... Wn}
.
*
Procedure{*}
returns 1
. {* Z ...}
returns the product of its arguments.
%
Procedure{% N M}
returns N
modulo M
.
+
Procedure{+}
returns 0
. {+ Z ...}
returns the sum of its arguments.
abs
Procedure{abs X}
returns the absolute value of X
.
ceiling
Procedure{ceiling X}
returns the smallest integer that is at least as large as X
.
exact
Procedure{exact Z}
returns the coercion of Z
into an exact number.
floor
Procedure{floor X}
returns the largest integer that is no more than X
.
inexact
Procedure{inexact Z}
returns the coercion of Z
into an inexact number.
max
Procedure{max X ...}
compares its arguments as numbers and returns the largest.
min
Procedure{min X ...}
compares its arguments as numbers and returns the smallest.
round
Procedure{round X}
returns the integer closest to X
, resolving ties in favor
of an even number.
truncate
Procedure{truncate X}
returns the integer farthest from 0 that isn't
farther from 0 than X
.
The procedures defined in dictionary files accept any number of arguments, which can be accessed through these special procedures.
local
Procedure{local N}
returns the N
-th argument that
was provided to the procedure where it appears; signals an error if no
such argument exists. {local N D}
returns D
if there is no N
-th argument.
locals
Procedure{locals}
returns the number of arguments that were
provided to the procedure where it appears. {locals P}
iterates the arguments; for each argument, temporarily defines a
procedure named P
, which returns the argument, and calls
{yield}
.
You can place Racket code in a file named custom.rkt
in your project's directory to customize Pinaret. This file should
usually start with:
#lang racket (require pinaret/custom)
in order to access Pinaret's symbols. An already running instance
of pinaret-repl
or pinaret
doesn't detect
changes to custom.rkt
; you'll need to restart the program
for the changes to take effect.
(ignore-file? (const #f))
ParameterProcedure used to determine which static files from the
input
directory must be ignored when the file generation
has already finished and the program has to decide whether to do it
again; it's only effective when the --loop
command line
option is provided. The procedure receives the file path as its only
argument, and must return #f
if the file mustn't be
ignored.
(omit-file? (const #f))
ParameterProcedure used to determine which files must be omitted when
copying static files from the input
directory to the
output
directory. The procedure receives the file path
as its only argument, and must return #f
if the file
mustn't be omitted.
The predefined Pinaret procedures have restricted access to the system resources, but the custom procedures written in Racket may not. It's your responsibility to ensure your custom procedures are safe, if safety is one of your requirements.
current-procedures
ParameterList of available procedure groups. Usually, you don't need to use
this parameter directly. Procedure groups are immutable hash tables
created with hasheq
, with symbols as keys and procedures
as values. By default, it contains the predefined procedures, which
should be kept there.
define-procedure-group
Syntax(define-procedure-group G A)
defines the
G
symbol to refer to a new procedure group, and the
A
symbol, used to add procedures to the group.
Example:
(define-procedure-group custom-procedures define-custom-procedure)
A Racket procedure which is meant to be used as a Pinaret procedure should always return a string, and should treat its arguments as thunks that return strings; calling the thunks is equivalent to evaluating the Pinaret arguments, and usually you'll only want to call them once.
To add a procedure to a group you have to supply the name of the procedure, an optional documentation string, and a Racket procedure. Example:
(define-custom-procedure hello "{hello U} returns a greeting for U." (lambda (who) (format "hello ~a" (who))))
define-procedure-group/provide
Syntax(define-procedure-group/provide G A)
is equivalent to
(define-procedure-group G A) (provide G)
. This is mostly
useful when you are writing a module for a procedure group in order to
reuse it in different custom.rkt
files.
use-procedure-group
Syntax(use-procedure-group G)
adds the procedure group
G
to current-procedures
. Example:
(use-procedure-group custom-procedures)
If the procedure group you want to use is on a module, you may omit
the require
and use the syntax (use-procedure-group
M G)
, where M
is the name of the module. For
example, if you have a module called pinaret-custom/misc
with a procedure group called misc-procedures
:
(use-procedure-group pinaret-custom/misc misc-procedures)
apply-procedure
Syntax(apply-procedure P A ... L)
applies the procedure
P
using the content of the list of unevaluated arguments
L
, with A
s prepended.
call-procedure
Syntax(call-procedure P A ...)
calls the procedure
P
with A
s as arguments, which must be
unevaluated.
procedure-ref
Syntax(procedure-ref S)
returns the Pinaret procedure
corresponding to the symbol S
, or signals an error if it
isn't found. The returned procedure isn't meant to be called
directly, otherwise the recursion limit wouldn't be enforced and the
backtraces displayed on error would be incomplete.
procedure-ref/apply
Syntax(procedure-ref/apply S A ... L)
applies the Pinaret
procedure corresponding to the symbol S
using the content
of the list of unevaluated arguments L
, with
A
s prepended, or signals an error if it isn't found.
procedure-ref/call
Syntax(procedure-ref/call S A ...)
calls the Pinaret
procedure corresponding to the symbol S
with
A
s as arguments (which must be unevaluated), or signals
an error if it isn't found.
procedure-ref/forgive
Procedure(procedure-ref/forgive S)
returns the Pinaret
procedure corresponding to the symbol S
, or returns
#f
if it isn't found. The returned procedure isn't meant
to be called directly, otherwise the recursion limit wouldn't be
enforced and the backtraces displayed on error would be
incomplete.
To signal a Pinaret error from a Racket procedure, you must raise an
error/params
exception.
error/params?
Procedure(error/params? E)
returns #t
if
E
is an error/params
exception, otherwise
returns #f
.
error/params-args
Procedure(error/params-args E)
returns the arguments of the
error/params
exception E
.
error/params-params
Procedure(error/params-params E)
returns the parameterization
of the error/params
exception E
.
error/params-raiser
Procedure(error/params-raiser A ...)
may receive zero or more
arguments, and returns a procedure that receives a single argument,
let's call it B
; if B
is an
error/params
exception, a new error/params
exception is raised with B
's parameterization and with
A
s prepended to B
's arguments; if
B
isn't an error/params
exception, an
error/params
exception is raised with the current
parameterization and A
s with B
on its
tail. error/params-raiser
is typically used in
conjunction with with-handlers
.
raise-error/params
ProcedureThe raise-error/params
procedure raises an
error/params
exception that includes the current
parameterization and the list of its arguments. By convention, the
arguments should be symbols followed by other values.
call-yield
Syntax(call-yield T)
temporarily sets
current-yield
to T
and calls the
yield
Pinaret procedure.
(current-yield #f)
ParameterThe current-yield
parameter contains the element
processing coroutine, which must be either a thunk that doesn't
necessarily return a string, or #f
, when no sequence is
expected. You may store the value of (current-yield)
to
later pass it to call-yield
. For example, this is a
simplified version of the drop
procedure:
(define-custom-procedure drop-1 (lambda (sequence) (let ((yield (current-yield)) (dropped? #f)) (parameterize ((current-yield (lambda () (if dropped? (call-yield yield) (set! dropped? #t))))) (sequence)) "")))
boolean->string
Procedure(boolean->string B)
returns the false
string if B
is false, otherwise returns the
true
string.
(false-string "false")
ParameterString used as a convention to represent the Boolean false value.
string->boolean
Procedure(string->boolean S)
if S
is the
true
string, returns #t
; if it's the
false
string, returns #f
; otherwise signals
an error.
(true-string "true")
ParameterString used as a convention to represent the Boolean true value.
string->divisor
Procedure(string->divisor S)
reads and returns a number from
the string S
; signals an error if it's not possible, or
if the number is zero.
string->integer
Procedure(string->integer S)
reads and returns a number from
the string S
; signals an error if it's not possible, or
if the number isn't an integer.
string->natural
Procedure(string->natural S)
reads and returns a number from
the string S
; signals an error if it's not possible, or
if the number isn't a natural number.
string->number*
Procedure(string->number* S)
reads and returns a number from
the string S
; signals an error if it's not possible.
date->iso8601-string
Procedure(date->iso8601-string D)
returns the ISO-8601
year-month-day-hour-minute-second-timezone string representation of
the date D
.
pregexp*
Procedure(pregexp* S)
returns (pregexp S)
; signals
an error if S
isn't a valid regular expression
make-match-procedure
Procedure(make-match-procedure M)
, where M
is a
value returned by regexp-match
(or an element of a list
returned by regexp-match*
), returns a procedure that can
be used as a Pinaret procedure; let's call it P
.
{P 0}
returns the match substring. {P}
is
equivalent to {P 0}
. {P N}
, where
N
is greater than 0, returns the match group number
N
, or signals an error when N
is out of
range.
call-extended
Procedure(call-extended T K P)
temporarily extends the set of
procedures with the procedure P
named K
,
which will usually be an unevaluated argument (a thunk that returns a
string), and calls the thunk T
. (call-extended T
K1 P1 K2 P2)
is equivalent to (call-extended (lambda ()
(call-extended T K2 P2)) K1 P1)
; any number of keys/values may
be provided.
call-extended*
Procedure(call-extended* T L)
temporarily extends the set of
procedures with procedures from the list L
, and calls the
thunk T
. L
must contain an even number of
unevaluated arguments (otherwise signals an error); the elements in
the odd positions are the keys, and those in the even positions are
the values. call-extended*
accepts a keyword argument
#:call-values?
(which is #t
if omitted); if
it's #f
the values won't be evaluated, otherwise they
will be evaluated.
extended
Procedure(extended G L)
returns the procedure group
G
with added procedures from the list L
,
which must contain an even number of unevaluated arguments (otherwise
signals an error); the elements in the odd positions are the keys, and
those in the even positions are the values. extended
accepts a keyword argument #:call-values?
(which is
#t
if omitted); if it's #f
the values won't
be evaluated, otherwise they will be evaluated.
(bytes?-key 'bytes?)
ParameterSymbol of the bytes?
procedure.
(dict-suffix ".dict")
ParameterString used as the file extension of dictionary files.
(index-file-key 'index-file)
ParameterSymbol of the index-file
procedure.
(input-dir "input")
ParameterString used as the path to the input
directory.
(input-key 'input)
ParameterSymbol of the input
procedure.
(library-dir "library")
ParameterString used as the path to the library
directory.
(mode-key 'mode)
ParameterSymbol of the mode
procedure.
(output-dir "output")
ParameterString used as the path to the output
directory.
(output-key 'output)
ParameterSymbol of the output
procedure.
(path-key 'path)
ParameterSymbol of the path
procedure.
(repl-prompt "\n> ")
ParameterString used as a prompt for the REPL.
(repl-prompt* "| ")
ParameterString used as a prompt for the REPL when more input is expected in order to evaluate the previous line.
(application-end-char #\})
ParameterCharacter used to mark the end of a procedure application.
(application-start-char #\{)
ParameterCharacter used to mark the start of a procedure application.
(comment-key '|#|)
ParameterSymbol used to determine which procedure definitions to ignore in dictionary files.
(escape-char #\\)
ParameterCharacter used to escape the following character inside procedure
applications or to remove the special meaning of |
at the
beginning of lines in values of dictionary files, or to avoid string
interpolation outside procedure applications.
(filter-char #\|)
ParameterCharacter used to mark the application of a filter inside a procedure application.
(interpolation-char #\$)
ParameterCharacter used to mark the beginning of a string interpolation.
(key-char #\|)
ParameterCharacter used to mark the start of a procedure definition in the beginning of a line in a dictionary file.
(quote-chars '(#\' #\" #\`))
ParameterList of characters than can be used to avoid the special meaning of whitespace characters and | inside a procedure application.
(current-recursion-limit 1024)
ParameterPositive integer used as a limit to prevent infinite loops when calling recursive procedures.
(local-key 'local)
ParameterSymbol of the special local
procedure.
(locals-key 'locals)
ParameterSymbol of the special locals
procedure.