Brian Silverman | 70325d6 | 2015-09-20 17:00:43 -0400 | [diff] [blame] | 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 GOOGLE_NAMESPACE::Template; |
| 86 | using GOOGLE_NAMESPACE::TemplateContext; |
| 87 | using GOOGLE_NAMESPACE::Strip; |
| 88 | using GOOGLE_NAMESPACE::STRIP_WHITESPACE; |
| 89 | using GOOGLE_NAMESPACE::STRIP_BLANK_LINES; |
| 90 | using GOOGLE_NAMESPACE::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 { |
| 98 | 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(GOOGLE_NAMESPACE::kCWD); // "./" |
| 106 | static string FLAG_strip = ""; // cmd-line arg -s |
| 107 | static bool FLAG_verbose = false; // cmd-line arg -v |
| 108 | |
| 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 |
| 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 | |
| 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. |
| 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. |
| 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. |
| 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 | |
| 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 | } |