Skip to content

Commit

Permalink
Initial version (#1)
Browse files Browse the repository at this point in the history
This is the first working version of `keeparelease`.
  • Loading branch information
rgreinho authored Jun 23, 2019
1 parent 206ea13 commit 80e7c66
Show file tree
Hide file tree
Showing 8 changed files with 331 additions and 1 deletion.
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [[1.0.0]] - 2019-06-23

Initial release.

[//]: # (Release links)


[//]: # (Issue/PR links)
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
# keeparelease
Publish beautiful GitHub release with Keep a changelog

Publish beautiful GitHub release with Keep a changelog.
106 changes: 106 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package cmd

import (
"fmt"
"os"
"path/filepath"
"strings"

"github.com/github/hub/github"
"github.com/github/hub/ui"
"github.com/github/hub/utils"

"github.com/rgreinho/keeparelease/keeparelease"
"github.com/spf13/cobra"
)

var cfgFile string

// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "keeparelease",
Short: "Create beautiful GitHub releases.",
RunE: func(cmd *cobra.Command, args []string) error {
// Get the flags.
extract, err := cmd.Flags().GetBool("extract")
utils.Check(err)

// Read the last release information from the Changelog.
title, content, err := keeparelease.ReadChangelog()
if err != nil {
return err
}

// If extract only, simply display the content of the last release.
if extract {
fmt.Print(content)
return nil
}

// Inspect the local repo.
localRepo, err := github.LocalRepo()
utils.Check(err)
project, nil := localRepo.MainProject()
utils.Check(err)

// Prepare the Hub client.
gh := github.NewClient(project.Host)

// Prepare the release.
tag, err := cmd.Flags().GetString("tag")
params := &github.Release{
TagName: tag,
Name: title,
Body: content,
}

// Create the release.
var release *github.Release
release, err = gh.CreateRelease(project, params)
utils.Check(err)

// Upload assets.
assets, err := cmd.Flags().GetStringArray("attach")
uploadAssets(gh, release, assets)

return nil
},
}

// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}

func init() {
// rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.keeparelease.yaml)")
rootCmd.Flags().BoolP("extract", "x", false, "Only extract the last release information")
rootCmd.Flags().StringP("tag", "t", "", "Use a specific tag")
rootCmd.Flags().StringArrayP("attach", "a", []string{}, "Specify the assets to include into the release")
}

func uploadAssets(gh *github.Client, release *github.Release, assets []string) {
for _, asset := range assets {
var label string
parts := strings.SplitN(asset, "#", 2)
asset = parts[0]
if len(parts) > 1 {
label = parts[1]
}

for _, existingAsset := range release.Assets {
if existingAsset.Name == filepath.Base(asset) {
err := gh.DeleteReleaseAsset(&existingAsset)
utils.Check(err)
break
}
}
ui.Errorf("Attaching release asset `%s'...\n", asset)
_, err := gh.UploadReleaseAsset(release, asset, label)
utils.Check(err)
}
}
20 changes: 20 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module github.com/rgreinho/keeparelease

go 1.12

require (
github.com/BurntSushi/toml v0.3.1 // indirect
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
github.com/github/hub v2.11.1+incompatible
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/kr/pretty v0.1.0 // indirect
github.com/lithammer/dedent v1.1.0
github.com/mattn/go-colorable v0.1.1 // indirect
github.com/mattn/go-isatty v0.0.7 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/spf13/cobra v0.0.3
github.com/spf13/pflag v1.0.3 // indirect
golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac // indirect
gopkg.in/yaml.v2 v2.2.2 // indirect
)
37 changes: 37 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
github.com/github/hub v2.11.1+incompatible h1:z/Sy79RwOBJtDCX2dwWJliYHTjVRx0BHEEsQ3I6cTOY=
github.com/github/hub v2.11.1+incompatible/go.mod h1:zQrzJEdze2hfWJDgktd/L6sROjAdCThFrzjbxw4keTs=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lithammer/dedent v1.1.0 h1:VNzHMVCBNG1j0fh3OrsFRkVUwStdDArbgBWoPAffktY=
github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc=
github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/rgreinho/keeparelease v0.0.0-20190620004850-206ea132e078 h1:CFHFrdOhBQ/f5Qfo3aH7gGtb/bC9pwMcInrh0zCK0xE=
github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac h1:7d7lG9fHOLdL6jZPtnV4LpI41SbohIJ1Atq7U991dMg=
golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
72 changes: 72 additions & 0 deletions keeparelease/keeparelease.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package keeparelease

import (
"errors"
"io/ioutil"
"regexp"
"strings"
)

const regexTitle string = `^\#\#\s+\[{1,2}(?P<full_version>(?P<major>(?:0|[1-9][0-9]*))\.(?P<minor>(?:0|[1-9][0-9]*))\.(?P<patch>(?:0|[1-9][0-9]*))(\-(?P<prerelease>(?:0|[1-9A-Za-z-][0-9A-Za-z-]*)(\.(?:0|[1-9A-Za-z-][0-9A-Za-z-]*))*))?(\+(?P<build>[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?)\]{1,2}\s+-?\s+(?P<date>.*)`

// ParseChangelog parses a ChangeLog respecting the Keep A Changelog format.
func ParseChangelog(changelog []string) (string, string, error) {
re := regexp.MustCompile(regexTitle)
title := ""
releaseInfo := make([]string, 1)
foundStart := false
for _, line := range changelog {

// Look for a release line.
if re.MatchString(line) {
if !foundStart {

// Indicate we found the first line of the last release information.
foundStart = true
match := re.FindStringSubmatch(line)
result := make(map[string]string)
for i, name := range re.SubexpNames() {
if i != 0 && name != "" {
result[name] = match[i]
}
}

// Add title line.
title = result["full_version"]
continue

} else {
// We reached the last line of the last release information.
break
}
}
if foundStart {
// Ensure the first line is followed by a blank line.
if len(releaseInfo) == 1 && line != "" {
releaseInfo = append(releaseInfo, "")
}

// Append the content.
releaseInfo = append(releaseInfo, line)
}
}
if len(releaseInfo) == 0 {
return "", "", errors.New("could not extract release information")
}

return title, strings.Join(releaseInfo, "\n"), nil
}

// ReadChangelog reads the changelog file.
func ReadChangelog() (string, string, error) {
dat, err := ioutil.ReadFile("CHANGELOG.md")
if err != nil {
return "", "", err
}
content := string(dat)
title, info, err := ParseChangelog(strings.Split(content, "\n"))
if err != nil {
return "", "", err
}
return title, info, nil
}
70 changes: 70 additions & 0 deletions keeparelease/keeparelease_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package keeparelease

import (
"strings"
"testing"

"github.com/lithammer/dedent"
)

const changelog string = `
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [[0.4.0]] - 2018-09-07
### Added
- A feature
### Fixed
- A fix
## [[0.3.0]] - 2018-09-05
### Added
- Make the Git operations idempotent.
- Implement all the operations required to create an environment.
- Create an Octopus client to simplify the operation using its REST API.
- Add --dry-run flag to bypass the write operations.
[//]: # (Release links)
[0.3.0]: https://github.com/shipstation/hyperion/releases/tag/0.3.0
[0.4.0]: https://github.com/shipstation/hyperion/releases/tag/0.4.0
[//]: # (Issue/PR links)
[#15]: https://github.com/shipstation/hyperion/pull/15
[#16]: https://github.com/shipstation/hyperion/pull/16
`

func TestParseChangelog00(t *testing.T) {
title, content, err := ParseChangelog(strings.Split(changelog, "\n"))
if err != nil {
t.Fatalf("failed to parse the changelog: %s", err)
}
expected := `
### Added
- A feature
### Fixed
- A fix
`

if dedent.Dedent(title) != dedent.Dedent("0.4.0") {
t.Fatalf("Error: title is %s, but expected is 0.4.0", title)
}
if dedent.Dedent(content) != dedent.Dedent(expected) {
t.Fatalf("Error: actual is %s, but expected is %s", content, expected)
}

}
7 changes: 7 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package main

import "github.com/rgreinho/keeparelease/cmd"

func main() {
cmd.Execute()
}

0 comments on commit 80e7c66

Please sign in to comment.