Skip to content

Commit 14b2b95

Browse files
authored
feat: simplify --list output by merging NAME and USAGE columns (#92)
1 parent 4db8285 commit 14b2b95

File tree

5 files changed

+65
-40
lines changed

5 files changed

+65
-40
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ node_modules
4949
.glide/
5050

5151
# Stavefile output
52-
stave_output_file.go
52+
stave_output_file*.go
5353

5454
# Stave test binaries
5555
stave_test_out

CHANGELOG.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [0.13.0] - 2026-03-04
11+
12+
### Changed
13+
14+
- Simplify `--list` output by merging NAME and USAGE columns into a single USAGE column (while still preserving original color-coding & annotations that NAME column previously had.
15+
1016
## [0.12.0] - 2026-02-26
1117

1218
### Changed
@@ -473,7 +479,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
473479

474480
- Parallelized tests where possible, including locking mechanism to prevent parallel tests in same `testdata/(xyz/)` subdir.
475481

476-
[unreleased]: https://github.com/yaklabco/stave/compare/v0.12.0...HEAD
482+
[unreleased]: https://github.com/yaklabco/stave/compare/v0.13.0...HEAD
483+
[0.13.0]: https://github.com/yaklabco/stave/compare/v0.12.0...v0.13.0
477484
[0.12.0]: https://github.com/yaklabco/stave/compare/v0.11.1...v0.12.0
478485
[0.11.1]: https://github.com/yaklabco/stave/compare/v0.11.0...v0.11.1
479486
[0.11.0]: https://github.com/yaklabco/stave/compare/v0.10.10...v0.11.0

pkg/stave/list.go

Lines changed: 55 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ type targetItem struct {
4141
key targetKey
4242

4343
displayName string
44-
usage string
44+
args []parse.Arg
4545
synopsis string
4646
aliases []string
4747
isDefault bool
@@ -58,8 +58,8 @@ var nsDefaultSuffix = ":" + strings.ToLower(defaultLabel) //nolint:gochecknoglob
5858
//
5959
// It is implemented in the Stave binary (not in the generated mainfile) so it can
6060
// use Charmbracelet styling without requiring additional dependencies in user projects.
61-
func renderTargetList(out io.Writer, binaryName string, info *parse.PkgInfo, filters []string) error {
62-
items := buildTargetItems(binaryName, info)
61+
func renderTargetList(out io.Writer, info *parse.PkgInfo, filters []string) error {
62+
items := buildTargetItems(info)
6363
items = applyTargetFilters(items, filters)
6464

6565
anyWatch := false
@@ -95,27 +95,47 @@ func renderTargetList(out io.Writer, binaryName string, info *parse.PkgInfo, fil
9595
watchStyle = watchStyle.Foreground(cs.QuotedString).Reverse(true).Bold(true)
9696
}
9797

98-
renderName := func(name string, isDefault, isWatch bool) string {
98+
renderName := func(name string, isDefault, isWatch bool, args []parse.Arg) string {
99+
var sb strings.Builder
99100
if !colorEnabled {
101+
sb.WriteString(name)
102+
for _, a := range args {
103+
if strings.TrimSpace(a.Name) == "" {
104+
continue
105+
}
106+
sb.WriteString(" <")
107+
sb.WriteString(a.Name)
108+
sb.WriteString(">")
109+
}
100110
if isWatch {
101-
return name + " [W]"
111+
sb.WriteString(" [W]")
102112
}
103-
return name
113+
return sb.String()
104114
}
105115

106-
var renderedName string
107116
if isDefault {
108117
// Default target is highlighted with a distinct color so it is visually discoverable.
109-
renderedName = defaultNameStyle.Render(name)
118+
sb.WriteString(defaultNameStyle.Render(name))
110119
} else {
111120
// Non-default targets use the existing env-driven target color semantics.
112-
renderedName = targetStyle.Render(name)
121+
sb.WriteString(targetStyle.Render(name))
122+
}
123+
124+
for _, a := range args {
125+
if strings.TrimSpace(a.Name) == "" {
126+
continue
127+
}
128+
sb.WriteString(" <")
129+
sb.WriteString(a.Name)
130+
sb.WriteString(">")
113131
}
114132

115133
if isWatch {
116-
renderedName += " " + watchStyle.Render("[W]")
134+
sb.WriteString(" ")
135+
sb.WriteString(watchStyle.Render("[W]"))
117136
}
118-
return renderedName
137+
138+
return sb.String()
119139
}
120140

121141
// Header
@@ -154,7 +174,7 @@ func renderTargetList(out io.Writer, binaryName string, info *parse.PkgInfo, fil
154174
return nil
155175
}
156176

157-
func buildTargetItems(binaryName string, info *parse.PkgInfo) []targetItem {
177+
func buildTargetItems(info *parse.PkgInfo) []targetItem {
158178
aliasByKey := make(map[targetKey][]string)
159179
for alias, fn := range info.Aliases {
160180
if fn == nil {
@@ -191,7 +211,7 @@ func buildTargetItems(binaryName string, info *parse.PkgInfo) []targetItem {
191211
items = append(items, targetItem{
192212
key: funcKey,
193213
displayName: display,
194-
usage: usageFor(binaryName, display, fn.Args),
214+
args: fn.Args,
195215
synopsis: fn.Synopsis,
196216
aliases: aliasByKey[funcKey],
197217
isDefault: funcKey == defaultKey && fn.Name != "",
@@ -219,7 +239,7 @@ func buildTargetItems(binaryName string, info *parse.PkgInfo) []targetItem {
219239
items = append(items, targetItem{
220240
key: funcKey,
221241
displayName: display,
222-
usage: usageFor(binaryName, display, fn.Args),
242+
args: fn.Args,
223243
synopsis: fn.Synopsis,
224244
aliases: aliasByKey[funcKey],
225245
isDefault: funcKey == defaultKey && fn.Name != "",
@@ -293,7 +313,7 @@ func applyTargetFilters(items []targetItem, filters []string) []targetItem {
293313
out := make([]targetItem, 0, len(items))
294314
for _, it := range items {
295315
aliases := strings.Join(it.aliases, ", ")
296-
if matchAll(strings.Join([]string{it.displayName, it.usage, it.synopsis, aliases, it.groupName, it.groupMeta}, " ")) {
316+
if matchAll(strings.Join([]string{it.displayName, it.synopsis, aliases, it.groupName, it.groupMeta}, " ")) {
297317
out = append(out, it)
298318
}
299319
}
@@ -349,15 +369,14 @@ func groupTargets(items []targetItem) targetSections {
349369
switch it.groupKind {
350370
case targetGroupLocal:
351371
if it.isDefault {
352-
it.usage = strings.TrimSuffix(it.usage, it.displayName)
353-
it.usage += "(" + it.displayName + ")"
372+
it.displayName = "(" + it.displayName + ")"
354373
}
355374

356375
locals = append(locals, it)
357376
case targetGroupNamespace:
358377
if it.key.name == defaultLabel {
359378
it.isDefault = true
360-
it.usage = strings.TrimSuffix(it.usage, nsDefaultSuffix)
379+
it.displayName = strings.TrimSuffix(it.displayName, nsDefaultSuffix)
361380
}
362381
nsByName[it.groupName] = append(nsByName[it.groupName], it)
363382
case targetGroupImport:
@@ -386,7 +405,7 @@ func writeTable(
386405
out io.Writer,
387406
headerStyle, subsectionStyle lipgloss.Style,
388407
group targetGroup,
389-
renderName func(name string, isDefault, isWatch bool) string,
408+
renderName func(name string, isDefault, isWatch bool, args []parse.Arg) string,
390409
indent string,
391410
) {
392411
if len(group.items) == 0 {
@@ -404,16 +423,15 @@ func writeTable(
404423

405424
type row struct {
406425
name string
407-
usage string
426+
args []parse.Arg
408427
synopsis string
409428
isDefault bool
410429
isWatch bool
411430
}
412431

413432
rows := make([]row, 0, len(group.items)+1)
414433
rows = append(rows, row{
415-
name: "NAME",
416-
usage: "USAGE",
434+
name: "USAGE",
417435
synopsis: "SYNOPSIS",
418436
})
419437

@@ -428,22 +446,25 @@ func writeTable(
428446
}
429447
rows = append(rows, row{
430448
name: name,
431-
usage: it.usage,
449+
args: it.args,
432450
synopsis: syn,
433451
isDefault: it.isDefault,
434452
isWatch: it.isWatch,
435453
})
436454
}
437455

438456
// Column widths (ANSI-aware via lipgloss.Width).
439-
maxName, maxUsage, maxSyn := 0, 0, 0
440-
for _, theRow := range rows {
441-
name := theRow.name
442-
if theRow.isWatch {
443-
name += " [W]"
457+
maxUsage, maxSyn := 0, 0
458+
for i, theRow := range rows {
459+
usage := theRow.name
460+
if i > 0 {
461+
// For data rows, use the non-colored usage string to calculate width
462+
usage = usageFor("", theRow.name, theRow.args)
463+
if theRow.isWatch {
464+
usage += " [W]"
465+
}
444466
}
445-
maxName = max(maxName, lipgloss.Width(name))
446-
maxUsage = max(maxUsage, lipgloss.Width(theRow.usage))
467+
maxUsage = max(maxUsage, lipgloss.Width(usage))
447468
maxSyn = max(maxSyn, lipgloss.Width(theRow.synopsis))
448469
}
449470

@@ -461,16 +482,15 @@ func writeTable(
461482
// Print header.
462483
h := rows[0]
463484
headerLine := strings.Join([]string{
464-
pad(h.name, maxName),
465-
pad(h.usage, maxUsage),
485+
pad(h.name, maxUsage),
466486
h.synopsis,
467487
}, " ")
468488
_, _ = fmt.Fprintln(out, indent+headerStyle.Render(headerLine))
469489

470490
// Compute terminal width and synopsis column width for wrapping.
471491
termWidth := detectTermWidth(out)
472492
const gap = 2
473-
leftOffset := lipgloss.Width(indent) + maxName + gap + maxUsage + gap
493+
leftOffset := lipgloss.Width(indent) + maxUsage + gap
474494
synWidth := termWidth - leftOffset
475495
if synWidth < termWidthFloor {
476496
synWidth = termWidthFloor
@@ -480,15 +500,14 @@ func writeTable(
480500

481501
// Print rows with word-wrapped synopsis using a hanging indent.
482502
for _, theRow := range rows[1:] {
483-
name := renderName(theRow.name, theRow.isDefault, theRow.isWatch)
503+
usage := renderName(theRow.name, theRow.isDefault, theRow.isWatch, theRow.args)
484504

485505
wrappedSyn := wordwrap.String(theRow.synopsis, synWidth)
486506
// Align continuation lines under the start of the synopsis column.
487507
wrappedSyn = strings.ReplaceAll(wrappedSyn, "\n", "\n"+spaceLeft)
488508

489509
line := strings.Join([]string{
490-
pad(name, maxName),
491-
pad(theRow.usage, maxUsage),
510+
pad(usage, maxUsage),
492511
wrappedSyn,
493512
}, strings.Repeat(" ", gap))
494513
_, _ = fmt.Fprintln(out, indent+line)

pkg/stave/list_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ func TestRenderTargetList_Watch(t *testing.T) {
2727
}
2828

2929
buf := &bytes.Buffer{}
30-
err := renderTargetList(buf, "stave", info, nil)
30+
err := renderTargetList(buf, info, nil)
3131
require.NoError(t, err)
3232

3333
output := buf.String()

pkg/stave/main.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,6 @@ func runListMode(ctx context.Context, params RunParams) error {
225225

226226
return renderTargetList(
227227
params.Stdout,
228-
filepath.Base(os.Args[0]),
229228
info,
230229
params.Args,
231230
)

0 commit comments

Comments
 (0)