Update to gitstatus@v0.4.1 (#4)

* Moves functions to main.go

* format: add and use package

* ci: use latest Go minor
This commit is contained in:
Aurélien Rainone 2019-11-06 23:12:39 +01:00 committed by Aurélien Rainone
parent ffafab15e2
commit 6f1a6ec93f
11 changed files with 482 additions and 115 deletions

View file

@ -1,8 +1,7 @@
language: go
go:
- "1.13"
- tip
- "1.13.x"
# calls goreleaser
deploy:
@ -11,5 +10,3 @@ deploy:
script: curl -sL https://git.io/goreleaser | bash
on:
tags: true
condition: $TRAVIS_OS_NAME = linux
condition: $TRAVIS_GO_VERSION = 1.13

73
cli.go
View file

@ -1,73 +0,0 @@
package main
import (
"flag"
"fmt"
"os"
"github.com/arl/gitstatus/format/tmux"
"gopkg.in/yaml.v2"
)
func check(err error, quiet bool) {
if err != nil {
if !quiet {
fmt.Println("error:", err)
}
os.Exit(1)
}
}
var version = "<<development version>>"
var usage = `gitmux ` + version + `
Usage: gitmux [options] [dir]
gitmux prints the status of a Git working tree.
If directory is not given, it default to the working directory.
Options:
-q be quiet. In case of errors, don't print nothing.
-fmt output format, defaults to json.
json prints status as a JSON object.
tmux prints status as a tmux format string.
-cfg cfgfile use cfgfile when printing git status.
-printcfg prints default configuration file.
-V prints gitmux version and exits.
`
var defaultCfg = Config{Tmux: tmux.DefaultCfg}
func parseOptions() (dir string, format string, quiet bool, cfg Config) {
fmtOpt := flag.String("fmt", "json", "")
cfgOpt := flag.String("cfg", "", "")
printCfgOpt := flag.Bool("printcfg", false, "")
quietOpt := flag.Bool("q", false, "")
versionOpt := flag.Bool("V", false, "")
flag.Usage = func() {
fmt.Println(usage)
}
flag.Parse()
dir = "."
if flag.NArg() > 0 {
dir = flag.Arg(0)
}
cfg = defaultCfg
if *versionOpt {
fmt.Println(version)
os.Exit(0)
}
if *printCfgOpt {
enc := yaml.NewEncoder(os.Stdout)
check(enc.Encode(&defaultCfg), *quietOpt)
enc.Close()
os.Exit(0)
}
if *cfgOpt != "" {
f, err := os.Open(*cfgOpt)
check(err, *quietOpt)
dec := yaml.NewDecoder(f)
check(dec.Decode(&cfg), *quietOpt)
}
return dir, *fmtOpt, *quietOpt, cfg
}

View file

@ -1,8 +0,0 @@
package main
import "github.com/arl/gitstatus/format/tmux"
// Config configures output formatting.
type Config struct {
Tmux tmux.Config
}

22
format/json/formater.go Normal file
View file

@ -0,0 +1,22 @@
package json
import (
"encoding/json"
"fmt"
"io"
"github.com/arl/gitstatus"
)
// A Formater formats git status to JSON.
type Formater struct{}
// Format writes st as json into w.
func (Formater) Format(w io.Writer, st *gitstatus.Status) error {
enc := json.NewEncoder(w)
enc.SetIndent("", " ")
if err := enc.Encode(st); err != nil {
return fmt.Errorf("can't format status to json: %v", err)
}
return nil
}

193
format/tmux/formater.go Normal file
View file

@ -0,0 +1,193 @@
package tmux
import (
"bytes"
"fmt"
"io"
"strings"
"github.com/arl/gitstatus"
)
const clear string = "#[fg=default]"
// Config is the configuration of the Git status tmux formatter.
type Config struct {
// Symbols contains the symbols printed before the Git status components.
Symbols symbols
// Styles contains the tmux style strings for symbols and Git status
// components.
Styles styles
}
type symbols struct {
Branch string // Branch is the string shown before local branch name.
HashPrefix string // HasPrefix is the string shown before a SHA1 ref.
Ahead string // Ahead is the string shown before the ahead count for the local/upstream branch divergence.
Behind string // Behind is the string shown before the behind count for the local/upstream branch divergence.
Staged string // Staged is the string shown before the count of staged files.
Conflict string // Conflict is the string shown before the count of files with conflicts.
Modified string // Modified is the string shown before the count of modified files.
Untracked string // Untracked is the string shown before the count of untracked files.
Stashed string // Stashed is the string shown before the count of stash entries.
Clean string // Clean is the string shown when the working tree is clean.
}
type styles struct {
State string // State is the style string printed before eventual special state.
Branch string // Branch is the style string printed before the local branch.
Remote string // Remote is the style string printed before the upstream branch.
Staged string // Staged is the style string printed before the staged files count.
Conflict string // Conflict is the style string printed before the conflict count.
Modified string // Modified is the style string printed before the modified files count.
Untracked string // Untracked is the style string printed before the untracked files count.
Stashed string // Stashed is the style string printed before the stash entries count.
Clean string // Clean is the style string printed before the clean symbols.
}
var DefaultCfg = Config{
Symbols: symbols{
Branch: "⎇ ",
Staged: "●",
Conflict: "✖ ",
Modified: "✚ ",
Untracked: "…",
Stashed: "⚑ ",
Clean: "✔",
Ahead: "↑·",
Behind: "↓·",
HashPrefix: ":",
},
Styles: styles{
State: "#[fg=red,bold]",
Branch: "#[fg=white,bold]",
Remote: "#[fg=cyan]",
Staged: "#[fg=green,bold]",
Conflict: "#[fg=red,bold]",
Modified: "#[fg=red,bold]",
Untracked: "#[fg=magenta,bold]",
Stashed: "#[fg=cyan,bold]",
Clean: "#[fg=green,bold]",
},
}
// A Formater formats git status to a tmux style string.
type Formater struct {
Config
b bytes.Buffer
st *gitstatus.Status
}
// Format writes st as json into w.
func (f *Formater) Format(w io.Writer, st *gitstatus.Status) error {
f.st = st
f.clear()
// overall working tree state
if f.st.IsInitial {
fmt.Fprintf(w, "%s%s [no commits yet]", f.Styles.Branch, f.st.LocalBranch)
goto fileCounts
}
f.specialState()
f.remote()
fileCounts:
f.flags()
_, err := f.b.WriteTo(w)
return err
}
func (f *Formater) specialState() {
f.clear()
switch f.st.State {
case gitstatus.Rebasing:
fmt.Fprintf(&f.b, "%s[rebase] ", f.Styles.State)
case gitstatus.AM:
fmt.Fprintf(&f.b, "%s[am] ", f.Styles.State)
case gitstatus.AMRebase:
fmt.Fprintf(&f.b, "%s[am-rebase] ", f.Styles.State)
case gitstatus.Merging:
fmt.Fprintf(&f.b, "%s[merge] ", f.Styles.State)
case gitstatus.CherryPicking:
fmt.Fprintf(&f.b, "%s[cherry-pick] ", f.Styles.State)
case gitstatus.Reverting:
fmt.Fprintf(&f.b, "%s[revert] ", f.Styles.State)
case gitstatus.Bisecting:
fmt.Fprintf(&f.b, "%s[bisect] ", f.Styles.State)
case gitstatus.Default:
fmt.Fprintf(&f.b, "%s%s", f.Styles.Branch, f.Symbols.Branch)
}
f.currentRef()
}
func (f *Formater) remote() {
f.clear()
if f.st.RemoteBranch != "" {
fmt.Fprintf(&f.b, "..%s%s", f.Styles.Remote, f.st.RemoteBranch)
f.divergence()
}
}
func (f *Formater) clear() {
// clear global style
f.b.WriteString(clear)
}
func (f *Formater) currentRef() {
f.clear()
if f.st.IsDetached {
fmt.Fprintf(&f.b, "%s%s", f.Symbols.HashPrefix, f.st.HEAD)
return
}
fmt.Fprintf(&f.b, "%s", f.st.LocalBranch)
}
func (f *Formater) divergence() {
f.clear()
pref := " "
if f.st.BehindCount != 0 {
fmt.Fprintf(&f.b, " %s%d", f.Symbols.Behind, f.st.BehindCount)
pref = ""
}
if f.st.AheadCount != 0 {
fmt.Fprintf(&f.b, "%s%s%d", pref, f.Symbols.Ahead, f.st.AheadCount)
}
}
func (f *Formater) flags() {
f.clear()
f.b.WriteString(" - ")
if f.st.IsClean {
fmt.Fprintf(&f.b, "%s%s", f.Styles.Clean, f.Symbols.Clean)
return
}
var flags []string
if f.st.NumStaged != 0 {
flags = append(flags,
fmt.Sprintf("%s%s%d", f.Styles.Staged, f.Symbols.Staged, f.st.NumStaged))
}
if f.st.NumConflicts != 0 {
flags = append(flags,
fmt.Sprintf("%s%s%d", f.Styles.Conflict, f.Symbols.Conflict, f.st.NumConflicts))
}
if f.st.NumModified != 0 {
flags = append(flags,
fmt.Sprintf("%s%s%d", f.Styles.Modified, f.Symbols.Modified, f.st.NumModified))
}
if f.st.NumStashed != 0 {
flags = append(flags,
fmt.Sprintf("%s%s%d", f.Styles.Stashed, f.Symbols.Stashed, f.st.NumStashed))
}
if f.st.NumUntracked != 0 {
flags = append(flags,
fmt.Sprintf("%s%s%d", f.Styles.Untracked, f.Symbols.Untracked, f.st.NumUntracked))
}
f.b.WriteString(strings.Join(flags, " "))
}

View file

@ -0,0 +1,158 @@
package tmux
import (
"testing"
"github.com/arl/gitstatus"
"github.com/stretchr/testify/require"
)
func TestFormater_flags(t *testing.T) {
tests := []struct {
name string
styles styles
symbols symbols
st *gitstatus.Status
want string
}{
{
name: "clean flag",
styles: styles{
Clean: "CleanStyle",
},
symbols: symbols{
Clean: "CleanSymbol",
},
st: &gitstatus.Status{
IsClean: true,
},
want: clear + " - CleanStyleCleanSymbol",
},
{
name: "mixed flags",
styles: styles{
Modified: "StyleMod",
Stashed: "StyleStash",
Staged: "StyleStaged",
},
symbols: symbols{
Modified: "SymbolMod",
Stashed: "SymbolStash",
Staged: "SymbolStaged",
},
st: &gitstatus.Status{
NumStashed: 1,
Porcelain: gitstatus.Porcelain{
NumModified: 2,
NumStaged: 3,
},
},
want: clear + " - StyleStagedSymbolStaged3 StyleModSymbolMod2 StyleStashSymbolStash1",
},
{
name: "mixed flags 2",
styles: styles{
Conflict: "StyleConflict",
Untracked: "StyleUntracked",
},
symbols: symbols{
Conflict: "SymbolConflict",
Untracked: "SymbolUntracked",
},
st: &gitstatus.Status{
Porcelain: gitstatus.Porcelain{
NumConflicts: 42,
NumUntracked: 17,
},
},
want: clear + " - StyleConflictSymbolConflict42 StyleUntrackedSymbolUntracked17",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
f := &Formater{
Config: Config{Styles: tc.styles, Symbols: tc.symbols},
st: tc.st,
}
f.flags()
require.EqualValues(t, tc.want, f.b.String())
})
}
}
func TestFormater_divergence(t *testing.T) {
tests := []struct {
name string
styles styles
symbols symbols
st *gitstatus.Status
want string
}{
{
name: "no divergence",
symbols: symbols{
Ahead: "↓·",
Behind: "↑·",
},
st: &gitstatus.Status{
Porcelain: gitstatus.Porcelain{
AheadCount: 0,
BehindCount: 0,
},
},
want: clear,
},
{
name: "ahead only",
symbols: symbols{
Ahead: "↓·",
Behind: "↑·",
},
st: &gitstatus.Status{
Porcelain: gitstatus.Porcelain{
AheadCount: 4,
BehindCount: 0,
},
},
want: clear + " ↓·4",
},
{
name: "behind only",
symbols: symbols{
Ahead: "↓·",
Behind: "↑·",
},
st: &gitstatus.Status{
Porcelain: gitstatus.Porcelain{
AheadCount: 0,
BehindCount: 12,
},
},
want: clear + " ↑·12",
},
{
name: "diverged both ways",
symbols: symbols{
Ahead: "↓·",
Behind: "↑·",
},
st: &gitstatus.Status{
Porcelain: gitstatus.Porcelain{
AheadCount: 41,
BehindCount: 128,
},
},
want: clear + " ↑·128↓·41",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
f := &Formater{
Config: Config{Styles: tc.styles, Symbols: tc.symbols},
st: tc.st,
}
f.divergence()
require.EqualValues(t, tc.want, f.b.String())
})
}
}

