working on image and nightly housekeeping bot

Add more examples and docs for SkImage; still a ways to go.
Fix bit-rotted examples.
Add typedef support.
Add json driver to pick files to work on; remove special-casing.
Fix unordered map traversal that made md output unreliable.

TBR=rmistry@google.com
Docs-Preview: https://skia.org/?cl=80060
Bug: skia:6898
Change-Id: Ib8eb9fdfa5a9db61c8332e657fa2e2f4b96a665f
Reviewed-on: https://skia-review.googlesource.com/80060
Reviewed-by: Cary Clark <caryclark@skia.org>
Commit-Queue: Cary Clark <caryclark@skia.org>
diff --git a/tools/bookmaker/bookmaker.cpp b/tools/bookmaker/bookmaker.cpp
index e048375..b4db36a 100644
--- a/tools/bookmaker/bookmaker.cpp
+++ b/tools/bookmaker/bookmaker.cpp
@@ -7,6 +7,7 @@
 
 #include "bookmaker.h"
 
+DEFINE_string2(status, a, "", "File containing status of documentation. (Use in place of -b -i)");
 DEFINE_string2(bmh, b, "", "Path to a *.bmh file or a directory.");
 DEFINE_bool2(catalog, c, false, "Write example catalog.htm. (Requires -b -f -r)");
 DEFINE_string2(examples, e, "", "File of fiddlecli input, usually fiddle.json (For now, disables -r -f -s)");
@@ -1056,6 +1057,71 @@
     }
 }
 
+string TextParser::typedefName() {
+    // look for typedef as one of three forms:
+    // typedef return-type (*NAME)(params);
+    // typedef alias NAME;
+    // typedef std::function<alias> NAME;
+    string builder;
+    const char* end = this->doubleLF();
+    if (!end) {
+       end = fEnd;
+    }
+    const char* altEnd = this->strnstr("#Typedef ##", end);
+    if (altEnd) {
+        end = this->strnchr('\n', end);
+    }
+    if (!end) {
+        return this->reportError<string>("missing typedef std::function end bracket >");
+    }
+
+    if (this->startsWith("std::function")) {
+        if (!this->skipToEndBracket('>')) {
+            return this->reportError<string>("missing typedef std::function end bracket >");
+        }
+        this->next();
+        this->skipWhiteSpace();
+        builder += string(fChar, end - fChar);
+    } else {
+        const char* paren = this->strnchr('(', end);
+        if (!paren) {
+            const char* lastWord = nullptr;
+            do {
+                this->skipToWhiteSpace();
+                if (fChar < end && isspace(fChar[0])) {
+                    this->skipWhiteSpace();
+                    lastWord = fChar;
+                } else {
+                    break;
+                }
+            } while (true);
+            if (!lastWord) {
+                return this->reportError<string>("missing typedef name");
+            }
+            builder += string(lastWord, end - lastWord);
+        } else {
+            this->skipTo(paren);
+            this->next();
+            if ('*' != this->next()) {
+                return this->reportError<string>("missing typedef function asterisk");
+            }
+            const char* nameStart = fChar;
+            if (!this->skipToEndBracket(')')) {
+                return this->reportError<string>("missing typedef function )");
+            }
+            builder += string(nameStart, fChar - nameStart);
+            if (!this->skipToEndBracket('(')) {
+                return this->reportError<string>("missing typedef params (");
+            }
+            if (! this->skipToEndBracket(')')) {
+                return this->reportError<string>("missing typedef params )");
+            }
+            this->skipTo(end);
+        }
+    }
+    return builder;
+}
+
 bool BmhParser::skipNoName() {
     if ('\n' == this->peek()) {
         this->next();
@@ -1253,67 +1319,12 @@
         SkASSERT(fMC != this->peek());
         return fParent->fName;
     }
-    // look for typedef as one of three forms:
-    // typedef return-type (*NAME)(params);
-    // typedef alias NAME;
-    // typedef std::function<alias> NAME;
     string builder;
-    const char* end = this->doubleLF();
-    if (!end) {
-       end = fEnd;
+    const Definition* parent = this->parentSpace();
+    if (parent && parent->fName.length() > 0) {
+        builder = parent->fName + "::";
     }
-    const char* altEnd = this->strnstr("#Typedef ##", end);
-    if (altEnd) {
-        end = this->strnchr('\n', end);
-    }
-    if (!end) {
-        return this->reportError<string>("missing typedef std::function end bracket >");
-    }
-
-    if (this->startsWith("std::function")) {
-        if (!this->skipToEndBracket('>')) {
-            return this->reportError<string>("missing typedef std::function end bracket >");
-        }
-        this->next();
-        this->skipWhiteSpace();
-        builder = string(fChar, end - fChar);
-    } else {
-        const char* paren = this->strnchr('(', end);
-        if (!paren) {
-            const char* lastWord = nullptr;
-            do {
-                this->skipToWhiteSpace();
-                if (fChar < end && isspace(fChar[0])) {
-                    this->skipWhiteSpace();
-                    lastWord = fChar;
-                } else {
-                    break;
-                }
-            } while (true);
-            if (!lastWord) {
-                return this->reportError<string>("missing typedef name");
-            }
-            builder = string(lastWord, end - lastWord);
-        } else {
-            this->skipTo(paren);
-            this->next();
-            if ('*' != this->next()) {
-                return this->reportError<string>("missing typedef function asterisk");
-            }
-            const char* nameStart = fChar;
-            if (!this->skipToEndBracket(')')) {
-                return this->reportError<string>("missing typedef function )");
-            }
-            builder = string(nameStart, fChar - nameStart);
-            if (!this->skipToEndBracket('(')) {
-                return this->reportError<string>("missing typedef params (");
-            }
-            if (! this->skipToEndBracket(')')) {
-                return this->reportError<string>("missing typedef params )");
-            }
-            this->skipTo(end);
-        }
-    }
+    builder += TextParser::typedefName();
     return uniqueRootName(builder, MarkType::kTypedef);
 }
 
@@ -1348,9 +1359,6 @@
         return markType == def.fMarkType && def.fName == numBuilder;
     };
 
