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(¶m);
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] = ¶m;
407 fLinkMap[paramName] = '#' + bmhDef->fFiddle + '_' + paramName;
408 }
409 }
410
411