From b42c9adde5bc00e75ebf563cc6343484c3920e53 Mon Sep 17 00:00:00 2001 From: Niclas van Eyk Date: Fri, 16 Jun 2023 17:59:20 +0200 Subject: [PATCH] yank command --- README.md | 12 +++++++ cmd/yank.go | 55 +++++++++++++++++++++++++++++++ internal/changelog/completions.go | 29 ++++++++++++++++ internal/changelog/inserter.go | 6 ++++ internal/changelog/parser.go | 12 +++++++ internal/changelog/yank.go | 16 +++++++++ 6 files changed, 130 insertions(+) create mode 100644 cmd/yank.go create mode 100644 internal/changelog/completions.go create mode 100644 internal/changelog/yank.go diff --git a/README.md b/README.md index ef20768..e036afc 100644 --- a/README.md +++ b/README.md @@ -103,3 +103,15 @@ Turns the `[Unreleased]` section into a proper versioned release. You may pass a version adhering to SemVer or use the `--major`, `--minor`, `--patch` flags. Note that there must be prior releases in order to use these! By default the current date is used as the realease date, but you may override this using the `--date` option. + +### `changelog yank ` + +Marks the specified released as yanked`. To cite [Keep a Changelog](https://keepachangelog.com/en/1.1.0/#yanked): + +> Yanked releases are versions that had to be pulled because of a serious bug or security issue. Often these versions don't even appear in change logs. They should. This is how you should display them: +> +> ```markdown +> ## [0.0.5] - 2014-12-13 [YANKED] +> ``` +> +> The [YANKED] tag is loud for a reason. It's important for people to notice it. Since it's surrounded by brackets it's also easier to parse programmatically. diff --git a/cmd/yank.go b/cmd/yank.go new file mode 100644 index 0000000..6312fd7 --- /dev/null +++ b/cmd/yank.go @@ -0,0 +1,55 @@ +package cmd + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" + + clog "github.com/niclasvaneyk/keepac/internal/changelog" +) + +// yankCmd represents the yank command +var yankCmd = &cobra.Command{ + Use: "yank", + Short: "Marks the specified release as yanked", + Long: `As described by https://keepachangelog.com, yanked releases are versions that had to be pulled because of a serious bug or security issue.`, + Args: cobra.ExactArgs(1), + ValidArgsFunction: clog.CompleteReleasesAsFirstArgument, + RunE: func(cmd *cobra.Command, args []string) error { + changelog, changelogPath, err := clog.ResolveChangelog() + if err != nil { + return err + } + + target := args[0] + + newSource, err := changelog.Yank(target) + if err != nil { + return err + } + + err = os.WriteFile(changelogPath, []byte(newSource), 0774) + if err != nil { + return err + } + + fmt.Printf("Marked '%s' as yanked!\n", target) + + return nil + }, +} + +func init() { + rootCmd.AddCommand(yankCmd) + + // Here you will define your flags and configuration settings. + + // Cobra supports Persistent Flags which will work for this command + // and all subcommands, e.g.: + // yankCmd.PersistentFlags().String("foo", "", "A help for foo") + + // Cobra supports local flags which will only run when this command + // is called directly, e.g.: + // yankCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} diff --git a/internal/changelog/completions.go b/internal/changelog/completions.go new file mode 100644 index 0000000..b2fbe70 --- /dev/null +++ b/internal/changelog/completions.go @@ -0,0 +1,29 @@ +package changelog + +import ( + "strings" + + "github.com/spf13/cobra" +) + +// Can be passed as ValidArgsFunction to support custom completions if the +// first argument is a released version. +func CompleteReleasesAsFirstArgument(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) != 0 { + return nil, cobra.ShellCompDirectiveNoFileComp + } + + changelog, _, err := ResolveChangelog() + if err != nil { + return nil, cobra.ShellCompDirectiveNoFileComp + } + + matchingVersions := make([]string, 0) + for _, release := range changelog.Releases.Past { + if strings.HasPrefix(release.Version, toComplete) { + matchingVersions = append(matchingVersions, release.Version) + } + } + + return matchingVersions, cobra.ShellCompDirectiveNoFileComp +} diff --git a/internal/changelog/inserter.go b/internal/changelog/inserter.go index d4d3207..1486a86 100644 --- a/internal/changelog/inserter.go +++ b/internal/changelog/inserter.go @@ -191,3 +191,9 @@ func (changelog *Changelog) ReplacedWithinBounds(bounds Bounds, replacement stri return source[:bounds.Start] + replacement + source[bounds.Stop:] } + +func (changelog *Changelog) WithAddition(insertionPoint int, addition string) string { + source := changelog.source + + return source[:insertionPoint] + addition + source[insertionPoint:] +} diff --git a/internal/changelog/parser.go b/internal/changelog/parser.go index 6a961ae..716cccf 100644 --- a/internal/changelog/parser.go +++ b/internal/changelog/parser.go @@ -3,6 +3,7 @@ package changelog import ( "math" "regexp" + "strings" "github.com/yuin/goldmark" "github.com/yuin/goldmark/ast" @@ -205,6 +206,7 @@ func Parse(source []byte) Changelog { line := string(heading.Text(source)) r := NewRelease(parseVersion(line), parseDate(line)) + r.Yanked = strings.Contains(line, "[YANKED]") r.HeadlineBounds = headingBounds r.Bounds = Bounds{ // we subtract the length of "## " to achieve better insertion points @@ -309,3 +311,13 @@ func parseDate(line string) string { return "" } + +func (changelog *Changelog) FindRelease(version string) *Release { + for _, release := range changelog.Releases.Past { + if release.Version == version { + return &release + } + } + + return nil +} diff --git a/internal/changelog/yank.go b/internal/changelog/yank.go new file mode 100644 index 0000000..5fabc33 --- /dev/null +++ b/internal/changelog/yank.go @@ -0,0 +1,16 @@ +package changelog + +import "fmt" + +func (changelog *Changelog) Yank(version string) (string, error) { + release := changelog.FindRelease(version) + if release == nil { + return "", fmt.Errorf("Release '%s' not found", version) + } + + if release.Yanked { + return "", fmt.Errorf("Release '%s' was already yanked", version) + } + + return changelog.WithAddition(release.HeadlineBounds.Stop, " [YANKED]"), nil +}