Skip to content

Commit faeb01c

Browse files
committed
update template format theme
1 parent 3a5cc9f commit faeb01c

6 files changed

Lines changed: 284 additions & 17 deletions

File tree

cmd/help/help.go

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ func HelpFunc(
3535
if help, _ := clients.Config.Flags.GetBool("help"); help {
3636
clients.Config.LoadExperiments(ctx, clients.IO.PrintDebug)
3737
}
38+
style.ToggleCharm(clients.Config.WithExperimentOn(experiment.Charm))
3839
experiments := []string{}
3940
for _, exp := range clients.Config.GetExperiments() {
4041
if experiment.Includes(exp) {
@@ -66,15 +67,64 @@ func PrintHelpTemplate(cmd *cobra.Command, data style.TemplateData) {
6667
cmd.PrintErrln(err)
6768
}
6869
cmd.Long = cmdLongF.String()
69-
tmpl := helpTemplate
70+
tmpl := legacyHelpTemplate
71+
if style.IsCharmEnabled() {
72+
tmpl = charmHelpTemplate
73+
}
7074
err = style.PrintTemplate(cmd.OutOrStdout(), tmpl, templateInfo{cmd, data})
7175
if err != nil {
7276
cmd.PrintErrln(err)
7377
}
7478
}
7579

76-
// helpTemplate formats values and information for a helpful output
77-
const helpTemplate string = `{{.Long}}
80+
// ════════════════════════════════════════════════════════════════════════════════
81+
// Charm help template — lipgloss styling
82+
// ════════════════════════════════════════════════════════════════════════════════
83+
84+
const charmHelpTemplate string = `{{.Long | ToDescription}}
85+
86+
{{Header "Usage"}}{{if .Runnable}}
87+
{{ToPrompt "$ "}}{{ToCommandText .UseLine}}{{end}}{{if gt (len .Aliases) 0}}
88+
89+
{{Header "Aliases"}}
90+
{{.NameAndAliases | ToCommandText}}{{end}}{{if .HasAvailableSubCommands}}
91+
92+
{{if eq .Name (GetProcessName)}}{{Header "Commands"}}{{range .Commands}}{{if and (.HasAvailableSubCommands) (not .Hidden)}}
93+
{{.Name | ToGroupName }}{{range .Commands}}{{if (not .Hidden)}}
94+
{{rpad .Name .NamePadding | ToCommandText}} {{.Short | ToDescription}}{{end}}{{end}}{{end}}{{end}}{{if and (.HasAvailableSubCommands) (not .Hidden)}}{{range .Commands}}{{if and (not .HasAvailableSubCommands) (not .Hidden)}}{{if not (IsAlias .Name $.Data.Aliases)}}
95+
{{(rpad .Name .NamePadding) | ToGroupName }}{{.Short | ToDescription}}{{end}}{{end}}{{end}}{{end}}{{else}}{{Header "Subcommands"}}{{if and (.HasAvailableSubCommands) (not .Hidden)}}{{range .Commands}}{{if not .HasAvailableSubCommands}}
96+
{{(rpad .Name .NamePadding) | ToCommandText }} {{.Short | ToDescription}}{{end}}{{end}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}}
97+
98+
{{Header "Flags"}}
99+
{{.LocalFlags.FlagUsages | trimTrailingWhitespaces | ToFlags}}{{end}}{{if and (.HasAvailableSubCommands) (not .Hidden)}}{{if or (HasAliasSubcommands .Name .Data.Aliases) (eq .Name (GetProcessName))}}
100+
101+
{{Header "Global aliases"}}{{range .Commands}}{{if and (IsAlias .Name $.Data.Aliases) (not .Hidden)}}
102+
{{(rpad .Name .NamePadding) | ToGroupName }} {{rpad (AliasParent .Name $.Data.Aliases) AliasPadding | ToAliasParent}} {{ToPrompt "❱"}} {{.Name | ToGroupName}}{{end}}{{end}}{{end}}{{end}}{{if .HasAvailableInheritedFlags}}
103+
104+
{{Header "Global flags"}}
105+
{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces | ToFlags}}{{end}}{{if .HasExample}}
106+
107+
{{Header "Example"}}
108+
{{ Examples .Example}}{{end}}{{if and (.HasAvailableSubCommands) (not .Hidden)}}
109+
110+
{{Header "Experiments"}}
111+
{{ Experiments .Data.Experiments }}
112+
113+
{{Header "Additional help"}}
114+
{{ToSecondary "For more information about a specific command, run:"}}
115+
{{ToPrompt "$ "}}{{ToCommandText .CommandPath}}{{if eq .Name (GetProcessName)}}{{ToCommandText " <command>"}}{{end}}{{ToCommandText " <subcommand> --help"}}
116+
117+
{{ToSecondary "For guides and documentation, head over to "}}{{LinkText "https://docs.slack.dev/tools/slack-cli"}}{{end}}
118+
119+
`
120+
121+
// ════════════════════════════════════════════════════════════════════════════════
122+
// DEPRECATED: Legacy help template — aurora styling
123+
//
124+
// Delete this entire block when the charm experiment is permanently enabled.
125+
// ════════════════════════════════════════════════════════════════════════════════
126+
127+
const legacyHelpTemplate string = `{{.Long}}
78128
79129
{{Header "Usage"}}{{if .Runnable}}
80130
$ {{.UseLine}}{{end}}{{if gt (len .Aliases) 0}}

