diff --git a/cmd/comment.go b/cmd/comment.go new file mode 100644 index 0000000..576840f --- /dev/null +++ b/cmd/comment.go @@ -0,0 +1,65 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + "strings" + + "code.gitea.io/tea/modules/interact" + + "code.gitea.io/sdk/gitea" + "code.gitea.io/tea/cmd/flags" + "code.gitea.io/tea/modules/context" + "code.gitea.io/tea/modules/print" + "code.gitea.io/tea/modules/utils" + "github.com/urfave/cli/v2" +) + +// CmdAddComment is the main command to operate with notifications +var CmdAddComment = cli.Command{ + Name: "comment", + Aliases: []string{"c"}, + Category: catEntities, + Usage: "Add a comment to an issue / pr", + Description: "Add a comment to an issue / pr", + ArgsUsage: " []", + Action: runAddComment, + Flags: flags.AllDefaultFlags, +} + +func runAddComment(cmd *cli.Context) error { + ctx := context.InitCommand(cmd) + ctx.Ensure(context.CtxRequirement{RemoteRepo: true}) + + args := ctx.Args() + if args.Len() == 0 { + return fmt.Errorf("Please specify issue / pr index") + } + + idx, err := utils.ArgToIndex(ctx.Args().First()) + if err != nil { + return err + } + + body := strings.Join(ctx.Args().Tail(), " ") + if len(body) == 0 { + if body, err = interact.PromptMultiline("Content"); err != nil { + return err + } + } + + client := ctx.Login.Client() + comment, _, err := client.CreateIssueComment(ctx.Owner, ctx.Repo, idx, gitea.CreateIssueCommentOption{ + Body: body, + }) + if err != nil { + return err + } + + print.Comment(comment) + + return nil +} diff --git a/cmd/issues.go b/cmd/issues.go index 6fbf63b..fb361a1 100644 --- a/cmd/issues.go +++ b/cmd/issues.go @@ -5,8 +5,11 @@ package cmd import ( + "fmt" + "code.gitea.io/tea/cmd/issues" "code.gitea.io/tea/modules/context" + "code.gitea.io/tea/modules/interact" "code.gitea.io/tea/modules/print" "code.gitea.io/tea/modules/utils" @@ -19,7 +22,7 @@ var CmdIssues = cli.Command{ Aliases: []string{"issue", "i"}, Category: catEntities, Usage: "List, create and update issues", - Description: "List, create and update issues", + Description: `Lists issues when called without argument. If issue index is provided, will show it in detail.`, ArgsUsage: "[]", Action: runIssues, Subcommands: []*cli.Command{ @@ -28,7 +31,12 @@ var CmdIssues = cli.Command{ &issues.CmdIssuesReopen, &issues.CmdIssuesClose, }, - Flags: issues.CmdIssuesList.Flags, + Flags: append([]cli.Flag{ + &cli.BoolFlag{ + Name: "comments", + Usage: "Wether to display comments (will prompt if not provided & run interactively)", + }, + }, issues.CmdIssuesList.Flags...), } func runIssues(ctx *cli.Context) error { @@ -51,5 +59,13 @@ func runIssueDetail(cmd *cli.Context, index string) error { return err } print.IssueDetails(issue) + + if issue.Comments > 0 { + err = interact.ShowCommentsMaybeInteractive(ctx, idx, issue.Comments) + if err != nil { + return fmt.Errorf("error loading comments: %v", err) + } + } + return nil } diff --git a/cmd/pulls.go b/cmd/pulls.go index 5977aab..b89011e 100644 --- a/cmd/pulls.go +++ b/cmd/pulls.go @@ -7,9 +7,9 @@ package cmd import ( "fmt" - "code.gitea.io/tea/cmd/flags" "code.gitea.io/tea/cmd/pulls" "code.gitea.io/tea/modules/context" + "code.gitea.io/tea/modules/interact" "code.gitea.io/tea/modules/print" "code.gitea.io/tea/modules/utils" @@ -23,10 +23,15 @@ var CmdPulls = cli.Command{ Aliases: []string{"pull", "pr"}, Category: catEntities, Usage: "Manage and checkout pull requests", - Description: `Manage and checkout pull requests`, + Description: `Lists PRs when called without argument. If PR index is provided, will show it in detail.`, ArgsUsage: "[]", Action: runPulls, - Flags: flags.IssuePRFlags, + Flags: append([]cli.Flag{ + &cli.BoolFlag{ + Name: "comments", + Usage: "Wether to display comments (will prompt if not provided & run interactively)", + }, + }, pulls.CmdPullsList.Flags...), Subcommands: []*cli.Command{ &pulls.CmdPullsList, &pulls.CmdPullsCheckout, @@ -72,5 +77,13 @@ func runPullDetail(cmd *cli.Context, index string) error { } print.PullDetails(pr, reviews, ci) + + if pr.Comments > 0 { + err = interact.ShowCommentsMaybeInteractive(ctx, idx, pr.Comments) + if err != nil { + fmt.Printf("error loading comments: %v\n", err) + } + } + return nil } diff --git a/cmd/times/list.go b/cmd/times/list.go index e89e855..93ceb6b 100644 --- a/cmd/times/list.go +++ b/cmd/times/list.go @@ -5,7 +5,6 @@ package times import ( - "fmt" "strings" "time" @@ -59,7 +58,6 @@ func RunTimesList(cmd *cli.Context) error { var err error user := ctx.Args().First() - fmt.Println(ctx.Command.ArgsUsage) if user == "" { // get all tracked times on the repo times, _, err = client.GetRepoTrackedTimes(ctx.Owner, ctx.Repo) diff --git a/main.go b/main.go index ea114e8..cdead95 100644 --- a/main.go +++ b/main.go @@ -44,6 +44,7 @@ func main() { &cmd.CmdTrackedTimes, &cmd.CmdOrgs, &cmd.CmdRepos, + &cmd.CmdAddComment, &cmd.CmdOpen, &cmd.CmdNotifications, diff --git a/modules/interact/comments.go b/modules/interact/comments.go new file mode 100644 index 0000000..6aba46e --- /dev/null +++ b/modules/interact/comments.go @@ -0,0 +1,75 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package interact + +import ( + "fmt" + "os" + + "code.gitea.io/sdk/gitea" + "code.gitea.io/tea/modules/context" + "code.gitea.io/tea/modules/print" + "github.com/AlecAivazis/survey/v2" + "golang.org/x/crypto/ssh/terminal" +) + +// ShowCommentsMaybeInteractive fetches & prints comments, depending on the --comments flag. +// If that flag is unset, and output is not piped, prompts the user first. +func ShowCommentsMaybeInteractive(ctx *context.TeaContext, idx int64, totalComments int) error { + if ctx.Bool("comments") { + opts := gitea.ListIssueCommentOptions{ListOptions: ctx.GetListOptions()} + c := ctx.Login.Client() + comments, _, err := c.ListIssueComments(ctx.Owner, ctx.Repo, idx, opts) + if err != nil { + return err + } + print.Comments(comments) + } else if isInteractive() && !ctx.IsSet("comments") { + // if we're interactive, but --comments hasn't been explicitly set to false + if err := ShowCommentsPaginated(ctx, idx, totalComments); err != nil { + fmt.Printf("error while loading comments: %v\n", err) + } + } + return nil +} + +// ShowCommentsPaginated prompts if issue/pr comments should be shown and continues to do so. +func ShowCommentsPaginated(ctx *context.TeaContext, idx int64, totalComments int) error { + c := ctx.Login.Client() + opts := gitea.ListIssueCommentOptions{ListOptions: ctx.GetListOptions()} + prompt := "show comments?" + commentsLoaded := 0 + + // paginated fetch + // NOTE: as of gitea 1.13, pagination is not provided by this endpoint, but handles + // this function gracefully anyways. + for { + loadComments := false + confirm := survey.Confirm{Message: prompt, Default: true} + if err := survey.AskOne(&confirm, &loadComments); err != nil { + return err + } else if !loadComments { + break + } else { + if comments, _, err := c.ListIssueComments(ctx.Owner, ctx.Repo, idx, opts); err != nil { + return err + } else if len(comments) != 0 { + print.Comments(comments) + commentsLoaded += len(comments) + } + if commentsLoaded >= totalComments { + break + } + opts.ListOptions.Page++ + prompt = "load more?" + } + } + return nil +} + +// IsInteractive checks if the output is piped, but NOT if the session is run interactively.. +func isInteractive() bool { + return terminal.IsTerminal(int(os.Stdout.Fd())) +} diff --git a/modules/interact/prompts.go b/modules/interact/prompts.go index 89c22b6..32566d4 100644 --- a/modules/interact/prompts.go +++ b/modules/interact/prompts.go @@ -11,6 +11,12 @@ import ( "github.com/AlecAivazis/survey/v2" ) +// PromptMultiline runs a textfield-style prompt and blocks until input was made. +func PromptMultiline(message string) (content string, err error) { + err = survey.AskOne(&survey.Multiline{Message: message}, &content) + return +} + // PromptPassword asks for a password and blocks until input was made. func PromptPassword(name string) (pass string, err error) { promptPW := &survey.Password{Message: name + " password:"} diff --git a/modules/print/comment.go b/modules/print/comment.go new file mode 100644 index 0000000..f3d710e --- /dev/null +++ b/modules/print/comment.go @@ -0,0 +1,44 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package print + +import ( + "fmt" + "strings" + + "code.gitea.io/sdk/gitea" +) + +// Comments renders a list of comments to stdout +func Comments(comments []*gitea.Comment) { + var out = make([]string, len(comments)) + for i, c := range comments { + out[i] = formatComment(c) + } + outputMarkdown(fmt.Sprintf( + // this will become a heading by means of the first --- from a comment + "Comments\n%s", + strings.Join(out, "\n"), + )) +} + +// Comment renders a comment to stdout +func Comment(c *gitea.Comment) { + outputMarkdown(formatComment(c)) +} + +func formatComment(c *gitea.Comment) string { + edited := "" + if c.Updated.After(c.Created) { + edited = fmt.Sprintf(" *(edited on %s)*", FormatTime(c.Updated)) + } + return fmt.Sprintf( + "---\n\n**@%s** wrote on %s%s:\n\n%s\n", + c.Poster.UserName, + FormatTime(c.Created), + edited, + c.Body, + ) +}