1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
|
// extract-changelog extracts the release notes for a specific version from a
// file matching the format prescribed by https://keepachangelog.com/en/1.0.0/.
package main
import (
"bufio"
"bytes"
"errors"
"flag"
"fmt"
"io"
"os"
"strings"
)
func main() {
cmd := mainCmd{
Stdout: os.Stdout,
Stderr: os.Stderr,
}
if err := cmd.Run(os.Args[1:]); err != nil && err != flag.ErrHelp {
fmt.Fprintln(cmd.Stderr, err)
os.Exit(1)
}
}
type mainCmd struct {
Stdout io.Writer
Stderr io.Writer
}
const _usage = `USAGE
%v [OPTIONS] VERSION
Retrieves the release notes for VERSION from a CHANGELOG.md file and prints
them to stdout.
EXAMPLES
extract-changelog -i CHANGELOG.md v1.2.3
extract-changelog 0.2.5
OPTIONS
`
func (cmd *mainCmd) Run(args []string) error {
flag := flag.NewFlagSet("extract-changelog", flag.ContinueOnError)
flag.SetOutput(cmd.Stderr)
flag.Usage = func() {
fmt.Fprintf(flag.Output(), _usage, flag.Name())
flag.PrintDefaults()
}
file := flag.String("i", "CHANGELOG.md", "input file")
if err := flag.Parse(args); err != nil {
return err
}
var version string
if args := flag.Args(); len(args) > 0 {
version = args[0]
}
version = strings.TrimPrefix(version, "v")
if len(version) == 0 {
return errors.New("please provide a version")
}
f, err := os.Open(*file)
if err != nil {
return fmt.Errorf("open changelog: %v", err)
}
defer f.Close()
s, err := extract(f, version)
if err != nil {
return err
}
_, err = io.WriteString(cmd.Stdout, s)
return err
}
func extract(r io.Reader, version string) (string, error) {
type _state int
const (
initial _state = iota
foundHeader
)
var (
state _state
buff bytes.Buffer
scanner = bufio.NewScanner(r)
)
scan:
for scanner.Scan() {
line := scanner.Text()
switch state {
case initial:
// Version headers take one of the following forms:
//
// ## 0.1.3 - 2021-08-18
// ## [0.1.3] - 2021-08-18
switch {
case strings.HasPrefix(line, "## "+version+" "),
strings.HasPrefix(line, "## ["+version+"]"):
fmt.Fprintln(&buff, line)
state = foundHeader
}
case foundHeader:
// Found a new version header. Stop extracting.
if strings.HasPrefix(line, "## ") {
break scan
}
fmt.Fprintln(&buff, line)
default:
// unreachable but guard against it.
return "", fmt.Errorf("unexpected state %v at %q", state, line)
}
}
if err := scanner.Err(); err != nil {
return "", err
}
if state < foundHeader {
return "", fmt.Errorf("changelog for %q not found", version)
}
out := buff.String()
out = strings.TrimSpace(out) + "\n" // always end with a single newline
return out, nil
}
|