Migrate to go-gh text package (#6236)
This commit is contained in:
parent
e16bf033ed
commit
e7102f9d84
60 changed files with 371 additions and 822 deletions
|
|
@ -20,15 +20,16 @@ import (
|
|||
"github.com/cli/cli/v2/internal/config"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/internal/run"
|
||||
"github.com/cli/cli/v2/internal/text"
|
||||
"github.com/cli/cli/v2/internal/update"
|
||||
"github.com/cli/cli/v2/pkg/cmd/alias/expand"
|
||||
"github.com/cli/cli/v2/pkg/cmd/factory"
|
||||
"github.com/cli/cli/v2/pkg/cmd/root"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/pkg/text"
|
||||
"github.com/cli/cli/v2/utils"
|
||||
"github.com/cli/safeexec"
|
||||
"github.com/mattn/go-isatty"
|
||||
"github.com/mgutz/ansi"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
|
@ -336,7 +337,11 @@ func shouldCheckForUpdate() bool {
|
|||
if os.Getenv("CODESPACES") != "" {
|
||||
return false
|
||||
}
|
||||
return updaterEnabled != "" && !isCI() && utils.IsTerminal(os.Stdout) && utils.IsTerminal(os.Stderr)
|
||||
return updaterEnabled != "" && !isCI() && isTerminal(os.Stdout) && isTerminal(os.Stderr)
|
||||
}
|
||||
|
||||
func isTerminal(f *os.File) bool {
|
||||
return isatty.IsTerminal(f.Fd()) || isatty.IsCygwinTerminal(f.Fd())
|
||||
}
|
||||
|
||||
// based on https://github.com/watson/ci-info/blob/HEAD/index.js
|
||||
|
|
|
|||
4
go.mod
4
go.mod
|
|
@ -8,7 +8,7 @@ require (
|
|||
github.com/briandowns/spinner v1.18.1
|
||||
github.com/charmbracelet/glamour v0.5.0
|
||||
github.com/charmbracelet/lipgloss v0.5.0
|
||||
github.com/cli/go-gh v0.1.1-0.20220817122932-3630ab390fe7
|
||||
github.com/cli/go-gh v0.1.1-0.20220913125123-04019861008e
|
||||
github.com/cli/oauth v0.9.0
|
||||
github.com/cli/safeexec v1.0.0
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2
|
||||
|
|
@ -25,7 +25,6 @@ require (
|
|||
github.com/mattn/go-colorable v0.1.13
|
||||
github.com/mattn/go-isatty v0.0.16
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d
|
||||
github.com/muesli/reflow v0.3.0
|
||||
github.com/muesli/termenv v0.12.0
|
||||
github.com/muhammadmuzzammil1998/jsonc v0.0.0-20201229145248-615b0916ca38
|
||||
github.com/opentracing/opentracing-go v1.1.0
|
||||
|
|
@ -59,6 +58,7 @@ require (
|
|||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||
github.com/microcosm-cc/bluemonday v1.0.19 // indirect
|
||||
github.com/muesli/reflow v0.3.0 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
|
|
|
|||
4
go.sum
4
go.sum
|
|
@ -58,8 +58,8 @@ github.com/cli/browser v1.1.0 h1:xOZBfkfY9L9vMBgqb1YwRirGu6QFaQ5dP/vXt5ENSOY=
|
|||
github.com/cli/browser v1.1.0/go.mod h1:HKMQAt9t12kov91Mn7RfZxyJQQgWgyS/3SZswlZ5iTI=
|
||||
github.com/cli/crypto v0.0.0-20210929142629-6be313f59b03 h1:3f4uHLfWx4/WlnMPXGai03eoWAI+oGHJwr+5OXfxCr8=
|
||||
github.com/cli/crypto v0.0.0-20210929142629-6be313f59b03/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
github.com/cli/go-gh v0.1.1-0.20220817122932-3630ab390fe7 h1:yLnT5/sLlUcnr5njBRmf/d7pEKr0Cu2TpeecdlQl8AY=
|
||||
github.com/cli/go-gh v0.1.1-0.20220817122932-3630ab390fe7/go.mod h1:UKRuMl3ZaitTvO4LPWj5bVw7QwZHnLu0S0lI9WWbdpc=
|
||||
github.com/cli/go-gh v0.1.1-0.20220913125123-04019861008e h1:zK2hqxSk5D/Jt4o+0NVH/qdEFh7fUhgGkhbukwPMzQU=
|
||||
github.com/cli/go-gh v0.1.1-0.20220913125123-04019861008e/go.mod h1:UKRuMl3ZaitTvO4LPWj5bVw7QwZHnLu0S0lI9WWbdpc=
|
||||
github.com/cli/oauth v0.9.0 h1:nxBC0Df4tUzMkqffAB+uZvisOwT3/N9FpkfdTDtafxc=
|
||||
github.com/cli/oauth v0.9.0/go.mod h1:qd/FX8ZBD6n1sVNQO3aIdRxeu5LGw9WhKnYhIIoC2A4=
|
||||
github.com/cli/safeexec v1.0.0 h1:0VngyaIyqACHdcMNWfo6+KdUYnqEr2Sg+bSP1pdF+dI=
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/cli/cli/v2/internal/codespaces/api"
|
||||
"github.com/cli/cli/v2/internal/text"
|
||||
"github.com/cli/cli/v2/pkg/liveshare"
|
||||
"github.com/cli/cli/v2/pkg/text"
|
||||
)
|
||||
|
||||
// PostCreateStateStatus is a string value representing the different statuses a state can have.
|
||||
|
|
|
|||
74
internal/text/text.go
Normal file
74
internal/text/text.go
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
package text
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cli/go-gh/pkg/text"
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
var whitespaceRE = regexp.MustCompile(`\s+`)
|
||||
|
||||
func Indent(s, indent string) string {
|
||||
return text.Indent(s, indent)
|
||||
}
|
||||
|
||||
// Title returns a copy of the string s with all Unicode letters that begin words mapped to their Unicode title case.
|
||||
func Title(s string) string {
|
||||
c := cases.Title(language.English)
|
||||
return c.String(s)
|
||||
}
|
||||
|
||||
// RemoveExcessiveWhitespace returns a copy of the string s with excessive whitespace removed.
|
||||
func RemoveExcessiveWhitespace(s string) string {
|
||||
return whitespaceRE.ReplaceAllString(strings.TrimSpace(s), " ")
|
||||
}
|
||||
|
||||
func DisplayWidth(s string) int {
|
||||
return text.DisplayWidth(s)
|
||||
}
|
||||
|
||||
func Truncate(maxWidth int, s string) string {
|
||||
return text.Truncate(maxWidth, s)
|
||||
}
|
||||
|
||||
func Pluralize(num int, thing string) string {
|
||||
return text.Pluralize(num, thing)
|
||||
}
|
||||
|
||||
func FuzzyAgo(a, b time.Time) string {
|
||||
return text.RelativeTimeAgo(a, b)
|
||||
}
|
||||
|
||||
// FuzzyAgoAbbr is an abbreviated version of FuzzyAgo. It returns a human readable string of the
|
||||
// time duration between a and b that is estimated to the nearest unit of time.
|
||||
func FuzzyAgoAbbr(a, b time.Time) string {
|
||||
ago := a.Sub(b)
|
||||
|
||||
if ago < time.Hour {
|
||||
return fmt.Sprintf("%d%s", int(ago.Minutes()), "m")
|
||||
}
|
||||
if ago < 24*time.Hour {
|
||||
return fmt.Sprintf("%d%s", int(ago.Hours()), "h")
|
||||
}
|
||||
if ago < 30*24*time.Hour {
|
||||
return fmt.Sprintf("%d%s", int(ago.Hours())/24, "d")
|
||||
}
|
||||
|
||||
return b.Format("Jan _2, 2006")
|
||||
}
|
||||
|
||||
// DisplayURL returns a copy of the string urlStr removing everything except the hostname and path.
|
||||
// If there is an error parsing urlStr then urlStr is returned without modification.
|
||||
func DisplayURL(urlStr string) string {
|
||||
u, err := url.Parse(urlStr)
|
||||
if err != nil {
|
||||
return urlStr
|
||||
}
|
||||
return u.Hostname() + u.Path
|
||||
}
|
||||
56
internal/text/text_test.go
Normal file
56
internal/text/text_test.go
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
package text
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRemoveExcessiveWhitespace(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "nothing to remove",
|
||||
input: "one two three",
|
||||
want: "one two three",
|
||||
},
|
||||
{
|
||||
name: "whitespace b-gone",
|
||||
input: "\n one\n\t two three\r\n ",
|
||||
want: "one two three",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := RemoveExcessiveWhitespace(tt.input)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFuzzyAgoAbbr(t *testing.T) {
|
||||
const form = "2006-Jan-02 15:04:05"
|
||||
now, _ := time.Parse(form, "2020-Nov-22 14:00:00")
|
||||
cases := map[string]string{
|
||||
"2020-Nov-22 14:00:00": "0m",
|
||||
"2020-Nov-22 13:59:00": "1m",
|
||||
"2020-Nov-22 13:30:00": "30m",
|
||||
"2020-Nov-22 13:00:00": "1h",
|
||||
"2020-Nov-22 02:00:00": "12h",
|
||||
"2020-Nov-21 14:00:00": "1d",
|
||||
"2020-Nov-07 14:00:00": "15d",
|
||||
"2020-Oct-24 14:00:00": "29d",
|
||||
"2020-Oct-23 14:00:00": "Oct 23, 2020",
|
||||
"2019-Nov-22 14:00:00": "Nov 22, 2019",
|
||||
}
|
||||
for createdAt, expected := range cases {
|
||||
d, err := time.Parse(form, createdAt)
|
||||
assert.NoError(t, err)
|
||||
fuzzy := FuzzyAgoAbbr(now, d)
|
||||
assert.Equal(t, expected, fuzzy)
|
||||
}
|
||||
}
|
||||
|
|
@ -15,9 +15,9 @@ import (
|
|||
"github.com/cli/cli/v2/git"
|
||||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/internal/text"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/utils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
|
@ -148,7 +148,7 @@ func runBrowse(opts *BrowseOptions) error {
|
|||
}
|
||||
|
||||
if opts.IO.IsStdoutTTY() {
|
||||
fmt.Fprintf(opts.IO.Out, "Opening %s in your browser.\n", utils.DisplayURL(url))
|
||||
fmt.Fprintf(opts.IO.Out, "Opening %s in your browser.\n", text.DisplayURL(url))
|
||||
}
|
||||
return opts.Browser.Browse(url)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ import (
|
|||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/cli/cli/v2/internal/codespaces"
|
||||
"github.com/cli/cli/v2/internal/codespaces/api"
|
||||
"github.com/cli/cli/v2/internal/text"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/utils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
|
@ -192,12 +192,12 @@ func (a *App) Create(ctx context.Context, opts createOptions) error {
|
|||
if len(devcontainers) > 0 {
|
||||
|
||||
// if there is only one devcontainer.json file and it is one of the default paths we can auto-select it
|
||||
if len(devcontainers) == 1 && utils.StringInSlice(devcontainers[0].Path, DEFAULT_DEVCONTAINER_DEFINITIONS) {
|
||||
if len(devcontainers) == 1 && stringInSlice(devcontainers[0].Path, DEFAULT_DEVCONTAINER_DEFINITIONS) {
|
||||
devContainerPath = devcontainers[0].Path
|
||||
} else {
|
||||
promptOptions := []string{}
|
||||
|
||||
if !utils.StringInSlice(devcontainers[0].Path, DEFAULT_DEVCONTAINER_DEFINITIONS) {
|
||||
if !stringInSlice(devcontainers[0].Path, DEFAULT_DEVCONTAINER_DEFINITIONS) {
|
||||
promptOptions = []string{DEVCONTAINER_PROMPT_DEFAULT}
|
||||
}
|
||||
|
||||
|
|
@ -284,7 +284,7 @@ func (a *App) handleAdditionalPermissions(ctx context.Context, createParams *api
|
|||
var (
|
||||
isInteractive = a.io.CanPrompt()
|
||||
cs = a.io.ColorScheme()
|
||||
displayURL = utils.DisplayURL(allowPermissionsURL)
|
||||
displayURL = text.DisplayURL(allowPermissionsURL)
|
||||
)
|
||||
|
||||
fmt.Fprintf(a.io.ErrOut, "You must authorize or deny additional permissions requested by this codespace before continuing.\n")
|
||||
|
|
@ -498,3 +498,12 @@ func buildDisplayName(displayName string, prebuildAvailability string) string {
|
|||
return displayName
|
||||
}
|
||||
}
|
||||
|
||||
func stringInSlice(a string, slice []string) bool {
|
||||
for _, b := range slice {
|
||||
if b == a {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/v2/internal/codespaces/api"
|
||||
"github.com/cli/cli/v2/internal/text"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/utils"
|
||||
"github.com/spf13/cobra"
|
||||
|
|
@ -144,7 +145,7 @@ func (a *App) List(ctx context.Context, opts *listOptions, exporter cmdutil.Expo
|
|||
if err != nil {
|
||||
return fmt.Errorf("error parsing date %q: %w", c.CreatedAt, err)
|
||||
}
|
||||
tp.AddField(utils.FuzzyAgoAbbr(time.Now(), ct), nil, cs.Gray)
|
||||
tp.AddField(text.FuzzyAgoAbbr(time.Now(), ct), nil, cs.Gray)
|
||||
} else {
|
||||
tp.AddField(c.CreatedAt, nil, nil)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,10 +18,10 @@ import (
|
|||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
"github.com/cli/cli/v2/internal/ghinstance"
|
||||
"github.com/cli/cli/v2/internal/text"
|
||||
"github.com/cli/cli/v2/pkg/cmd/gist/shared"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/utils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
|
@ -164,7 +164,7 @@ func createRun(opts *CreateOptions) error {
|
|||
fmt.Fprintf(errOut, "%s %s\n", cs.SuccessIconWithColor(cs.Green), completionMessage)
|
||||
|
||||
if opts.WebMode {
|
||||
fmt.Fprintf(opts.IO.Out, "Opening %s in your browser.\n", utils.DisplayURL(gist.HTMLURL))
|
||||
fmt.Fprintf(opts.IO.Out, "Opening %s in your browser.\n", text.DisplayURL(gist.HTMLURL))
|
||||
|
||||
return opts.Browser.Browse(gist.HTMLURL)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,10 +7,10 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
"github.com/cli/cli/v2/internal/text"
|
||||
"github.com/cli/cli/v2/pkg/cmd/gist/shared"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/pkg/text"
|
||||
"github.com/cli/cli/v2/utils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
|
@ -119,12 +119,12 @@ func listRun(opts *ListOptions) error {
|
|||
|
||||
gistTime := gist.UpdatedAt.Format(time.RFC3339)
|
||||
if tp.IsTTY() {
|
||||
gistTime = utils.FuzzyAgo(time.Since(gist.UpdatedAt))
|
||||
gistTime = text.FuzzyAgo(time.Now(), gist.UpdatedAt)
|
||||
}
|
||||
|
||||
tp.AddField(gist.ID, nil, nil)
|
||||
tp.AddField(text.ReplaceExcessiveWhitespace(description), nil, cs.Bold)
|
||||
tp.AddField(utils.Pluralize(fileCount, "file"), nil, nil)
|
||||
tp.AddField(text.RemoveExcessiveWhitespace(description), nil, cs.Bold)
|
||||
tp.AddField(text.Pluralize(fileCount, "file"), nil, nil)
|
||||
tp.AddField(visibility, nil, visColor)
|
||||
tp.AddField(gistTime, nil, cs.Gray)
|
||||
tp.EndRow()
|
||||
|
|
|
|||
|
|
@ -10,13 +10,12 @@ import (
|
|||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
"github.com/cli/cli/v2/internal/ghinstance"
|
||||
"github.com/cli/cli/v2/internal/text"
|
||||
"github.com/cli/cli/v2/pkg/cmd/gist/shared"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/pkg/markdown"
|
||||
"github.com/cli/cli/v2/pkg/prompt"
|
||||
"github.com/cli/cli/v2/pkg/text"
|
||||
"github.com/cli/cli/v2/utils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
|
@ -107,7 +106,7 @@ func viewRun(opts *ViewOptions) error {
|
|||
gistURL = ghinstance.GistPrefix(hostname) + gistID
|
||||
}
|
||||
if opts.IO.IsStderrTTY() {
|
||||
fmt.Fprintf(opts.IO.ErrOut, "Opening %s in your browser.\n", utils.DisplayURL(gistURL))
|
||||
fmt.Fprintf(opts.IO.ErrOut, "Opening %s in your browser.\n", text.DisplayURL(gistURL))
|
||||
}
|
||||
return opts.Browser.Browse(gistURL)
|
||||
}
|
||||
|
|
@ -236,9 +235,9 @@ func promptGists(client *http.Client, host string, cs *iostreams.ColorScheme) (g
|
|||
sort.Strings(filenames)
|
||||
gistName = filenames[0]
|
||||
|
||||
gistTime := utils.FuzzyAgo(time.Since(gist.UpdatedAt))
|
||||
gistTime := text.FuzzyAgo(time.Now(), gist.UpdatedAt)
|
||||
// TODO: support dynamic maxWidth
|
||||
description = text.Truncate(100, text.ReplaceExcessiveWhitespace(description))
|
||||
description = text.Truncate(100, text.RemoveExcessiveWhitespace(description))
|
||||
opt := fmt.Sprintf("%s %s %s", cs.Bold(gistName), description, cs.Gray(gistTime))
|
||||
opts = append(opts, opt)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
"github.com/cli/cli/v2/internal/text"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/utils"
|
||||
|
|
@ -81,7 +82,7 @@ func listRun(opts *ListOptions) error {
|
|||
|
||||
createdAt := gpgKey.CreatedAt.Format(time.RFC3339)
|
||||
if t.IsTTY() {
|
||||
createdAt = "Created " + utils.FuzzyAgoAbbr(now, gpgKey.CreatedAt)
|
||||
createdAt = "Created " + text.FuzzyAgoAbbr(now, gpgKey.CreatedAt)
|
||||
}
|
||||
t.AddField(createdAt, nil, cs.Gray)
|
||||
|
||||
|
|
|
|||
|
|
@ -9,11 +9,11 @@ import (
|
|||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/internal/text"
|
||||
"github.com/cli/cli/v2/pkg/cmd/pr/shared"
|
||||
prShared "github.com/cli/cli/v2/pkg/cmd/pr/shared"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/utils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
|
@ -161,7 +161,7 @@ func createRun(opts *CreateOptions) (err error) {
|
|||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !utils.ValidURL(openURL) {
|
||||
if !prShared.ValidURL(openURL) {
|
||||
err = fmt.Errorf("cannot open in browser: maximum URL length exceeded")
|
||||
return
|
||||
}
|
||||
|
|
@ -171,7 +171,7 @@ func createRun(opts *CreateOptions) (err error) {
|
|||
openURL = ghrepo.GenerateRepoURL(baseRepo, "issues/new")
|
||||
}
|
||||
if isTerminal {
|
||||
fmt.Fprintf(opts.IO.ErrOut, "Opening %s in your browser.\n", utils.DisplayURL(openURL))
|
||||
fmt.Fprintf(opts.IO.ErrOut, "Opening %s in your browser.\n", text.DisplayURL(openURL))
|
||||
}
|
||||
return opts.Browser.Browse(openURL)
|
||||
}
|
||||
|
|
@ -238,7 +238,7 @@ func createRun(opts *CreateOptions) (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
allowPreview := !tb.HasMetadata() && utils.ValidURL(openURL)
|
||||
allowPreview := !tb.HasMetadata() && prShared.ValidURL(openURL)
|
||||
action, err = prShared.ConfirmIssueSubmission(allowPreview, repo.ViewerCanTriage())
|
||||
if err != nil {
|
||||
err = fmt.Errorf("unable to confirm: %w", err)
|
||||
|
|
@ -277,7 +277,7 @@ func createRun(opts *CreateOptions) (err error) {
|
|||
|
||||
if action == prShared.PreviewAction {
|
||||
if isTerminal {
|
||||
fmt.Fprintf(opts.IO.ErrOut, "Opening %s in your browser.\n", utils.DisplayURL(openURL))
|
||||
fmt.Fprintf(opts.IO.ErrOut, "Opening %s in your browser.\n", text.DisplayURL(openURL))
|
||||
}
|
||||
return opts.Browser.Browse(openURL)
|
||||
} else if action == prShared.SubmitAction {
|
||||
|
|
|
|||
|
|
@ -12,12 +12,12 @@ import (
|
|||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/internal/text"
|
||||
issueShared "github.com/cli/cli/v2/pkg/cmd/issue/shared"
|
||||
"github.com/cli/cli/v2/pkg/cmd/pr/shared"
|
||||
prShared "github.com/cli/cli/v2/pkg/cmd/pr/shared"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/utils"
|
||||
"github.com/shurcooL/githubv4"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
|
@ -158,7 +158,7 @@ func listRun(opts *ListOptions) error {
|
|||
}
|
||||
|
||||
if isTerminal {
|
||||
fmt.Fprintf(opts.IO.ErrOut, "Opening %s in your browser.\n", utils.DisplayURL(openURL))
|
||||
fmt.Fprintf(opts.IO.ErrOut, "Opening %s in your browser.\n", text.DisplayURL(openURL))
|
||||
}
|
||||
return opts.Browser.Browse(openURL)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/internal/text"
|
||||
prShared "github.com/cli/cli/v2/pkg/cmd/pr/shared"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/pkg/text"
|
||||
"github.com/cli/cli/v2/utils"
|
||||
)
|
||||
|
||||
|
|
@ -22,15 +22,14 @@ func PrintIssues(io *iostreams.IOStreams, now time.Time, prefix string, totalCou
|
|||
issueNum = "#" + issueNum
|
||||
}
|
||||
issueNum = prefix + issueNum
|
||||
ago := now.Sub(issue.UpdatedAt)
|
||||
table.AddField(issueNum, nil, cs.ColorFromString(prShared.ColorForIssueState(issue)))
|
||||
if !table.IsTTY() {
|
||||
table.AddField(issue.State, nil, nil)
|
||||
}
|
||||
table.AddField(text.ReplaceExcessiveWhitespace(issue.Title), nil, nil)
|
||||
table.AddField(text.RemoveExcessiveWhitespace(issue.Title), nil, nil)
|
||||
table.AddField(issueLabelList(&issue, cs, table.IsTTY()), nil, nil)
|
||||
if table.IsTTY() {
|
||||
table.AddField(utils.FuzzyAgo(ago), nil, cs.Gray)
|
||||
table.AddField(text.FuzzyAgo(now, issue.UpdatedAt), nil, cs.Gray)
|
||||
} else {
|
||||
table.AddField(issue.UpdatedAt.String(), nil, nil)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,13 +12,13 @@ import (
|
|||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/internal/text"
|
||||
issueShared "github.com/cli/cli/v2/pkg/cmd/issue/shared"
|
||||
prShared "github.com/cli/cli/v2/pkg/cmd/pr/shared"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/pkg/markdown"
|
||||
"github.com/cli/cli/v2/pkg/set"
|
||||
"github.com/cli/cli/v2/utils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
|
@ -114,7 +114,7 @@ func viewRun(opts *ViewOptions) error {
|
|||
if opts.WebMode {
|
||||
openURL := issue.URL
|
||||
if opts.IO.IsStdoutTTY() {
|
||||
fmt.Fprintf(opts.IO.ErrOut, "Opening %s in your browser.\n", utils.DisplayURL(openURL))
|
||||
fmt.Fprintf(opts.IO.ErrOut, "Opening %s in your browser.\n", text.DisplayURL(openURL))
|
||||
}
|
||||
return opts.Browser.Browse(openURL)
|
||||
}
|
||||
|
|
@ -184,8 +184,6 @@ func printRawIssuePreview(out io.Writer, issue *api.Issue) error {
|
|||
|
||||
func printHumanIssuePreview(opts *ViewOptions, issue *api.Issue) error {
|
||||
out := opts.IO.Out
|
||||
now := opts.Now()
|
||||
ago := now.Sub(issue.CreatedAt)
|
||||
cs := opts.IO.ColorScheme()
|
||||
|
||||
// Header (Title and State)
|
||||
|
|
@ -194,8 +192,8 @@ func printHumanIssuePreview(opts *ViewOptions, issue *api.Issue) error {
|
|||
"%s • %s opened %s • %s\n",
|
||||
issueStateTitleWithColor(cs, issue),
|
||||
issue.Author.Login,
|
||||
utils.FuzzyAgo(ago),
|
||||
utils.Pluralize(issue.Comments.TotalCount, "comment"),
|
||||
text.FuzzyAgo(opts.Now(), issue.CreatedAt),
|
||||
text.Pluralize(issue.Comments.TotalCount, "comment"),
|
||||
)
|
||||
|
||||
// Reactions
|
||||
|
|
|
|||
|
|
@ -9,9 +9,9 @@ import (
|
|||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/internal/text"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/utils"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
|
@ -94,7 +94,7 @@ func cloneRun(opts *cloneOptions) error {
|
|||
if opts.IO.IsStdoutTTY() {
|
||||
cs := opts.IO.ColorScheme()
|
||||
pluralize := func(num int) string {
|
||||
return utils.Pluralize(num, "label")
|
||||
return text.Pluralize(num, "label")
|
||||
}
|
||||
|
||||
successCount := int(successCount)
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@ import (
|
|||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/internal/text"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/pkg/text"
|
||||
"github.com/cli/cli/v2/utils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
|
@ -96,7 +96,7 @@ func listRun(opts *listOptions) error {
|
|||
labelListURL := ghrepo.GenerateRepoURL(baseRepo, "labels")
|
||||
|
||||
if opts.IO.IsStdoutTTY() {
|
||||
fmt.Fprintf(opts.IO.ErrOut, "Opening %s in your browser.\n", utils.DisplayURL(labelListURL))
|
||||
fmt.Fprintf(opts.IO.ErrOut, "Opening %s in your browser.\n", text.DisplayURL(labelListURL))
|
||||
}
|
||||
|
||||
return opts.Browser.Browse(labelListURL)
|
||||
|
|
@ -148,5 +148,5 @@ func printLabels(io *iostreams.IOStreams, labels []label) error {
|
|||
}
|
||||
|
||||
func listHeader(repoName string, count int, totalCount int) string {
|
||||
return fmt.Sprintf("Showing %d of %s in %s", count, utils.Pluralize(totalCount, "label"), repoName)
|
||||
return fmt.Sprintf("Showing %d of %s in %s", count, text.Pluralize(totalCount, "label"), repoName)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,10 +9,10 @@ import (
|
|||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/internal/text"
|
||||
"github.com/cli/cli/v2/pkg/cmd/pr/shared"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/utils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
|
@ -105,7 +105,7 @@ func checksRunWebMode(opts *ChecksOptions) error {
|
|||
openURL := ghrepo.GenerateRepoURL(baseRepo, "pull/%d/checks", pr.Number)
|
||||
|
||||
if isTerminal {
|
||||
fmt.Fprintf(opts.IO.ErrOut, "Opening %s in your browser.\n", utils.DisplayURL(openURL))
|
||||
fmt.Fprintf(opts.IO.ErrOut, "Opening %s in your browser.\n", text.DisplayURL(openURL))
|
||||
}
|
||||
|
||||
return opts.Browser.Browse(openURL)
|
||||
|
|
|
|||
|
|
@ -17,11 +17,11 @@ import (
|
|||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/internal/text"
|
||||
"github.com/cli/cli/v2/pkg/cmd/pr/shared"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/pkg/prompt"
|
||||
"github.com/cli/cli/v2/utils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
|
@ -206,7 +206,7 @@ func createRun(opts *CreateOptions) (err error) {
|
|||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !utils.ValidURL(openURL) {
|
||||
if !shared.ValidURL(openURL) {
|
||||
err = fmt.Errorf("cannot open in browser: maximum URL length exceeded")
|
||||
return
|
||||
}
|
||||
|
|
@ -307,7 +307,7 @@ func createRun(opts *CreateOptions) (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
allowPreview := !state.HasMetadata() && utils.ValidURL(openURL)
|
||||
allowPreview := !state.HasMetadata() && shared.ValidURL(openURL)
|
||||
allowMetadata := ctx.BaseRepo.ViewerCanTriage()
|
||||
action, err := shared.ConfirmPRSubmission(allowPreview, allowMetadata, state.Draft)
|
||||
if err != nil {
|
||||
|
|
@ -377,7 +377,7 @@ func initDefaultTitleBody(ctx CreateContext, state *shared.IssueMetadataState) e
|
|||
}
|
||||
state.Body = body
|
||||
} else {
|
||||
state.Title = utils.Humanize(headRef)
|
||||
state.Title = humanize(headRef)
|
||||
|
||||
var body strings.Builder
|
||||
for i := len(commits) - 1; i >= 0; i-- {
|
||||
|
|
@ -510,7 +510,7 @@ func NewCreateContext(opts *CreateOptions) (*CreateContext, error) {
|
|||
}
|
||||
|
||||
if ucc, err := git.UncommittedChangeCount(); err == nil && ucc > 0 {
|
||||
fmt.Fprintf(opts.IO.ErrOut, "Warning: %s\n", utils.Pluralize(ucc, "uncommitted change"))
|
||||
fmt.Fprintf(opts.IO.ErrOut, "Warning: %s\n", text.Pluralize(ucc, "uncommitted change"))
|
||||
}
|
||||
|
||||
var headRepo ghrepo.Interface
|
||||
|
|
@ -661,7 +661,7 @@ func submitPR(opts CreateOptions, ctx CreateContext, state shared.IssueMetadataS
|
|||
|
||||
func previewPR(opts CreateOptions, openURL string) error {
|
||||
if opts.IO.IsStdinTTY() && opts.IO.IsStdoutTTY() {
|
||||
fmt.Fprintf(opts.IO.ErrOut, "Opening %s in your browser.\n", utils.DisplayURL(openURL))
|
||||
fmt.Fprintf(opts.IO.ErrOut, "Opening %s in your browser.\n", text.DisplayURL(openURL))
|
||||
}
|
||||
return opts.Browser.Browse(openURL)
|
||||
|
||||
|
|
@ -732,7 +732,7 @@ func handlePush(opts CreateOptions, ctx CreateContext) error {
|
|||
pushTries++
|
||||
// first wait 2 seconds after forking, then 4s, then 6s
|
||||
waitSeconds := 2 * pushTries
|
||||
fmt.Fprintf(opts.IO.ErrOut, "waiting %s before retrying...\n", utils.Pluralize(waitSeconds, "second"))
|
||||
fmt.Fprintf(opts.IO.ErrOut, "waiting %s before retrying...\n", text.Pluralize(waitSeconds, "second"))
|
||||
time.Sleep(time.Duration(waitSeconds) * time.Second)
|
||||
continue
|
||||
}
|
||||
|
|
@ -764,4 +764,16 @@ func generateCompareURL(ctx CreateContext, state shared.IssueMetadataState) (str
|
|||
return url, nil
|
||||
}
|
||||
|
||||
// Humanize returns a copy of the string s that replaces all instance of '-' and '_' with spaces.
|
||||
func humanize(s string) string {
|
||||
replace := "_-"
|
||||
h := func(r rune) rune {
|
||||
if strings.ContainsRune(replace, r) {
|
||||
return ' '
|
||||
}
|
||||
return r
|
||||
}
|
||||
return strings.Map(h, s)
|
||||
}
|
||||
|
||||
var gitPushRegexp = regexp.MustCompile("^remote: (Create a pull request.*by visiting|[[:space:]]*https://.*/pull/new/).*\n?$")
|
||||
|
|
|
|||
|
|
@ -11,10 +11,10 @@ import (
|
|||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/internal/text"
|
||||
"github.com/cli/cli/v2/pkg/cmd/pr/shared"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/pkg/text"
|
||||
"github.com/cli/cli/v2/utils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
|
@ -166,7 +166,7 @@ func listRun(opts *ListOptions) error {
|
|||
}
|
||||
|
||||
if opts.IO.IsStdoutTTY() {
|
||||
fmt.Fprintf(opts.IO.ErrOut, "Opening %s in your browser.\n", utils.DisplayURL(openURL))
|
||||
fmt.Fprintf(opts.IO.ErrOut, "Opening %s in your browser.\n", text.DisplayURL(openURL))
|
||||
}
|
||||
return opts.Browser.Browse(openURL)
|
||||
}
|
||||
|
|
@ -204,17 +204,15 @@ func listRun(opts *ListOptions) error {
|
|||
if table.IsTTY() {
|
||||
prNum = "#" + prNum
|
||||
}
|
||||
now := opts.Now()
|
||||
ago := now.Sub(pr.CreatedAt)
|
||||
|
||||
table.AddField(prNum, nil, cs.ColorFromString(shared.ColorForPRState(pr)))
|
||||
table.AddField(text.ReplaceExcessiveWhitespace(pr.Title), nil, nil)
|
||||
table.AddField(text.RemoveExcessiveWhitespace(pr.Title), nil, nil)
|
||||
table.AddField(pr.HeadLabel(), nil, cs.Cyan)
|
||||
if !table.IsTTY() {
|
||||
table.AddField(prStateWithDraft(&pr), nil, nil)
|
||||
}
|
||||
if table.IsTTY() {
|
||||
table.AddField(utils.FuzzyAgo(ago), nil, cs.Gray)
|
||||
table.AddField(text.FuzzyAgo(opts.Now(), pr.CreatedAt), nil, cs.Gray)
|
||||
} else {
|
||||
table.AddField(pr.CreatedAt.String(), nil, nil)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,10 +11,10 @@ import (
|
|||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/internal/text"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/pkg/surveyext"
|
||||
"github.com/cli/cli/v2/utils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
|
@ -86,7 +86,7 @@ func CommentableRun(opts *CommentableOptions) error {
|
|||
case InputTypeWeb:
|
||||
openURL := commentable.Link() + "#issuecomment-new"
|
||||
if opts.IO.IsStdoutTTY() && !opts.Quiet {
|
||||
fmt.Fprintf(opts.IO.ErrOut, "Opening %s in your browser.\n", utils.DisplayURL(openURL))
|
||||
fmt.Fprintf(opts.IO.ErrOut, "Opening %s in your browser.\n", text.DisplayURL(openURL))
|
||||
}
|
||||
return opts.OpenInBrowser(openURL)
|
||||
case InputTypeEditor:
|
||||
|
|
|
|||
|
|
@ -7,10 +7,9 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/internal/text"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/pkg/markdown"
|
||||
"github.com/cli/cli/v2/pkg/text"
|
||||
"github.com/cli/cli/v2/utils"
|
||||
)
|
||||
|
||||
type Comment interface {
|
||||
|
|
@ -62,7 +61,7 @@ func CommentList(io *iostreams.IOStreams, comments api.Comments, reviews api.Pul
|
|||
hiddenCount := totalCount - retrievedCount
|
||||
|
||||
if preview && hiddenCount > 0 {
|
||||
fmt.Fprint(&b, cs.Gray(fmt.Sprintf("———————— Not showing %s ————————", utils.Pluralize(hiddenCount, "comment"))))
|
||||
fmt.Fprint(&b, cs.Gray(fmt.Sprintf("———————— Not showing %s ————————", text.Pluralize(hiddenCount, "comment"))))
|
||||
fmt.Fprintf(&b, "\n\n\n")
|
||||
}
|
||||
|
||||
|
|
@ -102,7 +101,7 @@ func formatComment(io *iostreams.IOStreams, comment Comment, newest bool) (strin
|
|||
if comment.Association() != "NONE" {
|
||||
fmt.Fprint(&b, cs.Boldf(" (%s)", text.Title(comment.Association())))
|
||||
}
|
||||
fmt.Fprint(&b, cs.Boldf(" • %s", utils.FuzzyAgoAbbr(time.Now(), comment.Created())))
|
||||
fmt.Fprint(&b, cs.Boldf(" • %s", text.FuzzyAgoAbbr(time.Now(), comment.Created())))
|
||||
if comment.IsEdited() {
|
||||
fmt.Fprint(&b, cs.Bold(" • Edited"))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,10 +4,9 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/internal/text"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/pkg/text"
|
||||
"github.com/cli/cli/v2/utils"
|
||||
)
|
||||
|
||||
func StateTitleWithColor(cs *iostreams.ColorScheme, pr api.PullRequest) string {
|
||||
|
|
@ -66,8 +65,8 @@ func ListHeader(repoName string, itemName string, matchCount int, totalMatchCoun
|
|||
if totalMatchCount == 1 {
|
||||
matchVerb = "matches"
|
||||
}
|
||||
return fmt.Sprintf("Showing %d of %s in %s that %s your search", matchCount, utils.Pluralize(totalMatchCount, itemName), repoName, matchVerb)
|
||||
return fmt.Sprintf("Showing %d of %s in %s that %s your search", matchCount, text.Pluralize(totalMatchCount, itemName), repoName, matchVerb)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("Showing %d of %s in %s", matchCount, utils.Pluralize(totalMatchCount, fmt.Sprintf("open %s", itemName)), repoName)
|
||||
return fmt.Sprintf("Showing %d of %s in %s", matchCount, text.Pluralize(totalMatchCount, fmt.Sprintf("open %s", itemName)), repoName)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,6 +44,11 @@ func WithPrAndIssueQueryParams(client *api.Client, baseRepo ghrepo.Interface, ba
|
|||
return u.String(), nil
|
||||
}
|
||||
|
||||
// Maximum length of a URL: 8192 bytes
|
||||
func ValidURL(urlStr string) bool {
|
||||
return len(urlStr) < 8192
|
||||
}
|
||||
|
||||
// Ensure that tb.MetadataResult object exists and contains enough pre-fetched API data to be able
|
||||
// to resolve all object listed in tb to GraphQL IDs.
|
||||
func fillMetadata(client *api.Client, baseRepo ghrepo.Interface, tb *IssueMetadataState) error {
|
||||
|
|
|
|||
|
|
@ -13,10 +13,10 @@ import (
|
|||
"github.com/cli/cli/v2/git"
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/internal/text"
|
||||
"github.com/cli/cli/v2/pkg/cmd/pr/shared"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/pkg/text"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
|
@ -217,7 +217,7 @@ func printPrs(io *iostreams.IOStreams, totalCount int, prs ...api.PullRequest) {
|
|||
|
||||
prStateColorFunc := cs.ColorFromString(shared.ColorForPRState(pr))
|
||||
|
||||
fmt.Fprintf(w, " %s %s %s", prStateColorFunc(prNumber), text.Truncate(50, text.ReplaceExcessiveWhitespace(pr.Title)), cs.Cyan("["+pr.HeadLabel()+"]"))
|
||||
fmt.Fprintf(w, " %s %s %s", prStateColorFunc(prNumber), text.Truncate(50, text.RemoveExcessiveWhitespace(pr.Title)), cs.Cyan("["+pr.HeadLabel()+"]"))
|
||||
|
||||
checks := pr.ChecksStatus()
|
||||
reviews := pr.ReviewStatus()
|
||||
|
|
|
|||
|
|
@ -10,12 +10,11 @@ import (
|
|||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/internal/text"
|
||||
"github.com/cli/cli/v2/pkg/cmd/pr/shared"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/pkg/markdown"
|
||||
"github.com/cli/cli/v2/pkg/text"
|
||||
"github.com/cli/cli/v2/utils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
|
@ -105,7 +104,7 @@ func viewRun(opts *ViewOptions) error {
|
|||
if opts.BrowserMode {
|
||||
openURL := pr.URL
|
||||
if connectedToTerminal {
|
||||
fmt.Fprintf(opts.IO.ErrOut, "Opening %s in your browser.\n", utils.DisplayURL(openURL))
|
||||
fmt.Fprintf(opts.IO.ErrOut, "Opening %s in your browser.\n", text.DisplayURL(openURL))
|
||||
}
|
||||
return opts.Browser.Browse(openURL)
|
||||
}
|
||||
|
|
@ -168,8 +167,6 @@ func printRawPrPreview(io *iostreams.IOStreams, pr *api.PullRequest) error {
|
|||
func printHumanPrPreview(opts *ViewOptions, pr *api.PullRequest) error {
|
||||
out := opts.IO.Out
|
||||
cs := opts.IO.ColorScheme()
|
||||
now := opts.Now()
|
||||
ago := now.Sub(pr.CreatedAt)
|
||||
|
||||
// Header (Title and State)
|
||||
fmt.Fprintf(out, "%s #%d\n", cs.Bold(pr.Title), pr.Number)
|
||||
|
|
@ -177,10 +174,10 @@ func printHumanPrPreview(opts *ViewOptions, pr *api.PullRequest) error {
|
|||
"%s • %s wants to merge %s into %s from %s • %s • %s %s \n",
|
||||
shared.StateTitleWithColor(cs, *pr),
|
||||
pr.Author.Login,
|
||||
utils.Pluralize(pr.Commits.TotalCount, "commit"),
|
||||
text.Pluralize(pr.Commits.TotalCount, "commit"),
|
||||
pr.BaseRefName,
|
||||
pr.HeadRefName,
|
||||
utils.FuzzyAgo(ago),
|
||||
text.FuzzyAgo(opts.Now(), pr.CreatedAt),
|
||||
cs.Green("+"+strconv.Itoa(pr.Additions)),
|
||||
cs.Red("-"+strconv.Itoa(pr.Deletions)),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -14,12 +14,12 @@ import (
|
|||
"github.com/cli/cli/v2/internal/config"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/internal/run"
|
||||
"github.com/cli/cli/v2/internal/text"
|
||||
"github.com/cli/cli/v2/pkg/cmd/release/shared"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/pkg/prompt"
|
||||
"github.com/cli/cli/v2/pkg/surveyext"
|
||||
"github.com/cli/cli/v2/pkg/text"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/internal/text"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/pkg/text"
|
||||
"github.com/cli/cli/v2/utils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
|
@ -76,11 +76,10 @@ func listRun(opts *ListOptions) error {
|
|||
fmt.Fprintf(opts.IO.ErrOut, "failed to start pager: %v\n", err)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
table := utils.NewTablePrinter(opts.IO)
|
||||
iofmt := opts.IO.ColorScheme()
|
||||
for _, rel := range releases {
|
||||
title := text.ReplaceExcessiveWhitespace(rel.Name)
|
||||
title := text.RemoveExcessiveWhitespace(rel.Name)
|
||||
if title == "" {
|
||||
title = rel.TagName
|
||||
}
|
||||
|
|
@ -112,7 +111,7 @@ func listRun(opts *ListOptions) error {
|
|||
}
|
||||
publishedAt := pubDate.Format(time.RFC3339)
|
||||
if table.IsTTY() {
|
||||
publishedAt = utils.FuzzyAgo(now.Sub(pubDate))
|
||||
publishedAt = text.FuzzyAgo(time.Now(), pubDate)
|
||||
}
|
||||
table.AddField(publishedAt, nil, iofmt.Gray)
|
||||
table.EndRow()
|
||||
|
|
|
|||
|
|
@ -7,10 +7,10 @@ import (
|
|||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/internal/text"
|
||||
"github.com/cli/cli/v2/pkg/cmd/release/shared"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/utils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
|
@ -115,7 +115,7 @@ func uploadRun(opts *UploadOptions) error {
|
|||
if opts.IO.IsStdoutTTY() {
|
||||
iofmt := opts.IO.ColorScheme()
|
||||
fmt.Fprintf(opts.IO.Out, "Successfully uploaded %s to %s\n",
|
||||
utils.Pluralize(len(opts.Assets), "asset"),
|
||||
text.Pluralize(len(opts.Assets), "asset"),
|
||||
iofmt.Bold(release.TagName))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/internal/text"
|
||||
"github.com/cli/cli/v2/pkg/cmd/release/shared"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
|
|
@ -97,7 +98,7 @@ func viewRun(opts *ViewOptions) error {
|
|||
|
||||
if opts.WebMode {
|
||||
if opts.IO.IsStdoutTTY() {
|
||||
fmt.Fprintf(opts.IO.ErrOut, "Opening %s in your browser.\n", utils.DisplayURL(release.URL))
|
||||
fmt.Fprintf(opts.IO.ErrOut, "Opening %s in your browser.\n", text.DisplayURL(release.URL))
|
||||
}
|
||||
return opts.Browser.Browse(release.URL)
|
||||
}
|
||||
|
|
@ -136,9 +137,9 @@ func renderReleaseTTY(io *iostreams.IOStreams, release *shared.Release) error {
|
|||
fmt.Fprintf(w, "%s • ", iofmt.Yellow("Pre-release"))
|
||||
}
|
||||
if release.IsDraft {
|
||||
fmt.Fprintf(w, "%s\n", iofmt.Gray(fmt.Sprintf("%s created this %s", release.Author.Login, utils.FuzzyAgo(time.Since(release.CreatedAt)))))
|
||||
fmt.Fprintf(w, "%s\n", iofmt.Gray(fmt.Sprintf("%s created this %s", release.Author.Login, text.FuzzyAgo(time.Now(), release.CreatedAt))))
|
||||
} else {
|
||||
fmt.Fprintf(w, "%s\n", iofmt.Gray(fmt.Sprintf("%s released this %s", release.Author.Login, utils.FuzzyAgo(time.Since(*release.PublishedAt)))))
|
||||
fmt.Fprintf(w, "%s\n", iofmt.Gray(fmt.Sprintf("%s released this %s", release.Author.Login, text.FuzzyAgo(time.Now(), *release.PublishedAt))))
|
||||
}
|
||||
|
||||
renderedDescription, err := markdown.Render(release.Body,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/internal/text"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/utils"
|
||||
|
|
@ -81,7 +82,7 @@ func listRun(opts *ListOptions) error {
|
|||
|
||||
createdAt := deployKey.CreatedAt.Format(time.RFC3339)
|
||||
if t.IsTTY() {
|
||||
createdAt = utils.FuzzyAgoAbbr(now, deployKey.CreatedAt)
|
||||
createdAt = text.FuzzyAgoAbbr(now, deployKey.CreatedAt)
|
||||
}
|
||||
t.AddField(createdAt, nil, cs.Gray)
|
||||
t.EndRow()
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ import (
|
|||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/pkg/prompt"
|
||||
"github.com/cli/cli/v2/utils"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
|
@ -139,7 +138,7 @@ func forkRun(opts *ForkOptions) error {
|
|||
} else {
|
||||
repoArg := opts.Repository
|
||||
|
||||
if utils.IsURL(repoArg) {
|
||||
if isURL(repoArg) {
|
||||
parsedURL, err := url.Parse(repoArg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("did not understand argument: %w", err)
|
||||
|
|
@ -330,3 +329,7 @@ func forkRun(opts *ForkOptions) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func isURL(s string) bool {
|
||||
return strings.HasPrefix(s, "http:/") || strings.HasPrefix(s, "https:/")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,9 +11,9 @@ import (
|
|||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
fd "github.com/cli/cli/v2/internal/featuredetection"
|
||||
"github.com/cli/cli/v2/internal/text"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/pkg/text"
|
||||
"github.com/cli/cli/v2/utils"
|
||||
)
|
||||
|
||||
|
|
@ -165,7 +165,6 @@ func listRun(opts *ListOptions) error {
|
|||
|
||||
cs := opts.IO.ColorScheme()
|
||||
tp := utils.NewTablePrinter(opts.IO)
|
||||
now := opts.Now()
|
||||
|
||||
for _, repo := range listResult.Repositories {
|
||||
info := repoInfo(repo)
|
||||
|
|
@ -181,10 +180,10 @@ func listRun(opts *ListOptions) error {
|
|||
}
|
||||
|
||||
tp.AddField(repo.NameWithOwner, nil, cs.Bold)
|
||||
tp.AddField(text.ReplaceExcessiveWhitespace(repo.Description), nil, nil)
|
||||
tp.AddField(text.RemoveExcessiveWhitespace(repo.Description), nil, nil)
|
||||
tp.AddField(info, nil, infoColor)
|
||||
if tp.IsTTY() {
|
||||
tp.AddField(utils.FuzzyAgoAbbr(now, *t), nil, cs.Gray)
|
||||
tp.AddField(text.FuzzyAgoAbbr(opts.Now(), *t), nil, cs.Gray)
|
||||
} else {
|
||||
tp.AddField(t.Format(time.RFC3339), nil, nil)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,10 +13,10 @@ import (
|
|||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/internal/text"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/pkg/markdown"
|
||||
"github.com/cli/cli/v2/utils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
|
@ -128,7 +128,7 @@ func viewRun(opts *ViewOptions) error {
|
|||
openURL := generateBranchURL(toView, opts.Branch)
|
||||
if opts.Web {
|
||||
if opts.IO.IsStdoutTTY() {
|
||||
fmt.Fprintf(opts.IO.ErrOut, "Opening %s in your browser.\n", utils.DisplayURL(openURL))
|
||||
fmt.Fprintf(opts.IO.ErrOut, "Opening %s in your browser.\n", text.DisplayURL(openURL))
|
||||
}
|
||||
return opts.Browser.Browse(openURL)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ import (
|
|||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/cli/cli/v2/internal/text"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/text"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ import (
|
|||
"io"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/v2/internal/text"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/pkg/text"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
|
||||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/internal/text"
|
||||
"github.com/cli/cli/v2/pkg/cmd/run/shared"
|
||||
workflowShared "github.com/cli/cli/v2/pkg/cmd/workflow/shared"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
|
|
@ -154,7 +155,7 @@ func listRun(opts *ListOptions) error {
|
|||
tp.AddField(fmt.Sprintf("%d", run.ID), nil, cs.Cyan)
|
||||
|
||||
tp.AddField(run.Duration(opts.now).String(), nil, nil)
|
||||
tp.AddField(utils.FuzzyAgoAbbr(time.Now(), run.StartedTime()), nil, nil)
|
||||
tp.AddField(text.FuzzyAgoAbbr(time.Now(), run.StartedTime()), nil, nil)
|
||||
tp.EndRow()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,11 +20,11 @@ import (
|
|||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/internal/ghinstance"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/internal/text"
|
||||
"github.com/cli/cli/v2/pkg/cmd/run/shared"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/pkg/prompt"
|
||||
"github.com/cli/cli/v2/utils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
|
@ -243,7 +243,7 @@ func runView(opts *ViewOptions) error {
|
|||
url = selectedJob.URL + "?check_suite_focus=true"
|
||||
}
|
||||
if opts.IO.IsStdoutTTY() {
|
||||
fmt.Fprintf(opts.IO.Out, "Opening %s in your browser.\n", utils.DisplayURL(url))
|
||||
fmt.Fprintf(opts.IO.Out, "Opening %s in your browser.\n", text.DisplayURL(url))
|
||||
}
|
||||
|
||||
return opts.Browser.Browse(url)
|
||||
|
|
@ -316,10 +316,8 @@ func runView(opts *ViewOptions) error {
|
|||
|
||||
out := opts.IO.Out
|
||||
|
||||
ago := opts.Now().Sub(run.StartedTime())
|
||||
|
||||
fmt.Fprintln(out)
|
||||
fmt.Fprintln(out, shared.RenderRunHeader(cs, *run, utils.FuzzyAgo(ago), prNumber))
|
||||
fmt.Fprintln(out, shared.RenderRunHeader(cs, *run, text.FuzzyAgo(opts.Now(), run.StartedTime()), prNumber))
|
||||
fmt.Fprintln(out)
|
||||
|
||||
if len(jobs) == 0 && run.Conclusion == shared.Failure || run.Conclusion == shared.StartupFailure {
|
||||
|
|
|
|||
|
|
@ -10,10 +10,10 @@ import (
|
|||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/internal/text"
|
||||
"github.com/cli/cli/v2/pkg/cmd/run/shared"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/utils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
|
@ -206,8 +206,6 @@ func renderRun(out io.Writer, opts WatchOptions, client *api.Client, repo ghrepo
|
|||
return nil, fmt.Errorf("failed to get run: %w", err)
|
||||
}
|
||||
|
||||
ago := opts.Now().Sub(run.StartedTime())
|
||||
|
||||
jobs, err := shared.GetJobs(client, repo, *run)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get jobs: %w", err)
|
||||
|
|
@ -238,7 +236,7 @@ func renderRun(out io.Writer, opts WatchOptions, client *api.Client, repo ghrepo
|
|||
return nil, fmt.Errorf("failed to get annotations: %w", annotationErr)
|
||||
}
|
||||
|
||||
fmt.Fprintln(out, shared.RenderRunHeader(cs, *run, utils.FuzzyAgo(ago), prNumber))
|
||||
fmt.Fprintln(out, shared.RenderRunHeader(cs, *run, text.FuzzyAgo(opts.Now(), run.StartedTime()), prNumber))
|
||||
fmt.Fprintln(out)
|
||||
|
||||
if len(jobs) == 0 {
|
||||
|
|
|
|||
|
|
@ -7,11 +7,11 @@ import (
|
|||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/internal/text"
|
||||
"github.com/cli/cli/v2/pkg/cmd/search/shared"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/pkg/search"
|
||||
"github.com/cli/cli/v2/pkg/text"
|
||||
"github.com/cli/cli/v2/utils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
|
@ -127,7 +127,7 @@ func reposRun(opts *ReposOptions) error {
|
|||
if opts.WebMode {
|
||||
url := opts.Searcher.URL(opts.Query)
|
||||
if io.IsStdoutTTY() {
|
||||
fmt.Fprintf(io.ErrOut, "Opening %s in your browser.\n", utils.DisplayURL(url))
|
||||
fmt.Fprintf(io.ErrOut, "Opening %s in your browser.\n", text.DisplayURL(url))
|
||||
}
|
||||
return opts.Browser.Browse(url)
|
||||
}
|
||||
|
|
@ -169,10 +169,10 @@ func displayResults(io *iostreams.IOStreams, results search.RepositoriesResult)
|
|||
}
|
||||
tp.AddField(repo.FullName, nil, cs.Bold)
|
||||
description := repo.Description
|
||||
tp.AddField(text.ReplaceExcessiveWhitespace(description), nil, nil)
|
||||
tp.AddField(text.RemoveExcessiveWhitespace(description), nil, nil)
|
||||
tp.AddField(info, nil, infoColor)
|
||||
if tp.IsTTY() {
|
||||
tp.AddField(utils.FuzzyAgoAbbr(time.Now(), repo.UpdatedAt), nil, cs.Gray)
|
||||
tp.AddField(text.FuzzyAgoAbbr(time.Now(), repo.UpdatedAt), nil, cs.Gray)
|
||||
} else {
|
||||
tp.AddField(repo.UpdatedAt.Format(time.RFC3339), nil, nil)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,10 +7,10 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/internal/text"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/pkg/search"
|
||||
"github.com/cli/cli/v2/pkg/text"
|
||||
"github.com/cli/cli/v2/utils"
|
||||
)
|
||||
|
||||
|
|
@ -54,7 +54,7 @@ func SearchIssues(opts *IssuesOptions) error {
|
|||
if opts.WebMode {
|
||||
url := opts.Searcher.URL(opts.Query)
|
||||
if io.IsStdoutTTY() {
|
||||
fmt.Fprintf(io.ErrOut, "Opening %s in your browser.\n", utils.DisplayURL(url))
|
||||
fmt.Fprintf(io.ErrOut, "Opening %s in your browser.\n", text.DisplayURL(url))
|
||||
}
|
||||
return opts.Browser.Browse(url)
|
||||
}
|
||||
|
|
@ -117,12 +117,10 @@ func displayIssueResults(io *iostreams.IOStreams, et EntityType, results search.
|
|||
if !tp.IsTTY() {
|
||||
tp.AddField(issue.State, nil, nil)
|
||||
}
|
||||
tp.AddField(text.ReplaceExcessiveWhitespace(issue.Title), nil, nil)
|
||||
tp.AddField(text.RemoveExcessiveWhitespace(issue.Title), nil, nil)
|
||||
tp.AddField(listIssueLabels(&issue, cs, tp.IsTTY()), nil, nil)
|
||||
now := time.Now()
|
||||
ago := now.Sub(issue.UpdatedAt)
|
||||
if tp.IsTTY() {
|
||||
tp.AddField(utils.FuzzyAgo(ago), nil, cs.Gray)
|
||||
tp.AddField(text.FuzzyAgo(time.Now(), issue.UpdatedAt), nil, cs.Gray)
|
||||
} else {
|
||||
tp.AddField(issue.UpdatedAt.String(), nil, nil)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import (
|
|||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/cli/cli/v2/pkg/text"
|
||||
"github.com/cli/cli/v2/internal/text"
|
||||
)
|
||||
|
||||
type Visibility string
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
"github.com/cli/cli/v2/internal/text"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/utils"
|
||||
|
|
@ -72,7 +73,7 @@ func listRun(opts *ListOptions) error {
|
|||
|
||||
createdAt := sshKey.CreatedAt.Format(time.RFC3339)
|
||||
if t.IsTTY() {
|
||||
createdAt = utils.FuzzyAgoAbbr(now, sshKey.CreatedAt)
|
||||
createdAt = text.FuzzyAgoAbbr(now, sshKey.CreatedAt)
|
||||
}
|
||||
t.AddField(createdAt, nil, cs.Gray)
|
||||
t.EndRow()
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/internal/text"
|
||||
runShared "github.com/cli/cli/v2/pkg/cmd/run/shared"
|
||||
"github.com/cli/cli/v2/pkg/cmd/workflow/shared"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
|
|
@ -123,7 +124,7 @@ func runView(opts *ViewOptions) error {
|
|||
address = ghrepo.GenerateRepoURL(repo, "actions/workflows/%s", url.QueryEscape(workflow.Base()))
|
||||
}
|
||||
if opts.IO.IsStdoutTTY() {
|
||||
fmt.Fprintf(opts.IO.Out, "Opening %s in your browser.\n", utils.DisplayURL(address))
|
||||
fmt.Fprintf(opts.IO.Out, "Opening %s in your browser.\n", text.DisplayURL(address))
|
||||
}
|
||||
return opts.Browser.Browse(address)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,7 @@ import (
|
|||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/cli/cli/v2/pkg/text"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -84,7 +83,7 @@ func (q Qualifiers) Map() map[string][]string {
|
|||
t := reflect.TypeOf(q)
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
fieldName := t.Field(i).Name
|
||||
key := text.CamelToKebab(fieldName)
|
||||
key := camelToKebab(fieldName)
|
||||
typ := v.FieldByName(fieldName).Kind()
|
||||
value := v.FieldByName(fieldName)
|
||||
switch typ {
|
||||
|
|
@ -140,3 +139,29 @@ func formatKeywords(ks []string) []string {
|
|||
}
|
||||
return ks
|
||||
}
|
||||
|
||||
// CamelToKebab returns a copy of the string s that is converted from camel case form to '-' separated form.
|
||||
func camelToKebab(s string) string {
|
||||
var output []rune
|
||||
var segment []rune
|
||||
for _, r := range s {
|
||||
if !unicode.IsLower(r) && string(r) != "-" && !unicode.IsNumber(r) {
|
||||
output = addSegment(output, segment)
|
||||
segment = nil
|
||||
}
|
||||
segment = append(segment, unicode.ToLower(r))
|
||||
}
|
||||
output = addSegment(output, segment)
|
||||
return string(output)
|
||||
}
|
||||
|
||||
func addSegment(inrune, segment []rune) []rune {
|
||||
if len(segment) == 0 {
|
||||
return inrune
|
||||
}
|
||||
if len(inrune) != 0 {
|
||||
inrune = append(inrune, '-')
|
||||
}
|
||||
inrune = append(inrune, segment...)
|
||||
return inrune
|
||||
}
|
||||
|
|
|
|||
|
|
@ -133,3 +133,57 @@ func TestQualifiersMap(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCamelToKebab(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
in string
|
||||
out string
|
||||
}{
|
||||
{
|
||||
name: "single lowercase word",
|
||||
in: "test",
|
||||
out: "test",
|
||||
},
|
||||
{
|
||||
name: "multiple mixed words",
|
||||
in: "testTestTest",
|
||||
out: "test-test-test",
|
||||
},
|
||||
{
|
||||
name: "multiple uppercase words",
|
||||
in: "TestTest",
|
||||
out: "test-test",
|
||||
},
|
||||
{
|
||||
name: "multiple lowercase words",
|
||||
in: "testtest",
|
||||
out: "testtest",
|
||||
},
|
||||
{
|
||||
name: "multiple mixed words with number",
|
||||
in: "test2Test",
|
||||
out: "test2-test",
|
||||
},
|
||||
{
|
||||
name: "multiple lowercase words with number",
|
||||
in: "test2test",
|
||||
out: "test2test",
|
||||
},
|
||||
{
|
||||
name: "multiple lowercase words with dash",
|
||||
in: "test-test",
|
||||
out: "test-test",
|
||||
},
|
||||
{
|
||||
name: "multiple uppercase words with dash",
|
||||
in: "Test-Test",
|
||||
out: "test--test",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equal(t, tt.out, camelToKebab(tt.in))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,39 +0,0 @@
|
|||
package text
|
||||
|
||||
import (
|
||||
"unicode"
|
||||
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
// Copied from: https://github.com/asaskevich/govalidator
|
||||
func CamelToKebab(str string) string {
|
||||
var output []rune
|
||||
var segment []rune
|
||||
for _, r := range str {
|
||||
if !unicode.IsLower(r) && string(r) != "-" && !unicode.IsNumber(r) {
|
||||
output = addSegment(output, segment)
|
||||
segment = nil
|
||||
}
|
||||
segment = append(segment, unicode.ToLower(r))
|
||||
}
|
||||
output = addSegment(output, segment)
|
||||
return string(output)
|
||||
}
|
||||
|
||||
func addSegment(inrune, segment []rune) []rune {
|
||||
if len(segment) == 0 {
|
||||
return inrune
|
||||
}
|
||||
if len(inrune) != 0 {
|
||||
inrune = append(inrune, '-')
|
||||
}
|
||||
inrune = append(inrune, segment...)
|
||||
return inrune
|
||||
}
|
||||
|
||||
func Title(str string) string {
|
||||
c := cases.Title(language.English)
|
||||
return c.String(str)
|
||||
}
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
package text
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCamelToKebab(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
in string
|
||||
out string
|
||||
}{
|
||||
{
|
||||
name: "single lowercase word",
|
||||
in: "test",
|
||||
out: "test",
|
||||
},
|
||||
{
|
||||
name: "multiple mixed words",
|
||||
in: "testTestTest",
|
||||
out: "test-test-test",
|
||||
},
|
||||
{
|
||||
name: "multiple uppercase words",
|
||||
in: "TestTest",
|
||||
out: "test-test",
|
||||
},
|
||||
{
|
||||
name: "multiple lowercase words",
|
||||
in: "testtest",
|
||||
out: "testtest",
|
||||
},
|
||||
{
|
||||
name: "multiple mixed words with number",
|
||||
in: "test2Test",
|
||||
out: "test2-test",
|
||||
},
|
||||
{
|
||||
name: "multiple lowercase words with number",
|
||||
in: "test2test",
|
||||
out: "test2test",
|
||||
},
|
||||
{
|
||||
name: "multiple lowercase words with dash",
|
||||
in: "test-test",
|
||||
out: "test-test",
|
||||
},
|
||||
{
|
||||
name: "multiple uppercase words with dash",
|
||||
in: "Test-Test",
|
||||
out: "test--test",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equal(t, tt.out, CamelToKebab(tt.in))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
package text
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var lineRE = regexp.MustCompile(`(?m)^`)
|
||||
|
||||
func Indent(s, indent string) string {
|
||||
if len(strings.TrimSpace(s)) == 0 {
|
||||
return s
|
||||
}
|
||||
return lineRE.ReplaceAllLiteralString(s, indent)
|
||||
}
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
package text
|
||||
|
||||
import "testing"
|
||||
|
||||
func Test_Indent(t *testing.T) {
|
||||
type args struct {
|
||||
s string
|
||||
indent string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
args: args{
|
||||
s: "",
|
||||
indent: "--",
|
||||
},
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "blank",
|
||||
args: args{
|
||||
s: "\n",
|
||||
indent: "--",
|
||||
},
|
||||
want: "\n",
|
||||
},
|
||||
{
|
||||
name: "indent",
|
||||
args: args{
|
||||
s: "one\ntwo\nthree",
|
||||
indent: "--",
|
||||
},
|
||||
want: "--one\n--two\n--three",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := Indent(tt.args.s, tt.args.indent); got != tt.want {
|
||||
t.Errorf("indent() = %q, want %q", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
package text
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var ws = regexp.MustCompile(`\s+`)
|
||||
|
||||
func ReplaceExcessiveWhitespace(s string) string {
|
||||
return ws.ReplaceAllString(strings.TrimSpace(s), " ")
|
||||
}
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
package text
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestReplaceExcessiveWhitespace(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "no replacements",
|
||||
input: "one two three",
|
||||
want: "one two three",
|
||||
},
|
||||
{
|
||||
name: "whitespace b-gone",
|
||||
input: "\n one\n\t two three\r\n ",
|
||||
want: "one two three",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := ReplaceExcessiveWhitespace(tt.input); got != tt.want {
|
||||
t.Errorf("ReplaceExcessiveWhitespace() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
package text
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/muesli/reflow/ansi"
|
||||
"github.com/muesli/reflow/truncate"
|
||||
)
|
||||
|
||||
const (
|
||||
ellipsis = "..."
|
||||
minWidthForEllipsis = len(ellipsis) + 2
|
||||
)
|
||||
|
||||
// DisplayWidth calculates what the rendered width of a string may be
|
||||
func DisplayWidth(s string) int {
|
||||
return ansi.PrintableRuneWidth(s)
|
||||
}
|
||||
|
||||
// Truncate shortens a string to fit the maximum display width
|
||||
func Truncate(maxWidth int, s string) string {
|
||||
w := DisplayWidth(s)
|
||||
if w <= maxWidth {
|
||||
return s
|
||||
}
|
||||
|
||||
tail := ""
|
||||
if maxWidth >= minWidthForEllipsis {
|
||||
tail = ellipsis
|
||||
}
|
||||
|
||||
r := truncate.StringWithTail(s, uint(maxWidth), tail)
|
||||
if DisplayWidth(r) < maxWidth {
|
||||
r += " "
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// TruncateColumn replaces the first new line character with an ellipsis
|
||||
// and shortens a string to fit the maximum display width
|
||||
func TruncateColumn(maxWidth int, s string) string {
|
||||
if i := strings.IndexAny(s, "\r\n"); i >= 0 {
|
||||
s = s[:i] + ellipsis
|
||||
}
|
||||
return Truncate(maxWidth, s)
|
||||
}
|
||||
|
|
@ -1,265 +0,0 @@
|
|||
package text
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTruncate(t *testing.T) {
|
||||
type args struct {
|
||||
max int
|
||||
s string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "Short enough",
|
||||
args: args{
|
||||
max: 5,
|
||||
s: "short",
|
||||
},
|
||||
want: "short",
|
||||
},
|
||||
{
|
||||
name: "Too short",
|
||||
args: args{
|
||||
max: 4,
|
||||
s: "short",
|
||||
},
|
||||
want: "shor",
|
||||
},
|
||||
{
|
||||
name: "Japanese",
|
||||
args: args{
|
||||
max: 11,
|
||||
s: "テストテストテストテスト",
|
||||
},
|
||||
want: "テストテ...",
|
||||
},
|
||||
{
|
||||
name: "Japanese filled",
|
||||
args: args{
|
||||
max: 11,
|
||||
s: "aテストテストテストテスト",
|
||||
},
|
||||
want: "aテスト... ",
|
||||
},
|
||||
{
|
||||
name: "Chinese",
|
||||
args: args{
|
||||
max: 11,
|
||||
s: "幫新舉報違章工廠新增編號",
|
||||
},
|
||||
want: "幫新舉報...",
|
||||
},
|
||||
{
|
||||
name: "Chinese filled",
|
||||
args: args{
|
||||
max: 11,
|
||||
s: "a幫新舉報違章工廠新增編號",
|
||||
},
|
||||
want: "a幫新舉... ",
|
||||
},
|
||||
{
|
||||
name: "Korean",
|
||||
args: args{
|
||||
max: 11,
|
||||
s: "프로젝트 내의",
|
||||
},
|
||||
want: "프로젝트...",
|
||||
},
|
||||
{
|
||||
name: "Korean filled",
|
||||
args: args{
|
||||
max: 11,
|
||||
s: "a프로젝트 내의",
|
||||
},
|
||||
want: "a프로젝... ",
|
||||
},
|
||||
{
|
||||
name: "Emoji",
|
||||
args: args{
|
||||
max: 11,
|
||||
s: "💡💡💡💡💡💡💡💡💡💡💡💡",
|
||||
},
|
||||
want: "💡💡💡💡...",
|
||||
},
|
||||
{
|
||||
name: "Accented characters",
|
||||
args: args{
|
||||
max: 11,
|
||||
s: "é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́",
|
||||
},
|
||||
want: "é́́é́́é́́é́́é́́é́́é́́é́́...",
|
||||
},
|
||||
{
|
||||
name: "Red accented characters",
|
||||
args: args{
|
||||
max: 11,
|
||||
s: "\x1b[0;31mé́́é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́\x1b[0m",
|
||||
},
|
||||
want: "\x1b[0;31mé́́é́́é́́é́́é́́é́́é́́é́́...\x1b[0m",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := Truncate(tt.args.max, tt.args.s); got != tt.want {
|
||||
t.Errorf("Truncate() = %q, want %q", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTruncateColumn(t *testing.T) {
|
||||
type args struct {
|
||||
max int
|
||||
s string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "exactly minimum width",
|
||||
args: args{
|
||||
max: 5,
|
||||
s: "short",
|
||||
},
|
||||
want: "short",
|
||||
},
|
||||
{
|
||||
name: "exactly minimum width with new line",
|
||||
args: args{
|
||||
max: 5,
|
||||
s: "short\n",
|
||||
},
|
||||
want: "sh...",
|
||||
},
|
||||
{
|
||||
name: "less than minimum width",
|
||||
args: args{
|
||||
max: 4,
|
||||
s: "short",
|
||||
},
|
||||
want: "shor",
|
||||
},
|
||||
{
|
||||
name: "less than minimum width with new line",
|
||||
args: args{
|
||||
max: 4,
|
||||
s: "short\n",
|
||||
},
|
||||
want: "shor",
|
||||
},
|
||||
{
|
||||
name: "first line of multiple is short enough",
|
||||
args: args{
|
||||
max: 80,
|
||||
s: "short\n\nthis is a new line",
|
||||
},
|
||||
want: "short...",
|
||||
},
|
||||
{
|
||||
name: "using Windows line endings",
|
||||
args: args{
|
||||
max: 80,
|
||||
s: "short\r\n\r\nthis is a new line",
|
||||
},
|
||||
want: "short...",
|
||||
},
|
||||
{
|
||||
name: "using older MacOS line endings",
|
||||
args: args{
|
||||
max: 80,
|
||||
s: "short\r\rthis is a new line",
|
||||
},
|
||||
want: "short...",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := TruncateColumn(tt.args.max, tt.args.s); got != tt.want {
|
||||
t.Errorf("TruncateColumn() = %q, want %q", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDisplayWidth(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
text string
|
||||
want int
|
||||
}{
|
||||
{
|
||||
name: "check mark",
|
||||
text: `✓`,
|
||||
want: 1,
|
||||
},
|
||||
{
|
||||
name: "bullet icon",
|
||||
text: `•`,
|
||||
want: 1,
|
||||
},
|
||||
{
|
||||
name: "middle dot",
|
||||
text: `·`,
|
||||
want: 1,
|
||||
},
|
||||
{
|
||||
name: "ellipsis",
|
||||
text: `…`,
|
||||
want: 1,
|
||||
},
|
||||
{
|
||||
name: "right arrow",
|
||||
text: `→`,
|
||||
want: 1,
|
||||
},
|
||||
{
|
||||
name: "smart double quotes",
|
||||
text: `“”`,
|
||||
want: 2,
|
||||
},
|
||||
{
|
||||
name: "smart single quotes",
|
||||
text: `‘’`,
|
||||
want: 2,
|
||||
},
|
||||
{
|
||||
name: "em dash",
|
||||
text: `—`,
|
||||
want: 1,
|
||||
},
|
||||
{
|
||||
name: "en dash",
|
||||
text: `–`,
|
||||
want: 1,
|
||||
},
|
||||
{
|
||||
name: "emoji",
|
||||
text: `👍`,
|
||||
want: 2,
|
||||
},
|
||||
{
|
||||
name: "accent character",
|
||||
text: `é́́`,
|
||||
want: 1,
|
||||
},
|
||||
{
|
||||
name: "color codes",
|
||||
text: "\x1b[0;31mred\x1b[0m",
|
||||
want: 3,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := DisplayWidth(tt.text); got != tt.want {
|
||||
t.Errorf("DisplayWidth() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -6,8 +6,8 @@ import (
|
|||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/cli/cli/v2/internal/text"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/pkg/text"
|
||||
)
|
||||
|
||||
type TablePrinter interface {
|
||||
|
|
|
|||
|
|
@ -1,25 +0,0 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/mattn/go-isatty"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
var IsTerminal = func(f *os.File) bool {
|
||||
return isatty.IsTerminal(f.Fd()) || IsCygwinTerminal(f)
|
||||
}
|
||||
|
||||
func IsCygwinTerminal(f *os.File) bool {
|
||||
return isatty.IsCygwinTerminal(f.Fd())
|
||||
}
|
||||
|
||||
var TerminalSize = func(w interface{}) (int, int, error) {
|
||||
if f, isFile := w.(*os.File); isFile {
|
||||
return term.GetSize(int(f.Fd()))
|
||||
}
|
||||
|
||||
return 0, 0, fmt.Errorf("%v is not a file", w)
|
||||
}
|
||||
|
|
@ -2,98 +2,11 @@ package utils
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
func Pluralize(num int, thing string) string {
|
||||
if num == 1 {
|
||||
return fmt.Sprintf("%d %s", num, thing)
|
||||
}
|
||||
return fmt.Sprintf("%d %ss", num, thing)
|
||||
}
|
||||
|
||||
func fmtDuration(amount int, unit string) string {
|
||||
return fmt.Sprintf("about %s ago", Pluralize(amount, unit))
|
||||
}
|
||||
|
||||
func FuzzyAgo(ago time.Duration) string {
|
||||
if ago < time.Minute {
|
||||
return "less than a minute ago"
|
||||
}
|
||||
if ago < time.Hour {
|
||||
return fmtDuration(int(ago.Minutes()), "minute")
|
||||
}
|
||||
if ago < 24*time.Hour {
|
||||
return fmtDuration(int(ago.Hours()), "hour")
|
||||
}
|
||||
if ago < 30*24*time.Hour {
|
||||
return fmtDuration(int(ago.Hours())/24, "day")
|
||||
}
|
||||
if ago < 365*24*time.Hour {
|
||||
return fmtDuration(int(ago.Hours())/24/30, "month")
|
||||
}
|
||||
|
||||
return fmtDuration(int(ago.Hours()/24/365), "year")
|
||||
}
|
||||
|
||||
func FuzzyAgoAbbr(now time.Time, createdAt time.Time) string {
|
||||
ago := now.Sub(createdAt)
|
||||
|
||||
if ago < time.Hour {
|
||||
return fmt.Sprintf("%d%s", int(ago.Minutes()), "m")
|
||||
}
|
||||
if ago < 24*time.Hour {
|
||||
return fmt.Sprintf("%d%s", int(ago.Hours()), "h")
|
||||
}
|
||||
if ago < 30*24*time.Hour {
|
||||
return fmt.Sprintf("%d%s", int(ago.Hours())/24, "d")
|
||||
}
|
||||
|
||||
return createdAt.Format("Jan _2, 2006")
|
||||
}
|
||||
|
||||
func Humanize(s string) string {
|
||||
// Replaces - and _ with spaces.
|
||||
replace := "_-"
|
||||
h := func(r rune) rune {
|
||||
if strings.ContainsRune(replace, r) {
|
||||
return ' '
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
return strings.Map(h, s)
|
||||
}
|
||||
|
||||
func IsURL(s string) bool {
|
||||
return strings.HasPrefix(s, "http:/") || strings.HasPrefix(s, "https:/")
|
||||
}
|
||||
|
||||
func DisplayURL(urlStr string) string {
|
||||
u, err := url.Parse(urlStr)
|
||||
if err != nil {
|
||||
return urlStr
|
||||
}
|
||||
return u.Hostname() + u.Path
|
||||
}
|
||||
|
||||
// Maximum length of a URL: 8192 bytes
|
||||
func ValidURL(urlStr string) bool {
|
||||
return len(urlStr) < 8192
|
||||
}
|
||||
|
||||
func StringInSlice(a string, slice []string) bool {
|
||||
for _, b := range slice {
|
||||
if b == a {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func IsDebugEnabled() (bool, string) {
|
||||
debugValue, isDebugSet := os.LookupEnv("GH_DEBUG")
|
||||
legacyDebugValue := os.Getenv("DEBUG")
|
||||
|
|
@ -114,3 +27,11 @@ func IsDebugEnabled() (bool, string) {
|
|||
return true, debugValue
|
||||
}
|
||||
}
|
||||
|
||||
var TerminalSize = func(w interface{}) (int, int, error) {
|
||||
if f, isFile := w.(*os.File); isFile {
|
||||
return term.GetSize(int(f.Fd()))
|
||||
}
|
||||
|
||||
return 0, 0, fmt.Errorf("%v is not a file", w)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,63 +0,0 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestFuzzyAgo(t *testing.T) {
|
||||
cases := map[string]string{
|
||||
"1s": "less than a minute ago",
|
||||
"30s": "less than a minute ago",
|
||||
"1m08s": "about 1 minute ago",
|
||||
"15m0s": "about 15 minutes ago",
|
||||
"59m10s": "about 59 minutes ago",
|
||||
"1h10m02s": "about 1 hour ago",
|
||||
"15h0m01s": "about 15 hours ago",
|
||||
"30h10m": "about 1 day ago",
|
||||
"50h": "about 2 days ago",
|
||||
"720h05m": "about 1 month ago",
|
||||
"3000h10m": "about 4 months ago",
|
||||
"8760h59m": "about 1 year ago",
|
||||
"17601h59m": "about 2 years ago",
|
||||
"262800h19m": "about 30 years ago",
|
||||
}
|
||||
|
||||
for duration, expected := range cases {
|
||||
d, e := time.ParseDuration(duration)
|
||||
if e != nil {
|
||||
t.Errorf("failed to create a duration: %s", e)
|
||||
}
|
||||
|
||||
fuzzy := FuzzyAgo(d)
|
||||
if fuzzy != expected {
|
||||
t.Errorf("unexpected fuzzy duration value: %s for %s", fuzzy, duration)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFuzzyAgoAbbr(t *testing.T) {
|
||||
const form = "2006-Jan-02 15:04:05"
|
||||
now, _ := time.Parse(form, "2020-Nov-22 14:00:00")
|
||||
|
||||
cases := map[string]string{
|
||||
"2020-Nov-22 14:00:00": "0m",
|
||||
"2020-Nov-22 13:59:00": "1m",
|
||||
"2020-Nov-22 13:30:00": "30m",
|
||||
"2020-Nov-22 13:00:00": "1h",
|
||||
"2020-Nov-22 02:00:00": "12h",
|
||||
"2020-Nov-21 14:00:00": "1d",
|
||||
"2020-Nov-07 14:00:00": "15d",
|
||||
"2020-Oct-24 14:00:00": "29d",
|
||||
"2020-Oct-23 14:00:00": "Oct 23, 2020",
|
||||
"2019-Nov-22 14:00:00": "Nov 22, 2019",
|
||||
}
|
||||
|
||||
for createdAt, expected := range cases {
|
||||
d, _ := time.Parse(form, createdAt)
|
||||
fuzzy := FuzzyAgoAbbr(now, d)
|
||||
if fuzzy != expected {
|
||||
t.Errorf("unexpected fuzzy duration abbr value: %s for %s", fuzzy, createdAt)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue