skdiff: recurse over subdirectories, unless --norecurse option is given
in anticipation of https://code.google.com/p/skia/issues/detail?id=743 ('move gm baselines outside of trunk, and modify naming convention')
Review URL: https://codereview.appspot.com/6465053

git-svn-id: http://skia.googlecode.com/svn/trunk@5121 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/tools/skdiff_main.cpp b/tools/skdiff_main.cpp
index 9c327df..61fd83b 100644
--- a/tools/skdiff_main.cpp
+++ b/tools/skdiff_main.cpp
@@ -26,7 +26,7 @@
  * third directory for each pair.
  * Creates an index.html in the current third directory to compare each
  * pair that does not match exactly.
- * Does *not* recursively descend directories.
+ * Recursively descends directories, unless run with --norecurse.
  *
  * Returns zero exit code if all images match across baseDir and comparisonDir.
  */
@@ -612,6 +612,27 @@
     dr->fAverageMismatchB = ((float) totalMismatchB) / pixelCount;
 }
 
+/// Return a copy of the "input" string, within which we have replaced all instances
+/// of oldSubstring with newSubstring.
+///
+/// TODO: If we like this, we should move it into the core SkString implementation,
+/// adding more checks and ample test cases, and paying more attention to efficiency.
+static SkString replace_all(const SkString &input,
+                            const char oldSubstring[], const char newSubstring[]) {
+    SkString output;
+    const char *input_cstr = input.c_str();
+    const char *first_char = input_cstr;
+    const char *match_char;
+    int oldSubstringLen = strlen(oldSubstring);
+    while (NULL != (match_char = strstr(first_char, oldSubstring))) {
+        output.append(first_char, (match_char - first_char));
+        output.append(newSubstring);
+        first_char = match_char + oldSubstringLen;
+    }
+    output.append(first_char);
+    return output;
+}
+
 static SkString filename_to_derived_filename (const SkString& filename,
                                               const char *suffix) {
     SkString diffName (filename);
@@ -619,6 +640,10 @@
     int dotOffset = strrchr(cstring, '.') - cstring;
     diffName.remove(dotOffset, diffName.size() - dotOffset);
     diffName.append(suffix);
+
+    // In case we recursed into subdirectories, replace slashes with something else
+    // so the diffs will all be written into a single flat directory.
+    diffName = replace_all(diffName, PATH_DIV_STR, "_");
     return diffName;
 }
 
@@ -686,22 +711,70 @@
     return false;
 }
 
