diff --git a/api/queries_issue.go b/api/queries_issue.go index 43faa7321..39a998199 100644 --- a/api/queries_issue.go +++ b/api/queries_issue.go @@ -16,10 +16,17 @@ type IssuesAndTotalCount struct { } type Issue struct { - Number int - Title string - URL string - State string + Number int + Title string + URL string + State string + Body string + Comments struct { + TotalCount int + } + Author struct { + Login string + } Labels struct { Nodes []IssueLabel @@ -234,6 +241,19 @@ func IssueByNumber(client *Client, ghRepo Repo, number int) (*Issue, error) { query($owner: String!, $repo: String!, $issue_number: Int!) { repository(owner: $owner, name: $repo) { issue(number: $issue_number) { + title + body + author { + login + } + comments { + totalCount + } + labels(first: 3) { + nodes { + name + } + } number url } diff --git a/api/queries_pr.go b/api/queries_pr.go index 22d0ec3fd..9603bf0f2 100644 --- a/api/queries_pr.go +++ b/api/queries_pr.go @@ -21,8 +21,13 @@ type PullRequest struct { Title string State string URL string + BaseRefName string HeadRefName string + Body string + Author struct { + Login string + } HeadRepositoryOwner struct { Login string } @@ -38,7 +43,8 @@ type PullRequest struct { ReviewDecision string Commits struct { - Nodes []struct { + TotalCount int + Nodes []struct { Commit struct { StatusCheckRollup struct { Contexts struct { @@ -296,6 +302,15 @@ func PullRequestByNumber(client *Client, ghRepo Repo, number int) (*PullRequest, pullRequest(number: $pr_number) { url number + title + body + author { + login + } + commits { + totalCount + } + baseRefName headRefName headRepositoryOwner { login @@ -343,7 +358,15 @@ func PullRequestForBranch(client *Client, ghRepo Repo, branch string) (*PullRequ nodes { number title + body + author { + login + } + commits { + totalCount + } url + baseRefName headRefName headRepositoryOwner { login diff --git a/command/issue.go b/command/issue.go index 89a8465fe..6a5820309 100644 --- a/command/issue.go +++ b/command/issue.go @@ -34,6 +34,8 @@ func init() { issueListCmd.Flags().StringSliceP("label", "l", nil, "Filter by label") issueListCmd.Flags().StringP("state", "s", "", "Filter by state: {open|closed|all}") issueListCmd.Flags().IntP("limit", "L", 30, "Maximum number of issues to fetch") + + issueViewCmd.Flags().BoolP("preview", "p", false, "Preview PR in terminal") } var issueCmd = &cobra.Command{ @@ -217,8 +219,39 @@ func issueView(cmd *cobra.Command, args []string) error { } openURL := issue.URL - fmt.Fprintf(cmd.ErrOrStderr(), "Opening %s in your browser.\n", openURL) - return utils.OpenInBrowser(openURL) + preview, err := cmd.Flags().GetBool("preview") + if err != nil { + return err + } + + if preview { + out := colorableOut(cmd) + printIssuePreview(out, issue) + return nil + } else { + fmt.Fprintf(cmd.ErrOrStderr(), "Opening %s in your browser.\n", openURL) + return utils.OpenInBrowser(openURL) + } + +} + +func printIssuePreview(out io.Writer, issue *api.Issue) { + coloredLabels := labelList(*issue) + if coloredLabels != "" { + coloredLabels = utils.Gray(fmt.Sprintf("(%s)", coloredLabels)) + } + + fmt.Fprintln(out, utils.Bold(issue.Title)) + fmt.Fprintln(out, utils.Gray(fmt.Sprintf( + "opened by %s. %s. %s", + issue.Author.Login, + utils.Pluralize(issue.Comments.TotalCount, "comment"), + coloredLabels, + ))) + fmt.Fprintln(out) + fmt.Fprintln(out, utils.RenderMarkdown(issue.Body)) + fmt.Fprintln(out) + fmt.Fprintf(out, utils.Gray("View this issue on GitHub: %s\n"), issue.URL) } var issueURLRE = regexp.MustCompile(`^https://github\.com/([^/]+)/([^/]+)/issues/(\d+)`) diff --git a/command/issue_test.go b/command/issue_test.go index 78189e4c7..e9f616f68 100644 --- a/command/issue_test.go +++ b/command/issue_test.go @@ -231,6 +231,51 @@ func TestIssueView(t *testing.T) { eq(t, url, "https://github.com/OWNER/REPO/issues/123") } +func TestIssueView_preview(t *testing.T) { + initBlankContext("OWNER/REPO", "master") + http := initFakeHTTP() + + http.StubResponse(200, bytes.NewBufferString(` + { "data": { "repository": { "issue": { + "number": 123, + "body": "**bold story**", + "title": "ix of coins", + "author": { + "login": "marseilles" + }, + "labels": { + "nodes": [ + {"name": "tarot"} + ] + }, + "comments": { + "totalCount": 9 + }, + "url": "https://github.com/OWNER/REPO/issues/123" + } } } } + `)) + + output, err := RunCommand(issueViewCmd, "issue view -p 123") + if err != nil { + t.Errorf("error running command `issue view`: %v", err) + } + + eq(t, output.Stderr(), "") + + expectedLines := []*regexp.Regexp{ + regexp.MustCompile(`ix of coins`), + regexp.MustCompile(`opened by marseilles. 9 comments. \(tarot\)`), + regexp.MustCompile(`bold story`), + regexp.MustCompile(`View this issue on GitHub: https://github.com/OWNER/REPO/issues/123`), + } + for _, r := range expectedLines { + if !r.MatchString(output.String()) { + t.Errorf("output did not match regexp /%s/\n> output\n%s\n", r, output) + return + } + } +} + func TestIssueView_notFound(t *testing.T) { initBlankContext("OWNER/REPO", "master") http := initFakeHTTP() diff --git a/command/pr.go b/command/pr.go index cdf5dd6bc..328ebb134 100644 --- a/command/pr.go +++ b/command/pr.go @@ -31,6 +31,8 @@ func init() { prListCmd.Flags().StringP("base", "B", "", "Filter by base branch") prListCmd.Flags().StringSliceP("label", "l", nil, "Filter by label") prListCmd.Flags().StringP("assignee", "a", "", "Filter by assignee") + + prViewCmd.Flags().BoolP("preview", "p", false, "Preview PR in terminal") } var prCmd = &cobra.Command{ @@ -253,9 +255,15 @@ func prView(cmd *cobra.Command, args []string) error { return err } + preview, err := cmd.Flags().GetBool("preview") + if err != nil { + return err + } + var openURL string + var pr *api.PullRequest if len(args) > 0 { - pr, err := prFromArg(apiClient, baseRepo, args[0]) + pr, err = prFromArg(apiClient, baseRepo, args[0]) if err != nil { return err } @@ -268,8 +276,14 @@ func prView(cmd *cobra.Command, args []string) error { if prNumber > 0 { openURL = fmt.Sprintf("https://github.com/%s/%s/pull/%d", baseRepo.RepoOwner(), baseRepo.RepoName(), prNumber) + if preview { + pr, err = api.PullRequestByNumber(apiClient, baseRepo, prNumber) + if err != nil { + return err + } + } } else { - pr, err := api.PullRequestForBranch(apiClient, baseRepo, branchWithOwner) + pr, err = api.PullRequestForBranch(apiClient, baseRepo, branchWithOwner) if err != nil { var notFoundErr *api.NotFoundError if errors.As(err, ¬FoundErr) { @@ -282,8 +296,29 @@ func prView(cmd *cobra.Command, args []string) error { } } - fmt.Fprintf(cmd.ErrOrStderr(), "Opening %s in your browser.\n", openURL) - return utils.OpenInBrowser(openURL) + if preview { + out := colorableOut(cmd) + printPrPreview(out, pr) + return nil + } else { + fmt.Fprintf(cmd.ErrOrStderr(), "Opening %s in your browser.\n", openURL) + return utils.OpenInBrowser(openURL) + } +} + +func printPrPreview(out io.Writer, pr *api.PullRequest) { + fmt.Fprintln(out, utils.Bold(pr.Title)) + fmt.Fprintln(out, utils.Gray(fmt.Sprintf( + "%s wants to merge %s into %s from %s", + pr.Author.Login, + utils.Pluralize(pr.Commits.TotalCount, "commit"), + pr.BaseRefName, + pr.HeadRefName, + ))) + fmt.Fprintln(out) + fmt.Fprintln(out, utils.RenderMarkdown(pr.Body)) + fmt.Fprintln(out) + fmt.Fprintf(out, utils.Gray("View this PR on GitHub: %s\n"), pr.URL) } var prURLRE = regexp.MustCompile(`^https://github\.com/([^/]+)/([^/]+)/pull/(\d+)`) diff --git a/command/pr_test.go b/command/pr_test.go index 360b6d852..f6bc733b1 100644 --- a/command/pr_test.go +++ b/command/pr_test.go @@ -238,6 +238,69 @@ func TestPRList_filteringAssigneeLabels(t *testing.T) { } } +func TestPRView_preview(t *testing.T) { + initBlankContext("OWNER/REPO", "master") + http := initFakeHTTP() + + jsonFile, _ := os.Open("../test/fixtures/prViewPreview.json") + defer jsonFile.Close() + http.StubResponse(200, jsonFile) + + output, err := RunCommand(prViewCmd, "pr view -p 12") + if err != nil { + t.Errorf("error running command `pr view`: %v", err) + } + + eq(t, output.Stderr(), "") + + expectedLines := []*regexp.Regexp{ + regexp.MustCompile(`Blueberries are from a fork`), + regexp.MustCompile(`nobody wants to merge 12 commits into master from blueberries`), + regexp.MustCompile(`blueberries taste good`), + regexp.MustCompile(`View this PR on GitHub: https://github.com/OWNER/REPO/pull/12`), + } + for _, r := range expectedLines { + if !r.MatchString(output.String()) { + t.Errorf("output did not match regexp /%s/\n> output\n%s\n", r, output) + return + } + } +} + +func TestPRView_previewCurrentBranch(t *testing.T) { + initBlankContext("OWNER/REPO", "blueberries") + http := initFakeHTTP() + + jsonFile, _ := os.Open("../test/fixtures/prView.json") + defer jsonFile.Close() + http.StubResponse(200, jsonFile) + + restoreCmd := utils.SetPrepareCmd(func(cmd *exec.Cmd) utils.Runnable { + return &outputStub{} + }) + defer restoreCmd() + + output, err := RunCommand(prViewCmd, "pr view -p") + if err != nil { + t.Errorf("error running command `pr view`: %v", err) + } + + eq(t, output.Stderr(), "") + + expectedLines := []*regexp.Regexp{ + regexp.MustCompile(`Blueberries are a good fruit`), + regexp.MustCompile(`nobody wants to merge 8 commits into master from blueberries`), + regexp.MustCompile(`blueberries taste good`), + regexp.MustCompile(`View this PR on GitHub: https://github.com/OWNER/REPO/pull/10`), + } + for _, r := range expectedLines { + if !r.MatchString(output.String()) { + t.Errorf("output did not match regexp /%s/\n> output\n%s\n", r, output) + return + } + } +} + func TestPRView_currentBranch(t *testing.T) { initBlankContext("OWNER/REPO", "blueberries") http := initFakeHTTP() diff --git a/go.mod b/go.mod index 361ad8acf..df49a1664 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/pkg/errors v0.8.1 github.com/spf13/cobra v0.0.5 github.com/spf13/pflag v1.0.5 - github.com/stretchr/testify v1.3.0 // indirect + github.com/vilmibm/go-termd v0.0.4 golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5 gopkg.in/yaml.v3 v3.0.0-20191010095647-fc94e3f71652 ) diff --git a/go.sum b/go.sum index 9bc2c3c17..79806902b 100644 --- a/go.sum +++ b/go.sum @@ -2,34 +2,65 @@ github.com/AlecAivazis/survey/v2 v2.0.4 h1:qzXnJSzXEvmUllWqMBWpZndvT2YfoAUzAMvZU github.com/AlecAivazis/survey/v2 v2.0.4/go.mod h1:WYBhg6f0y/fNYUuesWQc0PKbJcEliGcYHB9sNT3Bg74= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0= +github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0= github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw= github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc= +github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= +github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U= +github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI= +github.com/alecthomas/chroma v0.6.8 h1:TW4JJaIdbAbMyUtGEd6BukFlOKYvVQz3vVhLBEUNwMU= +github.com/alecthomas/chroma v0.6.8/go.mod h1:o9ohftueRi7H5be3+Q2cQCNa/YnLBFUNx40ZJfGVFKA= +github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 h1:JHZL0hZKJ1VENNfmXvHbgYlbUOvpzYzvy2aZU5gXVeo= +github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0= +github.com/alecthomas/kong v0.1.17-0.20190424132513-439c674f7ae0/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI= +github.com/alecthomas/kong v0.2.1-0.20190708041108-0548c6b1afae/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI= +github.com/alecthomas/kong-hcl v0.1.8-0.20190615233001-b21fea9723c8/go.mod h1:MRgZdU3vrFd05IQ89AxUZ0aYdF39BYoNFa324SodPCA= +github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897 h1:p9Sln00KOTlrYkxI1zYWl1QLnEqAqEARBEYa8FQnQcY= +github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59 h1:WWB576BN5zNSZc/M9d/10pqEx5VHNhaQ/yOVAkmj5Yo= +github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E= +github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ= +github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dlclark/regexp2 v1.1.6 h1:CqB4MjHw0MFCDj+PHHjiESmHX+N7t0tJzKvC6M97BRg= +github.com/dlclark/regexp2 v1.1.6/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E= -github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= +github.com/gorilla/csrf v1.6.0/go.mod h1:7tSf8kmjNYr7IWDCYhd3U8Ck34iQ/Yw5CJu7bAkHEGI= +github.com/gorilla/handlers v1.4.1/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ= github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.4 h1:5Myjjh3JY/NaAi4IsUbHADytDyl1VE1Y9PXDlL+P/VQ= github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= @@ -37,14 +68,24 @@ github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1f github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= @@ -60,7 +101,17 @@ github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/tj/assert v0.0.0-20190920132354-ee03d75cd160 h1:NSWpaDaurcAJY7PkL8Xt0PhZE7qpvbZl5ljd8r6U0bI= +github.com/tj/assert v0.0.0-20190920132354-ee03d75cd160/go.mod h1:mZ9/Rh9oLWpLLDRpvE+3b7gP/C2YyLFYxNmcLnPTMe0= +github.com/tj/go-css v0.0.0-20191108133013-220a796d1705 h1:+UA89aFRjPMqdccHd9A0HLNCRDXIoElaDoW2C1V3TzA= +github.com/tj/go-css v0.0.0-20191108133013-220a796d1705/go.mod h1:e+JPLQ9wyQCgRnPenX2bo7MJoLphBHz5c1WUqaANSeA= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= +github.com/vilmibm/go-termd v0.0.4 h1:uCmDUZ3qZUblTN/D5Hvl+g1rTJj/HW746JQFWidqAyk= +github.com/vilmibm/go-termd v0.0.4/go.mod h1:ys+dRO6wlM3el0vPJmYBkhOPPozViBgDXHOEn1x5Vsc= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -68,6 +119,7 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5 h1:8dUaAV7K4uHsF56JQWkprecIQKdPHtR9jCHF5nB8uzc= golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= diff --git a/test/fixtures/prView.json b/test/fixtures/prView.json index c8ae23ce6..02277d7a5 100644 --- a/test/fixtures/prView.json +++ b/test/fixtures/prView.json @@ -6,21 +6,37 @@ { "number": 12, "title": "Blueberries are from a fork", + "body": "yeah", "url": "https://github.com/OWNER/REPO/pull/12", "headRefName": "blueberries", + "baseRefName": "master", "headRepositoryOwner": { "login": "hubot" }, + "commits": { + "totalCount": 12 + }, + "author": { + "login": "nobody" + }, "isCrossRepository": true }, { "number": 10, "title": "Blueberries are a good fruit", + "body": "**blueberries taste good**", "url": "https://github.com/OWNER/REPO/pull/10", + "baseRefName": "master", "headRefName": "blueberries", + "author": { + "login": "nobody" + }, "headRepositoryOwner": { "login": "OWNER" }, + "commits": { + "totalCount": 8 + }, "isCrossRepository": false } ] diff --git a/test/fixtures/prViewPreview.json b/test/fixtures/prViewPreview.json new file mode 100644 index 000000000..e40946774 --- /dev/null +++ b/test/fixtures/prViewPreview.json @@ -0,0 +1,24 @@ +{ + "data": { + "repository": { + "pullRequest": { + "number": 12, + "title": "Blueberries are from a fork", + "body": "**blueberries taste good**", + "url": "https://github.com/OWNER/REPO/pull/12", + "author": { + "login": "nobody" + }, + "commits": { + "totalCount": 12 + }, + "baseRefName": "master", + "headRefName": "blueberries", + "headRepositoryOwner": { + "login": "hubot" + }, + "isCrossRepository": true + } + } + } +} diff --git a/utils/utils.go b/utils/utils.go index 6a1730cb8..2c8c5ac0a 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -1,12 +1,15 @@ package utils import ( + "bytes" "errors" + "fmt" "os" "os/exec" "runtime" "github.com/kballard/go-shellquote" + md "github.com/vilmibm/go-termd" ) func OpenInBrowser(url string) error { @@ -51,3 +54,39 @@ func searchBrowserLauncher(goos string) (browser string) { return browser } + +func normalizeNewlines(d []byte) []byte { + d = bytes.Replace(d, []byte("\r\n"), []byte("\n"), -1) + d = bytes.Replace(d, []byte("\r"), []byte("\n"), -1) + return d +} + +func RenderMarkdown(text string) string { + textB := []byte(text) + textB = normalizeNewlines(textB) + mdCompiler := md.Compiler{ + Columns: 100, + SyntaxHighlighter: md.SyntaxTheme{ + "keyword": md.Style{Color: "#9196ed"}, + "comment": md.Style{ + Color: "#c0c0c2", + }, + "literal": md.Style{ + Color: "#aaedf7", + }, + "name": md.Style{ + Color: "#fe8eb5", + }, + }, + } + + return mdCompiler.Compile(string(textB)) +} + +func Pluralize(num int, thing string) string { + if num == 1 { + return fmt.Sprintf("%d %s", num, thing) + } else { + return fmt.Sprintf("%d %ss", num, thing) + } +}