doc.go 18 KB

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. }