1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include <algorithm>
18#include <iostream>
19#include <sstream>
20
21#include "Generator.h"
22#include "Specification.h"
23#include "Utilities.h"
24
25using namespace std;
26
27struct DetailedFunctionEntry {
28    VersionInfo info;
29    string htmlDeclaration;
30};
31
32static const char OVERVIEW_HTML_FILE_NAME[] = "overview.html";
33static const char INDEX_HTML_FILE_NAME[] = "index.html";
34
35static void writeHeader(GeneratedFile* file, const string& title,
36                        const SpecFile& specFile) {
37    // Generate DevSite markups
38    *file
39        << "<html devsite>\n"
40           "<!-- " << AUTO_GENERATED_WARNING << "-->\n"
41           "<head>\n"
42           "  <title>RenderScript " << title << "</title>\n"
43           "  <meta name=\"top_category\" value=\"develop\" />\n"
44           "  <meta name=\"subcategory\" value=\"guide\" />\n"
45           "  <meta name=\"book_path\" value=\"/guide/_book.yaml\" />\n"
46           "  <meta name=\"project_path\" value=\"/guide/_project.yaml\" />\n";
47    auto desc = specFile.getFullDescription();
48    if (desc.size()) {
49        *file << "  <meta name=\"description\" content=\"";
50        // Output only the first two lines. Assuming there's no other HTML
51        // markups there
52        // TODO: escape/remove markups
53        for (unsigned int i = 0; i < std::min(desc.size(), 2UL); ++i) {
54            if (i) *file << " ";
55            *file << desc[i];
56        }
57        *file << "…\">\n";
58    }
59    *file << "</head>\n\n"
60             "<body>\n\n";
61    *file << "<div class='renderscript'>\n";
62}
63
64static void writeFooter(GeneratedFile* file) {
65    *file << "</div>\n";
66    *file << "\n\n</body>\n";
67    *file << "</html>\n";
68}
69
70// If prefix starts input, copy it to stream and remove it from input.
71static void skipPrefix(ostringstream* stream, string* input, const string& prefix) {
72    size_t size = prefix.size();
73    if (input->compare(0, size, prefix) != 0) {
74        return;
75    }
76    input->erase(0, size);
77    *stream << prefix;
78}
79
80// Merge b into a.  Returns true if successful
81static bool mergeVersionInfo(VersionInfo* a, const VersionInfo& b) {
82    if (a->intSize != b.intSize) {
83        cerr << "Error.  We don't currently support versions that differ based on int size\n";
84        return false;
85    }
86    if (b.minVersion != 0 && a->maxVersion == b.minVersion - 1) {
87        a->maxVersion = b.maxVersion;
88    } else if (b.maxVersion != 0 && a->minVersion == b.maxVersion + 1) {
89        a->minVersion = b.minVersion;
90    } else {
91        cerr << "Error.  This code currently assume that all versions are contiguous.  Don't know "
92                "how to merge versions (" << a->minVersion << " - " << a->maxVersion << ") and ("
93             << b.minVersion << " - " << b.maxVersion << ")\n";
94        return false;
95    }
96    return true;
97}
98
99static string getHtmlStringForType(const ParameterDefinition& parameter) {
100    string s = parameter.rsType;
101    ostringstream stream;
102    skipPrefix(&stream, &s, "const ");
103    skipPrefix(&stream, &s, "volatile ");
104    bool endsWithAsterisk = s.size() > 0 && s[s.size() - 1] == '*';
105    if (endsWithAsterisk) {
106        s.erase(s.size() - 1, 1);
107    }
108
109    string anchor = systemSpecification.getHtmlAnchor(s);
110    if (anchor.empty()) {
111        // Not a RenderScript specific type.
112        return parameter.rsType;
113    } else {
114        stream << anchor;
115    }
116    if (endsWithAsterisk) {
117        stream << "*";
118    }
119    return stream.str();
120}
121
122static string getDetailedHtmlDeclaration(const FunctionPermutation& permutation) {
123    ostringstream stream;
124    auto ret = permutation.getReturn();
125    if (ret) {
126        stream << getHtmlStringForType(*ret);
127    } else {
128        stream << "void";
129    }
130    stream << " " << permutation.getName() << "(";
131    bool needComma = false;
132    for (auto p : permutation.getParams()) {
133        if (needComma) {
134            stream << ", ";
135        }
136        stream << getHtmlStringForType(*p);
137        if (p->isOutParameter) {
138            stream << "*";
139        }
140        if (!p->specName.empty()) {
141            stream << " " << p->specName;
142        }
143        needComma = true;
144    }
145    stream << ");\n";
146    return stream.str();
147}
148
149/* Some functions (like max) have changed implementations but not their
150 * declaration.  We need to unify these so that we don't end up with entries
151 * like:
152 *   char max(char a, char b);  Removed from API level 20
153 *   char max(char a, char b);  Added to API level 20
154 */
155static bool getUnifiedFunctionPrototypes(Function* function,
156                                         map<string, DetailedFunctionEntry>* entries) {
157    for (auto f : function->getSpecifications()) {
158        DetailedFunctionEntry entry;
159        entry.info = f->getVersionInfo();
160        for (auto p : f->getPermutations()) {
161            entry.htmlDeclaration = getDetailedHtmlDeclaration(*p);
162            const string s = stripHtml(entry.htmlDeclaration);
163            auto i = entries->find(s);
164            if (i == entries->end()) {
165                entries->insert(pair<string, DetailedFunctionEntry>(s, entry));
166            } else {
167                if (!mergeVersionInfo(&i->second.info, entry.info)) {
168                    return false;
169                }
170            }
171        }
172    }
173    return true;
174}
175
176// Convert words starting with @ into HTML references.  Returns false if error.
177static bool convertDocumentationRefences(string* s) {
178    bool success = true;
179    size_t end = 0;
180    for (;;) {
181        size_t start = s->find('@', end);
182        if (start == string::npos) {
183            break;
184        }
185        // Find the end of the identifier
186        end = start;
187        char c;
188        do {
189            c = (*s)[++end];
190        } while (isalnum(c) || c == '_');
191
192        const string id = s->substr(start + 1, end - start - 1);
193        string anchor = systemSpecification.getHtmlAnchor(id);
194        if (anchor.empty()) {
195            cerr << "Error:  Can't convert the documentation reference @" << id << "\n";
196            success = false;
197        }
198        s->replace(start, end - start, anchor);
199    }
200    return success;
201}
202
203static bool generateHtmlParagraphs(GeneratedFile* file, const vector<string>& description) {
204    bool inParagraph = false;
205    for (auto s : description) {
206        // Empty lines in the .spec marks paragraphs.
207        if (s.empty()) {
208            if (inParagraph) {
209                *file << "</p>\n";
210                inParagraph = false;
211            }
212        } else {
213            if (!inParagraph) {
214                *file << "<p> ";
215                inParagraph = true;
216            }
217        }
218        if (!convertDocumentationRefences(&s)) {
219            return false;
220        }
221        *file << s << "\n";
222    }
223    if (inParagraph) {
224        *file << "</p>\n";
225    }
226    return true;
227}
228
229static void writeSummaryTableStart(GeneratedFile* file, const string& label, bool labelIsHeading) {
230    if (labelIsHeading) {
231        *file << "<h2 style='margin-bottom: 0px;'>" << label << "</h2>\n";
232    }
233    *file << "<table class='jd-sumtable'><tbody>\n";
234    if (!labelIsHeading) {
235        *file << "  <tr><th colspan='2'>" << label << "</th></tr>\n";
236    }
237}
238
239static void writeSummaryTableEnd(GeneratedFile* file) {
240    *file << "</tbody></table>\n";
241}
242
243enum DeprecatedSelector {
244    DEPRECATED_ONLY,
245    NON_DEPRECATED_ONLY,
246    ALL,
247};
248
249static void writeSummaryTableEntry(ostream* stream, Definition* definition,
250                                   DeprecatedSelector deprecatedSelector) {
251    if (definition->hidden()) {
252        return;
253    }
254    const bool deprecated = definition->deprecated();
255    if ((deprecatedSelector == DEPRECATED_ONLY && !deprecated) ||
256        (deprecatedSelector == NON_DEPRECATED_ONLY && deprecated)) {
257        return;
258    }
259
260    *stream << "  <tr class='alt-color api apilevel-1'>\n";
261    *stream << "    <td class='jd-linkcol'>\n";
262    *stream << "      <a href='" << definition->getUrl() << "'>" << definition->getName()
263            << "</a>\n";
264    *stream << "    </td>\n";
265    *stream << "    <td class='jd-descrcol' width='100%'>\n";
266    *stream << "      ";
267    if (deprecated) {
268        *stream << "<b>Deprecated</b>.  ";
269    }
270    *stream << definition->getSummary() << "\n";
271    *stream << "    </td>\n";
272    *stream << "  </tr>\n";
273}
274
275static void writeSummaryTable(GeneratedFile* file, const ostringstream* entries, const char* name,
276                              DeprecatedSelector deprecatedSelector, bool labelAsHeader) {
277    string s = entries->str();
278    if (!s.empty()) {
279        string prefix;
280        if (deprecatedSelector == DEPRECATED_ONLY) {
281            prefix = "Deprecated ";
282        }
283        writeSummaryTableStart(file, prefix + name, labelAsHeader);
284        *file << s;
285        writeSummaryTableEnd(file);
286    }
287}
288
289static void writeSummaryTables(GeneratedFile* file, const map<string, Constant*>& constants,
290                               const map<string, Type*>& types,
291                               const map<string, Function*>& functions,
292                               DeprecatedSelector deprecatedSelector, bool labelAsHeader) {
293    ostringstream constantStream;
294    for (auto e : constants) {
295        writeSummaryTableEntry(&constantStream, e.second, deprecatedSelector);
296    }
297    writeSummaryTable(file, &constantStream, "Constants", deprecatedSelector, labelAsHeader);
298
299    ostringstream typeStream;
300    for (auto e : types) {
301        writeSummaryTableEntry(&typeStream, e.second, deprecatedSelector);
302    }
303    writeSummaryTable(file, &typeStream, "Types", deprecatedSelector, labelAsHeader);
304
305    ostringstream functionStream;
306    for (auto e : functions) {
307        writeSummaryTableEntry(&functionStream, e.second, deprecatedSelector);
308    }
309    writeSummaryTable(file, &functionStream, "Functions", deprecatedSelector, labelAsHeader);
310}
311
312static void writeHtmlVersionTag(GeneratedFile* file, VersionInfo info,
313                                bool addSpacing) {
314    ostringstream stream;
315    if (info.intSize == 32) {
316        stream << "When compiling for 32 bits. ";
317    } else if (info.intSize == 64) {
318        stream << "When compiling for 64 bits. ";
319    }
320
321    if (info.minVersion > 1 || info.maxVersion) {
322        const char* mid =
323                    "<a "
324                    "href='http://developer.android.com/guide/topics/manifest/"
325                    "uses-sdk-element.html#ApiLevels'>API level ";
326        if (info.minVersion <= 1) {
327            // No minimum
328            if (info.maxVersion > 0) {
329                stream << "Removed from " << mid << info.maxVersion + 1 << " and higher";
330            }
331        } else {
332            if (info.maxVersion == 0) {
333                // No maximum
334                stream << "Added in " << mid << info.minVersion;
335            } else {
336                stream << mid << info.minVersion << " - " << info.maxVersion;
337            }
338        }
339        stream << "</a>";
340    }
341    string s = stream.str();
342    // Remove any trailing whitespace
343    while (s.back() == ' ') {
344        s.pop_back();
345    }
346    if (!s.empty()) {
347        *file << (addSpacing ? "    " : "") << s << "\n";
348    }
349}
350
351static void writeDetailedTypeSpecification(GeneratedFile* file, const TypeSpecification* spec) {
352    switch (spec->getKind()) {
353        case SIMPLE: {
354            Type* type = spec->getType();
355            *file << "<p>A typedef of: " << spec->getSimpleType()
356                  << makeAttributeTag(spec->getAttribute(), "", type->getDeprecatedApiLevel(),
357                                      type->getDeprecatedMessage())
358                  << "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
359            writeHtmlVersionTag(file, spec->getVersionInfo(), false);
360            *file << "</p>\n";
361            break;
362        }
363        case RS_OBJECT: {
364            *file << "<p>";
365            writeHtmlVersionTag(file, spec->getVersionInfo(), false);
366            *file << "</p>\n";
367            break;
368        }
369        case ENUM: {
370            *file << "<p>An enum with the following values:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\n";
371            writeHtmlVersionTag(file, spec->getVersionInfo(), false);
372            *file << "</p>\n";
373
374            *file << "  <table class='jd-tagtable'><tbody>\n";
375            const vector<string>& values = spec->getValues();
376            const vector<string>& valueComments = spec->getValueComments();
377            for (size_t i = 0; i < values.size(); i++) {
378                *file << "    <tr><th>" << values[i] << "</th><td>";
379                if (valueComments.size() > i) {
380                    *file << valueComments[i];
381                }
382                *file << "</td></tr>\n";
383            }
384            *file << "  </tbody></table><br/>\n";
385            break;
386        }
387        case STRUCT: {
388            *file << "<p>A structure with the following fields:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
389            writeHtmlVersionTag(file, spec->getVersionInfo(), false);
390            *file << "</p>\n";
391
392            *file << "  <table class='jd-tagtable'><tbody>\n";
393            const vector<string>& fields = spec->getFields();
394            const vector<string>& fieldComments = spec->getFieldComments();
395            for (size_t i = 0; i < fields.size(); i++) {
396                *file << "    <tr><th>" << fields[i] << "</th><td>";
397                if (fieldComments.size() > i && !fieldComments[i].empty()) {
398                    *file << fieldComments[i];
399                }
400                *file << "</td></tr>\n";
401            }
402            *file << "  </tbody></table><br/>\n";
403            break;
404        }
405    }
406}
407
408static void writeDetailedConstantSpecification(GeneratedFile* file, ConstantSpecification* c) {
409    *file << "      <tr><td>";
410    *file << "Value: " << c->getValue() << "\n";
411    writeHtmlVersionTag(file, c->getVersionInfo(), true);
412    *file << "      </td></tr>\n";
413    *file << "<br/>\n";
414}
415
416static bool writeOverviewForFile(GeneratedFile* file, const SpecFile& specFile) {
417    bool success = true;
418    *file << "<h2>" << specFile.getBriefDescription() << "</h2>\n";
419    if (!generateHtmlParagraphs(file, specFile.getFullDescription())) {
420        success = false;
421    }
422
423    // Write the summary tables.
424    // file << "<h2>Summary</h2>\n";
425    writeSummaryTables(file, specFile.getDocumentedConstants(), specFile.getDocumentedTypes(),
426                       specFile.getDocumentedFunctions(), NON_DEPRECATED_ONLY, false);
427
428    return success;
429}
430
431static bool generateOverview(const string& directory) {
432    GeneratedFile file;
433    if (!file.start(directory, OVERVIEW_HTML_FILE_NAME)) {
434        return false;
435    }
436    bool success = true;
437
438    // Take the description from the first spec file (rs_core.spec, based on how
439    // currently this generator is called)
440    writeHeader(&file, "Runtime API Reference",
441                *(systemSpecification.getSpecFiles()[0]));
442
443    for (auto specFile : systemSpecification.getSpecFiles()) {
444        if (!writeOverviewForFile(&file, *specFile)) {
445            success = false;
446        }
447    }
448
449    writeFooter(&file);
450    file.close();
451    return success;
452}
453
454static bool generateAlphabeticalIndex(const string& directory) {
455    GeneratedFile file;
456    if (!file.start(directory, INDEX_HTML_FILE_NAME)) {
457        return false;
458    }
459    writeHeader(&file, "Index", SpecFile(""));
460
461    writeSummaryTables(&file, systemSpecification.getConstants(), systemSpecification.getTypes(),
462                       systemSpecification.getFunctions(), NON_DEPRECATED_ONLY, true);
463
464    writeSummaryTables(&file, systemSpecification.getConstants(), systemSpecification.getTypes(),
465                       systemSpecification.getFunctions(), DEPRECATED_ONLY, true);
466
467    writeFooter(&file);
468    file.close();
469    return true;
470}
471
472static void writeDeprecatedWarning(GeneratedFile* file, Definition* definition) {
473    if (definition->deprecated()) {
474        *file << "    <p><b>Deprecated.</b>  ";
475        string s = definition->getDeprecatedMessage();
476        convertDocumentationRefences(&s);
477        if (!s.empty()) {
478            *file << s;
479        } else {
480            *file << "Do not use.";
481        }
482        *file << "</p>\n";
483    }
484}
485
486static bool writeDetailedConstant(GeneratedFile* file, Constant* constant) {
487    if (constant->hidden()) {
488        return true;
489    }
490    const string& name = constant->getName();
491
492    *file << "<a name='android_rs:" << name << "'></a>\n";
493    *file << "<div class='jd-details'>\n";
494    *file << "  <h4 class='jd-details-title'>\n";
495    *file << "    <span class='sympad'>" << name << "</span>\n";
496    *file << "    <span class='normal'>: " << constant->getSummary() << "</span>\n";
497    *file << "  </h4>\n";
498
499    *file << "  <div class='jd-details-descr'>\n";
500    *file << "    <table class='jd-tagtable'><tbody>\n";
501    auto specifications = constant->getSpecifications();
502    bool addSeparator = specifications.size() > 1;
503    for (auto spec : specifications) {
504        if (addSeparator) {
505            *file << "    <h5 class='jd-tagtitle'>Variant:</h5>\n";
506        }
507        writeDetailedConstantSpecification(file, spec);
508    }
509    *file << "    </tbody></table>\n";
510    *file << "  </div>\n";
511
512    *file << "    <div class='jd-tagdata jd-tagdescr'>\n";
513
514    writeDeprecatedWarning(file, constant);
515    if (!generateHtmlParagraphs(file, constant->getDescription())) {
516        return false;
517    }
518    *file << "    </div>\n";
519
520    *file << "</div>\n";
521    *file << "\n";
522    return true;
523}
524
525static bool writeDetailedType(GeneratedFile* file, Type* type) {
526    if (type->hidden()) {
527        return true;
528    }
529    const string& name = type->getName();
530
531    *file << "<a name='android_rs:" << name << "'></a>\n";
532    *file << "<div class='jd-details'>\n";
533    *file << "  <h4 class='jd-details-title'>\n";
534    *file << "    <span class='sympad'>" << name << "</span>\n";
535    *file << "    <span class='normal'>: " << type->getSummary() << "</span>\n";
536    *file << "  </h4>\n";
537
538    *file << "  <div class='jd-details-descr'>\n";
539    for (auto spec : type->getSpecifications()) {
540        writeDetailedTypeSpecification(file, spec);
541    }
542
543    writeDeprecatedWarning(file, type);
544    if (!generateHtmlParagraphs(file, type->getDescription())) {
545        return false;
546    }
547
548    *file << "  </div>\n";
549    *file << "</div>\n";
550    *file << "\n";
551    return true;
552}
553
554static bool writeDetailedFunction(GeneratedFile* file, Function* function) {
555    if (function->hidden()) {
556        return true;
557    }
558    const string& name = function->getName();
559
560    *file << "<a name='android_rs:" << name << "'></a>\n";
561    *file << "<div class='jd-details'>\n";
562    *file << "  <h4 class='jd-details-title'>\n";
563    *file << "    <span class='sympad'>" << name << "</span>\n";
564    *file << "    <span class='normal'>: " << function->getSummary() << "</span>\n";
565    *file << "  </h4>\n";
566
567    *file << "  <div class='jd-details-descr'>\n";
568    map<string, DetailedFunctionEntry> entries;
569    if (!getUnifiedFunctionPrototypes(function, &entries)) {
570        return false;
571    }
572    *file << "    <table class='jd-tagtable'><tbody>\n";
573    for (auto i : entries) {
574        *file << "      <tr>\n";
575        *file << "        <td>" << i.second.htmlDeclaration << "</td>\n";
576        *file << "        <td>";
577        writeHtmlVersionTag(file, i.second.info, true);
578        *file << "        </td>\n";
579        *file << "      </tr>\n";
580    }
581    *file << "    </tbody></table>\n";
582    *file << "  </div>\n";
583
584    if (function->someParametersAreDocumented()) {
585        *file << "  <div class='jd-tagdata'>";
586        *file << "    <h5 class='jd-tagtitle'>Parameters</h5>\n";
587        *file << "    <table class='jd-tagtable'><tbody>\n";
588        for (ParameterEntry* p : function->getParameters()) {
589            *file << "    <tr><th>" << p->name << "</th><td>" << p->documentation << "</td></tr>\n";
590        }
591        *file << "    </tbody></table>\n";
592        *file << "  </div>\n";
593    }
594
595    string ret = function->getReturnDocumentation();
596    if (!ret.empty()) {
597        *file << "  <div class='jd-tagdata'>";
598        *file << "    <h5 class='jd-tagtitle'>Returns</h5>\n";
599        *file << "    <table class='jd-tagtable'><tbody>\n";
600        *file << "    <tr><td>" << ret << "</td></tr>\n";
601        *file << "    </tbody></table>\n";
602        *file << "  </div>\n";
603    }
604
605    *file << "  <div class='jd-tagdata jd-tagdescr'>\n";
606    writeDeprecatedWarning(file, function);
607    if (!generateHtmlParagraphs(file, function->getDescription())) {
608        return false;
609    }
610    *file << "  </div>\n";
611
612    *file << "</div>\n";
613    *file << "\n";
614    return true;
615}
616
617static bool writeDetailedDocumentationFile(const string& directory,
618                                           const SpecFile& specFile) {
619    if (!specFile.hasSpecifications()) {
620        // This is true for rs_core.spec
621        return true;
622    }
623
624    GeneratedFile file;
625    const string fileName = stringReplace(specFile.getSpecFileName(), ".spec",
626                                          ".html");
627    if (!file.start(directory, fileName)) {
628        return false;
629    }
630    bool success = true;
631
632    string title = specFile.getBriefDescription();
633    writeHeader(&file, title, specFile);
634
635    file << "<h2>Overview</h2>\n";
636    if (!generateHtmlParagraphs(&file, specFile.getFullDescription())) {
637        success = false;
638    }
639
640    // Write the summary tables.
641    file << "<h2>Summary</h2>\n";
642    const auto& constants = specFile.getDocumentedConstants();
643    const auto& types = specFile.getDocumentedTypes();
644    const auto& functions = specFile.getDocumentedFunctions();
645
646    writeSummaryTables(&file, constants, types, functions, NON_DEPRECATED_ONLY, false);
647    writeSummaryTables(&file, constants, types, functions, DEPRECATED_ONLY, false);
648
649    // Write the full details of each constant, type, and function.
650    if (!constants.empty()) {
651        file << "<h2>Constants</h2>\n";
652        for (auto i : constants) {
653            if (!writeDetailedConstant(&file, i.second)) {
654                success = false;
655            }
656        }
657    }
658    if (!types.empty()) {
659        file << "<h2>Types</h2>\n";
660        for (auto i : types) {
661            if (!writeDetailedType(&file, i.second)) {
662                success = false;
663            }
664        }
665    }
666    if (!functions.empty()) {
667        file << "<h2>Functions</h2>\n";
668        for (auto i : functions) {
669            if (!writeDetailedFunction(&file, i.second)) {
670                success = false;
671            }
672        }
673    }
674
675    writeFooter(&file);
676    file.close();
677
678    if (!success) {
679        // If in error, write a final message to make it easier to figure out which file failed.
680        cerr << fileName << ": Failed due to errors.\n";
681    }
682    return success;
683}
684
685static void generateSnippet(GeneratedFile* file, const string& fileName, const string& title) {
686    const char offset[] = "                  ";
687    *file << offset << "<li><a href=\"<?cs var:toroot ?>guide/topics/renderscript/reference/"
688          << fileName << "\">\n";
689    *file << offset << "  <span class=\"en\">" << title << "</span>\n";
690    *file << offset << "</a></li>\n";
691}
692
693/* Generate a partial file of links that should be cut & pasted into the proper section of the
694 * guide_toc.cs file.
695 */
696static bool generateAndroidTableOfContentSnippet(const string& directory) {
697    GeneratedFile file;
698    if (!file.start(directory, "guide_toc.cs")) {
699        return false;
700    }
701    file << "<!-- Copy and paste the following lines into the RenderScript section of\n";
702    file << "     platform/frameworks/base/docs/html/guide/guide_toc.cs\n\n";
703
704    const char offset[] = "              ";
705    file << offset << "<li class=\"nav-section\">\n";
706    file << offset << "  <div class=\"nav-section-header\">\n";
707    file << offset << "    <a href=\"<?cs var:toroot ?>guide/topics/renderscript/reference/" <<
708            OVERVIEW_HTML_FILE_NAME << "\">\n";
709    file << offset << "      <span class=\"en\">Runtime API Reference</span>\n";
710    file << offset << "    </a></div>\n";
711    file << offset << "  <ul>\n";
712
713    for (auto specFile : systemSpecification.getSpecFiles()) {
714        if (specFile->hasSpecifications()) {
715            const string fileName = stringReplace(specFile->getSpecFileName(), ".spec", ".html");
716            generateSnippet(&file, fileName, specFile->getBriefDescription());
717        }
718    }
719    generateSnippet(&file, INDEX_HTML_FILE_NAME, "Index");
720
721    file << offset << "  </ul>\n";
722    file << offset << "</li>\n";
723
724    return true;
725}
726
727bool generateDocumentation(const string& directory) {
728    bool success = generateOverview(directory) &&
729                   generateAlphabeticalIndex(directory) &&
730                   generateAndroidTableOfContentSnippet(directory);
731    for (auto specFile : systemSpecification.getSpecFiles()) {
732        if (!writeDetailedDocumentationFile(directory, *specFile)) {
733            success = false;
734        }
735    }
736    return success;
737}
738