diff --git a/internal/config/config.go b/internal/config/config.go index e7534dfdb..c621e7fdf 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -14,7 +14,12 @@ import ( ghConfig "github.com/cli/go-gh/v2/pkg/config" ) +// It is important to note that some of these configuration setting keys are used outside of `cli/cli` +// such as `accessible_colors`, `browser`, and `http_unix_socket` used in `cli/go-gh`. +// +// These configuration settings are defined here to avoid `cli/cli` being changed unexpectedly. const ( + accessibleColorsKey = "accessible_colors" aliasesKey = "aliases" browserKey = "browser" colorLabelsKey = "color_labels" @@ -109,6 +114,11 @@ func (c *cfg) Authentication() gh.AuthConfig { return &AuthConfig{cfg: c.cfg} } +func (c *cfg) AccessibleColors(hostname string) gh.ConfigEntry { + // Intentionally panic if there is no user provided value or default value (which would be a programmer error) + return c.GetOrDefault(hostname, accessibleColorsKey).Unwrap() +} + func (c *cfg) Browser(hostname string) gh.ConfigEntry { // Intentionally panic if there is no user provided value or default value (which would be a programmer error) return c.GetOrDefault(hostname, browserKey).Unwrap() @@ -540,6 +550,8 @@ http_unix_socket: browser: # Whether to display labels using their RGB hex color codes in terminals that support truecolor. Supported values: enabled, disabled color_labels: disabled +# Whether customizable, 4-bit accessible colors should be used. Supported values: enabled, disabled +accessible_colors: disabled ` type ConfigOption struct { @@ -619,6 +631,15 @@ var Options = []ConfigOption{ return c.ColorLabels(hostname).Value }, }, + { + Key: accessibleColorsKey, + Description: "whether customizable, 4-bit accessible colors should be used", + DefaultValue: "disabled", + AllowedValues: []string{"enabled", "disabled"}, + CurrentValue: func(c gh.Config, hostname string) string { + return c.AccessibleColors(hostname).Value + }, + }, } func HomeDirPath(subdir string) (string, error) { diff --git a/internal/config/stub.go b/internal/config/stub.go index 78073da4a..8b9f14290 100644 --- a/internal/config/stub.go +++ b/internal/config/stub.go @@ -52,6 +52,9 @@ func NewFromString(cfgStr string) *ghmock.ConfigMock { }, } } + mock.AccessibleColorsFunc = func(hostname string) gh.ConfigEntry { + return cfg.AccessibleColors(hostname) + } mock.BrowserFunc = func(hostname string) gh.ConfigEntry { return cfg.Browser(hostname) } diff --git a/internal/gh/gh.go b/internal/gh/gh.go index b17c6bd67..8f3e3cd5b 100644 --- a/internal/gh/gh.go +++ b/internal/gh/gh.go @@ -35,6 +35,8 @@ type Config interface { // Set provides primitive access for setting configuration values, optionally scoped by host. Set(hostname string, key string, value string) + // AccessibleColors returns the configured accessible_colors setting, optionally scoped by host. + AccessibleColors(hostname string) ConfigEntry // Browser returns the configured browser, optionally scoped by host. Browser(hostname string) ConfigEntry // ColorLabels returns the configured color_label setting, optionally scoped by host. diff --git a/internal/gh/mock/config.go b/internal/gh/mock/config.go index b94cb084d..600eea5c1 100644 --- a/internal/gh/mock/config.go +++ b/internal/gh/mock/config.go @@ -19,6 +19,9 @@ var _ gh.Config = &ConfigMock{} // // // make and configure a mocked gh.Config // mockedConfig := &ConfigMock{ +// AccessibleColorsFunc: func(hostname string) gh.ConfigEntry { +// panic("mock out the AccessibleColors method") +// }, // AliasesFunc: func() gh.AliasConfig { // panic("mock out the Aliases method") // }, @@ -74,6 +77,9 @@ var _ gh.Config = &ConfigMock{} // // } type ConfigMock struct { + // AccessibleColorsFunc mocks the AccessibleColors method. + AccessibleColorsFunc func(hostname string) gh.ConfigEntry + // AliasesFunc mocks the Aliases method. AliasesFunc func() gh.AliasConfig @@ -124,6 +130,11 @@ type ConfigMock struct { // calls tracks calls to the methods. calls struct { + // AccessibleColors holds details about calls to the AccessibleColors method. + AccessibleColors []struct { + // Hostname is the hostname argument value. + Hostname string + } // Aliases holds details about calls to the Aliases method. Aliases []struct { } @@ -201,6 +212,7 @@ type ConfigMock struct { Write []struct { } } + lockAccessibleColors sync.RWMutex lockAliases sync.RWMutex lockAuthentication sync.RWMutex lockBrowser sync.RWMutex @@ -219,6 +231,38 @@ type ConfigMock struct { lockWrite sync.RWMutex } +// AccessibleColors calls AccessibleColorsFunc. +func (mock *ConfigMock) AccessibleColors(hostname string) gh.ConfigEntry { + if mock.AccessibleColorsFunc == nil { + panic("ConfigMock.AccessibleColorsFunc: method is nil but Config.AccessibleColors was just called") + } + callInfo := struct { + Hostname string + }{ + Hostname: hostname, + } + mock.lockAccessibleColors.Lock() + mock.calls.AccessibleColors = append(mock.calls.AccessibleColors, callInfo) + mock.lockAccessibleColors.Unlock() + return mock.AccessibleColorsFunc(hostname) +} + +// AccessibleColorsCalls gets all the calls that were made to AccessibleColors. +// Check the length with: +// +// len(mockedConfig.AccessibleColorsCalls()) +func (mock *ConfigMock) AccessibleColorsCalls() []struct { + Hostname string +} { + var calls []struct { + Hostname string + } + mock.lockAccessibleColors.RLock() + calls = mock.calls.AccessibleColors + mock.lockAccessibleColors.RUnlock() + return calls +} + // Aliases calls AliasesFunc. func (mock *ConfigMock) Aliases() gh.AliasConfig { if mock.AliasesFunc == nil { diff --git a/internal/prompter/prompter_mock.go b/internal/prompter/prompter_mock.go index b817a491f..b15f8bf96 100644 --- a/internal/prompter/prompter_mock.go +++ b/internal/prompter/prompter_mock.go @@ -20,28 +20,28 @@ var _ Prompter = &PrompterMock{} // AuthTokenFunc: func() (string, error) { // panic("mock out the AuthToken method") // }, -// ConfirmFunc: func(s string, b bool) (bool, error) { +// ConfirmFunc: func(prompt string, defaultValue bool) (bool, error) { // panic("mock out the Confirm method") // }, -// ConfirmDeletionFunc: func(s string) error { +// ConfirmDeletionFunc: func(requiredValue string) error { // panic("mock out the ConfirmDeletion method") // }, -// InputFunc: func(s1 string, s2 string) (string, error) { +// InputFunc: func(prompt string, defaultValue string) (string, error) { // panic("mock out the Input method") // }, // InputHostnameFunc: func() (string, error) { // panic("mock out the InputHostname method") // }, -// MarkdownEditorFunc: func(s1 string, s2 string, b bool) (string, error) { +// MarkdownEditorFunc: func(prompt string, defaultValue string, blankAllowed bool) (string, error) { // panic("mock out the MarkdownEditor method") // }, // MultiSelectFunc: func(prompt string, defaults []string, options []string) ([]int, error) { // panic("mock out the MultiSelect method") // }, -// PasswordFunc: func(s string) (string, error) { +// PasswordFunc: func(prompt string) (string, error) { // panic("mock out the Password method") // }, -// SelectFunc: func(s1 string, s2 string, strings []string) (int, error) { +// SelectFunc: func(prompt string, defaultValue string, options []string) (int, error) { // panic("mock out the Select method") // }, // } @@ -55,28 +55,28 @@ type PrompterMock struct { AuthTokenFunc func() (string, error) // ConfirmFunc mocks the Confirm method. - ConfirmFunc func(s string, b bool) (bool, error) + ConfirmFunc func(prompt string, defaultValue bool) (bool, error) // ConfirmDeletionFunc mocks the ConfirmDeletion method. - ConfirmDeletionFunc func(s string) error + ConfirmDeletionFunc func(requiredValue string) error // InputFunc mocks the Input method. - InputFunc func(s1 string, s2 string) (string, error) + InputFunc func(prompt string, defaultValue string) (string, error) // InputHostnameFunc mocks the InputHostname method. InputHostnameFunc func() (string, error) // MarkdownEditorFunc mocks the MarkdownEditor method. - MarkdownEditorFunc func(s1 string, s2 string, b bool) (string, error) + MarkdownEditorFunc func(prompt string, defaultValue string, blankAllowed bool) (string, error) // MultiSelectFunc mocks the MultiSelect method. MultiSelectFunc func(prompt string, defaults []string, options []string) ([]int, error) // PasswordFunc mocks the Password method. - PasswordFunc func(s string) (string, error) + PasswordFunc func(prompt string) (string, error) // SelectFunc mocks the Select method. - SelectFunc func(s1 string, s2 string, strings []string) (int, error) + SelectFunc func(prompt string, defaultValue string, options []string) (int, error) // calls tracks calls to the methods. calls struct { @@ -85,34 +85,34 @@ type PrompterMock struct { } // Confirm holds details about calls to the Confirm method. Confirm []struct { - // S is the s argument value. - S string - // B is the b argument value. - B bool + // Prompt is the prompt argument value. + Prompt string + // DefaultValue is the defaultValue argument value. + DefaultValue bool } // ConfirmDeletion holds details about calls to the ConfirmDeletion method. ConfirmDeletion []struct { - // S is the s argument value. - S string + // RequiredValue is the requiredValue argument value. + RequiredValue string } // Input holds details about calls to the Input method. Input []struct { - // S1 is the s1 argument value. - S1 string - // S2 is the s2 argument value. - S2 string + // Prompt is the prompt argument value. + Prompt string + // DefaultValue is the defaultValue argument value. + DefaultValue string } // InputHostname holds details about calls to the InputHostname method. InputHostname []struct { } // MarkdownEditor holds details about calls to the MarkdownEditor method. MarkdownEditor []struct { - // S1 is the s1 argument value. - S1 string - // S2 is the s2 argument value. - S2 string - // B is the b argument value. - B bool + // Prompt is the prompt argument value. + Prompt string + // DefaultValue is the defaultValue argument value. + DefaultValue string + // BlankAllowed is the blankAllowed argument value. + BlankAllowed bool } // MultiSelect holds details about calls to the MultiSelect method. MultiSelect []struct { @@ -125,17 +125,17 @@ type PrompterMock struct { } // Password holds details about calls to the Password method. Password []struct { - // S is the s argument value. - S string + // Prompt is the prompt argument value. + Prompt string } // Select holds details about calls to the Select method. Select []struct { - // S1 is the s1 argument value. - S1 string - // S2 is the s2 argument value. - S2 string - // Strings is the strings argument value. - Strings []string + // Prompt is the prompt argument value. + Prompt string + // DefaultValue is the defaultValue argument value. + DefaultValue string + // Options is the options argument value. + Options []string } } lockAuthToken sync.RWMutex @@ -177,21 +177,21 @@ func (mock *PrompterMock) AuthTokenCalls() []struct { } // Confirm calls ConfirmFunc. -func (mock *PrompterMock) Confirm(s string, b bool) (bool, error) { +func (mock *PrompterMock) Confirm(prompt string, defaultValue bool) (bool, error) { if mock.ConfirmFunc == nil { panic("PrompterMock.ConfirmFunc: method is nil but Prompter.Confirm was just called") } callInfo := struct { - S string - B bool + Prompt string + DefaultValue bool }{ - S: s, - B: b, + Prompt: prompt, + DefaultValue: defaultValue, } mock.lockConfirm.Lock() mock.calls.Confirm = append(mock.calls.Confirm, callInfo) mock.lockConfirm.Unlock() - return mock.ConfirmFunc(s, b) + return mock.ConfirmFunc(prompt, defaultValue) } // ConfirmCalls gets all the calls that were made to Confirm. @@ -199,12 +199,12 @@ func (mock *PrompterMock) Confirm(s string, b bool) (bool, error) { // // len(mockedPrompter.ConfirmCalls()) func (mock *PrompterMock) ConfirmCalls() []struct { - S string - B bool + Prompt string + DefaultValue bool } { var calls []struct { - S string - B bool + Prompt string + DefaultValue bool } mock.lockConfirm.RLock() calls = mock.calls.Confirm @@ -213,19 +213,19 @@ func (mock *PrompterMock) ConfirmCalls() []struct { } // ConfirmDeletion calls ConfirmDeletionFunc. -func (mock *PrompterMock) ConfirmDeletion(s string) error { +func (mock *PrompterMock) ConfirmDeletion(requiredValue string) error { if mock.ConfirmDeletionFunc == nil { panic("PrompterMock.ConfirmDeletionFunc: method is nil but Prompter.ConfirmDeletion was just called") } callInfo := struct { - S string + RequiredValue string }{ - S: s, + RequiredValue: requiredValue, } mock.lockConfirmDeletion.Lock() mock.calls.ConfirmDeletion = append(mock.calls.ConfirmDeletion, callInfo) mock.lockConfirmDeletion.Unlock() - return mock.ConfirmDeletionFunc(s) + return mock.ConfirmDeletionFunc(requiredValue) } // ConfirmDeletionCalls gets all the calls that were made to ConfirmDeletion. @@ -233,10 +233,10 @@ func (mock *PrompterMock) ConfirmDeletion(s string) error { // // len(mockedPrompter.ConfirmDeletionCalls()) func (mock *PrompterMock) ConfirmDeletionCalls() []struct { - S string + RequiredValue string } { var calls []struct { - S string + RequiredValue string } mock.lockConfirmDeletion.RLock() calls = mock.calls.ConfirmDeletion @@ -245,21 +245,21 @@ func (mock *PrompterMock) ConfirmDeletionCalls() []struct { } // Input calls InputFunc. -func (mock *PrompterMock) Input(s1 string, s2 string) (string, error) { +func (mock *PrompterMock) Input(prompt string, defaultValue string) (string, error) { if mock.InputFunc == nil { panic("PrompterMock.InputFunc: method is nil but Prompter.Input was just called") } callInfo := struct { - S1 string - S2 string + Prompt string + DefaultValue string }{ - S1: s1, - S2: s2, + Prompt: prompt, + DefaultValue: defaultValue, } mock.lockInput.Lock() mock.calls.Input = append(mock.calls.Input, callInfo) mock.lockInput.Unlock() - return mock.InputFunc(s1, s2) + return mock.InputFunc(prompt, defaultValue) } // InputCalls gets all the calls that were made to Input. @@ -267,12 +267,12 @@ func (mock *PrompterMock) Input(s1 string, s2 string) (string, error) { // // len(mockedPrompter.InputCalls()) func (mock *PrompterMock) InputCalls() []struct { - S1 string - S2 string + Prompt string + DefaultValue string } { var calls []struct { - S1 string - S2 string + Prompt string + DefaultValue string } mock.lockInput.RLock() calls = mock.calls.Input @@ -308,23 +308,23 @@ func (mock *PrompterMock) InputHostnameCalls() []struct { } // MarkdownEditor calls MarkdownEditorFunc. -func (mock *PrompterMock) MarkdownEditor(s1 string, s2 string, b bool) (string, error) { +func (mock *PrompterMock) MarkdownEditor(prompt string, defaultValue string, blankAllowed bool) (string, error) { if mock.MarkdownEditorFunc == nil { panic("PrompterMock.MarkdownEditorFunc: method is nil but Prompter.MarkdownEditor was just called") } callInfo := struct { - S1 string - S2 string - B bool + Prompt string + DefaultValue string + BlankAllowed bool }{ - S1: s1, - S2: s2, - B: b, + Prompt: prompt, + DefaultValue: defaultValue, + BlankAllowed: blankAllowed, } mock.lockMarkdownEditor.Lock() mock.calls.MarkdownEditor = append(mock.calls.MarkdownEditor, callInfo) mock.lockMarkdownEditor.Unlock() - return mock.MarkdownEditorFunc(s1, s2, b) + return mock.MarkdownEditorFunc(prompt, defaultValue, blankAllowed) } // MarkdownEditorCalls gets all the calls that were made to MarkdownEditor. @@ -332,14 +332,14 @@ func (mock *PrompterMock) MarkdownEditor(s1 string, s2 string, b bool) (string, // // len(mockedPrompter.MarkdownEditorCalls()) func (mock *PrompterMock) MarkdownEditorCalls() []struct { - S1 string - S2 string - B bool + Prompt string + DefaultValue string + BlankAllowed bool } { var calls []struct { - S1 string - S2 string - B bool + Prompt string + DefaultValue string + BlankAllowed bool } mock.lockMarkdownEditor.RLock() calls = mock.calls.MarkdownEditor @@ -388,19 +388,19 @@ func (mock *PrompterMock) MultiSelectCalls() []struct { } // Password calls PasswordFunc. -func (mock *PrompterMock) Password(s string) (string, error) { +func (mock *PrompterMock) Password(prompt string) (string, error) { if mock.PasswordFunc == nil { panic("PrompterMock.PasswordFunc: method is nil but Prompter.Password was just called") } callInfo := struct { - S string + Prompt string }{ - S: s, + Prompt: prompt, } mock.lockPassword.Lock() mock.calls.Password = append(mock.calls.Password, callInfo) mock.lockPassword.Unlock() - return mock.PasswordFunc(s) + return mock.PasswordFunc(prompt) } // PasswordCalls gets all the calls that were made to Password. @@ -408,10 +408,10 @@ func (mock *PrompterMock) Password(s string) (string, error) { // // len(mockedPrompter.PasswordCalls()) func (mock *PrompterMock) PasswordCalls() []struct { - S string + Prompt string } { var calls []struct { - S string + Prompt string } mock.lockPassword.RLock() calls = mock.calls.Password @@ -420,23 +420,23 @@ func (mock *PrompterMock) PasswordCalls() []struct { } // Select calls SelectFunc. -func (mock *PrompterMock) Select(s1 string, s2 string, strings []string) (int, error) { +func (mock *PrompterMock) Select(prompt string, defaultValue string, options []string) (int, error) { if mock.SelectFunc == nil { panic("PrompterMock.SelectFunc: method is nil but Prompter.Select was just called") } callInfo := struct { - S1 string - S2 string - Strings []string + Prompt string + DefaultValue string + Options []string }{ - S1: s1, - S2: s2, - Strings: strings, + Prompt: prompt, + DefaultValue: defaultValue, + Options: options, } mock.lockSelect.Lock() mock.calls.Select = append(mock.calls.Select, callInfo) mock.lockSelect.Unlock() - return mock.SelectFunc(s1, s2, strings) + return mock.SelectFunc(prompt, defaultValue, options) } // SelectCalls gets all the calls that were made to Select. @@ -444,14 +444,14 @@ func (mock *PrompterMock) Select(s1 string, s2 string, strings []string) (int, e // // len(mockedPrompter.SelectCalls()) func (mock *PrompterMock) SelectCalls() []struct { - S1 string - S2 string - Strings []string + Prompt string + DefaultValue string + Options []string } { var calls []struct { - S1 string - S2 string - Strings []string + Prompt string + DefaultValue string + Options []string } mock.lockSelect.RLock() calls = mock.calls.Select diff --git a/pkg/cmd/config/list/list_test.go b/pkg/cmd/config/list/list_test.go index 2184d0f16..2a1dd72ee 100644 --- a/pkg/cmd/config/list/list_test.go +++ b/pkg/cmd/config/list/list_test.go @@ -101,6 +101,7 @@ func Test_listRun(t *testing.T) { http_unix_socket= browser=brave color_labels=disabled + accessible_colors=disabled `), }, } diff --git a/pkg/cmd/root/help_topic.go b/pkg/cmd/root/help_topic.go index 4b692777c..0c9534306 100644 --- a/pkg/cmd/root/help_topic.go +++ b/pkg/cmd/root/help_topic.go @@ -84,6 +84,8 @@ var HelpTopics = []helpTopic{ %[1]sGH_COLOR_LABELS%[1]s: set to any value to display labels using their RGB hex color codes in terminals that support truecolor. + %[1]sGH_ACCESSIBLE_COLORS%[1]s (preview): set to a truthy value to use customizable, 4-bit accessible colors. + %[1]sGH_FORCE_TTY%[1]s: set to any value to force terminal-style output even when the output is redirected. When the value is a number, it is interpreted as the number of columns available in the viewport. When the value is a percentage, it will be applied against