-        if (string::npos != base.find("SkMatrix::operator")) {
-            SkDebugf("");
-        }
     string builder(base);
     if (!builder.length()) {
         builder = fParent->fName;
@@ -1389,9 +1397,6 @@
             cloned->fCloned = true;
         }
         fCloned = true;
-        if (string::npos != builder.find("operator")) {
-            SkDebugf("");
-        }
         numBuilder = builder + '_' + to_string(number);
     } while (++number);
     return numBuilder;
@@ -1485,8 +1490,8 @@
         "              bookmaker -b path/to/bmh_files -e fiddle.json\n"
         "              ~/go/bin/fiddlecli --input fiddle.json --output fiddleout.json\n"
         "              bookmaker -b path/to/bmh_files -f fiddleout.json -r path/to/md_files\n"
-        "              bookmaker -b path/to/bmh_files -i path/to/include.h -x\n"
-        "              bookmaker -b path/to/bmh_files -i path/to/include.h -p\n");
+        "              bookmaker -a path/to/status.json -x\n"
+        "              bookmaker -a path/to/status.json -p\n");
     bool help = false;
     for (int i = 1; i < argc; i++) {
         if (0 == strcmp("-h", argv[i]) || 0 == strcmp("--help", argv[i])) {
@@ -1505,23 +1510,38 @@
     } else {
         SkCommandLineFlags::PrintUsage();
         const char* const commands[] = { "", "-h", "bmh", "-h", "examples", "-h", "include", "-h", "fiddle",
-            "-h", "ref", "-h", "tokens",
+            "-h", "ref", "-h", "status", "-h", "tokens",
             "-h", "crosscheck", "-h", "populate", "-h", "spellcheck" };
         SkCommandLineFlags::Parse(SK_ARRAY_COUNT(commands), commands);
         return 0;
     }
-    if (FLAGS_bmh.isEmpty() && FLAGS_include.isEmpty()) {
-        SkDebugf("requires -b or -i\n");
+    if (FLAGS_bmh.isEmpty() && FLAGS_include.isEmpty() && FLAGS_status.isEmpty()) {
+        SkDebugf("requires at least one of: -b -i -a\n");
         SkCommandLineFlags::PrintUsage();
         return 1;
     }
-    if ((FLAGS_bmh.isEmpty() || FLAGS_fiddle.isEmpty() || FLAGS_ref.isEmpty()) && FLAGS_catalog) {
-        SkDebugf("-c requires -b -f -r\n");
+    if (!FLAGS_bmh.isEmpty() && !FLAGS_status.isEmpty()) {
+        SkDebugf("requires -b or -a but not both\n");
         SkCommandLineFlags::PrintUsage();
         return 1;
     }
-    if (FLAGS_bmh.isEmpty() && !FLAGS_examples.isEmpty()) {
-        SkDebugf("-e requires -b\n");
+    if (!FLAGS_include.isEmpty() && !FLAGS_status.isEmpty()) {
+        SkDebugf("requires -i or -a but not both\n");
+        SkCommandLineFlags::PrintUsage();
+        return 1;
+    }
+    if (FLAGS_bmh.isEmpty() && FLAGS_status.isEmpty() && FLAGS_catalog) {
+         SkDebugf("-c requires -b or -a\n");
+        SkCommandLineFlags::PrintUsage();
+        return 1;
+    }
+    if ((FLAGS_fiddle.isEmpty() || FLAGS_ref.isEmpty()) && FLAGS_catalog) {
+        SkDebugf("-c requires -f -r\n");
+        SkCommandLineFlags::PrintUsage();
+        return 1;
+    }
+    if (FLAGS_bmh.isEmpty() && FLAGS_status.isEmpty() && !FLAGS_examples.isEmpty()) {
+        SkDebugf("-e requires -b or -a\n");
         SkCommandLineFlags::PrintUsage();
         return 1;
     }
@@ -1538,18 +1558,19 @@
         }
         return 0;
     }
-    if ((FLAGS_include.isEmpty() || FLAGS_bmh.isEmpty()) && FLAGS_populate) {
-        SkDebugf("-p requires -b -i\n");
+    if ((FLAGS_include.isEmpty() || FLAGS_bmh.isEmpty()) && FLAGS_status.isEmpty() &&
+            FLAGS_populate) {
+        SkDebugf("-p requires -b -i or -a\n");
         SkCommandLineFlags::PrintUsage();
         return 1;
     }
-    if (FLAGS_bmh.isEmpty() && !FLAGS_ref.isEmpty()) {
-        SkDebugf("-r requires -b\n");
+    if (FLAGS_bmh.isEmpty() && FLAGS_status.isEmpty() && !FLAGS_ref.isEmpty()) {
+        SkDebugf("-r requires -b or -a\n");
         SkCommandLineFlags::PrintUsage();
         return 1;
     }
-    if (FLAGS_bmh.isEmpty() && !FLAGS_spellcheck.isEmpty()) {
-        SkDebugf("-s requires -b\n");
+    if (FLAGS_bmh.isEmpty() && FLAGS_status.isEmpty() && !FLAGS_spellcheck.isEmpty()) {
+        SkDebugf("-s requires -b or -a\n");
         SkCommandLineFlags::PrintUsage();
         return 1;
     }
@@ -1558,8 +1579,9 @@
         SkCommandLineFlags::PrintUsage();
         return 1;
     }
-    if ((FLAGS_include.isEmpty() || FLAGS_bmh.isEmpty()) && FLAGS_crosscheck) {
-        SkDebugf("-x requires -b -i\n");
+    if ((FLAGS_include.isEmpty() || FLAGS_bmh.isEmpty()) && FLAGS_status.isEmpty() &&
+            FLAGS_crosscheck) {
+        SkDebugf("-x requires -b -i or -a\n");
         SkCommandLineFlags::PrintUsage();
         return 1;
     }
@@ -1568,31 +1590,51 @@
         if (!bmhParser.parseFile(FLAGS_bmh[0], ".bmh")) {
             return -1;
         }
+    } else if (!FLAGS_status.isEmpty()) {
+        bmhParser.reset();
+        if (!bmhParser.parseStatus(FLAGS_status[0], ".bmh", StatusFilter::kInProgress)) {
+            return -1;
+        }
     }
     bool done = false;
-    if (!FLAGS_include.isEmpty()) {
-        if (FLAGS_tokens || FLAGS_crosscheck) {
+    if (!FLAGS_include.isEmpty() && FLAGS_tokens) {
+        IncludeParser includeParser;
+        includeParser.validate();
+        if (!includeParser.parseFile(FLAGS_include[0], ".h")) {
+            return -1;
+        }
+        if (FLAGS_tokens) {
+            includeParser.fDebugOut = FLAGS_stdout;
+            if (includeParser.dumpTokens(FLAGS_bmh[0])) {
+                bmhParser.fWroteOut = true;
+            }
+            done = true;
+        }
+    } else if (!FLAGS_include.isEmpty() || !FLAGS_status.isEmpty()) {
+        if (FLAGS_crosscheck) {
             IncludeParser includeParser;
             includeParser.validate();
-            if (!includeParser.parseFile(FLAGS_include[0], ".h")) {
+            if (!FLAGS_include.isEmpty() &&
+                    !includeParser.parseFile(FLAGS_include[0], ".h")) {
                 return -1;
             }
-            if (FLAGS_tokens) {
-                includeParser.fDebugOut = FLAGS_stdout;
-                if (includeParser.dumpTokens(FLAGS_bmh[0])) {
-                    bmhParser.fWroteOut = true;
-                }
-                done = true;
-            } else if (FLAGS_crosscheck) {
-                if (!includeParser.crossCheck(bmhParser)) {
-                    return -1;
-                }
-                done = true;
+            if (!FLAGS_status.isEmpty() && !includeParser.parseStatus(FLAGS_status[0], ".h",
+                    StatusFilter::kCompleted)) {
+                return -1;
             }
+            if (!includeParser.crossCheck(bmhParser)) {
+                return -1;
+            }
+            done = true;
         } else if (FLAGS_populate) {
             IncludeWriter includeWriter;
             includeWriter.validate();
-            if (!includeWriter.parseFile(FLAGS_include[0], ".h")) {
+            if (!FLAGS_include.isEmpty() &&
+                    !includeWriter.parseFile(FLAGS_include[0], ".h")) {
+                return -1;
+            }
+            if (!FLAGS_status.isEmpty() && !includeWriter.parseStatus(FLAGS_status[0], ".h",
+                    StatusFilter::kCompleted)) {
                 return -1;
             }
             includeWriter.fDebugOut = FLAGS_stdout;
@@ -1603,22 +1645,25 @@
             done = true;
         }
     }
-    if (!done && !FLAGS_catalog && !FLAGS_fiddle.isEmpty() && FLAGS_examples.isEmpty()) {
+    if (!done && !FLAGS_fiddle.isEmpty() && FLAGS_examples.isEmpty()) {
         FiddleParser fparser(&bmhParser);
         if (!fparser.parseFile(FLAGS_fiddle[0], ".txt")) {
             return -1;
         }
     }
     if (!done && FLAGS_catalog && FLAGS_examples.isEmpty()) {
-        Catalog fparser(&bmhParser);
-        fparser.fDebugOut = FLAGS_stdout;
-        if (!fparser.openCatalog(FLAGS_bmh[0], FLAGS_ref[0])) {
+        Catalog cparser(&bmhParser);
+        cparser.fDebugOut = FLAGS_stdout;
+        if (!FLAGS_bmh.isEmpty() && !cparser.openCatalog(FLAGS_bmh[0], FLAGS_ref[0])) {
             return -1;
         }
-        if (!fparser.parseFile(FLAGS_fiddle[0], ".txt")) {
+        if (!FLAGS_status.isEmpty() && !cparser.openStatus(FLAGS_status[0], FLAGS_ref[0])) {
             return -1;
         }
-        if (!fparser.closeCatalog()) {
+        if (!cparser.parseFile(FLAGS_fiddle[0], ".txt")) {
+            return -1;
+        }
+        if (!cparser.closeCatalog()) {
             return -1;
         }
         bmhParser.fWroteOut = true;
@@ -1627,12 +1672,20 @@
     if (!done && !FLAGS_ref.isEmpty() && FLAGS_examples.isEmpty()) {
         MdOut mdOut(bmhParser);
         mdOut.fDebugOut = FLAGS_stdout;
-        if (mdOut.buildReferences(FLAGS_bmh[0], FLAGS_ref[0])) {
+        if (!FLAGS_bmh.isEmpty() && mdOut.buildReferences(FLAGS_bmh[0], FLAGS_ref[0])) {
+            bmhParser.fWroteOut = true;
+        }
+        if (!FLAGS_status.isEmpty() && mdOut.buildStatus(FLAGS_status[0], FLAGS_ref[0])) {
             bmhParser.fWroteOut = true;
         }
     }
     if (!done && !FLAGS_spellcheck.isEmpty() && FLAGS_examples.isEmpty()) {
-        bmhParser.spellCheck(FLAGS_bmh[0], FLAGS_spellcheck);
+        if (!FLAGS_bmh.isEmpty()) {
+            bmhParser.spellCheck(FLAGS_bmh[0], FLAGS_spellcheck);
+        }
+        if (!FLAGS_status.isEmpty()) {
+            bmhParser.spellStatus(FLAGS_status[0], FLAGS_spellcheck);
+        }
         bmhParser.fWroteOut = true;
         done = true;
     }
diff --git a/tools/bookmaker/bookmaker.h b/tools/bookmaker/bookmaker.h
index 2d0f03c..d4126ce 100644
--- a/tools/bookmaker/bookmaker.h
+++ b/tools/bookmaker/bookmaker.h
@@ -10,6 +10,7 @@
 
 #include "SkCommandLineFlags.h"
 #include "SkData.h"
+#include "SkJSONCPP.h"
 
 #include <algorithm>
 #include <cmath>
@@ -175,6 +176,12 @@
     kPreprocessor,
 };
 
+enum class StatusFilter {
+    kCompleted,
+    kInProgress,
+    kUnknown,
+};
+
 struct IncludeKey {
     const char* fName;
     KeyWord fKeyWord;
@@ -228,6 +235,7 @@
     TextParser() {}  // only for ParserCommon to call
     friend class ParserCommon;
 public:
+    virtual ~TextParser() {}
     class Save {
     public:
         Save(TextParser* parser) {
@@ -636,6 +644,10 @@
         }
     }
 
+    // FIXME: nothing else in TextParser knows from C++ --
+    // there could be a class between TextParser and ParserCommon
+    virtual string typedefName();
+
     const char* wordEnd() const {
         const char* end = fChar;
         while (isalnum(end[0]) || '_' == end[0] || '-' == end[0]) {
@@ -690,7 +702,7 @@
         fEnd = writer;
     }
 
-    virtual ~EscapeParser() {
+    ~EscapeParser() override {
         delete fStorage;
     }
 private:
@@ -939,6 +951,7 @@
     unordered_map<string, Definition*> fMembers;
     unordered_map<string, Definition*> fMethods;
     unordered_map<string, Definition*> fStructs;
+    unordered_map<string, Definition*> fTypedefs;
 };
 
 struct Reference {
@@ -965,7 +978,7 @@
     {
     }
 
-    virtual ~ParserCommon() {
+    ~ParserCommon() override {
     }
 
     void addDefinition(Definition* def) {
@@ -1018,6 +1031,7 @@
     }
 
     bool parseFile(const char* file, const char* suffix);
+    bool parseStatus(const char* file, const char* suffix, StatusFilter filter);
     virtual bool parseFromFile(const char* path) = 0;
     bool parseSetup(const char* path);
 
@@ -1053,7 +1067,6 @@
         fMaxLF = 1;
     }
 
-
     void writeBlock(int size, const char* data) {
         SkAssertResult(writeBlockTrim(size, data));
     }
@@ -1109,7 +1122,28 @@
     typedef TextParser INHERITED;
 };
 
+struct JsonStatus {
+    const Json::Value& fObject;
+    Json::Value::iterator fIter;
+    string fName;
+};
 
+class StatusIter : public ParserCommon {
+public:
+    StatusIter(const char* statusFile, const char* suffix, StatusFilter);
+    ~StatusIter() override {}
+    string baseDir();
+    bool empty() { return fStack.empty(); }
+    bool next(string* file);
+protected:
+    bool parseFromFile(const char* path) override;
+    void reset() override;
+private:
+    vector<JsonStatus> fStack;
+    Json::Value fRoot;
+    const char* fSuffix;
+    StatusFilter fFilter;
+};
 
 class BmhParser : public ParserCommon {
 public:
@@ -1290,9 +1324,10 @@
     bool skipNoName();
     bool skipToDefinitionEnd(MarkType markType);
     void spellCheck(const char* match, SkCommandLineFlags::StringArray report) const;
+    void spellStatus(const char* match, SkCommandLineFlags::StringArray report) const;
     vector<string> topicName();
     vector<string> typeName(MarkType markType, bool* expectEnd);
-    string typedefName();
+    string typedefName() override;
     string uniqueName(const string& base, MarkType markType);
     string uniqueRootName(const string& base, MarkType markType);
     void validate() const;
@@ -1473,6 +1508,7 @@
     bool parseEnum(Definition* child, Definition* markupDef);
 
     bool parseFromFile(const char* path) override {
+        this->reset();
         if (!INHERITED::parseSetup(path)) {
             return false;
         }
@@ -1486,7 +1522,7 @@
     bool parseObject(Definition* child, Definition* markupDef);
     bool parseObjects(Definition* parent, Definition* markupDef);
     bool parseTemplate();
-    bool parseTypedef();
+    bool parseTypedef(Definition* child, Definition* markupDef);
     bool parseUnion();
 
     void popBracket() {
@@ -1783,7 +1819,6 @@
         fMethodDef = nullptr;
         fBmhStructDef = nullptr;
         fAttrDeprecated = nullptr;
-        fAnonymousEnumCount = 1;
         fInStruct = false;
         fWroteMethod = false;
         fIndentNext = false;
@@ -1883,6 +1918,7 @@
     bool appendFile(const string& path);
     bool closeCatalog();
     bool openCatalog(const char* inDir, const char* outDir);
+    bool openStatus(const char* inDir, const char* outDir);
 
     bool parseFromFile(const char* path) override ;
 private:
@@ -1926,6 +1962,7 @@
     }
 
     bool buildReferences(const char* path, const char* outDir);
+    bool buildStatus(const char* path, const char* outDir);
 private:
     enum class TableState {
         kNone,
@@ -2003,6 +2040,8 @@
         , fClassName(className) {
     }
 
+    ~MethodParser() override {}
+
     void skipToMethodStart() {
         if (!fClassName.length()) {
             this->skipToAlphaNum();
diff --git a/tools/bookmaker/cataloger.cpp b/tools/bookmaker/cataloger.cpp
index 48e3b48..c227437 100644
--- a/tools/bookmaker/cataloger.cpp
+++ b/tools/bookmaker/cataloger.cpp
@@ -50,6 +50,17 @@
     return true;
 }
 
+bool Catalog::openStatus(const char* statusFile, const char* outDir) {
+    StatusIter iter(statusFile, ".bmh", StatusFilter::kInProgress);
+    string unused;
+    // FIXME: iterate through only chosen files by setting fDocsDir to iter
+    // read one file to find directory
+    if (!iter.next(&unused)) {
+        return false;
+    }
+    return openCatalog(iter.baseDir().c_str(), outDir);
+}
+
 bool Catalog::closeCatalog() {
     if (fOut) {
         this->lf(1);
diff --git a/tools/bookmaker/definition.cpp b/tools/bookmaker/definition.cpp
index 44da2df..91592f9 100644
--- a/tools/bookmaker/definition.cpp
+++ b/tools/bookmaker/definition.cpp
@@ -541,7 +541,7 @@
                 break;
             case MarkType::kFunction: {
                 // emit this, but don't wrap this in draw()
-                string funcText(iter->fContentStart, iter->fContentEnd - iter->fContentStart - 1);
+                string funcText(iter->fContentStart, iter->fContentEnd - iter->fContentStart);
                 size_t pos = 0;
                 while (pos < funcText.length() && ' ' > funcText[pos]) {
                     ++pos;
@@ -576,9 +576,6 @@
     size_t end = text.length();
     size_t outIndent = 0;
     size_t textIndent = count_indent(text, pos, end);
-    if ("MakeFromBackendTexture" == fName) {
-        SkDebugf("");
-    }
     if (fWrapper.length() > 0) {
         code += fWrapper;
         code += "\\n";
@@ -899,9 +896,6 @@
     const char* lastStart = methodParser.fChar;
     const int limit = 100;  // todo: allow this to be set by caller or in global or something
     string name = this->methodName();
-    if ("MakeFromBackendTextureAsRenderTarget" == name) {
-        SkDebugf("");
-    }
     const char* nameInParser = methodParser.strnstr(name.c_str(), methodParser.fEnd);
     methodParser.skipTo(nameInParser);
     const char* lastEnd = methodParser.fChar;
@@ -1227,10 +1221,6 @@
             if ("SkBitmap::validate()" == leaf.first) {
                 continue;
             }
-            // typedef uint32_t SaveLayerFlags not seen in SkCanvas.h, don't know why
-            if ("SaveLayerFlags" == leaf.first) {
-                continue;
-            }
             // SkPath::pathRefIsValid in #ifdef ; prefer to remove chrome dependency to fix
             if ("SkPath::pathRefIsValid" == leaf.first) {
                 continue;
diff --git a/tools/bookmaker/includeParser.cpp b/tools/bookmaker/includeParser.cpp
index 955e6ef..cd6e9e3 100644
--- a/tools/bookmaker/includeParser.cpp
+++ b/tools/bookmaker/includeParser.cpp
@@ -426,6 +426,13 @@
                         SkDebugf("member missing from bmh: %s\n", fullName.c_str());
                     }
                     break;
+                case MarkType::kTypedef:
+                    if (def) {
+                        def->fVisited = true;
+                    } else {
+                        SkDebugf("typedef missing from bmh: %s\n", fullName.c_str());
+                    }
+                    break;
                 default:
                     SkASSERT(0);  // unhandled
                     break;
@@ -1474,9 +1481,6 @@
         break;
     }
     tokenIter->fName = nameStr;
-    if ("operator" == nameStr) {
-        SkDebugf("");
-    }
     tokenIter->fMarkType = MarkType::kMethod;
     tokenIter->fPrivate = string::npos != nameStr.find("::");
     auto testIter = child->fParent->fTokens.begin();
@@ -1538,9 +1542,6 @@
         auto globalFunction = &fIFunctionMap[name];
         globalFunction->fContentStart = start;
         globalFunction->fName = name;
-        if ("operator+" == name) {
-            SkDebugf("");
-        }
         globalFunction->fFiddle = name;
         globalFunction->fContentEnd = end;
         globalFunction->fMarkType = MarkType::kMethod;
@@ -1555,9 +1556,6 @@
     SkASSERT(classDef.fStart);
     string uniqueName = this->uniqueName(classDef.fMethods, nameStr);
     markupChild->fName = uniqueName;
-        if ("operator+" == uniqueName) {
-            SkDebugf("");
-        }
     if (!this->findComments(*child, markupChild)) {
         return false;
     }
@@ -1603,7 +1601,7 @@
                     }
                     break;
                 case KeyWord::kTypedef:
-                    if (!this->parseTypedef()) {
+                    if (!this->parseTypedef(child, markupDef)) {
                         return child->reportError<bool>("failed to parse typedef");
                     }
                     break;
@@ -1720,8 +1718,29 @@
     return true;
 }
 
-bool IncludeParser::parseTypedef() {
-
+bool IncludeParser::parseTypedef(Definition* child, Definition* markupDef) {
+    TextParser typedefParser(child);
+    string nameStr = typedefParser.typedefName();
+    if (!markupDef) {
+        Definition& typedefDef = fITypedefMap[nameStr];
+        SkASSERT(!typedefDef.fStart);
+        typedefDef.fStart = child->fContentStart;
+        typedefDef.fContentStart = child->fContentStart;
+        typedefDef.fName = nameStr;
+        typedefDef.fFiddle = nameStr;
+        typedefDef.fContentEnd = child->fContentEnd;
+        typedefDef.fTerminator = child->fContentEnd;
+        typedefDef.fMarkType = MarkType::kTypedef;
+        typedefDef.fLineCount = child->fLineCount;
+        return true;
+    }
+    markupDef->fTokens.emplace_back(MarkType::kTypedef, child->fContentStart, child->fContentEnd,
+        child->fLineCount, markupDef);
+    Definition* markupChild = &markupDef->fTokens.back();
+    markupChild->fName = nameStr;
+    markupChild->fTerminator = markupChild->fContentEnd;
+    IClassDefinition& classDef = fIClassMap[markupDef->fName];
+    classDef.fTypedefs[nameStr] = markupChild;
     return true;
 }
 
diff --git a/tools/bookmaker/includeWriter.cpp b/tools/bookmaker/includeWriter.cpp
index 8c6d7a9..b18585f 100644
--- a/tools/bookmaker/includeWriter.cpp
+++ b/tools/bookmaker/includeWriter.cpp
@@ -8,9 +8,6 @@
 #include "bookmaker.h"
 
 void IncludeWriter::descriptionOut(const Definition* def) {
-    if ("SkPoint_length" == def->fFiddle) {
-        SkDebugf("");
-    }
     const char* commentStart = def->fContentStart;
     int commentLen = (int) (def->fContentEnd - commentStart);
     bool breakOut = false;
@@ -911,9 +908,6 @@
     const Definition* requireDense = nullptr;
     const Definition* startDef = nullptr;
     for (auto& child : def->fTokens) {
-        if (18 == child.fParentIndex) {
-            SkDebugf("");
-        }
         if (KeyWord::kOperator == child.fKeyWord && method &&
                 Definition::MethodType::kOperator == method->fMethodType) {
             eatOperator = true;
@@ -1034,9 +1028,6 @@
                     --continueEnd;
                 }
                 methodName += string(fContinuation, continueEnd - fContinuation);
-                if ("SkIPoint::operator+" == methodName) {
-                    SkDebugf("");
-                }
                 method = root->find(methodName, RootDefinition::AllowParens::kNo);
                 if (!method) {
                     fLineCount = child.fLineCount;
@@ -1093,9 +1084,6 @@
             startDef = &child;
             fStart = child.fContentStart;
             methodName = root->fName + "::" + child.fName;
-                if ("SkIPoint::operator+" == methodName) {
-                    SkDebugf("");
-                }
             inConstructor = root->fName == child.fName;
             fContinuation = child.fContentEnd;
             method = root->find(methodName, RootDefinition::AllowParens::kNo);
@@ -1447,6 +1435,7 @@
         root->clearVisited();
         fStart = includeMapper.second.fContentStart;
         fEnd = includeMapper.second.fContentEnd;
+        fAnonymousEnumCount = 1;
         allPassed &= this->populate(&includeMapper.second, nullptr, root);
         this->writeBlock((int) (fEnd - fStart), fStart);
         fIndent = 0;
diff --git a/tools/bookmaker/mdOut.cpp b/tools/bookmaker/mdOut.cpp
index 6b082ad..216a104 100644
--- a/tools/bookmaker/mdOut.cpp
+++ b/tools/bookmaker/mdOut.cpp
@@ -63,9 +63,6 @@
         } else {
             leadingSpaces = string(base, wordStart - base);
         }
-        if (!strncmp("SkPoint::operator-()", start, 20)) {
-            SkDebugf("");
-        }
         t.skipToMethodEnd();
         if (base == t.fChar) {
             break;
@@ -77,9 +74,6 @@
             continue;
         }
         ref = string(start, t.fChar - start);
-        if (412 == t.fLineCount) {
-            SkDebugf("");
-        }
         if (const Definition* def = this->isDefined(t, ref,
                 BmhParser::Resolvable::kOut != resolvable)) {
             SkASSERT(def->fFiddle.length());
@@ -218,6 +212,9 @@
                 continue;
             }
         }
+        if ("RasterReleaseProc" == ref) {
+            SkDebugf("");
+        }
         Definition* test = fRoot;
         do {
             if (!test->isRoot()) {
@@ -269,6 +266,19 @@
     return true;
 }
 
+bool MdOut::buildStatus(const char* statusFile, const char* outDir) {
+    StatusIter iter(statusFile, ".bmh", StatusFilter::kInProgress);
+    for (string file; iter.next(&file); ) {
+        SkString p = SkOSPath::Join(iter.baseDir().c_str(), file.c_str());
+        const char* hunk = p.c_str();
+        if (!this->buildRefFromFile(hunk, outDir)) {
+            SkDebugf("failed to parse %s\n", hunk);
+            return false;
+        }
+    }
+    return true;
+}
+
 bool MdOut::buildRefFromFile(const char* name, const char* outDir) {
     fFileName = string(name);
     string filename(name);
@@ -285,8 +295,16 @@
     match += ".bmh";
     fOut = nullptr;
     string fullName;
-    for (const auto& topic : fBmhParser.fTopicMap) {
-        Definition* topicDef = topic.second;
+
+    vector<string> keys;
+    keys.reserve(fBmhParser.fTopicMap.size());
+    for (const auto& it : fBmhParser.fTopicMap) {
+        keys.push_back(it.first);
+    }
+    std::sort(keys.begin(), keys.end());
+    for (auto key : keys) {
+        string s(key);
+        auto topicDef = fBmhParser.fTopicMap.at(s);
         if (topicDef->fParent) {
             continue;
         }
@@ -723,16 +741,18 @@
                     gpuAndCpu = platParse.strnstr("cpu", platParse.fEnd);
                 }
             }
-            if (fHasFiddle) {
+            if (fHasFiddle && !def->hasChild(MarkType::kError)) {
+                SkASSERT(def->fHash.length() > 0);
                 fprintf(fOut, "<div><fiddle-embed name=\"%s\"", def->fHash.c_str());
                 if (showGpu) {
-                    fprintf(fOut, "gpu=\"true\"");
+                    fprintf(fOut, " gpu=\"true\"");
                     if (gpuAndCpu) {
-                        fprintf(fOut, "cpu=\"true\"");
+                        fprintf(fOut, " cpu=\"true\"");
                     }
                 }
                 fprintf(fOut, ">");
             } else {
+                SkASSERT(def->fHash.length() == 0);
                 fprintf(fOut, "<pre style=\"padding: 1em 1em 1em 1em; font-size: 13px"
                         " width: 62.5em; background-color: #f0f0f0\">");
                 this->lfAlways(1);
diff --git a/tools/bookmaker/parserCommon.cpp b/tools/bookmaker/parserCommon.cpp
index 79503a6..af1031a 100644
--- a/tools/bookmaker/parserCommon.cpp
+++ b/tools/bookmaker/parserCommon.cpp
@@ -16,7 +16,6 @@
 }
 
 bool ParserCommon::parseFile(const char* fileOrPath, const char* suffix) {
-//    this->reset();
     if (!sk_isdir(fileOrPath)) {
         if (!this->parseFromFile(fileOrPath)) {
             SkDebugf("failed to parse %s\n", fileOrPath);
@@ -39,9 +38,23 @@
     return true;
 }
 
+bool ParserCommon::parseStatus(const char* statusFile, const char* suffix, StatusFilter filter) {
+    StatusIter iter(statusFile, suffix, filter);
+    if (iter.empty()) {
+        return false;
+    }
+    for (string file; iter.next(&file); ) {
+        SkString p = SkOSPath::Join(iter.baseDir().c_str(), file.c_str());
+        const char* hunk = p.c_str();
+        if (!this->parseFromFile(hunk)) {
+            SkDebugf("failed to parse %s\n", hunk);
+            return false;
+        }
+    }
+    return true;
+}
 
 bool ParserCommon::parseSetup(const char* path) {
-//    this->reset();
     sk_sp<SkData> data = SkData::MakeFromFileName(path);
     if (nullptr == data.get()) {
         SkDebugf("%s missing\n", path);
@@ -210,3 +223,105 @@
     fLinefeeds = 0;
     fMaxLF = 2;
 }
+
+StatusIter::StatusIter(const char* statusFile, const char* suffix, StatusFilter filter)
+    : fSuffix(suffix)
+    , fFilter(filter) {
+    if (!this->parseFromFile(statusFile)) {
+        return;
+    }
+}
+
+static const char* block_names[] = {
+    "Completed",
+    "InProgress",
+};
+
+string StatusIter::baseDir() {
+    SkASSERT(fStack.back().fObject.isArray());
+    SkASSERT(fStack.size() > 2);
+    string dir;
+    for (unsigned index = 2; index < fStack.size(); ++index) {
+        dir += fStack[index].fName;
+        if (index < fStack.size() - 1) {
+            dir += SkOSPath::SEPARATOR;
+        }
+    }
+    return dir;
+}
+
+// FIXME: need to compare fBlockName against fFilter
+// need to compare fSuffix against next value returned
+bool StatusIter::next(string* str) {
+    JsonStatus* status;
+    do {
+        do {
+            if (fStack.empty()) {
+                return false;
+            }
+            status = &fStack.back();
+            if (status->fIter != status->fObject.end()) {
+                break;
+            }
+            fStack.pop_back();
+        } while (true);
+        if (1 == fStack.size()) {
+            do {
+                StatusFilter blockType = StatusFilter::kUnknown;
+                for (unsigned index = 0; index < SK_ARRAY_COUNT(block_names); ++index) {
+                    if (status->fIter.key().asString() == block_names[index]) {
+                        blockType = (StatusFilter) index;
+                        break;
+                    }
+                }
+                if (blockType <= fFilter) {
+                    break;
+                }
+                status->fIter++;
+            } while (status->fIter != status->fObject.end());
+            if (status->fIter == status->fObject.end()) {
+                continue;
+            }
+        }
+        if (!status->fObject.isArray()) {
+            SkASSERT(status->fIter != status->fObject.end());
+            JsonStatus block = {
+                *status->fIter,
+                status->fIter->begin(),
+                status->fIter.key().asString()
+            };
+            fStack.emplace_back(block);
+            status = &(&fStack.back())[-1];
+            status->fIter++;
+            status = &fStack.back();
+            continue;
+        }
+        *str = status->fIter->asString();
+        status->fIter++;
+        if (str->length() - strlen(fSuffix) == str->find(fSuffix)) {
+            return true;
+        }
+    } while (true);
+    return true;
+}
+
+bool StatusIter::parseFromFile(const char* path) {
+    sk_sp<SkData> json(SkData::MakeFromFileName(path));
+    if (!json) {
+        SkDebugf("file %s:\n", path);
+        return this->reportError<bool>("file not readable");
+    }
+    Json::Reader reader;
+    const char* data = (const char*)json->data();
+    if (!reader.parse(data, data+json->size(), fRoot)) {
+        SkDebugf("file %s:\n", path);
+        return this->reportError<bool>("file not parsable");
+    }
+    JsonStatus block = { fRoot, fRoot.begin(), "" };
+    fStack.emplace_back(block);
+    return true;
+}
+
+void StatusIter::reset() {
+    fStack.clear();
+}
diff --git a/tools/bookmaker/spellCheck.cpp b/tools/bookmaker/spellCheck.cpp
index 6f50cb3..33c0578 100644
--- a/tools/bookmaker/spellCheck.cpp
+++ b/tools/bookmaker/spellCheck.cpp
@@ -96,6 +96,14 @@
     checker.report(report);
 }
 
+void BmhParser::spellStatus(const char* statusFile, SkCommandLineFlags::StringArray report) const {
+    SpellCheck checker(*this);
+    StatusIter iter(statusFile, ".bmh", StatusFilter::kInProgress);
+    string match = iter.baseDir();
+    checker.check(match.c_str());
+    checker.report(report);
+}
+
 bool SpellCheck::check(const char* match) {
     for (const auto& topic : fBmhParser.fTopicMap) {
         Definition* topicDef = topic.second;