blob: c03f74286d8863e69054d59ec40d2110052274d8 [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)"
Adam Langley09feb0f2016-07-11 14:07:19 -070075 endif = "#endif"
Adam Langley95c29f32014-06-20 12:00:00 -070076 commentStart = "/* "
77 commentEnd = " */"
78)
79
80func extractComment(lines []string, lineNo int) (comment []string, rest []string, restLineNo int, err error) {
81 if len(lines) == 0 {
82 return nil, lines, lineNo, nil
83 }
84
85 restLineNo = lineNo
86 rest = lines
87
88 if !strings.HasPrefix(rest[0], commentStart) {
89 panic("extractComment called on non-comment")
90 }
91 commentParagraph := rest[0][len(commentStart):]
92 rest = rest[1:]
93 restLineNo++
94
95 for len(rest) > 0 {
96 i := strings.Index(commentParagraph, commentEnd)
97 if i >= 0 {
98 if i != len(commentParagraph)-len(commentEnd) {
99 err = fmt.Errorf("garbage after comment end on line %d", restLineNo)
100 return
101 }
102 commentParagraph = commentParagraph[:i]
103 if len(commentParagraph) > 0 {
104 comment = append(comment, commentParagraph)
105 }
106 return
107 }
108
109 line := rest[0]
110 if !strings.HasPrefix(line, " *") {
111 err = fmt.Errorf("comment doesn't start with block prefix on line %d: %s", restLineNo, line)
112 return
113 }
David Benjamin48b31502015-04-08 23:17:55 -0400114 if len(line) == 2 || line[2] != '/' {
115 line = line[2:]
116 }
Adam Langley95c29f32014-06-20 12:00:00 -0700117 if strings.HasPrefix(line, " ") {
118 /* Identing the lines of a paragraph marks them as
119 * preformatted. */
120 if len(commentParagraph) > 0 {
121 commentParagraph += "\n"
122 }
123 line = line[3:]
124 }
125 if len(line) > 0 {
126 commentParagraph = commentParagraph + line
127 if len(commentParagraph) > 0 && commentParagraph[0] == ' ' {
128 commentParagraph = commentParagraph[1:]
129 }
130 } else {
131 comment = append(comment, commentParagraph)
132 commentParagraph = ""
133 }
134 rest = rest[1:]
135 restLineNo++
136 }
137
138 err = errors.New("hit EOF in comment")
139 return
140}
141
142func extractDecl(lines []string, lineNo int) (decl string, rest []string, restLineNo int, err error) {
143 if len(lines) == 0 {
144 return "", lines, lineNo, nil
145 }
146
147 rest = lines
148 restLineNo = lineNo
149
150 var stack []rune
151 for len(rest) > 0 {
152 line := rest[0]
153 for _, c := range line {
154 switch c {
155 case '(', '{', '[':
156 stack = append(stack, c)
157 case ')', '}', ']':
158 if len(stack) == 0 {
159 err = fmt.Errorf("unexpected %c on line %d", c, restLineNo)
160 return
161 }
162 var expected rune
163 switch c {
164 case ')':
165 expected = '('
166 case '}':
167 expected = '{'
168 case ']':
169 expected = '['
170 default:
171 panic("internal error")
172 }
173 if last := stack[len(stack)-1]; last != expected {
174 err = fmt.Errorf("found %c when expecting %c on line %d", c, last, restLineNo)
175 return
176 }
177 stack = stack[:len(stack)-1]
178 }
179 }
180 if len(decl) > 0 {
181 decl += "\n"
182 }
183 decl += line
184 rest = rest[1:]
185 restLineNo++
186
187 if len(stack) == 0 && (len(decl) == 0 || decl[len(decl)-1] != '\\') {
188 break
189 }
190 }
191
192 return
193}
194
David Benjamin71485af2015-04-09 00:06:03 -0400195func skipLine(s string) string {
196 i := strings.Index(s, "\n")
197 if i > 0 {
198 return s[i:]
199 }
200 return ""
201}
202
Adam Langley95c29f32014-06-20 12:00:00 -0700203func getNameFromDecl(decl string) (string, bool) {
David Benjaminb4804282015-05-16 12:12:31 -0400204 for strings.HasPrefix(decl, "#if") || strings.HasPrefix(decl, "#elif") {
David Benjamin71485af2015-04-09 00:06:03 -0400205 decl = skipLine(decl)
206 }
Adam Langleyeac0ce02016-01-25 14:26:05 -0800207
208 if strings.HasPrefix(decl, "typedef ") {
Adam Langley95c29f32014-06-20 12:00:00 -0700209 return "", false
210 }
Adam Langleyeac0ce02016-01-25 14:26:05 -0800211
212 for _, prefix := range []string{"struct ", "enum ", "#define "} {
213 if !strings.HasPrefix(decl, prefix) {
214 continue
215 }
216
217 decl = strings.TrimPrefix(decl, prefix)
218
David Benjamin6deacb32015-05-16 12:00:51 -0400219 for len(decl) > 0 && decl[0] == ' ' {
220 decl = decl[1:]
221 }
Adam Langleyeac0ce02016-01-25 14:26:05 -0800222
223 // struct and enum types can be the return type of a
224 // function.
225 if prefix[0] != '#' && strings.Index(decl, "{") == -1 {
226 break
227 }
228
David Benjamin6deacb32015-05-16 12:00:51 -0400229 i := strings.IndexAny(decl, "( ")
230 if i < 0 {
231 return "", false
232 }
233 return decl[:i], true
234 }
David Benjamin361ecc02015-09-13 01:16:50 -0400235 decl = strings.TrimPrefix(decl, "OPENSSL_EXPORT ")
236 decl = strings.TrimPrefix(decl, "STACK_OF(")
237 decl = strings.TrimPrefix(decl, "LHASH_OF(")
Adam Langley95c29f32014-06-20 12:00:00 -0700238 i := strings.Index(decl, "(")
239 if i < 0 {
240 return "", false
241 }
242 j := strings.LastIndex(decl[:i], " ")
243 if j < 0 {
244 return "", false
245 }
246 for j+1 < len(decl) && decl[j+1] == '*' {
247 j++
248 }
249 return decl[j+1 : i], true
250}
251
David Benjamin1bfce802015-09-07 13:21:08 -0400252func sanitizeAnchor(name string) string {
253 return strings.Replace(name, " ", "-", -1)
254}
255
David Benjamin5ef619e2015-10-18 00:10:28 -0400256func isPrivateSection(name string) bool {
257 return strings.HasPrefix(name, "Private functions") || strings.HasPrefix(name, "Private structures") || strings.Contains(name, "(hidden)")
258}
259
Adam Langley95c29f32014-06-20 12:00:00 -0700260func (config *Config) parseHeader(path string) (*HeaderFile, error) {
261 headerPath := filepath.Join(config.BaseDirectory, path)
262
263 headerFile, err := os.Open(headerPath)
264 if err != nil {
265 return nil, err
266 }
267 defer headerFile.Close()
268
269 scanner := bufio.NewScanner(headerFile)
270 var lines, oldLines []string
271 for scanner.Scan() {
272 lines = append(lines, scanner.Text())
273 }
274 if err := scanner.Err(); err != nil {
275 return nil, err
276 }
277
David Benjamin68a533c2016-05-17 17:36:47 -0400278 lineNo := 1
Adam Langley95c29f32014-06-20 12:00:00 -0700279 found := false
280 for i, line := range lines {
Adam Langley95c29f32014-06-20 12:00:00 -0700281 if line == cppGuard {
282 lines = lines[i+1:]
David Benjamin68a533c2016-05-17 17:36:47 -0400283 lineNo += i + 1
Adam Langley95c29f32014-06-20 12:00:00 -0700284 found = true
285 break
286 }
287 }
288
289 if !found {
290 return nil, errors.New("no C++ guard found")
291 }
292
293 if len(lines) == 0 || lines[0] != "extern \"C\" {" {
294 return nil, errors.New("no extern \"C\" found after C++ guard")
295 }
Adam Langley09feb0f2016-07-11 14:07:19 -0700296
297 for i, line := range lines {
298 if line == endif {
299 lines = lines[i+1:]
300 lineNo += i + 1
301 break
302 }
303 }
Adam Langley95c29f32014-06-20 12:00:00 -0700304
305 header := &HeaderFile{
David Benjamindfa9c4a2015-10-18 01:08:11 -0400306 Name: filepath.Base(path),
307 AllDecls: make(map[string]string),
Adam Langley95c29f32014-06-20 12:00:00 -0700308 }
309
310 for i, line := range lines {
Adam Langley95c29f32014-06-20 12:00:00 -0700311 if len(line) > 0 {
312 lines = lines[i:]
David Benjamin68a533c2016-05-17 17:36:47 -0400313 lineNo += i
Adam Langley95c29f32014-06-20 12:00:00 -0700314 break
315 }
316 }
317
318 oldLines = lines
319 if len(lines) > 0 && strings.HasPrefix(lines[0], commentStart) {
320 comment, rest, restLineNo, err := extractComment(lines, lineNo)
321 if err != nil {
322 return nil, err
323 }
324
325 if len(rest) > 0 && len(rest[0]) == 0 {
326 if len(rest) < 2 || len(rest[1]) != 0 {
327 return nil, errors.New("preamble comment should be followed by two blank lines")
328 }
329 header.Preamble = comment
330 lineNo = restLineNo + 2
331 lines = rest[2:]
332 } else {
333 lines = oldLines
334 }
335 }
336
David Benjamin1bfce802015-09-07 13:21:08 -0400337 allAnchors := make(map[string]struct{})
Adam Langley95c29f32014-06-20 12:00:00 -0700338
339 for {
340 // Start of a section.
341 if len(lines) == 0 {
342 return nil, errors.New("unexpected end of file")
343 }
344 line := lines[0]
345 if line == cppGuard {
346 break
347 }
348
349 if len(line) == 0 {
350 return nil, fmt.Errorf("blank line at start of section on line %d", lineNo)
351 }
352
David Benjamin1bfce802015-09-07 13:21:08 -0400353 var section HeaderSection
Adam Langley95c29f32014-06-20 12:00:00 -0700354
355 if strings.HasPrefix(line, commentStart) {
356 comment, rest, restLineNo, err := extractComment(lines, lineNo)
357 if err != nil {
358 return nil, err
359 }
360 if len(rest) > 0 && len(rest[0]) == 0 {
David Benjamin1bfce802015-09-07 13:21:08 -0400361 anchor := sanitizeAnchor(firstSentence(comment))
362 if len(anchor) > 0 {
363 if _, ok := allAnchors[anchor]; ok {
364 return nil, fmt.Errorf("duplicate anchor: %s", anchor)
365 }
366 allAnchors[anchor] = struct{}{}
367 }
368
Adam Langley95c29f32014-06-20 12:00:00 -0700369 section.Preamble = comment
David Benjamin5ef619e2015-10-18 00:10:28 -0400370 section.IsPrivate = len(comment) > 0 && isPrivateSection(comment[0])
David Benjamin1bfce802015-09-07 13:21:08 -0400371 section.Anchor = anchor
Adam Langley95c29f32014-06-20 12:00:00 -0700372 lines = rest[1:]
373 lineNo = restLineNo + 1
374 }
375 }
376
377 for len(lines) > 0 {
378 line := lines[0]
379 if len(line) == 0 {
380 lines = lines[1:]
381 lineNo++
382 break
383 }
384 if line == cppGuard {
385 return nil, errors.New("hit ending C++ guard while in section")
386 }
387
388 var comment []string
389 var decl string
390 if strings.HasPrefix(line, commentStart) {
391 comment, lines, lineNo, err = extractComment(lines, lineNo)
392 if err != nil {
393 return nil, err
394 }
395 }
396 if len(lines) == 0 {
397 return nil, errors.New("expected decl at EOF")
398 }
David Benjamin68a533c2016-05-17 17:36:47 -0400399 declLineNo := lineNo
Adam Langley95c29f32014-06-20 12:00:00 -0700400 decl, lines, lineNo, err = extractDecl(lines, lineNo)
401 if err != nil {
402 return nil, err
403 }
404 name, ok := getNameFromDecl(decl)
405 if !ok {
406 name = ""
407 }
408 if last := len(section.Decls) - 1; len(name) == 0 && len(comment) == 0 && last >= 0 {
409 section.Decls[last].Decl += "\n" + decl
410 } else {
Adam Langley5f889992015-11-04 14:05:00 -0800411 // As a matter of style, comments should start
412 // with the name of the thing that they are
413 // commenting on. We make an exception here for
414 // #defines (because we often have blocks of
415 // them) and collective comments, which are
416 // detected by starting with “The” or “These”.
417 if len(comment) > 0 &&
418 !strings.HasPrefix(comment[0], name) &&
David Benjamin68a533c2016-05-17 17:36:47 -0400419 !strings.HasPrefix(comment[0], "A "+name) &&
420 !strings.HasPrefix(comment[0], "An "+name) &&
Adam Langley5f889992015-11-04 14:05:00 -0800421 !strings.HasPrefix(decl, "#define ") &&
422 !strings.HasPrefix(comment[0], "The ") &&
423 !strings.HasPrefix(comment[0], "These ") {
David Benjamin68a533c2016-05-17 17:36:47 -0400424 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 -0800425 }
David Benjamin1bfce802015-09-07 13:21:08 -0400426 anchor := sanitizeAnchor(name)
427 // TODO(davidben): Enforce uniqueness. This is
428 // skipped because #ifdefs currently result in
429 // duplicate table-of-contents entries.
430 allAnchors[anchor] = struct{}{}
431
David Benjamindfa9c4a2015-10-18 01:08:11 -0400432 header.AllDecls[name] = anchor
433
Adam Langley95c29f32014-06-20 12:00:00 -0700434 section.Decls = append(section.Decls, HeaderDecl{
435 Comment: comment,
436 Name: name,
437 Decl: decl,
David Benjamin1bfce802015-09-07 13:21:08 -0400438 Anchor: anchor,
Adam Langley95c29f32014-06-20 12:00:00 -0700439 })
Adam Langley95c29f32014-06-20 12:00:00 -0700440 }
441
442 if len(lines) > 0 && len(lines[0]) == 0 {
443 lines = lines[1:]
444 lineNo++
445 }
446 }
447
448 header.Sections = append(header.Sections, section)
449 }
450
451 return header, nil
452}
453
454func firstSentence(paragraphs []string) string {
455 if len(paragraphs) == 0 {
456 return ""
457 }
458 s := paragraphs[0]
459 i := strings.Index(s, ". ")
460 if i >= 0 {
461 return s[:i]
462 }
463 if lastIndex := len(s) - 1; s[lastIndex] == '.' {
464 return s[:lastIndex]
465 }
466 return s
467}
468
David Benjamindfa9c4a2015-10-18 01:08:11 -0400469func markupPipeWords(allDecls map[string]string, s string) template.HTML {
Adam Langley95c29f32014-06-20 12:00:00 -0700470 ret := ""
471
472 for {
473 i := strings.Index(s, "|")
474 if i == -1 {
475 ret += s
476 break
477 }
478 ret += s[:i]
479 s = s[i+1:]
480
481 i = strings.Index(s, "|")
482 j := strings.Index(s, " ")
483 if i > 0 && (j == -1 || j > i) {
484 ret += "<tt>"
David Benjamindfa9c4a2015-10-18 01:08:11 -0400485 anchor, isLink := allDecls[s[:i]]
486 if isLink {
487 ret += fmt.Sprintf("<a href=\"%s\">", template.HTMLEscapeString(anchor))
488 }
Adam Langley95c29f32014-06-20 12:00:00 -0700489 ret += s[:i]
David Benjamindfa9c4a2015-10-18 01:08:11 -0400490 if isLink {
491 ret += "</a>"
492 }
Adam Langley95c29f32014-06-20 12:00:00 -0700493 ret += "</tt>"
494 s = s[i+1:]
495 } else {
496 ret += "|"
497 }
498 }
499
500 return template.HTML(ret)
501}
502
503func markupFirstWord(s template.HTML) template.HTML {
David Benjamin5b082e82014-12-26 00:54:52 -0500504 start := 0
505again:
506 end := strings.Index(string(s[start:]), " ")
507 if end > 0 {
508 end += start
509 w := strings.ToLower(string(s[start:end]))
David Benjamindfa9c4a2015-10-18 01:08:11 -0400510 // The first word was already marked up as an HTML tag. Don't
511 // mark it up further.
512 if strings.ContainsRune(w, '<') {
513 return s
514 }
David Benjamin7e40d4e2015-09-07 13:17:45 -0400515 if w == "a" || w == "an" {
David Benjamin5b082e82014-12-26 00:54:52 -0500516 start = end + 1
517 goto again
518 }
519 return s[:start] + "<span class=\"first-word\">" + s[start:end] + "</span>" + s[end:]
Adam Langley95c29f32014-06-20 12:00:00 -0700520 }
521 return s
522}
523
524func newlinesToBR(html template.HTML) template.HTML {
525 s := string(html)
526 if !strings.Contains(s, "\n") {
527 return html
528 }
529 s = strings.Replace(s, "\n", "<br>", -1)
530 s = strings.Replace(s, " ", "&nbsp;", -1)
531 return template.HTML(s)
532}
533
534func generate(outPath string, config *Config) (map[string]string, error) {
David Benjamindfa9c4a2015-10-18 01:08:11 -0400535 allDecls := make(map[string]string)
536
Adam Langley95c29f32014-06-20 12:00:00 -0700537 headerTmpl := template.New("headerTmpl")
538 headerTmpl.Funcs(template.FuncMap{
539 "firstSentence": firstSentence,
David Benjamindfa9c4a2015-10-18 01:08:11 -0400540 "markupPipeWords": func(s string) template.HTML { return markupPipeWords(allDecls, s) },
Adam Langley95c29f32014-06-20 12:00:00 -0700541 "markupFirstWord": markupFirstWord,
542 "newlinesToBR": newlinesToBR,
543 })
David Benjamin5b082e82014-12-26 00:54:52 -0500544 headerTmpl, err := headerTmpl.Parse(`<!DOCTYPE html>
Adam Langley95c29f32014-06-20 12:00:00 -0700545<html>
546 <head>
547 <title>BoringSSL - {{.Name}}</title>
548 <meta charset="utf-8">
549 <link rel="stylesheet" type="text/css" href="doc.css">
550 </head>
551
552 <body>
553 <div id="main">
David Benjamin2b1ca802016-05-20 11:28:59 -0400554 <div class="title">
555 <h2>{{.Name}}</h2>
556 <a href="headers.html">All headers</a>
557 </div>
Adam Langley95c29f32014-06-20 12:00:00 -0700558
559 {{range .Preamble}}<p>{{. | html | markupPipeWords}}</p>{{end}}
560
561 <ol>
562 {{range .Sections}}
563 {{if not .IsPrivate}}
David Benjamin1bfce802015-09-07 13:21:08 -0400564 {{if .Anchor}}<li class="header"><a href="#{{.Anchor}}">{{.Preamble | firstSentence | html | markupPipeWords}}</a></li>{{end}}
Adam Langley95c29f32014-06-20 12:00:00 -0700565 {{range .Decls}}
David Benjamin1bfce802015-09-07 13:21:08 -0400566 {{if .Anchor}}<li><a href="#{{.Anchor}}"><tt>{{.Name}}</tt></a></li>{{end}}
Adam Langley95c29f32014-06-20 12:00:00 -0700567 {{end}}
568 {{end}}
569 {{end}}
570 </ol>
571
572 {{range .Sections}}
573 {{if not .IsPrivate}}
David Benjamindfa9c4a2015-10-18 01:08:11 -0400574 <div class="section" {{if .Anchor}}id="{{.Anchor}}"{{end}}>
Adam Langley95c29f32014-06-20 12:00:00 -0700575 {{if .Preamble}}
576 <div class="sectionpreamble">
Adam Langley95c29f32014-06-20 12:00:00 -0700577 {{range .Preamble}}<p>{{. | html | markupPipeWords}}</p>{{end}}
Adam Langley95c29f32014-06-20 12:00:00 -0700578 </div>
579 {{end}}
580
581 {{range .Decls}}
David Benjamindfa9c4a2015-10-18 01:08:11 -0400582 <div class="decl" {{if .Anchor}}id="{{.Anchor}}"{{end}}>
Adam Langley95c29f32014-06-20 12:00:00 -0700583 {{range .Comment}}
584 <p>{{. | html | markupPipeWords | newlinesToBR | markupFirstWord}}</p>
585 {{end}}
586 <pre>{{.Decl}}</pre>
Adam Langley95c29f32014-06-20 12:00:00 -0700587 </div>
588 {{end}}
589 </div>
590 {{end}}
591 {{end}}
592 </div>
593 </body>
594</html>`)
595 if err != nil {
596 return nil, err
597 }
598
599 headerDescriptions := make(map[string]string)
David Benjamindfa9c4a2015-10-18 01:08:11 -0400600 var headers []*HeaderFile
Adam Langley95c29f32014-06-20 12:00:00 -0700601
602 for _, section := range config.Sections {
603 for _, headerPath := range section.Headers {
604 header, err := config.parseHeader(headerPath)
605 if err != nil {
606 return nil, errors.New("while parsing " + headerPath + ": " + err.Error())
607 }
608 headerDescriptions[header.Name] = firstSentence(header.Preamble)
David Benjamindfa9c4a2015-10-18 01:08:11 -0400609 headers = append(headers, header)
610
611 for name, anchor := range header.AllDecls {
612 allDecls[name] = fmt.Sprintf("%s#%s", header.Name+".html", anchor)
Adam Langley95c29f32014-06-20 12:00:00 -0700613 }
David Benjamindfa9c4a2015-10-18 01:08:11 -0400614 }
615 }
616
617 for _, header := range headers {
618 filename := filepath.Join(outPath, header.Name+".html")
619 file, err := os.OpenFile(filename, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
620 if err != nil {
621 panic(err)
622 }
623 defer file.Close()
624 if err := headerTmpl.Execute(file, header); err != nil {
625 return nil, err
Adam Langley95c29f32014-06-20 12:00:00 -0700626 }
627 }
628
629 return headerDescriptions, nil
630}
631
632func generateIndex(outPath string, config *Config, headerDescriptions map[string]string) error {
633 indexTmpl := template.New("indexTmpl")
634 indexTmpl.Funcs(template.FuncMap{
635 "baseName": filepath.Base,
636 "headerDescription": func(header string) string {
637 return headerDescriptions[header]
638 },
639 })
640 indexTmpl, err := indexTmpl.Parse(`<!DOCTYPE html5>
641
642 <head>
643 <title>BoringSSL - Headers</title>
644 <meta charset="utf-8">
645 <link rel="stylesheet" type="text/css" href="doc.css">
646 </head>
647
648 <body>
649 <div id="main">
David Benjamin2b1ca802016-05-20 11:28:59 -0400650 <div class="title">
651 <h2>BoringSSL Headers</h2>
652 </div>
Adam Langley95c29f32014-06-20 12:00:00 -0700653 <table>
654 {{range .Sections}}
655 <tr class="header"><td colspan="2">{{.Name}}</td></tr>
656 {{range .Headers}}
657 <tr><td><a href="{{. | baseName}}.html">{{. | baseName}}</a></td><td>{{. | baseName | headerDescription}}</td></tr>
658 {{end}}
659 {{end}}
660 </table>
661 </div>
662 </body>
663</html>`)
664
665 if err != nil {
666 return err
667 }
668
669 file, err := os.OpenFile(filepath.Join(outPath, "headers.html"), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
670 if err != nil {
671 panic(err)
672 }
673 defer file.Close()
674
675 if err := indexTmpl.Execute(file, config); err != nil {
676 return err
677 }
678
679 return nil
680}
681
Brian Smith55a3cf42015-08-09 17:08:49 -0400682func copyFile(outPath string, inFilePath string) error {
683 bytes, err := ioutil.ReadFile(inFilePath)
684 if err != nil {
685 return err
686 }
687 return ioutil.WriteFile(filepath.Join(outPath, filepath.Base(inFilePath)), bytes, 0666)
688}
689
Adam Langley95c29f32014-06-20 12:00:00 -0700690func main() {
691 var (
Adam Langley0fd56392015-04-08 17:32:55 -0700692 configFlag *string = flag.String("config", "doc.config", "Location of config file")
693 outputDir *string = flag.String("out", ".", "Path to the directory where the output will be written")
Adam Langley95c29f32014-06-20 12:00:00 -0700694 config Config
695 )
696
697 flag.Parse()
698
699 if len(*configFlag) == 0 {
700 fmt.Printf("No config file given by --config\n")
701 os.Exit(1)
702 }
703
704 if len(*outputDir) == 0 {
705 fmt.Printf("No output directory given by --out\n")
706 os.Exit(1)
707 }
708
709 configBytes, err := ioutil.ReadFile(*configFlag)
710 if err != nil {
711 fmt.Printf("Failed to open config file: %s\n", err)
712 os.Exit(1)
713 }
714
715 if err := json.Unmarshal(configBytes, &config); err != nil {
716 fmt.Printf("Failed to parse config file: %s\n", err)
717 os.Exit(1)
718 }
719
720 headerDescriptions, err := generate(*outputDir, &config)
721 if err != nil {
722 fmt.Printf("Failed to generate output: %s\n", err)
723 os.Exit(1)
724 }
725
726 if err := generateIndex(*outputDir, &config, headerDescriptions); err != nil {
727 fmt.Printf("Failed to generate index: %s\n", err)
728 os.Exit(1)
729 }
Brian Smith55a3cf42015-08-09 17:08:49 -0400730
731 if err := copyFile(*outputDir, "doc.css"); err != nil {
732 fmt.Printf("Failed to copy static file: %s\n", err)
733 os.Exit(1)
734 }
Adam Langley95c29f32014-06-20 12:00:00 -0700735}