Skip to content

Commit 2ffceb8

Browse files
Reuse the tabwriter to avoid excess allocations
1 parent a75dac7 commit 2ffceb8

2 files changed

Lines changed: 27 additions & 9 deletions

File tree

command.go

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import (
1111
"strings"
1212
"unicode/utf8"
1313

14+
"go.followtheprocess.codes/hue/tabwriter"
15+
1416
publicflag "go.followtheprocess.codes/cli/flag"
1517

1618
"go.followtheprocess.codes/cli/internal/arg"
@@ -481,6 +483,11 @@ func showHelp(cmd *Command) error {
481483
s := &strings.Builder{}
482484
s.Grow(helpBufferSize)
483485

486+
// One tabwriter threaded through every aligned section, reset
487+
// between them with ResetTabwriter so only the first section pays the
488+
// internal-buffer allocation cost.
489+
tw := style.Tabwriter(s)
490+
484491
// If we have a short description, write that
485492
if cmd.short != "" {
486493
s.WriteString(cmd.short)
@@ -517,7 +524,7 @@ func showHelp(cmd *Command) error {
517524

518525
// If we have defined, list them explicitly and use their descriptions
519526
if len(cmd.args) != 0 {
520-
if err := writeArgumentsSection(cmd, s); err != nil {
527+
if err := writeArgumentsSection(cmd, s, tw); err != nil {
521528
return err
522529
}
523530
}
@@ -529,7 +536,7 @@ func showHelp(cmd *Command) error {
529536

530537
// Now show subcommands
531538
if len(cmd.subcommands) != 0 {
532-
if err := writeSubcommands(cmd, s); err != nil {
539+
if err := writeSubcommands(cmd, s, tw); err != nil {
533540
return err
534541
}
535542
}
@@ -546,7 +553,7 @@ func showHelp(cmd *Command) error {
546553
s.WriteString(optionsTitle)
547554
s.WriteString(":\n\n")
548555

549-
if err := writeFlags(cmd, s); err != nil {
556+
if err := writeFlags(cmd, s, tw); err != nil {
550557
return err
551558
}
552559

@@ -584,11 +591,11 @@ func writePositionalArgs(cmd *Command, s *strings.Builder) {
584591

585592
// writeArgumentsSection writes the entire positional arguments block to the help
586593
// text string builder.
587-
func writeArgumentsSection(cmd *Command, s *strings.Builder) error {
594+
func writeArgumentsSection(cmd *Command, s *strings.Builder, tw *tabwriter.Writer) error {
588595
s.WriteString("\n\n")
589596
s.WriteString(argumentsTitle)
590597
s.WriteString(":\n\n")
591-
tw := style.Tabwriter(s)
598+
style.ResetTabwriter(tw, s)
592599

593600
for _, arg := range cmd.args {
594601
if def := arg.Default(); def != "" {
@@ -638,7 +645,7 @@ func writeExamples(cmd *Command, s *strings.Builder) {
638645
}
639646

640647
// writeSubcommands writes the subcommand block to the help text string builder.
641-
func writeSubcommands(cmd *Command, s *strings.Builder) error {
648+
func writeSubcommands(cmd *Command, s *strings.Builder, tw *tabwriter.Writer) error {
642649
// If there were examples, the last one would have printed a newline
643650
if len(cmd.examples) != 0 {
644651
s.WriteByte('\n')
@@ -650,7 +657,8 @@ func writeSubcommands(cmd *Command, s *strings.Builder) error {
650657
s.WriteByte(':')
651658
s.WriteString("\n\n")
652659

653-
tw := style.Tabwriter(s)
660+
style.ResetTabwriter(tw, s)
661+
654662
for _, subcommand := range cmd.subcommands {
655663
fmt.Fprintf(tw, " %s\t%s\n", style.Bold.Text(subcommand.name), subcommand.short)
656664
}
@@ -663,8 +671,8 @@ func writeSubcommands(cmd *Command, s *strings.Builder) error {
663671
}
664672

665673
// writeFlags writes the flag usage block to the help text string builder.
666-
func writeFlags(cmd *Command, s *strings.Builder) error {
667-
tw := style.Tabwriter(s)
674+
func writeFlags(cmd *Command, s *strings.Builder, tw *tabwriter.Writer) error {
675+
style.ResetTabwriter(tw, s)
668676

669677
for name, fl := range cmd.flags.Sorted() {
670678
var shorthand string

internal/style/style.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,13 @@ const (
3838
func Tabwriter(w io.Writer) *tabwriter.Writer {
3939
return tabwriter.NewWriter(w, minWidth, tabWidth, padding, padChar, flags)
4040
}
41+
42+
// ResetTabwriter re-initialises an existing [tabwriter.Writer] with the cli
43+
// house style, pointing it at w. It returns the same writer for chaining.
44+
//
45+
// Init reuses the writer's internal buffers rather than allocating new ones,
46+
// so threading a single writer through the help renderer avoids a fresh
47+
// tabwriter allocation per section.
48+
func ResetTabwriter(tw *tabwriter.Writer, w io.Writer) *tabwriter.Writer {
49+
return tw.Init(w, minWidth, tabWidth, padding, padChar, flags)
50+
}

0 commit comments

Comments
 (0)