feat: add list command with global filters

This commit is contained in:
Rogee
2025-10-29 16:08:46 +08:00
parent 88563d48e2
commit fa57af8a26
29 changed files with 1892 additions and 25 deletions

View File

@@ -0,0 +1,21 @@
package output
import "fmt"
// Format identifiers mirrored from listing package to avoid import cycle.
const (
FormatTable = "table"
FormatPlain = "plain"
)
// NewFormatter selects the appropriate renderer based on format key.
func NewFormatter(format string) (Formatter, error) {
switch format {
case FormatPlain:
return NewPlainFormatter(), nil
case FormatTable, "":
return NewTableFormatter(), nil
default:
return nil, fmt.Errorf("unsupported format %q", format)
}
}

View File

@@ -0,0 +1,52 @@
package output
import (
"fmt"
"io"
)
// Formatter renders listing entries in a chosen representation.
type Formatter interface {
Begin(w io.Writer) error
WriteEntry(w io.Writer, entry Entry) error
WriteSummary(w io.Writer, summary Summary) error
}
// Entry represents a single listing output record in a formatter-agnostic form.
type Entry struct {
Path string
Type string
SizeBytes int64
Depth int
MatchedExtension string
}
// Summary aggregates counts for final reporting.
type Summary struct {
Files int
Directories int
Symlinks int
}
// Add records a new entry in the summary counters.
func (s *Summary) Add(entry Entry) {
switch entry.Type {
case "file":
s.Files++
case "directory":
s.Directories++
case "symlink":
s.Symlinks++
}
}
// Total returns the sum of all entry classifications.
func (s Summary) Total() int {
return s.Files + s.Directories + s.Symlinks
}
// DefaultSummaryLine produces a human-readable summary string for any format.
func DefaultSummaryLine(summary Summary) string {
return fmt.Sprintf("Total: %d entries (files: %d, directories: %d, symlinks: %d)",
summary.Total(), summary.Files, summary.Directories, summary.Symlinks)
}

28
internal/output/plain.go Normal file
View File

@@ -0,0 +1,28 @@
package output
import (
"fmt"
"io"
)
// plainFormatter emits one entry per line suitable for piping into other tools.
type plainFormatter struct{}
// NewPlainFormatter constructs a formatter for plain output.
func NewPlainFormatter() Formatter {
return &plainFormatter{}
}
func (plainFormatter) Begin(io.Writer) error {
return nil
}
func (plainFormatter) WriteEntry(w io.Writer, entry Entry) error {
_, err := fmt.Fprintln(w, entry.Path)
return err
}
func (plainFormatter) WriteSummary(w io.Writer, summary Summary) error {
_, err := fmt.Fprintln(w, DefaultSummaryLine(summary))
return err
}

48
internal/output/table.go Normal file
View File

@@ -0,0 +1,48 @@
package output
import (
"fmt"
"io"
"text/tabwriter"
)
// tableFormatter renders aligned columns for human-friendly review.
type tableFormatter struct {
writer *tabwriter.Writer
}
// NewTableFormatter constructs a table formatter.
func NewTableFormatter() Formatter {
return &tableFormatter{}
}
func (f *tableFormatter) Begin(w io.Writer) error {
f.writer = tabwriter.NewWriter(w, 0, 4, 2, ' ', 0)
_, err := fmt.Fprintln(f.writer, "PATH\tTYPE\tSIZE")
return err
}
func (f *tableFormatter) WriteEntry(w io.Writer, entry Entry) error {
if f.writer == nil {
return fmt.Errorf("table formatter not initialized")
}
size := "-"
if entry.Type == "file" && entry.SizeBytes >= 0 {
size = fmt.Sprintf("%d", entry.SizeBytes)
}
_, err := fmt.Fprintf(f.writer, "%s\t%s\t%s\n", entry.Path, entry.Type, size)
return err
}
func (f *tableFormatter) WriteSummary(w io.Writer, summary Summary) error {
if f.writer == nil {
return fmt.Errorf("table formatter not initialized")
}
if err := f.writer.Flush(); err != nil {
return err
}
_, err := fmt.Fprintln(w, DefaultSummaryLine(summary))
return err
}