Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement default renderer #55

Merged
merged 5 commits into from
Jun 15, 2022
Merged
Show file tree
Hide file tree
Changes from all 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
Expand Up @@ -32,7 +32,7 @@ require (
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa // indirect
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f // indirect
golang.org/x/sys v0.0.0-20211210111614-af8b64212486 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/text v0.3.7
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/ini.v1 v1.66.2 // indirect
Expand Down
86 changes: 86 additions & 0 deletions internal/changelog/asciidoc-template.asciidoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// begin {{.Version}} relnotes

[[release-notes-{{.Version}}]]
== {{.Component}} {{.Version}}

// TODO: support multiple components
Review important information about the {{.Component}} {{.Version}} release.

{{ if .Security -}}
[discrete]
[[security-updates-{{.Version}}]]
=== Security updates

{{.Component}}::{{ range $item := .Security }}
* {{ $item.Summary | beautify }} {{ linkPRSource $item.Component $item.LinkedPR }} {{ linkIssueSource $item.Component $item.LinkedIssue }}
{{- end }}
{{- end }}
{{ if .BreakingChange -}}
[discrete]
[[breaking-changes-{{.Version}}]]
=== Breaking changes

Breaking changes can prevent your application from optimal operation and
performance. Before you upgrade, review the breaking changes, then mitigate the
impact to your application.

// TODO: add details and impact
{{.Component}}::{{ range $item := .BreakingChange }}
* {{ $item.Summary | beautify }} {{ linkPRSource $item.Component $item.LinkedPR }} {{ linkIssueSource $item.Component $item.LinkedIssue }}
{{- end }}
{{- end }}
{{ if .KnownIssue -}}
[discrete]
[[known-issues-{{.Version}}]]
=== Known issues

// TODO: add details and impact
{{.Component}}::{{ range $item := .KnownIssue }}
* {{ $item.Summary | beautify }} {{ linkPRSource $item.Component $item.LinkedPR }} {{ linkIssueSource $item.Component $item.LinkedIssue }}
{{- end }}
{{- end }}
{{ if .Deprecation -}}
[discrete]
[[deprecations-{{.Version}}]]
=== Deprecations

The following functionality is deprecated in {{.Version}}, and will be removed in
{{.Version}}. Deprecated functionality does not have an immediate impact on your
application, but we strongly recommend you make the necessary updates after you
upgrade to {{.Version}}.

{{.Component}}::{{ range $item := .Deprecation }}
* {{ $item.Summary | beautify }} {{ linkPRSource $item.Component $item.LinkedPR }} {{ linkIssueSource $item.Component $item.LinkedIssue }}
{{- end }}
{{- end }}
{{ if .Feature -}}
[discrete]
[[new-features-{{.Version}}]]
=== New features

The {{.Version}} release adds the following new and notable features.

{{.Component}}::{{ range $item := .Feature }}
* {{ $item.Summary | beautify }} {{ linkPRSource $item.Component $item.LinkedPR }} {{ linkIssueSource $item.Component $item.LinkedIssue }}
{{- end }}
{{- end }}
{{ if .Enhancement }}
[discrete]
[[enhancements-{{.Version}}]]
=== Enhancements

{{.Component}}::{{ range $item := .Enhancement }}
* {{ $item.Summary | beautify }} {{ linkPRSource $item.Component $item.LinkedPR }} {{ linkIssueSource $item.Component $item.LinkedIssue }}
{{- end }}
{{- end }}
{{ if .BugFix }}
[discrete]
[[bug-fixes-{{.Version}}]]
=== Bug fixes

{{.Component}}::{{ range $item := .BugFix }}
* {{ $item.Summary | beautify }} {{ linkPRSource $item.Component $item.LinkedPR }} {{ linkIssueSource $item.Component $item.LinkedIssue }}
{{- end }}
{{- end }}

// end {{.Version}} relnotes
20 changes: 12 additions & 8 deletions internal/changelog/entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,26 @@ type FragmentFileInfo struct {
}

type Entry struct {
Summary string `yaml:"summary"`
Description string `yaml:"description"`
Kind Kind `yaml:"kind"`
LinkedPR int `yaml:"pr"`
LinkedIssue int `yaml:"issue"`
Timestamp int64 `yaml:"timestamp"`
File FragmentFileInfo `yaml:"file"`
// fields from template
Kind Kind `yaml:"kind"`
Summary string `yaml:"summary"`
Description string `yaml:"description"`
Component string `yaml:"component" `
LinkedPR int `yaml:"pr"`
LinkedIssue int `yaml:"issue"`

Timestamp int64 `yaml:"timestamp"`
File FragmentFileInfo `yaml:"file"`
}

// EntriesFromFragment returns one or more entries based on the fragment File.
// A single Fragment can contain multiple Changelog entries.
func EntryFromFragment(f fragment.File) Entry {
e := Entry{
Kind: kind2kind(f),
Summary: f.Fragment.Summary,
Description: f.Fragment.Description,
Kind: kind2kind(f),
Component: f.Fragment.Component,
LinkedPR: f.Fragment.Pr,
LinkedIssue: f.Fragment.Issue,
Timestamp: f.Timestamp,
Expand Down
26 changes: 26 additions & 0 deletions internal/changelog/file.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License 2.0;
// you may not use this file except in compliance with the Elastic License 2.0.

package changelog

import (
"fmt"

"github.com/spf13/afero"
"gopkg.in/yaml.v3"
)

func FromFile(fs afero.Fs, inFile string) (Changelog, error) {
b, err := afero.ReadFile(fs, inFile)
if err != nil {
return Changelog{}, fmt.Errorf("cannot read file: %s", inFile)
}

c := Changelog{}
if err := yaml.Unmarshal(b, &c); err != nil {
return Changelog{}, fmt.Errorf("cannot parse changelog YAML from file: %s", inFile)
}

return c, nil
}
147 changes: 147 additions & 0 deletions internal/changelog/renderer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License 2.0;
// you may not use this file except in compliance with the Elastic License 2.0.

package changelog

import (
"bytes"
"embed"
"fmt"
"html/template"
"log"
"path"
"strings"

"github.com/spf13/afero"
"golang.org/x/text/cases"
"golang.org/x/text/language"
)

type Renderer struct {
changelog Changelog
fs afero.Fs
// dest is the destination location where the changelog is written to
dest string
}

func NewRenderer(fs afero.Fs, c Changelog, dest string) *Renderer {
return &Renderer{
changelog: c,
fs: fs,
dest: dest,
}
}

func (r Renderer) Render() error {
log.Printf("render changelog for version: %s\n", r.changelog.Version)

tpl, err := r.Template()
if err != nil {
log.Fatal(err)
}

type TemplateData struct {
Component string
Version string
Changelog Changelog
Kinds map[Kind]bool

BreakingChange []Entry
Deprecation []Entry
BugFix []Entry
Enhancement []Entry
Feature []Entry
KnownIssue []Entry
Security []Entry
Upgrade []Entry
Other []Entry
}
td := TemplateData{
"{agent}", r.changelog.Version, r.changelog,
collectKinds(r.changelog.Entries),
collectByKind(r.changelog.Entries, BreakingChange),
collectByKind(r.changelog.Entries, Deprecation),
collectByKind(r.changelog.Entries, BugFix),
collectByKind(r.changelog.Entries, Enhancement),
collectByKind(r.changelog.Entries, Feature),
collectByKind(r.changelog.Entries, KnownIssue),
collectByKind(r.changelog.Entries, Security),
collectByKind(r.changelog.Entries, Upgrade),
collectByKind(r.changelog.Entries, Other),
}

tmpl, err := template.New("asciidoc-release-notes").
Funcs(template.FuncMap{
// nolint:staticcheck // ignoring for now, supports for multiple component is not implemented
"linkPRSource": func(component string, id int) string {
component = "agent" // TODO: remove this when implementing support for multiple components
return fmt.Sprintf("{%s-pull}%d[#%d]", component, id, id)
},
// nolint:staticcheck // ignoring for now, supports for multiple component is not implemented
"linkIssueSource": func(component string, id int) string {
component = "agent" // TODO: remove this when implementing support for multiple components
return fmt.Sprintf("{%s-issue}%d[#%d]", component, id, id)
},
// Capitalize sentence and ensure ends with .
"beautify": func(s1 string) string {
s2 := strings.Builder{}
s2.WriteString(cases.Title(language.English).String(s1))
if !strings.HasSuffix(s1, ".") {
s2.WriteString(".")
}
return s2.String()
},
}).
Parse(string(tpl))
if err != nil {
panic(err)
}

var data bytes.Buffer

err = tmpl.Execute(&data, td)
if err != nil {
panic(err)
}

outFile := path.Join(r.dest, fmt.Sprintf("%s.asciidoc", r.changelog.Version))
log.Printf("saving changelog in %s\n", outFile)

return afero.WriteFile(r.fs, outFile, data.Bytes(), changelogFilePerm)
}

//go:embed asciidoc-template.asciidoc
var asciidocTemplate embed.FS

func (r Renderer) Template() ([]byte, error) {
data, err := asciidocTemplate.ReadFile("asciidoc-template.asciidoc")
if err != nil {
return []byte{}, fmt.Errorf("cannot read embedded template: %w", err)
}

return data, nil
}

func collectKinds(items []Entry) map[Kind]bool {
// NOTE: collect kinds in a set-like map to avoid duplicates
kinds := map[Kind]bool{}

for _, e := range items {
kinds[e.Kind] = true
}

return kinds
}

func collectByKind(items []Entry, k Kind) []Entry {
entries := []Entry{}

for _, e := range items {
if e.Kind == k {
entries = append(entries, e)
}
}

return entries
}
Loading