diff options
author | Cary Clark <caryclark@skia.org> | 2017-07-28 11:04:54 -0400 |
---|---|---|
committer | Skia Commit-Bot <skia-commit-bot@chromium.org> | 2017-07-28 15:30:38 +0000 |
commit | 8032b983faaa8c76f81bf3cf028e9c64f4635478 (patch) | |
tree | ed3be061ff02a99dab1b3e443d48b7f5c906417e /tools/bookmaker/mdOut.cpp | |
parent | acaa607328fb0dfac0894d4a2fcdead520e696b3 (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.cpp | 929 |
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 + } +} |