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
107 changes: 52 additions & 55 deletions commands/bug/bug.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/MichaelMure/git-bug/commands/execenv"
"github.com/MichaelMure/git-bug/entities/bug"
"github.com/MichaelMure/git-bug/entities/common"
"github.com/MichaelMure/git-bug/entity"
"github.com/MichaelMure/git-bug/query"
"github.com/MichaelMure/git-bug/util/colors"
)
Expand Down Expand Up @@ -93,10 +94,10 @@ git bug status:open --by creation "foo bar" baz
flags.StringVarP(&options.sortDirection, "direction", "d", "asc",
"Select the sorting direction. Valid values are [asc,desc]")
cmd.RegisterFlagCompletionFunc("direction", completion.From([]string{"asc", "desc"}))
flags.StringVarP(&options.outputFormat, "format", "f", "default",
"Select the output formatting style. Valid values are [default,plain,compact,id,json,org-mode]")
flags.StringVarP(&options.outputFormat, "format", "f", "",
"Select the output formatting style. Valid values are [default,plain,id,json,org-mode]")
cmd.RegisterFlagCompletionFunc("format",
completion.From([]string{"default", "plain", "compact", "id", "json", "org-mode"}))
completion.From([]string{"default", "plain", "id", "json", "org-mode"}))

const selectGroup = "select"
cmd.AddGroup(&cobra.Group{ID: selectGroup, Title: "Implicit selection"})
Expand Down Expand Up @@ -146,28 +147,32 @@ func runBug(env *execenv.Env, opts bugOptions, args []string) error {
return err
}

bugExcerpt := make([]*cache.BugExcerpt, len(allIds))
excerpts := make([]*cache.BugExcerpt, len(allIds))
for i, id := range allIds {
b, err := env.Backend.Bugs().ResolveExcerpt(id)
if err != nil {
return err
}
bugExcerpt[i] = b
excerpts[i] = b
}

switch opts.outputFormat {
case "org-mode":
return bugsOrgmodeFormatter(env, bugExcerpt)
case "":
if env.Out.IsTerminal() {
return bugsDefaultFormatter(env, excerpts)
} else {
return bugsPlainFormatter(env, excerpts)
}
case "default":
return bugsDefaultFormatter(env, excerpts)
case "id":
return bugsIDFormatter(env, excerpts)
case "plain":
return bugsPlainFormatter(env, bugExcerpt)
return bugsPlainFormatter(env, excerpts)
case "json":
return bugsJsonFormatter(env, bugExcerpt)
case "compact":
return bugsCompactFormatter(env, bugExcerpt)
case "id":
return bugsIDFormatter(env, bugExcerpt)
case "default":
return bugsDefaultFormatter(env, bugExcerpt)
return bugsJsonFormatter(env, excerpts)
case "org-mode":
return bugsOrgmodeFormatter(env, excerpts)
default:
return fmt.Errorf("unknown format %s", opts.outputFormat)
}
Expand All @@ -186,9 +191,9 @@ func repairQuery(args []string) string {
return strings.Join(args, " ")
}

