88 "io"
99 "io/fs"
1010 "maps"
11+ "os"
1112 "path/filepath"
1213 "slices"
1314 "sort"
@@ -16,14 +17,6 @@ import (
1617 "github.com/xo/ox/text"
1718)
1819
19- // StringWriter extends the [io.StringWriter] interface with a Len() method,
20- // allowing both [strings.Builder] and [bytes.Buffer] to be used when
21- // generating help output.
22- type StringWriter interface {
23- io.StringWriter
24- Len () int
25- }
26-
2720// AddHelpFlag recursively adds a `--help` flag for all commands in the command
2821// tree, copying the command's [CommandHelp.Sort], [CommandHelp.CommandSort],
2922// and [CommandHelp.MaxDist] settings.
@@ -325,7 +318,7 @@ func (help *CommandHelp) SetContext(ctx *Context) {
325318
326319// WriteTo satisfies the [io.WriterTo] interface.
327320func (help * CommandHelp ) WriteTo (w io.Writer ) (int64 , error ) {
328- var buf bytes. Buffer
321+ wr := NewStringWriter ( w )
329322 for _ , f := range []func (StringWriter ){
330323 help .AddBanner ,
331324 help .AddUsage ,
@@ -335,10 +328,10 @@ func (help *CommandHelp) WriteTo(w io.Writer) (int64, error) {
335328 help .AddFlags ,
336329 help .AddFooter ,
337330 } {
338- f (& buf )
331+ f (wr )
339332 }
340- _ = buf . WriteByte ( '\n' )
341- return buf . WriteTo ( w )
333+ _ , _ = wr . WriteString ( " \n " )
334+ return int64 ( wr . Len ()), nil
342335}
343336
344337// AddBanner adds the command's banner.
@@ -665,6 +658,69 @@ func NewCompletion(name, usage string, dist int) Completion {
665658 }
666659}
667660
661+ // StringWriter extends the [io.StringWriter] interface with a Len() method,
662+ // allowing both [strings.Builder] and [bytes.Buffer] to be used when
663+ // generating help output.
664+ type StringWriter interface {
665+ io.StringWriter
666+ Len () int
667+ }
668+
669+ // NewStringWriter converts a writer to a [StringWriter], or wraps the writer
670+ // when necessary. Wraps (or directly returns) [os.File], [strings.Builder],
671+ // [bytes.Buffer], or other [io.Writer].
672+ func NewStringWriter (w io.Writer ) StringWriter {
673+ switch x := w .(type ) {
674+ case * os.File :
675+ return & fileWriter {w : x }
676+ case * strings.Builder :
677+ return x
678+ case * bytes.Buffer :
679+ return x
680+ case StringWriter :
681+ return x
682+ }
683+ return & stringWriter {w : w }
684+ }
685+
686+ // fileWriter wraps writing to a file.
687+ type fileWriter struct {
688+ w * os.File
689+ n bool
690+ }
691+
692+ // WriteString satisfies the [StringWriter] interface.
693+ func (w * fileWriter ) WriteString (s string ) (int , error ) {
694+ w .n = w .n || s != ""
695+ return w .w .WriteString (s )
696+ }
697+
698+ // Len satisfies the [StringWriter] interface.
699+ func (w * fileWriter ) Len () int {
700+ if w .n {
701+ return 1
702+ }
703+ return 0
704+ }
705+
706+ type stringWriter struct {
707+ w io.Writer
708+ n bool
709+ }
710+
711+ func (w * stringWriter ) WriteString (s string ) (int , error ) {
712+ w .n = w .n || s != ""
713+ return w .w .Write ([]byte (s ))
714+ }
715+
716+ // Len satisfies the [StringWriter] interface.
717+ func (w * stringWriter ) Len () int {
718+ if w .n {
719+ return 1
720+ }
721+ return 0
722+ }
723+
668724// has builds a string for a.
669725func has [T inti | uinti ](w StringWriter , a , b T , s string ) {
670726 if a & b != 0 {
0 commit comments