internal/style/colors.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,23 +34,23 @@ var lightDark = lipgloss.LightDark(hasDarkBG)
3434

3535
// Brand colors — primary on light backgrounds, secondary on dark backgrounds
3636
var (
37-
slackAubergine color.Color = lightDark(lipgloss.Color("#4a154b"), lipgloss.Color("#b26ec3")) // Core/Aubergine → Sec/Mauve
38-
slackBlue color.Color = lightDark(lipgloss.Color("#36c5f0"), lipgloss.Color("#78d7dd")) // Core/Slack blue → Sec/Pool
39-
slackGreen color.Color = lipgloss.Color("#2eb67d") // Core/Slack green (reads well on both)
40-
slackYellow color.Color = lightDark(lipgloss.Color("#ecb22e"), lipgloss.Color("#ffd57e")) // Core/Slack yellow → Sec/Sandbar
41-
slackRed color.Color = lightDark(lipgloss.Color("#e01e5a"), lipgloss.Color("#f2606a")) // Core/Slack red → Sec/Salmon
42-
slackRedDark color.Color = lightDark(lipgloss.Color("#5e1237"), lipgloss.Color("#f2606a")) // Sec/Berry → Sec/Salmon
37+
slackAubergine color.Color = lightDark(lipgloss.Color("#39063a"), lipgloss.Color("#eabdfb")) // Core/Aubergine → Sec/Mauve
38+
slackBlue color.Color = lightDark(lipgloss.Color("#36c5f0"), lipgloss.Color("#67d7f8")) // Core/Slack blue → Sec/Pool
39+
slackGreen color.Color = lightDark(lipgloss.Color("#2eb67d"), lipgloss.Color("#74dbaf")) // Core/Slack green (reads well on both)
40+
slackYellow color.Color = lightDark(lipgloss.Color("#ecb22e"), lipgloss.Color("#f4c360")) // Core/Slack yellow → Sec/Sandbar
41+
slackRed color.Color = lightDark(lipgloss.Color("#e01e5a"), lipgloss.Color("#ffa3c2")) // Core/Slack red → Sec/Salmon
42+
slackRedDark color.Color = lightDark(lipgloss.Color("#5e1237"), lipgloss.Color("#edb4ce")) // Sec/Berry → Sec/Salmon
4343
)
4444

4545
// Supplementary colors
4646
var (
47-
slackPool color.Color = lipgloss.Color("#78d7dd") // Sec/Pool
47+
slackPool color.Color = lipgloss.Color("#78d7dd")
4848
slackLegalGray color.Color = lightDark(lipgloss.Color("#5e5d60"), lipgloss.Color("#eaeaea")) // Sec/Legal → Sec/Inactive gray
4949
)
5050

5151
// Adaptive text colors
5252
var (
5353
slackOptionText color.Color = lightDark(lipgloss.Color("#1d1c1d"), lipgloss.Color("#f4ede4")) // Core/Black → Core/Horchatta
54-
slackDescriptionText color.Color = lightDark(lipgloss.Color("#454447"), lipgloss.Color("#5e5d60")) // Sec/Small text → Sec/Inactive gray
55-
slackPlaceholderText color.Color = lightDark(lipgloss.Color("#5e5d60"), lipgloss.Color("#5e5d60")) // Sec/Legal → Sec/Inactive gray
54+
slackDescriptionText color.Color = lightDark(lipgloss.Color("#454447"), lipgloss.Color("#eaeaea")) // Sec/Small text → Sec/Inactive gray
55+
slackPlaceholderText color.Color = lightDark(lipgloss.Color("#ffd57e"), lipgloss.Color("#fed4be")) // Sec/Legal → Core/Horchatta
5656
)

