• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "SkOSPath.h"
9 
10 #include "bmhParser.h"
11 #include "fiddleParser.h"
12 #include "mdOut.h"
13 #include "includeWriter.h"
14 #include "selfCheck.h"
15 
16 DEFINE_string2(status, a, "", "File containing status of documentation. (Use in place of -b -i)");
17 DEFINE_string2(bmh, b, "", "Path to a *.bmh file or a directory.");
18 DEFINE_bool2(catalog, c, false, "Write example catalog.htm. (Requires -b -f -r)");
19 DEFINE_string2(examples, e, "", "File of fiddlecli input, usually fiddle.json (For now, disables -r -f -s)");
20 DEFINE_bool2(extract, E, false, "Extract examples into fiddle.json");
21 DEFINE_string2(fiddle, f, "", "File of fiddlecli output, usually fiddleout.json.");
22 DEFINE_bool2(hack, H, false, "Do a find/replace hack to update all *.bmh files. (Requires -b)");
23 // h is reserved for help
24 DEFINE_string2(include, i, "", "Path to a *.h file or a directory.");
25 DEFINE_bool2(selfcheck, k, false, "Check bmh against itself. (Requires -b)");
26 DEFINE_bool2(stdout, o, false, "Write file out to standard out.");
27 DEFINE_bool2(populate, p, false, "Populate include from bmh. (Requires -b -i)");
28 // q is reserved for quiet
29 DEFINE_string2(ref, r, "", "Resolve refs and write *.md files to path. (Requires -b -f)");
30 DEFINE_string2(spellcheck, s, "", "Spell-check [once, all, mispelling]. (Requires -b)");
31 DEFINE_bool2(tokens, t, false, "Write bmh from include. (Requires -i)");
32 DEFINE_bool2(crosscheck, x, false, "Check bmh against includes. (Requires -b -i)");
33 // v is reserved for verbose
34 DEFINE_bool2(validate, V, false, "Validate that all anchor references have definitions. (Requires -r)");
35 DEFINE_bool2(skip, z, false, "Skip degenerate missed in legacy preprocessor.");
36 
37 // -b docs -i include/core/SkRect.h -f fiddleout.json -r site/user/api
38 // -b docs/SkIRect_Reference.bmh -H
39 /* todos:
40 
41 if #Subtopic contains #SeeAlso or #Example generate horizontal rule at end
42 constexpr populated with filter inside subtopic does not have definition body
43 
44 #List needs '# content ##', formatting
45 rewrap text to fit in some number of columns
46 #Literal is inflexible, making the entire #Code block link-less (see $Literal in SkImageInfo)
47      would rather keep links for body above #Literal, and/or make it a block and not a one-liner
48 add check to require #Const to contain #Code block if defining const or constexpr (enum consts have
49      #Code blocks inside the #Enum def)
50 subclasses (e.g. Iter in SkPath) need to check for #Line and generate overview
51      subclass methods should also disallow #In
52 
53 It's awkward that phrase param is a child of the phrase def. Since phrase refs may also be children,
54 there is special case code to skip phrase def when looking for additional substitutions in the
55 phrase def. Could put it in the token list instead I guess, or make a definition subclass used
56 by phrase def with an additional slot...
57 
58 rearrange const out for md so that const / value / short description comes first in a table,
59 followed by more elaborate descriptions, examples, seealso. In md.cpp, look to see if #Subtopic
60 has #Const children. If so, generate a summary table first.
61 Or, only allow #Line and moderate text description in #Const. Put more verbose text, example,
62 seealso, in subsequent #SubTopic. Alpha_Type does this and it looks good.
63 
64 IPoint is awkward. SkPoint and SkIPoint are named things; Point is a topic, which
65 refers to float points or integer points. There needn't be an IPoint topic.
66 One way to resolve this would be to combine SkPoint_Reference and SkIPoint_Reference into
67 Point_Reference that then contains both structs (or just move SKIPoint into SkPoint_Reference).
68 Most Point references would be replaced with SkPoint / SkIPoint (if that's what they mean),
69 or remain Point if the text indicates the concept rather one of the C structs.
70 
71 see head of selfCheck.cpp for additional todos
72 see head of spellCheck.cpp for additional todos
73  */
74 
75 /*
76   class contains named struct, enum, enum-member, method, topic, subtopic
77      everything contained by class is uniquely named
78      contained names may be reused by other classes
79   method contains named parameters
80      parameters may be reused in other methods
81  */
82 
83 
84 // pass one: parse text, collect definitions
85 // pass two: lookup references
86 
count_children(const Definition & def,MarkType markType)87 static int count_children(const Definition& def, MarkType markType) {
88     int count = 0;
89     if (markType == def.fMarkType) {
90         ++count;
91     }
92     for (auto& child : def.fChildren ) {
93         count += count_children(*child, markType);
94     }
95     return count;
96 }
97 
main(int argc,char ** const argv)98 int main(int argc, char** const argv) {
99     BmhParser bmhParser(FLAGS_skip);
100     bmhParser.validate();
101 
102     SkCommandLineFlags::SetUsage(
103         "Common Usage: bookmaker -b path/to/bmh_files -i path/to/include.h -t\n"
104         "              bookmaker -b path/to/bmh_files -e fiddle.json\n"
105         "              ~/go/bin/fiddlecli --input fiddle.json --output fiddleout.json\n"
106         "              bookmaker -b path/to/bmh_files -f fiddleout.json -r path/to/md_files\n"
107         "              bookmaker -a path/to/status.json -x\n"
108         "              bookmaker -a path/to/status.json -p\n");
109     bool help = false;
110     for (int i = 1; i < argc; i++) {
111         if (0 == strcmp("-h", argv[i]) || 0 == strcmp("--help", argv[i])) {
112             help = true;
113             for (int j = i + 1; j < argc; j++) {
114                 if (SkStrStartsWith(argv[j], '-')) {
115                     break;
116                 }
117                 help = false;
118             }
119             break;
120         }
121     }
122     if (!help) {
123         SkCommandLineFlags::Parse(argc, argv);
124     } else {
125         SkCommandLineFlags::PrintUsage();
126         const char* const commands[] = { "", "-h", "bmh", "-h", "examples", "-h", "include",
127             "-h", "fiddle", "-h", "ref", "-h", "status", "-h", "tokens",
128             "-h", "crosscheck", "-h", "populate", "-h", "spellcheck" };
129         SkCommandLineFlags::Parse(SK_ARRAY_COUNT(commands), commands);
130         return 0;
131     }
132     bool runAll = false;
133     if (FLAGS_bmh.isEmpty() && FLAGS_include.isEmpty() && FLAGS_status.isEmpty()) {
134         FLAGS_status.set(0, "docs/status.json");
135         if (FLAGS_extract) {
136             FLAGS_examples.set(0, "fiddle.json");
137         } else {
138             FLAGS_fiddle.set(0, "fiddleout.json");
139             FLAGS_ref.set(0, "site/user/api");
140             runAll = true;
141         }
142     }
143     if (!FLAGS_bmh.isEmpty() && !FLAGS_status.isEmpty()) {
144         SkDebugf("requires -b or -a but not both\n");
145         SkCommandLineFlags::PrintUsage();
146         return 1;
147     }
148     if (!FLAGS_include.isEmpty() && !FLAGS_status.isEmpty()) {
149         SkDebugf("requires -i or -a but not both\n");
150         SkCommandLineFlags::PrintUsage();
151         return 1;
152     }
153     if (FLAGS_bmh.isEmpty() && FLAGS_status.isEmpty() && FLAGS_catalog) {
154          SkDebugf("-c requires -b or -a\n");
155         SkCommandLineFlags::PrintUsage();
156         return 1;
157     }
158     if ((FLAGS_fiddle.isEmpty() || FLAGS_ref.isEmpty()) && FLAGS_catalog) {
159         SkDebugf("-c requires -f -r\n");
160         SkCommandLineFlags::PrintUsage();
161         return 1;
162     }
163     if (FLAGS_bmh.isEmpty() && FLAGS_status.isEmpty() && !FLAGS_examples.isEmpty()) {
164         SkDebugf("-e requires -b or -a\n");
165         SkCommandLineFlags::PrintUsage();
166         return 1;
167     }
168     if ((FLAGS_include.isEmpty() || FLAGS_bmh.isEmpty()) && FLAGS_status.isEmpty() &&
169             FLAGS_populate) {
170         SkDebugf("-p requires -b -i or -a\n");
171         SkCommandLineFlags::PrintUsage();
172         return 1;
173     }
174     if (FLAGS_bmh.isEmpty() && FLAGS_status.isEmpty() && !FLAGS_ref.isEmpty()) {
175         SkDebugf("-r requires -b or -a\n");
176         SkCommandLineFlags::PrintUsage();
177         return 1;
178     }
179     if (FLAGS_bmh.isEmpty() && FLAGS_status.isEmpty() && !FLAGS_spellcheck.isEmpty()) {
180         SkDebugf("-s requires -b or -a\n");
181         SkCommandLineFlags::PrintUsage();
182         return 1;
183     }
184     if (FLAGS_include.isEmpty() && FLAGS_tokens) {
185         SkDebugf("-t requires -b -i\n");
186         SkCommandLineFlags::PrintUsage();
187         return 1;
188     }
189     if ((FLAGS_include.isEmpty() || FLAGS_bmh.isEmpty()) && FLAGS_status.isEmpty() &&
190             FLAGS_crosscheck) {
191         SkDebugf("-x requires -b -i or -a\n");
192         SkCommandLineFlags::PrintUsage();
193         return 1;
194     }
195     bmhParser.reset();
196     if (!FLAGS_bmh.isEmpty()) {
197         if (!bmhParser.parseFile(FLAGS_bmh[0], ".bmh", ParserCommon::OneFile::kNo)) {
198             return -1;
199         }
200     } else if (!FLAGS_status.isEmpty()) {
201         if (!bmhParser.parseStatus(FLAGS_status[0], ".bmh", StatusFilter::kInProgress)) {
202             return -1;
203         }
204     }
205     if (FLAGS_hack) {
206         if (FLAGS_bmh.isEmpty() && FLAGS_status.isEmpty()) {
207             SkDebugf("-H or --hack requires -a or -b\n");
208             SkCommandLineFlags::PrintUsage();
209             return 1;
210         }
211         HackParser hacker(bmhParser);
212         hacker.fDebugOut = FLAGS_stdout;
213         if (!FLAGS_status.isEmpty() && !hacker.parseStatus(FLAGS_status[0], ".bmh",
214                 StatusFilter::kInProgress)) {
215             SkDebugf("hack failed\n");
216             return -1;
217         }
218         if (!FLAGS_bmh.isEmpty() && !hacker.parseFile(FLAGS_bmh[0], ".bmh",
219                 ParserCommon::OneFile::kNo)) {
220             SkDebugf("hack failed\n");
221             return -1;
222         }
223         return 0;
224     }
225     if (FLAGS_selfcheck && !SelfCheck(bmhParser)) {
226         return -1;
227     }
228     if (!FLAGS_fiddle.isEmpty() && FLAGS_examples.isEmpty()) {
229         FiddleParser fparser(&bmhParser);
230         if (!fparser.parseFromFile(FLAGS_fiddle[0])) {
231             return -1;
232         }
233     }
234     if (runAll || (!FLAGS_catalog && !FLAGS_ref.isEmpty())) {
235         IncludeParser includeParser;
236         includeParser.validate();
237         if (!FLAGS_status.isEmpty() && !includeParser.parseStatus(FLAGS_status[0], ".h",
238                 StatusFilter::kCompleted)) {
239             return -1;
240         }
241         if (!FLAGS_include.isEmpty() && !includeParser.parseFile(FLAGS_include[0], ".h",
242                 ParserCommon::OneFile::kYes)) {
243             return -1;
244         }
245         includeParser.fDebugWriteCodeBlock = FLAGS_stdout;
246         includeParser.writeCodeBlock();
247         MdOut mdOut(bmhParser, includeParser);
248         mdOut.fDebugOut = FLAGS_stdout;
249         mdOut.fDebugWriteCodeBlock = FLAGS_stdout;
250         mdOut.fValidate = FLAGS_validate;
251         if (!FLAGS_bmh.isEmpty() && mdOut.buildReferences(FLAGS_bmh[0], FLAGS_ref[0])) {
252             bmhParser.fWroteOut = true;
253         }
254         if (!FLAGS_status.isEmpty() && mdOut.buildStatus(FLAGS_status[0], FLAGS_ref[0])) {
255             bmhParser.fWroteOut = true;
256         }
257         if (FLAGS_validate) {
258             mdOut.checkAnchors();
259         }
260     }
261     if (runAll || (FLAGS_catalog && !FLAGS_ref.isEmpty())) {
262         Catalog cparser(&bmhParser);
263         cparser.fDebugOut = FLAGS_stdout;
264         if (!FLAGS_bmh.isEmpty() && !cparser.openCatalog(FLAGS_bmh[0])) {
265             return -1;
266         }
267         if (!FLAGS_status.isEmpty() && !cparser.openStatus(FLAGS_status[0])) {
268             return -1;
269         }
270         if (!cparser.parseFile(FLAGS_fiddle[0], ".txt", ParserCommon::OneFile::kNo)) {
271             return -1;
272         }
273         if (!cparser.closeCatalog(FLAGS_ref[0])) {
274             return -1;
275         }
276         bmhParser.fWroteOut = true;
277     }
278     if (FLAGS_tokens) {
279         IncludeParser::RemoveFile(nullptr, FLAGS_include[0]);
280         IncludeParser includeParser;
281         includeParser.validate();
282         if (!includeParser.parseFile(FLAGS_include[0], ".h", ParserCommon::OneFile::kNo)) {
283             return -1;
284         }
285         includeParser.fDebugOut = FLAGS_stdout;
286         if (includeParser.dumpTokens()) {
287             bmhParser.fWroteOut = true;
288         }
289     }
290     if (runAll || FLAGS_crosscheck) {
291         IncludeParser includeParser;
292         includeParser.validate();
293         if (!FLAGS_include.isEmpty() &&
294                 !includeParser.parseFile(FLAGS_include[0], ".h", ParserCommon::OneFile::kNo)) {
295             return -1;
296         }
297         if (!FLAGS_status.isEmpty() && !includeParser.parseStatus(FLAGS_status[0], ".h",
298                 StatusFilter::kCompleted)) {
299             return -1;
300         }
301         if (!includeParser.crossCheck(bmhParser)) {
302             return -1;
303         }
304     }
305     if (runAll || FLAGS_populate) {
306         IncludeWriter includeWriter;
307         includeWriter.validate();
308         if (!FLAGS_include.isEmpty() &&
309                 !includeWriter.parseFile(FLAGS_include[0], ".h", ParserCommon::OneFile::kNo)) {
310             return -1;
311         }
312         if (!FLAGS_status.isEmpty() && !includeWriter.parseStatus(FLAGS_status[0], ".h",
313                 StatusFilter::kCompleted)) {
314             return -1;
315         }
316         includeWriter.fDebugOut = FLAGS_stdout;
317         if (!includeWriter.populate(bmhParser)) {
318             return -1;
319         }
320         bmhParser.fWroteOut = true;
321     }
322     if (!FLAGS_spellcheck.isEmpty()) {
323         if (!FLAGS_bmh.isEmpty()) {
324             bmhParser.spellCheck(FLAGS_bmh[0], FLAGS_spellcheck);
325         }
326         if (!FLAGS_status.isEmpty()) {
327             bmhParser.spellStatus(FLAGS_status[0], FLAGS_spellcheck);
328         }
329         bmhParser.fWroteOut = true;
330     }
331     if (!FLAGS_examples.isEmpty()) {
332         // check to see if examples have duplicate names
333         if (!bmhParser.checkExamples()) {
334             return -1;
335         }
336         bmhParser.fDebugOut = FLAGS_stdout;
337         if (!bmhParser.dumpExamples(FLAGS_examples[0])) {
338             return -1;
339         }
340         return 0;
341     }
342     if (!bmhParser.fWroteOut) {
343         int examples = 0;
344         int methods = 0;
345         int topics = 0;
346         for (const auto& topic : bmhParser.fTopicMap) {
347             if (topic.second->fParent) {
348                 continue;
349             }
350             examples += count_children(*topic.second, MarkType::kExample);
351             methods += count_children(*topic.second, MarkType::kMethod);
352             topics += count_children(*topic.second, MarkType::kSubtopic);
353             topics += count_children(*topic.second, MarkType::kTopic);
354         }
355         SkDebugf("topics=%d classes=%d methods=%d examples=%d\n",
356                 bmhParser.fTopicMap.size(), bmhParser.fClassMap.size(),
357                 methods, examples);
358     }
359     return 0;
360 }
361 
copyToParent(NameMap * parent) const362 void NameMap::copyToParent(NameMap* parent) const {
363     size_t colons = fName.rfind("::");
364     string topName = string::npos == colons ? fName : fName.substr(colons + 2);
365     for (auto& entry : fRefMap) {
366         string scoped = topName + "::" + entry.first;
367         SkASSERT(parent->fRefMap.end() == parent->fRefMap.find(scoped));
368         parent->fRefMap[scoped] = entry.second;
369         auto scopedLinkIter = fLinkMap.find(entry.first);
370         if (fLinkMap.end() != scopedLinkIter) {
371             SkASSERT(parent->fLinkMap.end() == parent->fLinkMap.find(scoped));
372             parent->fLinkMap[scoped] = scopedLinkIter->second;
373         }
374     }
375 }
376 
setParams(Definition * bmhDef,Definition * iMethod)377 void NameMap::setParams(Definition* bmhDef, Definition* iMethod) {
378     Definition* pParent = bmhDef->csParent();
379     string parentName;
380     if (pParent) {
381         parentName = pParent->fName + "::";
382         fParent = &pParent->asRoot()->fNames;
383     }
384     fName = parentName + iMethod->fName;
385     TextParser methParams(iMethod);
386     for (auto& param : iMethod->fTokens) {
387         if (MarkType::kComment != param.fMarkType) {
388             continue;
389         }
390         TextParser paramParser(&param);
391         if (!paramParser.skipExact("@param ")) { // write parameters, if any
392             continue;
393         }
394         paramParser.skipSpace();
395         const char* start = paramParser.fChar;
396         paramParser.skipToSpace();
397         string paramName(start, paramParser.fChar - start);
398     #ifdef SK_DEBUG
399         for (char c : paramName) {
400             SkASSERT(isalnum(c) || '_' == c);
401         }
402     #endif
403         if (!methParams.containsWord(paramName.c_str(), methParams.fEnd, nullptr)) {
404             param.reportError<void>("mismatched param name");
405         }
406         fRefMap[paramName] = &param;
407         fLinkMap[paramName] = '#' + bmhDef->fFiddle + '_' + paramName;
408     }
409 }
410 
411