blob: 5a833f81d4e77a799b5b43b333d14f45445066c0 [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// Heavily inspired from make_tpl_varnames_h.cc
32//
33// A utility for evaluating the changes in escaping modifiers
34// applied to variables between two versions of a template file.
35// This may come in handy when converting a template to Auto-Escape:
36// If the template previously had escaping modifiers, this tool will show
37// the variables for which Auto-Escaped determined a different escaping.
38//
39// How it works:
40// . You provide two template files, assumed to be identical in content
41// (same variables in the same order) except for escaping modifiers
42// and possibly the AUTOESCAPE pragma. You also provide the Strip mode
43// or a default of STRIP_WHITESPACE is assumed.
44//
45// . The tool loads both files and invokes DumpToString on both. It then
46// compares the escaping modifiers for each variable and when they do
47// not match, it prints a line with the variable name as well as
48// the differing modifiers.
49//
50// . We accept some command-line flags, the most notable are:
51// --template_dir to set a template root directory other than cwd
52// --strip to set the Strip mode to other than STRIP_WHITESPACE.
53// For correct operation of Auto-Escape, ensure this matches
54// the Strip mode you normally use on these templates.
55//
56//
57// Exit code is zero if there were no differences. It is non-zero
58// if we failed to load the templates or we found one or more
59// differences.
60//
61// TODO(jad): Add flag to optionally report differences when a variable
62// does not have modifiers in either template.
63
64// This is for opensource ctemplate on windows. Even though we
65// #include config.h, just like the files used to compile the dll, we
66// are actually a *client* of the dll, so we don't get to decl anything.
67#include <config.h>
68#undef CTEMPLATE_DLL_DECL
69
70#include <stdlib.h>
71#include <stdio.h>
72#ifdef HAVE_UNISTD_H
73# include <unistd.h>
74#endif
75#include <stdarg.h>
76#ifdef HAVE_GETOPT_H
77# include <getopt.h>
78#endif
79#include <string.h>
80#include <string>
81#include <ctemplate/template.h>
82#include <ctemplate/template_pathops.h>
83using std::string;
84using std::vector;
85using GOOGLE_NAMESPACE::Template;
86using GOOGLE_NAMESPACE::TemplateContext;
87using GOOGLE_NAMESPACE::Strip;
88using GOOGLE_NAMESPACE::STRIP_WHITESPACE;
89using GOOGLE_NAMESPACE::STRIP_BLANK_LINES;
90using GOOGLE_NAMESPACE::DO_NOT_STRIP;
91
92enum {LOG_VERBOSE, LOG_INFO, LOG_WARNING, LOG_ERROR, LOG_FATAL};
93
94// A variable name and optional modifiers.
95// For example: in {{NAME:j:x-bla}}
96// variable_name is "NAME" and modifiers is "j:x-bla".
97struct VariableAndMod {
98 VariableAndMod(string name, string mods)
99 : variable_name(name), modifiers(mods) { }
100 string variable_name;
101 string modifiers;
102};
103typedef vector<VariableAndMod> VariableAndMods;
104
105static string FLAG_template_dir(GOOGLE_NAMESPACE::kCWD); // "./"
106static string FLAG_strip = ""; // cmd-line arg -s
107static bool FLAG_verbose = false; // cmd-line arg -v
108
109static void LogPrintf(int severity, const char* pat, ...) {
110 if (severity == LOG_VERBOSE && !FLAG_verbose)
111 return;
112 if (severity == LOG_FATAL)
113 fprintf(stderr, "FATAL ERROR: ");
114 if (severity == LOG_VERBOSE)
115 fprintf(stdout, "[VERBOSE] ");
116 va_list ap;
117 va_start(ap, pat);
118 vfprintf(severity == LOG_INFO || severity == LOG_VERBOSE ? stdout: stderr,
119 pat, ap);
120 va_end(ap);
121 if (severity == LOG_FATAL)
122 exit(1);
123}
124
125// Prints to outfile -- usually stdout or stderr -- and then exits
126static int Usage(const char* argv0, FILE* outfile) {
127 fprintf(outfile, "USAGE: %s [-t<dir>] [-v] [-b] [-s<n>] <file1> <file2>\n",
128 argv0);
129
130 fprintf(outfile,
131 " -t --template_dir=<dir> Root directory of templates\n"
132 " -s --strip=<strip> STRIP_WHITESPACE [default],\n"
133 " STRIP_BLANK_LINES, DO_NOT_STRIP\n"
134 " -h --help This help\n"
135 " -v --verbose For a bit more output\n"
136 " -V --version Version information\n");
137 fprintf(outfile, "\n"
138 "This program reports changes to modifiers between two template\n"
139 "files assumed to be identical except for modifiers applied\n"
140 "to variables. One use case is converting a template to\n"
141 "Auto-Escape and using this program to obtain the resulting\n"
142 "changes in escaping modifiers.\n"
143 "The Strip value should match what you provide in\n"
144 "Template::GetTemplate.\n"
145 "NOTE: Variables that do not have escaping modifiers in one of\n"
146 "two templates are ignored and do not count in the differences.\n");
147 exit(0);
148}
149
150static int Version(FILE* outfile) {
151 fprintf(outfile,
152 "diff_tpl_auto_escape (part of google-template 0.9x)\n"
153 "\n"
154 "Copyright 2008 Google Inc.\n"
155 "\n"
156 "This is BSD licensed software; see the source for copying conditions\n"
157 "and license information.\n"
158 "There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A\n"
159 "PARTICULAR PURPOSE.\n"
160 );
161 exit(0);
162}
163
164// Populates the vector of VariableAndMods from the DumpToString
165// representation of the template file.
166//
167// Each VariableAndMod represents a variable node found in the template
168// along with the optional modifiers attached to it (or empty string).
169// The parsing is very simple. It looks for lines of the form:
170// "Variable Node: <VAR_NAME>[:<VAR_MODS>]\n"
171// as outputted by DumpToString() and extracts from each such line the
172// variable name and modifiers when present.
173// Because DumpToString also outputs text nodes, it is possible
174// to trip this function. Probably ok since this is just a helper tool.
175bool LoadVariables(const char* filename, Strip strip,
176 VariableAndMods& vars_and_mods) {
177 const string kVariablePreambleText = "Variable Node: ";
178 Template *tpl;
179 tpl = Template::GetTemplate(filename, strip);
180 if (tpl == NULL) {
181 LogPrintf(LOG_FATAL, "Could not load file: %s\n", filename);
182 return false;
183 }
184
185 string output;
186 tpl->DumpToString(filename, &output);
187
188 string::size_type index = 0;
189 string::size_type delim, end;
190 // TODO(jad): Switch to using regular expressions.
191 while((index = output.find(kVariablePreambleText, index)) != string::npos) {
192 index += kVariablePreambleText.length();
193 end = output.find('\n', index);
194 if (end == string::npos) {
195 // Should never happen but no need to LOG_FATAL.
196 LogPrintf(LOG_ERROR, "%s: Did not find terminating newline...\n",
197 filename);
198 end = output.length();
199 }
200 string name_and_mods = output.substr(index, end - index);
201 delim = name_and_mods.find(":");
202 if (delim == string::npos) // no modifiers.
203 delim = name_and_mods.length();
204 VariableAndMod var_mod(name_and_mods.substr(0, delim),
205 name_and_mods.substr(delim));
206 vars_and_mods.push_back(var_mod);
207 }
208 return true;
209}
210
211// Returns true if the difference in the modifier strings
212// is non-significant and can be safely omitted. This is the
213// case when one is ":j:h" and the other is ":j" since
214// the :h is a no-op after a :j.
215bool SuppressLameDiff(string modifiers_a, string modifiers_b) {
216 if ((modifiers_a == ":j:h" && modifiers_b == ":j") ||
217 (modifiers_a == ":j" && modifiers_b == ":j:h"))
218 return true;
219 return false;
220}
221
222// Main function to analyze differences in escaping modifiers between
223// two template files. These files are assumed to be identical in
224// content [strictly speaking: same number of variables in the same order].
225// If that is not the case, we fail.
226// We return true if there were no differences, false if we failed
227// or we found one or more differences.
228bool DiffTemplates(const char* filename_a, const char* filename_b,
229 Strip strip) {
230 vector<VariableAndMod> vars_and_mods_a, vars_and_mods_b;
231
232 if (!LoadVariables(filename_a, strip, vars_and_mods_a) ||
233 !LoadVariables(filename_b, strip, vars_and_mods_b))
234 return false;
235
236 if (vars_and_mods_a.size() != vars_and_mods_b.size())
237 LogPrintf(LOG_FATAL, "Templates differ: %s [%d vars] vs. %s [%d vars].\n",
238 filename_a, vars_and_mods_a.size(),
239 filename_b, vars_and_mods_b.size());
240
241 int mismatch_count = 0; // How many differences there were.
242 int no_modifiers_count = 0; // How many variables without modifiers.
243 VariableAndMods::const_iterator iter_a, iter_b;
244 for (iter_a = vars_and_mods_a.begin(), iter_b = vars_and_mods_b.begin();
245 iter_a != vars_and_mods_a.end() && iter_b != vars_and_mods_b.end();
246 ++iter_a, ++iter_b) {
247 // The templates have different variables, we fail!
248 if (iter_a->variable_name != iter_b->variable_name)
249 LogPrintf(LOG_FATAL, "Variable name mismatch: %s vs. %s\n",
250 iter_a->variable_name.c_str(),
251 iter_b->variable_name.c_str());
252 // Variables without modifiers are ignored from the diff. They simply
253 // get counted and the count is shown in verbose logging/
254 if (iter_a->modifiers == "" || iter_b->modifiers == "") {
255 no_modifiers_count++;
256 } else {
257 if (iter_a->modifiers != iter_b->modifiers &&
258 !SuppressLameDiff(iter_a->modifiers, iter_b->modifiers)) {
259 mismatch_count++;
260 LogPrintf(LOG_INFO, "Difference for variable %s -- %s vs. %s\n",
261 iter_a->variable_name.c_str(),
262 iter_a->modifiers.c_str(), iter_b->modifiers.c_str());
263 }
264 }
265 }
266
267 LogPrintf(LOG_VERBOSE, "Variables Found: Total=%d; Diffs=%d; NoMods=%d\n",
268 vars_and_mods_a.size(), mismatch_count, no_modifiers_count);
269
270 return (mismatch_count == 0);
271}
272
273int main(int argc, char **argv) {
274#if defined(HAVE_GETOPT_LONG)
275 static struct option longopts[] = {
276 {"help", 0, NULL, 'h'},
277 {"strip", 1, NULL, 's'},
278 {"template_dir", 1, NULL, 't'},
279 {"verbose", 0, NULL, 'v'},
280 {"version", 0, NULL, 'V'},
281 {0, 0, 0, 0}
282 };
283 int option_index;
284# define GETOPT(argc, argv) getopt_long(argc, argv, "t:s:hvV", \
285 longopts, &option_index)
286#elif defined(HAVE_GETOPT_H)
287# define GETOPT(argc, argv) getopt(argc, argv, "t:s:hvV")
288#else
289 // TODO(csilvers): implement something reasonable for windows/etc
290# define GETOPT(argc, argv) -1
291 int optind = 1; // first non-opt argument
292 const char* optarg = ""; // not used
293#endif
294
295 int r = 0;
296 while (r != -1) { // getopt()/getopt_long() return -1 upon no-more-input
297 r = GETOPT(argc, argv);
298 switch (r) {
299 case 's': FLAG_strip.assign(optarg); break;
300 case 't': FLAG_template_dir.assign(optarg); break;
301 case 'v': FLAG_verbose = true; break;
302 case 'V': Version(stdout); break;
303 case -1: break; // means 'no more input'
304 default: Usage(argv[0], stderr);
305 }
306 }
307
308 Template::SetTemplateRootDirectory(FLAG_template_dir);
309
310
311 if (argc != (optind + 2))
312 LogPrintf(LOG_FATAL,
313 "Must specify exactly two template files on the command line.\n");
314
315 // Validate the Strip value. Default is STRIP_WHITESPACE.
316 Strip strip = STRIP_WHITESPACE; // To avoid compiler warnings.
317 if (FLAG_strip == "STRIP_WHITESPACE" || FLAG_strip == "")
318 strip = STRIP_WHITESPACE;
319 else if (FLAG_strip == "STRIP_BLANK_LINES")
320 strip = STRIP_BLANK_LINES;
321 else if (FLAG_strip == "DO_NOT_STRIP")
322 strip = DO_NOT_STRIP;
323 else
324 LogPrintf(LOG_FATAL, "Unrecognized Strip: %s. Must be one of: "
325 "STRIP_WHITESPACE, STRIP_BLANK_LINES or DO_NOT_STRIP\n",
326 FLAG_strip.c_str());
327
328 const char* filename_a = argv[optind];
329 const char* filename_b = argv[optind + 1];
330 LogPrintf(LOG_VERBOSE, "------ Diff of [%s, %s] ------\n",
331 filename_a, filename_b);
332
333 if (DiffTemplates(filename_a, filename_b, strip))
334 return 0;
335 else
336 return 1;
337}