Skip to content

Commit

Permalink
Add release notes markdown template output
Browse files Browse the repository at this point in the history
Signed-off-by: Sascha Grunert <sgrunert@suse.com>
  • Loading branch information
saschagrunert committed Oct 9, 2019
1 parent 015c48d commit 14dcce1
Show file tree
Hide file tree
Showing 2 changed files with 263 additions and 1 deletion.
14 changes: 13 additions & 1 deletion cmd/release-notes/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ func (o *options) BindFlags() *flag.FlagSet {
&o.format,
"format",
env.String("FORMAT", "markdown"),
"The format for notes output (options: markdown, json)",
"The format for notes output (options: markdown, json, template)",
)

flags.StringVar(
Expand Down Expand Up @@ -251,6 +251,18 @@ func (o *options) WriteReleaseNotes(releaseNotes notes.ReleaseNotes, history not
return err
}

case "template":
tpl, err := notes.CreateTemplate(o.logger, releaseNotes)
if err != nil {
level.Error(o.logger).Log("msg", "error creating release note document", "err", err)
return err
}

if err := tpl.RenderTemplate(output); err != nil {
level.Error(o.logger).Log("msg", "error rendering release note document to markdown template", "err", err)
return err
}

default:
errString := fmt.Sprintf("%q is an unsupported format", o.format)
level.Error(o.logger).Log("msg", errString)
Expand Down
250 changes: 250 additions & 0 deletions pkg/notes/template.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
package notes

import (
"fmt"
"io"
"strings"
"text/template"

"github.com/go-kit/kit/log"
"github.com/go-kit/kit/log/level"
)

type Template struct {
UpgradeNotes [14]SIG
Metrics Metrics
Features Features
APIChanges []string
Other [14]SIG
}

type SIG struct {
Header string
Content []string
}

var availableSIGs = map[string]definedSIG{
"api-machinery": {"API Machinery", 0},
"apps": {"Apps", 1},
"auth": {"Auth", 2},
"autoscaling": {"Autoscaling", 3},
"cli": {"CLI", 4},
"cloud-provider": {"Cloud Provider", 5},
"cluster-lifecycle": {"Cluster Lifecycle", 6},
"instrumentation": {"Instrumentation", 7},
"network": {"Network", 8},
"node": {"Node", 9},
"release": {"Release", 10},
"scheduling": {"Scheduling", 11},
"storage": {"Storage", 12},
"windows": {"Windows", 13},
}

type definedSIG struct {
name string
id int
}

type Metrics struct {
Changes []string
Added []string
Removed []string
Deprecated []string
}

type Features struct {
Stable []string
Beta []string
Alpha []string
StagingRepositories []string
CliImprovements []string
Misc []string
}

const notesTemplate = `## What’s New (Major Themes)
<!-- Add themes from Comms Blog here -->
## Known Issues
<!-- Add issues from known issues bucket (known-issues-bucket.Md) here -->
## Urgent Upgrade Notes
### (No, really, you MUST read this before you upgrade)
{{range .UpgradeNotes}}{{if .Header}}
#### {{.Header}}
{{range .Content}}{{.}}
{{end}}{{end}}{{end}}
## Deprecations and Removals
<!--
Add it in the format
- Component
- deprecation
- removal
-->
## Metrics Changes
{{range .Metrics.Changes}}{{.}}
{{end}}
### Added metrics
{{range .Metrics.Added}}{{.}}
{{end}}
### Removed metrics
{{range .Metrics.Removed}}{{.}}
{{end}}
### Deprecated/changed metrics
{{range .Metrics.Deprecated}}{{.}}
{{end}}
## Notable Features
### Stable
{{range .Features.Stable}}{{.}}
{{end}}
### Beta
{{range .Features.Beta}}{{.}}
{{end}}
### Alpha
{{range .Features.Alpha}}{{.}}
{{end}}
### Staging Repositories
{{range .Features.StagingRepositories}}{{.}}
{{end}}
### CLI Improvements
{{range .Features.CliImprovements}}{{.}}
{{end}}
### Misc
{{range .Features.Misc}}{{.}}
{{end}}
## API Changes
{{range .APIChanges}}{{.}}
{{end}}
## Other notable changes
{{range .Other}}{{if .Header}}
### {{ .Header }}
{{range .Content}}{{.}}
{{end}}{{end}}{{end}}
## Dependencies
<!-- Add by hand -->
`

func CreateTemplate(logger log.Logger, notes ReleaseNoteList) (*Template, error) {
res := &Template{}
for _, note := range notes {
for _, sig := range note.SIGs {

definedSIG, ok := availableSIGs[sig]
if !ok {
level.Warn(logger).Log("msg", "unable to find ID for SIG "+sig)
continue
}
i := definedSIG.id

if note.ActionRequired {
res.UpgradeNotes[i].setHeaderIfNeeded(definedSIG.name)
res.UpgradeNotes[i].Content = markdownAppend(res.UpgradeNotes[i].Content, note)

} else if wordIn(note, "metrics") {
res.Metrics.classifyMetricsAppend(note)

} else if wordIn(note, "api") {
res.APIChanges = markdownAppend(res.APIChanges, note)

} else if note.Feature {
res.Features.classifyFeaturesAppend(note)

} else {
res.Other[i].setHeaderIfNeeded(definedSIG.name)
res.Other[i].Content = markdownAppend(res.Other[i].Content, note)
}
}
}

return res, nil
}

func (m Metrics) classifyMetricsAppend(note *ReleaseNote) {
if wordIn(note, "added") {
m.Added = markdownAppend(m.Added, note)

} else if wordIn(note, "removed") {
m.Removed = markdownAppend(m.Removed, note)

} else if wordIn(note, "deprecated") {
m.Deprecated = markdownAppend(m.Deprecated, note)

} else {
m.Changes = markdownAppend(m.Changes, note)
}
}

func (f Features) classifyFeaturesAppend(note *ReleaseNote) {
if wordIn(note, "alpha") {
f.Alpha = markdownAppend(f.Alpha, note)

} else if wordIn(note, "beta") {
f.Beta = markdownAppend(f.Beta, note)

} else if wordIn(note, "stable") {
f.Stable = markdownAppend(f.Stable, note)

} else if wordIn(note, "staging") {
f.StagingRepositories = markdownAppend(f.StagingRepositories, note)

} else if wordIn(note, "cli") {
f.CliImprovements = markdownAppend(f.CliImprovements, note)

} else {
f.Misc = markdownAppend(f.Misc, note)
}
}

func wordIn(note *ReleaseNote, word string) bool {
str, word := strings.ToUpper(note.Markdown), strings.ToUpper(word)
return strings.Contains(str, fmt.Sprintf(" %s ", word))
}

func markdownAppend(input []string, note *ReleaseNote) []string {
s := note.Markdown
if !strings.HasPrefix(s, "- ") {
s = "- " + s
}
// Do not add duplicates
for _, n := range input {
if n == s {
return input
}
}
return append(input, s)
}

func (s *SIG) setHeaderIfNeeded(to string) {
if s.Header == "" {
s.Header = to
}
}

// RenderTemplate writes the template to the provided writer
func (t *Template) RenderTemplate(w io.Writer) error {
const templateName = "notes"
tpl, err := template.New(templateName).Parse(notesTemplate)
if err != nil {
return err
}
return tpl.ExecuteTemplate(w, templateName, t)
}

0 comments on commit 14dcce1

Please sign in to comment.