Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.

doc.go 15 KiB

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