func bugsJsonFormatter(env *execenv.Env, bugExcerpts []*cache.BugExcerpt) error {
jsonBugs := make([]cmdjson.BugExcerpt, len(bugExcerpts))
for i, b := range bugExcerpts {
func bugsJsonFormatter(env *execenv.Env, excerpts []*cache.BugExcerpt) error {
jsonBugs := make([]cmdjson.BugExcerpt, len(excerpts))
for i, b := range excerpts {
jsonBug, err := cmdjson.NewBugExcerpt(env.Backend, b)
if err != nil {
return err
Expand All @@ -198,42 +203,34 @@ func bugsJsonFormatter(env *execenv.Env, bugExcerpts []*cache.BugExcerpt) error
return env.Out.PrintJSON(jsonBugs)
}

func bugsCompactFormatter(env *execenv.Env, bugExcerpts []*cache.BugExcerpt) error {
for _, b := range bugExcerpts {
author, err := env.Backend.Identities().ResolveExcerpt(b.AuthorId)
if err != nil {
return err
}

var labelsTxt strings.Builder
for _, l := range b.Labels {
lc256 := l.Color().Term256()
labelsTxt.WriteString(lc256.Escape())
labelsTxt.WriteString("◼")
labelsTxt.WriteString(lc256.Unescape())
}

env.Out.Printf("%s %s %s %s %s\n",
colors.Cyan(b.Id().Human()),
colors.Yellow(b.Status),
text.LeftPadMaxLine(strings.TrimSpace(b.Title), 40, 0),
text.LeftPadMaxLine(labelsTxt.String(), 5, 0),
colors.Magenta(text.TruncateMax(author.DisplayName(), 15)),
)
func bugsIDFormatter(env *execenv.Env, excerpts []*cache.BugExcerpt) error {
for _, b := range excerpts {
env.Out.Println(b.Id().String())
}

return nil
}

func bugsIDFormatter(env *execenv.Env, bugExcerpts []*cache.BugExcerpt) error {
for _, b := range bugExcerpts {
env.Out.Println(b.Id().String())
func bugsDefaultFormatter(env *execenv.Env, excerpts []*cache.BugExcerpt) error {
width := env.Out.Width()
widthId := entity.HumanIdLength
widthStatus := len("closed")
widthComment := 6

widthRemaining := width -
widthId - 1 -
widthStatus - 1 -
widthComment - 1

widthTitle := int(float32(widthRemaining-3) * 0.7)
if widthTitle < 0 {
widthTitle = 0
}

return nil
}
widthRemaining = widthRemaining - widthTitle - 3 - 2
widthAuthor := widthRemaining

func bugsDefaultFormatter(env *execenv.Env, bugExcerpts []*cache.BugExcerpt) error {
for _, b := range bugExcerpts {
for _, b := range excerpts {
author, err := env.Backend.Identities().ResolveExcerpt(b.AuthorId)
if err != nil {
return err
Expand All @@ -249,8 +246,8 @@ func bugsDefaultFormatter(env *execenv.Env, bugExcerpts []*cache.BugExcerpt) err

// truncate + pad if needed
labelsFmt := text.TruncateMax(labelsTxt.String(), 10)
titleFmt := text.LeftPadMaxLine(strings.TrimSpace(b.Title), 50-text.Len(labelsFmt), 0)
authorFmt := text.LeftPadMaxLine(author.DisplayName(), 15, 0)
titleFmt := text.LeftPadMaxLine(strings.TrimSpace(b.Title), widthTitle-text.Len(labelsFmt), 0)
authorFmt := text.LeftPadMaxLine(author.DisplayName(), widthAuthor, 0)

comments := fmt.Sprintf("%3d 💬", b.LenComments-1)
if b.LenComments-1 <= 0 {
Expand All @@ -260,7 +257,7 @@ func bugsDefaultFormatter(env *execenv.Env, bugExcerpts []*cache.BugExcerpt) err
comments = " ∞ 💬"
}

env.Out.Printf("%s\t%s\t%s\t%s\t%s\n",
env.Out.Printf("%s\t%s\t%s %s %s\n",
colors.Cyan(b.Id().Human()),
colors.Yellow(b.Status),
titleFmt+labelsFmt,
Expand All @@ -271,14 +268,14 @@ func bugsDefaultFormatter(env *execenv.Env, bugExcerpts []*cache.BugExcerpt) err
return nil
}

func bugsPlainFormatter(env *execenv.Env, bugExcerpts []*cache.BugExcerpt) error {
for _, b := range bugExcerpts {
env.Out.Printf("%s [%s] %s\n", b.Id().Human(), b.Status, strings.TrimSpace(b.Title))
func bugsPlainFormatter(env *execenv.Env, excerpts []*cache.BugExcerpt) error {
for _, b := range excerpts {
env.Out.Printf("%s\t%s\t%s\n", b.Id().Human(), b.Status, strings.TrimSpace(b.Title))
}
return nil
}

func bugsOrgmodeFormatter(env *execenv.Env, bugExcerpts []*cache.BugExcerpt) error {
func bugsOrgmodeFormatter(env *execenv.Env, excerpts []*cache.BugExcerpt) error {
// see https://orgmode.org/manual/Tags.html
orgTagRe := regexp.MustCompile("[^[:alpha:]_@]")
formatTag := func(l bug.Label) string {
Expand All @@ -291,7 +288,7 @@ func bugsOrgmodeFormatter(env *execenv.Env, bugExcerpts []*cache.BugExcerpt) err

env.Out.Println("#+TODO: OPEN | CLOSED")

for _, b := range bugExcerpts {
for _, b := range excerpts {
status := strings.ToUpper(b.Status.String())

var title string
Expand Down
7 changes: 3 additions & 4 deletions commands/bug/bug_comment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@ package bugcmd

import (
"fmt"
"io/ioutil"
"os"
"strings"
"testing"
"time"

"github.com/stretchr/testify/require"

"github.com/MichaelMure/git-bug/commands/bug/testenv"
"github.com/MichaelMure/git-bug/commands/cmdtest"
"github.com/MichaelMure/git-bug/commands/execenv"
)

Expand Down Expand Up @@ -143,7 +142,7 @@ func requireCommentsEqual(t *testing.T, golden string, env *execenv.Env) {
t.Log("Got here")
for i, comment := range comments {
fileName := fmt.Sprintf(goldenFilePattern, golden, i)
require.NoError(t, ioutil.WriteFile(fileName, []byte(comment.message), 0644))
require.NoError(t, os.WriteFile(fileName, []byte(comment.message), 0644))
}
}

Expand All @@ -157,7 +156,7 @@ func requireCommentsEqual(t *testing.T, golden string, env *execenv.Env) {
require.Equal(t, date.Add(time.Duration(i)*time.Minute), comment.date)

fileName := fmt.Sprintf(goldenFilePattern, golden, i)
exp, err := ioutil.ReadFile(fileName)
exp, err := os.ReadFile(fileName)
require.NoError(t, err)
require.Equal(t, strings.ReplaceAll(string(exp), "\r", ""), strings.ReplaceAll(comment.message, "\r", ""))
}
Expand Down
50 changes: 19 additions & 31 deletions commands/bug/bug_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package bugcmd

import (
"encoding/json"
"fmt"
"testing"

"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -61,44 +60,33 @@ $`
format string
exp string
}{
{"default", "^[0-9a-f]{7}\topen\tthis is a bug title \tJohn Doe \t\n$"},
{"plain", "^[0-9a-f]{7} \\[open\\] this is a bug title\n$"},
{"compact", "^[0-9a-f]{7} open this is a bug title John Doe\n$"},
{"default", "^[0-9a-f]{7}\topen\tthis is a bug title John Doe \n$"},
{"plain", "^[0-9a-f]{7}\topen\tthis is a bug title\n$"},
{"id", "^[0-9a-f]{64}\n$"},
{"org-mode", expOrgMode},
{"json", ".*"},
}

for _, testcase := range cases {
opts := bugOptions{
sortDirection: "asc",
sortBy: "creation",
outputFormat: testcase.format,
}

name := fmt.Sprintf("with %s format", testcase.format)

t.Run(name, func(t *testing.T) {
t.Run(testcase.format, func(t *testing.T) {
env, _ := testenv.NewTestEnvAndBug(t)

opts := bugOptions{
sortDirection: "asc",
sortBy: "creation",
outputFormat: testcase.format,
}

require.NoError(t, runBug(env, opts, []string{}))
require.Regexp(t, testcase.exp, env.Out.String())

switch testcase.format {
case "json":
var bugs []cmdjson.BugExcerpt
require.NoError(t, json.Unmarshal(env.Out.Bytes(), &bugs))
require.Len(t, bugs, 1)
default:
require.Regexp(t, testcase.exp, env.Out.String())
}
})
}

t.Run("with JSON format", func(t *testing.T) {
opts := bugOptions{
sortDirection: "asc",
sortBy: "creation",
outputFormat: "json",
}

env, _ := testenv.NewTestEnvAndBug(t)

require.NoError(t, runBug(env, opts, []string{}))

var bugs []cmdjson.BugExcerpt
require.NoError(t, json.Unmarshal(env.Out.Bytes(), &bugs))

require.Len(t, bugs, 1)
})
}
13 changes: 13 additions & 0 deletions commands/execenv/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"os"

"github.com/mattn/go-isatty"
"golang.org/x/term"

"github.com/MichaelMure/git-bug/cache"
"github.com/MichaelMure/git-bug/repository"
Expand Down Expand Up @@ -57,6 +58,8 @@ type Out interface {
// IsTerminal tells if the output is a user terminal (rather than a buffer,
// a pipe ...), which tells if we can use colors and other interactive features.
IsTerminal() bool
// Width return the width of the attached terminal, or a good enough value.
Width() int

// Raw return the underlying io.Writer, or itself if not.
// This is useful if something need to access the raw file descriptor.
Expand Down Expand Up @@ -123,6 +126,16 @@ func (o out) IsTerminal() bool {
return false
}

func (o out) Width() int {
if f, ok := o.Raw().(*os.File); ok {
width, _, err := term.GetSize(int(f.Fd()))
if err == nil {
return width
}
}
return 80
}

func (o out) Raw() io.Writer {
return o.Writer
}
Expand Down
12 changes: 8 additions & 4 deletions commands/execenv/env_testing.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ type TestIn struct {
forceIsTerminal bool
}

func (t *TestIn) ForceIsTerminal(value bool) {
t.forceIsTerminal = value
}

func (t *TestIn) IsTerminal() bool {
return t.forceIsTerminal
}

func (t *TestIn) ForceIsTerminal(value bool) {
t.forceIsTerminal = value
}

var _ Out = &TestOut{}

type TestOut struct {
Expand Down Expand Up @@ -60,6 +60,10 @@ func (te *TestOut) IsTerminal() bool {
return te.forceIsTerminal
}

func (te *TestOut) Width() int {
return 80
}

func (te *TestOut) Raw() io.Writer {
return te.Buffer
}
Expand Down
6 changes: 0 additions & 6 deletions doc/man/git-bug-bridge-new.1
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,8 @@ git-bug-bridge-new - Configure a new bridge

.SH DESCRIPTION
.PP
.RS

.nf
Configure a new bridge by passing flags or/and using interactive terminal prompts. You can avoid all the terminal prompts by passing all the necessary flags to configure your bridge.

.fi
.RE


.SH OPTIONS
.PP
Expand Down
4 changes: 2 additions & 2 deletions doc/man/git-bug-bug.1
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ You can pass an additional query to filter and order the list. This query can be
Select the sorting direction. Valid values are [asc,desc]

.PP
\fB-f\fP, \fB--format\fP="default"
Select the output formatting style. Valid values are [default,plain,compact,id,json,org-mode]
\fB-f\fP, \fB--format\fP=""
Select the output formatting style. Valid values are [default,plain,id,json,org-mode]

.PP
\fB-h\fP, \fB--help\fP[=false]
Expand Down
2 changes: 1 addition & 1 deletion doc/md/git-bug_bridge_new.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Configure a new bridge

### Synopsis

Configure a new bridge by passing flags or/and using interactive terminal prompts. You can avoid all the terminal prompts by passing all the necessary flags to configure your bridge.
Configure a new bridge by passing flags or/and using interactive terminal prompts. You can avoid all the terminal prompts by passing all the necessary flags to configure your bridge.

```
git-bug bridge new [flags]
Expand Down
Loading