internal/style/format.go

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -253,19 +253,66 @@ func ExampleCommandsf(commands []ExampleCommand) string {
253253
// ExampleTemplatef indents and styles command examples for the help messages
254254
func ExampleTemplatef(template string) string {
255255
lines := strings.Split(template, "\n")
256-
re := regexp.MustCompile(`(^#.*$)|( #.*$)`)
256+
reComment := regexp.MustCompile(`(^#.*$)|( #.*$)`)
257257
examples := []string{}
258258
for _, cmd := range lines {
259259
example := ""
260260
if cmd != "" {
261-
styled := re.ReplaceAllStringFunc(cmd, Secondary)
262-
example = fmt.Sprintf(" %s", styled)
261+
if isCharmEnabled {
262+
example = fmt.Sprintf(" %s", styleExampleLine(cmd))
263+
} else {
264+
styled := reComment.ReplaceAllStringFunc(cmd, Secondary)
265+
example = fmt.Sprintf(" %s", styled)
266+
}
263267
}
264268
examples = append(examples, example)
265269
}
266270
return strings.Join(examples, "\n")
267271
}
268272

273+
// styleExampleLine styles an example line for charm, splitting command and comment portions.
274+
func styleExampleLine(line string) string {
275+
// Full-line comments
276+
if strings.HasPrefix(line, "#") {
277+
return Secondary(line)
278+
}
279+
// Split inline comments: "$ slack login # Log in"
280+
if idx := strings.Index(line, " #"); idx >= 0 {
281+
command := line[:idx]
282+
comment := line[idx:]
283+
return styleExampleCommand(command) + Secondary(comment)
284+
}
285+
// No comment — style the whole line as a command if it starts with $
286+
if strings.HasPrefix(line, "$") {
287+
return styleExampleCommand(line)
288+
}
289+
return line
290+
}
291+
292+
// styleExampleCommand styles a "$ command" string with a green prompt and blue command text.
293+
func styleExampleCommand(cmd string) string {
294+
if strings.HasPrefix(cmd, "$ ") {
295+
return Yellow("$ ") + CommandText(cmd[2:])
296+
}
297+
return CommandText(cmd)
298+
}
299+
300+
// StyleFlags post-processes Cobra's FlagUsages() output to colorize flag names and descriptions.
301+
// Each line is expected to have the format: " -s, --long TYPE Description text"
302+
func StyleFlags(text string) string {
303+
re := regexp.MustCompile(`^(\s{2,6}-\S.*?\s{2,})(\S.*)$`)
304+
lines := strings.Split(text, "\n")
305+
styled := make([]string, len(lines))
306+
for i, line := range lines {
307+
if m := re.FindStringSubmatch(line); m != nil {
308+
styled[i] = Yellow(m[1]) + Secondary(m[2])
309+
} else {
310+
styled[i] = line
311+
}
312+
}
313+
return strings.Join(styled, "\n")
314+
}
315+
269316
// LocalRunDisplayName appends the (local) tag to apps created by the run command
270317
func LocalRunDisplayName(name string) string {
271318
if !strings.HasSuffix(name, LocalRunNameTag) {

internal/style/format_test.go

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,60 @@ func TestSurveyIcons(t *testing.T) {
264264
* Example commands
265265
*/
266266

267+
func TestStyleFlags(t *testing.T) {
268+
defer func() {
269+
ToggleStyles(false)
270+
ToggleCharm(false)
271+
}()
272+
ToggleStyles(true)
273+
ToggleCharm(true)
274+
275+
tests := map[string]struct {
276+
input string
277+
expected string
278+
}{
279+
"short and long flag with type and description": {
280+
input: " -s, --long string Description text",
281+
expected: Yellow(" -s, --long string ") + Secondary("Description text"),
282+
},
283+
"long-only flag with description": {
284+
input: " --verbose Enable verbose output",
285+
expected: Yellow(" --verbose ") + Secondary("Enable verbose output"),
286+
},
287+
"plain text without flag pattern returned unchanged": {
288+
input: "some plain text",
289+
expected: "some plain text",
290+
},
291+
"empty string returned unchanged": {
292+
input: "",
293+
expected: "",
294+
},
295+
"multiline flag output": {
296+
input: " -a, --all Show all\n --verbose Enable verbose",
297+
expected: Yellow(" -a, --all ") + Secondary("Show all") + "\n" + Yellow(" --verbose ") + Secondary("Enable verbose"),
298+
},
299+
}
300+
for name, tc := range tests {
301+
t.Run(name, func(t *testing.T) {
302+
actual := StyleFlags(tc.input)
303+
assert.Equal(t, tc.expected, actual)
304+
})
305+
}
306+
}
307+
308+
func TestStyleFlags_CharmDisabled(t *testing.T) {
309+
defer func() {
310+
ToggleStyles(false)
311+
ToggleCharm(false)
312+
}()
313+
ToggleStyles(false)
314+
ToggleCharm(false)
315+
316+
input := " -s, --long string Description text"
317+
actual := StyleFlags(input)
318+
assert.Equal(t, input, actual)
319+
}
320+
267321
func Test_ExampleCommandsf(t *testing.T) {
268322
tests := map[string]struct {
269323
name string
@@ -365,6 +419,67 @@ func Test_ExampleTemplatef(t *testing.T) {
365419
}
366420
}
367421

422+
func Test_styleExampleLine(t *testing.T) {
423+
defer func() {
424+
ToggleStyles(false)
425+
ToggleCharm(false)
426+
}()
427+
ToggleStyles(true)
428+
ToggleCharm(true)
429+
430+
tests := map[string]struct {
431+
input string
432+
expected string
433+
}{
434+
"full-line comment is styled as secondary": {
435+
input: "# Create a new project",
436+
expected: Secondary("# Create a new project"),
437+
},
438+
"command with inline comment splits styling": {
439+
input: "$ slack create # Create a project",
440+
expected: Yellow("$ ") + CommandText("slack create") + Secondary(" # Create a project"),
441+
},
442+
"command without comment is styled as command": {
443+
input: "$ slack create my-app",
444+
expected: Yellow("$ ") + CommandText("slack create my-app"),
445+
},
446+
"plain text without prefix is returned as-is": {
447+
input: "some other text",
448+
expected: "some other text",
449+
},
450+
}
451+
for name, tc := range tests {
452+
t.Run(name, func(t *testing.T) {
453+
actual := styleExampleLine(tc.input)
454+
assert.Equal(t, tc.expected, actual)
455+
})
456+
}
457+
}
458+
459+
func Test_ExampleTemplatef_Charm(t *testing.T) {
460+
defer func() {
461+
ToggleStyles(false)
462+
ToggleCharm(false)
463+
}()
464+
ToggleStyles(true)
465+
ToggleCharm(true)
466+
467+
template := []string{
468+
"# Create a new project from a selected template",
469+
"$ slack create",
470+
"",
471+
"$ slack create my-project -t sample/repo-url # Create a named project",
472+
}
473+
expected := []string{
474+
fmt.Sprintf(" %s", Secondary("# Create a new project from a selected template")),
475+
fmt.Sprintf(" %s%s", Yellow("$ "), CommandText("slack create")),
476+
"",
477+
fmt.Sprintf(" %s%s%s", Yellow("$ "), CommandText("slack create my-project -t sample/repo-url"), Secondary(" # Create a named project")),
478+
}
479+
actual := ExampleTemplatef(strings.Join(template, "\n"))
480+
assert.Equal(t, strings.Join(expected, "\n"), actual)
481+
}
482+
368483
/*
369484
* App name formatting
370485
*/

internal/style/style.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ func ToggleCharm(active bool) {
5757
isCharmEnabled = active
5858
}
5959

60+
// IsCharmEnabled reports whether lipgloss/charm styling is active
61+
func IsCharmEnabled() bool {
62+
return isCharmEnabled
63+
}
64+
6065
// render applies a lipgloss style to text, returning plain text when colors are disabled.
6166
func render(s lipgloss.Style, text string) string {
6267
if !isColorShown {
@@ -127,7 +132,7 @@ func LinkText(path string) string {
127132
if !isCharmEnabled {
128133
return legacyLinkText(path)
129134
}
130-
return render(lipgloss.NewStyle().Foreground(slackDescriptionText).Underline(true), path)
135+
return render(lipgloss.NewStyle().Foreground(slackPool).Underline(true), path)
131136
}
132137

133138
func Selector(text string) string {
@@ -148,7 +153,7 @@ func Warning(text string) string {
148153
if !isCharmEnabled {
149154
return legacyWarning(text)
150155
}
151-
return render(lipgloss.NewStyle().Foreground(slackYellow).Bold(true), text)
156+
return render(lipgloss.NewStyle().Foreground(slackGreen).Bold(true), text)
152157
}
153158

154159
func Header(text string) string {

0 commit comments

Comments
 (0)