13
formater.go Normal file
View file

@ -0,0 +1,13 @@
package main
import (
"io"
"github.com/arl/gitstatus"
)
// A formater writes the status of a Git working tree in a given format.
type formater interface {
// Format writes the representation of a git status.
Format(io.Writer, *gitstatus.Status) error
}

3
go.mod
View file

@ -3,6 +3,7 @@ module github.com/arl/gitmux
go 1.10
require (
github.com/arl/gitstatus v0.3.1
github.com/arl/gitstatus v0.4.1
github.com/stretchr/testify v1.3.0
gopkg.in/yaml.v2 v2.2.4
)

9
go.sum
View file

@ -1,14 +1,13 @@
github.com/arl/gitstatus v0.3.1 h1:KIv92sf+Ce6E6qGXrx/WEl76lDwmR/ibtsExvCp8yPM=
github.com/arl/gitstatus v0.3.1/go.mod h1:6QjgTVY8epnMyZyJN4QjEY5WZZUCNfJsCFSjkfzaLFA=
github.com/arl/gitstatus v0.4.1 h1:BDMFkvy+fet1nnxvkr4hTIc2Oy5FVcB6oIYRLY0eQp8=
github.com/arl/gitstatus v0.4.1/go.mod h1:pEiL+vLLz99X0m5G4MySAt4o0fQw2yUbFeq2s7sMUzY=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
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/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

