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.

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