-/// Iterate over dir and get all files that:
-///  - match any of the substrings in matchSubstrings, but...
-///  - DO NOT match any of the substrings in nomatchSubstrings
-/// Returns the list of files in *files.
+/// Internal (potentially recursive) implementation of get_file_list.
+static void get_file_list_subdir(const SkString& rootDir, const SkString& subDir,
+                                 const StringArray& matchSubstrings,
+                                 const StringArray& nomatchSubstrings,
+                                 bool recurseIntoSubdirs, FileArray *files) {
+    bool isSubDirEmpty = subDir.isEmpty();
+    SkString dir(rootDir);
+    if (!isSubDirEmpty) {
+        dir.append(PATH_DIV_STR);
+        dir.append(subDir);
+    }
+
+    // Iterate over files (not directories) within dir.
+    SkOSFile::Iter fileIterator(dir.c_str());
+    SkString fileName;
+    while (fileIterator.next(&fileName, false)) {
+        if (fileName.startsWith(".")) {
+            continue;
+        }
+        SkString pathRelativeToRootDir(subDir);
+        if (!isSubDirEmpty) {
+            pathRelativeToRootDir.append(PATH_DIV_STR);
+        }
+        pathRelativeToRootDir.append(fileName);
+        if (string_contains_any_of(pathRelativeToRootDir, matchSubstrings) &&
+            !string_contains_any_of(pathRelativeToRootDir, nomatchSubstrings)) {
+            files->push(new SkString(pathRelativeToRootDir));
+        }
+    }
+
+    // Recurse into any non-ignored subdirectories.
+    if (recurseIntoSubdirs) {
+        SkOSFile::Iter dirIterator(dir.c_str());
+        SkString dirName;
+        while (dirIterator.next(&dirName, true)) {
+            if (dirName.startsWith(".")) {
+                continue;
+            }
+            SkString pathRelativeToRootDir(subDir);
+            if (!isSubDirEmpty) {
+                pathRelativeToRootDir.append(PATH_DIV_STR);
+            }
+            pathRelativeToRootDir.append(dirName);
+            if (!string_contains_any_of(pathRelativeToRootDir, nomatchSubstrings)) {
+                get_file_list_subdir(rootDir, pathRelativeToRootDir,
+                                     matchSubstrings, nomatchSubstrings, recurseIntoSubdirs,
+                                     files);
+            }
+        }
+    }
+}
+
+/// Iterate over dir and get all files whose filename:
+///  - matches any of the substrings in matchSubstrings, but...
+///  - DOES NOT match any of the substrings in nomatchSubstrings
+///  - DOES NOT start with a dot (.)
+/// Adds the matching files to the list in *files.
 static void get_file_list(const SkString& dir,
                           const StringArray& matchSubstrings,
                           const StringArray& nomatchSubstrings,
-                          FileArray *files) {
-    SkOSFile::Iter it(dir.c_str());
-    SkString filename;
-    while (it.next(&filename)) {
-        if (string_contains_any_of(filename, matchSubstrings) &&
-            !string_contains_any_of(filename, nomatchSubstrings)) {
-            files->push(new SkString(filename));
-        }
-    }
+                          bool recurseIntoSubdirs, FileArray *files) {
+    get_file_list_subdir(dir, SkString(""),
+                         matchSubstrings, nomatchSubstrings, recurseIntoSubdirs,
+                         files);
 }
 
 static void release_file_list(FileArray *files) {
@@ -723,6 +796,7 @@
                                 const SkString& outputDir,
                                 const StringArray& matchSubstrings,
                                 const StringArray& nomatchSubstrings,
+                                bool recurseIntoSubdirs,
                                 DiffSummary* summary) {
     SkASSERT(!baseDir.isEmpty());
     SkASSERT(!comparisonDir.isEmpty());
@@ -730,8 +804,8 @@
     FileArray baseFiles;
     FileArray comparisonFiles;
 
-    get_file_list(baseDir, matchSubstrings, nomatchSubstrings, &baseFiles);
-    get_file_list(comparisonDir, matchSubstrings, nomatchSubstrings,
+    get_file_list(baseDir, matchSubstrings, nomatchSubstrings, recurseIntoSubdirs, &baseFiles);
+    get_file_list(comparisonDir, matchSubstrings, nomatchSubstrings, recurseIntoSubdirs,
                   &comparisonFiles);
 
     if (!baseFiles.isEmpty()) {
@@ -1146,6 +1220,7 @@
 "\n                           filenames contain this substring."
 "\n                           this flag may be repeated."
 "\n    --noprintdirs: do not print the directories used."
+"\n    --norecurse: do not recurse into subdirectories."
 "\n    --sortbymaxmismatch: sort by worst color channel mismatch;"
 "\n                         break ties with -sortbymismatch"
 "\n    --sortbymismatch: sort by average color channel mismatch"
@@ -1180,7 +1255,8 @@
 
     bool generateDiffs = true;
     bool listFilenames = false;
-    bool printDirs = true;
+    bool printDirNames = true;
+    bool recurseIntoSubdirs = true;
 
     RecordArray differences;
     DiffSummary summary;
@@ -1219,7 +1295,11 @@
             continue;
         }
         if (!strcmp(argv[i], "--noprintdirs")) {
-            printDirs = false;
+            printDirNames = false;
+            continue;
+        }
+        if (!strcmp(argv[i], "--norecurse")) {
+            recurseIntoSubdirs = false;
             continue;
         }
         if (!strcmp(argv[i], "--sortbymaxmismatch")) {
@@ -1271,14 +1351,14 @@
     if (!baseDir.endsWith(PATH_DIV_STR)) {
         baseDir.append(PATH_DIV_STR);
     }
-    if (printDirs) {
+    if (printDirNames) {
         printf("baseDir is [%s]\n", baseDir.c_str());
     }
 
     if (!comparisonDir.endsWith(PATH_DIV_STR)) {
         comparisonDir.append(PATH_DIV_STR);
     }
-    if (printDirs) {
+    if (printDirNames) {
         printf("comparisonDir is [%s]\n", comparisonDir.c_str());
     }
 
@@ -1286,11 +1366,11 @@
         outputDir.append(PATH_DIV_STR);
     }
     if (generateDiffs) {
-        if (printDirs) {
+        if (printDirNames) {
             printf("writing diffs to outputDir is [%s]\n", outputDir.c_str());
         }
     } else {
-        if (printDirs) {
+        if (printDirNames) {
             printf("not writing any diffs to outputDir [%s]\n", outputDir.c_str());
         }
         outputDir.set("");
@@ -1304,7 +1384,7 @@
 
     create_diff_images(diffProc, colorThreshold, &differences,
                        baseDir, comparisonDir, outputDir,
-                       matchSubstrings, nomatchSubstrings, &summary);
+                       matchSubstrings, nomatchSubstrings, recurseIntoSubdirs, &summary);
     summary.print(listFilenames, failOnResultType);
 
     if (differences.count()) {