94
main.go
View file

@ -2,13 +2,98 @@ package main
import (
"errors"
"flag"
"fmt"
"os"
"github.com/arl/gitmux/format/json"
"github.com/arl/gitmux/format/tmux"
"github.com/arl/gitstatus"
"github.com/arl/gitstatus/format/json"
"github.com/arl/gitstatus/format/tmux"
"gopkg.in/yaml.v2"
)
func check(err error, quiet bool) {
if err != nil {
if !quiet {
fmt.Println("error:", err)
}
os.Exit(1)
}
}
// Config configures output formatting.
type Config struct{ Tmux tmux.Config }
var version = "<<development version>>"
var usage = `gitmux ` + version + `
Usage: gitmux [options] [dir]
gitmux prints the status of a Git working tree.
If directory is not given, it default to the working directory.
Options:
-q be quiet. In case of errors, don't print nothing.
-fmt output format, defaults to json.
json prints status as a JSON object.
tmux prints status as a tmux format string.
-cfg cfgfile use cfgfile when printing git status.
-printcfg prints default configuration file.
-V prints gitmux version and exits.
`
var defaultCfg = Config{Tmux: tmux.DefaultCfg}
func parseOptions() (dir string, format string, quiet bool, cfg Config) {
fmtOpt := flag.String("fmt", "json", "")
cfgOpt := flag.String("cfg", "", "")
printCfgOpt := flag.Bool("printcfg", false, "")
quietOpt := flag.Bool("q", false, "")
versionOpt := flag.Bool("V", false, "")
flag.Usage = func() {
fmt.Println(usage)
}
flag.Parse()
dir = "."
if flag.NArg() > 0 {
dir = flag.Arg(0)
}
cfg = defaultCfg
if *versionOpt {
fmt.Println(version)
os.Exit(0)
}
if *printCfgOpt {
enc := yaml.NewEncoder(os.Stdout)
check(enc.Encode(&defaultCfg), *quietOpt)
enc.Close()
os.Exit(0)
}
if *cfgOpt != "" {
f, err := os.Open(*cfgOpt)
check(err, *quietOpt)
dec := yaml.NewDecoder(f)
check(dec.Decode(&cfg), *quietOpt)
}
return dir, *fmtOpt, *quietOpt, cfg
}
type popdir func() error
func pushdir(dir string) (popdir, error) {
pwd, err := os.Getwd()
if err != nil {
return nil, err
}
err = os.Chdir(dir)
if err != nil {
return nil, err
}
return func() error { return os.Chdir(pwd) }, nil
}
var errUnknownOutputFormat = errors.New("unknown output format")
func main() {
@ -29,7 +114,7 @@ func main() {
check(err, quiet)
// register formaters
formaters := make(map[string]gitstatus.Formater)
formaters := make(map[string]formater)
formaters["json"] = &json.Formater{}
formaters["tmux"] = &tmux.Formater{Config: cfg.Tmux}
@ -39,7 +124,6 @@ func main() {
}
// format and print
out, err := formater.Format(st)
err = formater.Format(os.Stdout, st)
check(err, quiet)
fmt.Print(out)
}

View file

@ -1,19 +0,0 @@
package main
import "os"
type popdir func() error
func pushdir(dir string) (popdir, error) {
pwd, err := os.Getwd()
if err != nil {
return nil, err
}
err = os.Chdir(dir)
if err != nil {
return nil, err
}
return func() error { return os.Chdir(pwd) }, nil
}