blob: 1dcb0692ebe2df075ff53e4ecc1cfe644be23fc4 [file] [log] [blame]
Brian Silverman70325d62015-09-20 17:00:43 -04001// Copyright (c) 2006, 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
11// copyright notice, this list of conditions and the following disclaimer
12// in the documentation and/or other materials provided with the
13// distribution.
14// * Neither the name of Google Inc. nor the names of its
15// contributors may be used to endorse or promote products derived from
16// this software without specific prior written permission.
17//
18// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30// ---
31//
32// A utility for checking syntax and generating headers to
33// use with Google Templates.
34//
35// For example:
36//
37// > <path_to>/make_tpl_varnames_h some_template_file.tpl
38//
39// This creates the header file some_template_file.tpl.varnames.h. If
40// there are any syntax errors they are reported to stderr (in which
41// case, no header file is created).
42//
43//
44// Exit code is the number of templates we were unable to parse.
45//
46// Headers can be all written to one output file (via --outputfile)
47// or written to one output file per template processed (via --header_dir).
48// As such, we have a first stage where we load each template and generate
49// its headers and a second stage where we write the headers to disk.
50//
51// TODO(jad): Prevent -f and -o from being used together.
52// Previously -o would be silently ignored.
53
54// This is for windows. Even though we #include config.h, just like
55// the files used to compile the dll, we are actually a *client* of
56// the dll, so we don't get to decl anything.
57#include <config.h>
58#undef CTEMPLATE_DLL_DECL
59#include <ctype.h> // for toupper(), isalnum()
60#include <errno.h>
61#ifdef HAVE_GETOPT_H
62# include <getopt.h>
63#endif
64#include <stdarg.h>
65#include <stdio.h>
66#include <stdlib.h>
67#include <string.h>
68#ifdef HAVE_UNISTD_H
69# include <unistd.h>
70#endif
71#include <string>
72#include <set>
73#include <vector>
74
75#include <ctemplate/template_pathops.h>
76#include <ctemplate/template.h>
77using std::set;
78using std::string;
79using std::vector;
80using GOOGLE_NAMESPACE::Template;
81
82enum {LOG_INFO, LOG_WARNING, LOG_ERROR, LOG_FATAL};
83
84// Holds information on each template we process.
85struct TemplateRecord {
86 const string name; // filename given on cmd-line (may be relative
87 bool error; // true iff an error occurred during template loading
88 string header_entries; // output of tpl->WriteHeaderEntries()
89
90 explicit TemplateRecord(const string& aname)
91 : name(aname), error(false) {
92 }
93};
94
95static void LogPrintf(int severity, int should_log_info, const char* pat, ...) {
96 if (severity == LOG_INFO && !should_log_info)
97 return;
98 if (severity == LOG_FATAL)
99 fprintf(stderr, "FATAL ERROR: ");
100 va_list ap;
101 va_start(ap, pat);
102 vfprintf(stderr, pat, ap);
103 va_end(ap);
104 fprintf(stderr, "\n");
105 if (severity == LOG_FATAL)
106 exit(1);
107}
108
109// prints to outfile -- usually stdout or stderr
110static void Usage(const char* argv0, FILE* outfile) {
111 fprintf(outfile, "USAGE: %s [-t<dir>] [-o<dir>] [-s<suffix>] [-f<filename>]"
112 " [-n] [-d] [-q] <template_filename> ...\n", argv0);
113 fprintf(outfile,
114 " -t<dir> --template_dir=<dir> Root directory of templates\n"
115 " -o<dir> --header_dir=<dir> Where to place output files\n"
116 " -s<suffix> --outputfile_suffix=<suffix>\n"
117 " outname = inname + suffix\n"
118 " -f<filename> --outputfile=<filename>\n"
119 " outname = filename (when given, \n"
120 " --header_dir is ignored)\n"
121 " -n --noheader Just check syntax, no output\n"
122 " -d --dump_templates Cause templates dump contents\n"
123 " -q --nolog_info Only log on error\n"
124 " --v=-1 Obsolete, confusing synonym for -q\n"
125 " -h --help This help\n"
126 " -V --version Version information\n");
127 fprintf(outfile, "\n"
128 "This program checks the syntax of one or more google templates.\n"
129 "By default (without -n) it also emits a header file to an output\n"
130 "directory that defines all valid template keys. This can be used\n"
131 "in programs to minimze the probability of typos in template code.\n");
132}
133
134static void Version(FILE* outfile) {
135 fprintf(outfile,
136 "make_tpl_varnames_h "
137 " (part of " PACKAGE_STRING ")"
138 "\n\n"
139 "Copyright 1998 Google Inc.\n"
140 "\n"
141 "This is BSD licensed software; see the source for copying conditions\n"
142 "and license information.\n"
143 "There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A\n"
144 "PARTICULAR PURPOSE.\n"
145 );
146}
147
148// Removes all non alphanumeric characters from a string to form a
149// valid C identifier to use as a double-inclusion guard.
150static void ConvertToIdentifier(string* s) {
151 for (string::size_type i = 0; i < s->size(); i++) {
152 if (!isalnum((*s)[i]))
153 (*s)[i] = '_';
154 else
155 (*s)[i] = toupper((*s)[i]);
156 }
157}
158
159// Returns the given header entries wrapped with a compiler guard
160// whose name is generated from the output_file name.
161static string WrapWithGuard(const string& output_file,
162 const string& header_entries) {
163 string guard(string("TPL_") + output_file);
164 ConvertToIdentifier(&guard);
165 guard.append("_H_");
166
167 string out;
168 out.append(string("#ifndef ") + guard + "\n");
169 out.append(string("#define ") + guard + "\n\n");
170
171 // Now append the header-entry info to the intro above
172 out.append(header_entries);
173
174 out.append(string("\n#endif // ") + guard + "\n");
175 return out;
176}
177
178// Generates a multi-line comment that will go at the top of the output file.
179// The comment includes the filename(s) that produced the output, one per line.
180static string Boilerplate(const string& progname,
181 const vector<string>& filenames) {
182 string out(string("//\n"));
183 if (filenames.size() > 1)
184 out.append("// This header file auto-generated for the templates\n");
185 else
186 out.append("// This header file auto-generated for the template\n");
187
188 for (vector<string>::size_type i = 0; i < filenames.size(); ++i)
189 out.append("// " + filenames[i] + "\n");
190
191 out.append("// by " + progname + "\n" +
192 "// DO NOT MODIFY THIS FILE DIRECTLY\n" +
193 "//\n");
194 return out;
195}
196
197// Returns true iff line is empty or only contains whitespace
198// (space, horizontal tab, vertical tab, form feed, carriage return).
199static bool LineIsAllWhitespace(const string& input) {
200 static const string kWhitespace(" \f\t\v\r");
201 return input.find_first_not_of(kWhitespace) == string::npos;
202}
203
204// Splits the input string into lines using the newline (\n)
205// as delimiter. The newlines are discarded.
206// An empty string input results in one empty line output.
207//
208// Examples: "Hello\nWorld\n" input results in two lines,
209// "Hello" and "World".
210// Same result for "Hello\nWorld" (not newline terminated).
211//
212static vector<string> SplitIntoLines(const string &input) {
213 vector<string> lines;
214
215 string::size_type begin_index = 0;
216 string::size_type input_len = input.length();
217
218 while (1) {
219 string::size_type end_index = input.find_first_of('\n', begin_index);
220 if (end_index == string::npos) {
221 lines.push_back(input.substr(begin_index));
222 break;
223 }
224 lines.push_back(input.substr(begin_index, (end_index - begin_index)));
225 begin_index = end_index + 1;
226 if (begin_index >= input_len) // To avoid adding a trailing empty line.
227 break;
228 }
229 return lines;
230}
231
232// Receives header entries concatenated together from one or more
233// templates and returns a string with the duplicate lines removed.
234//
235// Duplicate lines that contain only whitespace are not removed,
236// all other duplicate lines (identical #include directives and
237// identical variable definitions) are removed. If the last
238// (or only) input line did not terminate with newline, we add one.
239//
240// Consider the following two templates:
241// ex1.tpl: <p>{{USER}}</p>
242// ex2.tpl: <a href="{{URL}}">{{USER}}</a>
243//
244// The header entries for ex1.tpl are:
245// #include "template/template_string.h"
246// static const ::GOOGLE_NAMESPACE::StaticTemplateString ke_USER =
247// STS_INIT_WITH_HASH(ke_USER, "USER", 3254611514008215315LLU);
248//
249// The header entries for ex2.tpl are:
250// #include "template/template_string.h"
251// static const ::GOOGLE_NAMESPACE::StaticTemplateString ke_URL =
252// STS_INIT_WITH_HASH(ke_URL, "URL", 1026025273225241985LLU);
253// static const ::GOOGLE_NAMESPACE::StaticTemplateString ke_USER =
254// STS_INIT_WITH_HASH(ke_USER, "USER", 3254611514008215315LLU);
255//
256// Simply concatenating both header entries will result in
257// duplicate #include directives and duplicate definitions of
258// the ke_USER variable. This function instead outputs:
259//
260// #include "template/template_string.h"
261// static const ::GOOGLE_NAMESPACE::StaticTemplateString ke_USER =
262// STS_INIT_WITH_HASH(ke_USER, "USER", 3254611514008215315LLU);
263// static const ::GOOGLE_NAMESPACE::StaticTemplateString ke_URL =
264// STS_INIT_WITH_HASH(ke_URL, "URL", 1026025273225241985LLU);
265//
266static string TextWithDuplicateLinesRemoved(const string& header_entries) {
267 string output;
268 set<string> lines_seen;
269 vector<string> lines = SplitIntoLines(header_entries);
270 const int lines_len = lines.size();
271 for (int i = 0; i < lines_len; ++i) {
272 const string& line = lines[i];
273 if (LineIsAllWhitespace(line) || // Blank lines always go in
274 !lines_seen.count(line)) { // So do new lines
275 output.append(line);
276 output.append("\n");
277 lines_seen.insert(line);
278 }
279 }
280 return output;
281}
282
283// Writes the given text to the filename header_file.
284// Returns true if it succeeded, false otherwise.
285static bool WriteToDisk(bool log_info, const string& output_file,
286 const string& text) {
287 FILE* outfile = fopen(output_file.c_str(), "wb");
288 if (!outfile) {
289 LogPrintf(LOG_ERROR, log_info, "Can't open %s", output_file.c_str());
290 return false;
291 }
292 LogPrintf(LOG_INFO, log_info, "Creating %s", output_file.c_str());
293 if (fwrite(text.c_str(), 1, text.length(), outfile) != text.length()) {
294 LogPrintf(LOG_ERROR, log_info, "Can't write %s: %s",
295 output_file.c_str(), strerror(errno));
296 }
297 fclose(outfile);
298 return true;
299}
300
301int main(int argc, char **argv) {
302 string FLAG_template_dir(GOOGLE_NAMESPACE::kCWD); // "./"
303 string FLAG_header_dir(GOOGLE_NAMESPACE::kCWD);
304 GOOGLE_NAMESPACE::NormalizeDirectory(&FLAG_header_dir); // adds trailing slash
305 string FLAG_outputfile_suffix(".varnames.h");
306 string FLAG_outputfile("");
307 bool FLAG_header = true;
308 bool FLAG_dump_templates = false;
309 bool FLAG_log_info = true;
310
311#if defined(HAVE_GETOPT_LONG)
312 static struct option longopts[] = {
313 {"help", 0, NULL, 'h'},
314 {"version", 0, NULL, 'V'},
315 {"template_dir", 1, NULL, 't'},
316 {"header_dir", 1, NULL, 'o'},
317 {"outputfile_suffix", 1, NULL, 's'},
318 {"outputfile", 1, NULL, 'f'},
319 {"noheader", 0, NULL, 'n'},
320 {"dump_templates", 0, NULL, 'd'},
321 {"nolog_info", 0, NULL, 'q'},
322 {"v", 1, NULL, 'q'},
323 {0, 0, 0, 0}
324 };
325 int option_index;
326# define GETOPT(argc, argv) getopt_long(argc, argv, "t:o:s:f:ndqhV", \
327 longopts, &option_index)
328#elif defined(HAVE_GETOPT_H)
329# define GETOPT(argc, argv) getopt(argc, argv, "t:o:s:f:ndqhV")
330#else // TODO(csilvers): implement something reasonable for windows
331# define GETOPT(argc, argv) -1
332 int optind = 1; // first non-opt argument
333 const char* optarg = ""; // not used
334#endif
335
336 int r = 0;
337 while (r != -1) { // getopt()/getopt_long() return -1 upon no-more-input
338 r = GETOPT(argc, argv);
339 switch (r) {
340 case 't': FLAG_template_dir.assign(optarg); break;
341 case 'o': FLAG_header_dir.assign(optarg); break;
342 case 's': FLAG_outputfile_suffix.assign(optarg); break;
343 case 'f': FLAG_outputfile.assign(optarg); break;
344 case 'n': FLAG_header = false; break;
345 case 'd': FLAG_dump_templates = true; break;
346 case 'q': FLAG_log_info = false; break;
347 case 'V': Version(stdout); return 0; break;
348 case 'h': Usage(argv[0], stderr); return 0; break;
349 case -1: break; // means 'no more input'
350 default: Usage(argv[0], stderr); return 1; break;
351 }
352 }
353
354 if (optind >= argc) {
355 LogPrintf(LOG_FATAL, FLAG_log_info,
356 "Must specify at least one template file on the command line.");
357 }
358
359 Template::SetTemplateRootDirectory(FLAG_template_dir);
360
361
362 // Initialize the TemplateRecord array. It holds one element per
363 // template given on the command-line.
364 vector<TemplateRecord*> template_records;
365 for (int i = optind; i < argc; ++i) {
366 TemplateRecord *template_rec = new TemplateRecord(argv[i]);
367 template_records.push_back(template_rec);
368 }
369
370 // Iterate through each template and (unless -n is given), write
371 // its header entries into the headers array.
372 int num_errors = 0;
373 for (vector<TemplateRecord*>::iterator it = template_records.begin();
374 it != template_records.end(); ++it) {
375 const char* tplname = (*it)->name.c_str();
376 LogPrintf(LOG_INFO, FLAG_log_info, "\n------ Checking %s ------", tplname);
377
378 // The last two arguments in the following call do not matter
379 // since they control how the template gets expanded and we never
380 // expand the template after loading it here
381 Template * tpl = Template::GetTemplate(tplname, GOOGLE_NAMESPACE::DO_NOT_STRIP);
382
383 // The call to GetTemplate (above) loads the template from disk
384 // and attempts to parse it. If it cannot find the file or if it
385 // detects any template syntax errors, the parsing routines
386 // report the error and GetTemplate returns NULL. Syntax errors
387 // include such things as mismatched double-curly-bracket pairs,
388 // e.g. '{{VAR}', Invalid characters in template variables or
389 // section names, e.g. '{{BAD_VAR?}}' [the question mark is
390 // illegal], improperly nested section/end section markers,
391 // e.g. a section close marker with no section start marker or a
392 // section start of a different name.
393 // If that happens, since the parsing errors have already been reported
394 // we just continue on to the next one.
395 if (!tpl) {
396 LogPrintf(LOG_ERROR, FLAG_log_info, "Could not load file: %s", tplname);
397 num_errors++;
398 (*it)->error = true;
399 continue;
400 } else {
401 LogPrintf(LOG_INFO, FLAG_log_info, "No syntax errors detected in %s",
402 tplname);
403 if (FLAG_dump_templates)
404 tpl->Dump(tpl->template_file());
405 }
406
407 // The rest of the loop creates the header file
408 if (!FLAG_header)
409 continue; // They don't want header files
410
411 tpl->WriteHeaderEntries(&((*it)->header_entries));
412 }
413
414 // We have headers to emit:
415 // . If --outputfile was given, we combine all the header entries and
416 // write them to the given output file. If any template had errors,
417 // we fail and do not generate an output file.
418 // . Otherwise, we write one output file per template we processed.
419 // . In both cases, we add proper boilerplate first.
420 if (FLAG_header) {
421 string progname = argv[0];
422
423 if (!FLAG_outputfile.empty()) { // All header entries written to one file.
424 // If any template had an error, we do not produce an output file.
425 if (num_errors == 0) {
426 vector<string> template_filenames;
427 string all_header_entries;
428 for (vector<TemplateRecord*>::const_iterator
429 it = template_records.begin(); it != template_records.end(); ++it) {
430 all_header_entries.append((*it)->header_entries);
431 template_filenames.push_back((*it)->name);
432 }
433 string output = Boilerplate(progname, template_filenames);
434 const string cleantext =
435 TextWithDuplicateLinesRemoved(all_header_entries);
436 output.append(WrapWithGuard(FLAG_outputfile, cleantext));
437 if (!WriteToDisk(FLAG_log_info, FLAG_outputfile, output))
438 num_errors++;
439 }
440 } else {
441 // Each template will have its own output file. Skip any that had errors.
442 for (vector<TemplateRecord*>::const_iterator
443 it = template_records.begin(); it != template_records.end(); ++it) {
444 if ((*it)->error)
445 continue;
446 string basename = GOOGLE_NAMESPACE::Basename((*it)->name);
447 string output_file =
448 GOOGLE_NAMESPACE::PathJoin(FLAG_header_dir,
449 basename + FLAG_outputfile_suffix);
450 vector<string> template_filenames; // Contains one template filename.
451 template_filenames.push_back((*it)->name);
452 string output = Boilerplate(progname, template_filenames);
453 output.append(WrapWithGuard(output_file, (*it)->header_entries));
454 if (!WriteToDisk(FLAG_log_info, output_file, output))
455 num_errors++;
456 }
457 }
458 }
459
460 // Free dynamic memory
461 for (vector<TemplateRecord*>::iterator it = template_records.begin();
462 it != template_records.end(); ++it) {
463 delete *it;
464 }
465
466 // Cap at 127 to avoid causing problems with return code
467 return num_errors > 127 ? 127 : num_errors;
468}