// 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 #undef CTEMPLATE_DLL_DECL #include #include #ifdef HAVE_UNISTD_H # include #endif #include #ifdef HAVE_GETOPT_H # include #endif #include #include #include #include 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 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] [-v] [-b] [-s] \n", argv0); fprintf(outfile, " -t --template_dir= Root directory of templates\n" " -s --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: [:]\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 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; }