Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / third_party / boringssl / src / util / doc.go
1 // 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
8 package main
9
10 import (
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.
24 type 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
31 type 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.
38 type 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
45 }
46
47 type HeaderSection struct {
48         // Preamble contains a comment for a group of functions.
49         Preamble []string
50         Decls    []HeaderDecl
51         // Num is just the index of the section. It's included in order to help
52         // text/template generate anchors.
53         Num int
54         // IsPrivate is true if the section contains private functions (as
55         // indicated by its name).
56         IsPrivate bool
57 }
58
59 type HeaderDecl struct {
60         // Comment contains a comment for a specific function. Each string is a
61         // paragraph. Some paragraph may contain \n runes to indicate that they
62         // are preformatted.
63         Comment []string
64         // Name contains the name of the function, if it could be extracted.
65         Name string
66         // Decl contains the preformatted C declaration itself.
67         Decl string
68         // Num is an index for the declaration, but the value is unique for all
69         // declarations in a HeaderFile. It's included in order to help
70         // text/template generate anchors.
71         Num int
72 }
73
74 const (
75         cppGuard     = "#if defined(__cplusplus)"
76         commentStart = "/* "
77         commentEnd   = " */"
78 )
79
80 func 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                 }
114                 line = line[2:]
115                 if strings.HasPrefix(line, "   ") {
116                         /* Identing the lines of a paragraph marks them as
117                         * preformatted. */
118                         if len(commentParagraph) > 0 {
119                                 commentParagraph += "\n"
120                         }
121                         line = line[3:]
122                 }
123                 if len(line) > 0 {
124                         commentParagraph = commentParagraph + line
125                         if len(commentParagraph) > 0 && commentParagraph[0] == ' ' {
126                                 commentParagraph = commentParagraph[1:]
127                         }
128                 } else {
129                         comment = append(comment, commentParagraph)
130                         commentParagraph = ""
131                 }
132                 rest = rest[1:]
133                 restLineNo++
134         }
135
136         err = errors.New("hit EOF in comment")
137         return
138 }
139
140 func extractDecl(lines []string, lineNo int) (decl string, rest []string, restLineNo int, err error) {
141         if len(lines) == 0 {
142                 return "", lines, lineNo, nil
143         }
144
145         rest = lines
146         restLineNo = lineNo
147
148         var stack []rune
149         for len(rest) > 0 {
150                 line := rest[0]
151                 for _, c := range line {
152                         switch c {
153                         case '(', '{', '[':
154                                 stack = append(stack, c)
155                         case ')', '}', ']':
156                                 if len(stack) == 0 {
157                                         err = fmt.Errorf("unexpected %c on line %d", c, restLineNo)
158                                         return
159                                 }
160                                 var expected rune
161                                 switch c {
162                                 case ')':
163                                         expected = '('
164                                 case '}':
165                                         expected = '{'
166                                 case ']':
167                                         expected = '['
168                                 default:
169                                         panic("internal error")
170                                 }
171                                 if last := stack[len(stack)-1]; last != expected {
172                                         err = fmt.Errorf("found %c when expecting %c on line %d", c, last, restLineNo)
173                                         return
174                                 }
175                                 stack = stack[:len(stack)-1]
176                         }
177                 }
178                 if len(decl) > 0 {
179                         decl += "\n"
180                 }
181                 decl += line
182                 rest = rest[1:]
183                 restLineNo++
184
185                 if len(stack) == 0 && (len(decl) == 0 || decl[len(decl)-1] != '\\') {
186                         break
187                 }
188         }
189
190         return
191 }
192
193 func skipPast(s, skip string) string {
194         i := strings.Index(s, skip)
195         if i > 0 {
196                 return s[len(skip):]
197         }
198         return s
199 }
200
201 func getNameFromDecl(decl string) (string, bool) {
202         if strings.HasPrefix(decl, "struct ") {
203                 return "", false
204         }
205         decl = skipPast(decl, "STACK_OF(")
206         decl = skipPast(decl, "LHASH_OF(")
207         i := strings.Index(decl, "(")
208         if i < 0 {
209                 return "", false
210         }
211         j := strings.LastIndex(decl[:i], " ")
212         if j < 0 {
213                 return "", false
214         }
215         for j+1 < len(decl) && decl[j+1] == '*' {
216                 j++
217         }
218         return decl[j+1 : i], true
219 }
220
221 func (config *Config) parseHeader(path string) (*HeaderFile, error) {
222         headerPath := filepath.Join(config.BaseDirectory, path)
223
224         headerFile, err := os.Open(headerPath)
225         if err != nil {
226                 return nil, err
227         }
228         defer headerFile.Close()
229
230         scanner := bufio.NewScanner(headerFile)
231         var lines, oldLines []string
232         for scanner.Scan() {
233                 lines = append(lines, scanner.Text())
234         }
235         if err := scanner.Err(); err != nil {
236                 return nil, err
237         }
238
239         lineNo := 0
240         found := false
241         for i, line := range lines {
242                 lineNo++
243                 if line == cppGuard {
244                         lines = lines[i+1:]
245                         lineNo++
246                         found = true
247                         break
248                 }
249         }
250
251         if !found {
252                 return nil, errors.New("no C++ guard found")
253         }
254
255         if len(lines) == 0 || lines[0] != "extern \"C\" {" {
256                 return nil, errors.New("no extern \"C\" found after C++ guard")
257         }
258         lineNo += 2
259         lines = lines[2:]
260
261         header := &HeaderFile{
262                 Name: filepath.Base(path),
263         }
264
265         for i, line := range lines {
266                 lineNo++
267                 if len(line) > 0 {
268                         lines = lines[i:]
269                         break
270                 }
271         }
272
273         oldLines = lines
274         if len(lines) > 0 && strings.HasPrefix(lines[0], commentStart) {
275                 comment, rest, restLineNo, err := extractComment(lines, lineNo)
276                 if err != nil {
277                         return nil, err
278                 }
279
280                 if len(rest) > 0 && len(rest[0]) == 0 {
281                         if len(rest) < 2 || len(rest[1]) != 0 {
282                                 return nil, errors.New("preamble comment should be followed by two blank lines")
283                         }
284                         header.Preamble = comment
285                         lineNo = restLineNo + 2
286                         lines = rest[2:]
287                 } else {
288                         lines = oldLines
289                 }
290         }
291
292         var sectionNumber, declNumber int
293
294         for {
295                 // Start of a section.
296                 if len(lines) == 0 {
297                         return nil, errors.New("unexpected end of file")
298                 }
299                 line := lines[0]
300                 if line == cppGuard {
301                         break
302                 }
303
304                 if len(line) == 0 {
305                         return nil, fmt.Errorf("blank line at start of section on line %d", lineNo)
306                 }
307
308                 section := HeaderSection{
309                         Num: sectionNumber,
310                 }
311                 sectionNumber++
312
313                 if strings.HasPrefix(line, commentStart) {
314                         comment, rest, restLineNo, err := extractComment(lines, lineNo)
315                         if err != nil {
316                                 return nil, err
317                         }
318                         if len(rest) > 0 && len(rest[0]) == 0 {
319                                 section.Preamble = comment
320                                 section.IsPrivate = len(comment) > 0 && strings.HasPrefix(comment[0], "Private functions")
321                                 lines = rest[1:]
322                                 lineNo = restLineNo + 1
323                         }
324                 }
325
326                 for len(lines) > 0 {
327                         line := lines[0]
328                         if len(line) == 0 {
329                                 lines = lines[1:]
330                                 lineNo++
331                                 break
332                         }
333                         if line == cppGuard {
334                                 return nil, errors.New("hit ending C++ guard while in section")
335                         }
336
337                         var comment []string
338                         var decl string
339                         if strings.HasPrefix(line, commentStart) {
340                                 comment, lines, lineNo, err = extractComment(lines, lineNo)
341                                 if err != nil {
342                                         return nil, err
343                                 }
344                         }
345                         if len(lines) == 0 {
346                                 return nil, errors.New("expected decl at EOF")
347                         }
348                         decl, lines, lineNo, err = extractDecl(lines, lineNo)
349                         if err != nil {
350                                 return nil, err
351                         }
352                         name, ok := getNameFromDecl(decl)
353                         if !ok {
354                                 name = ""
355                         }
356                         if last := len(section.Decls) - 1; len(name) == 0 && len(comment) == 0 && last >= 0 {
357                                 section.Decls[last].Decl += "\n" + decl
358                         } else {
359                                 section.Decls = append(section.Decls, HeaderDecl{
360                                         Comment: comment,
361                                         Name:    name,
362                                         Decl:    decl,
363                                         Num:     declNumber,
364                                 })
365                                 declNumber++
366                         }
367
368                         if len(lines) > 0 && len(lines[0]) == 0 {
369                                 lines = lines[1:]
370                                 lineNo++
371                         }
372                 }
373
374                 header.Sections = append(header.Sections, section)
375         }
376
377         return header, nil
378 }
379
380 func firstSentence(paragraphs []string) string {
381         if len(paragraphs) == 0 {
382                 return ""
383         }
384         s := paragraphs[0]
385         i := strings.Index(s, ". ")
386         if i >= 0 {
387                 return s[:i]
388         }
389         if lastIndex := len(s) - 1; s[lastIndex] == '.' {
390                 return s[:lastIndex]
391         }
392         return s
393 }
394
395 func markupPipeWords(s string) template.HTML {
396         ret := ""
397
398         for {
399                 i := strings.Index(s, "|")
400                 if i == -1 {
401                         ret += s
402                         break
403                 }
404                 ret += s[:i]
405                 s = s[i+1:]
406
407                 i = strings.Index(s, "|")
408                 j := strings.Index(s, " ")
409                 if i > 0 && (j == -1 || j > i) {
410                         ret += "<tt>"
411                         ret += s[:i]
412                         ret += "</tt>"
413                         s = s[i+1:]
414                 } else {
415                         ret += "|"
416                 }
417         }
418
419         return template.HTML(ret)
420 }
421
422 func markupFirstWord(s template.HTML) template.HTML {
423         i := strings.Index(string(s), " ")
424         if i > 0 {
425                 return "<span class=\"first-word\">" + s[:i] + "</span>" + s[i:]
426         }
427         return s
428 }
429
430 func newlinesToBR(html template.HTML) template.HTML {
431         s := string(html)
432         if !strings.Contains(s, "\n") {
433                 return html
434         }
435         s = strings.Replace(s, "\n", "<br>", -1)
436         s = strings.Replace(s, " ", "&nbsp;", -1)
437         return template.HTML(s)
438 }
439
440 func generate(outPath string, config *Config) (map[string]string, error) {
441         headerTmpl := template.New("headerTmpl")
442         headerTmpl.Funcs(template.FuncMap{
443                 "firstSentence":   firstSentence,
444                 "markupPipeWords": markupPipeWords,
445                 "markupFirstWord": markupFirstWord,
446                 "newlinesToBR":    newlinesToBR,
447         })
448         headerTmpl, err := headerTmpl.Parse(`<!DOCTYPE html5>
449 <html>
450   <head>
451     <title>BoringSSL - {{.Name}}</title>
452     <meta charset="utf-8">
453     <link rel="stylesheet" type="text/css" href="doc.css">
454   </head>
455
456   <body>
457     <div id="main">
458     <h2>{{.Name}}</h2>
459
460     {{range .Preamble}}<p>{{. | html | markupPipeWords}}</p>{{end}}
461
462     <ol>
463       {{range .Sections}}
464         {{if not .IsPrivate}}
465           {{if .Preamble}}<li class="header"><a href="#section-{{.Num}}">{{.Preamble | firstSentence}}</a></li>{{end}}
466           {{range .Decls}}
467             {{if .Name}}<li><a href="#decl-{{.Num}}"><tt>{{.Name}}</tt></a></li>{{end}}
468           {{end}}
469         {{end}}
470       {{end}}
471     </ol>
472
473     {{range .Sections}}
474       {{if not .IsPrivate}}
475         <div class="section">
476         {{if .Preamble}}
477           <div class="sectionpreamble">
478           <a name="section-{{.Num}}">
479           {{range .Preamble}}<p>{{. | html | markupPipeWords}}</p>{{end}}
480           </a>
481           </div>
482         {{end}}
483
484         {{range .Decls}}
485           <div class="decl">
486           <a name="decl-{{.Num}}">
487           {{range .Comment}}
488             <p>{{. | html | markupPipeWords | newlinesToBR | markupFirstWord}}</p>
489           {{end}}
490           <pre>{{.Decl}}</pre>
491           </a>
492           </div>
493         {{end}}
494         </div>
495       {{end}}
496     {{end}}
497     </div>
498   </body>
499 </html>`)
500         if err != nil {
501                 return nil, err
502         }
503
504         headerDescriptions := make(map[string]string)
505
506         for _, section := range config.Sections {
507                 for _, headerPath := range section.Headers {
508                         header, err := config.parseHeader(headerPath)
509                         if err != nil {
510                                 return nil, errors.New("while parsing " + headerPath + ": " + err.Error())
511                         }
512                         headerDescriptions[header.Name] = firstSentence(header.Preamble)
513                         filename := filepath.Join(outPath, header.Name+".html")
514                         file, err := os.OpenFile(filename, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
515                         if err != nil {
516                                 panic(err)
517                         }
518                         defer file.Close()
519                         if err := headerTmpl.Execute(file, header); err != nil {
520                                 return nil, err
521                         }
522                 }
523         }
524
525         return headerDescriptions, nil
526 }
527
528 func generateIndex(outPath string, config *Config, headerDescriptions map[string]string) error {
529         indexTmpl := template.New("indexTmpl")
530         indexTmpl.Funcs(template.FuncMap{
531                 "baseName": filepath.Base,
532                 "headerDescription": func(header string) string {
533                         return headerDescriptions[header]
534                 },
535         })
536         indexTmpl, err := indexTmpl.Parse(`<!DOCTYPE html5>
537
538   <head>
539     <title>BoringSSL - Headers</title>
540     <meta charset="utf-8">
541     <link rel="stylesheet" type="text/css" href="doc.css">
542   </head>
543
544   <body>
545     <div id="main">
546       <table>
547         {{range .Sections}}
548           <tr class="header"><td colspan="2">{{.Name}}</td></tr>
549           {{range .Headers}}
550             <tr><td><a href="{{. | baseName}}.html">{{. | baseName}}</a></td><td>{{. | baseName | headerDescription}}</td></tr>
551           {{end}}
552         {{end}}
553       </table>
554     </div>
555   </body>
556 </html>`)
557
558         if err != nil {
559                 return err
560         }
561
562         file, err := os.OpenFile(filepath.Join(outPath, "headers.html"), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
563         if err != nil {
564                 panic(err)
565         }
566         defer file.Close()
567
568         if err := indexTmpl.Execute(file, config); err != nil {
569                 return err
570         }
571
572         return nil
573 }
574
575 func main() {
576         var (
577                 configFlag *string = flag.String("config", "", "Location of config file")
578                 outputDir  *string = flag.String("out", "", "Path to the directory where the output will be written")
579                 config     Config
580         )
581
582         flag.Parse()
583
584         if len(*configFlag) == 0 {
585                 fmt.Printf("No config file given by --config\n")
586                 os.Exit(1)
587         }
588
589         if len(*outputDir) == 0 {
590                 fmt.Printf("No output directory given by --out\n")
591                 os.Exit(1)
592         }
593
594         configBytes, err := ioutil.ReadFile(*configFlag)
595         if err != nil {
596                 fmt.Printf("Failed to open config file: %s\n", err)
597                 os.Exit(1)
598         }
599
600         if err := json.Unmarshal(configBytes, &config); err != nil {
601                 fmt.Printf("Failed to parse config file: %s\n", err)
602                 os.Exit(1)
603         }
604
605         headerDescriptions, err := generate(*outputDir, &config)
606         if err != nil {
607                 fmt.Printf("Failed to generate output: %s\n", err)
608                 os.Exit(1)
609         }
610
611         if err := generateIndex(*outputDir, &config, headerDescriptions); err != nil {
612                 fmt.Printf("Failed to generate index: %s\n", err)
613                 os.Exit(1)
614         }
615 }