Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module github.com/ahmetb/gen-crd-api-reference-docs
module github.com/djencks/gen-crd-api-reference-docs

go 1.15

Expand Down
65 changes: 48 additions & 17 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ type generatorConfig struct {
// MarkdownDisabled controls markdown rendering for comment lines.
MarkdownDisabled bool `json:"markdownDisabled"`

// AsciiDoc controls HTML/text output and some comment escaping.
AsciiDoc bool `json:"asciiDoc"`

// GitCommitDisabled causes the git commit information to be excluded from the output.
GitCommitDisabled bool `json:"gitCommitDisabled"`
}
Expand Down Expand Up @@ -150,9 +153,11 @@ func main() {
return "", errors.Wrap(err, "failed to render the result")
}

// remove trailing whitespace from each html line for markdown renderers
s := regexp.MustCompile(`(?m)^\s+`).ReplaceAllString(b.String(), "")
return s, nil
if !config.AsciiDoc {
// remove trailing whitespace from each html line for markdown renderers
return regexp.MustCompile(`(?m)^\s+`).ReplaceAllString(b.String(), ""), nil
}
return b.String(), nil
}

if *flOutFile != "" {
Expand Down Expand Up @@ -346,7 +351,7 @@ func isLocalType(t *types.Type, typePkgMap map[*types.Type]*apiPackage) bool {
return ok
}

func renderComments(s []string, markdown bool) string {
func renderComments(s []string, markdown bool, asciiDoc bool) string {
s = filterCommentTags(s)
doc := strings.Join(s, "\n")

Expand All @@ -355,7 +360,17 @@ func renderComments(s []string, markdown bool) string {
// we treat this as a HTML tag with markdown renderer below. solve this.
return string(blackfriday.Run([]byte(doc)))
}
return nl2br(doc)

if asciiDoc{
doc = strings.Replace(doc, "#", "\\#", strings.Count(doc, "#") - 1) // avoid highlighting
non_attribute_re := regexp.MustCompile("{([^} ]+})")
doc = non_attribute_re.ReplaceAllString(doc, "\\{$1") // avoid interpretation as attribute
doc = strings.ReplaceAll(doc, "|", "{vbar}") // avoid alternation interpreted as column separator
} else {
doc = nl2br(doc)
}

return doc
}

func safe(s string) template.HTML { return template.HTML(s) }
Expand Down Expand Up @@ -393,7 +408,7 @@ func apiGroupForType(t *types.Type, typePkgMap map[*types.Type]*apiPackage) stri

// anchorIDForLocalType returns the #anchor string for the local type
func anchorIDForLocalType(t *types.Type, typePkgMap map[*types.Type]*apiPackage) string {
return fmt.Sprintf("%s.%s", apiGroupForType(t, typePkgMap), t.Name.Name)
return sanitizeId(fmt.Sprintf("%s.%s", apiGroupForType(t, typePkgMap), t.Name.Name))
Copy link
Owner

@ahmetb ahmetb Jan 30, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a breaking change as I understand it, hence not quite desirable

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't sure if you'd consider this a breaking change: I have no problem changing it. I see three options:

  • introduce a sanitizeId function called from templates, similar to the asciiDocAttributeEscape method I introduced. This is certainly the least complex and most flexible, but increases the API exposed to templates.
  • sanitize ids based on the asciiDoc flag. This will probably end up with the most complex code, and will be the least flexible: perhaps users wanting html generation will want to have simplified IDs.
  • introduce a sanitizeId flag.

I lean towards the first solution, exposing a sanitizeId function to templates. What is your preference?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went ahead and pushed a commit to expose a sanitizeId method. I had to add a tryDereference to anchorIDForLocalType: I think exposing this to templates without the dereference was a bug.

Travis was using Go 1.11: I updated to 1.12 to allow use of strings.ReplaceAll. I'm not sure if there are other more modern API usages. Is updating the Go version OK?

}

// linkForType returns an anchor to the type if it can be generated. returns
Expand Down Expand Up @@ -648,26 +663,30 @@ func constantsOfType(t *types.Type, pkg *apiPackage) []*types.Type {
return sortTypes(constants)
}

func sanitizeId(s string) string {
return "_" + regexp.MustCompile("[./ -]+").ReplaceAllString(s, "_")
}

func render(w io.Writer, pkgs []*apiPackage, config generatorConfig) error {
references := findTypeReferences(pkgs)
typePkgMap := extractTypeToPackageMap(pkgs)

t, err := template.New("").Funcs(map[string]interface{}{
funcMap := map[string]interface{}{
"isExportedType": isExportedType,
"fieldName": fieldName,
"fieldEmbedded": fieldEmbedded,
"typeIdentifier": func(t *types.Type) string { return typeIdentifier(t) },
"typeDisplayName": func(t *types.Type) string { return typeDisplayName(t, config, typePkgMap) },
"visibleTypes": func(t []*types.Type) []*types.Type { return visibleTypes(t, config) },
"renderComments": func(s []string) string { return renderComments(s, !config.MarkdownDisabled) },
"renderComments": func(s []string) string { return renderComments(s, !config.MarkdownDisabled, config.AsciiDoc) },
"packageDisplayName": func(p *apiPackage) string { return p.identifier() },
"apiGroup": func(t *types.Type) string { return apiGroupForType(t, typePkgMap) },
"packageAnchorID": func(p *apiPackage) string {
// TODO(ahmetb): currently this is the same as packageDisplayName
// func, and it's fine since it retuns valid DOM id strings like
// 'serving.knative.dev/v1alpha1' which is valid per HTML5, except
// spaces, so just trim those.
return strings.Replace(p.identifier(), " ", "", -1)
return sanitizeId(p.identifier())
},
"linkForType": func(t *types.Type) string {
v, err := linkForType(t, config, typePkgMap)
Expand All @@ -682,22 +701,34 @@ func render(w io.Writer, pkgs []*apiPackage, config generatorConfig) error {
"sortedTypes": sortTypes,
"typeReferences": func(t *types.Type) []*types.Type { return typeReferences(t, config, references) },
"hiddenMember": func(m types.Member) bool { return hiddenMember(m, config) },
"isLocalType": isLocalType,
"isLocalType": func(t *types.Type) bool { return isLocalType(t, typePkgMap) },
"isOptionalMember": isOptionalMember,
"constantsOfType": func(t *types.Type) []*types.Type { return constantsOfType(t, typePkgMap[t]) },
}).ParseGlob(filepath.Join(*flTemplateDir, "*.tpl"))
if err != nil {
return errors.Wrap(err, "parse error")
"asciiDocAttributeEscape": func(s string) string { return strings.ReplaceAll(s, "]", "\\]") },
}

var gitCommit []byte
if !config.GitCommitDisabled {
gitCommit, _ = exec.Command("git", "rev-parse", "--short", "HEAD").Output()
}

return errors.Wrap(t.ExecuteTemplate(w, "packages", map[string]interface{}{
data := map[string]interface{}{
"packages": pkgs,
"config": config,
"gitCommit": strings.TrimSpace(string(gitCommit)),
}), "template execution error")
}

if config.AsciiDoc {
t, err := texttemplate.New("").Funcs(funcMap).ParseGlob(filepath.Join(*flTemplateDir, "*.tpl"))
if err != nil {
return errors.Wrap(err, "parse error")
}

return errors.Wrap(t.ExecuteTemplate(w, "packages", data), "template execution error")
} else {
t, err := template.New("").Funcs(funcMap).ParseGlob(filepath.Join(*flTemplateDir, "*.tpl"))
if err != nil {
return errors.Wrap(err, "parse error")
}

return errors.Wrap(t.ExecuteTemplate(w, "packages", data), "template execution error")
}
}