From da8042199e8283798eaea244465d3f6f45bde8f8 Mon Sep 17 00:00:00 2001 From: Haruaki Tamada Date: Mon, 8 Apr 2019 14:36:21 +0900 Subject: [PATCH 01/15] ref #43 support colorized output on list, status, and group list command. However, colorized output breaks position indentation. --- Gopkg.lock | 9 +++ Gopkg.toml | 4 ++ common/colorable_output.go | 102 ++++++++++++++++++++++++++++++++ common/colorable_output_test.go | 29 +++++++++ common/config.go | 8 ++- common/config_test.go | 3 + group/group_cmd.go | 10 ++-- list/list_cmd.go | 22 +++---- status/status.go | 6 +- status/status_cmd.go | 6 +- 10 files changed, 174 insertions(+), 25 deletions(-) create mode 100644 common/colorable_output.go create mode 100644 common/colorable_output_test.go diff --git a/Gopkg.lock b/Gopkg.lock index 7255713..b03b1b5 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -48,6 +48,14 @@ revision = "5b77d2a35fb0ede96d138fc9a99f5c9b6aef11b4" version = "v1.7.0" +[[projects]] + digest = "1:723aa81f50d50bb5e5e67c3076c0cb498c07cbb74dbcac462c2e8973a84df90c" + name = "github.com/gookit/color" + packages = ["."] + pruneopts = "UT" + revision = "03c3187105615d576d255124f688cee79b8e25b5" + version = "v1.1.6" + [[projects]] digest = "1:0ade334594e69404d80d9d323445d2297ff8161637f9b2d347cc6973d2d6f05b" name = "github.com/hashicorp/errwrap" @@ -284,6 +292,7 @@ analyzer-version = 1 input-imports = [ "github.com/dustin/go-humanize", + "github.com/gookit/color", "github.com/mitchellh/cli", "github.com/mitchellh/go-homedir", "gopkg.in/src-d/go-git.v4", diff --git a/Gopkg.toml b/Gopkg.toml index 327ba6b..9fc2dfd 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -41,6 +41,10 @@ name = "github.com/mitchellh/go-homedir" version = "1.1.0" +[[constraint]] + name = "github.com/gookit/color" + version = "1.1.5" + [prune] go-tests = true unused-packages = true diff --git a/common/colorable_output.go b/common/colorable_output.go new file mode 100644 index 0000000..ea0963f --- /dev/null +++ b/common/colorable_output.go @@ -0,0 +1,102 @@ +package common + +import ( + "strings" + + "github.com/gookit/color" +) + +var repoColorFunc func(r string) string = nil +var groupColorFunc func(r string) string = nil + +var repoColor = "" +var groupColor = "" + +var supportedForeColor = map[string]color.Color{ + "red": color.FgRed, + "black": color.FgBlack, + "white": color.FgWhite, + "cyan": color.FgCyan, + "blue": color.FgBlue, + "green": color.FgGreen, + "yellow": color.FgYellow, + "magenta": color.FgMagenta, +} + +var supportedBackColor = map[string]color.Color{ + "red": color.BgRed, + "black": color.BgBlack, + "white": color.BgWhite, + "cyan": color.BgCyan, + "blue": color.BgBlue, + "green": color.BgGreen, + "yellow": color.BgYellow, + "magenta": color.BgMagenta, +} + +func RepositoryID(repoID string, config *Config) string { + if repoColorFunc == nil { + updateColor(config) + } + return repoColorFunc(repoID) +} + +func GroupName(groupName string, config *Config) string { + if groupColorFunc == nil { + updateColor(config) + } + return groupColorFunc(groupName) +} + +func parse(colorSettings string) { + var colors = strings.Split(colorSettings, "+") + repoColor = "" + groupColor = "" + for _, c := range colors { + if strings.HasPrefix(c, "repository:") { + repoColor = color.ParseCodeFromAttr(strings.ReplaceAll(c, "repository:", "")) + } else if strings.HasPrefix(c, "group:") { + groupColor = color.ParseCodeFromAttr(strings.ReplaceAll(c, "group:", "")) + } + } + updateFuncs() +} + +func updateFuncs() { + updateRepoFunc(repoColor) + updateGroupFunc(groupColor) +} + +func updateRepoFunc(repoColor string) { + if repoColor != "" { + var printer = color.NewPrinter(repoColor) + repoColorFunc = func(r string) string { + return printer.Sprint(r) + } + } else { + repoColorFunc = func(r string) string { + return r + } + } +} + +func updateGroupFunc(groupColor string) { + if groupColor != "" { + var printer = color.NewPrinter(groupColor) + groupColorFunc = func(r string) string { + return printer.Sprint(r) + } + } else { + groupColorFunc = func(r string) string { + return r + } + } +} + +func updateColor(config *Config) { + var colorSetting = config.GetValue(RrhColor) + if colorSetting != "" { + parse(colorSetting) + } + updateFuncs() +} diff --git a/common/colorable_output_test.go b/common/colorable_output_test.go new file mode 100644 index 0000000..2a5203d --- /dev/null +++ b/common/colorable_output_test.go @@ -0,0 +1,29 @@ +package common + +import ( + "fmt" + "testing" +) + +func TestParse(t *testing.T) { + var testcases = []struct { + givenString string + repo string + group string + }{ + {"repository:fg=white;op=bold,underscore", "37;1;4", ""}, + {"group: fg=red+repository:fg=white;op=bold,underscore", "37;1;4", "31"}, + {"group: fg=red+group: fg=blue", "", "34"}, + } + + for _, tc := range testcases { + parse(tc.givenString) + if repoColor != tc.repo { + t.Errorf("%v: repo color did not match, wont: %s, got: %s", tc.givenString, tc.repo, repoColor) + } + if groupColor != tc.group { + t.Errorf("%v: group color did not match, wont: %s, got: %s", tc.givenString, tc.group, groupColor) + } + fmt.Printf("repo: %s, group: %s\n", RepositoryID("repository", nil), GroupName("groupName", nil)) + } +} diff --git a/common/config.go b/common/config.go index a85e132..590a79a 100644 --- a/common/config.go +++ b/common/config.go @@ -22,6 +22,7 @@ const ( RrhAutoDeleteGroup = "RRH_AUTO_DELETE_GROUP" RrhAutoCreateGroup = "RRH_AUTO_CREATE_GROUP" RrhCloneDestination = "RRH_CLONE_DESTINATION" + RrhColor = "RRH_COLOR" RrhConfigPath = "RRH_CONFIG_PATH" RrhDatabasePath = "RRH_DATABASE_PATH" RrhDefaultGroupName = "RRH_DEFAULT_GROUP_NAME" @@ -32,9 +33,9 @@ const ( ) var availableLabels = []string{ - RrhAutoCreateGroup, RrhAutoDeleteGroup, RrhCloneDestination, RrhConfigPath, - RrhDatabasePath, RrhDefaultGroupName, RrhHome, RrhOnError, RrhSortOnUpdating, - RrhTimeFormat, + RrhAutoCreateGroup, RrhAutoDeleteGroup, RrhCloneDestination, RrhColor, + RrhConfigPath, RrhDatabasePath, RrhDefaultGroupName, RrhHome, RrhOnError, + RrhSortOnUpdating, RrhTimeFormat, } var boolLabels = []string{ RrhAutoCreateGroup, RrhAutoDeleteGroup, RrhSortOnUpdating, @@ -85,6 +86,7 @@ var defaultValues = Config{ RrhAutoCreateGroup: "false", RrhAutoDeleteGroup: "false", RrhCloneDestination: ".", + RrhColor: "", RrhConfigPath: "${RRH_HOME}/config.json", RrhDatabasePath: "${RRH_HOME}/database.json", RrhDefaultGroupName: "no-group", diff --git a/common/config_test.go b/common/config_test.go index 9a49e6f..a323a25 100644 --- a/common/config_test.go +++ b/common/config_test.go @@ -107,6 +107,7 @@ func ExampleCommand() { // RRH_AUTO_CREATE_GROUP: true (config_file) // RRH_AUTO_DELETE_GROUP: false (config_file) // RRH_CLONE_DESTINATION: . (default) + // RRH_COLOR: (default) // RRH_CONFIG_PATH: ../testdata/config.json (environment) // RRH_DATABASE_PATH: ../testdata/tmp.json (environment) // RRH_DEFAULT_GROUP_NAME: no-group (default) @@ -125,6 +126,7 @@ func ExampleCommand_Run() { // RRH_AUTO_CREATE_GROUP: true (config_file) // RRH_AUTO_DELETE_GROUP: false (config_file) // RRH_CLONE_DESTINATION: . (default) + // RRH_COLOR: (default) // RRH_CONFIG_PATH: ../testdata/config.json (environment) // RRH_DATABASE_PATH: ../testdata/database.json (environment) // RRH_DEFAULT_GROUP_NAME: no-group (default) @@ -143,6 +145,7 @@ func Example_listCommand_Run() { // RRH_AUTO_CREATE_GROUP: true (config_file) // RRH_AUTO_DELETE_GROUP: false (config_file) // RRH_CLONE_DESTINATION: . (default) + // RRH_COLOR: (default) // RRH_CONFIG_PATH: ../testdata/config.json (environment) // RRH_DATABASE_PATH: ../testdata/database.json (default) // RRH_DEFAULT_GROUP_NAME: no-group (default) diff --git a/group/group_cmd.go b/group/group_cmd.go index f472fee..80d4096 100644 --- a/group/group_cmd.go +++ b/group/group_cmd.go @@ -240,8 +240,8 @@ func printRepositoryCount(count int) { } } -func (glc *listCommand) printResult(result Result, options *listOptions) { - fmt.Print(result.Name) +func (glc *listCommand) printResult(result Result, options *listOptions, config *common.Config) { + fmt.Print(common.GroupName(result.Name, config)) if !options.nameOnly && options.desc { fmt.Printf(",%s", result.Description) } @@ -254,9 +254,9 @@ func (glc *listCommand) printResult(result Result, options *listOptions) { fmt.Println() } -func (glc *listCommand) printAll(results []Result, options *listOptions) { +func (glc *listCommand) printAll(results []Result, options *listOptions, config *common.Config) { for _, result := range results { - glc.printResult(result, options) + glc.printResult(result, options, config) } } @@ -275,7 +275,7 @@ func (glc *listCommand) Run(args []string) int { return 2 } var results = glc.listGroups(db, listOption) - glc.printAll(results, listOption) + glc.printAll(results, listOption, config) return 0 } diff --git a/list/list_cmd.go b/list/list_cmd.go index 3d56423..fb1b935 100644 --- a/list/list_cmd.go +++ b/list/list_cmd.go @@ -86,8 +86,8 @@ func (options *options) generateFormatString(repos []Repo) string { return fmt.Sprintf(" %%-%ds", max) } -func (options *options) printRepo(repo Repo, result Result, formatString string) { - fmt.Printf(formatString, repo.Name) +func (options *options) printRepo(repo Repo, result Result, formatString string, config *common.Config) { + fmt.Printf(formatString, common.RepositoryID(repo.Name, config)) if options.localPath || options.all { fmt.Printf(" %s", repo.Path) } @@ -104,17 +104,17 @@ func (options *options) isPrintSimple(result Result) bool { return !options.noOmit && result.OmitList && len(options.args) == 0 } -func (options *options) printGroupName(result Result) int { +func (options *options) printGroupName(result Result, config *common.Config) int { if len(result.Repos) == 1 { - fmt.Printf("%s (1 repository)\n", result.GroupName) + fmt.Printf("%s (1 repository)\n", common.GroupName(result.GroupName, config)) } else { - fmt.Printf("%s (%d repositories)\n", result.GroupName, len(result.Repos)) + fmt.Printf("%s (%d repositories)\n", common.GroupName(result.GroupName, config), len(result.Repos)) } return len(result.Repos) } -func (options *options) printResult(result Result) int { - var repoCount = options.printGroupName(result) +func (options *options) printResult(result Result, config *common.Config) int { + var repoCount = options.printGroupName(result, config) if !options.isPrintSimple(result) { if options.description || options.all { fmt.Printf(" Description %s", result.Description) @@ -122,7 +122,7 @@ func (options *options) printResult(result Result) int { } var formatString = options.generateFormatString(result.Repos) for _, repo := range result.Repos { - options.printRepo(repo, result, formatString) + options.printRepo(repo, result, formatString, config) } } return repoCount @@ -157,7 +157,7 @@ func printGroupAndRepoCount(groupCount int, repoCount int) { fmt.Printf("%d %s, %d %s\n", groupCount, groupLabel, repoCount, repoLabel) } -func (options *options) printResults(results []Result) int { +func (options *options) printResults(results []Result, config *common.Config) int { if options.csv { return options.printResultsAsCsv(results) } else if options.repoNameOnly || options.groupRepoName { @@ -165,7 +165,7 @@ func (options *options) printResults(results []Result) int { } var repoCount int for _, result := range results { - repoCount += options.printResult(result) + repoCount += options.printResult(result, config) } printGroupAndRepoCount(len(results), repoCount) return 0 @@ -191,7 +191,7 @@ func (list *Command) Run(args []string) int { fmt.Println(err.Error()) return 3 } - return list.options.printResults(results) + return list.options.printResults(results, config) } /* diff --git a/status/status.go b/status/status.go index 6414827..bd727a4 100644 --- a/status/status.go +++ b/status/status.go @@ -169,14 +169,14 @@ func (status *Command) executeStatusOnRepository(db *common.Database, name relat } func (status *Command) executeStatus(db *common.Database, name string) ([]result, []error) { - if db.HasRepository(name) { + if db.HasGroup(name) { + return status.executeStatusOnGroup(db, name) + } else if db.HasRepository(name) { var results, err = status.executeStatusOnRepository(db, relation{"unknown-group", name}) if err != nil { return results, []error{err} } return results, nil - } else if db.HasGroup(name) { - return status.executeStatusOnGroup(db, name) } return nil, []error{fmt.Errorf("%s: group and repository not found", name)} } diff --git a/status/status_cmd.go b/status/status_cmd.go index 798467f..0a05190 100644 --- a/status/status_cmd.go +++ b/status/status_cmd.go @@ -74,15 +74,15 @@ func (status *Command) printResultInCsv(results []result, config *common.Config) func (status *Command) printResult(results []result, config *common.Config) { var groupName = results[0].GroupName var repositoryName = results[0].RepositoryName - fmt.Printf("%s\n %s\n", groupName, repositoryName) + fmt.Printf("%s\n %s\n", common.GroupName(groupName, config), common.RepositoryID(repositoryName, config)) var fmtString = status.parseFmtString(results) for _, result := range results { if groupName != result.GroupName { - fmt.Println(result.GroupName) + fmt.Println(common.GroupName(result.GroupName, config)) groupName = result.GroupName } if repositoryName != result.RepositoryName { - fmt.Printf(" %s\n", result.RepositoryName) + fmt.Printf(" %s\n", common.RepositoryID(result.RepositoryName, config)) repositoryName = result.RepositoryName } var time = "" From 98b860cd00d0a25f93f7e82a54428cb9362b130f Mon Sep 17 00:00:00 2001 From: Haruaki Tamada Date: Mon, 8 Apr 2019 15:12:59 +0900 Subject: [PATCH 02/15] update README.md --- README.md | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index deaef01..921caaa 100644 --- a/README.md +++ b/README.md @@ -386,34 +386,34 @@ We can see those variables by running `rrh config` sub-command. ### `RRH_HOME` * specifies the location of the RRH database and config file. -* default: `/Users/tamada/.rrh` +* Default: `/Users/tamada/.rrh` ### `RRH_CONFIG_PATH` * specifies the location of the location path. * RRH ignores to specify `RRH_CONFIG_PATH` in the config file. This variable availables only environment variable. -* default: `${RRH_HOME}/config.json` +* Default: `${RRH_HOME}/config.json` ### `RRH_DATABASE_PATH` * specifies the location of the database path. -* default: `${RRH_HOME}/database.json` +* Default: `${RRH_HOME}/database.json` ### `RRH_DEFAULT_GROUP_NAME` * specifies the default group name. -* default: `no-group` +* Default: `no-group` ### `RRH_CLONE_DESTINATION` * specifies the destination by cloning the repository. -* default: `.` +* Default: `.` ### `RRH_ON_ERROR` * specifies the behaviors of RRH on error. -* default: `WARN` +* Default: `WARN` * Available values: `FAIL_IMMEDIATELY`, `FAIL`, `WARN`, and `IGNORE` * `FAIL_IMMEDIATELY` * reports error immediately and quits RRH with a non-zero status. @@ -427,7 +427,7 @@ We can see those variables by running `rrh config` sub-command. ### `RRH_TIME_FORMAT` * specifies the time format for `status` command. -* default: `relative` +* Default: `relative` * Available value: `relative` and the time format for Go lang. * `relative` * shows times by humanized format (e.g., 2 weeks ago) @@ -438,17 +438,33 @@ We can see those variables by running `rrh config` sub-command. ### `RRH_AUTO_CREATE_GROUP` * specifies to create the group when the not existing group was specified, and it needs to create. -* default: false +* Default: false ### `RRH_AUTO_DELETE_GROUP` * specifies to delete the group when some group was no more needed. -* default: false +* Default: false ### `RRH_SORT_ON_UPDATING` * specifies to sort database entries on updating database. -* default: false +* Default: false + +### `RRH_COLOR` + +* specifies the colors of the output. +* Default: `""` (empty string) +* Format: `"repository:fg=;bg=;op=