TemplateString
provides a simple way to customize generic string templates
using macros and replacement.
A macro is represented as a string identifier like %MACRO%
in the template
string. Using TemplateString
, macros can be replaced by any Writable
data:
var tpl = new TemplateString("Hello %NAME%!")
tpl.replace("NAME", "Dave")
assert tpl.write_to_string == "Hello Dave!"
A macro identifier is valid if:
See String::is_valid_macro_name
for more details.
When using large template files it's recommanded to use external template files.
In external file example.tpl
:
<!DOCTYPE html>
<html lang="en">
<head>
<title>%TITLE%</title>
</head>
<body>
<h1>%TITLE%</h1>
<p>%ARTICLE%</p>
</body>
</html>
Loading the template file using TemplateString
:
var file = "example.tpl"
if file.file_exists then
tpl = new TemplateString.from_file("example.tpl")
tpl.replace("TITLE", "Home Page")
tpl.replace("ARTICLE", "Welcome on my site!")
end
Once macro replacement has been made, the TemplateString
can be
output like any other Template
using methods like write_to
, write_to_string
or write_to_file
.
tpl = new TemplateString("Hello %NAME%!")
tpl.replace("NAME", "Dave")
assert tpl.write_to_string == "Hello Dave!"
TemplateString
can be outputed even if all macros were not replaced.
In this case, the name of the macro will be displayed wuthout any replacement.
tpl = new TemplateString("Hello %NAME%!")
assert tpl.write_to_string == "Hello %NAME%!"
The check
method can be used to ensure that all macros were replaced before
performing the output. Warning messages will be stored in warnings
and can
be used to locate unreplaced macros.
tpl = new TemplateString("Hello %NAME%!")
if not tpl.check then
assert not tpl.warnings.is_empty
print "Cannot output unfinished template:"
print tpl.warnings.join("\n")
exit(0)
else
tpl.write_to sys.stdout
end
assert tpl.write_to_string == "Hello %NAME%!"
template :: TemplateString :: from_file
Creates a new template from the contents offile
.
template :: TemplateString :: iterator
Returns a view onself
macros on the form macro.name
/macro.replacement
.
template :: TemplateString :: macro_names
Available macros inself
.
template :: TemplateString :: marker
Macro identifier delimiter char ('%'
by default).
template :: TemplateString :: marker=
Macro identifier delimiter char ('%'
by default).
template :: TemplateString :: tpl_text=
Template original text.template $ TemplateString :: SELF
Type of this instance, automatically specialized in every classcore :: Object :: class_factory
Implementation used byget_class
to create the specific class.
core :: Writable :: defaultinit
core :: Object :: defaultinit
template :: Template :: defaultinit
template :: TemplateString :: from_file
Creates a new template from the contents offile
.
template :: Template :: is_frozen=
Is the template allowing more modification (add
)
core :: Object :: is_same_instance
Return true ifself
and other
are the same instance (i.e. same identity).
core :: Object :: is_same_serialized
Isself
the same as other
in a serialization context?
core :: Object :: is_same_type
Return true ifself
and other
have the same dynamic type.
template :: TemplateString :: iterator
Returns a view onself
macros on the form macro.name
/macro.replacement
.
template :: TemplateString :: macro_names
Available macros inself
.
template :: TemplateString :: marker
Macro identifier delimiter char ('%'
by default).
template :: TemplateString :: marker=
Macro identifier delimiter char ('%'
by default).
core :: Object :: output_class_name
Display class name on stdout (debug only).template :: TemplateString :: tpl_text=
Template original text.core :: Writable :: write_to_bytes
Likewrite_to
but return a new Bytes (may be quite large)
core :: Writable :: write_to_file
Likewrite_to
but take care of creating the file
core :: Writable :: write_to_string
Likewrite_to
but return a new String (may be quite large).
# Template with macros replacement.
#
# `TemplateString` provides a simple way to customize generic string templates
# using macros and replacement.
#
# A macro is represented as a string identifier like `%MACRO%` in the template
# string. Using `TemplateString`, macros can be replaced by any `Writable` data:
#
# var tpl = new TemplateString("Hello %NAME%!")
# tpl.replace("NAME", "Dave")
# assert tpl.write_to_string == "Hello Dave!"
#
# A macro identifier is valid if:
#
# * starts with an uppercase letter
# * contains only numbers, uppercase letters or '_'
#
# See `String::is_valid_macro_name` for more details.
#
# ## External template files
#
# When using large template files it's recommanded to use external template files.
#
# In external file `example.tpl`:
#
# ~~~html
# <!DOCTYPE html>
# <html lang="en">
# <head>
# <title>%TITLE%</title>
# </head>
# <body>
# <h1>%TITLE%</h1>
# <p>%ARTICLE%</p>
# </body>
# </html>
# ~~~
#
# Loading the template file using `TemplateString`:
#
# var file = "example.tpl"
# if file.file_exists then
# tpl = new TemplateString.from_file("example.tpl")
# tpl.replace("TITLE", "Home Page")
# tpl.replace("ARTICLE", "Welcome on my site!")
# end
#
# ## Outputting
#
# Once macro replacement has been made, the `TemplateString` can be
# output like any other `Template` using methods like `write_to`, `write_to_string`
# or `write_to_file`.
#
# tpl = new TemplateString("Hello %NAME%!")
# tpl.replace("NAME", "Dave")
# assert tpl.write_to_string == "Hello Dave!"
#
# ## Template correctness
#
# `TemplateString` can be outputed even if all macros were not replaced.
# In this case, the name of the macro will be displayed wuthout any replacement.
#
# tpl = new TemplateString("Hello %NAME%!")
# assert tpl.write_to_string == "Hello %NAME%!"
#
# The `check` method can be used to ensure that all macros were replaced before
# performing the output. Warning messages will be stored in `warnings` and can
# be used to locate unreplaced macros.
#
# tpl = new TemplateString("Hello %NAME%!")
# if not tpl.check then
# assert not tpl.warnings.is_empty
# print "Cannot output unfinished template:"
# print tpl.warnings.join("\n")
# exit(0)
# else
# tpl.write_to sys.stdout
# end
# assert tpl.write_to_string == "Hello %NAME%!"
class TemplateString
super Template
# Template original text.
var tpl_text: String
# Macros contained in the template file.
private var macros = new HashMap[String, Array[TemplateMacro]]
# Macro identifier delimiter char (`'%'` by default).
#
# To use a different delimiter you can subclasse `TemplateString` and defined the `marker`.
#
# class DollarTemplate
# super TemplateString
# redef var marker = '$'
# end
# var tpl = new DollarTemplate("Hello $NAME$!")
# tpl.replace("NAME", "Dave")
# assert tpl.write_to_string == "Hello Dave!"
protected var marker = '%'
# Creates a new template from a `text`.
#
# var tpl = new TemplateString("Hello %NAME%!")
# assert tpl.write_to_string == "Hello %NAME%!"
init do
parse
end
# Creates a new template from the contents of `file`.
init from_file(file: String) do
init load_template_file(file)
end
# Loads the template file contents.
private fun load_template_file(tpl_file: String): String do
var file = new FileReader.open(tpl_file)
var text = file.read_all
file.close
return text
end
# Finds all the macros contained in `text` and store them in `macros`.
#
# Also build `self` template parts using original template text.
private fun parse do
var text = tpl_text
var pos = 0
var out = new FlatBuffer
var start_pos: Int
var end_pos: Int
while pos < text.length do
# lookup opening tag
start_pos = text.read_until_char(pos, marker, out)
if start_pos < 0 then
text.read_until_pos(pos, text.length, out)
add out.to_s
break
end
add out.to_s
pos = start_pos + 1
# lookup closing tag
out.clear
end_pos = text.read_until_char(pos, marker, out)
if end_pos < 0 then
text.read_until_pos(pos, text.length, out)
add "%"
add out.to_s
break
end
pos = end_pos + 1
# check macro
var name = out.to_s
if name.is_valid_macro_name then
add make_macro(name, start_pos, end_pos)
else
add "%"
add name
add "%"
end
out.clear
end
end
# Add a new macro to the list
private fun make_macro(name: String, start_pos, end_pos: Int): TemplateMacro do
if not macros.has_key(name) then
macros[name] = new Array[TemplateMacro]
end
var macro = new TemplateMacro(name, start_pos, end_pos)
macros[name].add macro
return macro
end
# Available macros in `self`.
#
# var tpl = new TemplateString("Hello %NAME%!")
# assert tpl.macro_names.first == "NAME"
fun macro_names: Collection[String] do return macros.keys
# Does `self` contain a macro with `name`.
#
# var tpl = new TemplateString("Hello %NAME%")
# assert tpl.has_macro("NAME")
fun has_macro(name: String): Bool do return macro_names.has(name)
# Replace a `macro` by a streamable `replacement`.
#
# REQUIRE `has_macro(name)`
#
# var tpl = new TemplateString("Hello %NAME%!")
# tpl.replace("NAME", "Dave")
# assert tpl.write_to_string == "Hello Dave!"
fun replace(name: String, replacement: Writable) do
assert has_macro(name)
for macro in macros[name] do
macro.replacement = replacement
end
end
# Check if all macros were replaced.
#
# Return false if a macro was not replaced and store message in `warnings`.
#
# var tpl = new TemplateString("Hello %FIRSTNAME%, %LASTNAME%!")
# assert not tpl.check
# tpl.replace("FIRSTNAME", "Corben")
# tpl.replace("LASTNAME", "Dallas")
# assert tpl.check
fun check: Bool do
warnings.clear
var all_ok = true
for name, macros in self.macros do
for macro in macros do
if not macro.is_replaced then
all_ok = false
warnings.add "No replacement for macro %{macro.name}% at {macro.location}"
end
end
end
return all_ok
end
# Last `check` warnings.
#
# var tpl = new TemplateString("Hello %FIRSTNAME%, %LASTNAME%!")
# tpl.check
# assert tpl.warnings.length == 2
# assert tpl.warnings[0] == "No replacement for macro %FIRSTNAME% at (6:16)"
# assert tpl.warnings[1] == "No replacement for macro %LASTNAME% at (19:28)"
# tpl.replace("FIRSTNAME", "Corben")
# tpl.replace("LASTNAME", "Dallas")
# tpl.check
# assert tpl.warnings.is_empty
var warnings = new Array[String]
# Returns a view on `self` macros on the form `macro.name`/`macro.replacement`.
#
# Given that all macros with the same name are all replaced with the same
# replacement, this view contains only one entry for each name.
#
# var tpl = new TemplateString("Hello %FIRSTNAME%, %LASTNAME%!")
# for name, rep in tpl do assert rep == null
# tpl.replace("FIRSTNAME", "Corben")
# tpl.replace("LASTNAME", "Dallas")
# for name, rep in tpl do assert rep != null
fun iterator: MapIterator[String, nullable Writable] do
return new TemplateStringIterator(self)
end
end
lib/template/macro.nit:25,1--274,3