Skip to content

Commit

Permalink
tiup & playground: Tidy output (#2335)
Browse files Browse the repository at this point in the history
  • Loading branch information
breezewish authored Dec 19, 2023
1 parent ced2d6d commit 93560a1
Show file tree
Hide file tree
Showing 9 changed files with 270 additions and 26 deletions.
19 changes: 12 additions & 7 deletions components/playground/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import (
logprinter "github.com/pingcap/tiup/pkg/logger/printer"
"github.com/pingcap/tiup/pkg/repository"
"github.com/pingcap/tiup/pkg/telemetry"
"github.com/pingcap/tiup/pkg/tui/colorstr"
"github.com/pingcap/tiup/pkg/utils"
"github.com/pingcap/tiup/pkg/version"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -235,19 +236,23 @@ Examples:
if !semver.IsValid(options.Version) {
version, err := env.V1Repository().ResolveComponentVersion(spec.ComponentTiDB, options.Version)
if err != nil {
return errors.Annotate(err, fmt.Sprintf("can not expand version %s to a valid semver string", options.Version))
return errors.Annotate(err, fmt.Sprintf("Cannot resolve version %s to a valid semver string", options.Version))
}
// for nightly, may not use the same version for cluster
if options.Version == "nightly" {
version = "nightly"
}
fmt.Println(color.YellowString(`Using the version %s for version constraint "%s".

If you'd like to use a TiDB version other than %s, cancel and retry with the following arguments:
Specify version manually: tiup playground <version>
Specify version range: tiup playground ^5
The nightly version: tiup playground nightly
`, version, options.Version, version))
if options.Version != version.String() {
colorstr.Fprintf(os.Stderr, `
Note: Version constraint [bold]%s[reset] is resolved to [green][bold]%s[reset]. If you'd like to use other versions:
Use exact version: [tiup_command]tiup playground v7.1.0[reset]
Use version range: [tiup_command]tiup playground ^5[reset]
Use nightly: [tiup_command]tiup playground nightly[reset]
`, options.Version, version.String())
}

options.Version = version.String()
}
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ require (
github.com/juju/ansiterm v1.0.0
github.com/mattn/go-runewidth v0.0.14
github.com/minio/minio-go/v7 v7.0.52
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db
github.com/otiai10/copy v1.9.0
github.com/pelletier/go-toml v1.9.5
github.com/pingcap/check v0.0.0-20211026125417-57bd13f7b5f0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ github.com/minio/minio-go/v7 v7.0.52 h1:8XhG36F6oKQUDDSuz6dY3rioMzovKjW40W6ANuN0
github.com/minio/minio-go/v7 v7.0.52/go.mod h1:IbbodHyjUAguneyucUaahv+VMNs/EOTV9du7A7/Z3HU=
github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g=
github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
Expand Down
69 changes: 50 additions & 19 deletions pkg/exec/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,25 +21,32 @@ import (
"os/signal"
"path/filepath"
"strings"
"sync"
"syscall"
"time"

"github.com/fatih/color"
"github.com/pingcap/errors"
"github.com/pingcap/tiup/pkg/environment"
"github.com/pingcap/tiup/pkg/localdata"
"github.com/pingcap/tiup/pkg/telemetry"
"github.com/pingcap/tiup/pkg/tui/colorstr"
"github.com/pingcap/tiup/pkg/utils"
"github.com/pingcap/tiup/pkg/version"
"golang.org/x/mod/semver"
)

// Skip displaying "Starting component ..." message for some commonly used components.
var skipStartingMessages = map[string]bool{
"playground": true,
"cluster": true,
}

// RunComponent start a component and wait it
func RunComponent(env *environment.Environment, tag, spec, binPath string, args []string) error {
component, version := environment.ParseCompVersion(spec)

if version == "" {
cmdCheckUpdate(component, version, 2)
cmdCheckUpdate(component, version)
}

binPath, err := PrepareBinary(component, version, binPath)
Expand Down Expand Up @@ -74,7 +81,10 @@ func RunComponent(env *environment.Environment, tag, spec, binPath string, args
return err
}

fmt.Fprintf(os.Stderr, "Starting component `%s`: %s\n", component, strings.Join(c.Args, " "))
if skip, ok := skipStartingMessages[component]; !skip || !ok {
colorstr.Fprintf(os.Stderr, "Starting component [bold]%s[reset]: %s\n", component, strings.Join(c.Args, " "))
}

err = c.Start()
if err != nil {
return errors.Annotatef(err, "Failed to start component `%s`", component)
Expand Down Expand Up @@ -170,37 +180,58 @@ func PrepareCommand(p *PrepareCommandParams) (*exec.Cmd, error) {
return c, nil
}

func cmdCheckUpdate(component string, version utils.Version, timeoutSec int) {
fmt.Fprintf(os.Stderr, "tiup is checking updates for component %s ...", component)
updateC := make(chan string)
// timeout for check update
func cmdCheckUpdate(component string, version utils.Version) {
const (
slowTimeout = 1 * time.Second // Timeout to display checking message
cancelTimeout = 2 * time.Second // Timeout to cancel the check
)

// This mutex is used for protecting flag as well as stdout
mu := sync.Mutex{}
isCheckFinished := false

result := make(chan string, 1)

go func() {
time.Sleep(time.Duration(timeoutSec) * time.Second)
updateC <- color.YellowString("timeout(%ds)!", timeoutSec)
time.Sleep(slowTimeout)
mu.Lock()
defer mu.Unlock()
if !isCheckFinished {
colorstr.Fprintf(os.Stderr, "Checking updates for component [bold]%s[reset]... ", component)
}
}()

go func() {
time.Sleep(cancelTimeout)
result <- colorstr.Sprintf("[yellow]Timedout (after %s)", cancelTimeout)
}()

go func() {
var updateInfo string
latestV, _, err := environment.GlobalEnv().V1Repository().LatestStableVersion(component, false)
if err != nil {
result <- ""
return
}
selectVer, _ := environment.GlobalEnv().SelectInstalledVersion(component, version)

if semver.Compare(selectVer.String(), latestV.String()) < 0 {
updateInfo = fmt.Sprint(color.YellowString(`
A new version of %[1]s is available:
The latest version: %[2]s
Local installed version: %[3]s
Update current component: tiup update %[1]s
Update all components: tiup update --all
result <- colorstr.Sprintf(`
[yellow]A new version of [bold]%[1]s[reset][yellow] is available:[reset] [red][bold]%[2]s[reset] -> [green][bold]%[3]s[reset]
To update this component: [tiup_command]tiup update %[1]s[reset]
To update all components: [tiup_command]tiup update --all[reset]
`,
component, latestV.String(), selectVer.String()))
component, selectVer.String(), latestV.String())
}
updateC <- updateInfo
}()

fmt.Fprintln(os.Stderr, <-updateC)
s := <-result
mu.Lock()
defer mu.Unlock()
isCheckFinished = true
if len(s) > 0 {
fmt.Fprintln(os.Stderr, s)
}
}

// PrepareBinary use given binpath or download from tiup mirror
Expand Down
28 changes: 28 additions & 0 deletions pkg/tui/colorstr/builtin_tokens.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright 2024 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.

package colorstr

import (
"fmt"

"github.com/mitchellh/colorstring"
)

func init() {
// Register tiup specific color tokens
DefaultTokens.Colors["tiup_command"] = fmt.Sprintf("%s;%s",
colorstring.DefaultColors["light_cyan"],
colorstring.DefaultColors["bold"],
)
}
91 changes: 91 additions & 0 deletions pkg/tui/colorstr/color.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// Copyright 2024 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.

// Package colorstr interprets the input format containing color tokens like `[blue]hello [red]world`
// as the text "hello world" in two colors.
//
// Just like tokens in the fmt package (e.g. '%s'), color tokens will only be effective when specified
// as the format parameter. Tokens not in the format parameter will not be interpreted.
//
// colorstr.DefaultTokens.Printf("[blue]hello") ==> (a blue hello)
// colorstr.DefaultTokens.Printf("[ahh]") ==> "[ahh]"
//
// Color tokens in the Print arguments will never be interpreted. It can be useful to pass user inputs there.
package colorstr

import (
"fmt"
"io"

"github.com/mitchellh/colorstring"
)

type colorTokens struct {
colorstring.Colorize
}

// Note: Print, Println, Fprint, Fprintln are intentionally not implemented, as we would like to
// limit the usage of color token to be only placed in the "format" part.

// Printf is a convenience wrapper for fmt.Printf with support for color codes.
// Only color codes in the format param will be respected.
func (c colorTokens) Printf(format string, a ...any) (n int, err error) {
return fmt.Printf(c.Color(format), a...)
}

// Fprintf is a convenience wrapper for fmt.Fprintf with support for color codes.
// Only color codes in the format param will be respected.
func (c colorTokens) Fprintf(w io.Writer, format string, a ...any) (n int, err error) {
return fmt.Fprintf(w, c.Color(format), a...)
}

// Sprintf is a convenience wrapper for fmt.Sprintf with support for color codes.
// Only color codes in the format param will be respected.
func (c colorTokens) Sprintf(format string, a ...any) string {
return fmt.Sprintf(c.Color(format), a...)
}

// DefaultTokens uses default color tokens.
var DefaultTokens = (func() colorTokens {
// TODO: Respect NO_COLOR env
// TODO: Add more color tokens here
colors := make(map[string]string)
for k, v := range colorstring.DefaultColors {
colors[k] = v
}
return colorTokens{
Colorize: colorstring.Colorize{
Colors: colors,
Disable: false,
Reset: true,
},
}
})()

// Printf is a convenience wrapper for fmt.Printf with support for color codes.
// Only color codes in the format param will be respected.
func Printf(format string, a ...any) (n int, err error) {
return DefaultTokens.Printf(format, a...)
}

// Fprintf is a convenience wrapper for fmt.Fprintf with support for color codes.
// Only color codes in the format param will be respected.
func Fprintf(w io.Writer, format string, a ...any) (n int, err error) {
return DefaultTokens.Fprintf(w, format, a...)
}

// Sprintf is a convenience wrapper for fmt.Sprintf with support for color codes.
// Only color codes in the format param will be respected.
func Sprintf(format string, a ...any) string {
return DefaultTokens.Sprintf(format, a...)
}
31 changes: 31 additions & 0 deletions pkg/tui/colorstr/color_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright 2024 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.

package colorstr

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestDefaultTokens(t *testing.T) {
require.Equal(t, "[blue", DefaultTokens.Sprintf("[blue"))
require.Equal(t, "hello", DefaultTokens.Sprintf("hello"))
require.Equal(t, "\x1B[34mhello\x1B[0m", DefaultTokens.Sprintf("[blue]hello"))
require.Equal(t, "\x1B[34mhello\x1B[0m\x1B[0m", DefaultTokens.Sprintf("[blue]hello[reset]"))
require.Equal(t, "\x1B[34mhello\x1B[0mfoo\x1B[0m", DefaultTokens.Sprintf("[blue]hello[reset]foo"))
require.Equal(t, "\x1B[34mhello\x1B[31mfoo\x1B[0m", DefaultTokens.Sprintf("[blue]hello[red]foo"))
require.Equal(t, "\x1B[34mhello [blue]\x1B[0m", DefaultTokens.Sprintf("[blue]hello %s", "[blue]"))
require.Equal(t, "[ahh]hello", DefaultTokens.Sprintf("[ahh]hello"))
}
30 changes: 30 additions & 0 deletions pkg/tui/colorstr/test_util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright 2024 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.

package colorstr

import (
"testing"

"github.com/stretchr/testify/require"
)

// RequireEqualColorToken compares whether the actual string is equal to the expected string after color processing.
func RequireEqualColorToken(t *testing.T, expectColorTokens string, actualString string) {
require.Equal(t, DefaultTokens.Color(expectColorTokens), actualString)
}

// RequireNotEqualColorToken compares whether the actual string is not equal to the expected string after color processing.
func RequireNotEqualColorToken(t *testing.T, expectColorTokens string, actualString string) {
require.NotEqual(t, DefaultTokens.Color(expectColorTokens), actualString)
}
25 changes: 25 additions & 0 deletions pkg/tui/colorstr/test_util_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright 2024 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.

package colorstr

import (
"testing"
)

func TestRequireEqualColorToken(t *testing.T) {
RequireEqualColorToken(t, "[red]hello", DefaultTokens.Color("[red]hello"))
RequireNotEqualColorToken(t, "[yellow]hello", DefaultTokens.Color("[red]hello"))
RequireEqualColorToken(t, "[red]hello[reset]", DefaultTokens.Color("[red]hello[reset]"))
RequireNotEqualColorToken(t, "[red]hello", DefaultTokens.Color("[red]hello[reset]"))
}

0 comments on commit 93560a1

Please sign in to comment.