From 77a15fe825d395ad5eecf2df3c1137c713e58438 Mon Sep 17 00:00:00 2001 From: Clement Sam Date: Wed, 12 Aug 2020 07:06:18 +0000 Subject: [PATCH] feat: add --repo flag to issue view --- commands/issue.go | 26 +++-- commands/issue_view.go | 159 +++++++++++++++++++++++++++++ commands/mr.go | 25 ++--- internal/browser/browser.go | 3 +- internal/config/config.go | 8 +- internal/config/file.go | 43 ++++++++ internal/manip/manip.go | 38 ------- internal/{manip => utils}/utils.go | 16 ++- 8 files changed, 242 insertions(+), 76 deletions(-) create mode 100644 commands/issue_view.go create mode 100644 internal/config/file.go rename internal/{manip => utils}/utils.go (87%) diff --git a/commands/issue.go b/commands/issue.go index 177d7669..5b8c9a0b 100644 --- a/commands/issue.go +++ b/commands/issue.go @@ -3,40 +3,38 @@ package commands import ( "fmt" "github.com/gookit/color" - "os" - "strings" - "text/tabwriter" - + "github.com/gosuri/uitable" "github.com/spf13/cobra" "github.com/xanzy/go-gitlab" "glab/internal/git" "glab/internal/manip" + "strings" ) func displayAllIssues(m []*gitlab.Issue) { if len(m) > 0 { fmt.Printf("\nShowing issues %d of %d on %s\n\n", len(m), len(m), git.GetRepo()) - - // initialize tabwriter - w := new(tabwriter.Writer) - - // minwidth, tabwidth, padding, padchar, flags - w.Init(os.Stdout, 8, 8, 0, '\t', 0) - - defer w.Flush() + table := uitable.New() + table.MaxColWidth = 70 for _, issue := range m { var labels string for _, l := range issue.Labels { labels += " " + l + "," } labels = strings.Trim(labels, ", ") + if labels != ""{ + labels = "(" + labels + ")" + } + var issueID string duration := manip.TimeAgo(*issue.CreatedAt) if issue.State == "opened" { - _, _ = fmt.Fprintln(w, color.Sprintf("#%d\t%s\t(%s)\t%s", issue.IID, issue.Title, labels, duration)) + issueID = color.Sprintf("#%d", issue.IID) } else { - _, _ = fmt.Fprintln(w, color.Sprintf("#%d\t%s\t(%s)\t%s", issue.IID, issue.Title, labels, duration)) + issueID = color.Sprintf("#%d", issue.IID) } + table.AddRow(issueID, issue.Title, color.Cyan.Sprintf(labels), color.Gray.Sprintf(duration)) } + fmt.Println(table) } else { fmt.Println("No Issues available on " + git.GetRepo()) } diff --git a/commands/issue_view.go b/commands/issue_view.go new file mode 100644 index 00000000..239d7ec4 --- /dev/null +++ b/commands/issue_view.go @@ -0,0 +1,159 @@ +package commands + +import ( + "fmt" + "github.com/MakeNowJust/heredoc" + "github.com/gookit/color" + "github.com/gosuri/uitable" + "github.com/spf13/cobra" + "github.com/xanzy/go-gitlab" + "glab/internal/browser" + "glab/internal/git" + "glab/internal/manip" + "glab/internal/utils" + "log" + "strings" + "time" +) + +var issueViewCmd = &cobra.Command{ + Use: "view [flags]", + Short: `Display the title, body, and other information about an issue.`, + Long: ``, + Aliases: []string{"show"}, + Args: cobra.MaximumNArgs(3), + Run: func(cmd *cobra.Command, args []string) { + if len(args) == 0 || len(args) >1 { + cmdErr(cmd, args) + return + } + pid := manip.StringToInt(args[0]) + + gitlabClient, repo := git.InitGitlabClient() + + if r, _ := cmd.Flags().GetString("repo"); r != "" { + repo = r + } + + issue, _, err := gitlabClient.Issues.GetIssue(repo, pid) + if err != nil { + log.Fatal(err) + } + if lb, _ := cmd.Flags().GetBool("web"); lb { //open in browser if --web flag is specified + a, err := browser.Command(issue.WebURL) + if err != nil { + er(err) + } + if err:= a.Run(); err != nil { + er(err) + } + return + } + var issueState string + if issue.State == "opened" { + issueState = color.Green.Sprint(issue.State) + } else { + issueState = color.Red.Sprint(issue.State) + } + now := time.Now() + ago := now.Sub(*issue.CreatedAt) + color.Printf("\n%s #%d\n", issue.Title, issue.IID) + color.Printf("(%s) • opened by %s (%s) %s\n", issueState, + issue.Author.Username, + issue.Author.Name, + utils.PrettyTimeAgo(ago), + ) + if issue.Description != "" { + issue.Description, _ = utils.RenderMarkdown(issue.Description) + fmt.Println(issue.Description) + } + color.Printf("\n%d upvotes • %d downvotes • %d comments\n\n", + issue.Upvotes, issue.Downvotes, issue.UserNotesCount) + var labels string + for _, l := range issue.Labels { + labels += " " + l + "," + } + labels = strings.Trim(labels, ", ") + + var assignees string + for _, a := range issue.Assignees { + assignees += " " + a.Username + "(" + a.Name + ")," + } + assignees = strings.Trim(assignees, ", ") + table := uitable.New() + table.MaxColWidth = 50 + table.Wrap = true + table.AddRow("Project ID:", issue.ProjectID) + table.AddRow("Labels:", prettifyNilEmptyValues(labels, "None")) + table.AddRow("Milestone:", prettifyNilEmptyValues(issue.Milestone, "None")) + table.AddRow("Assignees:", prettifyNilEmptyValues(assignees, "None")) + table.AddRow("Due date:", prettifyNilEmptyValues(issue.DueDate, "None")) + table.AddRow("Weight:", prettifyNilEmptyValues(issue.Weight, "None")) + table.AddRow("Confidential:", prettifyNilEmptyValues(issue.Confidential, "None")) + table.AddRow("Discussion Locked:", prettifyNilEmptyValues(issue.DiscussionLocked, "false")) + table.AddRow("Subscribed:", prettifyNilEmptyValues(issue.Subscribed, "false")) + + if issue.State == "closed" { + now := time.Now() + ago := now.Sub(*issue.ClosedAt) + table.AddRow("Closed By:", + fmt.Sprintf("%s (%s) %s", issue.ClosedBy.Username, issue.ClosedBy.Name, utils.PrettyTimeAgo(ago))) + } + table.AddRow("Reference:", issue.References.Full) + table.AddRow("Web URL:", issue.WebURL) + fmt.Println(table) + fmt.Println() // Empty Space + + if c, _ := cmd.Flags().GetBool("comments"); c { //open in browser if --web flag is specified + l := &gitlab.ListIssueNotesOptions{} + notes, _, err := gitlabClient.Notes.ListIssueNotes(repo, pid, l) + if err != nil { + er(err) + } + + table := uitable.New() + table.MaxColWidth = 100 + table.Wrap = true + fmt.Println(heredoc.Doc(` + -------------------------------------------- + Comments / Notes + -------------------------------------------- + `)) + if len(notes) > 0 { + for _, note := range notes { + if note.System { + continue + } + //body, _ := utils.RenderMarkdown(note.Body) + table.AddRow(note.Author.Username+":", + fmt.Sprintf("%s\n%s", + note.Body, + color.Gray.Sprint(utils.TimeToPrettyTimeAgo(*note.CreatedAt)), + ), + ) + table.AddRow("") + } + fmt.Println(table) + } else { + fmt.Println("There are no comments on this issue") + } + } + }, +} + +func prettifyNilEmptyValues(value interface{}, defVal string) interface{} { + if value == nil || value == "" { + return defVal + } + if value == false { + return false + } + return value +} + +func init() { + issueViewCmd.Flags().StringP("repo", "r", "", "Select another repository using the OWNER/REPO format. Supports group namespaces") + issueViewCmd.Flags().BoolP("comments", "c", false, "Show issue comments and activities") + issueViewCmd.Flags().BoolP("web", "w", false, "Open issue in a browser. Uses default browser or browser specified in BROWSER variable") + issueCmd.AddCommand(issueViewCmd) +} diff --git a/commands/mr.go b/commands/mr.go index 2a14d976..c8aa2352 100644 --- a/commands/mr.go +++ b/commands/mr.go @@ -3,13 +3,11 @@ package commands import ( "fmt" "github.com/gookit/color" + "github.com/gosuri/uitable" "github.com/spf13/cobra" + "github.com/xanzy/go-gitlab" "glab/internal/git" "glab/internal/manip" - "os" - "text/tabwriter" - - "github.com/xanzy/go-gitlab" ) func displayMergeRequest(hm *gitlab.MergeRequest) { @@ -23,24 +21,21 @@ func displayMergeRequest(hm *gitlab.MergeRequest) { } func displayAllMergeRequests(m []*gitlab.MergeRequest) { - // initialize tabwriter - w := new(tabwriter.Writer) - - // minwidth, tabwidth, padding, padchar, flags - w.Init(os.Stdout, 8, 8, 0, '\t', 0) - - defer w.Flush() if len(m) > 0 { + table := uitable.New() + table.MaxColWidth = 70 fmt.Println() - fmt.Printf("Showing mergeRequests %d of %d on %s\n", len(m), len(m), git.GetRepo()) + fmt.Printf("Showing mergeRequests %d of %d on %s\n\n", len(m), len(m), git.GetRepo()) for _, mr := range m { + var mrID string if mr.State == "opened" { - _, _ = fmt.Fprintln(w, color.Sprintf("#%d\t%s\t\t(%s) ← (%s)", mr.IID, mr.Title, mr.TargetBranch, mr.SourceBranch)) + mrID = color.Sprintf("#%d", mr.IID) } else { - _, _ = fmt.Fprintln(w, color.Sprintf("#%d\t%s\t\t(%s) ← (%s)", mr.IID, mr.Title, mr.TargetBranch, mr.SourceBranch)) + mrID = color.Sprintf("#%d", mr.IID) } + table.AddRow(mrID, mr.Title, color.Sprintf("(%s) ← (%s)",mr.TargetBranch, mr.SourceBranch)) } - fmt.Println() + fmt.Println(table) } else { fmt.Println("No Merge Requests available on " + git.GetRepo()) } diff --git a/internal/browser/browser.go b/internal/browser/browser.go index 79babfcc..fc531175 100644 --- a/internal/browser/browser.go +++ b/internal/browser/browser.go @@ -7,11 +7,12 @@ import ( "strings" "github.com/google/shlex" + "glab/internal/config" ) // Command produces an exec.Cmd respecting runtime.GOOS and $BROWSER environment variable func Command(url string) (*exec.Cmd, error) { - launcher := os.Getenv("BROWSER") + launcher := config.GetEnv("BROWSER") if launcher != "" { return FromLauncher(launcher, url) } diff --git a/internal/config/config.go b/internal/config/config.go index 631902e1..228539ec 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -40,9 +40,9 @@ func GetEnv(key string) string { env := os.Getenv(key) //first get user defined variable via export if env == "" { - env = manip.GetKeyValueInFile(configFile, key) //Find variable in local env + env = GetKeyValueInFile(configFile, key) //Find variable in local env if env == "NOTFOUND" || env == "OK" { - env = manip.GetKeyValueInFile(globalConfigFile, key) //Find variable in global env + env = GetKeyValueInFile(globalConfigFile, key) //Find variable in global env if env == "NOTFOUND" || env == "OK" { //log.Fatal("Configuration not set for ", key) return "" @@ -93,8 +93,8 @@ func SetEnv(key, value string) { w := bufio.NewWriter(f) _, _ = w.WriteString(strings.Trim(newData, "\n")) _ = w.Flush() - if manip.GetKeyValueInFile(".gitignore", configFileFileParentDir) == "NOTFOUND" { - manip.ReadAndAppend(".gitignore", configFileFileParentDir) + if GetKeyValueInFile(".gitignore", configFileFileParentDir) == "NOTFOUND" { + ReadAndAppend(".gitignore", configFileFileParentDir) } } diff --git a/internal/config/file.go b/internal/config/file.go new file mode 100644 index 00000000..e52fe9d2 --- /dev/null +++ b/internal/config/file.go @@ -0,0 +1,43 @@ +package config + +import ( + "io/ioutil" + "log" + "os" + "strings" +) + +// ReadAndAppend : appends string to file +func ReadAndAppend(file, text string) { + // If the file doesn't exist, create it, or append to the file + f, err := os.OpenFile(file, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + log.Fatal(err) + } + if _, err := f.Write([]byte("\n" + text)); err != nil { + log.Fatal(err) + } + if err := f.Close(); err != nil { + log.Fatal(err) + } +} +// GetKeyValueInFile : returns env variable value +func GetKeyValueInFile(filePath, key string) string { + data, _ := ioutil.ReadFile(filePath) + + file := string(data) + line := 0 + temp := strings.Split(file, "\n") + for _, item := range temp { + //fmt.Println("[",line,"]",item) + env := strings.Split(item, "=") + if env[0] == key { + if len(env) > 1 { + return env[1] + } + return "OK" + } + line++ + } + return "NOTFOUND" +} \ No newline at end of file diff --git a/internal/manip/manip.go b/internal/manip/manip.go index 378b99bf..9d9caab3 100644 --- a/internal/manip/manip.go +++ b/internal/manip/manip.go @@ -3,10 +3,8 @@ package manip import ( "fmt" "github.com/AlecAivazis/survey/v2" - "io/ioutil" "log" "math" - "os" "regexp" "strconv" "strings" @@ -59,21 +57,6 @@ func AskQuestionMultiline(question string, defaultVal string) string { return str } -// ReadAndAppend : appends string to file -func ReadAndAppend(file, text string) { - // If the file doesn't exist, create it, or append to the file - f, err := os.OpenFile(file, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) - if err != nil { - log.Fatal(err) - } - if _, err := f.Write([]byte("\n" + text)); err != nil { - log.Fatal(err) - } - if err := f.Close(); err != nil { - log.Fatal(err) - } -} - // ReplaceNonAlphaNumericChars : Replaces non alpha-numeric values with provided char/string func ReplaceNonAlphaNumericChars(words, replaceWith string) string { reg, err := regexp.Compile("[^A-Za-z0-9]+") @@ -84,27 +67,6 @@ func ReplaceNonAlphaNumericChars(words, replaceWith string) string { return newStr } -// GetKeyValueInFile : returns env variable value -func GetKeyValueInFile(filePath, key string) string { - data, _ := ioutil.ReadFile(filePath) - - file := string(data) - line := 0 - temp := strings.Split(file, "\n") - for _, item := range temp { - //fmt.Println("[",line,"]",item) - env := strings.Split(item, "=") - if env[0] == key { - if len(env) > 1 { - return env[1] - } - return "OK" - } - line++ - } - return "NOTFOUND" -} - // CommandExists : checks if string is available in the defined commands func CommandExists(mapArr map[string]func(map[string]string, map[int]string), key string) bool { if _, ok := mapArr[key]; ok { diff --git a/internal/manip/utils.go b/internal/utils/utils.go similarity index 87% rename from internal/manip/utils.go rename to internal/utils/utils.go index 609d09a0..6e552256 100644 --- a/internal/manip/utils.go +++ b/internal/utils/utils.go @@ -1,7 +1,8 @@ -package manip +package utils import ( "fmt" + "glab/internal/config" "net/url" "strings" "time" @@ -26,9 +27,10 @@ func RenderMarkdown(text string) (string, error) { // we need to ensure that no such characters are present in the output. text = strings.ReplaceAll(text, "\r\n", "\n") - renderStyle := glamour.WithStandardStyle("notty") - - renderStyle = glamour.WithEnvironmentConfig() + renderStyle := glamour.WithStandardStyle("dark") + if config.GetEnv("GLAMOUR_STYLE") != "" { + renderStyle = glamour.WithEnvironmentConfig() + } tr, err := glamour.NewTermRenderer( renderStyle, @@ -71,6 +73,12 @@ func PrettyTimeAgo(ago time.Duration) string { return fmtDuration(int(ago.Hours()/24/365), "year") } +func TimeToPrettyTimeAgo(d time.Time) string { + now := time.Now() + ago := now.Sub(d) + return PrettyTimeAgo(ago) +} + func Humanize(s string) string { // Replaces - and _ with spaces. replace := "_-"