blob: 2d5a297f659cb08f34d2c4fffcfa424bda15b23f [file] [log] [blame]
Adam Langley95c29f32014-06-20 12:00:00 -07001// doc generates HTML files from the comments in header files.
2//
3// doc expects to be given the path to a JSON file via the --config option.
4// From that JSON (which is defined by the Config struct) it reads a list of
5// header file locations and generates HTML files for each in the current
6// directory.
7
8package main
9
10import (
11 "bufio"
12 "encoding/json"
13 "errors"
14 "flag"
15 "fmt"
16 "html/template"
17 "io/ioutil"
18 "os"
19 "path/filepath"
20 "strings"
21)
22
23// Config describes the structure of the config JSON file.
24type Config struct {
25 // BaseDirectory is a path to which other paths in the file are
26 // relative.
27 BaseDirectory string
28 Sections []ConfigSection
29}
30
31type ConfigSection struct {
32 Name string
33 // Headers is a list of paths to header files.
34 Headers []string
35}
36
37// HeaderFile is the internal representation of a header file.
38type HeaderFile struct {
39 // Name is the basename of the header file (e.g. "ex_data.html").
40 Name string
41 // Preamble contains a comment for the file as a whole. Each string
42 // is a separate paragraph.
43 Preamble []string
44 Sections []HeaderSection
David Benjamindfa9c4a2015-10-18 01:08:11 -040045 // AllDecls maps all decls to their URL fragments.
46 AllDecls map[string]string
Adam Langley95c29f32014-06-20 12:00:00 -070047}
48
49type HeaderSection struct {
50 // Preamble contains a comment for a group of functions.
51 Preamble []string
52 Decls []HeaderDecl
David Benjamin1bfce802015-09-07 13:21:08 -040053 // Anchor, if non-empty, is the URL fragment to use in anchor tags.
54 Anchor string
Adam Langley95c29f32014-06-20 12:00:00 -070055 // IsPrivate is true if the section contains private functions (as
56 // indicated by its name).
57 IsPrivate bool
58}
59
60type HeaderDecl struct {
61 // Comment contains a comment for a specific function. Each string is a
62 // paragraph. Some paragraph may contain \n runes to indicate that they
63 // are preformatted.
64 Comment []string
65 // Name contains the name of the function, if it could be extracted.
66 Name string
67 // Decl contains the preformatted C declaration itself.
68 Decl string
David Benjamin1bfce802015-09-07 13:21:08 -040069 // Anchor, if non-empty, is the URL fragment to use in anchor tags.
70 Anchor string
Adam Langley95c29f32014-06-20 12:00:00 -070071}
72
73const (
74 cppGuard = "#if defined(__cplusplus)"
75 commentStart = "/* "
76 commentEnd = " */"
David Benjaminef37ab52017-08-03 01:07:05 -040077 lineComment = "// "
Adam Langley95c29f32014-06-20 12:00:00 -070078)
79
David Benjaminef37ab52017-08-03 01:07:05 -040080func isComment(line string) bool {
81 return strings.HasPrefix(line, commentStart) || strings.HasPrefix(line, lineComment)
82}
83
Adam Langley95c29f32014-06-20 12:00:00 -070084func extractComment(lines []string, lineNo int) (comment []string, rest []string, restLineNo int, err error) {
85 if len(lines) == 0 {
86 return nil, lines, lineNo, nil
87 }
88
89 restLineNo = lineNo
90 rest = lines
91
David Benjaminef37ab52017-08-03 01:07:05 -040092 var isBlock bool
93 if strings.HasPrefix(rest[0], commentStart) {
94 isBlock = true
95 } else if !strings.HasPrefix(rest[0], lineComment) {
Adam Langley95c29f32014-06-20 12:00:00 -070096 panic("extractComment called on non-comment")
97 }
98 commentParagraph := rest[0][len(commentStart):]
99 rest = rest[1:]
100 restLineNo++
101
102 for len(rest) > 0 {
David Benjaminef37ab52017-08-03 01:07:05 -0400103 if isBlock {
104 i := strings.Index(commentParagraph, commentEnd)
105 if i >= 0 {
106 if i != len(commentParagraph)-len(commentEnd) {
107 err = fmt.Errorf("garbage after comment end on line %d", restLineNo)
108 return
109 }
110 commentParagraph = commentParagraph[:i]
111 if len(commentParagraph) > 0 {
112 comment = append(comment, commentParagraph)
113 }
Adam Langley95c29f32014-06-20 12:00:00 -0700114 return
115 }
David Benjaminef37ab52017-08-03 01:07:05 -0400116 }
117
118 line := rest[0]
119 if isBlock {
120 if !strings.HasPrefix(line, " *") {
121 err = fmt.Errorf("comment doesn't start with block prefix on line %d: %s", restLineNo, line)
122 return
123 }
124 } else if !strings.HasPrefix(line, "//") {
Adam Langley95c29f32014-06-20 12:00:00 -0700125 if len(commentParagraph) > 0 {
126 comment = append(comment, commentParagraph)
127 }
128 return
129 }
David Benjaminef37ab52017-08-03 01:07:05 -0400130 if len(line) == 2 || !isBlock || line[2] != '/' {
David Benjamin48b31502015-04-08 23:17:55 -0400131 line = line[2:]
132 }
Adam Langley95c29f32014-06-20 12:00:00 -0700133 if strings.HasPrefix(line, " ") {
134 /* Identing the lines of a paragraph marks them as
135 * preformatted. */
136 if len(commentParagraph) > 0 {
137 commentParagraph += "\n"
138 }
139 line = line[3:]
140 }
141 if len(line) > 0 {
142 commentParagraph = commentParagraph + line
143 if len(commentParagraph) > 0 && commentParagraph[0] == ' ' {
144 commentParagraph = commentParagraph[1:]
145 }
146 } else {
147 comment = append(comment, commentParagraph)
148 commentParagraph = ""
149 }
150 rest = rest[1:]
151 restLineNo++
152 }
153
154 err = errors.New("hit EOF in comment")
155 return
156}
157
158func extractDecl(lines []string, lineNo int) (decl string, rest []string, restLineNo int, err error) {
David Benjamin0a211df2016-12-17 15:25:55 -0500159 if len(lines) == 0 || len(lines[0]) == 0 {
Adam Langley95c29f32014-06-20 12:00:00 -0700160 return "", lines, lineNo, nil
161 }
162
163 rest = lines
164 restLineNo = lineNo
165
166 var stack []rune
167 for len(rest) > 0 {
168 line := rest[0]
169 for _, c := range line {
170 switch c {
171 case '(', '{', '[':
172 stack = append(stack, c)
173 case ')', '}', ']':
174 if len(stack) == 0 {
175 err = fmt.Errorf("unexpected %c on line %d", c, restLineNo)
176 return
177 }
178 var expected rune
179 switch c {
180 case ')':
181 expected = '('
182 case '}':
183 expected = '{'
184 case ']':
185 expected = '['
186 default:
187 panic("internal error")
188 }
189 if last := stack[len(stack)-1]; last != expected {
190 err = fmt.Errorf("found %c when expecting %c on line %d", c, last, restLineNo)
191 return
192 }
193 stack = stack[:len(stack)-1]
194 }
195 }
196 if len(decl) > 0 {
197 decl += "\n"
198 }
199 decl += line
200 rest = rest[1:]
201 restLineNo++
202
203 if len(stack) == 0 && (len(decl) == 0 || decl[len(decl)-1] != '\\') {
204 break
205 }
206 }
207
208 return
209}
210
David Benjamin71485af2015-04-09 00:06:03 -0400211func skipLine(s string) string {
212 i := strings.Index(s, "\n")
213 if i > 0 {
214 return s[i:]
215 }
216 return ""
217}
218
Adam Langley95c29f32014-06-20 12:00:00 -0700219func getNameFromDecl(decl string) (string, bool) {
David Benjaminb4804282015-05-16 12:12:31 -0400220 for strings.HasPrefix(decl, "#if") || strings.HasPrefix(decl, "#elif") {
David Benjamin71485af2015-04-09 00:06:03 -0400221 decl = skipLine(decl)
222 }
Adam Langleyeac0ce02016-01-25 14:26:05 -0800223
224 if strings.HasPrefix(decl, "typedef ") {
Adam Langley95c29f32014-06-20 12:00:00 -0700225 return "", false
226 }
Adam Langleyeac0ce02016-01-25 14:26:05 -0800227
228 for _, prefix := range []string{"struct ", "enum ", "#define "} {
229 if !strings.HasPrefix(decl, prefix) {
230 continue
231 }
232
233 decl = strings.TrimPrefix(decl, prefix)
234
David Benjamin6deacb32015-05-16 12:00:51 -0400235 for len(decl) > 0 && decl[0] == ' ' {
236 decl = decl[1:]
237 }
Adam Langleyeac0ce02016-01-25 14:26:05 -0800238
239 // struct and enum types can be the return type of a
240 // function.
241 if prefix[0] != '#' && strings.Index(decl, "{") == -1 {
242 break
243 }
244
David Benjamin6deacb32015-05-16 12:00:51 -0400245 i := strings.IndexAny(decl, "( ")
246 if i < 0 {
247 return "", false
248 }
249 return decl[:i], true
250 }
David Benjamin361ecc02015-09-13 01:16:50 -0400251 decl = strings.TrimPrefix(decl, "OPENSSL_EXPORT ")
252 decl = strings.TrimPrefix(decl, "STACK_OF(")
253 decl = strings.TrimPrefix(decl, "LHASH_OF(")
Adam Langley95c29f32014-06-20 12:00:00 -0700254 i := strings.Index(decl, "(")
255 if i < 0 {
256 return "", false
257 }
258 j := strings.LastIndex(decl[:i], " ")
259 if j < 0 {
260 return "", false
261 }
262 for j+1 < len(decl) && decl[j+1] == '*' {
263 j++
264 }
265 return decl[j+1 : i], true
266}
267
David Benjamin1bfce802015-09-07 13:21:08 -0400268func sanitizeAnchor(name string) string {
269 return strings.Replace(name, " ", "-", -1)
270}
271
David Benjamin5ef619e2015-10-18 00:10:28 -0400272func isPrivateSection(name string) bool {
273 return strings.HasPrefix(name, "Private functions") || strings.HasPrefix(name, "Private structures") || strings.Contains(name, "(hidden)")
274}
275
Adam Langley95c29f32014-06-20 12:00:00 -0700276func (config *Config) parseHeader(path string) (*HeaderFile, error) {
277 headerPath := filepath.Join(config.BaseDirectory, path)
278
279 headerFile, err := os.Open(headerPath)
280 if err != nil {
281 return nil, err
282 }
283 defer headerFile.Close()
284
285 scanner := bufio.NewScanner(headerFile)
286 var lines, oldLines []string
287 for scanner.Scan() {
288 lines = append(lines, scanner.Text())
289 }
290 if err := scanner.Err(); err != nil {
291 return nil, err
292 }
293
David Benjamin68a533c2016-05-17 17:36:47 -0400294 lineNo := 1
Adam Langley95c29f32014-06-20 12:00:00 -0700295 found := false
296 for i, line := range lines {
Adam Langley95c29f32014-06-20 12:00:00 -0700297 if line == cppGuard {
298 lines = lines[i+1:]
David Benjamin68a533c2016-05-17 17:36:47 -0400299 lineNo += i + 1
Adam Langley95c29f32014-06-20 12:00:00 -0700300 found = true
301 break
302 }
303 }
304
305 if !found {
306 return nil, errors.New("no C++ guard found")
307 }
308
309 if len(lines) == 0 || lines[0] != "extern \"C\" {" {
310 return nil, errors.New("no extern \"C\" found after C++ guard")
311 }
Adam Langley10f97f32016-07-12 08:09:33 -0700312 lineNo += 2
313 lines = lines[2:]
Adam Langley95c29f32014-06-20 12:00:00 -0700314
315 header := &HeaderFile{
David Benjamindfa9c4a2015-10-18 01:08:11 -0400316 Name: filepath.Base(path),
317 AllDecls: make(map[string]string),
Adam Langley95c29f32014-06-20 12:00:00 -0700318 }
319
320 for i, line := range lines {
Adam Langley95c29f32014-06-20 12:00:00 -0700321 if len(line) > 0 {
322 lines = lines[i:]
David Benjamin68a533c2016-05-17 17:36:47 -0400323 lineNo += i
Adam Langley95c29f32014-06-20 12:00:00 -0700324 break
325 }
326 }
327
328 oldLines = lines
David Benjaminef37ab52017-08-03 01:07:05 -0400329 if len(lines) > 0 && isComment(lines[0]) {
Adam Langley95c29f32014-06-20 12:00:00 -0700330 comment, rest, restLineNo, err := extractComment(lines, lineNo)
331 if err != nil {
332 return nil, err
333 }
334
335 if len(rest) > 0 && len(rest[0]) == 0 {
336 if len(rest) < 2 || len(rest[1]) != 0 {
337 return nil, errors.New("preamble comment should be followed by two blank lines")
338 }
339 header.Preamble = comment
340 lineNo = restLineNo + 2
341 lines = rest[2:]
342 } else {
343 lines = oldLines
344 }
345 }
346
David Benjamin1bfce802015-09-07 13:21:08 -0400347 allAnchors := make(map[string]struct{})
Adam Langley95c29f32014-06-20 12:00:00 -0700348
349 for {
350 // Start of a section.
351 if len(lines) == 0 {
352 return nil, errors.New("unexpected end of file")
353 }
354 line := lines[0]
355 if line == cppGuard {
356 break
357 }
358
359 if len(line) == 0 {
360 return nil, fmt.Errorf("blank line at start of section on line %d", lineNo)
361 }
362
David Benjamin1bfce802015-09-07 13:21:08 -0400363 var section HeaderSection
Adam Langley95c29f32014-06-20 12:00:00 -0700364
David Benjaminef37ab52017-08-03 01:07:05 -0400365 if isComment(line) {
Adam Langley95c29f32014-06-20 12:00:00 -0700366 comment, rest, restLineNo, err := extractComment(lines, lineNo)
367 if err != nil {
368 return nil, err
369 }
370 if len(rest) > 0 && len(rest[0]) == 0 {
David Benjamin1bfce802015-09-07 13:21:08 -0400371 anchor := sanitizeAnchor(firstSentence(comment))
372 if len(anchor) > 0 {
373 if _, ok := allAnchors[anchor]; ok {
374 return nil, fmt.Errorf("duplicate anchor: %s", anchor)
375 }
376 allAnchors[anchor] = struct{}{}
377 }
378
Adam Langley95c29f32014-06-20 12:00:00 -0700379 section.Preamble = comment
David Benjamin5ef619e2015-10-18 00:10:28 -0400380 section.IsPrivate = len(comment) > 0 && isPrivateSection(comment[0])
David Benjamin1bfce802015-09-07 13:21:08 -0400381 section.Anchor = anchor
Adam Langley95c29f32014-06-20 12:00:00 -0700382 lines = rest[1:]
383 lineNo = restLineNo + 1
384 }
385 }
386
387 for len(lines) > 0 {
388 line := lines[0]
389 if len(line) == 0 {
390 lines = lines[1:]
391 lineNo++
392 break
393 }
394 if line == cppGuard {
395 return nil, errors.New("hit ending C++ guard while in section")
396 }
397
398 var comment []string
399 var decl string
David Benjaminef37ab52017-08-03 01:07:05 -0400400 if isComment(line) {
Adam Langley95c29f32014-06-20 12:00:00 -0700401 comment, lines, lineNo, err = extractComment(lines, lineNo)
402 if err != nil {
403 return nil, err
404 }
405 }
406 if len(lines) == 0 {
407 return nil, errors.New("expected decl at EOF")
408 }
David Benjamin68a533c2016-05-17 17:36:47 -0400409 declLineNo := lineNo
Adam Langley95c29f32014-06-20 12:00:00 -0700410 decl, lines, lineNo, err = extractDecl(lines, lineNo)
411 if err != nil {
412 return nil, err
413 }
414 name, ok := getNameFromDecl(decl)
415 if !ok {
416 name = ""
417 }
418 if last := len(section.Decls) - 1; len(name) == 0 && len(comment) == 0 && last >= 0 {
419 section.Decls[last].Decl += "\n" + decl
420 } else {
Adam Langley5f889992015-11-04 14:05:00 -0800421 // As a matter of style, comments should start
422 // with the name of the thing that they are
423 // commenting on. We make an exception here for
424 // #defines (because we often have blocks of
425 // them) and collective comments, which are
426 // detected by starting with “The” or “These”.
427 if len(comment) > 0 &&
428 !strings.HasPrefix(comment[0], name) &&
David Benjamin68a533c2016-05-17 17:36:47 -0400429 !strings.HasPrefix(comment[0], "A "+name) &&
430 !strings.HasPrefix(comment[0], "An "+name) &&
Adam Langley5f889992015-11-04 14:05:00 -0800431 !strings.HasPrefix(decl, "#define ") &&
432 !strings.HasPrefix(comment[0], "The ") &&
433 !strings.HasPrefix(comment[0], "These ") {
David Benjamin68a533c2016-05-17 17:36:47 -0400434 return nil, fmt.Errorf("Comment for %q doesn't seem to match line %s:%d\n", name, path, declLineNo)
Adam Langley5f889992015-11-04 14:05:00 -0800435 }
David Benjamin1bfce802015-09-07 13:21:08 -0400436 anchor := sanitizeAnchor(name)
437 // TODO(davidben): Enforce uniqueness. This is
438 // skipped because #ifdefs currently result in
439 // duplicate table-of-contents entries.
440 allAnchors[anchor] = struct{}{}
441
David Benjamindfa9c4a2015-10-18 01:08:11 -0400442 header.AllDecls[name] = anchor
443
Adam Langley95c29f32014-06-20 12:00:00 -0700444 section.Decls = append(section.Decls, HeaderDecl{
445 Comment: comment,
446 Name: name,
447 Decl: decl,
David Benjamin1bfce802015-09-07 13:21:08 -0400448 Anchor: anchor,
Adam Langley95c29f32014-06-20 12:00:00 -0700449 })
Adam Langley95c29f32014-06-20 12:00:00 -0700450 }
451
452 if len(lines) > 0 && len(lines[0]) == 0 {
453 lines = lines[1:]
454 lineNo++
455 }
456 }
457
458 header.Sections = append(header.Sections, section)
459 }
460
461 return header, nil
462}
463
464func firstSentence(paragraphs []string) string {
465 if len(paragraphs) == 0 {
466 return ""
467 }
468 s := paragraphs[0]
469 i := strings.Index(s, ". ")
470 if i >= 0 {
471 return s[:i]
472 }
473 if lastIndex := len(s) - 1; s[lastIndex] == '.' {
474 return s[:lastIndex]
475 }
476 return s
477}
478
David Benjamind8ea3902017-08-04 19:08:44 -0400479// markupPipeWords converts |s| into an HTML string, safe to be included outside
480// a tag, while also marking up words surrounded by |.
David Benjamindfa9c4a2015-10-18 01:08:11 -0400481func markupPipeWords(allDecls map[string]string, s string) template.HTML {
David Benjamind8ea3902017-08-04 19:08:44 -0400482 // It is safe to look for '|' in the HTML-escaped version of |s|
483 // below. The escaped version cannot include '|' instead tags because
484 // there are no tags by construction.
485 s = template.HTMLEscapeString(s)
Adam Langley95c29f32014-06-20 12:00:00 -0700486 ret := ""
487
488 for {
489 i := strings.Index(s, "|")
490 if i == -1 {
491 ret += s
492 break
493 }
494 ret += s[:i]
495 s = s[i+1:]
496
497 i = strings.Index(s, "|")
498 j := strings.Index(s, " ")
499 if i > 0 && (j == -1 || j > i) {
500 ret += "<tt>"
David Benjamindfa9c4a2015-10-18 01:08:11 -0400501 anchor, isLink := allDecls[s[:i]]
502 if isLink {
503 ret += fmt.Sprintf("<a href=\"%s\">", template.HTMLEscapeString(anchor))
504 }
Adam Langley95c29f32014-06-20 12:00:00 -0700505 ret += s[:i]
David Benjamindfa9c4a2015-10-18 01:08:11 -0400506 if isLink {
507 ret += "</a>"
508 }
Adam Langley95c29f32014-06-20 12:00:00 -0700509 ret += "</tt>"
510 s = s[i+1:]
511 } else {
512 ret += "|"
513 }
514 }
515
516 return template.HTML(ret)
517}
518
519func markupFirstWord(s template.HTML) template.HTML {
David Benjamin5b082e82014-12-26 00:54:52 -0500520 start := 0
521again:
522 end := strings.Index(string(s[start:]), " ")
523 if end > 0 {
524 end += start
525 w := strings.ToLower(string(s[start:end]))
David Benjamindfa9c4a2015-10-18 01:08:11 -0400526 // The first word was already marked up as an HTML tag. Don't
527 // mark it up further.
528 if strings.ContainsRune(w, '<') {
529 return s
530 }
David Benjamin7e40d4e2015-09-07 13:17:45 -0400531 if w == "a" || w == "an" {
David Benjamin5b082e82014-12-26 00:54:52 -0500532 start = end + 1
533 goto again
534 }
535 return s[:start] + "<span class=\"first-word\">" + s[start:end] + "</span>" + s[end:]
Adam Langley95c29f32014-06-20 12:00:00 -0700536 }
537 return s
538}
539
540func newlinesToBR(html template.HTML) template.HTML {
541 s := string(html)
542 if !strings.Contains(s, "\n") {
543 return html
544 }
545 s = strings.Replace(s, "\n", "<br>", -1)
546 s = strings.Replace(s, " ", "&nbsp;", -1)
547 return template.HTML(s)
548}
549
550func generate(outPath string, config *Config) (map[string]string, error) {
David Benjamindfa9c4a2015-10-18 01:08:11 -0400551 allDecls := make(map[string]string)
552
Adam Langley95c29f32014-06-20 12:00:00 -0700553 headerTmpl := template.New("headerTmpl")
554 headerTmpl.Funcs(template.FuncMap{
555 "firstSentence": firstSentence,
David Benjamindfa9c4a2015-10-18 01:08:11 -0400556 "markupPipeWords": func(s string) template.HTML { return markupPipeWords(allDecls, s) },
Adam Langley95c29f32014-06-20 12:00:00 -0700557 "markupFirstWord": markupFirstWord,
558 "newlinesToBR": newlinesToBR,
559 })
David Benjamin5b082e82014-12-26 00:54:52 -0500560 headerTmpl, err := headerTmpl.Parse(`<!DOCTYPE html>
Adam Langley95c29f32014-06-20 12:00:00 -0700561<html>
562 <head>
563 <title>BoringSSL - {{.Name}}</title>
564 <meta charset="utf-8">
565 <link rel="stylesheet" type="text/css" href="doc.css">
566 </head>
567
568 <body>
569 <div id="main">
David Benjamin2b1ca802016-05-20 11:28:59 -0400570 <div class="title">
571 <h2>{{.Name}}</h2>
572 <a href="headers.html">All headers</a>
573 </div>
Adam Langley95c29f32014-06-20 12:00:00 -0700574
David Benjamind8ea3902017-08-04 19:08:44 -0400575 {{range .Preamble}}<p>{{. | markupPipeWords}}</p>{{end}}
Adam Langley95c29f32014-06-20 12:00:00 -0700576
577 <ol>
578 {{range .Sections}}
579 {{if not .IsPrivate}}
David Benjamind8ea3902017-08-04 19:08:44 -0400580 {{if .Anchor}}<li class="header"><a href="#{{.Anchor}}">{{.Preamble | firstSentence | markupPipeWords}}</a></li>{{end}}
Adam Langley95c29f32014-06-20 12:00:00 -0700581 {{range .Decls}}
David Benjamin1bfce802015-09-07 13:21:08 -0400582 {{if .Anchor}}<li><a href="#{{.Anchor}}"><tt>{{.Name}}</tt></a></li>{{end}}
Adam Langley95c29f32014-06-20 12:00:00 -0700583 {{end}}
584 {{end}}
585 {{end}}
586 </ol>
587
588 {{range .Sections}}
589 {{if not .IsPrivate}}
David Benjamindfa9c4a2015-10-18 01:08:11 -0400590 <div class="section" {{if .Anchor}}id="{{.Anchor}}"{{end}}>
Adam Langley95c29f32014-06-20 12:00:00 -0700591 {{if .Preamble}}
592 <div class="sectionpreamble">
David Benjamind8ea3902017-08-04 19:08:44 -0400593 {{range .Preamble}}<p>{{. | markupPipeWords}}</p>{{end}}
Adam Langley95c29f32014-06-20 12:00:00 -0700594 </div>
595 {{end}}
596
597 {{range .Decls}}
David Benjamindfa9c4a2015-10-18 01:08:11 -0400598 <div class="decl" {{if .Anchor}}id="{{.Anchor}}"{{end}}>
Adam Langley95c29f32014-06-20 12:00:00 -0700599 {{range .Comment}}
David Benjamind8ea3902017-08-04 19:08:44 -0400600 <p>{{. | markupPipeWords | newlinesToBR | markupFirstWord}}</p>
Adam Langley95c29f32014-06-20 12:00:00 -0700601 {{end}}
602 <pre>{{.Decl}}</pre>
Adam Langley95c29f32014-06-20 12:00:00 -0700603 </div>
604 {{end}}
605 </div>
606 {{end}}
607 {{end}}
608 </div>
609 </body>
610</html>`)
611 if err != nil {
612 return nil, err
613 }
614
615 headerDescriptions := make(map[string]string)
David Benjamindfa9c4a2015-10-18 01:08:11 -0400616 var headers []*HeaderFile
Adam Langley95c29f32014-06-20 12:00:00 -0700617
618 for _, section := range config.Sections {
619 for _, headerPath := range section.Headers {
620 header, err := config.parseHeader(headerPath)
621 if err != nil {
622 return nil, errors.New("while parsing " + headerPath + ": " + err.Error())
623 }
624 headerDescriptions[header.Name] = firstSentence(header.Preamble)
David Benjamindfa9c4a2015-10-18 01:08:11 -0400625 headers = append(headers, header)
626
627 for name, anchor := range header.AllDecls {
628 allDecls[name] = fmt.Sprintf("%s#%s", header.Name+".html", anchor)
Adam Langley95c29f32014-06-20 12:00:00 -0700629 }
David Benjamindfa9c4a2015-10-18 01:08:11 -0400630 }
631 }
632
633 for _, header := range headers {
634 filename := filepath.Join(outPath, header.Name+".html")
635 file, err := os.OpenFile(filename, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
636 if err != nil {
637 panic(err)
638 }
639 defer file.Close()
640 if err := headerTmpl.Execute(file, header); err != nil {
641 return nil, err
Adam Langley95c29f32014-06-20 12:00:00 -0700642 }
643 }
644
645 return headerDescriptions, nil
646}
647
648func generateIndex(outPath string, config *Config, headerDescriptions map[string]string) error {
649 indexTmpl := template.New("indexTmpl")
650 indexTmpl.Funcs(template.FuncMap{
651 "baseName": filepath.Base,
652 "headerDescription": func(header string) string {
653 return headerDescriptions[header]
654 },
655 })
656 indexTmpl, err := indexTmpl.Parse(`<!DOCTYPE html5>
657
658 <head>
659 <title>BoringSSL - Headers</title>
660 <meta charset="utf-8">
661 <link rel="stylesheet" type="text/css" href="doc.css">
662 </head>
663
664 <body>
665 <div id="main">
David Benjamin2b1ca802016-05-20 11:28:59 -0400666 <div class="title">
667 <h2>BoringSSL Headers</h2>
668 </div>
Adam Langley95c29f32014-06-20 12:00:00 -0700669 <table>
670 {{range .Sections}}
671 <tr class="header"><td colspan="2">{{.Name}}</td></tr>
672 {{range .Headers}}
673 <tr><td><a href="{{. | baseName}}.html">{{. | baseName}}</a></td><td>{{. | baseName | headerDescription}}</td></tr>
674 {{end}}
675 {{end}}
676 </table>
677 </div>
678 </body>
679</html>`)
680
681 if err != nil {
682 return err
683 }
684
685 file, err := os.OpenFile(filepath.Join(outPath, "headers.html"), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
686 if err != nil {
687 panic(err)
688 }
689 defer file.Close()
690
691 if err := indexTmpl.Execute(file, config); err != nil {
692 return err
693 }
694
695 return nil
696}
697
Brian Smith55a3cf42015-08-09 17:08:49 -0400698func copyFile(outPath string, inFilePath string) error {
699 bytes, err := ioutil.ReadFile(inFilePath)
700 if err != nil {
701 return err
702 }
703 return ioutil.WriteFile(filepath.Join(outPath, filepath.Base(inFilePath)), bytes, 0666)
704}
705
Adam Langley95c29f32014-06-20 12:00:00 -0700706func main() {
707 var (
Adam Langley0fd56392015-04-08 17:32:55 -0700708 configFlag *string = flag.String("config", "doc.config", "Location of config file")
709 outputDir *string = flag.String("out", ".", "Path to the directory where the output will be written")
Adam Langley95c29f32014-06-20 12:00:00 -0700710 config Config
711 )
712
713 flag.Parse()
714
715 if len(*configFlag) == 0 {
716 fmt.Printf("No config file given by --config\n")
717 os.Exit(1)
718 }
719
720 if len(*outputDir) == 0 {
721 fmt.Printf("No output directory given by --out\n")
722 os.Exit(1)
723 }
724
725 configBytes, err := ioutil.ReadFile(*configFlag)
726 if err != nil {
727 fmt.Printf("Failed to open config file: %s\n", err)
728 os.Exit(1)
729 }
730
731 if err := json.Unmarshal(configBytes, &config); err != nil {
732 fmt.Printf("Failed to parse config file: %s\n", err)
733 os.Exit(1)
734 }
735
736 headerDescriptions, err := generate(*outputDir, &config)
737 if err != nil {
738 fmt.Printf("Failed to generate output: %s\n", err)
739 os.Exit(1)
740 }
741
742 if err := generateIndex(*outputDir, &config, headerDescriptions); err != nil {
743 fmt.Printf("Failed to generate index: %s\n", err)
744 os.Exit(1)
745 }
Brian Smith55a3cf42015-08-09 17:08:49 -0400746
747 if err := copyFile(*outputDir, "doc.css"); err != nil {
748 fmt.Printf("Failed to copy static file: %s\n", err)
749 os.Exit(1)
750 }
Adam Langley95c29f32014-06-20 12:00:00 -0700751}