aboutsummaryrefslogtreecommitdiffstats
path: root/commands
diff options
context:
space:
mode:
Diffstat (limited to 'commands')
-rw-r--r--commands/add.go2
-rw-r--r--commands/comment_add.go2
-rw-r--r--commands/comment_edit.go2
-rw-r--r--commands/env.go4
-rw-r--r--commands/helper_completion.go2
-rw-r--r--commands/input/input.go348
-rw-r--r--commands/input/prompt.go265
-rw-r--r--commands/json_common.go2
-rw-r--r--commands/ls.go11
-rw-r--r--commands/ls_test.go58
-rw-r--r--commands/select/select.go2
-rw-r--r--commands/show.go2
-rw-r--r--commands/title_edit.go2
-rw-r--r--commands/user_create.go2
-rw-r--r--commands/webui.go2
15 files changed, 689 insertions, 17 deletions
diff --git a/commands/add.go b/commands/add.go
index 504207fb..b43eda36 100644
--- a/commands/add.go
+++ b/commands/add.go
@@ -3,7 +3,7 @@ package commands
import (
"github.com/spf13/cobra"
- "github.com/MichaelMure/git-bug/input"
+ "github.com/MichaelMure/git-bug/commands/input"
"github.com/MichaelMure/git-bug/util/text"
)
diff --git a/commands/comment_add.go b/commands/comment_add.go
index f308428c..e415763b 100644
--- a/commands/comment_add.go
+++ b/commands/comment_add.go
@@ -3,8 +3,8 @@ package commands
import (
"github.com/spf13/cobra"
+ "github.com/MichaelMure/git-bug/commands/input"
_select "github.com/MichaelMure/git-bug/commands/select"
- "github.com/MichaelMure/git-bug/input"
"github.com/MichaelMure/git-bug/util/text"
)
diff --git a/commands/comment_edit.go b/commands/comment_edit.go
index 759ea194..91c6d809 100644
--- a/commands/comment_edit.go
+++ b/commands/comment_edit.go
@@ -3,7 +3,7 @@ package commands
import (
"github.com/spf13/cobra"
- "github.com/MichaelMure/git-bug/input"
+ "github.com/MichaelMure/git-bug/commands/input"
)
type commentEditOptions struct {
diff --git a/commands/env.go b/commands/env.go
index a6bca7e4..11b91c4b 100644
--- a/commands/env.go
+++ b/commands/env.go
@@ -7,9 +7,9 @@ import (
"github.com/spf13/cobra"
- "github.com/MichaelMure/git-bug/bug"
"github.com/MichaelMure/git-bug/cache"
- "github.com/MichaelMure/git-bug/identity"
+ "github.com/MichaelMure/git-bug/entities/bug"
+ "github.com/MichaelMure/git-bug/entities/identity"
"github.com/MichaelMure/git-bug/repository"
"github.com/MichaelMure/git-bug/util/interrupt"
)
diff --git a/commands/helper_completion.go b/commands/helper_completion.go
index 3a089e35..847a0288 100644
--- a/commands/helper_completion.go
+++ b/commands/helper_completion.go
@@ -9,9 +9,9 @@ import (
"github.com/MichaelMure/git-bug/bridge"
"github.com/MichaelMure/git-bug/bridge/core/auth"
- "github.com/MichaelMure/git-bug/bug"
"github.com/MichaelMure/git-bug/cache"
_select "github.com/MichaelMure/git-bug/commands/select"
+ "github.com/MichaelMure/git-bug/entities/bug"
)
type validArgsFunction func(cmd *cobra.Command, args []string, toComplete string) (completions []string, directives cobra.ShellCompDirective)
diff --git a/commands/input/input.go b/commands/input/input.go
new file mode 100644
index 00000000..e9c1be1b
--- /dev/null
+++ b/commands/input/input.go
@@ -0,0 +1,348 @@
+// Inspired by the git-appraise project
+
+// Package input contains helpers to use a text editor as an input for
+// various field of a bug
+package input
+
+import (
+ "bufio"
+ "bytes"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strings"
+
+ "github.com/go-git/go-billy/v5/util"
+ "github.com/pkg/errors"
+
+ "github.com/MichaelMure/git-bug/repository"
+)
+
+const messageFilename = "BUG_MESSAGE_EDITMSG"
+
+// ErrEmptyMessage is returned when the required message has not been entered
+var ErrEmptyMessage = errors.New("empty message")
+
+// ErrEmptyMessage is returned when the required title has not been entered
+var ErrEmptyTitle = errors.New("empty title")
+
+const bugTitleCommentTemplate = `%s%s
+
+# Please enter the title and comment message. The first non-empty line will be
+# used as the title. Lines starting with '#' will be ignored.
+# An empty title aborts the operation.
+`
+
+// BugCreateEditorInput will open the default editor in the terminal with a
+// template for the user to fill. The file is then processed to extract title
+// and message.
+func BugCreateEditorInput(repo repository.RepoCommonStorage, preTitle string, preMessage string) (string, string, error) {
+ if preMessage != "" {
+ preMessage = "\n\n" + preMessage
+ }
+
+ template := fmt.Sprintf(bugTitleCommentTemplate, preTitle, preMessage)
+
+ raw, err := launchEditorWithTemplate(repo, messageFilename, template)
+
+ if err != nil {
+ return "", "", err
+ }
+
+ return processCreate(raw)
+}
+
+// BugCreateFileInput read from either from a file or from the standard input
+// and extract a title and a message
+func BugCreateFileInput(fileName string) (string, string, error) {
+ raw, err := fromFile(fileName)
+ if err != nil {
+ return "", "", err
+ }
+
+ return processCreate(raw)
+}
+
+func processCreate(raw string) (string, string, error) {
+ lines := strings.Split(raw, "\n")
+
+ var title string
+ var buffer bytes.Buffer
+ for _, line := range lines {
+ if strings.HasPrefix(line, "#") {
+ continue
+ }
+
+ if title == "" {
+ trimmed := strings.TrimSpace(line)
+ if trimmed != "" {
+ title = trimmed
+ }
+ continue
+ }
+
+ buffer.WriteString(line)
+ buffer.WriteString("\n")
+ }
+
+ if title == "" {
+ return "", "", ErrEmptyTitle
+ }
+
+ message := strings.TrimSpace(buffer.String())
+
+ return title, message, nil
+}
+
+const bugCommentTemplate = `%s
+
+# Please enter the comment message. Lines starting with '#' will be ignored,
+# and an empty message aborts the operation.
+`
+
+// BugCommentEditorInput will open the default editor in the terminal with a
+// template for the user to fill. The file is then processed to extract a comment.
+func BugCommentEditorInput(repo repository.RepoCommonStorage, preMessage string) (string, error) {
+ template := fmt.Sprintf(bugCommentTemplate, preMessage)
+
+ raw, err := launchEditorWithTemplate(repo, messageFilename, template)
+ if err != nil {
+ return "", err
+ }
+
+ return processComment(raw)
+}
+
+// BugCommentFileInput read from either from a file or from the standard input
+// and extract a message
+func BugCommentFileInput(fileName string) (string, error) {
+ raw, err := fromFile(fileName)
+ if err != nil {
+ return "", err
+ }
+
+ return processComment(raw)
+}
+
+func processComment(raw string) (string, error) {
+ lines := strings.Split(raw, "\n")
+
+ var buffer bytes.Buffer
+ for _, line := range lines {
+ if strings.HasPrefix(line, "#") {
+ continue
+ }
+ buffer.WriteString(line)
+ buffer.WriteString("\n")
+ }
+
+ message := strings.TrimSpace(buffer.String())
+
+ if message == "" {
+ return "", ErrEmptyMessage
+ }
+
+ return message, nil
+}
+
+const bugTitleTemplate = `%s
+
+# Please enter the new title. Only one line will used.
+# Lines starting with '#' will be ignored, and an empty title aborts the operation.
+`
+
+// BugTitleEditorInput will open the default editor in the terminal with a
+// template for the user to fill. The file is then processed to extract a title.
+func BugTitleEditorInput(repo repository.RepoCommonStorage, preTitle string) (string, error) {
+ template := fmt.Sprintf(bugTitleTemplate, preTitle)
+
+ raw, err := launchEditorWithTemplate(repo, messageFilename, template)
+ if err != nil {
+ return "", err
+ }
+
+ lines := strings.Split(raw, "\n")
+
+ var title string
+ for _, line := range lines {
+ if strings.HasPrefix(line, "#") {
+ continue
+ }
+ trimmed := strings.TrimSpace(line)
+ if trimmed == "" {
+ continue
+ }
+ title = trimmed
+ break
+ }
+
+ if title == "" {
+ return "", ErrEmptyTitle
+ }
+
+ return title, nil
+}
+
+const queryTemplate = `%s
+
+# Please edit the bug query.
+# Lines starting with '#' will be ignored, and an empty query aborts the operation.
+#
+# Example: status:open author:"rené descartes" sort:edit
+#
+# Valid filters are:
+#
+# - status:open, status:closed
+# - author:<query>
+# - title:<title>
+# - label:<label>
+# - no:label
+#
+# Sorting
+#
+# - sort:id, sort:id-desc, sort:id-asc
+# - sort:creation, sort:creation-desc, sort:creation-asc
+# - sort:edit, sort:edit-desc, sort:edit-asc
+#
+# Notes
+#
+# - queries are case insensitive.
+# - you can combine as many qualifiers as you want.
+# - you can use double quotes for multi-word search terms (ex: author:"René Descartes")
+`
+
+// QueryEditorInput will open the default editor in the terminal with a
+// template for the user to fill. The file is then processed to extract a query.
+func QueryEditorInput(repo repository.RepoCommonStorage, preQuery string) (string, error) {
+ template := fmt.Sprintf(queryTemplate, preQuery)
+
+ raw, err := launchEditorWithTemplate(repo, messageFilename, template)
+ if err != nil {
+ return "", err
+ }
+
+ lines := strings.Split(raw, "\n")
+
+ for _, line := range lines {
+ if strings.HasPrefix(line, "#") {
+ continue
+ }
+ trimmed := strings.TrimSpace(line)
+ if trimmed == "" {
+ continue
+ }
+ return trimmed, nil
+ }
+
+ return "", nil
+}
+
+// launchEditorWithTemplate will launch an editor as launchEditor do, but with a
+// provided template.
+func launchEditorWithTemplate(repo repository.RepoCommonStorage, fileName string, template string) (string, error) {
+ err := util.WriteFile(repo.LocalStorage(), fileName, []byte(template), 0644)
+ if err != nil {
+ return "", err
+ }
+
+ return launchEditor(repo, fileName)
+}
+
+// launchEditor launches the default editor configured for the given repo. This
+// method blocks until the editor command has returned.
+//
+// The specified filename should be a temporary file and provided as a relative path
+// from the repo (e.g. "FILENAME" will be converted to "[<reporoot>/].git/git-bug/FILENAME"). This file
+// will be deleted after the editor is closed and its contents have been read.
+//
+// This method returns the text that was read from the temporary file, or
+// an error if any step in the process failed.
+func launchEditor(repo repository.RepoCommonStorage, fileName string) (string, error) {
+ defer repo.LocalStorage().Remove(fileName)
+
+ editor, err := repo.GetCoreEditor()
+ if err != nil {
+ return "", fmt.Errorf("Unable to detect default git editor: %v\n", err)
+ }
+
+ repo.LocalStorage().Root()
+
+ // bypass the interface but that's ok: we need that because we are communicating
+ // the absolute path to an external program
+ path := filepath.Join(repo.LocalStorage().Root(), fileName)
+
+ cmd, err := startInlineCommand(editor, path)
+ if err != nil {
+ // Running the editor directly did not work. This might mean that
+ // the editor string is not a path to an executable, but rather
+ // a shell command (e.g. "emacsclient --tty"). As such, we'll try
+ // to run the command through bash, and if that fails, try with sh
+ args := []string{"-c", fmt.Sprintf("%s %q", editor, path)}
+ cmd, err = startInlineCommand("bash", args...)
+ if err != nil {
+ cmd, err = startInlineCommand("sh", args...)
+ }
+ }
+ if err != nil {
+ return "", fmt.Errorf("Unable to start editor: %v\n", err)
+ }
+
+ if err := cmd.Wait(); err != nil {
+ return "", fmt.Errorf("Editing finished with error: %v\n", err)
+ }
+
+ output, err := ioutil.ReadFile(path)
+
+ if err != nil {
+ return "", fmt.Errorf("Error reading edited file: %v\n", err)
+ }
+
+ return string(output), err
+}
+
+// fromFile loads and returns the contents of a given file. If - is passed
+// through, much like git, it will read from stdin. This can be piped data,
+// unless there is a tty in which case the user will be prompted to enter a
+// message.
+func fromFile(fileName string) (string, error) {
+ if fileName == "-" {
+ stat, err := os.Stdin.Stat()
+ if err != nil {
+ return "", fmt.Errorf("Error reading from stdin: %v\n", err)
+ }
+ if (stat.Mode() & os.ModeCharDevice) == 0 {
+ // There is no tty. This will allow us to read piped data instead.
+ output, err := ioutil.ReadAll(os.Stdin)
+ if err != nil {
+ return "", fmt.Errorf("Error reading from stdin: %v\n", err)
+ }
+ return string(output), err
+ }
+
+ fmt.Printf("(reading comment from standard input)\n")
+ var output bytes.Buffer
+ s := bufio.NewScanner(os.Stdin)
+ for s.Scan() {
+ output.Write(s.Bytes())
+ output.WriteRune('\n')
+ }
+ return output.String(), nil
+ }
+
+ output, err := ioutil.ReadFile(fileName)
+ if err != nil {
+ return "", fmt.Errorf("Error reading file: %v\n", err)
+ }
+ return string(output), err
+}
+
+func startInlineCommand(command string, args ...string) (*exec.Cmd, error) {
+ cmd := exec.Command(command, args...)
+ cmd.Stdin = os.Stdin
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ err := cmd.Start()
+ return cmd, err
+}
diff --git a/commands/input/prompt.go b/commands/input/prompt.go
new file mode 100644
index 00000000..576d45bb
--- /dev/null
+++ b/commands/input/prompt.go
@@ -0,0 +1,265 @@
+package input
+
+import (
+ "bufio"
+ "fmt"
+ "net/url"
+ "os"
+ "sort"
+ "strconv"
+ "strings"
+ "syscall"
+ "time"
+
+ "golang.org/x/crypto/ssh/terminal"
+
+ "github.com/MichaelMure/git-bug/bridge/core/auth"
+ "github.com/MichaelMure/git-bug/util/colors"
+ "github.com/MichaelMure/git-bug/util/interrupt"
+)
+
+// PromptValidator is a validator for a user entry
+// If complaint is "", value is considered valid, otherwise it's the error reported to the user
+// If err != nil, a terminal error happened
+type PromptValidator func(name string, value string) (complaint string, err error)
+
+// Required is a validator preventing a "" value
+func Required(name string, value string) (string, error) {
+ if value == "" {
+ return fmt.Sprintf("%s is empty", name), nil
+ }
+ return "", nil
+}
+
+// IsURL is a validator checking that the value is a fully formed URL
+func IsURL(name string, value string) (string, error) {
+ u, err := url.Parse(value)
+ if err != nil {
+ return fmt.Sprintf("%s is invalid: %v", name, err), nil
+ }
+ if u.Scheme == "" {
+ return fmt.Sprintf("%s is missing a scheme", name), nil
+ }
+ if u.Host == "" {
+ return fmt.Sprintf("%s is missing a host", name), nil
+ }
+ return "", nil
+}
+
+// Prompts
+
+// Prompt is a simple text input.
+func Prompt(prompt, name string, validators ...PromptValidator) (string, error) {
+ return PromptDefault(prompt, name, "", validators...)
+}
+
+// PromptDefault is a simple text input with a default value.
+func PromptDefault(prompt, name, preValue string, validators ...PromptValidator) (string, error) {
+loop:
+ for {
+ if preValue != "" {
+ _, _ = fmt.Fprintf(os.Stderr, "%s [%s]: ", prompt, preValue)
+ } else {
+ _, _ = fmt.Fprintf(os.Stderr, "%s: ", prompt)
+ }
+
+ line, err := bufio.NewReader(os.Stdin).ReadString('\n')
+ if err != nil {
+ return "", err
+ }
+
+ line = strings.TrimSpace(line)
+
+ if preValue != "" && line == "" {
+ line = preValue
+ }
+
+ for _, validator := range validators {
+ complaint, err := validator(name, line)
+ if err != nil {
+ return "", err
+ }
+ if complaint != "" {
+ _, _ = fmt.Fprintln(os.Stderr, complaint)
+ continue loop
+ }
+ }
+
+ return line, nil
+ }
+}
+
+// PromptPassword is a specialized text input that doesn't display the characters entered.
+func PromptPassword(prompt, name string, validators ...PromptValidator) (string, error) {
+ termState, err := terminal.GetState(int(syscall.Stdin))
+ if err != nil {
+ return "", err
+ }
+
+ cancel := interrupt.RegisterCleaner(func() error {
+ return terminal.Restore(int(syscall.Stdin), termState)
+ })
+ defer cancel()
+
+loop:
+ for {
+ _, _ = fmt.Fprintf(os.Stderr, "%s: ", prompt)
+
+ bytePassword, err := terminal.ReadPassword(int(syscall.Stdin))
+ // new line for coherent formatting, ReadPassword clip the normal new line
+ // entered by the user
+ fmt.Println()
+
+ if err != nil {
+ return "", err
+ }
+
+ pass := string(bytePassword)
+
+ for _, validator := range validators {
+ complaint, err := validator(name, pass)
+ if err != nil {
+ return "", err
+ }
+ if complaint != "" {
+ _, _ = fmt.Fprintln(os.Stderr, complaint)
+ continue loop
+ }
+ }
+
+ return pass, nil
+ }
+}
+
+// PromptChoice is a prompt giving possible choices
+// Return the index starting at zero of the choice selected.
+func PromptChoice(prompt string, choices []string) (int, error) {
+ for {
+ for i, choice := range choices {
+ _, _ = fmt.Fprintf(os.Stderr, "[%d]: %s\n", i+1, choice)
+ }
+ _, _ = fmt.Fprintf(os.Stderr, "%s: ", prompt)
+
+ line, err := bufio.NewReader(os.Stdin).ReadString('\n')
+ fmt.Println()
+ if err != nil {
+ return 0, err
+ }
+
+ line = strings.TrimSpace(line)
+
+ index, err := strconv.Atoi(line)
+ if err != nil || index < 1 || index > len(choices) {
+ _, _ = fmt.Fprintln(os.Stderr, "invalid input")
+ continue
+ }
+
+ return index - 1, nil
+ }
+}
+
+func PromptURLWithRemote(prompt, name string, validRemotes []string, validators ...PromptValidator) (string, error) {
+ if len(validRemotes) == 0 {
+ return Prompt(prompt, name, validators...)
+ }
+
+ sort.Strings(validRemotes)
+
+ for {
+ _, _ = fmt.Fprintln(os.Stderr, "\nDetected projects:")
+
+ for i, remote := range validRemotes {
+ _, _ = fmt.Fprintf(os.Stderr, "[%d]: %v\n", i+1, remote)
+ }
+
+ _, _ = fmt.Fprintf(os.Stderr, "\n[0]: Another project\n\n")
+ _, _ = fmt.Fprintf(os.Stderr, "Select option: ")
+
+ line, err := bufio.NewReader(os.Stdin).ReadString('\n')
+ if err != nil {
+ return "", err
+ }
+
+ line = strings.TrimSpace(line)
+
+ index, err := strconv.Atoi(line)
+ if err != nil || index < 0 || index > len(validRemotes) {
+ _, _ = fmt.Fprintln(os.Stderr, "invalid input")
+ continue
+ }
+
+ // if user want to enter another project url break this loop
+ if index == 0 {
+ break
+ }
+
+ return validRemotes[index-1], nil
+ }
+
+ return Prompt(prompt, name, validators...)
+}
+
+func PromptCredential(target, name string, credentials []auth.Credential, choices []string) (auth.Credential, int, error) {
+ if len(credentials) == 0 && len(choices) == 0 {
+ return nil, 0, fmt.Errorf("no possible choice")
+ }
+ if len(credentials) == 0 && len(choices) == 1 {
+ return nil, 0, nil
+ }
+
+ sort.Sort(auth.ById(credentials))
+
+ for {
+ _, _ = fmt.Fprintln(os.Stderr)
+
+ offset := 0
+ for i, choice := range choices {
+ _, _ = fmt.Fprintf(os.Stderr, "[%d]: %s\n", i+1, choice)
+ offset++
+ }
+
+ if len(credentials) > 0 {
+ _, _ = fmt.Fprintln(os.Stderr)
+ _, _ = fmt.Fprintf(os.Stderr, "Existing %s for %s:\n", name, target)
+
+ for i, cred := range credentials {
+ meta := make([]string, 0, len(cred.Metadata()))
+ for k, v := range cred.Metadata() {
+ meta = append(meta, k+":"+v)
+ }
+ sort.Strings(meta)
+ metaFmt := strings.Join(meta, ",")
+
+ fmt.Printf("[%d]: %s => (%s) (%s)\n",
+ i+1+offset,
+ colors.Cyan(cred.ID().Human()),
+ metaFmt,
+ cred.CreateTime().Format(time.RFC822),
+ )
+ }
+ }
+
+ _, _ = fmt.Fprintln(os.Stderr)
+ _, _ = fmt.Fprintf(os.Stderr, "Select option: ")
+
+ line, err := bufio.NewReader(os.Stdin).ReadString('\n')
+ _, _ = fmt.Fprintln(os.Stderr)
+ if err != nil {
+ return nil, 0, err
+ }
+
+ line = strings.TrimSpace(line)
+ index, err := strconv.Atoi(line)
+ if err != nil || index < 1 || index > len(choices)+len(credentials) {
+ _, _ = fmt.Fprintln(os.Stderr, "invalid input")
+ continue
+ }
+
+ switch {
+ case index <= len(choices):
+ return nil, index - 1, nil
+ default:
+ return credentials[index-len(choices)-1], 0, nil
+ }
+ }
+}
diff --git a/commands/json_common.go b/commands/json_common.go
index 9a144a1e..3ceee1ec 100644
--- a/commands/json_common.go
+++ b/commands/json_common.go
@@ -4,7 +4,7 @@ import (
"time"
"github.com/MichaelMure/git-bug/cache"
- "github.com/MichaelMure/git-bug/identity"
+ "github.com/MichaelMure/git-bug/entities/identity"
"github.com/MichaelMure/git-bug/util/lamport"
)
diff --git a/commands/ls.go b/commands/ls.go
index da5ea8ce..6ec06f39 100644
--- a/commands/ls.go
+++ b/commands/ls.go
@@ -10,8 +10,9 @@ import (
text "github.com/MichaelMure/go-term-text"
"github.com/spf13/cobra"
- "github.com/MichaelMure/git-bug/bug"
"github.com/MichaelMure/git-bug/cache"
+ "github.com/MichaelMure/git-bug/entities/bug"
+ "github.com/MichaelMure/git-bug/entities/common"
"github.com/MichaelMure/git-bug/query"
"github.com/MichaelMure/git-bug/util/colors"
)
@@ -91,9 +92,9 @@ git bug ls status:open --by creation "foo bar" baz
"Select the sorting direction. Valid values are [asc,desc]")
cmd.RegisterFlagCompletionFunc("direction", completeFrom([]string{"asc", "desc"}))
flags.StringVarP(&options.outputFormat, "format", "f", "default",
- "Select the output formatting style. Valid values are [default,plain,json,org-mode]")
+ "Select the output formatting style. Valid values are [default,plain,compact,json,org-mode]")
cmd.RegisterFlagCompletionFunc("format",
- completeFrom([]string{"default", "plain", "json", "org-mode"}))
+ completeFrom([]string{"default", "plain", "compact", "json", "org-mode"}))
return cmd
}
@@ -279,7 +280,7 @@ func lsDefaultFormatter(env *Env, bugExcerpts []*cache.BugExcerpt) error {
comments = " ∞ 💬"
}
- env.out.Printf("%s %s\t%s\t%s\t%s\n",
+ env.out.Printf("%s\t%s\t%s\t%s\t%s\n",
colors.Cyan(b.Id.Human()),
colors.Yellow(b.Status),
titleFmt+labelsFmt,
@@ -379,7 +380,7 @@ func lsOrgmodeFormatter(env *Env, bugExcerpts []*cache.BugExcerpt) error {
// Finish the command flags transformation into the query.Query
func completeQuery(q *query.Query, opts lsOptions) error {
for _, str := range opts.statusQuery {
- status, err := bug.StatusFromString(str)
+ status, err := common.StatusFromString(str)
if err != nil {
return err
}
diff --git a/commands/ls_test.go b/commands/ls_test.go
index aff94e03..6e31ed83 100644
--- a/commands/ls_test.go
+++ b/commands/ls_test.go
@@ -1,6 +1,8 @@
package commands
import (
+ "encoding/json"
+ "fmt"
"testing"
"github.com/stretchr/testify/require"
@@ -41,3 +43,59 @@ func Test_repairQuery(t *testing.T) {
require.Equal(t, tc.output, repairQuery(tc.args))
}
}
+
+func TestLs_Format(t *testing.T) {
+ const expOrgMode = `^#+TODO: OPEN | CLOSED
+[*] OPEN [0-9a-f]{7} \[\d\d\d\d-\d\d-\d\d [[:alpha:]]{3} \d\d:\d\d\] John Doe: this is a bug title ::
+[*]{2} Last Edited: \[\d\d\d\d-\d\d-\d\d [[:alpha:]]{3} \d\d:\d\d\]
+[*]{2} Actors:
+: [0-9a-f]{7} John Doe
+[*]{2} Participants:
+: [0-9a-f]{7} John Doe
+$`
+
+ cases := []struct {
+ 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$"},
+ {"org-mode", expOrgMode},
+ }
+
+ for _, testcase := range cases {
+ opts := lsOptions{
+ sortDirection: "asc",
+ sortBy: "creation",
+ outputFormat: testcase.format,
+ }
+
+ name := fmt.Sprintf("with %s format", testcase.format)
+
+ t.Run(name, func(t *testing.T) {
+ env, _ := newTestEnvAndBug(t)
+
+ require.NoError(t, runLs(env.env, opts, []string{}))
+ t.Log(env.out.String())
+ require.Regexp(t, testcase.exp, env.out.String())
+ })
+ }
+
+ t.Run("with JSON format", func(t *testing.T) {
+ opts := lsOptions{
+ sortDirection: "asc",
+ sortBy: "creation",
+ outputFormat: "json",
+ }
+
+ env, _ := newTestEnvAndBug(t)
+
+ require.NoError(t, runLs(env.env, opts, []string{}))
+
+ bugs := []JSONBugExcerpt{}
+ require.NoError(t, json.Unmarshal(env.out.Bytes(), &bugs))
+
+ require.Len(t, bugs, 1)
+ })
+}
diff --git a/commands/select/select.go b/commands/select/select.go
index 3df74387..908ad58c 100644
--- a/commands/select/select.go
+++ b/commands/select/select.go
@@ -8,8 +8,8 @@ import (
"github.com/pkg/errors"
- "github.com/MichaelMure/git-bug/bug"
"github.com/MichaelMure/git-bug/cache"
+ "github.com/MichaelMure/git-bug/entities/bug"
"github.com/MichaelMure/git-bug/entity"
)
diff --git a/commands/show.go b/commands/show.go
index 16747214..d145ffe7 100644
--- a/commands/show.go
+++ b/commands/show.go
@@ -8,8 +8,8 @@ import (
"github.com/spf13/cobra"
- "github.com/MichaelMure/git-bug/bug"
_select "github.com/MichaelMure/git-bug/commands/select"
+ "github.com/MichaelMure/git-bug/entities/bug"
"github.com/MichaelMure/git-bug/util/colors"
)
diff --git a/commands/title_edit.go b/commands/title_edit.go
index a9e7fe4b..a1ba0324 100644
--- a/commands/title_edit.go
+++ b/commands/title_edit.go
@@ -3,8 +3,8 @@ package commands
import (
"github.com/spf13/cobra"
+ "github.com/MichaelMure/git-bug/commands/input"
_select "github.com/MichaelMure/git-bug/commands/select"
- "github.com/MichaelMure/git-bug/input"
"github.com/MichaelMure/git-bug/util/text"
)
diff --git a/commands/user_create.go b/commands/user_create.go
index b5cb0528..6941cff5 100644
--- a/commands/user_create.go
+++ b/commands/user_create.go
@@ -3,7 +3,7 @@ package commands
import (
"github.com/spf13/cobra"
- "github.com/MichaelMure/git-bug/input"
+ "github.com/MichaelMure/git-bug/commands/input"
)
type createUserOptions struct {
diff --git a/commands/webui.go b/commands/webui.go
index 2f80bcd0..406485c3 100644
--- a/commands/webui.go
+++ b/commands/webui.go
@@ -22,7 +22,7 @@ import (
"github.com/MichaelMure/git-bug/api/graphql"
httpapi "github.com/MichaelMure/git-bug/api/http"
"github.com/MichaelMure/git-bug/cache"
- "github.com/MichaelMure/git-bug/identity"
+ "github.com/MichaelMure/git-bug/entities/identity"
"github.com/MichaelMure/git-bug/repository"
"github.com/MichaelMure/git-bug/webui"
)