-- | Plat is a template engine, and, as such, it is designed to help you present your data -- as simple text with the markup of your choice. -- -- In order to use it, you'll have to do three things. -- -- * Prepare your data; -- -- * Create a template to insert this data into; and -- -- * Render the template with this data. -- -- See the following sections for explanations and examples. module Plat ( -- * Context -- $contextDoc Context, (=:), -- ** Context elements ContextElement, ElementC(..), listValues, -- * Template -- $templateDoc Template, templateE, template, -- ** Template errors TemplateErr(..), TemplateErrLeft(..), TemplateErrRight(..), templateErr, -- * Rendering -- $renderDoc render, -- ** Rendering errors RenderErr(..), ContextType(..), renderErr, -- * Position CharNumber, LineNumber, Pos, posContext ) where import Plat.Context import Plat.Template import Plat.Rendering -- $contextDoc -- Let's repeat: Plat is a template engine. Therefore, it's impossible to do calculations -- in the template, even small ones, like counting the lines. You need to have your data -- prepared before you render the template, and that includes sorting, indexing, etc. -- -- There are different types of data, which are used differently. As for now, there are -- four types, although in the future there could be more. They are: -- -- * Strings, that are inserted into the template; -- -- * Booleans, that are checked during rendering; -- -- * Arrays, that are iterated over; and -- -- * Records, that keep other values of different types. -- -- Using one type instead of the other results in a run-time error. -- -- Records and arrays form a hierarchy, the root of which should always be a record. -- This root becomes a context which the template is rendered with. Again: it can't be -- a string, or a boolean, or even an array. -- -- Defining strings and booleans is pretty straightforward, usual Haskell 'String's and -- 'Boolean's will do, and Haskell lists are a way to define arrays. As for records, -- we provide a handy 'Context' monad, which allows us to define records with just one -- '=:' operator. -- -- Below is an example of data, prepared to be inserted into a template. It's a simple -- list of employees in a fictional company named "Mac's tools". For each employee we -- provide his name, numerical index (as we know, it's impossible to implement this -- numbering in Plat itself), and, optionaly a boolean "bad", which, probably, is a Mac's -- evaluation of this particular employee's performance. -- -- >ctx = -- > do "name" =: "Mac's tools" -- > "staff" =: [ -- > do "index" =: "1" -- > "name" =: "Alice" -- > , -- > do "name" =: "Bob" -- > "index" =: "2" -- > "bad" =: True -- > , -- > do "index" =: "3" -- > "name" =: "Nicolás" -- > ] -- -- Of course, as this is nothing but a simple Haskell value, one can use all power of -- Haskell to create such data automatically. -- $templateDoc -- Unlike contexts, templates are not created in Haskell. Instead, they are typically -- held in separate files and written in a special markup language. Of course, there isn't -- anything to prevent them from being held in strings inside Haskell source code. -- However, they are compiled into a special opaque data type 'Template' before they are -- used. This compilation needs to happen just once, unless the template is changed. -- -- All template commands start from the letter \'\@\'. Therefore, all \'\@\'s that should -- be in the rendering result need to be escaped. In fact, that is one of the commands: -- -- [@\@\@@] Represent the string \"\@\". -- -- There is a way to insert comments in the template. For now, we only support one-line -- comments. They won't be present in the result of rendering. -- -- [@\@#@] Starts a one-line comment, continued up to the end of the current line. -- -- There is also a command that does nothing. -- -- [@\@.@] Does nothing. -- -- It can be used as a separator between a command and some other text. For example, -- if you want the letter \'a\' to follow the output of the command \'\@foo\' -- without any space between them, you can write \'\@foo\@.a\'. -- -- There is one command to insert the string from the context into the template. -- -- [@\@/expr/@] Insert the value of the expression @\'/expr/\'@, which should be a string. -- -- For now, the expression syntax is very simple. Any expression is just a sequence -- of names separated by dots (\'.\'). Expressions are evaluated from left to right, -- starting with the context, used to render the template. Each name is just a name -- of a field in the current record, and the value of this field replaces that record. -- For example, if the expression is @\'foo.bar.baz\'@, then it's value is the value -- of the field @\'baz\'@ of the record, which is the value of the field @\'bar\'@ of -- another record, which, in turn, is the value if the field @\'foo\'@ of the context. -- -- It's a runtime error if the required field doesn't exist or if the current value is -- not a record. There are two exceptions. First, if the value of @\'/expr/\'@ is boolean, -- then the expression @\'/expr/.not\'@ is valid and it's value is also boolean, -- complement to the value of @\'/expr/\'@. Second, if the value of @\'/expr/\'@ is -- an array, then the expression @\'/expr/.empty\'@ is, again, boolean, and it is true -- if and only if the value of @\'/expr/\'@ is an empty array. Of course, neither of this -- two expressions can be inserted into the template as a string. -- -- There is a command to check for the value of a boolean expression. -- -- [@\@!/expr/@] Checks the value of the expression @\'/expr/\'@, which must be true. -- -- It's a runtime error if the value of @\'/expr/\'@ is not boolean, or if it's false. -- However, there is a difference between this two variants. If the expression -- @\'/expr/\'@ is not valid, then the expression @\'/expr/.not\'@ is also not valid, -- but if @\'/expr/\'@ is false, then @\'/expr/.not\'@ is valid and true. -- -- Of course, checking the boolean value is not very useful, unless you do it between -- the branching commands. -- -- [@\@{@] Starts the list of branches. -- -- [@\@|@] Separates branches in the list. -- -- [@\@}@] Ends the list of branches. -- -- Each branch is a separate template, which is rendered with the same context as the -- list of branches itself. Plat chooses the first branch that doesn't fail and uses it -- instead of the whole list as if there was no failure. It's a runtime error -- if all branches fail. It's a compile-time error to have unbalanced starting or ending -- branching command, or to have the separating command not surrounded by them. Here, -- \'compile-time\' means \'when the template is compiled\', not when the Haskell source -- is compiled. -- -- At last, there are two looping commands. -- -- [@\@/expr/\[/name/@] Starts the loop. -- -- [@\@\]@] Ends the loop. -- -- Here, the value of @\'/expr/\'@ should be an array. Each of it's items, in turn, is -- added to the context under the name @\'/name/\'@, and the template between these -- commands is rendered with this new context. -- -- An example: -- -- >List of employees at @name: -- >@staff[person -- >@person.index@.. @person.name@{@!person.bad (going to be fired)@|@}@ -- >@]@# There won't be an empty line in the result -- -- This is a simple template, which can be used with the data example above. Note that -- if the employee is marked as \'bad\', then there would be a note that he is going to be -- fired; otherwise, there won't be anything, as the second branch is empty. Of course, -- you can use several branches, not just two of them. -- -- There is just one quirk here. If the line in the template contains only some control -- commands (i.e. branching, looping, or checking commands), padded with whitespaces -- at the beginning and the end, then this line won't be present in the result of -- rendering. All the whitespace, together with the line break, would be removed. -- So it's safe, for example, to end the loop on a separate line. On the other hand, -- if the line contains nothing but whitespace, then it won't be removed. -- -- If you don't want a line to be removed, you can just insert the @\'\@.\'@ command. -- $renderDoc -- Now, when you have a 'Context' and a 'Template', it's time to bring them together. -- -- With the examples given above, the result of rendering would be the following: -- -- >List of employees at Mac's tools: -- >1. Alice -- >2. Bob (going to be fired) -- >3. Nicolás