File: main.go

package info (click to toggle)
golang-go.uber-mock 0.5.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,176 kB
  • sloc: sh: 37; makefile: 3
file content (140 lines) | stat: -rw-r--r-- 2,679 bytes parent folder | download
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
}