Skip to content
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
41 changes: 41 additions & 0 deletions cli.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package gitsemvers

import (
"fmt"
"io"
"strings"

"github.com/jessevdk/go-flags"
)

const (
exitcodeOK = iota
exitCodeParseFlagErr
exitCodeErr
)

// CLI is for command line
type CLI struct {
OutStream, ErrStream io.Writer
}

// Run the cli
func (cli *CLI) Run(argv []string) int {
p, sv, err := parseArgs(argv)
if err != nil {
if ferr, ok := err.(*flags.Error); !ok || ferr.Type != flags.ErrHelp {
p.WriteHelp(cli.ErrStream)
}
return exitCodeParseFlagErr
}
fmt.Fprintln(cli.OutStream, strings.Join(sv.VersionStrings(), "\n"))
return exitcodeOK
}

func parseArgs(args []string) (*flags.Parser, *Semvers, error) {
sv := &Semvers{}
p := flags.NewParser(sv, flags.Default)
p.Usage = "[OPTIONS]\n\nVersion: " + version
_, err := p.ParseArgs(args)
return p, sv, err
}
11 changes: 11 additions & 0 deletions cmd/git-semvers/git-semvers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package main

import (
"os"

"github.com/Songmu/gitsemvers"
)

func main() {
os.Exit((&gitsemvers.CLI{ErrStream: os.Stderr, OutStream: os.Stdout}).Run(os.Args[1:]))
}
117 changes: 117 additions & 0 deletions gitsemvers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package gitsemvers

import (
"bytes"
"os"
"os/exec"
"regexp"
"sort"
"strings"

"github.com/Masterminds/semver"
)

const version = "0.0.0"

var verRegStr = `^v?[0-9]+(?:\.[0-9]+){0,2}`
var extension = `[-0-9A-Za-z]+(?:\.[-0-9A-Za-z]+)*`
var withPreReleaseRegStr = "(?:-" + extension + ")?"
var withBuildMetadataRegStr = `(?:\+` + extension + ")?"

type regBuilder uint

const (
naked regBuilder = 0

withPreRelease = 1 << (iota - 1)
withBuildMetadata

withPreReleaseAndBuildMetadata = withPreRelease | withBuildMetadata
)

var cache = make(map[regBuilder]*regexp.Regexp)

func (rb regBuilder) build() string {
b := bytes.NewBufferString(verRegStr)
if rb&withPreRelease != 0 {
b.WriteString(withPreReleaseRegStr)
}
if rb&withBuildMetadata != 0 {
b.WriteString(withBuildMetadataRegStr)
}
b.WriteString("$")
return b.String()
}

func (rb regBuilder) reg() *regexp.Regexp {
return cache[rb]
}

func init() {
regs := []regBuilder{naked, withPreRelease, withBuildMetadata, withPreReleaseAndBuildMetadata}
for _, v := range regs {
cache[v] = regexp.MustCompile(v.build())
}
}

// Semvers retrieve semvers from git tags
type Semvers struct {
RepoPath string `short:"r" long:"repo" default:"." description:"git repository path"`
GitPath string `short:"g" long:"git" default:"git" description:"git path"`
WithPreRelease bool `short:"P" long:"with-pre-release" description:"display pre-release versions"`
WithBuildMetadata bool `short:"B" long:"with-build-metadata" description:"display build-metadata versions"`
}

// VersionStrings returns version strings
func (sv *Semvers) VersionStrings() []string {
tags, err := sv.gitTags()
if err != nil {
return nil
}
return sv.parseVersions(tags)
}

func (sv *Semvers) reg() *regexp.Regexp {
regB := regBuilder(0)
if sv.WithPreRelease {
regB |= withPreRelease
}
if sv.WithBuildMetadata {
regB |= withBuildMetadata
}
return regB.reg()
}

func (sv *Semvers) gitProg() string {
if sv.GitPath != "" {
return sv.GitPath
}
return "git"
}

func (sv *Semvers) gitTags() (string, error) {
cmd := exec.Command(sv.gitProg(), "-C", sv.RepoPath, "tag")
var b bytes.Buffer
cmd.Stdout = &b
cmd.Stderr = os.Stderr
err := cmd.Run()
return b.String(), err
}

func (sv *Semvers) parseVersions(out string) []string {
rawTags := strings.Split(out, "\n")
var versions []*semver.Version
for _, tag := range rawTags {
t := strings.TrimSpace(tag)
if sv.reg().MatchString(t) {
v, _ := semver.NewVersion(t)
versions = append(versions, v)
}
}
sort.Sort(sort.Reverse(semver.Collection(versions)))
var vers = make([]string, len(versions))
for i, v := range versions {
vers[i] = v.Original()
}
return vers
}
74 changes: 74 additions & 0 deletions gitsemvers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package gitsemvers

import (
"reflect"
"testing"
)

var input = `dummy
v0.10.1
v0.9.0
v0.9.3
v0.8.4-pre
v0.8.4
v0.8.3+win
v0.8.2-pre.pre+win.win
v0.7.0-pre+win+invalid
`

func TestParseVersions(t *testing.T) {
expect := []string{
"v0.10.1",
"v0.9.3",
"v0.9.0",
"v0.8.4",
}
sv := &Semvers{}
if !reflect.DeepEqual(sv.parseVersions(input), expect) {
t.Errorf("something went wrong %+v", sv.parseVersions(input))
}
}

func TestParseVersionsWithPreRelease(t *testing.T) {
expect := []string{
"v0.10.1",
"v0.9.3",
"v0.9.0",
"v0.8.4",
"v0.8.4-pre",
}
sv := &Semvers{WithPreRelease: true}
if !reflect.DeepEqual(sv.parseVersions(input), expect) {
t.Errorf("something went wrong %+v", sv.parseVersions(input))
}
}

func TestParseVersionsWithBuildMetadata(t *testing.T) {
expect := []string{
"v0.10.1",
"v0.9.3",
"v0.9.0",
"v0.8.4",
"v0.8.3+win",
}
sv := &Semvers{WithBuildMetadata: true}
if !reflect.DeepEqual(sv.parseVersions(input), expect) {
t.Errorf("something went wrong %+v", sv.parseVersions(input))
}
}

func TestParseVersionsWithAllExtensions(t *testing.T) {
expect := []string{
"v0.10.1",
"v0.9.3",
"v0.9.0",
"v0.8.4",
"v0.8.4-pre",
"v0.8.3+win",
"v0.8.2-pre.pre+win.win",
}
sv := &Semvers{WithPreRelease: true, WithBuildMetadata: true}
if !reflect.DeepEqual(sv.parseVersions(input), expect) {
t.Errorf("something went wrong %+v", sv.parseVersions(input))
}
}