1 // 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>
83 using std::string;
84 using std::vector;
85 using ctemplate::Template;
86 using ctemplate::TemplateContext;
87 using ctemplate::Strip;
88 using ctemplate::STRIP_WHITESPACE;
89 using ctemplate::STRIP_BLANK_LINES;
90 using ctemplate::DO_NOT_STRIP;
91 
92 enum {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".
97 struct VariableAndMod {
VariableAndModVariableAndMod98   VariableAndMod(string name, string mods)
99       : variable_name(name), modifiers(mods) { }
100   string variable_name;
101   string modifiers;
102 };
103 typedef vector<VariableAndMod> VariableAndMods;
104 
105 static string FLAG_template_dir(ctemplate::kCWD);   // "./"
106 static string FLAG_strip = "";      // cmd-line arg -s
107 static bool FLAG_verbose = false;   // cmd-line arg -v
108 
LogPrintf(int severity,const char * pat,...)109 static 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
Usage(const char * argv0,FILE * outfile)126 static 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 
Version(FILE * outfile)150 static 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.
LoadVariables(const char * filename,Strip strip,VariableAndMods & vars_and_mods)175 bool 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.
SuppressLameDiff(string modifiers_a,string modifiers_b)215 bool 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.
DiffTemplates(const char * filename_a,const char * filename_b,Strip strip)228 bool 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 
main(int argc,char ** argv)273 int 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 }
338