diff options
Diffstat (limited to 'roms/edk2/CryptoPkg/Library/OpensslLib/openssl/boringssl/util/doc.go')
-rw-r--r-- | roms/edk2/CryptoPkg/Library/OpensslLib/openssl/boringssl/util/doc.go | 728 |
1 files changed, 728 insertions, 0 deletions
diff --git a/roms/edk2/CryptoPkg/Library/OpensslLib/openssl/boringssl/util/doc.go b/roms/edk2/CryptoPkg/Library/OpensslLib/openssl/boringssl/util/doc.go new file mode 100644 index 000000000..987794c92 --- /dev/null +++ b/roms/edk2/CryptoPkg/Library/OpensslLib/openssl/boringssl/util/doc.go @@ -0,0 +1,728 @@ +// 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" + "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 + // AllDecls maps all decls to their URL fragments. + AllDecls map[string]string +} + +type HeaderSection struct { + // Preamble contains a comment for a group of functions. + Preamble []string + Decls []HeaderDecl + // Anchor, if non-empty, is the URL fragment to use in anchor tags. + Anchor string + // 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 + // Anchor, if non-empty, is the URL fragment to use in anchor tags. + Anchor string +} + +const ( + cppGuard = "#if defined(__cplusplus)" + commentStart = "/* " + commentEnd = " */" +) + +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 + + if !strings.HasPrefix(rest[0], commentStart) { + panic("extractComment called on non-comment") + } + commentParagraph := rest[0][len(commentStart):] + rest = rest[1:] + restLineNo++ + + for len(rest) > 0 { + 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) + } + return + } + + line := rest[0] + if !strings.HasPrefix(line, " *") { + err = fmt.Errorf("comment doesn't start with block prefix on line %d: %s", restLineNo, line) + return + } + if len(line) == 2 || line[2] != '/' { + line = line[2:] + } + 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) { + if len(lines) == 0 || len(lines[0]) == 0 { + 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 +} + +func skipLine(s string) string { + i := strings.Index(s, "\n") + if i > 0 { + return s[i:] + } + return "" +} + +func getNameFromDecl(decl string) (string, bool) { + for strings.HasPrefix(decl, "#if") || strings.HasPrefix(decl, "#elif") { + decl = skipLine(decl) + } + + if strings.HasPrefix(decl, "typedef ") { + return "", false + } + + for _, prefix := range []string{"struct ", "enum ", "#define "} { + if !strings.HasPrefix(decl, prefix) { + continue + } + + decl = strings.TrimPrefix(decl, prefix) + + for len(decl) > 0 && decl[0] == ' ' { + decl = decl[1:] + } + + // struct and enum types can be the return type of a + // function. + if prefix[0] != '#' && strings.Index(decl, "{") == -1 { + break + } + + i := strings.IndexAny(decl, "( ") + if i < 0 { + return "", false + } + return decl[:i], true + } + decl = strings.TrimPrefix(decl, "OPENSSL_EXPORT ") + decl = strings.TrimPrefix(decl, "STACK_OF(") + decl = strings.TrimPrefix(decl, "LHASH_OF(") + 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 +} + +func sanitizeAnchor(name string) string { + return strings.Replace(name, " ", "-", -1) +} + +func isPrivateSection(name string) bool { + return strings.HasPrefix(name, "Private functions") || strings.HasPrefix(name, "Private structures") || strings.Contains(name, "(hidden)") +} + +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 + } + + lineNo := 1 + found := false + for i, line := range lines { + if line == cppGuard { + lines = lines[i+1:] + lineNo += i + 1 + 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") + } + lineNo += 2 + lines = lines[2:] + + header := &HeaderFile{ + Name: filepath.Base(path), + AllDecls: make(map[string]string), + } + + for i, line := range lines { + if len(line) > 0 { + lines = lines[i:] + lineNo += i + break + } + } + + oldLines = lines + if len(lines) > 0 && strings.HasPrefix(lines[0], commentStart) { + 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 + } + } + + allAnchors := make(map[string]struct{}) + + 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) + } + + var section HeaderSection + + if strings.HasPrefix(line, commentStart) { + comment, rest, restLineNo, err := extractComment(lines, lineNo) + if err != nil { + return nil, err + } + if len(rest) > 0 && len(rest[0]) == 0 { + anchor := sanitizeAnchor(firstSentence(comment)) + if len(anchor) > 0 { + if _, ok := allAnchors[anchor]; ok { + return nil, fmt.Errorf("duplicate anchor: %s", anchor) + } + allAnchors[anchor] = struct{}{} + } + + section.Preamble = comment + section.IsPrivate = len(comment) > 0 && isPrivateSection(comment[0]) + section.Anchor = anchor + 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 + if strings.HasPrefix(line, commentStart) { + 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") + } + declLineNo := lineNo + 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 { + // 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 + // #defines (because we often have blocks of + // them) and collective comments, which are + // detected by starting with “The” or “These”. + if len(comment) > 0 && + !strings.HasPrefix(comment[0], name) && + !strings.HasPrefix(comment[0], "A "+name) && + !strings.HasPrefix(comment[0], "An "+name) && + !strings.HasPrefix(decl, "#define ") && + !strings.HasPrefix(comment[0], "The ") && + !strings.HasPrefix(comment[0], "These ") { + return nil, fmt.Errorf("Comment for %q doesn't seem to match line %s:%d\n", name, path, declLineNo) + } + anchor := sanitizeAnchor(name) + // TODO(davidben): Enforce uniqueness. This is + // skipped because #ifdefs currently result in + // duplicate table-of-contents entries. + allAnchors[anchor] = struct{}{} + + header.AllDecls[name] = anchor + + section.Decls = append(section.Decls, HeaderDecl{ + Comment: comment, + Name: name, + Decl: decl, + Anchor: anchor, + }) + } + + 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 +} + +func markupPipeWords(allDecls map[string]string, s string) template.HTML { + 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>" + anchor, isLink := allDecls[s[:i]] + if isLink { + ret += fmt.Sprintf("<a href=\"%s\">", template.HTMLEscapeString(anchor)) + } + ret += s[:i] + if isLink { + ret += "</a>" + } + ret += "</tt>" + s = s[i+1:] + } else { + ret += "|" + } + } + + return template.HTML(ret) +} + +func markupFirstWord(s template.HTML) template.HTML { + start := 0 +again: + end := strings.Index(string(s[start:]), " ") + if end > 0 { + end += start + w := strings.ToLower(string(s[start:end])) + // The first word was already marked up as an HTML tag. Don't + // mark it up further. + if strings.ContainsRune(w, '<') { + return s + } + if w == "a" || w == "an" { + start = end + 1 + goto again + } + return s[:start] + "<span class=\"first-word\">" + s[start:end] + "</span>" + s[end:] + } + 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) { + allDecls := make(map[string]string) + + headerTmpl := template.New("headerTmpl") + headerTmpl.Funcs(template.FuncMap{ + "firstSentence": firstSentence, + "markupPipeWords": func(s string) template.HTML { return markupPipeWords(allDecls, s) }, + "markupFirstWord": markupFirstWord, + "newlinesToBR": newlinesToBR, + }) + headerTmpl, err := headerTmpl.Parse(`<!DOCTYPE html> +<html> + <head> + <title>BoringSSL - {{.Name}}</title> + <meta charset="utf-8"> + <link rel="stylesheet" type="text/css" href="doc.css"> + </head> + + <body> + <div id="main"> + <div class="title"> + <h2>{{.Name}}</h2> + <a href="headers.html">All headers</a> + </div> + + {{range .Preamble}}<p>{{. | html | markupPipeWords}}</p>{{end}} + + <ol> + {{range .Sections}} + {{if not .IsPrivate}} + {{if .Anchor}}<li class="header"><a href="#{{.Anchor}}">{{.Preamble | firstSentence | html | markupPipeWords}}</a></li>{{end}} + {{range .Decls}} + {{if .Anchor}}<li><a href="#{{.Anchor}}"><tt>{{.Name}}</tt></a></li>{{end}} + {{end}} + {{end}} + {{end}} + </ol> + + {{range .Sections}} + {{if not .IsPrivate}} + <div class="section" {{if .Anchor}}id="{{.Anchor}}"{{end}}> + {{if .Preamble}} + <div class="sectionpreamble"> + {{range .Preamble}}<p>{{. | html | markupPipeWords}}</p>{{end}} + </div> + {{end}} + + {{range .Decls}} + <div class="decl" {{if .Anchor}}id="{{.Anchor}}"{{end}}> + {{range .Comment}} + <p>{{. | html | markupPipeWords | newlinesToBR | markupFirstWord}}</p> + {{end}} + <pre>{{.Decl}}</pre> + </div> + {{end}} + </div> + {{end}} + {{end}} + </div> + </body> +</html>`) + if err != nil { + return nil, err + } + + headerDescriptions := make(map[string]string) + var headers []*HeaderFile + + 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) + headers = append(headers, header) + + for name, anchor := range header.AllDecls { + allDecls[name] = fmt.Sprintf("%s#%s", header.Name+".html", anchor) + } + } + } + + 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 + } + } + + 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"> + <div class="title"> + <h2>BoringSSL Headers</h2> + </div> + <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 +} + +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) +} + +func main() { + var ( + 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") + 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) + } + + if err := copyFile(*outputDir, "doc.css"); err != nil { + fmt.Printf("Failed to copy static file: %s\n", err) + os.Exit(1) + } +} |