Não pode escolher mais do que 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 
 
 
 

626 linhas
14 KiB

  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 getNameFromDecl(decl string) (string, bool) {
  183. if strings.HasPrefix(decl, "struct ") {
  184. return "", false
  185. }
  186. decl = skipPast(decl, "STACK_OF(")
  187. decl = skipPast(decl, "LHASH_OF(")
  188. i := strings.Index(decl, "(")
  189. if i < 0 {
  190. return "", false
  191. }
  192. j := strings.LastIndex(decl[:i], " ")
  193. if j < 0 {
  194. return "", false
  195. }
  196. for j+1 < len(decl) && decl[j+1] == '*' {
  197. j++
  198. }
  199. return decl[j+1 : i], true
  200. }
  201. func (config *Config) parseHeader(path string) (*HeaderFile, error) {
  202. headerPath := filepath.Join(config.BaseDirectory, path)
  203. headerFile, err := os.Open(headerPath)
  204. if err != nil {
  205. return nil, err
  206. }
  207. defer headerFile.Close()
  208. scanner := bufio.NewScanner(headerFile)
  209. var lines, oldLines []string
  210. for scanner.Scan() {
  211. lines = append(lines, scanner.Text())
  212. }
  213. if err := scanner.Err(); err != nil {
  214. return nil, err
  215. }
  216. lineNo := 0
  217. found := false
  218. for i, line := range lines {
  219. lineNo++
  220. if line == cppGuard {
  221. lines = lines[i+1:]
  222. lineNo++
  223. found = true
  224. break
  225. }
  226. }
  227. if !found {
  228. return nil, errors.New("no C++ guard found")
  229. }
  230. if len(lines) == 0 || lines[0] != "extern \"C\" {" {
  231. return nil, errors.New("no extern \"C\" found after C++ guard")
  232. }
  233. lineNo += 2
  234. lines = lines[2:]
  235. header := &HeaderFile{
  236. Name: filepath.Base(path),
  237. }
  238. for i, line := range lines {
  239. lineNo++
  240. if len(line) > 0 {
  241. lines = lines[i:]
  242. break
  243. }
  244. }
  245. oldLines = lines
  246. if len(lines) > 0 && strings.HasPrefix(lines[0], commentStart) {
  247. comment, rest, restLineNo, err := extractComment(lines, lineNo)
  248. if err != nil {
  249. return nil, err
  250. }
  251. if len(rest) > 0 && len(rest[0]) == 0 {
  252. if len(rest) < 2 || len(rest[1]) != 0 {
  253. return nil, errors.New("preamble comment should be followed by two blank lines")
  254. }
  255. header.Preamble = comment
  256. lineNo = restLineNo + 2
  257. lines = rest[2:]
  258. } else {
  259. lines = oldLines
  260. }
  261. }
  262. var sectionNumber, declNumber int
  263. for {
  264. // Start of a section.
  265. if len(lines) == 0 {
  266. return nil, errors.New("unexpected end of file")
  267. }
  268. line := lines[0]
  269. if line == cppGuard {
  270. break
  271. }
  272. if len(line) == 0 {
  273. return nil, fmt.Errorf("blank line at start of section on line %d", lineNo)
  274. }
  275. section := HeaderSection{
  276. Num: sectionNumber,
  277. }
  278. sectionNumber++
  279. if strings.HasPrefix(line, commentStart) {
  280. comment, rest, restLineNo, err := extractComment(lines, lineNo)
  281. if err != nil {
  282. return nil, err
  283. }
  284. if len(rest) > 0 && len(rest[0]) == 0 {
  285. section.Preamble = comment
  286. section.IsPrivate = len(comment) > 0 && strings.HasPrefix(comment[0], "Private functions")
  287. lines = rest[1:]
  288. lineNo = restLineNo + 1
  289. }
  290. }
  291. for len(lines) > 0 {
  292. line := lines[0]
  293. if len(line) == 0 {
  294. lines = lines[1:]
  295. lineNo++
  296. break
  297. }
  298. if line == cppGuard {
  299. return nil, errors.New("hit ending C++ guard while in section")
  300. }
  301. var comment []string
  302. var decl string
  303. if strings.HasPrefix(line, commentStart) {
  304. comment, lines, lineNo, err = extractComment(lines, lineNo)
  305. if err != nil {
  306. return nil, err
  307. }
  308. }
  309. if len(lines) == 0 {
  310. return nil, errors.New("expected decl at EOF")
  311. }
  312. decl, lines, lineNo, err = extractDecl(lines, lineNo)
  313. if err != nil {
  314. return nil, err
  315. }
  316. name, ok := getNameFromDecl(decl)
  317. if !ok {
  318. name = ""
  319. }
  320. if last := len(section.Decls) - 1; len(name) == 0 && len(comment) == 0 && last >= 0 {
  321. section.Decls[last].Decl += "\n" + decl
  322. } else {
  323. section.Decls = append(section.Decls, HeaderDecl{
  324. Comment: comment,
  325. Name: name,
  326. Decl: decl,
  327. Num: declNumber,
  328. })
  329. declNumber++
  330. }
  331. if len(lines) > 0 && len(lines[0]) == 0 {
  332. lines = lines[1:]
  333. lineNo++
  334. }
  335. }
  336. header.Sections = append(header.Sections, section)
  337. }
  338. return header, nil
  339. }
  340. func firstSentence(paragraphs []string) string {
  341. if len(paragraphs) == 0 {
  342. return ""
  343. }
  344. s := paragraphs[0]
  345. i := strings.Index(s, ". ")
  346. if i >= 0 {
  347. return s[:i]
  348. }
  349. if lastIndex := len(s) - 1; s[lastIndex] == '.' {
  350. return s[:lastIndex]
  351. }
  352. return s
  353. }
  354. func markupPipeWords(s string) template.HTML {
  355. ret := ""
  356. for {
  357. i := strings.Index(s, "|")
  358. if i == -1 {
  359. ret += s
  360. break
  361. }
  362. ret += s[:i]
  363. s = s[i+1:]
  364. i = strings.Index(s, "|")
  365. j := strings.Index(s, " ")
  366. if i > 0 && (j == -1 || j > i) {
  367. ret += "<tt>"
  368. ret += s[:i]
  369. ret += "</tt>"
  370. s = s[i+1:]
  371. } else {
  372. ret += "|"
  373. }
  374. }
  375. return template.HTML(ret)
  376. }
  377. func markupFirstWord(s template.HTML) template.HTML {
  378. start := 0
  379. again:
  380. end := strings.Index(string(s[start:]), " ")
  381. if end > 0 {
  382. end += start
  383. w := strings.ToLower(string(s[start:end]))
  384. if w == "a" || w == "an" || w == "deprecated:" {
  385. start = end + 1
  386. goto again
  387. }
  388. return s[:start] + "<span class=\"first-word\">" + s[start:end] + "</span>" + s[end:]
  389. }
  390. return s
  391. }
  392. func newlinesToBR(html template.HTML) template.HTML {
  393. s := string(html)
  394. if !strings.Contains(s, "\n") {
  395. return html
  396. }
  397. s = strings.Replace(s, "\n", "<br>", -1)
  398. s = strings.Replace(s, " ", "&nbsp;", -1)
  399. return template.HTML(s)
  400. }
  401. func generate(outPath string, config *Config) (map[string]string, error) {
  402. headerTmpl := template.New("headerTmpl")
  403. headerTmpl.Funcs(template.FuncMap{
  404. "firstSentence": firstSentence,
  405. "markupPipeWords": markupPipeWords,
  406. "markupFirstWord": markupFirstWord,
  407. "newlinesToBR": newlinesToBR,
  408. })
  409. headerTmpl, err := headerTmpl.Parse(`<!DOCTYPE html>
  410. <html>
  411. <head>
  412. <title>BoringSSL - {{.Name}}</title>
  413. <meta charset="utf-8">
  414. <link rel="stylesheet" type="text/css" href="doc.css">
  415. </head>
  416. <body>
  417. <div id="main">
  418. <h2>{{.Name}}</h2>
  419. {{range .Preamble}}<p>{{. | html | markupPipeWords}}</p>{{end}}
  420. <ol>
  421. {{range .Sections}}
  422. {{if not .IsPrivate}}
  423. {{if .Preamble}}<li class="header"><a href="#section-{{.Num}}">{{.Preamble | firstSentence | html | markupPipeWords}}</a></li>{{end}}
  424. {{range .Decls}}
  425. {{if .Name}}<li><a href="#decl-{{.Num}}"><tt>{{.Name}}</tt></a></li>{{end}}
  426. {{end}}
  427. {{end}}
  428. {{end}}
  429. </ol>
  430. {{range .Sections}}
  431. {{if not .IsPrivate}}
  432. <div class="section">
  433. {{if .Preamble}}
  434. <div class="sectionpreamble">
  435. <a name="section-{{.Num}}">
  436. {{range .Preamble}}<p>{{. | html | markupPipeWords}}</p>{{end}}
  437. </a>
  438. </div>
  439. {{end}}
  440. {{range .Decls}}
  441. <div class="decl">
  442. <a name="decl-{{.Num}}">
  443. {{range .Comment}}
  444. <p>{{. | html | markupPipeWords | newlinesToBR | markupFirstWord}}</p>
  445. {{end}}
  446. <pre>{{.Decl}}</pre>
  447. </a>
  448. </div>
  449. {{end}}
  450. </div>
  451. {{end}}
  452. {{end}}
  453. </div>
  454. </body>
  455. </html>`)
  456. if err != nil {
  457. return nil, err
  458. }
  459. headerDescriptions := make(map[string]string)
  460. for _, section := range config.Sections {
  461. for _, headerPath := range section.Headers {
  462. header, err := config.parseHeader(headerPath)
  463. if err != nil {
  464. return nil, errors.New("while parsing " + headerPath + ": " + err.Error())
  465. }
  466. headerDescriptions[header.Name] = firstSentence(header.Preamble)
  467. filename := filepath.Join(outPath, header.Name+".html")
  468. file, err := os.OpenFile(filename, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
  469. if err != nil {
  470. panic(err)
  471. }
  472. defer file.Close()
  473. if err := headerTmpl.Execute(file, header); err != nil {
  474. return nil, err
  475. }
  476. }
  477. }
  478. return headerDescriptions, nil
  479. }
  480. func generateIndex(outPath string, config *Config, headerDescriptions map[string]string) error {
  481. indexTmpl := template.New("indexTmpl")
  482. indexTmpl.Funcs(template.FuncMap{
  483. "baseName": filepath.Base,
  484. "headerDescription": func(header string) string {
  485. return headerDescriptions[header]
  486. },
  487. })
  488. indexTmpl, err := indexTmpl.Parse(`<!DOCTYPE html5>
  489. <head>
  490. <title>BoringSSL - Headers</title>
  491. <meta charset="utf-8">
  492. <link rel="stylesheet" type="text/css" href="doc.css">
  493. </head>
  494. <body>
  495. <div id="main">
  496. <table>
  497. {{range .Sections}}
  498. <tr class="header"><td colspan="2">{{.Name}}</td></tr>
  499. {{range .Headers}}
  500. <tr><td><a href="{{. | baseName}}.html">{{. | baseName}}</a></td><td>{{. | baseName | headerDescription}}</td></tr>
  501. {{end}}
  502. {{end}}
  503. </table>
  504. </div>
  505. </body>
  506. </html>`)
  507. if err != nil {
  508. return err
  509. }
  510. file, err := os.OpenFile(filepath.Join(outPath, "headers.html"), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
  511. if err != nil {
  512. panic(err)
  513. }
  514. defer file.Close()
  515. if err := indexTmpl.Execute(file, config); err != nil {
  516. return err
  517. }
  518. return nil
  519. }
  520. func main() {
  521. var (
  522. configFlag *string = flag.String("config", "doc.config", "Location of config file")
  523. outputDir *string = flag.String("out", ".", "Path to the directory where the output will be written")
  524. config Config
  525. )
  526. flag.Parse()
  527. if len(*configFlag) == 0 {
  528. fmt.Printf("No config file given by --config\n")
  529. os.Exit(1)
  530. }
  531. if len(*outputDir) == 0 {
  532. fmt.Printf("No output directory given by --out\n")
  533. os.Exit(1)
  534. }
  535. configBytes, err := ioutil.ReadFile(*configFlag)
  536. if err != nil {
  537. fmt.Printf("Failed to open config file: %s\n", err)
  538. os.Exit(1)
  539. }
  540. if err := json.Unmarshal(configBytes, &config); err != nil {
  541. fmt.Printf("Failed to parse config file: %s\n", err)
  542. os.Exit(1)
  543. }
  544. headerDescriptions, err := generate(*outputDir, &config)
  545. if err != nil {
  546. fmt.Printf("Failed to generate output: %s\n", err)
  547. os.Exit(1)
  548. }
  549. if err := generateIndex(*outputDir, &config, headerDescriptions); err != nil {
  550. fmt.Printf("Failed to generate index: %s\n", err)
  551. os.Exit(1)
  552. }
  553. }