Text and Templates¶
Often the job of output is not about individual text lines, but about creating
multi-line files such as scripts and reports. This often leads away from standard
output mechanisms toward template packages, but say
has you covered here as
well.
from say import Text
# assume `hostname` and `filepath` already defined
script = Text()
script += """
!#/bin/bash
# Output the results of a ping command to the given file
ping {hostname!r} >{filepath!r}
"""
script.write_to("script.sh")
Then script.sh
will contain:
!#/bin/bash
# Output the results of a ping command to the given file
ping 'server1234.example.com' >'ping-results.txt'
Text
objects are basically a list of text lines. In most cases, when you add
text (either as multi-line strings or lists of strings), Text
will
automatically interpolate variables the same way say
does. One can
simply print
or
say
Text
objects, as their str()
value is the full text you would
assume. Text
objects have both text
and lines
properties which
can be either accessed or assigned to.
+=
incremental assignment
automatically removes blank starting and ending lines, and any whitespace prefix
that is common to all of the lines (i.e. it will dedent any given text).
This ensures you don’t need to give up
nice Python program formatting just to include a template.
While +=
is a handy way of incrementally building text, it
isn’t strictly necessary in the simple example above; the
Text(...)
constructor itself accepts a string or set of lines.
Other in-place operators are: |=
for adding text while preserving leading white
space (no dedent) and &=
adds text verbatim–without dedent or string
interpolation.
One can read_from()
a file (appending the contents of the file to the given
text object, with optional interpolation and dedenting). One can also
write_to()
a file. Use the append
flag if you wish to add to rather than
overwrite the file of a given name, and you can set an output encoding if you
like (encoding='utf-8'
is the default).
So far we’ve discussed Text
objects almost like strings, but they also act
as lists of individual lines (strings). They are, for example,
indexable via []
, and they are iterable.
Their len()
is the number of lines they contain. One can
append()
or extend()
them with one or multiple strings, respectively.
append()
takes a keyword parameter interpolate
that controls whether
{}
expressions in the string are interpolated. extend()
additionally takes
a dedent
flag that, if true, will
automatically remove blank starting and ending lines, and any whitespace prefix
that is common to all of the lines.
If t
is a Text
instance, str(t)
will be the full string representing it.
If you wish to move from multiple lines to a single-line, joined string, ' '.join(t)
does the trick.
Text
objects, unlike strings, are mutable. The replace(x, y)
method will
replace all instances of x
with y
in situ. If given just one argument,
a dict
, all the keys will be replaced with their corresponding values.
Text
doesn’t have the full set of text-onboarding options seen in textdata, but it should suit many circumstances.
If you need more, textdata
can be used alongside Text
.
Finally, it’s possible to use a Text
object like a file and write to it.
So:
t = Text()
say.set(files=[sys.stdout, t])
say('something')
will now append each thing said to both sys.stdout
and t
.
There is a related class Template
that does not interpolate its
format variables when constructed, but rather when explicitly rendered. This
suits certain form-filling operations:
t = Template("Dear {name},\n\nWelcome to our club!\n")
for name in 'Joe Jane Jeremey'.split():
print t.render()