This modulel realises a template text processor by applying recursive text block substititon filters:
Parser (input, FiltersTable, Variables)
repeat for input
block <- collect text block
filter() <- FiltersTable (block)
data <- filter (block, FiltersTable, Variables)
output <- for data substitute text fragments from Variables
Built-in text blocks
Single text lines, terminated with '\n'
- Lines with first character ';' are discarded. Formally, the filter() applied here returns empty data.
- Lines not starting with a key word :<keyName>: are passed on as-is (i.e. unfiltered). A key word can be preceeded by spaces.
Multi lines text:
- all text lines between :STASH: and :END:
:STASH: .. text block .. :END:
are collected and shashed as variable @STASHED@. Formally, the filter() applied here returns empty data.
Other text blocks
All other text blocks are defined by means of a lookup table which may differ with every filter() recursion. A lookup table entry looks like
("<keyName>",(<ifFilter>,<elseFilter>))
where the <keyName> is looked up and one of the filter functions <ifFilter> or <elseFilter> is applied to the selected text block.
- If <keyName> matches IF_<ifName> for some text <ifName>, then the following syntax is supported
:IF_<ifName>: .. text block .. :ELSE: .. alternative text block .. :END: :IF_NOT_<ifName>: .. alternative text block .. :ELSE: .. text block .. :END:
where in the first case the filters are applied as
ifFilter(".. text block ..") elseFilter(".. alternative text block ..")and in the second case
elseFilter(".. alternative text block ..") ifFilter(".. text block ..")If the :ELSE: clause is missing the corresponding filter is not applied.
A trivial example for an if/else filter table entry for a true condition can be set up with the toIfElse() convenience function as
("IF_TRUE", toIfElse(true)) - Any other <keyName> value (not matching IF_<ifName>) supports the clause
:<keyName>: .. text block .. :END:
in which case the <elseFilter> of the lookup table entry is ignored. A loop filter can be contructed from an iterator example
iterator exampleItems(p: TmplParser): TmplParser {.closure.} = for k in .. items ..: var localVars = @[("@VAR1@", val1(k)), ("@VAR2@", val2(k)), ..] yield (p.filter, localVars.concat(p.vars))using a variant of the toIfElse() convenience function as
("FOR_ITEMS", toIfElse(exampleItems))
Other directives
The key word :STOP: teminates any text bock collection other than in :STASH: scope.
Variable substitution filter
After a filter() is applied to a text block, the Variables list consisting of pairs
("<textN>", "<substituteN>")
is appied to the fitered data replacing each ocurrence of <textN> with <substituteN> for all N.
Parser usage example
Example:
import algorithm, os, sequtils iterator envItems(p: TmplParser): TmplParser {.closure.} = for k in envPairs.toSeq.mapIt(it.key.string).sorted: var # Loop-local variables key = ("@KEY@", k) value = ("@VALUE@", k.getEnv.string) yield (p.filter, @[key, value].concat(p.vars)) # Text template processor parser specification proc newSpecs(): TmplParser = result = (newTmplFilter(), newSeq[(string,string)]()) # Global variable result.vars.add(("@TITLE@", "Hello World")) # Simple if/else clause result.filter["IF_ENABLED"] = toIfElse(true) # Loop clause result.filter["FOR_ENV"] = envItems.toIfElse(result) # Template text page var tmplPage = """ @TITLE@ ; -- comment :STASH: stashed text :END: :IF_ENABLED: -- outer +++ aaa :IF_NOT_ENABLED: -- inner +++ bbb :ELSE: -- inner +++ ddd @STASHED@ :END: -- inner +++ eee :ELSE: -- outer +++ bbb :END: -- outer :STASH: *** :END: :FOR_ENV: @STASHED@ @KEY@ = @VALUE@ :END: """ # Apply template processor var expandedPage = tmplPage.tmplParser(newSpecs())then the contents of the variable expandedPage above contains something like
Hello World +++ aaa +++ ddd stashed text +++ eee *** COLUMNS = 79 *** DBUS_SESSION_BUS_ADDRESS = unix:path=/run/user/1000/bus *** DESKTOP_AUTOSTART_ID = 1054b02024a... *** DESKTOP_SESSION = lightdm-xsession *** DESKTOP_STARTUP_ID = x-session-manager-... *** DISPLAY = :0 *** GDMSESSION = lightdm-xsession *** GPG_AGENT_INFO = /run/user/1000/... *** GTK3_MODULES = gtk-vector-screenshot *** GTK_MODULES = gail:atk-bridge *** ...
Debugging
When including the library, compile with the flag tracer as in
nim c -r -d:tracer:1 ...
in order to dump the parser state to stderr after each action of the template processor.
Types
TmplFilterFn = proc (x: string): string {...}{.closure.}
TmplIfElse = (TmplFilterFn, TmplFilterFn)
TmplFilter = TableRef[string, TmplIfElse]
TmplParser = tuple[filter: TmplFilter, vars: seq[(string, string)]]
TmplLoopIt = iterator (parser: TmplParser): TmplParser {...}{.closure.}
Procs
proc tmplUncomment(tmplText: string): string {...}{.raises: [], tags: [].}
- Filter out all lines starting with ';'.
proc newTmplFilter(): TmplFilter {...}{.raises: [], tags: [].}
- Create empty table to be eventually used in as filters argument in tmplParser().
proc tmplParser(tmplText: string; parser: TmplParser): string {...}{. raises: [ValueError, Exception, KeyError], tags: [RootEffect].}
- This function realises the template text processor as described above in the module documentation.
proc tmplParser(tmplText: string; filters: TmplFilter; vars: varargs[(string, string)]): string {...}{. raises: [ValueError, Exception, KeyError], tags: [RootEffect].}
- Variant of tmplParser().
proc tmplParser(tmplText: string; vars: varargs[(string, string)]): string {...}{. raises: [ValueError, Exception, KeyError], tags: [RootEffect].}
- Simplified template parser without block section clauses. As a consequence, only variable/text substitution is performed.
proc toIfElse(isIfClauseOk = true): TmplIfElse {...}{.raises: [], tags: [].}
-
Returns the
(<pass-through-filter>,<discard-filter>)
filter pair if argument isIfClauseOk is true, otherwise the tuple entries are swapped. The tuple returned is supposed to be used with some lookup entry for the filters argument table for the tmplParser() function.
proc toIfElse(tmplIt: TmplLoopIt; parser: TmplParser): TmplIfElse {...}{.raises: [], tags: [].}
- Returns a TmplIfElse filter derived from the iterator argument tmplIt(). The return value is supposed to be used with some lookup entry for the filters argument table for the tmplParser() function.
proc toIfElse(tmplIt: TmplLoopIt; filters: TmplFilter; vars: varargs[(string, string)]): TmplIfElse {...}{.raises: [], tags: [].}
- Variant of toIfElse().
proc toIfElseNil(): TmplIfElse {...}{.inline, raises: [], tags: [].}
- Simple text discarding <sectionFilter> pair for tmplTextFilter()
proc toIfElseNil(ignIt: TmplLoopIt; ignFilters: TmplFilter; ignVars: varargs[(string, string)]): TmplIfElse {...}{.inline, raises: [], tags: [].}
- Variant of toIfElseNil().