1 // Copyright 2012 Google Inc.
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are
6 // met:
7 //
8 // * Redistributions of source code must retain the above copyright
9 // notice, this list of conditions and the following disclaimer.
10 // * Redistributions in binary form must reproduce the above copyright
11 // notice, this list of conditions and the following disclaimer in the
12 // documentation and/or other materials provided with the distribution.
13 // * Neither the name of Google Inc. nor the names of its contributors
14 // may be used to endorse or promote products derived from this software
15 // without specific prior written permission.
16 //
17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29 #include "utils/text/templates.hpp"
30
31 #include <algorithm>
32 #include <fstream>
33 #include <sstream>
34 #include <stack>
35
36 #include "utils/format/macros.hpp"
37 #include "utils/noncopyable.hpp"
38 #include "utils/sanity.hpp"
39 #include "utils/text/exceptions.hpp"
40 #include "utils/text/operations.ipp"
41
42 namespace text = utils::text;
43
44
45 namespace {
46
47
48 /// Definition of a template statement.
49 ///
50 /// A template statement is a particular line in the input file that is
51 /// preceeded by a template marker. This class provides a high-level
52 /// representation of the contents of such statement and a mechanism to parse
53 /// the textual line into this high-level representation.
54 class statement_def {
55 public:
56 /// Types of the known statements.
57 enum statement_type {
58 /// Alternative clause of a conditional.
59 ///
60 /// Takes no arguments.
61 type_else,
62
63 /// End of conditional marker.
64 ///
65 /// Takes no arguments.
66 type_endif,
67
68 /// End of loop marker.
69 ///
70 /// Takes no arguments.
71 type_endloop,
72
73 /// Beginning of a conditional.
74 ///
75 /// Takes a single argument, which denotes the name of the variable or
76 /// vector to check for existence. This is the only expression
77 /// supported.
78 type_if,
79
80 /// Beginning of a loop over all the elements of a vector.
81 ///
82 /// Takes two arguments: the name of the vector over which to iterate
83 /// and the name of the iterator to later index this vector.
84 type_loop,
85 };
86
87 private:
88 /// Internal data describing the structure of a particular statement type.
89 struct type_descriptor {
90 /// The native type of the statement.
91 statement_type type;
92
93 /// The expected number of arguments.
94 unsigned int n_arguments;
95
96 /// Constructs a new type descriptor.
97 ///
98 /// \param type_ The native type of the statement.
99 /// \param n_arguments_ The expected number of arguments.
type_descriptor__anondfe2d8320111::statement_def::type_descriptor100 type_descriptor(const statement_type type_,
101 const unsigned int n_arguments_)
102 : type(type_), n_arguments(n_arguments_)
103 {
104 }
105 };
106
107 /// Mapping of statement type names to their definitions.
108 typedef std::map< std::string, type_descriptor > types_map;
109
110 /// Description of the different statement types.
111 ///
112 /// This static map is initialized once and reused later for any statement
113 /// lookup. Unfortunately, we cannot perform this initialization in a
114 /// static manner without C++11.
115 static types_map _types;
116
117 /// Generates a new types definition map.
118 ///
119 /// \return A new types definition map, to be assigned to _types.
120 static types_map
generate_types_map(void)121 generate_types_map(void)
122 {
123 // If you change this, please edit the comments in the enum above.
124 types_map types;
125 types.insert(types_map::value_type(
126 "else", type_descriptor(type_else, 0)));
127 types.insert(types_map::value_type(
128 "endif", type_descriptor(type_endif, 0)));
129 types.insert(types_map::value_type(
130 "endloop", type_descriptor(type_endloop, 0)));
131 types.insert(types_map::value_type(
132 "if", type_descriptor(type_if, 1)));
133 types.insert(types_map::value_type(
134 "loop", type_descriptor(type_loop, 2)));
135 return types;
136 }
137
138 public:
139 /// The type of the statement.
140 statement_type type;
141
142 /// The arguments to the statement, in textual form.
143 const std::vector< std::string > arguments;
144
145 /// Creates a new statement.
146 ///
147 /// \param type_ The type of the statement.
148 /// \param arguments_ The arguments to the statement.
statement_def(const statement_type & type_,const std::vector<std::string> & arguments_)149 statement_def(const statement_type& type_,
150 const std::vector< std::string >& arguments_) :
151 type(type_), arguments(arguments_)
152 {
153 #if !defined(NDEBUG)
154 for (types_map::const_iterator iter = _types.begin();
155 iter != _types.end(); ++iter) {
156 const type_descriptor& descriptor = (*iter).second;
157 if (descriptor.type == type_) {
158 PRE(descriptor.n_arguments == arguments_.size());
159 return;
160 }
161 }
162 UNREACHABLE;
163 #endif
164 }
165
166 /// Parses a statement.
167 ///
168 /// \param line The textual representation of the statement without any
169 /// prefix.
170 ///
171 /// \return The parsed statement.
172 ///
173 /// \throw text::syntax_error If the statement is not correctly defined.
174 static statement_def
parse(const std::string & line)175 parse(const std::string& line)
176 {
177 if (_types.empty())
178 _types = generate_types_map();
179
180 const std::vector< std::string > words = text::split(line, ' ');
181 if (words.empty())
182 throw text::syntax_error("Empty statement");
183
184 const types_map::const_iterator iter = _types.find(words[0]);
185 if (iter == _types.end())
186 throw text::syntax_error(F("Unknown statement '%s'") % words[0]);
187 const type_descriptor& descriptor = (*iter).second;
188
189 if (words.size() - 1 != descriptor.n_arguments)
190 throw text::syntax_error(F("Invalid number of arguments for "
191 "statement '%s'") % words[0]);
192
193 std::vector< std::string > new_arguments;
194 new_arguments.resize(words.size() - 1);
195 std::copy(words.begin() + 1, words.end(), new_arguments.begin());
196
197 return statement_def(descriptor.type, new_arguments);
198 }
199 };
200
201
202 statement_def::types_map statement_def::_types;
203
204
205 /// Definition of a loop.
206 ///
207 /// This simple structure is used to keep track of the parameters of a loop.
208 struct loop_def {
209 /// The name of the vector over which this loop is iterating.
210 std::string vector;
211
212 /// The name of the iterator defined by this loop.
213 std::string iterator;
214
215 /// Position in the input to which to rewind to on looping.
216 ///
217 /// This position points to the line after the loop statement, not the loop
218 /// itself. This is one of the reasons why we have this structure, so that
219 /// we can maintain the data about the loop without having to re-process it.
220 std::istream::pos_type position;
221
222 /// Constructs a new loop definition.
223 ///
224 /// \param vector_ The name of the vector (first argument).
225 /// \param iterator_ The name of the iterator (second argumnet).
226 /// \param position_ Position of the next line after the loop statement.
loop_def__anondfe2d8320111::loop_def227 loop_def(const std::string& vector_, const std::string& iterator_,
228 const std::istream::pos_type position_) :
229 vector(vector_), iterator(iterator_), position(position_)
230 {
231 }
232 };
233
234
235 /// Stateful class to instantiate the templates in an input stream.
236 ///
237 /// The goal of this parser is to scan the input once and not buffer anything in
238 /// memory. The only exception are loops: loops are reinterpreted on every
239 /// iteration from the same input file by rewidining the stream to the
240 /// appropriate position.
241 class templates_parser : utils::noncopyable {
242 /// The templates to apply.
243 ///
244 /// Note that this is not const because the parser has to have write access
245 /// to the templates. In particular, it needs to be able to define the
246 /// iterators as regular variables.
247 text::templates_def _templates;
248
249 /// Prefix that marks a line as a statement.
250 const std::string _prefix;
251
252 /// Delimiter to surround an expression instantiation.
253 const std::string _delimiter;
254
255 /// Whether to skip incoming lines or not.
256 ///
257 /// The top of the stack is true whenever we encounter a conditional that
258 /// evaluates to false or a loop that does not have any iterations left.
259 /// Under these circumstances, we need to continue scanning the input stream
260 /// until we find the matching closing endif or endloop construct.
261 ///
262 /// This is a stack rather than a plain boolean to allow us deal with
263 /// if-else clauses.
264 std::stack< bool > _skip;
265
266 /// Current count of nested conditionals.
267 unsigned int _if_level;
268
269 /// Level of the top-most conditional that evaluated to false.
270 unsigned int _exit_if_level;
271
272 /// Current count of nested loops.
273 unsigned int _loop_level;
274
275 /// Level of the top-most loop that does not have any iterations left.
276 unsigned int _exit_loop_level;
277
278 /// Information about all the nested loops up to the current point.
279 std::stack< loop_def > _loops;
280
281 /// Checks if a line is a statement or not.
282 ///
283 /// \param line The line to validate.
284 ///
285 /// \return True if the line looks like a statement, which is determined by
286 /// checking if the line starts by the predefined prefix.
287 bool
is_statement(const std::string & line)288 is_statement(const std::string& line)
289 {
290 return ((line.length() >= _prefix.length() &&
291 line.substr(0, _prefix.length()) == _prefix) &&
292 (line.length() < _delimiter.length() ||
293 line.substr(0, _delimiter.length()) != _delimiter));
294 }
295
296 /// Parses a given statement line into a statement definition.
297 ///
298 /// \param line The line to validate; it must be a valid statement.
299 ///
300 /// \return The parsed statement.
301 ///
302 /// \throw text::syntax_error If the input is not a valid statement.
303 statement_def
parse_statement(const std::string & line)304 parse_statement(const std::string& line)
305 {
306 PRE(is_statement(line));
307 return statement_def::parse(line.substr(_prefix.length()));
308 }
309
310 /// Processes a line from the input when not in skip mode.
311 ///
312 /// \param line The line to be processed.
313 /// \param input The input stream from which the line was read. The current
314 /// position in the stream must be after the line being processed.
315 /// \param output The output stream into which to write the results.
316 ///
317 /// \throw text::syntax_error If the input is not valid.
318 void
handle_normal(const std::string & line,std::istream & input,std::ostream & output)319 handle_normal(const std::string& line, std::istream& input,
320 std::ostream& output)
321 {
322 if (!is_statement(line)) {
323 // Fast path. Mostly to avoid an indentation level for the big
324 // chunk of code below.
325 output << line << '\n';
326 return;
327 }
328
329 const statement_def statement = parse_statement(line);
330
331 switch (statement.type) {
332 case statement_def::type_else:
333 _skip.top() = !_skip.top();
334 break;
335
336 case statement_def::type_endif:
337 _if_level--;
338 break;
339
340 case statement_def::type_endloop: {
341 PRE(_loops.size() == _loop_level);
342 loop_def& loop = _loops.top();
343
344 const std::size_t next_index = 1 + text::to_type< std::size_t >(
345 _templates.get_variable(loop.iterator));
346
347 if (next_index < _templates.get_vector(loop.vector).size()) {
348 _templates.add_variable(loop.iterator, F("%s") % next_index);
349 input.seekg(loop.position);
350 } else {
351 _loop_level--;
352 _loops.pop();
353 _templates.remove_variable(loop.iterator);
354 }
355 } break;
356
357 case statement_def::type_if: {
358 _if_level++;
359 const std::string value = _templates.evaluate(
360 statement.arguments[0]);
361 if (value.empty() || value == "0" || value == "false") {
362 _exit_if_level = _if_level;
363 _skip.push(true);
364 } else {
365 _skip.push(false);
366 }
367 } break;
368
369 case statement_def::type_loop: {
370 _loop_level++;
371
372 const loop_def loop(statement.arguments[0], statement.arguments[1],
373 input.tellg());
374 if (_templates.get_vector(loop.vector).empty()) {
375 _exit_loop_level = _loop_level;
376 _skip.push(true);
377 } else {
378 _templates.add_variable(loop.iterator, "0");
379 _loops.push(loop);
380 _skip.push(false);
381 }
382 } break;
383 }
384 }
385
386 /// Processes a line from the input when in skip mode.
387 ///
388 /// \param line The line to be processed.
389 ///
390 /// \throw text::syntax_error If the input is not valid.
391 void
handle_skip(const std::string & line)392 handle_skip(const std::string& line)
393 {
394 PRE(_skip.top());
395
396 if (!is_statement(line))
397 return;
398
399 const statement_def statement = parse_statement(line);
400 switch (statement.type) {
401 case statement_def::type_else:
402 if (_exit_if_level == _if_level)
403 _skip.top() = !_skip.top();
404 break;
405
406 case statement_def::type_endif:
407 INV(_if_level >= _exit_if_level);
408 if (_if_level == _exit_if_level)
409 _skip.top() = false;
410 _if_level--;
411 _skip.pop();
412 break;
413
414 case statement_def::type_endloop:
415 INV(_loop_level >= _exit_loop_level);
416 if (_loop_level == _exit_loop_level)
417 _skip.top() = false;
418 _loop_level--;
419 _skip.pop();
420 break;
421
422 case statement_def::type_if:
423 _if_level++;
424 _skip.push(true);
425 break;
426
427 case statement_def::type_loop:
428 _loop_level++;
429 _skip.push(true);
430 break;
431
432 default:
433 break;
434 }
435 }
436
437 /// Evaluates expressions on a given input line.
438 ///
439 /// An expression is surrounded by _delimiter on both sides. We scan the
440 /// string from left to right finding any expressions that may appear, yank
441 /// them out and call templates_def::evaluate() to get their value.
442 ///
443 /// Lonely or unbalanced appearances of _delimiter on the input line are
444 /// not considered an error, given that the user may actually want to supply
445 /// that character sequence without being interpreted as a template.
446 ///
447 /// \param in_line The input line from which to evaluate expressions.
448 ///
449 /// \return The evaluated line.
450 ///
451 /// \throw text::syntax_error If the expressions in the line are malformed.
452 std::string
evaluate(const std::string & in_line)453 evaluate(const std::string& in_line)
454 {
455 std::string out_line;
456
457 std::string::size_type last_pos = 0;
458 while (last_pos != std::string::npos) {
459 const std::string::size_type open_pos = in_line.find(
460 _delimiter, last_pos);
461 if (open_pos == std::string::npos) {
462 out_line += in_line.substr(last_pos);
463 last_pos = std::string::npos;
464 } else {
465 const std::string::size_type close_pos = in_line.find(
466 _delimiter, open_pos + _delimiter.length());
467 if (close_pos == std::string::npos) {
468 out_line += in_line.substr(last_pos);
469 last_pos = std::string::npos;
470 } else {
471 out_line += in_line.substr(last_pos, open_pos - last_pos);
472 out_line += _templates.evaluate(in_line.substr(
473 open_pos + _delimiter.length(),
474 close_pos - open_pos - _delimiter.length()));
475 last_pos = close_pos + _delimiter.length();
476 }
477 }
478 }
479
480 return out_line;
481 }
482
483 public:
484 /// Constructs a new template parser.
485 ///
486 /// \param templates_ The templates to apply to the processed file.
487 /// \param prefix_ The prefix that identifies lines as statements.
488 /// \param delimiter_ Delimiter to surround a variable instantiation.
templates_parser(const text::templates_def & templates_,const std::string & prefix_,const std::string & delimiter_)489 templates_parser(const text::templates_def& templates_,
490 const std::string& prefix_,
491 const std::string& delimiter_) :
492 _templates(templates_),
493 _prefix(prefix_),
494 _delimiter(delimiter_),
495 _if_level(0),
496 _exit_if_level(0),
497 _loop_level(0),
498 _exit_loop_level(0)
499 {
500 }
501
502 /// Applies the templates to a given input.
503 ///
504 /// \param input The stream to which to apply the templates.
505 /// \param output The stream into which to write the results.
506 ///
507 /// \throw text::syntax_error If the input is not valid. Note that the
508 /// is not guaranteed to be unmodified on exit if an error is
509 /// encountered.
510 void
instantiate(std::istream & input,std::ostream & output)511 instantiate(std::istream& input, std::ostream& output)
512 {
513 std::string line;
514 while (std::getline(input, line).good()) {
515 if (!_skip.empty() && _skip.top())
516 handle_skip(line);
517 else
518 handle_normal(evaluate(line), input, output);
519 }
520 }
521 };
522
523
524 } // anonymous namespace
525
526
527 /// Constructs an empty templates definition.
templates_def(void)528 text::templates_def::templates_def(void)
529 {
530 }
531
532
533 /// Sets a string variable in the templates.
534 ///
535 /// If the variable already exists, its value is replaced. This behavior is
536 /// required to implement iterators, but client code should really not be
537 /// redefining variables.
538 ///
539 /// \pre The variable must not already exist as a vector.
540 ///
541 /// \param name The name of the variable to set.
542 /// \param value The value to set the given variable to.
543 void
add_variable(const std::string & name,const std::string & value)544 text::templates_def::add_variable(const std::string& name,
545 const std::string& value)
546 {
547 PRE(_vectors.find(name) == _vectors.end());
548 _variables[name] = value;
549 }
550
551
552 /// Unsets a string variable from the templates.
553 ///
554 /// Client code has no reason to use this. This is only required to implement
555 /// proper scoping of loop iterators.
556 ///
557 /// \pre The variable must exist.
558 ///
559 /// \param name The name of the variable to remove from the templates.
560 void
remove_variable(const std::string & name)561 text::templates_def::remove_variable(const std::string& name)
562 {
563 PRE(_variables.find(name) != _variables.end());
564 _variables.erase(_variables.find(name));
565 }
566
567
568 /// Creates a new vector in the templates.
569 ///
570 /// If the vector already exists, it is cleared. Client code should really not
571 /// be redefining variables.
572 ///
573 /// \pre The vector must not already exist as a variable.
574 ///
575 /// \param name The name of the vector to set.
576 void
add_vector(const std::string & name)577 text::templates_def::add_vector(const std::string& name)
578 {
579 PRE(_variables.find(name) == _variables.end());
580 _vectors[name] = strings_vector();
581 }
582
583
584 /// Adds a value to an existing vector in the templates.
585 ///
586 /// \pre name The vector must exist.
587 ///
588 /// \param name The name of the vector to append the value to.
589 /// \param value The textual value to append to the vector.
590 void
add_to_vector(const std::string & name,const std::string & value)591 text::templates_def::add_to_vector(const std::string& name,
592 const std::string& value)
593 {
594 PRE(_variables.find(name) == _variables.end());
595 PRE(_vectors.find(name) != _vectors.end());
596 _vectors[name].push_back(value);
597 }
598
599
600 /// Checks whether a given identifier exists as a variable or a vector.
601 ///
602 /// This is used to implement the evaluation of conditions in if clauses.
603 ///
604 /// \param name The name of the variable or vector.
605 ///
606 /// \return True if the given name exists as a variable or a vector; false
607 /// otherwise.
608 bool
exists(const std::string & name) const609 text::templates_def::exists(const std::string& name) const
610 {
611 return (_variables.find(name) != _variables.end() ||
612 _vectors.find(name) != _vectors.end());
613 }
614
615
616 /// Gets the value of a variable.
617 ///
618 /// \param name The name of the variable.
619 ///
620 /// \return The value of the requested variable.
621 ///
622 /// \throw text::syntax_error If the variable does not exist.
623 const std::string&
get_variable(const std::string & name) const624 text::templates_def::get_variable(const std::string& name) const
625 {
626 const variables_map::const_iterator iter = _variables.find(name);
627 if (iter == _variables.end())
628 throw text::syntax_error(F("Unknown variable '%s'") % name);
629 return (*iter).second;
630 }
631
632
633 /// Gets a vector.
634 ///
635 /// \param name The name of the vector.
636 ///
637 /// \return A reference to the requested vector.
638 ///
639 /// \throw text::syntax_error If the vector does not exist.
640 const text::templates_def::strings_vector&
get_vector(const std::string & name) const641 text::templates_def::get_vector(const std::string& name) const
642 {
643 const vectors_map::const_iterator iter = _vectors.find(name);
644 if (iter == _vectors.end())
645 throw text::syntax_error(F("Unknown vector '%s'") % name);
646 return (*iter).second;
647 }
648
649
650 /// Indexes a vector and gets the value.
651 ///
652 /// \param name The name of the vector to index.
653 /// \param index_name The name of a variable representing the index to use.
654 /// This must be convertible to a natural.
655 ///
656 /// \return The value of the vector at the given index.
657 ///
658 /// \throw text::syntax_error If the vector does not existor if the index is out
659 /// of range.
660 const std::string&
get_vector(const std::string & name,const std::string & index_name) const661 text::templates_def::get_vector(const std::string& name,
662 const std::string& index_name) const
663 {
664 const strings_vector& vector = get_vector(name);
665 const std::string& index_str = get_variable(index_name);
666
667 std::size_t index;
668 try {
669 index = text::to_type< std::size_t >(index_str);
670 } catch (const text::syntax_error& e) {
671 throw text::syntax_error(F("Index '%s' not an integer, value '%s'") %
672 index_name % index_str);
673 }
674 if (index >= vector.size())
675 throw text::syntax_error(F("Index '%s' out of range at position '%s'") %
676 index_name % index);
677
678 return vector[index];
679 }
680
681
682 /// Evaluates a expression using these templates.
683 ///
684 /// An expression is a query on the current templates to fetch a particular
685 /// value. The value is always returned as a string, as this is how templates
686 /// are internally stored.
687 ///
688 /// \param expression The expression to evaluate. This should not include any
689 /// of the delimiters used in the user input, as otherwise the expression
690 /// will not be evaluated properly.
691 ///
692 /// \return The result of the expression evaluation as a string.
693 ///
694 /// \throw text::syntax_error If there is any problem while evaluating the
695 /// expression.
696 std::string
evaluate(const std::string & expression) const697 text::templates_def::evaluate(const std::string& expression) const
698 {
699 const std::string::size_type paren_open = expression.find('(');
700 if (paren_open == std::string::npos) {
701 return get_variable(expression);
702 } else {
703 const std::string::size_type paren_close = expression.find(
704 ')', paren_open);
705 if (paren_close == std::string::npos)
706 throw text::syntax_error(F("Expected ')' in expression '%s')") %
707 expression);
708 if (paren_close != expression.length() - 1)
709 throw text::syntax_error(F("Unexpected text found after ')' in "
710 "expression '%s'") % expression);
711
712 const std::string arg0 = expression.substr(0, paren_open);
713 const std::string arg1 = expression.substr(
714 paren_open + 1, paren_close - paren_open - 1);
715 if (arg0 == "defined") {
716 return exists(arg1) ? "true" : "false";
717 } else if (arg0 == "length") {
718 return F("%s") % get_vector(arg1).size();
719 } else {
720 return get_vector(arg0, arg1);
721 }
722 }
723 }
724
725
726 /// Applies a set of templates to an input stream.
727 ///
728 /// \param templates The templates to use.
729 /// \param input The input to process.
730 /// \param output The stream to which to write the processed text.
731 ///
732 /// \throw text::syntax_error If there is any problem processing the input.
733 void
instantiate(const templates_def & templates,std::istream & input,std::ostream & output)734 text::instantiate(const templates_def& templates,
735 std::istream& input, std::ostream& output)
736 {
737 templates_parser parser(templates, "%", "%%");
738 parser.instantiate(input, output);
739 }
740
741
742 /// Applies a set of templates to an input file and writes an output file.
743 ///
744 /// \param templates The templates to use.
745 /// \param input_file The path to the input to process.
746 /// \param output_file The path to the file into which to write the output.
747 ///
748 /// \throw text::error If the input or output files cannot be opened.
749 /// \throw text::syntax_error If there is any problem processing the input.
750 void
instantiate(const templates_def & templates,const fs::path & input_file,const fs::path & output_file)751 text::instantiate(const templates_def& templates,
752 const fs::path& input_file, const fs::path& output_file)
753 {
754 std::ifstream input(input_file.c_str());
755 if (!input)
756 throw text::error(F("Failed to open %s for read") % input_file);
757
758 std::ofstream output(output_file.c_str());
759 if (!output)
760 throw text::error(F("Failed to open %s for write") % output_file);
761
762 instantiate(templates, input, output);
763 }
764