| // Copyright (c) 2006, Google Inc. |
| // All rights reserved. |
| // |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are |
| // met: |
| // |
| // * Redistributions of source code must retain the above copyright |
| // notice, this list of conditions and the following disclaimer. |
| // * Redistributions in binary form must reproduce the above |
| // copyright notice, this list of conditions and the following disclaimer |
| // in the documentation and/or other materials provided with the |
| // distribution. |
| // * Neither the name of Google Inc. nor the names of its |
| // contributors may be used to endorse or promote products derived from |
| // this software without specific prior written permission. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| // --- |
| // Heavily inspired from make_tpl_varnames_h.cc |
| // |
| // A utility for evaluating the changes in escaping modifiers |
| // applied to variables between two versions of a template file. |
| // This may come in handy when converting a template to Auto-Escape: |
| // If the template previously had escaping modifiers, this tool will show |
| // the variables for which Auto-Escaped determined a different escaping. |
| // |
| // How it works: |
| // . You provide two template files, assumed to be identical in content |
| // (same variables in the same order) except for escaping modifiers |
| // and possibly the AUTOESCAPE pragma. You also provide the Strip mode |
| // or a default of STRIP_WHITESPACE is assumed. |
| // |
| // . The tool loads both files and invokes DumpToString on both. It then |
| // compares the escaping modifiers for each variable and when they do |
| // not match, it prints a line with the variable name as well as |
| // the differing modifiers. |
| // |
| // . We accept some command-line flags, the most notable are: |
| // --template_dir to set a template root directory other than cwd |
| // --strip to set the Strip mode to other than STRIP_WHITESPACE. |
| // For correct operation of Auto-Escape, ensure this matches |
| // the Strip mode you normally use on these templates. |
| // |
| // |
| // Exit code is zero if there were no differences. It is non-zero |
| // if we failed to load the templates or we found one or more |
| // differences. |
| // |
| // TODO(jad): Add flag to optionally report differences when a variable |
| // does not have modifiers in either template. |
| |
| // This is for opensource ctemplate on windows. Even though we |
| // #include config.h, just like the files used to compile the dll, we |
| // are actually a *client* of the dll, so we don't get to decl anything. |
| #include <config.h> |
| #undef CTEMPLATE_DLL_DECL |
| |
| #include <stdlib.h> |
| #include <stdio.h> |
| #ifdef HAVE_UNISTD_H |
| # include <unistd.h> |
| #endif |
| #include <stdarg.h> |
| #ifdef HAVE_GETOPT_H |
| # include <getopt.h> |
| #endif |
| #include <string.h> |
| #include <string> |
| #include <ctemplate/template.h> |
| #include <ctemplate/template_pathops.h> |
| using std::string; |
| using std::vector; |
| using GOOGLE_NAMESPACE::Template; |
| using GOOGLE_NAMESPACE::TemplateContext; |
| using GOOGLE_NAMESPACE::Strip; |
| using GOOGLE_NAMESPACE::STRIP_WHITESPACE; |
| using GOOGLE_NAMESPACE::STRIP_BLANK_LINES; |
| using GOOGLE_NAMESPACE::DO_NOT_STRIP; |
| |
| enum {LOG_VERBOSE, LOG_INFO, LOG_WARNING, LOG_ERROR, LOG_FATAL}; |
| |
| // A variable name and optional modifiers. |
| // For example: in {{NAME:j:x-bla}} |
| // variable_name is "NAME" and modifiers is "j:x-bla". |
| struct VariableAndMod { |
| VariableAndMod(string name, string mods) |
| : variable_name(name), modifiers(mods) { } |
| string variable_name; |
| string modifiers; |
| }; |
| typedef vector<VariableAndMod> VariableAndMods; |
| |
| static string FLAG_template_dir(GOOGLE_NAMESPACE::kCWD); // "./" |
| static string FLAG_strip = ""; // cmd-line arg -s |
| static bool FLAG_verbose = false; // cmd-line arg -v |
| |
| static void LogPrintf(int severity, const char* pat, ...) { |
| if (severity == LOG_VERBOSE && !FLAG_verbose) |
| return; |
| if (severity == LOG_FATAL) |
| fprintf(stderr, "FATAL ERROR: "); |
| if (severity == LOG_VERBOSE) |
| fprintf(stdout, "[VERBOSE] "); |
| va_list ap; |
| va_start(ap, pat); |
| vfprintf(severity == LOG_INFO || severity == LOG_VERBOSE ? stdout: stderr, |
| pat, ap); |
| va_end(ap); |
| if (severity == LOG_FATAL) |
| exit(1); |
| } |
| |
| // Prints to outfile -- usually stdout or stderr -- and then exits |
| static int Usage(const char* argv0, FILE* outfile) { |
| fprintf(outfile, "USAGE: %s [-t<dir>] [-v] [-b] [-s<n>] <file1> <file2>\n", |
| argv0); |
| |
| fprintf(outfile, |
| " -t --template_dir=<dir> Root directory of templates\n" |
| " -s --strip=<strip> STRIP_WHITESPACE [default],\n" |
| " STRIP_BLANK_LINES, DO_NOT_STRIP\n" |
| " -h --help This help\n" |
| " -v --verbose For a bit more output\n" |
| " -V --version Version information\n"); |
| fprintf(outfile, "\n" |
| "This program reports changes to modifiers between two template\n" |
| "files assumed to be identical except for modifiers applied\n" |
| "to variables. One use case is converting a template to\n" |
| "Auto-Escape and using this program to obtain the resulting\n" |
| "changes in escaping modifiers.\n" |
| "The Strip value should match what you provide in\n" |
| "Template::GetTemplate.\n" |
| "NOTE: Variables that do not have escaping modifiers in one of\n" |
| "two templates are ignored and do not count in the differences.\n"); |
| exit(0); |
| } |
| |
| static int Version(FILE* outfile) { |
| fprintf(outfile, |
| "diff_tpl_auto_escape (part of google-template 0.9x)\n" |
| "\n" |
| "Copyright 2008 Google Inc.\n" |
| "\n" |
| "This is BSD licensed software; see the source for copying conditions\n" |
| "and license information.\n" |
| "There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A\n" |
| "PARTICULAR PURPOSE.\n" |
| ); |
| exit(0); |
| } |
| |
| // Populates the vector of VariableAndMods from the DumpToString |
| // representation of the template file. |
| // |
| // Each VariableAndMod represents a variable node found in the template |
| // along with the optional modifiers attached to it (or empty string). |
| // The parsing is very simple. It looks for lines of the form: |
| // "Variable Node: <VAR_NAME>[:<VAR_MODS>]\n" |
| // as outputted by DumpToString() and extracts from each such line the |
| // variable name and modifiers when present. |
| // Because DumpToString also outputs text nodes, it is possible |
| // to trip this function. Probably ok since this is just a helper tool. |
| bool LoadVariables(const char* filename, Strip strip, |
| VariableAndMods& vars_and_mods) { |
| const string kVariablePreambleText = "Variable Node: "; |
| Template *tpl; |
| tpl = Template::GetTemplate(filename, strip); |
| if (tpl == NULL) { |
| LogPrintf(LOG_FATAL, "Could not load file: %s\n", filename); |
| return false; |
| } |
| |
| string output; |
| tpl->DumpToString(filename, &output); |
| |
| string::size_type index = 0; |
| string::size_type delim, end; |
| // TODO(jad): Switch to using regular expressions. |
| while((index = output.find(kVariablePreambleText, index)) != string::npos) { |
| index += kVariablePreambleText.length(); |
| end = output.find('\n', index); |
| if (end == string::npos) { |
| // Should never happen but no need to LOG_FATAL. |
| LogPrintf(LOG_ERROR, "%s: Did not find terminating newline...\n", |
| filename); |
| end = output.length(); |
| } |
| string name_and_mods = output.substr(index, end - index); |
| delim = name_and_mods.find(":"); |
| if (delim == string::npos) // no modifiers. |
| delim = name_and_mods.length(); |
| VariableAndMod var_mod(name_and_mods.substr(0, delim), |
| name_and_mods.substr(delim)); |
| vars_and_mods.push_back(var_mod); |
| } |
| return true; |
| } |
| |
| // Returns true if the difference in the modifier strings |
| // is non-significant and can be safely omitted. This is the |
| // case when one is ":j:h" and the other is ":j" since |
| // the :h is a no-op after a :j. |
| bool SuppressLameDiff(string modifiers_a, string modifiers_b) { |
| if ((modifiers_a == ":j:h" && modifiers_b == ":j") || |
| (modifiers_a == ":j" && modifiers_b == ":j:h")) |
| return true; |
| return false; |
| } |
| |
| // Main function to analyze differences in escaping modifiers between |
| // two template files. These files are assumed to be identical in |
| // content [strictly speaking: same number of variables in the same order]. |
| // If that is not the case, we fail. |
| // We return true if there were no differences, false if we failed |
| // or we found one or more differences. |
| bool DiffTemplates(const char* filename_a, const char* filename_b, |
| Strip strip) { |
| vector<VariableAndMod> vars_and_mods_a, vars_and_mods_b; |
| |
| if (!LoadVariables(filename_a, strip, vars_and_mods_a) || |
| !LoadVariables(filename_b, strip, vars_and_mods_b)) |
| return false; |
| |
| if (vars_and_mods_a.size() != vars_and_mods_b.size()) |
| LogPrintf(LOG_FATAL, "Templates differ: %s [%d vars] vs. %s [%d vars].\n", |
| filename_a, vars_and_mods_a.size(), |
| filename_b, vars_and_mods_b.size()); |
| |
| int mismatch_count = 0; // How many differences there were. |
| int no_modifiers_count = 0; // How many variables without modifiers. |
| VariableAndMods::const_iterator iter_a, iter_b; |
| for (iter_a = vars_and_mods_a.begin(), iter_b = vars_and_mods_b.begin(); |
| iter_a != vars_and_mods_a.end() && iter_b != vars_and_mods_b.end(); |
| ++iter_a, ++iter_b) { |
| // The templates have different variables, we fail! |
| if (iter_a->variable_name != iter_b->variable_name) |
| LogPrintf(LOG_FATAL, "Variable name mismatch: %s vs. %s\n", |
| iter_a->variable_name.c_str(), |
| iter_b->variable_name.c_str()); |
| // Variables without modifiers are ignored from the diff. They simply |
| // get counted and the count is shown in verbose logging/ |
| if (iter_a->modifiers == "" || iter_b->modifiers == "") { |
| no_modifiers_count++; |
| } else { |
| if (iter_a->modifiers != iter_b->modifiers && |
| !SuppressLameDiff(iter_a->modifiers, iter_b->modifiers)) { |
| mismatch_count++; |
| LogPrintf(LOG_INFO, "Difference for variable %s -- %s vs. %s\n", |
| iter_a->variable_name.c_str(), |
| iter_a->modifiers.c_str(), iter_b->modifiers.c_str()); |
| } |
| } |
| } |
| |
| LogPrintf(LOG_VERBOSE, "Variables Found: Total=%d; Diffs=%d; NoMods=%d\n", |
| vars_and_mods_a.size(), mismatch_count, no_modifiers_count); |
| |
| return (mismatch_count == 0); |
| } |
| |
| int main(int argc, char **argv) { |
| #if defined(HAVE_GETOPT_LONG) |
| static struct option longopts[] = { |
| {"help", 0, NULL, 'h'}, |
| {"strip", 1, NULL, 's'}, |
| {"template_dir", 1, NULL, 't'}, |
| {"verbose", 0, NULL, 'v'}, |
| {"version", 0, NULL, 'V'}, |
| {0, 0, 0, 0} |
| }; |
| int option_index; |
| # define GETOPT(argc, argv) getopt_long(argc, argv, "t:s:hvV", \ |
| longopts, &option_index) |
| #elif defined(HAVE_GETOPT_H) |
| # define GETOPT(argc, argv) getopt(argc, argv, "t:s:hvV") |
| #else |
| // TODO(csilvers): implement something reasonable for windows/etc |
| # define GETOPT(argc, argv) -1 |
| int optind = 1; // first non-opt argument |
| const char* optarg = ""; // not used |
| #endif |
| |
| int r = 0; |
| while (r != -1) { // getopt()/getopt_long() return -1 upon no-more-input |
| r = GETOPT(argc, argv); |
| switch (r) { |
| case 's': FLAG_strip.assign(optarg); break; |
| case 't': FLAG_template_dir.assign(optarg); break; |
| case 'v': FLAG_verbose = true; break; |
| case 'V': Version(stdout); break; |
| case -1: break; // means 'no more input' |
| default: Usage(argv[0], stderr); |
| } |
| } |
| |
| Template::SetTemplateRootDirectory(FLAG_template_dir); |
| |
| |
| if (argc != (optind + 2)) |
| LogPrintf(LOG_FATAL, |
| "Must specify exactly two template files on the command line.\n"); |
| |
| // Validate the Strip value. Default is STRIP_WHITESPACE. |
| Strip strip = STRIP_WHITESPACE; // To avoid compiler warnings. |
| if (FLAG_strip == "STRIP_WHITESPACE" || FLAG_strip == "") |
| strip = STRIP_WHITESPACE; |
| else if (FLAG_strip == "STRIP_BLANK_LINES") |
| strip = STRIP_BLANK_LINES; |
| else if (FLAG_strip == "DO_NOT_STRIP") |
| strip = DO_NOT_STRIP; |
| else |
| LogPrintf(LOG_FATAL, "Unrecognized Strip: %s. Must be one of: " |
| "STRIP_WHITESPACE, STRIP_BLANK_LINES or DO_NOT_STRIP\n", |
| FLAG_strip.c_str()); |
| |
| const char* filename_a = argv[optind]; |
| const char* filename_b = argv[optind + 1]; |
| LogPrintf(LOG_VERBOSE, "------ Diff of [%s, %s] ------\n", |
| filename_a, filename_b); |
| |
| if (DiffTemplates(filename_a, filename_b, strip)) |
| return 0; |
| else |
| return 1; |
| } |