2014-06-20 20:00:00 +01:00
|
|
|
// doc generates HTML files from the comments in header files.
|
|
|
|
//
|
|
|
|
// doc expects to be given the path to a JSON file via the --config option.
|
|
|
|
// From that JSON (which is defined by the Config struct) it reads a list of
|
|
|
|
// header file locations and generates HTML files for each in the current
|
|
|
|
// directory.
|
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
|
|
|
"flag"
|
|
|
|
"fmt"
|
|
|
|
"html/template"
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2018-04-25 22:34:24 +01:00
|
|
|
"regexp"
|
2014-06-20 20:00:00 +01:00
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Config describes the structure of the config JSON file.
|
|
|
|
type Config struct {
|
|
|
|
// BaseDirectory is a path to which other paths in the file are
|
|
|
|
// relative.
|
|
|
|
BaseDirectory string
|
|
|
|
Sections []ConfigSection
|
|
|
|
}
|
|
|
|
|
|
|
|
type ConfigSection struct {
|
|
|
|
Name string
|
|
|
|
// Headers is a list of paths to header files.
|
|
|
|
Headers []string
|
|
|
|
}
|
|
|
|
|
|
|
|
// HeaderFile is the internal representation of a header file.
|
|
|
|
type HeaderFile struct {
|
|
|
|
// Name is the basename of the header file (e.g. "ex_data.html").
|
|
|
|
Name string
|
|
|
|
// Preamble contains a comment for the file as a whole. Each string
|
|
|
|
// is a separate paragraph.
|
|
|
|
Preamble []string
|
|
|
|
Sections []HeaderSection
|
2015-10-18 06:08:11 +01:00
|
|
|
// AllDecls maps all decls to their URL fragments.
|
|
|
|
AllDecls map[string]string
|
2014-06-20 20:00:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
type HeaderSection struct {
|
|
|
|
// Preamble contains a comment for a group of functions.
|
|
|
|
Preamble []string
|
|
|
|
Decls []HeaderDecl
|
2015-09-07 18:21:08 +01:00
|
|
|
// Anchor, if non-empty, is the URL fragment to use in anchor tags.
|
|
|
|
Anchor string
|
2014-06-20 20:00:00 +01:00
|
|
|
// IsPrivate is true if the section contains private functions (as
|
|
|
|
// indicated by its name).
|
|
|
|
IsPrivate bool
|
|
|
|
}
|
|
|
|
|
|
|
|
type HeaderDecl struct {
|
|
|
|
// Comment contains a comment for a specific function. Each string is a
|
|
|
|
// paragraph. Some paragraph may contain \n runes to indicate that they
|
|
|
|
// are preformatted.
|
|
|
|
Comment []string
|
|
|
|
// Name contains the name of the function, if it could be extracted.
|
|
|
|
Name string
|
|
|
|
// Decl contains the preformatted C declaration itself.
|
|
|
|
Decl string
|
2015-09-07 18:21:08 +01:00
|
|
|
// Anchor, if non-empty, is the URL fragment to use in anchor tags.
|
|
|
|
Anchor string
|
2014-06-20 20:00:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
const (
|
|
|
|
cppGuard = "#if defined(__cplusplus)"
|
|
|
|
commentStart = "/* "
|
|
|
|
commentEnd = " */"
|
2017-08-03 06:07:05 +01:00
|
|
|
lineComment = "// "
|
2014-06-20 20:00:00 +01:00
|
|
|
)
|
|
|
|
|
2017-08-03 06:07:05 +01:00
|
|
|
func isComment(line string) bool {
|
|
|
|
return strings.HasPrefix(line, commentStart) || strings.HasPrefix(line, lineComment)
|
|
|
|
}
|
|
|
|
|
2018-09-03 22:20:09 +01:00
|
|
|
func commentSubject(line string) string {
|
|
|
|
if strings.HasPrefix(line, "A ") {
|
|
|
|
line = line[len("A "):]
|
|
|
|
} else if strings.HasPrefix(line, "An ") {
|
|
|
|
line = line[len("An "):]
|
|
|
|
}
|
|
|
|
idx := strings.IndexAny(line, " ,")
|
|
|
|
if idx < 0 {
|
|
|
|
return line
|
|
|
|
}
|
|
|
|
return line[:idx]
|
|
|
|
}
|
|
|
|
|
2014-06-20 20:00:00 +01:00
|
|
|
func extractComment(lines []string, lineNo int) (comment []string, rest []string, restLineNo int, err error) {
|
|
|
|
if len(lines) == 0 {
|
|
|
|
return nil, lines, lineNo, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
restLineNo = lineNo
|
|
|
|
rest = lines
|
|
|
|
|
2017-08-03 06:07:05 +01:00
|
|
|
var isBlock bool
|
|
|
|
if strings.HasPrefix(rest[0], commentStart) {
|
|
|
|
isBlock = true
|
|
|
|
} else if !strings.HasPrefix(rest[0], lineComment) {
|
2014-06-20 20:00:00 +01:00
|
|
|
panic("extractComment called on non-comment")
|
|
|
|
}
|
|
|
|
commentParagraph := rest[0][len(commentStart):]
|
|
|
|
rest = rest[1:]
|
|
|
|
restLineNo++
|
|
|
|
|
|
|
|
for len(rest) > 0 {
|
2017-08-03 06:07:05 +01:00
|
|
|
if isBlock {
|
|
|
|
i := strings.Index(commentParagraph, commentEnd)
|
|
|
|
if i >= 0 {
|
|
|
|
if i != len(commentParagraph)-len(commentEnd) {
|
|
|
|
err = fmt.Errorf("garbage after comment end on line %d", restLineNo)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
commentParagraph = commentParagraph[:i]
|
|
|
|
if len(commentParagraph) > 0 {
|
|
|
|
comment = append(comment, commentParagraph)
|
|
|
|
}
|
2014-06-20 20:00:00 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
line := rest[0]
|
2017-08-03 06:07:05 +01:00
|
|
|
if isBlock {
|
|
|
|
if !strings.HasPrefix(line, " *") {
|
|
|
|
err = fmt.Errorf("comment doesn't start with block prefix on line %d: %s", restLineNo, line)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
} else if !strings.HasPrefix(line, "//") {
|
|
|
|
if len(commentParagraph) > 0 {
|
|
|
|
comment = append(comment, commentParagraph)
|
|
|
|
}
|
2014-06-20 20:00:00 +01:00
|
|
|
return
|
|
|
|
}
|
2017-08-03 06:07:05 +01:00
|
|
|
if len(line) == 2 || !isBlock || line[2] != '/' {
|
2015-04-09 04:17:55 +01:00
|
|
|
line = line[2:]
|
|
|
|
}
|
2014-06-20 20:00:00 +01:00
|
|
|
if strings.HasPrefix(line, " ") {
|
|
|
|
/* Identing the lines of a paragraph marks them as
|
|
|
|
* preformatted. */
|
|
|
|
if len(commentParagraph) > 0 {
|
|
|
|
commentParagraph += "\n"
|
|
|
|
}
|
|
|
|
line = line[3:]
|
|
|
|
}
|
|
|
|
if len(line) > 0 {
|
|
|
|
commentParagraph = commentParagraph + line
|
|
|
|
if len(commentParagraph) > 0 && commentParagraph[0] == ' ' {
|
|
|
|
commentParagraph = commentParagraph[1:]
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
comment = append(comment, commentParagraph)
|
|
|
|
commentParagraph = ""
|
|
|
|
}
|
|
|
|
rest = rest[1:]
|
|
|
|
restLineNo++
|
|
|
|
}
|
|
|
|
|
|
|
|
err = errors.New("hit EOF in comment")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func extractDecl(lines []string, lineNo int) (decl string, rest []string, restLineNo int, err error) {
|
2016-12-17 20:25:55 +00:00
|
|
|
if len(lines) == 0 || len(lines[0]) == 0 {
|
2014-06-20 20:00:00 +01:00
|
|
|
return "", lines, lineNo, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
rest = lines
|
|
|
|
restLineNo = lineNo
|
|
|
|
|
|
|
|
var stack []rune
|
|
|
|
for len(rest) > 0 {
|
|
|
|
line := rest[0]
|
|
|
|
for _, c := range line {
|
|
|
|
switch c {
|
|
|
|
case '(', '{', '[':
|
|
|
|
stack = append(stack, c)
|
|
|
|
case ')', '}', ']':
|
|
|
|
if len(stack) == 0 {
|
|
|
|
err = fmt.Errorf("unexpected %c on line %d", c, restLineNo)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
var expected rune
|
|
|
|
switch c {
|
|
|
|
case ')':
|
|
|
|
expected = '('
|
|
|
|
case '}':
|
|
|
|
expected = '{'
|
|
|
|
case ']':
|
|
|
|
expected = '['
|
|
|
|
default:
|
|
|
|
panic("internal error")
|
|
|
|
}
|
|
|
|
if last := stack[len(stack)-1]; last != expected {
|
|
|
|
err = fmt.Errorf("found %c when expecting %c on line %d", c, last, restLineNo)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
stack = stack[:len(stack)-1]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(decl) > 0 {
|
|
|
|
decl += "\n"
|
|
|
|
}
|
|
|
|
decl += line
|
|
|
|
rest = rest[1:]
|
|
|
|
restLineNo++
|
|
|
|
|
|
|
|
if len(stack) == 0 && (len(decl) == 0 || decl[len(decl)-1] != '\\') {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2015-04-09 05:06:03 +01:00
|
|
|
func skipLine(s string) string {
|
|
|
|
i := strings.Index(s, "\n")
|
|
|
|
if i > 0 {
|
|
|
|
return s[i:]
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
2018-04-25 22:34:24 +01:00
|
|
|
var stackOfRegexp = regexp.MustCompile(`STACK_OF\(([^)]*)\)`)
|
|
|
|
var lhashOfRegexp = regexp.MustCompile(`LHASH_OF\(([^)]*)\)`)
|
|
|
|
|
2014-06-20 20:00:00 +01:00
|
|
|
func getNameFromDecl(decl string) (string, bool) {
|
2015-05-16 17:12:31 +01:00
|
|
|
for strings.HasPrefix(decl, "#if") || strings.HasPrefix(decl, "#elif") {
|
2015-04-09 05:06:03 +01:00
|
|
|
decl = skipLine(decl)
|
|
|
|
}
|
2016-01-25 22:26:05 +00:00
|
|
|
|
|
|
|
if strings.HasPrefix(decl, "typedef ") {
|
2014-06-20 20:00:00 +01:00
|
|
|
return "", false
|
|
|
|
}
|
2016-01-25 22:26:05 +00:00
|
|
|
|
|
|
|
for _, prefix := range []string{"struct ", "enum ", "#define "} {
|
|
|
|
if !strings.HasPrefix(decl, prefix) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
decl = strings.TrimPrefix(decl, prefix)
|
|
|
|
|
2015-05-16 17:00:51 +01:00
|
|
|
for len(decl) > 0 && decl[0] == ' ' {
|
|
|
|
decl = decl[1:]
|
|
|
|
}
|
2016-01-25 22:26:05 +00:00
|
|
|
|
|
|
|
// struct and enum types can be the return type of a
|
|
|
|
// function.
|
|
|
|
if prefix[0] != '#' && strings.Index(decl, "{") == -1 {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
2015-05-16 17:00:51 +01:00
|
|
|
i := strings.IndexAny(decl, "( ")
|
|
|
|
if i < 0 {
|
|
|
|
return "", false
|
|
|
|
}
|
|
|
|
return decl[:i], true
|
|
|
|
}
|
2015-09-13 06:16:50 +01:00
|
|
|
decl = strings.TrimPrefix(decl, "OPENSSL_EXPORT ")
|
2018-04-25 22:34:24 +01:00
|
|
|
decl = strings.TrimPrefix(decl, "const ")
|
|
|
|
decl = stackOfRegexp.ReplaceAllString(decl, "STACK_OF_$1")
|
|
|
|
decl = lhashOfRegexp.ReplaceAllString(decl, "LHASH_OF_$1")
|
2014-06-20 20:00:00 +01:00
|
|
|
i := strings.Index(decl, "(")
|
|
|
|
if i < 0 {
|
|
|
|
return "", false
|
|
|
|
}
|
|
|
|
j := strings.LastIndex(decl[:i], " ")
|
|
|
|
if j < 0 {
|
|
|
|
return "", false
|
|
|
|
}
|
|
|
|
for j+1 < len(decl) && decl[j+1] == '*' {
|
|
|
|
j++
|
|
|
|
}
|
|
|
|
return decl[j+1 : i], true
|
|
|
|
}
|
|
|
|
|
2015-09-07 18:21:08 +01:00
|
|
|
func sanitizeAnchor(name string) string {
|
|
|
|
return strings.Replace(name, " ", "-", -1)
|
|
|
|
}
|
|
|
|
|
2015-10-18 05:10:28 +01:00
|
|
|
func isPrivateSection(name string) bool {
|
|
|
|
return strings.HasPrefix(name, "Private functions") || strings.HasPrefix(name, "Private structures") || strings.Contains(name, "(hidden)")
|
|
|
|
}
|
|
|
|
|
2014-06-20 20:00:00 +01:00
|
|
|
func (config *Config) parseHeader(path string) (*HeaderFile, error) {
|
|
|
|
headerPath := filepath.Join(config.BaseDirectory, path)
|
|
|
|
|
|
|
|
headerFile, err := os.Open(headerPath)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer headerFile.Close()
|
|
|
|
|
|
|
|
scanner := bufio.NewScanner(headerFile)
|
|
|
|
var lines, oldLines []string
|
|
|
|
for scanner.Scan() {
|
|
|
|
lines = append(lines, scanner.Text())
|
|
|
|
}
|
|
|
|
if err := scanner.Err(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2016-05-17 22:36:47 +01:00
|
|
|
lineNo := 1
|
2014-06-20 20:00:00 +01:00
|
|
|
found := false
|
|
|
|
for i, line := range lines {
|
|
|
|
if line == cppGuard {
|
|
|
|
lines = lines[i+1:]
|
2016-05-17 22:36:47 +01:00
|
|
|
lineNo += i + 1
|
2014-06-20 20:00:00 +01:00
|
|
|
found = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !found {
|
|
|
|
return nil, errors.New("no C++ guard found")
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(lines) == 0 || lines[0] != "extern \"C\" {" {
|
|
|
|
return nil, errors.New("no extern \"C\" found after C++ guard")
|
|
|
|
}
|
2016-07-12 16:09:33 +01:00
|
|
|
lineNo += 2
|
|
|
|
lines = lines[2:]
|
2014-06-20 20:00:00 +01:00
|
|
|
|
|
|
|
header := &HeaderFile{
|
2015-10-18 06:08:11 +01:00
|
|
|
Name: filepath.Base(path),
|
|
|
|
AllDecls: make(map[string]string),
|
2014-06-20 20:00:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
for i, line := range lines {
|
|
|
|
if len(line) > 0 {
|
|
|
|
lines = lines[i:]
|
2016-05-17 22:36:47 +01:00
|
|
|
lineNo += i
|
2014-06-20 20:00:00 +01:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
oldLines = lines
|
2017-08-03 06:07:05 +01:00
|
|
|
if len(lines) > 0 && isComment(lines[0]) {
|
2014-06-20 20:00:00 +01:00
|
|
|
comment, rest, restLineNo, err := extractComment(lines, lineNo)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(rest) > 0 && len(rest[0]) == 0 {
|
|
|
|
if len(rest) < 2 || len(rest[1]) != 0 {
|
|
|
|
return nil, errors.New("preamble comment should be followed by two blank lines")
|
|
|
|
}
|
|
|
|
header.Preamble = comment
|
|
|
|
lineNo = restLineNo + 2
|
|
|
|
lines = rest[2:]
|
|
|
|
} else {
|
|
|
|
lines = oldLines
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-07 18:21:08 +01:00
|
|
|
allAnchors := make(map[string]struct{})
|
2014-06-20 20:00:00 +01:00
|
|
|
|
|
|
|
for {
|
|
|
|
// Start of a section.
|
|
|
|
if len(lines) == 0 {
|
|
|
|
return nil, errors.New("unexpected end of file")
|
|
|
|
}
|
|
|
|
line := lines[0]
|
|
|
|
if line == cppGuard {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(line) == 0 {
|
|
|
|
return nil, fmt.Errorf("blank line at start of section on line %d", lineNo)
|
|
|
|
}
|
|
|
|
|
2015-09-07 18:21:08 +01:00
|
|
|
var section HeaderSection
|
2014-06-20 20:00:00 +01:00
|
|
|
|
2017-08-03 06:07:05 +01:00
|
|
|
if isComment(line) {
|
2014-06-20 20:00:00 +01:00
|
|
|
comment, rest, restLineNo, err := extractComment(lines, lineNo)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if len(rest) > 0 && len(rest[0]) == 0 {
|
2015-09-07 18:21:08 +01:00
|
|
|
anchor := sanitizeAnchor(firstSentence(comment))
|
|
|
|
if len(anchor) > 0 {
|
|
|
|
if _, ok := allAnchors[anchor]; ok {
|
|
|
|
return nil, fmt.Errorf("duplicate anchor: %s", anchor)
|
|
|
|
}
|
|
|
|
allAnchors[anchor] = struct{}{}
|
|
|
|
}
|
|
|
|
|
2014-06-20 20:00:00 +01:00
|
|
|
section.Preamble = comment
|
2015-10-18 05:10:28 +01:00
|
|
|
section.IsPrivate = len(comment) > 0 && isPrivateSection(comment[0])
|
2015-09-07 18:21:08 +01:00
|
|
|
section.Anchor = anchor
|
2014-06-20 20:00:00 +01:00
|
|
|
lines = rest[1:]
|
|
|
|
lineNo = restLineNo + 1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for len(lines) > 0 {
|
|
|
|
line := lines[0]
|
|
|
|
if len(line) == 0 {
|
|
|
|
lines = lines[1:]
|
|
|
|
lineNo++
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if line == cppGuard {
|
|
|
|
return nil, errors.New("hit ending C++ guard while in section")
|
|
|
|
}
|
|
|
|
|
|
|
|
var comment []string
|
|
|
|
var decl string
|
2017-08-03 06:07:05 +01:00
|
|
|
if isComment(line) {
|
2014-06-20 20:00:00 +01:00
|
|
|
comment, lines, lineNo, err = extractComment(lines, lineNo)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(lines) == 0 {
|
|
|
|
return nil, errors.New("expected decl at EOF")
|
|
|
|
}
|
2016-05-17 22:36:47 +01:00
|
|
|
declLineNo := lineNo
|
2014-06-20 20:00:00 +01:00
|
|
|
decl, lines, lineNo, err = extractDecl(lines, lineNo)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
name, ok := getNameFromDecl(decl)
|
|
|
|
if !ok {
|
|
|
|
name = ""
|
|
|
|
}
|
|
|
|
if last := len(section.Decls) - 1; len(name) == 0 && len(comment) == 0 && last >= 0 {
|
|
|
|
section.Decls[last].Decl += "\n" + decl
|
|
|
|
} else {
|
2015-11-04 22:05:00 +00:00
|
|
|
// As a matter of style, comments should start
|
|
|
|
// with the name of the thing that they are
|
|
|
|
// commenting on. We make an exception here for
|
2018-09-03 22:20:09 +01:00
|
|
|
// collective comments, which are detected by
|
|
|
|
// starting with “The” or “These”.
|
2015-11-04 22:05:00 +00:00
|
|
|
if len(comment) > 0 &&
|
2018-09-03 22:20:09 +01:00
|
|
|
len(name) > 0 &&
|
2015-11-04 22:05:00 +00:00
|
|
|
!strings.HasPrefix(comment[0], "The ") &&
|
|
|
|
!strings.HasPrefix(comment[0], "These ") {
|
2018-09-03 22:20:09 +01:00
|
|
|
subject := commentSubject(comment[0])
|
|
|
|
ok := subject == name
|
|
|
|
if l := len(subject); l > 0 && subject[l-1] == '*' {
|
|
|
|
// Groups of names, notably #defines, are often
|
|
|
|
// denoted with a wildcard.
|
|
|
|
ok = strings.HasPrefix(name, subject[:l-1])
|
|
|
|
}
|
|
|
|
if !ok {
|
2018-09-14 21:36:12 +01:00
|
|
|
return nil, fmt.Errorf("comment for %q doesn't seem to match line %s:%d\n", name, path, declLineNo)
|
2018-09-03 22:20:09 +01:00
|
|
|
}
|
2015-11-04 22:05:00 +00:00
|
|
|
}
|
2015-09-07 18:21:08 +01:00
|
|
|
anchor := sanitizeAnchor(name)
|
|
|
|
// TODO(davidben): Enforce uniqueness. This is
|
|
|
|
// skipped because #ifdefs currently result in
|
|
|
|
// duplicate table-of-contents entries.
|
|
|
|
allAnchors[anchor] = struct{}{}
|
|
|
|
|
2015-10-18 06:08:11 +01:00
|
|
|
header.AllDecls[name] = anchor
|
|
|
|
|
2014-06-20 20:00:00 +01:00
|
|
|
section.Decls = append(section.Decls, HeaderDecl{
|
|
|
|
Comment: comment,
|
|
|
|
Name: name,
|
|
|
|
Decl: decl,
|
2015-09-07 18:21:08 +01:00
|
|
|
Anchor: anchor,
|
2014-06-20 20:00:00 +01:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(lines) > 0 && len(lines[0]) == 0 {
|
|
|
|
lines = lines[1:]
|
|
|
|
lineNo++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
header.Sections = append(header.Sections, section)
|
|
|
|
}
|
|
|
|
|
|
|
|
return header, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func firstSentence(paragraphs []string) string {
|
|
|
|
if len(paragraphs) == 0 {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
s := paragraphs[0]
|
|
|
|
i := strings.Index(s, ". ")
|
|
|
|
if i >= 0 {
|
|
|
|
return s[:i]
|
|
|
|
}
|
|
|
|
if lastIndex := len(s) - 1; s[lastIndex] == '.' {
|
|
|
|
return s[:lastIndex]
|
|
|
|
}
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
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>
2017-08-05 00:08:44 +01:00
|
|
|
// markupPipeWords converts |s| into an HTML string, safe to be included outside
|
|
|
|
// a tag, while also marking up words surrounded by |.
|
2015-10-18 06:08:11 +01:00
|
|
|
func markupPipeWords(allDecls map[string]string, s string) template.HTML {
|
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>
2017-08-05 00:08:44 +01:00
|
|
|
// It is safe to look for '|' in the HTML-escaped version of |s|
|
|
|
|
// below. The escaped version cannot include '|' instead tags because
|
|
|
|
// there are no tags by construction.
|
|
|
|
s = template.HTMLEscapeString(s)
|
2014-06-20 20:00:00 +01:00
|
|
|
ret := ""
|
|
|
|
|
|
|
|
for {
|
|
|
|
i := strings.Index(s, "|")
|
|
|
|
if i == -1 {
|
|
|
|
ret += s
|
|
|
|
break
|
|
|
|
}
|
|
|
|
ret += s[:i]
|
|
|
|
s = s[i+1:]
|
|
|
|
|
|
|
|
i = strings.Index(s, "|")
|
|
|
|
j := strings.Index(s, " ")
|
|
|
|
if i > 0 && (j == -1 || j > i) {
|
|
|
|
ret += "<tt>"
|
2015-10-18 06:08:11 +01:00
|
|
|
anchor, isLink := allDecls[s[:i]]
|
|
|
|
if isLink {
|
|
|
|
ret += fmt.Sprintf("<a href=\"%s\">", template.HTMLEscapeString(anchor))
|
|
|
|
}
|
2014-06-20 20:00:00 +01:00
|
|
|
ret += s[:i]
|
2015-10-18 06:08:11 +01:00
|
|
|
if isLink {
|
|
|
|
ret += "</a>"
|
|
|
|
}
|
2014-06-20 20:00:00 +01:00
|
|
|
ret += "</tt>"
|
|
|
|
s = s[i+1:]
|
|
|
|
} else {
|
|
|
|
ret += "|"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return template.HTML(ret)
|
|
|
|
}
|
|
|
|
|
|
|
|
func markupFirstWord(s template.HTML) template.HTML {
|
2014-12-26 05:54:52 +00:00
|
|
|
start := 0
|
|
|
|
again:
|
|
|
|
end := strings.Index(string(s[start:]), " ")
|
|
|
|
if end > 0 {
|
|
|
|
end += start
|
|
|
|
w := strings.ToLower(string(s[start:end]))
|
2015-10-18 06:08:11 +01:00
|
|
|
// The first word was already marked up as an HTML tag. Don't
|
|
|
|
// mark it up further.
|
|
|
|
if strings.ContainsRune(w, '<') {
|
|
|
|
return s
|
|
|
|
}
|
2015-09-07 18:17:45 +01:00
|
|
|
if w == "a" || w == "an" {
|
2014-12-26 05:54:52 +00:00
|
|
|
start = end + 1
|
|
|
|
goto again
|
|
|
|
}
|
|
|
|
return s[:start] + "<span class=\"first-word\">" + s[start:end] + "</span>" + s[end:]
|
2014-06-20 20:00:00 +01:00
|
|
|
}
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
|
|
|
func newlinesToBR(html template.HTML) template.HTML {
|
|
|
|
s := string(html)
|
|
|
|
if !strings.Contains(s, "\n") {
|
|
|
|
return html
|
|
|
|
}
|
|
|
|
s = strings.Replace(s, "\n", "<br>", -1)
|
|
|
|
s = strings.Replace(s, " ", " ", -1)
|
|
|
|
return template.HTML(s)
|
|
|
|
}
|
|
|
|
|
|
|
|
func generate(outPath string, config *Config) (map[string]string, error) {
|
2015-10-18 06:08:11 +01:00
|
|
|
allDecls := make(map[string]string)
|
|
|
|
|
2014-06-20 20:00:00 +01:00
|
|
|
headerTmpl := template.New("headerTmpl")
|
|
|
|
headerTmpl.Funcs(template.FuncMap{
|
|
|
|
"firstSentence": firstSentence,
|
2015-10-18 06:08:11 +01:00
|
|
|
"markupPipeWords": func(s string) template.HTML { return markupPipeWords(allDecls, s) },
|
2014-06-20 20:00:00 +01:00
|
|
|
"markupFirstWord": markupFirstWord,
|
|
|
|
"newlinesToBR": newlinesToBR,
|
|
|
|
})
|
2014-12-26 05:54:52 +00:00
|
|
|
headerTmpl, err := headerTmpl.Parse(`<!DOCTYPE html>
|
2014-06-20 20:00:00 +01:00
|
|
|
<html>
|
|
|
|
<head>
|
|
|
|
<title>BoringSSL - {{.Name}}</title>
|
|
|
|
<meta charset="utf-8">
|
|
|
|
<link rel="stylesheet" type="text/css" href="doc.css">
|
|
|
|
</head>
|
|
|
|
|
|
|
|
<body>
|
|
|
|
<div id="main">
|
2016-05-20 16:28:59 +01:00
|
|
|
<div class="title">
|
|
|
|
<h2>{{.Name}}</h2>
|
|
|
|
<a href="headers.html">All headers</a>
|
|
|
|
</div>
|
2014-06-20 20:00:00 +01:00
|
|
|
|
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>
2017-08-05 00:08:44 +01:00
|
|
|
{{range .Preamble}}<p>{{. | markupPipeWords}}</p>{{end}}
|
2014-06-20 20:00:00 +01:00
|
|
|
|
|
|
|
<ol>
|
|
|
|
{{range .Sections}}
|
|
|
|
{{if not .IsPrivate}}
|
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>
2017-08-05 00:08:44 +01:00
|
|
|
{{if .Anchor}}<li class="header"><a href="#{{.Anchor}}">{{.Preamble | firstSentence | markupPipeWords}}</a></li>{{end}}
|
2014-06-20 20:00:00 +01:00
|
|
|
{{range .Decls}}
|
2015-09-07 18:21:08 +01:00
|
|
|
{{if .Anchor}}<li><a href="#{{.Anchor}}"><tt>{{.Name}}</tt></a></li>{{end}}
|
2014-06-20 20:00:00 +01:00
|
|
|
{{end}}
|
|
|
|
{{end}}
|
|
|
|
{{end}}
|
|
|
|
</ol>
|
|
|
|
|
|
|
|
{{range .Sections}}
|
|
|
|
{{if not .IsPrivate}}
|
2015-10-18 06:08:11 +01:00
|
|
|
<div class="section" {{if .Anchor}}id="{{.Anchor}}"{{end}}>
|
2014-06-20 20:00:00 +01:00
|
|
|
{{if .Preamble}}
|
|
|
|
<div class="sectionpreamble">
|
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>
2017-08-05 00:08:44 +01:00
|
|
|
{{range .Preamble}}<p>{{. | markupPipeWords}}</p>{{end}}
|
2014-06-20 20:00:00 +01:00
|
|
|
</div>
|
|
|
|
{{end}}
|
|
|
|
|
|
|
|
{{range .Decls}}
|
2015-10-18 06:08:11 +01:00
|
|
|
<div class="decl" {{if .Anchor}}id="{{.Anchor}}"{{end}}>
|
2014-06-20 20:00:00 +01:00
|
|
|
{{range .Comment}}
|
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>
2017-08-05 00:08:44 +01:00
|
|
|
<p>{{. | markupPipeWords | newlinesToBR | markupFirstWord}}</p>
|
2014-06-20 20:00:00 +01:00
|
|
|
{{end}}
|
|
|
|
<pre>{{.Decl}}</pre>
|
|
|
|
</div>
|
|
|
|
{{end}}
|
|
|
|
</div>
|
|
|
|
{{end}}
|
|
|
|
{{end}}
|
|
|
|
</div>
|
|
|
|
</body>
|
|
|
|
</html>`)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
headerDescriptions := make(map[string]string)
|
2015-10-18 06:08:11 +01:00
|
|
|
var headers []*HeaderFile
|
2014-06-20 20:00:00 +01:00
|
|
|
|
|
|
|
for _, section := range config.Sections {
|
|
|
|
for _, headerPath := range section.Headers {
|
|
|
|
header, err := config.parseHeader(headerPath)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.New("while parsing " + headerPath + ": " + err.Error())
|
|
|
|
}
|
|
|
|
headerDescriptions[header.Name] = firstSentence(header.Preamble)
|
2015-10-18 06:08:11 +01:00
|
|
|
headers = append(headers, header)
|
|
|
|
|
|
|
|
for name, anchor := range header.AllDecls {
|
|
|
|
allDecls[name] = fmt.Sprintf("%s#%s", header.Name+".html", anchor)
|
2014-06-20 20:00:00 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-10-18 06:08:11 +01:00
|
|
|
for _, header := range headers {
|
|
|
|
filename := filepath.Join(outPath, header.Name+".html")
|
|
|
|
file, err := os.OpenFile(filename, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
if err := headerTmpl.Execute(file, header); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-20 20:00:00 +01:00
|
|
|
return headerDescriptions, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func generateIndex(outPath string, config *Config, headerDescriptions map[string]string) error {
|
|
|
|
indexTmpl := template.New("indexTmpl")
|
|
|
|
indexTmpl.Funcs(template.FuncMap{
|
|
|
|
"baseName": filepath.Base,
|
|
|
|
"headerDescription": func(header string) string {
|
|
|
|
return headerDescriptions[header]
|
|
|
|
},
|
|
|
|
})
|
|
|
|
indexTmpl, err := indexTmpl.Parse(`<!DOCTYPE html5>
|
|
|
|
|
|
|
|
<head>
|
|
|
|
<title>BoringSSL - Headers</title>
|
|
|
|
<meta charset="utf-8">
|
|
|
|
<link rel="stylesheet" type="text/css" href="doc.css">
|
|
|
|
</head>
|
|
|
|
|
|
|
|
<body>
|
|
|
|
<div id="main">
|
2016-05-20 16:28:59 +01:00
|
|
|
<div class="title">
|
|
|
|
<h2>BoringSSL Headers</h2>
|
|
|
|
</div>
|
2014-06-20 20:00:00 +01:00
|
|
|
<table>
|
|
|
|
{{range .Sections}}
|
|
|
|
<tr class="header"><td colspan="2">{{.Name}}</td></tr>
|
|
|
|
{{range .Headers}}
|
|
|
|
<tr><td><a href="{{. | baseName}}.html">{{. | baseName}}</a></td><td>{{. | baseName | headerDescription}}</td></tr>
|
|
|
|
{{end}}
|
|
|
|
{{end}}
|
|
|
|
</table>
|
|
|
|
</div>
|
|
|
|
</body>
|
|
|
|
</html>`)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
file, err := os.OpenFile(filepath.Join(outPath, "headers.html"), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
|
|
|
|
if err := indexTmpl.Execute(file, config); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-08-09 22:08:49 +01:00
|
|
|
func copyFile(outPath string, inFilePath string) error {
|
|
|
|
bytes, err := ioutil.ReadFile(inFilePath)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return ioutil.WriteFile(filepath.Join(outPath, filepath.Base(inFilePath)), bytes, 0666)
|
|
|
|
}
|
|
|
|
|
2014-06-20 20:00:00 +01:00
|
|
|
func main() {
|
|
|
|
var (
|
2015-04-09 01:32:55 +01:00
|
|
|
configFlag *string = flag.String("config", "doc.config", "Location of config file")
|
|
|
|
outputDir *string = flag.String("out", ".", "Path to the directory where the output will be written")
|
2014-06-20 20:00:00 +01:00
|
|
|
config Config
|
|
|
|
)
|
|
|
|
|
|
|
|
flag.Parse()
|
|
|
|
|
|
|
|
if len(*configFlag) == 0 {
|
|
|
|
fmt.Printf("No config file given by --config\n")
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(*outputDir) == 0 {
|
|
|
|
fmt.Printf("No output directory given by --out\n")
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
|
|
|
|
configBytes, err := ioutil.ReadFile(*configFlag)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("Failed to open config file: %s\n", err)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := json.Unmarshal(configBytes, &config); err != nil {
|
|
|
|
fmt.Printf("Failed to parse config file: %s\n", err)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
|
|
|
|
headerDescriptions, err := generate(*outputDir, &config)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("Failed to generate output: %s\n", err)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := generateIndex(*outputDir, &config, headerDescriptions); err != nil {
|
|
|
|
fmt.Printf("Failed to generate index: %s\n", err)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
2015-08-09 22:08:49 +01:00
|
|
|
|
|
|
|
if err := copyFile(*outputDir, "doc.css"); err != nil {
|
|
|
|
fmt.Printf("Failed to copy static file: %s\n", err)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
2014-06-20 20:00:00 +01:00
|
|
|
}
|