1
|
import os
|
2
|
import os.path
|
3
|
import re
|
4
|
import contextlib
|
5
|
import tempfile
|
6
|
|
7
|
__ALL__ = [ 'DocTemplateError', 'make_doc_from_template' ]
|
8
|
|
9
|
VARIABLE_RE = re.compile(r'@[A-Z]{2,3}[0-9]{0,2}')
|
10
|
|
11
|
class DocTemplateError(RuntimeError):
|
12
|
def __str__(self):
|
13
|
return ' '.join(map(str, self.args))
|
14
|
|
15
|
|
16
|
@contextlib.contextmanager
|
17
|
def delete_on_error(f):
|
18
|
'''Context manager which deletes a file if an exception occur'''
|
19
|
try:
|
20
|
yield
|
21
|
except:
|
22
|
os.remove(f.name)
|
23
|
raise
|
24
|
|
25
|
|
26
|
def replace_variables(template, variables, ignore_unused=True):
|
27
|
'''Lookup all substring looking like @XXX9 in the string template, and
|
28
|
replace them by the value of the key XXX9 in the dictionary variables.
|
29
|
|
30
|
Returns a new :class:str
|
31
|
|
32
|
:param template: string containing the variables references
|
33
|
:param variables: dictionary containing the variables values
|
34
|
|
35
|
'''
|
36
|
needed_vars = set([v[1:] for v in re.findall(VARIABLE_RE, template)])
|
37
|
given_vars = set(variables.keys())
|
38
|
if given_vars != needed_vars:
|
39
|
missing = needed_vars - given_vars
|
40
|
if missing or not ignore_unused:
|
41
|
unused = given_vars - needed_vars
|
42
|
raise DocTemplateError(
|
43
|
'Mismatch between given and needed variables: missing={0} unused={1}'
|
44
|
.format(map(repr, missing), map(repr, unused)))
|
45
|
def variable_replacement(match_obj):
|
46
|
return variables[match_obj.group(0)[1:]]
|
47
|
return re.sub(VARIABLE_RE, variable_replacement, template)
|
48
|
|
49
|
|
50
|
def char_to_rtf(c):
|
51
|
if ord(c) < 128:
|
52
|
return c
|
53
|
else:
|
54
|
return '\u%d?' % ord(c)
|
55
|
|
56
|
|
57
|
def utf8_to_rtf(s):
|
58
|
s = ''.join([ char_to_rtf(c) for c in s])
|
59
|
return '{\uc1{%s}}' % s
|
60
|
|
61
|
|
62
|
def unicode_to_rtf(value):
|
63
|
try:
|
64
|
value = unicode(value)
|
65
|
except Exception, e:
|
66
|
raise DocTemplateError('Unable to get a unicode value', e)
|
67
|
return utf8_to_rtf(value)
|
68
|
|
69
|
|
70
|
def variables_to_rtf(variables):
|
71
|
return dict(((k, unicode_to_rtf(v)) for k,v in variables.iteritems()))
|
72
|
|
73
|
|
74
|
def make_doc_from_template(from_path, to_path, variables, persistent):
|
75
|
'''Use file from_path as a template to combine with the variables
|
76
|
dictionary and place the result in the file to_path.
|
77
|
Encode value of variable into encoding of UTF-8 for the RTF file format.
|
78
|
|
79
|
:param from_path: the template file path
|
80
|
:param to_path: the newly created file containing the result of the templating
|
81
|
:param variables: the dictionary of variables to replace
|
82
|
'''
|
83
|
|
84
|
if not os.path.exists(from_path):
|
85
|
raise DocTemplateError('Template file does not exist', repr(from_path))
|
86
|
if os.path.exists(to_path):
|
87
|
raise DocTemplateError('Destination file already exists', repr(to_path))
|
88
|
variables = variables_to_rtf(variables)
|
89
|
if persistent:
|
90
|
with open(to_path, 'w') as to_file:
|
91
|
with open(from_path) as from_file:
|
92
|
with delete_on_error(to_file):
|
93
|
to_file.write(replace_variables(from_file.read(), variables))
|
94
|
else:
|
95
|
with tempfile.NamedTemporaryFile(prefix=to_path,
|
96
|
delete=False) as to_file:
|
97
|
with open(from_path) as from_file:
|
98
|
with delete_on_error(to_file):
|
99
|
to_file.write(replace_variables(from_file.read(), variables))
|
100
|
return to_file.name
|
101
|
|
102
|
if __name__ == '__main__':
|
103
|
import sys
|
104
|
try:
|
105
|
variables = dict((map(lambda x: unicode(x, 'utf-8'), arg.split('=')) for arg in sys.argv[3:]))
|
106
|
make_doc_from_template(sys.argv[1], sys.argv[2], variables)
|
107
|
except Exception, e:
|
108
|
raise
|
109
|
print 'Usage: python doc_template.py <template.rtf> <output.rtf> [KEY1=value1 KEY2=value2 ...]'
|