1 /* 2 * Copyright 2017 Google Inc. 3 * 4 * Use of this source code is governed by a BSD-style license that can be 5 * found in the LICENSE file. 6 */ 7 8 #include "bookmaker.h" 9 10 #include "SkOSFile.h" 11 #include "SkOSPath.h" 12 13 #define FPRINTF(...) \ 14 if (fDebugOut) { \ 15 SkDebugf(__VA_ARGS__); \ 16 } \ 17 fprintf(fOut, __VA_ARGS__) 18 19 static void add_ref(const string& leadingSpaces, const string& ref, string* result) { 20 *result += leadingSpaces + ref; 21 } 22 23 static string preformat(const string& orig) { 24 string result; 25 for (auto c : orig) { 26 if ('<' == c) { 27 result += "<"; 28 } else if ('>' == c) { 29 result += ">"; 30 } else { 31 result += c; 32 } 33 } 34 return result; 35 } 36 37 static bool all_lower(const string& ref) { 38 for (auto ch : ref) { 39 if (!islower(ch)) { 40 return false; 41 } 42 } 43 return true; 44 } 45 46 // FIXME: preserve inter-line spaces and don't add new ones 47 string MdOut::addReferences(const char* refStart, const char* refEnd, 48 BmhParser::Resolvable resolvable) { 49 string result; 50 MethodParser t(fRoot ? fRoot->fName : string(), fFileName, refStart, refEnd, fLineCount); 51 bool lineStart = true; 52 string ref; 53 string leadingSpaces; 54 int distFromParam = 99; 55 do { 56 ++distFromParam; 57 const char* base = t.fChar; 58 t.skipWhiteSpace(); 59 const char* wordStart = t.fChar; 60 t.skipToMethodStart(); 61 const char* start = t.fChar; 62 if (wordStart < start) { 63 if (lineStart) { 64 lineStart = false; 65 } else { 66 wordStart = base; 67 } 68 result += string(wordStart, start - wordStart); 69 if ('\n' != result.back()) { 70 while (start > wordStart && '\n' == start[-1]) { 71 result += '\n'; 72 --start; 73 } 74 } 75 } 76 if (lineStart) { 77 lineStart = false; 78 } else { 79 leadingSpaces = string(base, wordStart - base); 80 } 81 t.skipToMethodEnd(); 82 if (base == t.fChar) { 83 if (!t.eof() && '~' == base[0] && !isalnum(base[1])) { 84 t.next(); 85 } else { 86 break; 87 } 88 } 89 if (start >= t.fChar) { 90 continue; 91 } 92 if (!t.eof() && '"' == t.peek() && start > wordStart && '"' == start[-1]) { 93 continue; 94 } 95 ref = string(start, t.fChar - start); 96 if (const Definition* def = this->isDefined(t, ref, 97 BmhParser::Resolvable::kOut != resolvable)) { 98 if (MarkType::kExternal == def->fMarkType) { 99 add_ref(leadingSpaces, ref, &result); 100 continue; 101 } 102 SkASSERT(def->fFiddle.length()); 103 if (!t.eof() && '(' == t.peek() && t.strnchr(')', t.fEnd)) { 104 if (!t.skipToEndBracket(')')) { 105 t.reportError("missing close paren"); 106 return result; 107 } 108 t.next(); 109 string fullRef = string(start, t.fChar - start); 110 // if _2 etc alternates are defined, look for paren match 111 // may ignore () if ref is all lower case 112 // otherwise flag as error 113 int suffix = '2'; 114 bool foundMatch = false; 115 const Definition* altDef = def; 116 while (altDef && suffix <= '9') { 117 if ((foundMatch = altDef->paramsMatch(fullRef, ref))) { 118 def = altDef; 119 ref = fullRef; 120 break; 121 } 122 string altTest = ref + '_'; 123 altTest += suffix++; 124 altDef = this->isDefined(t, altTest, false); 125 } 126 if (suffix > '9') { 127 t.reportError("too many alts"); 128 return result; 129 } 130 if (!foundMatch) { 131 if (!(def = this->isDefined(t, fullRef, 132 BmhParser::Resolvable::kOut != resolvable))) { 133 if (!result.size()) { 134 t.reportError("missing method"); 135 } 136 return result; 137 } 138 ref = fullRef; 139 } 140 } else if (BmhParser::Resolvable::kClone != resolvable && 141 all_lower(ref) && (t.eof() || '(' != t.peek())) { 142 add_ref(leadingSpaces, ref, &result); 143 continue; 144 } 145 result += linkRef(leadingSpaces, def, ref, resolvable); 146 continue; 147 } 148 if (!t.eof() && '(' == t.peek()) { 149 if (!t.skipToEndBracket(')')) { 150 t.reportError("missing close paren"); 151 return result; 152 } 153 t.next(); 154 ref = string(start, t.fChar - start); 155 if (const Definition* def = this->isDefined(t, ref, true)) { 156 SkASSERT(def->fFiddle.length()); 157 result += linkRef(leadingSpaces, def, ref, resolvable); 158 continue; 159 } 160 } 161 // class, struct, and enum start with capitals 162 // methods may start with upper (static) or lower (most) 163 164 // see if this should have been a findable reference 165 166 // look for Sk / sk / SK .. 167 if (!ref.compare(0, 2, "Sk") && ref != "Skew" && ref != "Skews" && 168 ref != "Skip" && ref != "Skips") { 169 t.reportError("missed Sk prefixed"); 170 return result; 171 } 172 if (!ref.compare(0, 2, "SK")) { 173 if (BmhParser::Resolvable::kOut != resolvable) { 174 t.reportError("missed SK prefixed"); 175 } 176 return result; 177 } 178 if (!isupper(start[0])) { 179 // TODO: 180 // look for all lowercase w/o trailing parens as mistaken method matches 181 // will also need to see if Example Description matches var in example 182 const Definition* def; 183 if (fMethod && (def = fMethod->hasParam(ref))) { 184 result += linkRef(leadingSpaces, def, ref, resolvable); 185 fLastParam = def; 186 distFromParam = 0; 187 continue; 188 } else if (!fInDescription && ref[0] != '0' 189 && string::npos != ref.find_first_of("ABCDEFGHIJKLMNOPQRSTUVWXYZ")) { 190 // FIXME: see isDefined(); check to see if fXX is a member of xx.fXX 191 if (('f' != ref[0] && string::npos == ref.find("()")) 192 // || '.' != t.backup(ref.c_str()) 193 && ('k' != ref[0] && string::npos == ref.find("_Private"))) { 194 if ('.' == wordStart[0] && (distFromParam >= 1 && distFromParam <= 16)) { 195 const Definition* paramType = this->findParamType(); 196 if (paramType) { 197 string fullName = paramType->fName + "::" + ref; 198 if (paramType->hasMatch(fullName)) { 199 result += linkRef(leadingSpaces, paramType, ref, resolvable); 200 continue; 201 } 202 } 203 } 204 if (BmhParser::Resolvable::kOut != resolvable) { 205 t.reportError("missed camelCase"); 206 return result; 207 } 208 } 209 } 210 add_ref(leadingSpaces, ref, &result); 211 continue; 212 } 213 auto topicIter = fBmhParser.fTopicMap.find(ref); 214 if (topicIter != fBmhParser.fTopicMap.end()) { 215 result += linkRef(leadingSpaces, topicIter->second, ref, resolvable); 216 continue; 217 } 218 bool startsSentence = t.sentenceEnd(start); 219 if (!t.eof() && ' ' != t.peek()) { 220 add_ref(leadingSpaces, ref, &result); 221 continue; 222 } 223 if (t.fChar + 1 >= t.fEnd || (!isupper(t.fChar[1]) && startsSentence)) { 224 add_ref(leadingSpaces, ref, &result); 225 continue; 226 } 227 if (isupper(t.fChar[1]) && startsSentence) { 228 TextParser next(t.fFileName, &t.fChar[1], t.fEnd, t.fLineCount); 229 string nextWord(next.fChar, next.wordEnd() - next.fChar); 230 if (this->isDefined(t, nextWord, true)) { 231 add_ref(leadingSpaces, ref, &result); 232 continue; 233 } 234 } 235 const Definition* test = fRoot; 236 do { 237 if (!test->isRoot()) { 238 continue; 239 } 240 for (string prefix : { "_", "::" } ) { 241 const RootDefinition* root = test->asRoot(); 242 string prefixed = root->fName + prefix + ref; 243 if (const Definition* def = root->find(prefixed, 244 RootDefinition::AllowParens::kYes)) { 245 result += linkRef(leadingSpaces, def, ref, resolvable); 246 goto found; 247 } 248 } 249 } while ((test = test->fParent)); 250 found: 251 if (!test) { 252 if (BmhParser::Resolvable::kOut != resolvable) { 253 t.reportError("undefined reference"); 254 } 255 } 256 } while (!t.eof()); 257 return result; 258 } 259 260 261 262 bool MdOut::buildReferences(const char* docDir, const char* mdFileOrPath) { 263 if (!sk_isdir(mdFileOrPath)) { 264 SkString mdFile = SkOSPath::Basename(mdFileOrPath); 265 SkString bmhFile = SkOSPath::Join(docDir, mdFile.c_str()); 266 bmhFile.remove(bmhFile.size() - 3, 3); 267 bmhFile += ".bmh"; 268 SkString mdPath = SkOSPath::Dirname(mdFileOrPath); 269 if (!this->buildRefFromFile(bmhFile.c_str(), mdPath.c_str())) { 270 SkDebugf("failed to parse %s\n", mdFileOrPath); 271 return false; 272 } 273 } else { 274 SkOSFile::Iter it(docDir, ".bmh"); 275 for (SkString file; it.next(&file); ) { 276 SkString p = SkOSPath::Join(docDir, file.c_str()); 277 if (!this->buildRefFromFile(p.c_str(), mdFileOrPath)) { 278 SkDebugf("failed to parse %s\n", p.c_str()); 279 return false; 280 } 281 } 282 } 283 return true; 284 } 285 286 bool MdOut::buildStatus(const char* statusFile, const char* outDir) { 287 StatusIter iter(statusFile, ".bmh", StatusFilter::kInProgress); 288 for (string file; iter.next(&file); ) { 289 SkString p = SkOSPath::Join(iter.baseDir().c_str(), file.c_str()); 290 const char* hunk = p.c_str(); 291 if (!this->buildRefFromFile(hunk, outDir)) { 292 SkDebugf("failed to parse %s\n", hunk); 293 return false; 294 } 295 } 296 return true; 297 } 298 299 bool MdOut::buildRefFromFile(const char* name, const char* outDir) { 300 if (!SkStrEndsWith(name, ".bmh")) { 301 return true; 302 } 303 if (SkStrEndsWith(name, "markup.bmh")) { // don't look inside this for now 304 return true; 305 } 306 if (SkStrEndsWith(name, "illustrations.bmh")) { // don't look inside this for now 307 return true; 308 } 309 fFileName = string(name); 310 string filename(name); 311 if (filename.substr(filename.length() - 4) == ".bmh") { 312 filename = filename.substr(0, filename.length() - 4); 313 } 314 size_t start = filename.length(); 315 while (start > 0 && (isalnum(filename[start - 1]) || '_' == filename[start - 1])) { 316 --start; 317 } 318 string match = filename.substr(start); 319 string header = match; 320 filename = match + ".md"; 321 match += ".bmh"; 322 fOut = nullptr; 323 string fullName; 324 325 vector<string> keys; 326 keys.reserve(fBmhParser.fTopicMap.size()); 327 for (const auto& it : fBmhParser.fTopicMap) { 328 keys.push_back(it.first); 329 } 330 std::sort(keys.begin(), keys.end()); 331 for (auto key : keys) { 332 string s(key); 333 auto topicDef = fBmhParser.fTopicMap.at(s); 334 if (topicDef->fParent) { 335 continue; 336 } 337 if (!topicDef->isRoot()) { 338 return this->reportError<bool>("expected root topic"); 339 } 340 fRoot = topicDef->asRoot(); 341 if (string::npos == fRoot->fFileName.rfind(match)) { 342 continue; 343 } 344 if (!fOut) { 345 fullName = outDir; 346 if ('/' != fullName.back()) { 347 fullName += '/'; 348 } 349 fullName += filename; 350 fOut = fopen(filename.c_str(), "wb"); 351 if (!fOut) { 352 SkDebugf("could not open output file %s\n", fullName.c_str()); 353 return false; 354 } 355 size_t underscorePos = header.find('_'); 356 if (string::npos != underscorePos) { 357 header.replace(underscorePos, 1, " "); 358 } 359 SkASSERT(string::npos == header.find('_')); 360 FPRINTF("%s", header.c_str()); 361 this->lfAlways(1); 362 FPRINTF("==="); 363 } 364 fPopulators.clear(); 365 fPopulators[kClassesAndStructs].fDescription = "embedded struct and class members"; 366 fPopulators[kConstants].fDescription = "enum and enum class, const values"; 367 fPopulators[kConstructors].fDescription = "functions that construct"; 368 fPopulators[kMemberFunctions].fDescription = "static functions and member methods"; 369 fPopulators[kMembers].fDescription = "member values"; 370 fPopulators[kOperators].fDescription = "operator overloading methods"; 371 fPopulators[kRelatedFunctions].fDescription = "similar methods grouped together"; 372 fPopulators[kSubtopics].fDescription = ""; 373 this->populateTables(fRoot); 374 this->markTypeOut(topicDef); 375 } 376 if (fOut) { 377 this->writePending(); 378 fclose(fOut); 379 fflush(fOut); 380 if (this->writtenFileDiffers(filename, fullName)) { 381 fOut = fopen(fullName.c_str(), "wb"); 382 int writtenSize; 383 const char* written = ReadToBuffer(filename, &writtenSize); 384 fwrite(written, 1, writtenSize, fOut); 385 fclose(fOut); 386 fflush(fOut); 387 SkDebugf("wrote updated %s\n", fullName.c_str()); 388 } 389 remove(filename.c_str()); 390 fOut = nullptr; 391 } 392 return true; 393 } 394 395 bool MdOut::checkParamReturnBody(const Definition* def) const { 396 TextParser paramBody(def); 397 const char* descriptionStart = paramBody.fChar; 398 if (!islower(descriptionStart[0]) && !isdigit(descriptionStart[0])) { 399 paramBody.skipToNonAlphaNum(); 400 string ref = string(descriptionStart, paramBody.fChar - descriptionStart); 401 if (!this->isDefined(paramBody, ref, true)) { 402 string errorStr = MarkType::kReturn == def->fMarkType ? "return" : "param"; 403 errorStr += " description must start with lower case"; 404 paramBody.reportError(errorStr.c_str()); 405 return false; 406 } 407 } 408 if ('.' == paramBody.fEnd[-1]) { 409 paramBody.reportError("make param description a phrase; should not end with period"); 410 return false; 411 } 412 return true; 413 } 414 415 void MdOut::childrenOut(const Definition* def, const char* start) { 416 const char* end; 417 fLineCount = def->fLineCount; 418 if (def->isRoot()) { 419 fRoot = const_cast<RootDefinition*>(def->asRoot()); 420 } else if (MarkType::kEnumClass == def->fMarkType) { 421 fEnumClass = def; 422 } 423 BmhParser::Resolvable resolvable = this->resolvable(def); 424 for (auto& child : def->fChildren) { 425 end = child->fStart; 426 if (BmhParser::Resolvable::kNo != resolvable) { 427 this->resolveOut(start, end, resolvable); 428 } 429 this->markTypeOut(child); 430 start = child->fTerminator; 431 } 432 if (BmhParser::Resolvable::kNo != resolvable) { 433 end = def->fContentEnd; 434 this->resolveOut(start, end, resolvable); 435 } 436 if (MarkType::kEnumClass == def->fMarkType) { 437 fEnumClass = nullptr; 438 } 439 } 440 441 const Definition* MdOut::csParent() const { 442 const Definition* csParent = fRoot->csParent(); 443 if (!csParent) { 444 const Definition* topic = fRoot; 445 while (topic && MarkType::kTopic != topic->fMarkType) { 446 topic = topic->fParent; 447 } 448 for (auto child : topic->fChildren) { 449 if (child->isStructOrClass() || MarkType::kTypedef == child->fMarkType) { 450 csParent = child; 451 break; 452 } 453 } 454 SkASSERT(csParent || string::npos == fRoot->fFileName.find("Sk")); 455 } 456 return csParent; 457 } 458 459 const Definition* MdOut::findParamType() { 460 SkASSERT(fMethod); 461 TextParser parser(fMethod->fFileName, fMethod->fStart, fMethod->fContentStart, 462 fMethod->fLineCount); 463 string lastFull; 464 do { 465 parser.skipToAlpha(); 466 if (parser.eof()) { 467 return nullptr; 468 } 469 const char* word = parser.fChar; 470 parser.skipFullName(); 471 SkASSERT(!parser.eof()); 472 string name = string(word, parser.fChar - word); 473 if (fLastParam->fName == name) { 474 const Definition* paramType = this->isDefined(parser, lastFull, false); 475 return paramType; 476 } 477 if (isupper(name[0])) { 478 lastFull = name; 479 } 480 } while (true); 481 return nullptr; 482 } 483 484 const Definition* MdOut::isDefined(const TextParser& parser, const string& ref, bool report) const { 485 auto rootIter = fBmhParser.fClassMap.find(ref); 486 if (rootIter != fBmhParser.fClassMap.end()) { 487 return &rootIter->second; 488 } 489 auto typedefIter = fBmhParser.fTypedefMap.find(ref); 490 if (typedefIter != fBmhParser.fTypedefMap.end()) { 491 return &typedefIter->second; 492 } 493 auto enumIter = fBmhParser.fEnumMap.find(ref); 494 if (enumIter != fBmhParser.fEnumMap.end()) { 495 return &enumIter->second; 496 } 497 auto constIter = fBmhParser.fConstMap.find(ref); 498 if (constIter != fBmhParser.fConstMap.end()) { 499 return &constIter->second; 500 } 501 auto methodIter = fBmhParser.fMethodMap.find(ref); 502 if (methodIter != fBmhParser.fMethodMap.end()) { 503 return &methodIter->second; 504 } 505 auto aliasIter = fBmhParser.fAliasMap.find(ref); 506 if (aliasIter != fBmhParser.fAliasMap.end()) { 507 return aliasIter->second; 508 } 509 for (const auto& external : fBmhParser.fExternals) { 510 if (external.fName == ref) { 511 return &external; 512 } 513 } 514 if (fRoot) { 515 if (ref == fRoot->fName) { 516 return fRoot; 517 } 518 if (const Definition* definition = fRoot->find(ref, RootDefinition::AllowParens::kYes)) { 519 return definition; 520 } 521 const Definition* test = fRoot; 522 do { 523 if (!test->isRoot()) { 524 continue; 525 } 526 const RootDefinition* root = test->asRoot(); 527 for (auto& leaf : root->fBranches) { 528 if (ref == leaf.first) { 529 return leaf.second; 530 } 531 const Definition* definition = leaf.second->find(ref, 532 RootDefinition::AllowParens::kYes); 533 if (definition) { 534 return definition; 535 } 536 } 537 for (string prefix : { "::", "_" } ) { 538 string prefixed = root->fName + prefix + ref; 539 if (const Definition* definition = root->find(prefixed, 540 RootDefinition::AllowParens::kYes)) { 541 return definition; 542 } 543 if (isupper(prefixed[0])) { 544 auto topicIter = fBmhParser.fTopicMap.find(prefixed); 545 if (topicIter != fBmhParser.fTopicMap.end()) { 546 return topicIter->second; 547 } 548 } 549 } 550 string fiddlePrefixed = root->fFiddle + "_" + ref; 551 auto topicIter = fBmhParser.fTopicMap.find(fiddlePrefixed); 552 if (topicIter != fBmhParser.fTopicMap.end()) { 553 return topicIter->second; 554 } 555 } while ((test = test->fParent)); 556 } 557 size_t doubleColon = ref.find("::"); 558 if (string::npos != doubleColon) { 559 string className = ref.substr(0, doubleColon); 560 auto classIter = fBmhParser.fClassMap.find(className); 561 if (classIter != fBmhParser.fClassMap.end()) { 562 const RootDefinition& classDef = classIter->second; 563 const Definition* result = classDef.find(ref, RootDefinition::AllowParens::kYes); 564 if (result) { 565 return result; 566 } 567 } 568 569 } 570 if (!ref.compare(0, 2, "SK") || !ref.compare(0, 3, "sk_") 571 || (('k' == ref[0] || 'g' == ref[0] || 'f' == ref[0]) && 572 ref.length() > 1 && isupper(ref[1]))) { 573 // try with a prefix 574 if ('k' == ref[0]) { 575 for (auto const& iter : fBmhParser.fEnumMap) { 576 auto def = iter.second.find(ref, RootDefinition::AllowParens::kYes); 577 if (def) { 578 return def; 579 } 580 } 581 if (fEnumClass) { 582 string fullName = fEnumClass->fName + "::" + ref; 583 for (auto child : fEnumClass->fChildren) { 584 if (fullName == child->fName) { 585 return child; 586 } 587 } 588 } 589 if (string::npos != ref.find("_Private")) { 590 return nullptr; 591 } 592 } 593 if ('f' == ref[0]) { 594 // FIXME : find def associated with prior, e.g.: r.fX where 'SkPoint r' was earlier 595 // need to have pushed last resolve on stack to do this 596 // for now, just try to make sure that it's there and error if not 597 if ('.' != parser.backup(ref.c_str())) { 598 parser.reportError("fX member undefined"); 599 return nullptr; 600 } 601 } else { 602 if (report) { 603 parser.reportError("SK undefined"); 604 } 605 return nullptr; 606 } 607 } 608 if (isupper(ref[0])) { 609 auto topicIter = fBmhParser.fTopicMap.find(ref); 610 if (topicIter != fBmhParser.fTopicMap.end()) { 611 return topicIter->second; 612 } 613 size_t pos = ref.find('_'); 614 if (string::npos != pos) { 615 // see if it is defined by another base class 616 string className(ref, 0, pos); 617 auto classIter = fBmhParser.fClassMap.find(className); 618 if (classIter != fBmhParser.fClassMap.end()) { 619 if (const Definition* definition = classIter->second.find(ref, 620 RootDefinition::AllowParens::kYes)) { 621 return definition; 622 } 623 } 624 auto enumIter = fBmhParser.fEnumMap.find(className); 625 if (enumIter != fBmhParser.fEnumMap.end()) { 626 if (const Definition* definition = enumIter->second.find(ref, 627 RootDefinition::AllowParens::kYes)) { 628 return definition; 629 } 630 } 631 if (report) { 632 parser.reportError("_ undefined"); 633 } 634 return nullptr; 635 } 636 } 637 return nullptr; 638 } 639 640 string MdOut::linkName(const Definition* ref) const { 641 string result = ref->fName; 642 size_t under = result.find('_'); 643 if (string::npos != under) { 644 string classPart = result.substr(0, under); 645 string namePart = result.substr(under + 1, result.length()); 646 if (fRoot && (fRoot->fName == classPart 647 || (fRoot->fParent && fRoot->fParent->fName == classPart))) { 648 result = namePart; 649 } 650 } 651 return result; 652 } 653 654 // for now, hard-code to html links 655 // def should not include SkXXX_ 656 string MdOut::linkRef(const string& leadingSpaces, const Definition* def, 657 const string& ref, BmhParser::Resolvable resolvable) const { 658 string buildup; 659 string refName; 660 const string* str = &def->fFiddle; 661 SkASSERT(str->length() > 0); 662 string classPart = *str; 663 bool globalEnumMember = false; 664 if (MarkType::kAlias == def->fMarkType) { 665 def = def->fParent; 666 SkASSERT(def); 667 SkASSERT(MarkType::kSubtopic == def->fMarkType ||MarkType::kTopic == def->fMarkType); 668 } 669 if (MarkType::kSubtopic == def->fMarkType) { 670 const Definition* topic = def->topicParent(); 671 SkASSERT(topic); 672 classPart = topic->fName; 673 refName = def->fName; 674 } else if (MarkType::kTopic == def->fMarkType) { 675 refName = def->fName; 676 } else { 677 if ('k' == (*str)[0] && string::npos != str->find("_Sk")) { 678 globalEnumMember = true; 679 } else { 680 SkASSERT("Sk" == str->substr(0, 2) || "SK" == str->substr(0, 2) 681 // FIXME: kitchen sink catch below, need to do better 682 || string::npos != def->fFileName.find("undocumented")); 683 size_t under = str->find('_'); 684 classPart = string::npos != under ? str->substr(0, under) : *str; 685 } 686 refName = def->fFiddle; 687 } 688 bool classMatch = fRoot->fFileName == def->fFileName; 689 SkASSERT(fRoot); 690 SkASSERT(fRoot->fFileName.length()); 691 if (!classMatch) { 692 string filename = def->fFileName; 693 if (filename.substr(filename.length() - 4) == ".bmh") { 694 filename = filename.substr(0, filename.length() - 4); 695 } 696 size_t start = filename.length(); 697 while (start > 0 && (isalnum(filename[start - 1]) || '_' == filename[start - 1])) { 698 --start; 699 } 700 buildup = filename.substr(start); 701 } 702 buildup += "#" + refName; 703 if (MarkType::kParam == def->fMarkType) { 704 const Definition* parent = def->fParent; 705 SkASSERT(MarkType::kMethod == parent->fMarkType); 706 buildup = '#' + parent->fFiddle + '_' + ref; 707 } 708 string refOut(ref); 709 if (!globalEnumMember) { 710 std::replace(refOut.begin(), refOut.end(), '_', ' '); 711 } 712 if (ref.length() > 2 && islower(ref[0]) && "()" == ref.substr(ref.length() - 2)) { 713 refOut = refOut.substr(0, refOut.length() - 2); 714 } 715 string result = leadingSpaces + "<a href=\"" + buildup + "\">" + refOut + "</a>"; 716 if (BmhParser::Resolvable::kClone == resolvable && MarkType::kMethod == def->fMarkType && 717 def->fCloned && !def->fClone) { 718 bool found = false; 719 string match = def->fName; 720 if ("()" == match.substr(match.length() - 2)) { 721 match = match.substr(0, match.length() - 2); 722 } 723 match += '_'; 724 auto classIter = fBmhParser.fClassMap.find(classPart); 725 if (fBmhParser.fClassMap.end() != classIter) { 726 for (char num = '2'; num <= '9'; ++num) { 727 string clone = match + num; 728 const auto& leafIter = classIter->second.fLeaves.find(clone); 729 if (leafIter != classIter->second.fLeaves.end()) { 730 result += "<sup><a href=\"" + buildup + "_" + num + "\">[" + num + "]</a></sup>"; 731 found = true; 732 } 733 } 734 } 735 if (!found) { 736 SkDebugf(""); // convenient place to set a breakpoint 737 } 738 } 739 return result; 740 } 741 742 void MdOut::markTypeOut(Definition* def) { 743 string printable = def->printableName(); 744 const char* textStart = def->fContentStart; 745 if (MarkType::kParam != def->fMarkType && MarkType::kConst != def->fMarkType && 746 (!def->fParent || MarkType::kConst != def->fParent->fMarkType) && 747 TableState::kNone != fTableState) { 748 this->writePending(); 749 FPRINTF("</table>"); 750 this->lf(2); 751 fTableState = TableState::kNone; 752 } 753 switch (def->fMarkType) { 754 case MarkType::kAlias: 755 break; 756 case MarkType::kAnchor: { 757 if (fColumn > 0) { 758 this->writeSpace(); 759 } 760 this->writePending(); 761 TextParser parser(def); 762 const char* start = parser.fChar; 763 parser.skipToEndBracket(" # "); 764 string anchorText(start, parser.fChar - start); 765 parser.skipExact(" # "); 766 string anchorLink(parser.fChar, parser.fEnd - parser.fChar); 767 FPRINTF("<a href=\"%s\">%s", anchorLink.c_str(), anchorText.c_str()); 768 } break; 769 case MarkType::kBug: 770 break; 771 case MarkType::kClass: 772 this->mdHeaderOut(1); 773 FPRINTF("<a name=\"%s\"></a> Class %s", this->linkName(def).c_str(), 774 def->fName.c_str()); 775 this->lf(1); 776 break; 777 case MarkType::kCode: 778 this->lfAlways(2); 779 FPRINTF("<pre style=\"padding: 1em 1em 1em 1em;" 780 "width: 62.5em; background-color: #f0f0f0\">"); 781 this->lf(1); 782 break; 783 case MarkType::kColumn: 784 this->writePending(); 785 if (fInList) { 786 FPRINTF(" <td>"); 787 } else { 788 FPRINTF("| "); 789 } 790 break; 791 case MarkType::kComment: 792 break; 793 case MarkType::kConst: { 794 if (TableState::kNone == fTableState) { 795 this->mdHeaderOut(3); 796 FPRINTF("Constants\n" 797 "\n" 798 "<table>"); 799 fTableState = TableState::kRow; 800 this->lf(1); 801 } 802 if (TableState::kRow == fTableState) { 803 this->writePending(); 804 FPRINTF(" <tr>"); 805 this->lf(1); 806 fTableState = TableState::kColumn; 807 } 808 this->writePending(); 809 FPRINTF(" <td><a name=\"%s\"> <code><strong>%s </strong></code> </a></td>", 810 def->fFiddle.c_str(), def->fName.c_str()); 811 const char* lineEnd = strchr(textStart, '\n'); 812 SkASSERT(lineEnd < def->fTerminator); 813 SkASSERT(lineEnd > textStart); 814 SkASSERT((int) (lineEnd - textStart) == lineEnd - textStart); 815 FPRINTF("<td>%.*s</td>", (int) (lineEnd - textStart), textStart); 816 FPRINTF("<td>"); 817 textStart = lineEnd; 818 } break; 819 case MarkType::kDefine: 820 break; 821 case MarkType::kDefinedBy: 822 break; 823 case MarkType::kDeprecated: 824 break; 825 case MarkType::kDescription: 826 fInDescription = true; 827 this->writePending(); 828 FPRINTF("<div>"); 829 break; 830 case MarkType::kDoxygen: 831 break; 832 case MarkType::kDuration: 833 break; 834 case MarkType::kEnum: 835 case MarkType::kEnumClass: 836 this->mdHeaderOut(2); 837 FPRINTF("<a name=\"%s\"></a> Enum %s", def->fFiddle.c_str(), def->fName.c_str()); 838 this->lf(2); 839 break; 840 case MarkType::kExample: { 841 this->mdHeaderOut(3); 842 FPRINTF("Example\n" 843 "\n"); 844 fHasFiddle = true; 845 bool showGpu = false; 846 bool gpuAndCpu = false; 847 const Definition* platform = def->hasChild(MarkType::kPlatform); 848 if (platform) { 849 TextParser platParse(platform); 850 fHasFiddle = !platParse.strnstr("!fiddle", platParse.fEnd); 851 showGpu = platParse.strnstr("gpu", platParse.fEnd); 852 if (showGpu) { 853 gpuAndCpu = platParse.strnstr("cpu", platParse.fEnd); 854 } 855 } 856 if (fHasFiddle) { 857 SkASSERT(def->fHash.length() > 0); 858 FPRINTF("<div><fiddle-embed name=\"%s\"", def->fHash.c_str()); 859 if (showGpu) { 860 FPRINTF(" gpu=\"true\""); 861 if (gpuAndCpu) { 862 FPRINTF(" cpu=\"true\""); 863 } 864 } 865 FPRINTF(">"); 866 } else { 867 SkASSERT(def->fHash.length() == 0); 868 FPRINTF("<pre style=\"padding: 1em 1em 1em 1em; font-size: 13px" 869 " width: 62.5em; background-color: #f0f0f0\">"); 870 this->lfAlways(1); 871 if (def->fWrapper.length() > 0) { 872 FPRINTF("%s", def->fWrapper.c_str()); 873 } 874 fRespectLeadingSpace = true; 875 } 876 } break; 877 case MarkType::kExperimental: 878 break; 879 case MarkType::kExternal: 880 break; 881 case MarkType::kFile: 882 break; 883 case MarkType::kFormula: 884 break; 885 case MarkType::kFunction: 886 break; 887 case MarkType::kHeight: 888 break; 889 case MarkType::kIllustration: { 890 string illustName = "Illustrations_" + def->fParent->fFiddle; 891 auto illustIter = fBmhParser.fTopicMap.find(illustName); 892 SkASSERT(fBmhParser.fTopicMap.end() != illustIter); 893 Definition* illustDef = illustIter->second; 894 SkASSERT(MarkType::kSubtopic == illustDef->fMarkType); 895 SkASSERT(1 == illustDef->fChildren.size()); 896 Definition* illustExample = illustDef->fChildren[0]; 897 SkASSERT(MarkType::kExample == illustExample->fMarkType); 898 string hash = illustExample->fHash; 899 SkASSERT("" != hash); 900 string title; 901 this->writePending(); 902 FPRINTF("![%s](https://fiddle.skia.org/i/%s_raster.png \"%s\")", 903 def->fName.c_str(), hash.c_str(), title.c_str()); 904 this->lf(2); 905 } break; 906 case MarkType::kImage: 907 break; 908 case MarkType::kIn: 909 break; 910 case MarkType::kLegend: 911 break; 912 case MarkType::kLine: 913 break; 914 case MarkType::kLink: 915 break; 916 case MarkType::kList: 917 fInList = true; 918 this->lfAlways(2); 919 FPRINTF("<table>"); 920 this->lf(1); 921 break; 922 case MarkType::kLiteral: 923 break; 924 case MarkType::kMarkChar: 925 fBmhParser.fMC = def->fContentStart[0]; 926 break; 927 case MarkType::kMember: { 928 TextParser tp(def->fFileName, def->fStart, def->fContentStart, def->fLineCount); 929 tp.skipExact("#Member"); 930 tp.skipWhiteSpace(); 931 const char* end = tp.trimmedBracketEnd('\n'); 932 this->lfAlways(2); 933 FPRINTF("<a name=\"%s\"> <code><strong>%.*s</strong></code> </a>", 934 def->fFiddle.c_str(), (int) (end - tp.fChar), tp.fChar); 935 this->lf(2); 936 } break; 937 case MarkType::kMethod: { 938 string method_name = def->methodName(); 939 string formattedStr = def->formatFunction(Definition::Format::kIncludeReturn); 940 941 this->lfAlways(2); 942 FPRINTF("<a name=\"%s\"></a>", def->fFiddle.c_str()); 943 if (!def->isClone()) { 944 this->mdHeaderOutLF(2, 1); 945 FPRINTF("%s", method_name.c_str()); 946 } 947 this->lf(2); 948 949 // TODO: put in css spec that we can define somewhere else (if markup supports that) 950 // TODO: 50em below should match limit = 80 in formatFunction() 951 this->writePending(); 952 string preformattedStr = preformat(formattedStr); 953 FPRINTF("<pre style=\"padding: 1em 1em 1em 1em;" 954 "width: 62.5em; background-color: #f0f0f0\">\n" 955 "%s\n" 956 "</pre>", preformattedStr.c_str()); 957 this->lf(2); 958 fTableState = TableState::kNone; 959 fMethod = def; 960 } break; 961 case MarkType::kNoExample: 962 break; 963 case MarkType::kOutdent: 964 break; 965 case MarkType::kParam: { 966 if (TableState::kNone == fTableState) { 967 this->mdHeaderOut(3); 968 fprintf(fOut, 969 "Parameters\n" 970 "\n" 971 "<table>" 972 ); 973 this->lf(1); 974 fTableState = TableState::kRow; 975 } 976 if (TableState::kRow == fTableState) { 977 FPRINTF(" <tr>"); 978 this->lf(1); 979 fTableState = TableState::kColumn; 980 } 981 TextParser paramParser(def->fFileName, def->fStart, def->fContentStart, 982 def->fLineCount); 983 paramParser.skipWhiteSpace(); 984 SkASSERT(paramParser.startsWith("#Param")); 985 paramParser.next(); // skip hash 986 paramParser.skipToNonAlphaNum(); // skip Param 987 paramParser.skipSpace(); 988 const char* paramName = paramParser.fChar; 989 paramParser.skipToSpace(); 990 string paramNameStr(paramName, (int) (paramParser.fChar - paramName)); 991 if (!this->checkParamReturnBody(def)) { 992 return; 993 } 994 string refNameStr = def->fParent->fFiddle + "_" + paramNameStr; 995 fprintf(fOut, 996 " <td><a name=\"%s\"> <code><strong>%s </strong></code> </a></td> <td>", 997 refNameStr.c_str(), paramNameStr.c_str()); 998 } break; 999 case MarkType::kPlatform: 1000 break; 1001 case MarkType::kPopulate: { 1002 SkASSERT(MarkType::kSubtopic == def->fParent->fMarkType); 1003 string name = def->fParent->fName; 1004 if (kSubtopics == name) { 1005 this->subtopicsOut(); 1006 } else { 1007 this->subtopicOut(this->populator(name.c_str())); 1008 } 1009 } break; 1010 case MarkType::kPrivate: 1011 break; 1012 case MarkType::kReturn: 1013 this->mdHeaderOut(3); 1014 FPRINTF("Return Value"); 1015 if (!this->checkParamReturnBody(def)) { 1016 return; 1017 } 1018 this->lf(2); 1019 break; 1020 case MarkType::kRow: 1021 if (fInList) { 1022 FPRINTF(" <tr>"); 1023 this->lf(1); 1024 } 1025 break; 1026 case MarkType::kSeeAlso: 1027 this->mdHeaderOut(3); 1028 FPRINTF("See Also"); 1029 this->lf(2); 1030 break; 1031 case MarkType::kSet: 1032 break; 1033 case MarkType::kStdOut: { 1034 TextParser code(def); 1035 this->mdHeaderOut(4); 1036 fprintf(fOut, 1037 "Example Output\n" 1038 "\n" 1039 "~~~~"); 1040 this->lfAlways(1); 1041 code.skipSpace(); 1042 while (!code.eof()) { 1043 const char* end = code.trimmedLineEnd(); 1044 FPRINTF("%.*s\n", (int) (end - code.fChar), code.fChar); 1045 code.skipToLineStart(); 1046 } 1047 FPRINTF("~~~~"); 1048 this->lf(2); 1049 } break; 1050 case MarkType::kStruct: 1051 fRoot = def->asRoot(); 1052 this->mdHeaderOut(1); 1053 FPRINTF("<a name=\"%s\"></a> Struct %s", def->fFiddle.c_str(), def->fName.c_str()); 1054 this->lf(1); 1055 break; 1056 case MarkType::kSubstitute: 1057 break; 1058 case MarkType::kSubtopic: 1059 this->mdHeaderOut(2); 1060 FPRINTF("<a name=\"%s\"></a> %s", def->fName.c_str(), printable.c_str()); 1061 this->lf(2); 1062 break; 1063 case MarkType::kTable: 1064 this->lf(2); 1065 break; 1066 case MarkType::kTemplate: 1067 break; 1068 case MarkType::kText: 1069 break; 1070 case MarkType::kTime: 1071 break; 1072 case MarkType::kToDo: 1073 break; 1074 case MarkType::kTopic: 1075 this->mdHeaderOut(1); 1076 FPRINTF("<a name=\"%s\"></a> %s", this->linkName(def).c_str(), 1077 printable.c_str()); 1078 this->lf(1); 1079 break; 1080 case MarkType::kTrack: 1081 // don't output children 1082 return; 1083 case MarkType::kTypedef: 1084 break; 1085 case MarkType::kUnion: 1086 break; 1087 case MarkType::kVolatile: 1088 break; 1089 case MarkType::kWidth: 1090 break; 1091 default: 1092 SkDebugf("fatal error: MarkType::k%s unhandled in %s()\n", 1093 fBmhParser.fMaps[(int) def->fMarkType].fName, __func__); 1094 SkASSERT(0); // handle everything 1095 break; 1096 } 1097 this->childrenOut(def, textStart); 1098 switch (def->fMarkType) { // post child work, at least for tables 1099 case MarkType::kAnchor: 1100 if (fColumn > 0) { 1101 this->writeSpace(); 1102 } 1103 break; 1104 case MarkType::kCode: 1105 this->writePending(); 1106 FPRINTF("</pre>"); 1107 this->lf(2); 1108 break; 1109 case MarkType::kColumn: 1110 if (fInList) { 1111 this->writePending(); 1112 FPRINTF("</td>"); 1113 this->lf(1); 1114 } else { 1115 FPRINTF(" "); 1116 } 1117 break; 1118 case MarkType::kDescription: 1119 this->writePending(); 1120 FPRINTF("</div>"); 1121 fInDescription = false; 1122 break; 1123 case MarkType::kEnum: 1124 case MarkType::kEnumClass: 1125 this->lfAlways(2); 1126 break; 1127 case MarkType::kExample: 1128 this->writePending(); 1129 if (fHasFiddle) { 1130 FPRINTF("</fiddle-embed></div>"); 1131 } else { 1132 this->lfAlways(1); 1133 if (def->fWrapper.length() > 0) { 1134 FPRINTF("}"); 1135 this->lfAlways(1); 1136 } 1137 FPRINTF("</pre>"); 1138 } 1139 this->lf(2); 1140 fRespectLeadingSpace = false; 1141 break; 1142 case MarkType::kLink: 1143 this->writeString("</a>"); 1144 this->writeSpace(); 1145 break; 1146 case MarkType::kList: 1147 fInList = false; 1148 this->writePending(); 1149 FPRINTF("</table>"); 1150 this->lf(2); 1151 break; 1152 case MarkType::kLegend: { 1153 SkASSERT(def->fChildren.size() == 1); 1154 const Definition* row = def->fChildren[0]; 1155 SkASSERT(MarkType::kRow == row->fMarkType); 1156 size_t columnCount = row->fChildren.size(); 1157 SkASSERT(columnCount > 0); 1158 this->writePending(); 1159 for (size_t index = 0; index < columnCount; ++index) { 1160 FPRINTF("| --- "); 1161 } 1162 FPRINTF(" |"); 1163 this->lf(1); 1164 } break; 1165 case MarkType::kMethod: 1166 fMethod = nullptr; 1167 this->lfAlways(2); 1168 FPRINTF("---"); 1169 this->lf(2); 1170 break; 1171 case MarkType::kConst: 1172 case MarkType::kParam: 1173 SkASSERT(TableState::kColumn == fTableState); 1174 fTableState = TableState::kRow; 1175 this->writePending(); 1176 FPRINTF("</td>\n"); 1177 FPRINTF(" </tr>"); 1178 this->lf(1); 1179 break; 1180 case MarkType::kReturn: 1181 case MarkType::kSeeAlso: 1182 this->lf(2); 1183 break; 1184 case MarkType::kRow: 1185 if (fInList) { 1186 FPRINTF(" </tr>"); 1187 } else { 1188 FPRINTF("|"); 1189 } 1190 this->lf(1); 1191 break; 1192 case MarkType::kStruct: 1193 fRoot = fRoot->rootParent(); 1194 break; 1195 case MarkType::kTable: 1196 this->lf(2); 1197 break; 1198 case MarkType::kPrivate: 1199 break; 1200 default: 1201 break; 1202 } 1203 } 1204 1205 void MdOut::mdHeaderOutLF(int depth, int lf) { 1206 this->lfAlways(lf); 1207 for (int index = 0; index < depth; ++index) { 1208 FPRINTF("#"); 1209 } 1210 FPRINTF(" "); 1211 } 1212 1213 void MdOut::populateTables(const Definition* def) { 1214 const Definition* csParent = this->csParent(); 1215 if (!csParent) { 1216 return; 1217 } 1218 for (auto child : def->fChildren) { 1219 if (MarkType::kTopic == child->fMarkType || MarkType::kSubtopic == child->fMarkType) { 1220 string name = child->fName; 1221 bool builtInTopic = name == kClassesAndStructs || name == kConstants 1222 || name == kConstructors || name == kMemberFunctions || name == kMembers 1223 || name == kOperators || name == kOverview || name == kRelatedFunctions 1224 || name == kSubtopics; 1225 if (!builtInTopic && child->fName != kOverview) { 1226 this->populator(kRelatedFunctions).fMembers.push_back(child); 1227 } 1228 this->populateTables(child); 1229 continue; 1230 } 1231 if (child->isStructOrClass()) { 1232 if (fClassStack.size() > 0) { 1233 this->populator(kClassesAndStructs).fMembers.push_back(child); 1234 } 1235 fClassStack.push_back(child); 1236 this->populateTables(child); 1237 fClassStack.pop_back(); 1238 continue; 1239 } 1240 if (MarkType::kEnum == child->fMarkType || MarkType::kEnumClass == child->fMarkType) { 1241 this->populator(kConstants).fMembers.push_back(child); 1242 continue; 1243 } 1244 if (MarkType::kMember == child->fMarkType) { 1245 this->populator(kMembers).fMembers.push_back(child); 1246 continue; 1247 } 1248 if (MarkType::kMethod != child->fMarkType) { 1249 continue; 1250 } 1251 if (child->fClone) { 1252 continue; 1253 } 1254 if (Definition::MethodType::kConstructor == child->fMethodType 1255 || Definition::MethodType::kDestructor == child->fMethodType) { 1256 this->populator(kConstructors).fMembers.push_back(child); 1257 continue; 1258 } 1259 if (Definition::MethodType::kOperator == child->fMethodType) { 1260 this->populator(kOperators).fMembers.push_back(child); 1261 continue; 1262 } 1263 this->populator(kMemberFunctions).fMembers.push_back(child); 1264 if (csParent && (0 == child->fName.find(csParent->fName + "::Make") 1265 || 0 == child->fName.find(csParent->fName + "::make"))) { 1266 this->populator(kConstructors).fMembers.push_back(child); 1267 continue; 1268 } 1269 for (auto item : child->fChildren) { 1270 if (MarkType::kIn == item->fMarkType) { 1271 string name(item->fContentStart, item->fContentEnd - item->fContentStart); 1272 fPopulators[name].fMembers.push_back(child); 1273 fPopulators[name].fShowClones = true; 1274 break; 1275 } 1276 } 1277 } 1278 } 1279 1280 void MdOut::resolveOut(const char* start, const char* end, BmhParser::Resolvable resolvable) { 1281 if ((BmhParser::Resolvable::kLiteral == resolvable || fRespectLeadingSpace) && end > start) { 1282 while ('\n' == *start) { 1283 ++start; 1284 } 1285 const char* spaceStart = start; 1286 while (' ' == *start) { 1287 ++start; 1288 } 1289 if (start > spaceStart) { 1290 fIndent = start - spaceStart; 1291 } 1292 this->writeBlockTrim(end - start, start); 1293 if ('\n' == end[-1]) { 1294 this->lf(1); 1295 } 1296 fIndent = 0; 1297 return; 1298 } 1299 // FIXME: this needs the markdown character present when the def was defined, 1300 // not the last markdown character the parser would have seen... 1301 while (fBmhParser.fMC == end[-1]) { 1302 --end; 1303 } 1304 if (start >= end) { 1305 return; 1306 } 1307 string resolved = this->addReferences(start, end, resolvable); 1308 trim_end_spaces(resolved); 1309 if (resolved.length()) { 1310 TextParser paragraph(fFileName, &*resolved.begin(), &*resolved.end(), fLineCount); 1311 TextParser original(fFileName, start, end, fLineCount); 1312 while (!original.eof() && '\n' == original.peek()) { 1313 original.next(); 1314 } 1315 original.skipSpace(); 1316 while (!paragraph.eof()) { 1317 paragraph.skipWhiteSpace(); 1318 const char* contentStart = paragraph.fChar; 1319 paragraph.skipToEndBracket('\n'); 1320 ptrdiff_t lineLength = paragraph.fChar - contentStart; 1321 if (lineLength) { 1322 while (lineLength && contentStart[lineLength - 1] <= ' ') { 1323 --lineLength; 1324 } 1325 string str(contentStart, lineLength); 1326 this->writeString(str.c_str()); 1327 } 1328 #if 0 1329 int linefeeds = 0; 1330 while (lineLength > 0 && '\n' == contentStart[--lineLength]) { 1331 1332 ++linefeeds; 1333 } 1334 if (lineLength > 0) { 1335 this->nl(); 1336 } 1337 fLinefeeds += linefeeds; 1338 #endif 1339 if (paragraph.eof()) { 1340 break; 1341 } 1342 if ('\n' == paragraph.next()) { 1343 int linefeeds = 1; 1344 if (!paragraph.eof() && '\n' == paragraph.peek()) { 1345 linefeeds = 2; 1346 } 1347 this->lf(linefeeds); 1348 } 1349 } 1350 #if 0 1351 while (end > start && end[0] == '\n') { 1352 FPRINTF("\n"); 1353 --end; 1354 } 1355 #endif 1356 } 1357 } 1358 1359 void MdOut::rowOut(const char* name, const string& description) { 1360 this->lfAlways(1); 1361 FPRINTF("| "); 1362 this->resolveOut(name, name + strlen(name), BmhParser::Resolvable::kYes); 1363 FPRINTF(" | "); 1364 this->resolveOut(&description.front(), &description.back() + 1, BmhParser::Resolvable::kYes); 1365 FPRINTF(" |"); 1366 this->lf(1); 1367 } 1368 1369 void MdOut::subtopicsOut() { 1370 const Definition* csParent = this->csParent(); 1371 SkASSERT(csParent); 1372 this->rowOut("name", "description"); 1373 this->rowOut("---", "---"); 1374 for (auto item : { kClassesAndStructs, kConstants, kConstructors, kMemberFunctions, 1375 kMembers, kOperators, kRelatedFunctions } ) { 1376 for (auto entry : this->populator(item).fMembers) { 1377 if (entry->csParent() == csParent) { 1378 string description = fPopulators.find(item)->second.fDescription; 1379 if (kConstructors == item) { 1380 description += " " + csParent->fName; 1381 } 1382 this->rowOut(item, description); 1383 break; 1384 } 1385 } 1386 } 1387 } 1388 1389 void MdOut::subtopicOut(const TableContents& tableContents) { 1390 const auto& data = tableContents.fMembers; 1391 const Definition* csParent = this->csParent(); 1392 SkASSERT(csParent); 1393 fRoot = csParent->asRoot(); 1394 this->rowOut("name", "description"); 1395 this->rowOut("---", "---"); 1396 std::map<string, const Definition*> items; 1397 for (auto entry : data) { 1398 if (entry->csParent() != csParent) { 1399 continue; 1400 } 1401 size_t start = entry->fName.find_last_of("::"); 1402 string name = entry->fName.substr(string::npos == start ? 0 : start + 1); 1403 items[name] = entry; 1404 } 1405 for (auto entry : items) { 1406 if (entry.second->fDeprecated) { 1407 continue; 1408 } 1409 const Definition* oneLiner = nullptr; 1410 for (auto child : entry.second->fChildren) { 1411 if (MarkType::kLine == child->fMarkType) { 1412 oneLiner = child; 1413 break; 1414 } 1415 } 1416 if (!oneLiner) { 1417 SkDebugf(""); // convenient place to set a breakpoint 1418 } 1419 // TODO: detect this earlier? throw error here? 1420 SkASSERT(oneLiner); 1421 this->rowOut(entry.first.c_str(), string(oneLiner->fContentStart, 1422 oneLiner->fContentEnd - oneLiner->fContentStart)); 1423 if (tableContents.fShowClones && entry.second->fCloned) { 1424 int cloneNo = 2; 1425 string builder = entry.second->fName; 1426 if ("()" == builder.substr(builder.length() - 2)) { 1427 builder = builder.substr(0, builder.length() - 2); 1428 } 1429 builder += '_'; 1430 this->rowOut("", 1431 preformat(entry.second->formatFunction(Definition::Format::kOmitReturn))); 1432 do { 1433 string match = builder + to_string(cloneNo); 1434 auto child = csParent->findClone(match); 1435 if (!child) { 1436 break; 1437 } 1438 this->rowOut("", preformat(child->formatFunction(Definition::Format::kOmitReturn))); 1439 } while (++cloneNo); 1440 } 1441 } 1442 } 1443