Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

Fix doc.go against Go tip. Go 1.9 is slated to have some backwards-incompatible changes to html/template. See https://github.com/golang/go/issues/19952. If I'm reading this correctly, the issue is that the context-aware auto escaper had some magic around the 'html' filter, but it would get confused if this was used in the wrong context. This does not apply to us because we never used it in an attribute, etc. Nonetheless, we can be compatible with it and tidy up markupPipeWords' type signature. It should have had type template.HTML -> template.HTML, not string -> template.HTML, because it expects the input to be pre-escaped. (The old 'html' escaper, in turn, probably should have had type string -> template.HTML, but I guess it didn't because all this existed for a text/template migration convenience of some sort?) I considered adding our own escapeHTML with type string -> template.HTML and fixing markupPipeWords to be template.HTML -> template.HTML, but markupPipeWords does not correctly handle all possible template.HTML input. If a | were in an attribute somewhere, it would mangle the text. Instead, I kept it of type string -> template.HTML and defined it to perform the HTML escaping itself. This seems to produce the same output as before in Go 1.8 and tip. Change-Id: I90618a3c5525ae54f9fe731352fcff5856b9ba60 Reviewed-on: https://boringssl-review.googlesource.com/18944 Commit-Queue: Adam Langley <agl@google.com> Reviewed-by: Adam Langley <agl@google.com> CQ-Verified: CQ bot account: commit-bot@chromium.org <commit-bot@chromium.org>
7 лет назад
Fix doc.go against Go tip. Go 1.9 is slated to have some backwards-incompatible changes to html/template. See https://github.com/golang/go/issues/19952. If I'm reading this correctly, the issue is that the context-aware auto escaper had some magic around the 'html' filter, but it would get confused if this was used in the wrong context. This does not apply to us because we never used it in an attribute, etc. Nonetheless, we can be compatible with it and tidy up markupPipeWords' type signature. It should have had type template.HTML -> template.HTML, not string -> template.HTML, because it expects the input to be pre-escaped. (The old 'html' escaper, in turn, probably should have had type string -> template.HTML, but I guess it didn't because all this existed for a text/template migration convenience of some sort?) I considered adding our own escapeHTML with type string -> template.HTML and fixing markupPipeWords to be template.HTML -> template.HTML, but markupPipeWords does not correctly handle all possible template.HTML input. If a | were in an attribute somewhere, it would mangle the text. Instead, I kept it of type string -> template.HTML and defined it to perform the HTML escaping itself. This seems to produce the same output as before in Go 1.8 and tip. Change-Id: I90618a3c5525ae54f9fe731352fcff5856b9ba60 Reviewed-on: https://boringssl-review.googlesource.com/18944 Commit-Queue: Adam Langley <agl@google.com> Reviewed-by: Adam Langley <agl@google.com> CQ-Verified: CQ bot account: commit-bot@chromium.org <commit-bot@chromium.org>
7 лет назад
Fix doc.go against Go tip. Go 1.9 is slated to have some backwards-incompatible changes to html/template. See https://github.com/golang/go/issues/19952. If I'm reading this correctly, the issue is that the context-aware auto escaper had some magic around the 'html' filter, but it would get confused if this was used in the wrong context. This does not apply to us because we never used it in an attribute, etc. Nonetheless, we can be compatible with it and tidy up markupPipeWords' type signature. It should have had type template.HTML -> template.HTML, not string -> template.HTML, because it expects the input to be pre-escaped. (The old 'html' escaper, in turn, probably should have had type string -> template.HTML, but I guess it didn't because all this existed for a text/template migration convenience of some sort?) I considered adding our own escapeHTML with type string -> template.HTML and fixing markupPipeWords to be template.HTML -> template.HTML, but markupPipeWords does not correctly handle all possible template.HTML input. If a | were in an attribute somewhere, it would mangle the text. Instead, I kept it of type string -> template.HTML and defined it to perform the HTML escaping itself. This seems to produce the same output as before in Go 1.8 and tip. Change-Id: I90618a3c5525ae54f9fe731352fcff5856b9ba60 Reviewed-on: https://boringssl-review.googlesource.com/18944 Commit-Queue: Adam Langley <agl@google.com> Reviewed-by: Adam Langley <agl@google.com> CQ-Verified: CQ bot account: commit-bot@chromium.org <commit-bot@chromium.org>
7 лет назад
Fix doc.go against Go tip. Go 1.9 is slated to have some backwards-incompatible changes to html/template. See https://github.com/golang/go/issues/19952. If I'm reading this correctly, the issue is that the context-aware auto escaper had some magic around the 'html' filter, but it would get confused if this was used in the wrong context. This does not apply to us because we never used it in an attribute, etc. Nonetheless, we can be compatible with it and tidy up markupPipeWords' type signature. It should have had type template.HTML -> template.HTML, not string -> template.HTML, because it expects the input to be pre-escaped. (The old 'html' escaper, in turn, probably should have had type string -> template.HTML, but I guess it didn't because all this existed for a text/template migration convenience of some sort?) I considered adding our own escapeHTML with type string -> template.HTML and fixing markupPipeWords to be template.HTML -> template.HTML, but markupPipeWords does not correctly handle all possible template.HTML input. If a | were in an attribute somewhere, it would mangle the text. Instead, I kept it of type string -> template.HTML and defined it to perform the HTML escaping itself. This seems to produce the same output as before in Go 1.8 and tip. Change-Id: I90618a3c5525ae54f9fe731352fcff5856b9ba60 Reviewed-on: https://boringssl-review.googlesource.com/18944 Commit-Queue: Adam Langley <agl@google.com> Reviewed-by: Adam Langley <agl@google.com> CQ-Verified: CQ bot account: commit-bot@chromium.org <commit-bot@chromium.org>
7 лет назад
Fix doc.go against Go tip. Go 1.9 is slated to have some backwards-incompatible changes to html/template. See https://github.com/golang/go/issues/19952. If I'm reading this correctly, the issue is that the context-aware auto escaper had some magic around the 'html' filter, but it would get confused if this was used in the wrong context. This does not apply to us because we never used it in an attribute, etc. Nonetheless, we can be compatible with it and tidy up markupPipeWords' type signature. It should have had type template.HTML -> template.HTML, not string -> template.HTML, because it expects the input to be pre-escaped. (The old 'html' escaper, in turn, probably should have had type string -> template.HTML, but I guess it didn't because all this existed for a text/template migration convenience of some sort?) I considered adding our own escapeHTML with type string -> template.HTML and fixing markupPipeWords to be template.HTML -> template.HTML, but markupPipeWords does not correctly handle all possible template.HTML input. If a | were in an attribute somewhere, it would mangle the text. Instead, I kept it of type string -> template.HTML and defined it to perform the HTML escaping itself. This seems to produce the same output as before in Go 1.8 and tip. Change-Id: I90618a3c5525ae54f9fe731352fcff5856b9ba60 Reviewed-on: https://boringssl-review.googlesource.com/18944 Commit-Queue: Adam Langley <agl@google.com> Reviewed-by: Adam Langley <agl@google.com> CQ-Verified: CQ bot account: commit-bot@chromium.org <commit-bot@chromium.org>
7 лет назад
Fix doc.go against Go tip. Go 1.9 is slated to have some backwards-incompatible changes to html/template. See https://github.com/golang/go/issues/19952. If I'm reading this correctly, the issue is that the context-aware auto escaper had some magic around the 'html' filter, but it would get confused if this was used in the wrong context. This does not apply to us because we never used it in an attribute, etc. Nonetheless, we can be compatible with it and tidy up markupPipeWords' type signature. It should have had type template.HTML -> template.HTML, not string -> template.HTML, because it expects the input to be pre-escaped. (The old 'html' escaper, in turn, probably should have had type string -> template.HTML, but I guess it didn't because all this existed for a text/template migration convenience of some sort?) I considered adding our own escapeHTML with type string -> template.HTML and fixing markupPipeWords to be template.HTML -> template.HTML, but markupPipeWords does not correctly handle all possible template.HTML input. If a | were in an attribute somewhere, it would mangle the text. Instead, I kept it of type string -> template.HTML and defined it to perform the HTML escaping itself. This seems to produce the same output as before in Go 1.8 and tip. Change-Id: I90618a3c5525ae54f9fe731352fcff5856b9ba60 Reviewed-on: https://boringssl-review.googlesource.com/18944 Commit-Queue: Adam Langley <agl@google.com> Reviewed-by: Adam Langley <agl@google.com> CQ-Verified: CQ bot account: commit-bot@chromium.org <commit-bot@chromium.org>
7 лет назад
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774
  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. "regexp"
  19. "strings"
  20. )
  21. // Config describes the structure of the config JSON file.
  22. type Config struct {
  23. // BaseDirectory is a path to which other paths in the file are
  24. // relative.
  25. BaseDirectory string
  26. Sections []ConfigSection
  27. }
  28. type ConfigSection struct {
  29. Name string
  30. // Headers is a list of paths to header files.
  31. Headers []string
  32. }
  33. // HeaderFile is the internal representation of a header file.
  34. type HeaderFile struct {
  35. // Name is the basename of the header file (e.g. "ex_data.html").
  36. Name string
  37. // Preamble contains a comment for the file as a whole. Each string
  38. // is a separate paragraph.
  39. Preamble []string
  40. Sections []HeaderSection
  41. // AllDecls maps all decls to their URL fragments.
  42. AllDecls map[string]string
  43. }
  44. type HeaderSection struct {
  45. // Preamble contains a comment for a group of functions.
  46. Preamble []string
  47. Decls []HeaderDecl
  48. // Anchor, if non-empty, is the URL fragment to use in anchor tags.
  49. Anchor string
  50. // IsPrivate is true if the section contains private functions (as
  51. // indicated by its name).
  52. IsPrivate bool
  53. }
  54. type HeaderDecl struct {
  55. // Comment contains a comment for a specific function. Each string is a
  56. // paragraph. Some paragraph may contain \n runes to indicate that they
  57. // are preformatted.
  58. Comment []string
  59. // Name contains the name of the function, if it could be extracted.
  60. Name string
  61. // Decl contains the preformatted C declaration itself.
  62. Decl string
  63. // Anchor, if non-empty, is the URL fragment to use in anchor tags.
  64. Anchor string
  65. }
  66. const (
  67. cppGuard = "#if defined(__cplusplus)"
  68. commentStart = "/* "
  69. commentEnd = " */"
  70. lineComment = "// "
  71. )
  72. func isComment(line string) bool {
  73. return strings.HasPrefix(line, commentStart) || strings.HasPrefix(line, lineComment)
  74. }
  75. func commentSubject(line string) string {
  76. if strings.HasPrefix(line, "A ") {
  77. line = line[len("A "):]
  78. } else if strings.HasPrefix(line, "An ") {
  79. line = line[len("An "):]
  80. }
  81. idx := strings.IndexAny(line, " ,")
  82. if idx < 0 {
  83. return line
  84. }
  85. return line[:idx]
  86. }
  87. func extractComment(lines []string, lineNo int) (comment []string, rest []string, restLineNo int, err error) {
  88. if len(lines) == 0 {
  89. return nil, lines, lineNo, nil
  90. }
  91. restLineNo = lineNo
  92. rest = lines
  93. var isBlock bool
  94. if strings.HasPrefix(rest[0], commentStart) {
  95. isBlock = true
  96. } else if !strings.HasPrefix(rest[0], lineComment) {
  97. panic("extractComment called on non-comment")
  98. }
  99. commentParagraph := rest[0][len(commentStart):]
  100. rest = rest[1:]
  101. restLineNo++
  102. for len(rest) > 0 {
  103. if isBlock {
  104. i := strings.Index(commentParagraph, commentEnd)
  105. if i >= 0 {
  106. if i != len(commentParagraph)-len(commentEnd) {
  107. err = fmt.Errorf("garbage after comment end on line %d", restLineNo)
  108. return
  109. }
  110. commentParagraph = commentParagraph[:i]
  111. if len(commentParagraph) > 0 {
  112. comment = append(comment, commentParagraph)
  113. }
  114. return
  115. }
  116. }
  117. line := rest[0]
  118. if isBlock {
  119. if !strings.HasPrefix(line, " *") {
  120. err = fmt.Errorf("comment doesn't start with block prefix on line %d: %s", restLineNo, line)
  121. return
  122. }
  123. } else if !strings.HasPrefix(line, "//") {
  124. if len(commentParagraph) > 0 {
  125. comment = append(comment, commentParagraph)
  126. }
  127. return
  128. }
  129. if len(line) == 2 || !isBlock || line[2] != '/' {
  130. line = line[2:]
  131. }
  132. if strings.HasPrefix(line, " ") {
  133. /* Identing the lines of a paragraph marks them as
  134. * preformatted. */
  135. if len(commentParagraph) > 0 {
  136. commentParagraph += "\n"
  137. }
  138. line = line[3:]
  139. }
  140. if len(line) > 0 {
  141. commentParagraph = commentParagraph + line
  142. if len(commentParagraph) > 0 && commentParagraph[0] == ' ' {
  143. commentParagraph = commentParagraph[1:]
  144. }
  145. } else {
  146. comment = append(comment, commentParagraph)
  147. commentParagraph = ""
  148. }
  149. rest = rest[1:]
  150. restLineNo++
  151. }
  152. err = errors.New("hit EOF in comment")
  153. return
  154. }
  155. func extractDecl(lines []string, lineNo int) (decl string, rest []string, restLineNo int, err error) {
  156. if len(lines) == 0 || len(lines[0]) == 0 {
  157. return "", lines, lineNo, nil
  158. }
  159. rest = lines
  160. restLineNo = lineNo
  161. var stack []rune
  162. for len(rest) > 0 {
  163. line := rest[0]
  164. for _, c := range line {
  165. switch c {
  166. case '(', '{', '[':
  167. stack = append(stack, c)
  168. case ')', '}', ']':
  169. if len(stack) == 0 {
  170. err = fmt.Errorf("unexpected %c on line %d", c, restLineNo)
  171. return
  172. }
  173. var expected rune
  174. switch c {
  175. case ')':
  176. expected = '('
  177. case '}':
  178. expected = '{'
  179. case ']':
  180. expected = '['
  181. default:
  182. panic("internal error")
  183. }
  184. if last := stack[len(stack)-1]; last != expected {
  185. err = fmt.Errorf("found %c when expecting %c on line %d", c, last, restLineNo)
  186. return
  187. }
  188. stack = stack[:len(stack)-1]
  189. }
  190. }
  191. if len(decl) > 0 {
  192. decl += "\n"
  193. }
  194. decl += line
  195. rest = rest[1:]
  196. restLineNo++
  197. if len(stack) == 0 && (len(decl) == 0 || decl[len(decl)-1] != '\\') {
  198. break
  199. }
  200. }
  201. return
  202. }
  203. func skipLine(s string) string {
  204. i := strings.Index(s, "\n")
  205. if i > 0 {
  206. return s[i:]
  207. }
  208. return ""
  209. }
  210. var stackOfRegexp = regexp.MustCompile(`STACK_OF\(([^)]*)\)`)
  211. var lhashOfRegexp = regexp.MustCompile(`LHASH_OF\(([^)]*)\)`)
  212. func getNameFromDecl(decl string) (string, bool) {
  213. for strings.HasPrefix(decl, "#if") || strings.HasPrefix(decl, "#elif") {
  214. decl = skipLine(decl)
  215. }
  216. if strings.HasPrefix(decl, "typedef ") {
  217. return "", false
  218. }
  219. for _, prefix := range []string{"struct ", "enum ", "#define "} {
  220. if !strings.HasPrefix(decl, prefix) {
  221. continue
  222. }
  223. decl = strings.TrimPrefix(decl, prefix)
  224. for len(decl) > 0 && decl[0] == ' ' {
  225. decl = decl[1:]
  226. }
  227. // struct and enum types can be the return type of a
  228. // function.
  229. if prefix[0] != '#' && strings.Index(decl, "{") == -1 {
  230. break
  231. }
  232. i := strings.IndexAny(decl, "( ")
  233. if i < 0 {
  234. return "", false
  235. }
  236. return decl[:i], true
  237. }
  238. decl = strings.TrimPrefix(decl, "OPENSSL_EXPORT ")
  239. decl = strings.TrimPrefix(decl, "const ")
  240. decl = stackOfRegexp.ReplaceAllString(decl, "STACK_OF_$1")
  241. decl = lhashOfRegexp.ReplaceAllString(decl, "LHASH_OF_$1")
  242. i := strings.Index(decl, "(")
  243. if i < 0 {
  244. return "", false
  245. }
  246. j := strings.LastIndex(decl[:i], " ")
  247. if j < 0 {
  248. return "", false
  249. }
  250. for j+1 < len(decl) && decl[j+1] == '*' {
  251. j++
  252. }
  253. return decl[j+1 : i], true
  254. }
  255. func sanitizeAnchor(name string) string {
  256. return strings.Replace(name, " ", "-", -1)
  257. }
  258. func isPrivateSection(name string) bool {
  259. return strings.HasPrefix(name, "Private functions") || strings.HasPrefix(name, "Private structures") || strings.Contains(name, "(hidden)")
  260. }
  261. func (config *Config) parseHeader(path string) (*HeaderFile, error) {
  262. headerPath := filepath.Join(config.BaseDirectory, path)
  263. headerFile, err := os.Open(headerPath)
  264. if err != nil {
  265. return nil, err
  266. }
  267. defer headerFile.Close()
  268. scanner := bufio.NewScanner(headerFile)
  269. var lines, oldLines []string
  270. for scanner.Scan() {
  271. lines = append(lines, scanner.Text())
  272. }
  273. if err := scanner.Err(); err != nil {
  274. return nil, err
  275. }
  276. lineNo := 1
  277. found := false
  278. for i, line := range lines {
  279. if line == cppGuard {
  280. lines = lines[i+1:]
  281. lineNo += i + 1
  282. found = true
  283. break
  284. }
  285. }
  286. if !found {
  287. return nil, errors.New("no C++ guard found")
  288. }
  289. if len(lines) == 0 || lines[0] != "extern \"C\" {" {
  290. return nil, errors.New("no extern \"C\" found after C++ guard")
  291. }
  292. lineNo += 2
  293. lines = lines[2:]
  294. header := &HeaderFile{
  295. Name: filepath.Base(path),
  296. AllDecls: make(map[string]string),
  297. }
  298. for i, line := range lines {
  299. if len(line) > 0 {
  300. lines = lines[i:]
  301. lineNo += i
  302. break
  303. }
  304. }
  305. oldLines = lines
  306. if len(lines) > 0 && isComment(lines[0]) {
  307. comment, rest, restLineNo, err := extractComment(lines, lineNo)
  308. if err != nil {
  309. return nil, err
  310. }
  311. if len(rest) > 0 && len(rest[0]) == 0 {
  312. if len(rest) < 2 || len(rest[1]) != 0 {
  313. return nil, errors.New("preamble comment should be followed by two blank lines")
  314. }
  315. header.Preamble = comment
  316. lineNo = restLineNo + 2
  317. lines = rest[2:]
  318. } else {
  319. lines = oldLines
  320. }
  321. }
  322. allAnchors := make(map[string]struct{})
  323. for {
  324. // Start of a section.
  325. if len(lines) == 0 {
  326. return nil, errors.New("unexpected end of file")
  327. }
  328. line := lines[0]
  329. if line == cppGuard {
  330. break
  331. }
  332. if len(line) == 0 {
  333. return nil, fmt.Errorf("blank line at start of section on line %d", lineNo)
  334. }
  335. var section HeaderSection
  336. if isComment(line) {
  337. comment, rest, restLineNo, err := extractComment(lines, lineNo)
  338. if err != nil {
  339. return nil, err
  340. }
  341. if len(rest) > 0 && len(rest[0]) == 0 {
  342. anchor := sanitizeAnchor(firstSentence(comment))
  343. if len(anchor) > 0 {
  344. if _, ok := allAnchors[anchor]; ok {
  345. return nil, fmt.Errorf("duplicate anchor: %s", anchor)
  346. }
  347. allAnchors[anchor] = struct{}{}
  348. }
  349. section.Preamble = comment
  350. section.IsPrivate = len(comment) > 0 && isPrivateSection(comment[0])
  351. section.Anchor = anchor
  352. lines = rest[1:]
  353. lineNo = restLineNo + 1
  354. }
  355. }
  356. for len(lines) > 0 {
  357. line := lines[0]
  358. if len(line) == 0 {
  359. lines = lines[1:]
  360. lineNo++
  361. break
  362. }
  363. if line == cppGuard {
  364. return nil, errors.New("hit ending C++ guard while in section")
  365. }
  366. var comment []string
  367. var decl string
  368. if isComment(line) {
  369. comment, lines, lineNo, err = extractComment(lines, lineNo)
  370. if err != nil {
  371. return nil, err
  372. }
  373. }
  374. if len(lines) == 0 {
  375. return nil, errors.New("expected decl at EOF")
  376. }
  377. declLineNo := lineNo
  378. decl, lines, lineNo, err = extractDecl(lines, lineNo)
  379. if err != nil {
  380. return nil, err
  381. }
  382. name, ok := getNameFromDecl(decl)
  383. if !ok {
  384. name = ""
  385. }
  386. if last := len(section.Decls) - 1; len(name) == 0 && len(comment) == 0 && last >= 0 {
  387. section.Decls[last].Decl += "\n" + decl
  388. } else {
  389. // As a matter of style, comments should start
  390. // with the name of the thing that they are
  391. // commenting on. We make an exception here for
  392. // collective comments, which are detected by
  393. // starting with “The” or “These”.
  394. if len(comment) > 0 &&
  395. len(name) > 0 &&
  396. !strings.HasPrefix(comment[0], "The ") &&
  397. !strings.HasPrefix(comment[0], "These ") {
  398. subject := commentSubject(comment[0])
  399. ok := subject == name
  400. if l := len(subject); l > 0 && subject[l-1] == '*' {
  401. // Groups of names, notably #defines, are often
  402. // denoted with a wildcard.
  403. ok = strings.HasPrefix(name, subject[:l-1])
  404. }
  405. if !ok {
  406. return nil, fmt.Errorf("comment for %q doesn't seem to match line %s:%d\n", name, path, declLineNo)
  407. }
  408. }
  409. anchor := sanitizeAnchor(name)
  410. // TODO(davidben): Enforce uniqueness. This is
  411. // skipped because #ifdefs currently result in
  412. // duplicate table-of-contents entries.
  413. allAnchors[anchor] = struct{}{}
  414. header.AllDecls[name] = anchor
  415. section.Decls = append(section.Decls, HeaderDecl{
  416. Comment: comment,
  417. Name: name,
  418. Decl: decl,
  419. Anchor: anchor,
  420. })
  421. }
  422. if len(lines) > 0 && len(lines[0]) == 0 {
  423. lines = lines[1:]
  424. lineNo++
  425. }
  426. }
  427. header.Sections = append(header.Sections, section)
  428. }
  429. return header, nil
  430. }
  431. func firstSentence(paragraphs []string) string {
  432. if len(paragraphs) == 0 {
  433. return ""
  434. }
  435. s := paragraphs[0]
  436. i := strings.Index(s, ". ")
  437. if i >= 0 {
  438. return s[:i]
  439. }
  440. if lastIndex := len(s) - 1; s[lastIndex] == '.' {
  441. return s[:lastIndex]
  442. }
  443. return s
  444. }
  445. // markupPipeWords converts |s| into an HTML string, safe to be included outside
  446. // a tag, while also marking up words surrounded by |.
  447. func markupPipeWords(allDecls map[string]string, s string) template.HTML {
  448. // It is safe to look for '|' in the HTML-escaped version of |s|
  449. // below. The escaped version cannot include '|' instead tags because
  450. // there are no tags by construction.
  451. s = template.HTMLEscapeString(s)
  452. ret := ""
  453. for {
  454. i := strings.Index(s, "|")
  455. if i == -1 {
  456. ret += s
  457. break
  458. }
  459. ret += s[:i]
  460. s = s[i+1:]
  461. i = strings.Index(s, "|")
  462. j := strings.Index(s, " ")
  463. if i > 0 && (j == -1 || j > i) {
  464. ret += "<tt>"
  465. anchor, isLink := allDecls[s[:i]]
  466. if isLink {
  467. ret += fmt.Sprintf("<a href=\"%s\">", template.HTMLEscapeString(anchor))
  468. }
  469. ret += s[:i]
  470. if isLink {
  471. ret += "</a>"
  472. }
  473. ret += "</tt>"
  474. s = s[i+1:]
  475. } else {
  476. ret += "|"
  477. }
  478. }
  479. return template.HTML(ret)
  480. }
  481. func markupFirstWord(s template.HTML) template.HTML {
  482. start := 0
  483. again:
  484. end := strings.Index(string(s[start:]), " ")
  485. if end > 0 {
  486. end += start
  487. w := strings.ToLower(string(s[start:end]))
  488. // The first word was already marked up as an HTML tag. Don't
  489. // mark it up further.
  490. if strings.ContainsRune(w, '<') {
  491. return s
  492. }
  493. if w == "a" || w == "an" {
  494. start = end + 1
  495. goto again
  496. }
  497. return s[:start] + "<span class=\"first-word\">" + s[start:end] + "</span>" + s[end:]
  498. }
  499. return s
  500. }
  501. func newlinesToBR(html template.HTML) template.HTML {
  502. s := string(html)
  503. if !strings.Contains(s, "\n") {
  504. return html
  505. }
  506. s = strings.Replace(s, "\n", "<br>", -1)
  507. s = strings.Replace(s, " ", "&nbsp;", -1)
  508. return template.HTML(s)
  509. }
  510. func generate(outPath string, config *Config) (map[string]string, error) {
  511. allDecls := make(map[string]string)
  512. headerTmpl := template.New("headerTmpl")
  513. headerTmpl.Funcs(template.FuncMap{
  514. "firstSentence": firstSentence,
  515. "markupPipeWords": func(s string) template.HTML { return markupPipeWords(allDecls, s) },
  516. "markupFirstWord": markupFirstWord,
  517. "newlinesToBR": newlinesToBR,
  518. })
  519. headerTmpl, err := headerTmpl.Parse(`<!DOCTYPE html>
  520. <html>
  521. <head>
  522. <title>BoringSSL - {{.Name}}</title>
  523. <meta charset="utf-8">
  524. <link rel="stylesheet" type="text/css" href="doc.css">
  525. </head>
  526. <body>
  527. <div id="main">
  528. <div class="title">
  529. <h2>{{.Name}}</h2>
  530. <a href="headers.html">All headers</a>
  531. </div>
  532. {{range .Preamble}}<p>{{. | markupPipeWords}}</p>{{end}}
  533. <ol>
  534. {{range .Sections}}
  535. {{if not .IsPrivate}}
  536. {{if .Anchor}}<li class="header"><a href="#{{.Anchor}}">{{.Preamble | firstSentence | markupPipeWords}}</a></li>{{end}}
  537. {{range .Decls}}
  538. {{if .Anchor}}<li><a href="#{{.Anchor}}"><tt>{{.Name}}</tt></a></li>{{end}}
  539. {{end}}
  540. {{end}}
  541. {{end}}
  542. </ol>
  543. {{range .Sections}}
  544. {{if not .IsPrivate}}
  545. <div class="section" {{if .Anchor}}id="{{.Anchor}}"{{end}}>
  546. {{if .Preamble}}
  547. <div class="sectionpreamble">
  548. {{range .Preamble}}<p>{{. | markupPipeWords}}</p>{{end}}
  549. </div>
  550. {{end}}
  551. {{range .Decls}}
  552. <div class="decl" {{if .Anchor}}id="{{.Anchor}}"{{end}}>
  553. {{range .Comment}}
  554. <p>{{. | markupPipeWords | newlinesToBR | markupFirstWord}}</p>
  555. {{end}}
  556. <pre>{{.Decl}}</pre>
  557. </div>
  558. {{end}}
  559. </div>
  560. {{end}}
  561. {{end}}
  562. </div>
  563. </body>
  564. </html>`)
  565. if err != nil {
  566. return nil, err
  567. }
  568. headerDescriptions := make(map[string]string)
  569. var headers []*HeaderFile
  570. for _, section := range config.Sections {
  571. for _, headerPath := range section.Headers {
  572. header, err := config.parseHeader(headerPath)
  573. if err != nil {
  574. return nil, errors.New("while parsing " + headerPath + ": " + err.Error())
  575. }
  576. headerDescriptions[header.Name] = firstSentence(header.Preamble)
  577. headers = append(headers, header)
  578. for name, anchor := range header.AllDecls {
  579. allDecls[name] = fmt.Sprintf("%s#%s", header.Name+".html", anchor)
  580. }
  581. }
  582. }
  583. for _, header := range headers {
  584. filename := filepath.Join(outPath, header.Name+".html")
  585. file, err := os.OpenFile(filename, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
  586. if err != nil {
  587. panic(err)
  588. }
  589. defer file.Close()
  590. if err := headerTmpl.Execute(file, header); err != nil {
  591. return nil, err
  592. }
  593. }
  594. return headerDescriptions, nil
  595. }
  596. func generateIndex(outPath string, config *Config, headerDescriptions map[string]string) error {
  597. indexTmpl := template.New("indexTmpl")
  598. indexTmpl.Funcs(template.FuncMap{
  599. "baseName": filepath.Base,
  600. "headerDescription": func(header string) string {
  601. return headerDescriptions[header]
  602. },
  603. })
  604. indexTmpl, err := indexTmpl.Parse(`<!DOCTYPE html5>
  605. <head>
  606. <title>BoringSSL - Headers</title>
  607. <meta charset="utf-8">
  608. <link rel="stylesheet" type="text/css" href="doc.css">
  609. </head>
  610. <body>
  611. <div id="main">
  612. <div class="title">
  613. <h2>BoringSSL Headers</h2>
  614. </div>
  615. <table>
  616. {{range .Sections}}
  617. <tr class="header"><td colspan="2">{{.Name}}</td></tr>
  618. {{range .Headers}}
  619. <tr><td><a href="{{. | baseName}}.html">{{. | baseName}}</a></td><td>{{. | baseName | headerDescription}}</td></tr>
  620. {{end}}
  621. {{end}}
  622. </table>
  623. </div>
  624. </body>
  625. </html>`)
  626. if err != nil {
  627. return err
  628. }
  629. file, err := os.OpenFile(filepath.Join(outPath, "headers.html"), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
  630. if err != nil {
  631. panic(err)
  632. }
  633. defer file.Close()
  634. if err := indexTmpl.Execute(file, config); err != nil {
  635. return err
  636. }
  637. return nil
  638. }
  639. func copyFile(outPath string, inFilePath string) error {
  640. bytes, err := ioutil.ReadFile(inFilePath)
  641. if err != nil {
  642. return err
  643. }
  644. return ioutil.WriteFile(filepath.Join(outPath, filepath.Base(inFilePath)), bytes, 0666)
  645. }
  646. func main() {
  647. var (
  648. configFlag *string = flag.String("config", "doc.config", "Location of config file")
  649. outputDir *string = flag.String("out", ".", "Path to the directory where the output will be written")
  650. config Config
  651. )
  652. flag.Parse()
  653. if len(*configFlag) == 0 {
  654. fmt.Printf("No config file given by --config\n")
  655. os.Exit(1)
  656. }
  657. if len(*outputDir) == 0 {
  658. fmt.Printf("No output directory given by --out\n")
  659. os.Exit(1)
  660. }
  661. configBytes, err := ioutil.ReadFile(*configFlag)
  662. if err != nil {
  663. fmt.Printf("Failed to open config file: %s\n", err)
  664. os.Exit(1)
  665. }
  666. if err := json.Unmarshal(configBytes, &config); err != nil {
  667. fmt.Printf("Failed to parse config file: %s\n", err)
  668. os.Exit(1)
  669. }
  670. headerDescriptions, err := generate(*outputDir, &config)
  671. if err != nil {
  672. fmt.Printf("Failed to generate output: %s\n", err)
  673. os.Exit(1)
  674. }
  675. if err := generateIndex(*outputDir, &config, headerDescriptions); err != nil {
  676. fmt.Printf("Failed to generate index: %s\n", err)
  677. os.Exit(1)
  678. }
  679. if err := copyFile(*outputDir, "doc.css"); err != nil {
  680. fmt.Printf("Failed to copy static file: %s\n", err)
  681. os.Exit(1)
  682. }
  683. }