blob: 744122a0a4b713557c6f5d376ab6aa8fc1b657f3 [file] [log] [blame]
/*
* 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))) {
if (!result.size()) {
t.reportError("missing method");
}
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())
&& ('k' != ref[0] && string::npos == ref.find("_Private"))) {
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 = 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;
}
size_t underscorePos = header.find('_');
if (string::npos != underscorePos) {
header.replace(underscorePos, 1, " ");
}
SkASSERT(string::npos == header.find('_'));
fprintf(fOut, "%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());
} else if (MarkType::kEnumClass == def->fMarkType) {
fEnumClass = def;
}
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);
}
if (MarkType::kEnumClass == def->fMarkType) {
fEnumClass = nullptr;
}
}
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 (fEnumClass) {
string fullName = fEnumClass->fName + "::" + ref;
for (auto child : fEnumClass->fChildren) {
if (fullName == child->fName) {
return child;
}
}
}
if (string::npos != ref.find("_Private")) {
return nullptr;
}
}
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;
const Definition* temp = def;
do {
defRoot = temp;
if (!(temp = temp->fParent)) {
break;
}
classMatch |= temp != defRoot && temp->fName == classPart;
} while (true);
string namePart = string::npos != under ? str->substr(under + 1, str->length()) : *str;
SkASSERT(fRoot);
SkASSERT(fRoot->fFileName.length());
if (classMatch) {
buildup = "#";
if (*str != classPart && "Sk" == classPart.substr(0, 2)) {
buildup += classPart + "_";
}
buildup += namePart;
} else {
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 = filename.substr(start) + "#" + (classMatch ? namePart : *str);
}
if (MarkType::kParam == def->fMarkType) {
const Definition* parent = def->fParent;
SkASSERT(MarkType::kMethod == parent->fMarkType);
buildup = '#' + parent->fFiddle + '_' + ref;
}
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=\"" + buildup + "\">" + 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\"> <code><strong>%s </strong></code> </a></td>",
def->fFiddle.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->fFiddle.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, "<a name=\"%s\"> <code><strong>%.*s</strong></code> </a>",
def->fFiddle.c_str(), (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->fFiddle.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();
string paramNameStr(paramName, (int) (paramParser.fChar - paramName));
string refNameStr = def->fParent->fFiddle + "_" + paramNameStr;
fprintf(fOut,
" <td><a name=\"%s\"> <code><strong>%s </strong></code> </a></td> <td>",
refNameStr.c_str(), paramNameStr.c_str());
} 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->fFiddle.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
}
}