aboutsummaryrefslogtreecommitdiffhomepage
path: root/tools/bookmaker/mdOut.cpp
diff options
context:
space:
mode:
authorGravatar Cary Clark <caryclark@skia.org>2017-07-28 11:04:54 -0400
committerGravatar Skia Commit-Bot <skia-commit-bot@chromium.org>2017-07-28 15:30:38 +0000
commit8032b983faaa8c76f81bf3cf028e9c64f4635478 (patch)
treeed3be061ff02a99dab1b3e443d48b7f5c906417e /tools/bookmaker/mdOut.cpp
parentacaa607328fb0dfac0894d4a2fcdead520e696b3 (diff)
bookmaker initial checkin
bookmaker is a tool that generates documentation backends from a canonical markup. Documentation for bookmaker itself is evolving at docs/usingBookmaker.bmh, which is visible online at skia.org/user/api/bmh_usingBookmaker Change-Id: Ic76ddf29134895b5c2ebfbc84603e40ff08caf09 Reviewed-on: https://skia-review.googlesource.com/28000 Commit-Queue: Cary Clark <caryclark@google.com> Reviewed-by: Cary Clark <caryclark@google.com>
Diffstat (limited to 'tools/bookmaker/mdOut.cpp')
-rw-r--r--tools/bookmaker/mdOut.cpp929
1 files changed, 929 insertions, 0 deletions
diff --git a/tools/bookmaker/mdOut.cpp b/tools/bookmaker/mdOut.cpp
new file mode 100644
index 0000000000..a7737d6d70
--- /dev/null
+++ b/tools/bookmaker/mdOut.cpp
@@ -0,0 +1,929 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "bookmaker.h"
+
+#include "SkOSFile.h"
+#include "SkOSPath.h"
+
+static void add_ref(const string& leadingSpaces, const string& ref, string* result) {
+ *result += leadingSpaces + ref;
+}
+
+// FIXME: preserve inter-line spaces and don't add new ones
+string MdOut::addReferences(const char* refStart, const char* refEnd,
+ BmhParser::Resolvable resolvable) {
+ string result;
+ MethodParser t(fRoot ? fRoot->fName : string(), fFileName, refStart, refEnd, fLineCount);
+ bool lineStart = true;
+ string ref;
+ string leadingSpaces;
+ do {
+ const char* base = t.fChar;
+ t.skipWhiteSpace();
+ const char* wordStart = t.fChar;
+ t.skipToMethodStart();
+ const char* start = t.fChar;
+ if (wordStart < start) {
+ if (lineStart) {
+ lineStart = false;
+ } else {
+ wordStart = base;
+ }
+ result += string(wordStart, start - wordStart);
+ if ('\n' != result.back()) {
+ while (start > wordStart && '\n' == start[-1]) {
+ result += '\n';
+ --start;
+ }
+ }
+ }
+ if (lineStart) {
+ lineStart = false;
+ } else {
+ leadingSpaces = string(base, wordStart - base);
+ }
+ t.skipToMethodEnd();
+ if (base == t.fChar) {
+ break;
+ }
+ if (start >= t.fChar) {
+ continue;
+ }
+ if (!t.eof() && '"' == t.peek() && start > wordStart && '"' == start[-1]) {
+ continue;
+ }
+ ref = string(start, t.fChar - start);
+ if (const Definition* def = this->isDefined(t, ref,
+ BmhParser::Resolvable::kOut != resolvable)) {
+ SkASSERT(def->fFiddle.length());
+ if (!t.eof() && '(' == t.peek() && t.strnchr(')', t.fEnd)) {
+ if (!t.skipToEndBracket(')')) {
+ t.reportError("missing close paren");
+ return result;
+ }
+ t.next();
+ string fullRef = string(start, t.fChar - start);
+ // if _2 etc alternates are defined, look for paren match
+ // may ignore () if ref is all lower case
+ // otherwise flag as error
+ int suffix = '2';
+ bool foundMatch = false;
+ const Definition* altDef = def;
+ while (altDef && suffix <= '9') {
+ if ((foundMatch = altDef->paramsMatch(fullRef, ref))) {
+ def = altDef;
+ ref = fullRef;
+ break;
+ }
+ string altTest = ref + '_';
+ altTest += suffix++;
+ altDef = this->isDefined(t, altTest, false);
+ }
+ if (suffix > '9') {
+ t.reportError("too many alts");
+ return result;
+ }
+ if (!foundMatch) {
+ if (!(def = this->isDefined(t, fullRef, true))) {
+ return result;
+ }
+ ref = fullRef;
+ }
+ }
+ result += linkRef(leadingSpaces, def, ref);
+ continue;
+ }
+ if (!t.eof() && '(' == t.peek()) {
+ if (!t.skipToEndBracket(')')) {
+ t.reportError("missing close paren");
+ return result;
+ }
+ t.next();
+ ref = string(start, t.fChar - start);
+ if (const Definition* def = this->isDefined(t, ref, true)) {
+ SkASSERT(def->fFiddle.length());
+ result += linkRef(leadingSpaces, def, ref);
+ continue;
+ }
+ }
+// class, struct, and enum start with capitals
+// methods may start with upper (static) or lower (most)
+
+ // see if this should have been a findable reference
+
+ // look for Sk / sk / SK ..
+ if (!ref.compare(0, 2, "Sk") && ref != "Skew" && ref != "Skews" &&
+ ref != "Skip" && ref != "Skips") {
+ t.reportError("missed Sk prefixed");
+ return result;
+ }
+ if (!ref.compare(0, 2, "SK")) {
+ if (BmhParser::Resolvable::kOut != resolvable) {
+ t.reportError("missed SK prefixed");
+ }
+ return result;
+ }
+ if (!isupper(start[0])) {
+ // TODO:
+ // look for all lowercase w/o trailing parens as mistaken method matches
+ // will also need to see if Example Description matches var in example
+ const Definition* def;
+ if (fMethod && (def = fMethod->hasParam(ref))) {
+ result += linkRef(leadingSpaces, def, ref);
+ continue;
+ } else if (!fInDescription && ref[0] != '0'
+ && string::npos != ref.find_first_of("ABCDEFGHIJKLMNOPQRSTUVWXYZ")) {
+ // FIXME: see isDefined(); check to see if fXX is a member of xx.fXX
+ if (('f' != ref[0] && string::npos == ref.find("()"))
+ || '.' != t.backup(ref.c_str())) {
+ if (BmhParser::Resolvable::kOut != resolvable) {
+ t.reportError("missed camelCase");
+ return result;
+ }
+ }
+ }
+ add_ref(leadingSpaces, ref, &result);
+ continue;
+ }
+ auto topicIter = fBmhParser.fTopicMap.find(ref);
+ if (topicIter != fBmhParser.fTopicMap.end()) {
+ result += linkRef(leadingSpaces, topicIter->second, ref);
+ continue;
+ }
+ bool startsSentence = t.sentenceEnd(start);
+ if (!t.eof() && ' ' != t.peek()) {
+ add_ref(leadingSpaces, ref, &result);
+ continue;
+ }
+ if (t.fChar + 1 >= t.fEnd || (!isupper(t.fChar[1]) && startsSentence)) {
+ add_ref(leadingSpaces, ref, &result);
+ continue;
+ }
+ if (isupper(t.fChar[1]) && startsSentence) {
+ TextParser next(t.fFileName, &t.fChar[1], t.fEnd, t.fLineCount);
+ string nextWord(next.fChar, next.wordEnd() - next.fChar);
+ if (this->isDefined(t, nextWord, true)) {
+ add_ref(leadingSpaces, ref, &result);
+ continue;
+ }
+ }
+ Definition* test = fRoot;
+ do {
+ if (!test->isRoot()) {
+ continue;
+ }
+ for (string prefix : { "_", "::" } ) {
+ RootDefinition* root = test->asRoot();
+ string prefixed = root->fName + prefix + ref;
+ if (const Definition* def = root->find(prefixed)) {
+ result += linkRef(leadingSpaces, def, ref);
+ goto found;
+ }
+ }
+ } while ((test = test->fParent));
+ found:
+ if (!test) {
+ if (BmhParser::Resolvable::kOut != resolvable) {
+ t.reportError("undefined reference");
+ }
+ }
+ } while (!t.eof());
+ return result;
+}
+
+bool MdOut::buildReferences(const char* fileOrPath, const char* outDir) {
+ if (!sk_isdir(fileOrPath)) {
+ if (!this->buildRefFromFile(fileOrPath, outDir)) {
+ SkDebugf("failed to parse %s\n", fileOrPath);
+ return false;
+ }
+ } else {
+ SkOSFile::Iter it(fileOrPath, ".bmh");
+ for (SkString file; it.next(&file); ) {
+ SkString p = SkOSPath::Join(fileOrPath, file.c_str());
+ const char* hunk = p.c_str();
+ if (!SkStrEndsWith(hunk, ".bmh")) {
+ continue;
+ }
+ if (SkStrEndsWith(hunk, "markup.bmh")) { // don't look inside this for now
+ continue;
+ }
+ 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);
+ if (filename.substr(filename.length() - 4) == ".bmh") {
+ filename = filename.substr(0, filename.length() - 4);
+ }
+ size_t start = filename.length();
+ while (start > 0 && (isalnum(filename[start - 1]) || '_' == filename[start - 1])) {
+ --start;
+ }
+ string match = filename.substr(start);
+ string header = match;
+ filename = "bmh_" + match + ".md";
+ match += ".bmh";
+ fOut = nullptr;
+ for (const auto& topic : fBmhParser.fTopicMap) {
+ Definition* topicDef = topic.second;
+ if (topicDef->fParent) {
+ continue;
+ }
+ if (!topicDef->isRoot()) {
+ return this->reportError<bool>("expected root topic");
+ }
+ fRoot = topicDef->asRoot();
+ if (string::npos == fRoot->fFileName.rfind(match)) {
+ continue;
+ }
+ if (!fOut) {
+ string fullName(outDir);
+ if ('/' != fullName.back()) {
+ fullName += '/';
+ }
+ fullName += filename;
+ fOut = fopen(fullName.c_str(), "wb");
+ if (!fOut) {
+ SkDebugf("could not open output file %s\n", fullName.c_str());
+ return false;
+ }
+ fprintf(fOut, "Experimental %s", header.c_str());
+ this->lfAlways(1);
+ fprintf(fOut, "===");
+ }
+ this->markTypeOut(topicDef);
+ }
+ if (fOut) {
+ this->writePending();
+ fclose(fOut);
+ fOut = nullptr;
+ }
+ return true;
+}
+
+void MdOut::childrenOut(const Definition* def, const char* start) {
+ const char* end;
+ fLineCount = def->fLineCount;
+ if (def->isRoot()) {
+ fRoot = const_cast<RootDefinition*>(def->asRoot());
+ }
+ BmhParser::Resolvable resolvable = this->resolvable(def->fMarkType);
+ for (auto& child : def->fChildren) {
+ end = child->fStart;
+ if (BmhParser::Resolvable::kNo != resolvable) {
+ this->resolveOut(start, end, resolvable);
+ }
+ this->markTypeOut(child);
+ start = child->fTerminator;
+ }
+ if (BmhParser::Resolvable::kNo != resolvable) {
+ end = def->fContentEnd;
+ this->resolveOut(start, end, resolvable);
+ }
+}
+
+const Definition* MdOut::isDefined(const TextParser& parser, const string& ref, bool report) const {
+ auto rootIter = fBmhParser.fClassMap.find(ref);
+ if (rootIter != fBmhParser.fClassMap.end()) {
+ return &rootIter->second;
+ }
+ auto typedefIter = fBmhParser.fTypedefMap.find(ref);
+ if (typedefIter != fBmhParser.fTypedefMap.end()) {
+ return &typedefIter->second;
+ }
+ auto enumIter = fBmhParser.fEnumMap.find(ref);
+ if (enumIter != fBmhParser.fEnumMap.end()) {
+ return &enumIter->second;
+ }
+ auto constIter = fBmhParser.fConstMap.find(ref);
+ if (constIter != fBmhParser.fConstMap.end()) {
+ return &constIter->second;
+ }
+ auto methodIter = fBmhParser.fMethodMap.find(ref);
+ if (methodIter != fBmhParser.fMethodMap.end()) {
+ return &methodIter->second;
+ }
+ auto aliasIter = fBmhParser.fAliasMap.find(ref);
+ if (aliasIter != fBmhParser.fAliasMap.end()) {
+ return aliasIter->second;
+ }
+ for (const auto& external : fBmhParser.fExternals) {
+ if (external.fName == ref) {
+ return &external;
+ }
+ }
+ if (fRoot) {
+ if (ref == fRoot->fName) {
+ return fRoot;
+ }
+ if (const Definition* definition = fRoot->find(ref)) {
+ return definition;
+ }
+ Definition* test = fRoot;
+ do {
+ if (!test->isRoot()) {
+ continue;
+ }
+ RootDefinition* root = test->asRoot();
+ for (auto& leaf : root->fBranches) {
+ if (ref == leaf.first) {
+ return leaf.second;
+ }
+ const Definition* definition = leaf.second->find(ref);
+ if (definition) {
+ return definition;
+ }
+ }
+ for (string prefix : { "::", "_" } ) {
+ string prefixed = root->fName + prefix + ref;
+ if (const Definition* definition = root->find(prefixed)) {
+ return definition;
+ }
+ if (isupper(prefixed[0])) {
+ auto topicIter = fBmhParser.fTopicMap.find(prefixed);
+ if (topicIter != fBmhParser.fTopicMap.end()) {
+ return topicIter->second;
+ }
+ }
+ }
+ } while ((test = test->fParent));
+ }
+ size_t doubleColon = ref.find("::");
+ if (string::npos != doubleColon) {
+ string className = ref.substr(0, doubleColon);
+ auto classIter = fBmhParser.fClassMap.find(className);
+ if (classIter != fBmhParser.fClassMap.end()) {
+ const RootDefinition& classDef = classIter->second;
+ const Definition* result = classDef.find(ref);
+ if (result) {
+ return result;
+ }
+ }
+
+ }
+ if (!ref.compare(0, 2, "SK") || !ref.compare(0, 3, "sk_")
+ || (('k' == ref[0] || 'g' == ref[0] || 'f' == ref[0]) &&
+ ref.length() > 1 && isupper(ref[1]))) {
+ // try with a prefix
+ if ('k' == ref[0]) {
+ for (auto const& iter : fBmhParser.fEnumMap) {
+ if (iter.second.find(ref)) {
+ return &iter.second;
+ }
+ }
+ }
+ if ('f' == ref[0]) {
+ // FIXME : find def associated with prior, e.g.: r.fX where 'SkPoint r' was earlier
+ // need to have pushed last resolve on stack to do this
+ // for now, just try to make sure that it's there and error if not
+ if ('.' != parser.backup(ref.c_str())) {
+ parser.reportError("fX member undefined");
+ return nullptr;
+ }
+ } else {
+ if (report) {
+ parser.reportError("SK undefined");
+ }
+ return nullptr;
+ }
+ }
+ if (isupper(ref[0])) {
+ auto topicIter = fBmhParser.fTopicMap.find(ref);
+ if (topicIter != fBmhParser.fTopicMap.end()) {
+ return topicIter->second;
+ }
+ size_t pos = ref.find('_');
+ if (string::npos != pos) {
+ // see if it is defined by another base class
+ string className(ref, 0, pos);
+ auto classIter = fBmhParser.fClassMap.find(className);
+ if (classIter != fBmhParser.fClassMap.end()) {
+ if (const Definition* definition = classIter->second.find(ref)) {
+ return definition;
+ }
+ }
+ auto enumIter = fBmhParser.fEnumMap.find(className);
+ if (enumIter != fBmhParser.fEnumMap.end()) {
+ if (const Definition* definition = enumIter->second.find(ref)) {
+ return definition;
+ }
+ }
+ if (report) {
+ parser.reportError("_ undefined");
+ }
+ return nullptr;
+ }
+ }
+ return nullptr;
+}
+
+string MdOut::linkName(const Definition* ref) const {
+ string result = ref->fName;
+ size_t under = result.find('_');
+ if (string::npos != under) {
+ string classPart = result.substr(0, under);
+ string namePart = result.substr(under + 1, result.length());
+ if (fRoot && (fRoot->fName == classPart
+ || (fRoot->fParent && fRoot->fParent->fName == classPart))) {
+ result = namePart;
+ }
+ }
+ return result;
+}
+
+// for now, hard-code to html links
+// def should not include SkXXX_
+string MdOut::linkRef(const string& leadingSpaces, const Definition* def,
+ const string& ref) const {
+ string buildup;
+ const string* str = &def->fFiddle;
+ SkASSERT(str->length() > 0);
+ size_t under = str->find('_');
+ Definition* curRoot = fRoot;
+ string classPart = string::npos != under ? str->substr(0, under) : *str;
+ bool classMatch = curRoot->fName == classPart;
+ while (curRoot->fParent) {
+ curRoot = curRoot->fParent;
+ classMatch |= curRoot->fName == classPart;
+ }
+ const Definition* defRoot;
+ do {
+ defRoot = def;
+ if (!(def = def->fParent)) {
+ break;
+ }
+ classMatch |= def != defRoot && def->fName == classPart;
+ } while (true);
+ string namePart = string::npos != under ? str->substr(under + 1, str->length()) : *str;
+ SkASSERT(fRoot);
+ SkASSERT(fRoot->fFileName.length());
+ if (false && classMatch) {
+ str = &namePart;
+ } else if (true || (curRoot != defRoot && defRoot->isRoot())) {
+ string filename = defRoot->asRoot()->fFileName;
+ if (filename.substr(filename.length() - 4) == ".bmh") {
+ filename = filename.substr(0, filename.length() - 4);
+ }
+ size_t start = filename.length();
+ while (start > 0 && (isalnum(filename[start - 1]) || '_' == filename[start - 1])) {
+ --start;
+ }
+ buildup = "bmh_" + filename.substr(start) + "?cl=9919#"
+ + (classMatch ? namePart : *str);
+ str = &buildup;
+ }
+ string refOut(ref);
+ std::replace(refOut.begin(), refOut.end(), '_', ' ');
+ if (ref.length() > 2 && islower(ref[0]) && "()" == ref.substr(ref.length() - 2)) {
+ refOut = refOut.substr(0, refOut.length() - 2);
+ }
+ return leadingSpaces + "<a href=\"" + *str + "\">" + refOut + "</a>";
+}
+
+void MdOut::markTypeOut(Definition* def) {
+ string printable = def->printableName();
+ const char* textStart = def->fContentStart;
+ if (MarkType::kParam != def->fMarkType && MarkType::kConst != def->fMarkType &&
+ (!def->fParent || MarkType::kConst != def->fParent->fMarkType) &&
+ TableState::kNone != fTableState) {
+ this->writePending();
+ fprintf(fOut, "</table>");
+ this->lf(2);
+ fTableState = TableState::kNone;
+ }
+ switch (def->fMarkType) {
+ case MarkType::kAlias:
+ break;
+ case MarkType::kAnchor:
+ break;
+ case MarkType::kBug:
+ break;
+ case MarkType::kClass:
+ this->mdHeaderOut(1);
+ fprintf(fOut, "<a name=\"%s\"></a> Class %s", this->linkName(def).c_str(),
+ def->fName.c_str());
+ this->lf(1);
+ break;
+ case MarkType::kCode:
+ this->lfAlways(2);
+ fprintf(fOut, "<pre style=\"padding: 1em 1em 1em 1em;"
+ "width: 44em; background-color: #f0f0f0\">");
+ this->lf(1);
+ break;
+ case MarkType::kColumn:
+ this->writePending();
+ if (fInList) {
+ fprintf(fOut, " <td>");
+ } else {
+ fprintf(fOut, "| ");
+ }
+ break;
+ case MarkType::kComment:
+ break;
+ case MarkType::kConst: {
+ if (TableState::kNone == fTableState) {
+ this->mdHeaderOut(3);
+ fprintf(fOut, "Constants\n"
+ "\n"
+ "<table>");
+ fTableState = TableState::kRow;
+ this->lf(1);
+ }
+ if (TableState::kRow == fTableState) {
+ this->writePending();
+ fprintf(fOut, " <tr>");
+ this->lf(1);
+ fTableState = TableState::kColumn;
+ }
+ this->writePending();
+ fprintf(fOut, " <td><a name=\"%s\"></a> <code><strong>%s </strong></code></td>",
+ def->fName.c_str(), def->fName.c_str());
+ const char* lineEnd = strchr(textStart, '\n');
+ SkASSERT(lineEnd < def->fTerminator);
+ SkASSERT(lineEnd > textStart);
+ SkASSERT((int) (lineEnd - textStart) == lineEnd - textStart);
+ fprintf(fOut, "<td>%.*s</td>", (int) (lineEnd - textStart), textStart);
+ fprintf(fOut, "<td>");
+ textStart = lineEnd;
+ } break;
+ case MarkType::kDefine:
+ break;
+ case MarkType::kDefinedBy:
+ break;
+ case MarkType::kDeprecated:
+ break;
+ case MarkType::kDescription:
+ fInDescription = true;
+ this->writePending();
+ fprintf(fOut, "<div>");
+ break;
+ case MarkType::kDoxygen:
+ break;
+ case MarkType::kEnum:
+ case MarkType::kEnumClass:
+ this->mdHeaderOut(2);
+ fprintf(fOut, "<a name=\"%s\"></a> Enum %s", def->fName.c_str(), def->fName.c_str());
+ this->lf(2);
+ break;
+ case MarkType::kError:
+ break;
+ case MarkType::kExample: {
+ this->mdHeaderOut(3);
+ fprintf(fOut, "Example\n"
+ "\n");
+ fHasFiddle = true;
+ const Definition* platform = def->hasChild(MarkType::kPlatform);
+ if (platform) {
+ TextParser platParse(platform);
+ fHasFiddle = !platParse.strnstr("!fiddle", platParse.fEnd);
+ }
+ if (fHasFiddle) {
+ fprintf(fOut, "<div><fiddle-embed name=\"%s\">", def->fHash.c_str());
+ } else {
+ fprintf(fOut, "<pre style=\"padding: 1em 1em 1em 1em;"
+ "width: 44em; background-color: #f0f0f0\">");
+ this->lf(1);
+ }
+ } break;
+ case MarkType::kExperimental:
+ break;
+ case MarkType::kExternal:
+ break;
+ case MarkType::kFile:
+ break;
+ case MarkType::kFormula:
+ break;
+ case MarkType::kFunction:
+ break;
+ case MarkType::kHeight:
+ break;
+ case MarkType::kImage:
+ break;
+ case MarkType::kLegend:
+ break;
+ case MarkType::kLink:
+ break;
+ case MarkType::kList:
+ fInList = true;
+ this->lfAlways(2);
+ fprintf(fOut, "<table>");
+ this->lf(1);
+ break;
+ case MarkType::kMarkChar:
+ fBmhParser.fMC = def->fContentStart[0];
+ break;
+ case MarkType::kMember: {
+ TextParser tp(def->fFileName, def->fStart, def->fContentStart, def->fLineCount);
+ tp.skipExact("#Member");
+ tp.skipWhiteSpace();
+ const char* end = tp.trimmedBracketEnd('\n', TextParser::OneLine::kYes);
+ this->lfAlways(2);
+ fprintf(fOut, "<code><strong>%.*s</strong></code>", (int) (end - tp.fChar), tp.fChar);
+ this->lf(2);
+ } break;
+ case MarkType::kMethod: {
+ string method_name = def->methodName();
+ string formattedStr = def->formatFunction();
+
+ if (!def->isClone()) {
+ this->lfAlways(2);
+ fprintf(fOut, "<a name=\"%s\"></a>", def->fiddleName().c_str());
+ this->mdHeaderOutLF(2, 1);
+ fprintf(fOut, "%s", method_name.c_str());
+ this->lf(2);
+ }
+
+ // TODO: put in css spec that we can define somewhere else (if markup supports that)
+ // TODO: 50em below should match limt = 80 in formatFunction()
+ this->writePending();
+ fprintf(fOut, "<pre style=\"padding: 1em 1em 1em 1em;"
+ "width: 50em; background-color: #f0f0f0\">\n"
+ "%s\n"
+ "</pre>", formattedStr.c_str());
+ this->lf(2);
+ fTableState = TableState::kNone;
+ fMethod = def;
+ } break;
+ case MarkType::kNoExample:
+ break;
+ case MarkType::kParam: {
+ if (TableState::kNone == fTableState) {
+ this->mdHeaderOut(3);
+ fprintf(fOut,
+ "Parameters\n"
+ "\n"
+ "<table>"
+ );
+ this->lf(1);
+ fTableState = TableState::kRow;
+ }
+ if (TableState::kRow == fTableState) {
+ fprintf(fOut, " <tr>");
+ this->lf(1);
+ fTableState = TableState::kColumn;
+ }
+ TextParser paramParser(def->fFileName, def->fStart, def->fContentStart,
+ def->fLineCount);
+ paramParser.skipWhiteSpace();
+ SkASSERT(paramParser.startsWith("#Param"));
+ paramParser.next(); // skip hash
+ paramParser.skipToNonAlphaNum(); // skip Param
+ paramParser.skipSpace();
+ const char* paramName = paramParser.fChar;
+ paramParser.skipToSpace();
+ fprintf(fOut,
+ " <td><code><strong>%.*s </strong></code></td> <td>",
+ (int) (paramParser.fChar - paramName), paramName);
+ } break;
+ case MarkType::kPlatform:
+ break;
+ case MarkType::kPrivate:
+ break;
+ case MarkType::kReturn:
+ this->mdHeaderOut(3);
+ fprintf(fOut, "Return Value");
+ this->lf(2);
+ break;
+ case MarkType::kRow:
+ if (fInList) {
+ fprintf(fOut, " <tr>");
+ this->lf(1);
+ }
+ break;
+ case MarkType::kSeeAlso:
+ this->mdHeaderOut(3);
+ fprintf(fOut, "See Also");
+ this->lf(2);
+ break;
+ case MarkType::kStdOut: {
+ TextParser code(def);
+ this->mdHeaderOut(4);
+ fprintf(fOut,
+ "Example Output\n"
+ "\n"
+ "~~~~");
+ this->lfAlways(1);
+ code.skipSpace();
+ while (!code.eof()) {
+ const char* end = code.trimmedLineEnd();
+ fprintf(fOut, "%.*s\n", (int) (end - code.fChar), code.fChar);
+ code.skipToLineStart();
+ }
+ fprintf(fOut, "~~~~");
+ this->lf(2);
+ } break;
+ case MarkType::kStruct:
+ fRoot = def->asRoot();
+ this->mdHeaderOut(1);
+ fprintf(fOut, "<a name=\"%s\"></a> Struct %s", def->fName.c_str(), def->fName.c_str());
+ this->lf(1);
+ break;
+ case MarkType::kSubstitute:
+ break;
+ case MarkType::kSubtopic:
+ this->mdHeaderOut(2);
+ fprintf(fOut, "<a name=\"%s\"></a> %s", def->fName.c_str(), printable.c_str());
+ this->lf(2);
+ break;
+ case MarkType::kTable:
+ this->lf(2);
+ break;
+ case MarkType::kTemplate:
+ break;
+ case MarkType::kText:
+ break;
+ case MarkType::kTime:
+ break;
+ case MarkType::kToDo:
+ break;
+ case MarkType::kTopic:
+ this->mdHeaderOut(1);
+ fprintf(fOut, "<a name=\"%s\"></a> %s", this->linkName(def).c_str(),
+ printable.c_str());
+ this->lf(1);
+ break;
+ case MarkType::kTrack:
+ // don't output children
+ return;
+ case MarkType::kTypedef:
+ break;
+ case MarkType::kUnion:
+ break;
+ case MarkType::kVolatile:
+ break;
+ case MarkType::kWidth:
+ break;
+ default:
+ SkDebugf("fatal error: MarkType::k%s unhandled in %s()\n",
+ fBmhParser.fMaps[(int) def->fMarkType].fName, __func__);
+ SkASSERT(0); // handle everything
+ break;
+ }
+ this->childrenOut(def, textStart);
+ switch (def->fMarkType) { // post child work, at least for tables
+ case MarkType::kCode:
+ this->writePending();
+ fprintf(fOut, "</pre>");
+ this->lf(2);
+ break;
+ case MarkType::kColumn:
+ if (fInList) {
+ this->writePending();
+ fprintf(fOut, "</td>");
+ this->lf(1);
+ } else {
+ fprintf(fOut, " ");
+ }
+ break;
+ case MarkType::kDescription:
+ this->writePending();
+ fprintf(fOut, "</div>");
+ fInDescription = false;
+ break;
+ case MarkType::kEnum:
+ case MarkType::kEnumClass:
+ this->lfAlways(2);
+ break;
+ case MarkType::kExample:
+ this->writePending();
+ if (fHasFiddle) {
+ fprintf(fOut, "</fiddle-embed></div>");
+ } else {
+ fprintf(fOut, "</pre>");
+ }
+ this->lf(2);
+ break;
+ case MarkType::kList:
+ fInList = false;
+ this->writePending();
+ fprintf(fOut, "</table>");
+ this->lf(2);
+ break;
+ case MarkType::kLegend: {
+ SkASSERT(def->fChildren.size() == 1);
+ const Definition* row = def->fChildren[0];
+ SkASSERT(MarkType::kRow == row->fMarkType);
+ size_t columnCount = row->fChildren.size();
+ SkASSERT(columnCount > 0);
+ this->writePending();
+ for (size_t index = 0; index < columnCount; ++index) {
+ fprintf(fOut, "| --- ");
+ }
+ fprintf(fOut, " |");
+ this->lf(1);
+ } break;
+ case MarkType::kMethod:
+ fMethod = nullptr;
+ this->lfAlways(2);
+ fprintf(fOut, "---");
+ this->lf(2);
+ break;
+ case MarkType::kConst:
+ case MarkType::kParam:
+ SkASSERT(TableState::kColumn == fTableState);
+ fTableState = TableState::kRow;
+ this->writePending();
+ fprintf(fOut, "</td>\n");
+ fprintf(fOut, " </tr>");
+ this->lf(1);
+ break;
+ case MarkType::kReturn:
+ case MarkType::kSeeAlso:
+ this->lf(2);
+ break;
+ case MarkType::kRow:
+ if (fInList) {
+ fprintf(fOut, " </tr>");
+ } else {
+ fprintf(fOut, "|");
+ }
+ this->lf(1);
+ break;
+ case MarkType::kStruct:
+ fRoot = fRoot->rootParent();
+ break;
+ case MarkType::kTable:
+ this->lf(2);
+ break;
+ case MarkType::kPrivate:
+ break;
+ default:
+ break;
+ }
+}
+
+void MdOut::mdHeaderOutLF(int depth, int lf) {
+ this->lfAlways(lf);
+ for (int index = 0; index < depth; ++index) {
+ fprintf(fOut, "#");
+ }
+ fprintf(fOut, " ");
+}
+
+void MdOut::resolveOut(const char* start, const char* end, BmhParser::Resolvable resolvable) {
+ // FIXME: this needs the markdown character present when the def was defined,
+ // not the last markdown character the parser would have seen...
+ while (fBmhParser.fMC == end[-1]) {
+ --end;
+ }
+ if (start >= end) {
+ return;
+ }
+ string resolved = this->addReferences(start, end, resolvable);
+ trim_end_spaces(resolved);
+ if (resolved.length()) {
+ TextParser paragraph(fFileName, &*resolved.begin(), &*resolved.end(), fLineCount);
+ TextParser original(fFileName, start, end, fLineCount);
+ while (!original.eof() && '\n' == original.peek()) {
+ original.next();
+ }
+ original.skipSpace();
+ while (!paragraph.eof()) {
+ paragraph.skipWhiteSpace();
+ const char* contentStart = paragraph.fChar;
+ paragraph.skipToEndBracket('\n');
+ ptrdiff_t lineLength = paragraph.fChar - contentStart;
+ if (lineLength) {
+ this->writePending();
+ fprintf(fOut, "%.*s", (int) lineLength, contentStart);
+ }
+ int linefeeds = 0;
+ while (lineLength > 0 && '\n' == contentStart[--lineLength]) {
+ ++linefeeds;
+ }
+ if (lineLength > 0) {
+ this->nl();
+ }
+ fLinefeeds += linefeeds;
+ if (paragraph.eof()) {
+ break;
+ }
+ if ('\n' == paragraph.next()) {
+ linefeeds = 1;
+ if (!paragraph.eof() && '\n' == paragraph.peek()) {
+ linefeeds = 2;
+ }
+ this->lf(linefeeds);
+ }
+ }
+#if 0
+ while (end > start && end[0] == '\n') {
+ fprintf(fOut, "\n");
+ --end;
+ }
+#endif
+ }
+}