Skip to content

Add table of contents support #980

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

Merged
merged 1 commit into from
Jan 7, 2020
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
16 changes: 16 additions & 0 deletions cmd/release-notes/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,13 @@ func init() {
util.EnvDefault("RELEASE_TARS", ""),
"Directory of tars to sha512 sum for display",
)

cmd.PersistentFlags().BoolVar(
&opts.TableOfContents,
"toc",
util.IsEnvSet("TOC"),
"Enable the rendering of the table of contents",
)
}

func GetReleaseNotes() (notes.ReleaseNotes, notes.ReleaseNotesHistory, error) {
Expand Down Expand Up @@ -302,6 +309,15 @@ func WriteReleaseNotes(releaseNotes notes.ReleaseNotes, history notes.ReleaseNot
if err != nil {
return errors.Wrapf(err, "rendering release note document to markdown")
}

if opts.TableOfContents {
toc, err := notes.GenerateTOC(markdown)
if err != nil {
return errors.Wrap(err, "generating table of contents")
}
markdown = toc + "\n" + markdown
}

if _, err := output.WriteString(markdown); err != nil {
return errors.Wrapf(err, "writing output file")
}
Expand Down
2 changes: 2 additions & 0 deletions pkg/notes/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ go_library(
"document.go",
"notes.go",
"options.go",
"toc.go",
],
importpath = "k8s.io/release/pkg/notes",
visibility = ["//visibility:public"],
Expand All @@ -27,6 +28,7 @@ go_test(
"notes_gatherer_test.go",
"notes_test.go",
"options_test.go",
"toc_test.go",
],
embed = [":go_default_library"],
deps = [
Expand Down
37 changes: 19 additions & 18 deletions pkg/notes/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,24 +23,25 @@ import (
)

type Options struct {
GithubToken string
GithubOrg string
GithubRepo string
Output string
Branch string
StartSHA string
EndSHA string
StartRev string
EndRev string
RepoPath string
ReleaseVersion string
Format string
RequiredAuthor string
Debug bool
DiscoverMode string
ReleaseBucket string
ReleaseTars string
gitCloneFn func(string, string, string, bool) (*git.Repo, error)
GithubToken string
GithubOrg string
GithubRepo string
Output string
Branch string
StartSHA string
EndSHA string
StartRev string
EndRev string
RepoPath string
ReleaseVersion string
Format string
RequiredAuthor string
Debug bool
DiscoverMode string
ReleaseBucket string
ReleaseTars string
TableOfContents bool
gitCloneFn func(string, string, string, bool) (*git.Repo, error)
}

type RevisionDiscoveryMode string
Expand Down
103 changes: 103 additions & 0 deletions pkg/notes/toc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
Copyright 2020 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package notes

import (
"bufio"
"fmt"
"io"
"regexp"
"strings"
)

var headerPattern = regexp.MustCompile("^(?P<indent>#+) ?(?P<title>.+)$")

func GenerateTOC(input string) (string, error) {
result := &strings.Builder{}

lastLen, indent := 0, 0
headers := map[string]int{}
seenBackTicks := 0

scanner := bufio.NewScanner(strings.NewReader(input))
for scanner.Scan() {
// skip code blocks if necessary
seenBackTicks += strings.Count(scanner.Text(), "`")
if seenBackTicks%2 != 0 {
continue
}

if headerPattern.Match(scanner.Bytes()) {
matches := headerPattern.FindStringSubmatch(scanner.Text())

i := len(matches[1])
if i == 1 {
indent = 1
} else if i > lastLen {
indent++
} else if i < lastLen {
indent--
}
lastLen = i

add(result, matches[2], indent-1, headers)
}
}

if err := scanner.Err(); err != nil {
return "", err
}

return result.String(), nil
}

func add(result io.StringWriter, title string, indent int, headers map[string]int) {
link := strings.NewReplacer(
"!", "",
"#", "",
"%", "",
"&", "",
"'", "",
"(", "",
")", "",
"*", "",
",", "",
".", "",
"@", "",
"[", "",
"\"", "",
"]", "",
"^", "",
"`", "",
"{", "",
"|", "",
"}", "",
"~", "",
" ", "-",
).Replace(strings.ToLower(title))

if _, ok := headers[link]; ok {
headers[link]++
link = fmt.Sprintf("%s-%d", link, headers[link]-1)
} else {
headers[link] = 1
}

result.WriteString(fmt.Sprintf( // nolint: errcheck
"%s- [%s](#%s)\n", strings.Repeat(" ", indent*2), title, link),
)
}
152 changes: 152 additions & 0 deletions pkg/notes/toc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
/*
Copyright 2019 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package notes

import (
"testing"

"github.com/stretchr/testify/require"
)

const testInput = `
# v1.16.4

[Documentation](https://docs.k8s.io)

## Downloads for v1.16.4

| filename | sha512 hash |
| -------- | ----------- |


### Client Binaries

| filename | sha512 hash |
| -------- | ----------- |


### Server Binaries

| filename | sha512 hash |
| -------- | ----------- |


### Node Binaries

| filename | sha512 hash |
| -------- | ----------- |


## Changelog since v1.16.3

### API Changes

- For x-kubernetes-list-type=set a scalar or atomic item type is now required, as documented. Persisted, invalid data is tolerated. ([#85385](https://github.com/kubernetes/kubernetes/pull/85385), [@sttts](https://github.com/sttts))
` + "```" + `
# A code block
` + "```" + `

### Notes from Multiple SIGs

#### SIG API Machinery, SIG Cloud Provider, and SIG Scalability

- Fixes a performance issue when using server-side apply with objects with very large atomic maps. ([#85462](https://github.com/kubernetes/kubernetes/pull/85462), [@jennybuckley](https://github.com/jennybuckley))

#### SIG Apps, and ` + "`SIG`" + ` Network

- kube-controller-manager: Fixes bug setting headless service labels on endpoints ([#85361](https://github.com/kubernetes/kubernetes/pull/85361), [@liggitt](https://github.com/liggitt))

### Notes from Individual SIGs

#### SIG API Machinery

- Filter published OpenAPI schema by making nullable, required fields non-required in order to avoid kubectl to wrongly reject null values. ([#85733](https://github.com/kubernetes/kubernetes/pull/85733), [@sttts](https://github.com/sttts))
- For x-kubernetes-list-type=set a scalar or atomic item type is now required, as documented. Persisted, invalid data is tolerated. ([#85385](https://github.com/kubernetes/kubernetes/pull/85385), [@sttts](https://github.com/sttts))

#### SIG Cloud Provider

- azure: update disk lock logic per vm during attach/detach to allow concurrent updates for different nodes. ([#85115](https://github.com/kubernetes/kubernetes/pull/85115), [@aramase](https://github.com/aramase))
- fix vmss dirty cache issue in disk attach/detach on vmss node ([#85158](https://github.com/kubernetes/kubernetes/pull/85158), [@andyzhangx](https://github.com/andyzhangx))
- fix race condition when attach/delete azure disk in same time ([#84917](https://github.com/kubernetes/kubernetes/pull/84917), [@andyzhangx](https://github.com/andyzhangx))
- Ensure health probes are created for local traffic policy UDP services on Azure ([#85189](https://github.com/kubernetes/kubernetes/pull/85189), [@nilo19](https://github.com/nilo19))
- Change GCP ILB firewall names to contain the "k8s-fw-" prefix like the rest of the firewall rules. This is needed for consistency and also for other components to identify the firewall rule as k8s/service-controller managed. ([#85102](https://github.com/kubernetes/kubernetes/pull/85102), [@prameshj](https://github.com/prameshj))

#### SIG Cluster Lifecycle

- Fixed issue with addon-resizer using deprecated extensions APIs ([#85865](https://github.com/kubernetes/kubernetes/pull/85865), [@liggitt](https://github.com/liggitt))
- kubeadm: prevent infinite hang on "kubeadm join" using token discovery ([#85292](https://github.com/kubernetes/kubernetes/pull/85292), [@neolit123](https://github.com/neolit123))
- In cases where the CoreDNS migration isn't supported and the user chooses to ignore the warnings from the preflight check, the migration will be skipped and the ConfigMap and Deployment of CoreDNS will be retained. ([#85096](https://github.com/kubernetes/kubernetes/pull/85096), [@rajansandeep](https://github.com/rajansandeep))
- kubeadm: fix skipped etcd upgrade on secondary control-plane nodes when the command "kubeadm upgrade node" is used. ([#85024](https://github.com/kubernetes/kubernetes/pull/85024), [@neolit123](https://github.com/neolit123))

#### SIG Network

- Change kube-proxy's default node IP back to 127.0.0.1, if this is incorrect, please use --bind-address to set the correct address ([#84391](https://github.com/kubernetes/kubernetes/pull/84391), [@zouyee](https://github.com/zouyee))

# v1.16.3
## Downloads for v1.16.3
### Client Binaries
### Server Binaries
### Node Binaries
## Changelog since v1.16.2

# v1.16.2
## Downloads for v1.16.2
### Client Binaries
### Server Binaries
### Node Binaries
## Changelog since v1.16.1`

const expectedOutput = `- [v1.16.4](#v1164)
- [Downloads for v1.16.4](#downloads-for-v1164)
- [Client Binaries](#client-binaries)
- [Server Binaries](#server-binaries)
- [Node Binaries](#node-binaries)
- [Changelog since v1.16.3](#changelog-since-v1163)
- [API Changes](#api-changes)
- [Notes from Multiple SIGs](#notes-from-multiple-sigs)
- [SIG API Machinery, SIG Cloud Provider, and SIG Scalability](#sig-api-machinery-sig-cloud-provider-and-sig-scalability)
- [SIG Apps, and ` + "`SIG`" + ` Network](#sig-apps-and-sig-network)
- [Notes from Individual SIGs](#notes-from-individual-sigs)
- [SIG API Machinery](#sig-api-machinery)
- [SIG Cloud Provider](#sig-cloud-provider)
- [SIG Cluster Lifecycle](#sig-cluster-lifecycle)
- [SIG Network](#sig-network)
- [v1.16.3](#v1163)
- [Downloads for v1.16.3](#downloads-for-v1163)
- [Client Binaries](#client-binaries-1)
- [Server Binaries](#server-binaries-1)
- [Node Binaries](#node-binaries-1)
- [Changelog since v1.16.2](#changelog-since-v1162)
- [v1.16.2](#v1162)
- [Downloads for v1.16.2](#downloads-for-v1162)
- [Client Binaries](#client-binaries-2)
- [Server Binaries](#server-binaries-2)
- [Node Binaries](#node-binaries-2)
- [Changelog since v1.16.1](#changelog-since-v1161)
`

func TestGenerateTOC(t *testing.T) {
toc, err := GenerateTOC(testInput)
require.Nil(t, err)
require.Equal(t, toc, expectedOutput)
}

func TestGenerateTOCBackTickInHeading(t *testing.T) {
toc, err := GenerateTOC("# `markdown` solves all our problems, they said")
require.Nil(t, err)
require.Equal(t, toc, "- [`markdown` solves all our problems, they said](#markdown-solves-all-our-problems-they-said)\n")
}