Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions internal/printer/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package printer

import (
"fmt"
"io"
"time"

"github.com/tmeckel/azdo-cli/internal/text"
)

// ListPrinter prints data as a list of key/value lines per object, separated by a blank line.
type ListPrinter interface {
Printer
}

func NewListPrinter(w io.Writer) (lp ListPrinter, err error) {
lp = &listPrinter{
out: w,
columns: []string{},
}
return lp, err
}

type listPrinter struct {
out io.Writer
columns []string
currentColumn int
currentFields []string
rows [][]string
}

var _ ListPrinter = &listPrinter{}

func (lp *listPrinter) AddColumns(columns ...string) {
lp.columns = append(lp.columns, columns...)
}

func (lp *listPrinter) AddField(s string, opts ...FieldOption) {
if lp.currentColumn < 0 || len(lp.currentFields) == 0 && lp.currentColumn == 0 {
lp.currentFields = []string{}
}
// ensure order aligns with lp.columns length
lp.currentFields = append(lp.currentFields, s)
lp.currentColumn++
}

func (lp *listPrinter) AddTimeField(now, t time.Time, c func(string) string) {
tf := text.FuzzyAgo(now, t)
lp.AddField(tf)
}

func (lp *listPrinter) EndRow() {
if len(lp.currentFields) > 0 {
lp.rows = append(lp.rows, lp.currentFields)
}
lp.currentFields = nil
lp.currentColumn = 0
}

func (lp *listPrinter) Render() error {
for ri, row := range lp.rows {
for ci, val := range row {
var key string
if ci < len(lp.columns) {
key = lp.columns[ci]
} else {
key = fmt.Sprintf("col%d", ci)
}
_, err := fmt.Fprintf(lp.out, "%s: %s\n", key, val)
if err != nil {
return err
}
}
if ri < len(lp.rows)-1 {
_, err := fmt.Fprint(lp.out, "\n")
if err != nil {
return err
}
}
}
return nil
}
101 changes: 101 additions & 0 deletions internal/printer/list_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package printer

import (
"bytes"
"strings"
"testing"
"time"

"github.com/stretchr/testify/require"
)

func TestListPrinter_SingleRow(t *testing.T) {
buf := &bytes.Buffer{}
lp, err := NewListPrinter(buf)
require.NoError(t, err)

lp.AddColumns("ID", "Name")
lp.AddField("123")
lp.AddField("Repo1")
lp.EndRow()

require.NoError(t, lp.Render())
out := buf.String()
require.Equal(t, "ID: 123\nName: Repo1\n", out)
}

func TestListPrinter_MultipleRows(t *testing.T) {
buf := &bytes.Buffer{}
lp, _ := NewListPrinter(buf)

lp.AddColumns("ID", "Name")
// Row 1
lp.AddField("123")
lp.AddField("Repo1")
lp.EndRow()
// Row 2
lp.AddField("456")
lp.AddField("Repo2")
lp.EndRow()

require.NoError(t, lp.Render())
lines := strings.Split(buf.String(), "\n")
// Expect blank line between objects
require.Contains(t, buf.String(), "\n\n")
require.Equal(t, "ID: 123", lines[0])
require.Equal(t, "Name: Repo1", lines[1])
require.Equal(t, "ID: 456", lines[3])
require.Equal(t, "Name: Repo2", lines[4])
}

func TestListPrinter_MissingColumnName(t *testing.T) {
buf := &bytes.Buffer{}
lp, _ := NewListPrinter(buf)

lp.AddColumns("ID")
lp.AddField("123")
lp.AddField("ExtraField") // no column name for this index
lp.EndRow()

require.NoError(t, lp.Render())
out := buf.String()
require.Contains(t, out, "col1: ExtraField")
}

func TestListPrinter_AddTimeField(t *testing.T) {
buf := &bytes.Buffer{}
lp, _ := NewListPrinter(buf)
lp.AddColumns("Created")
now := time.Date(2024, 1, 1, 12, 0, 0, 0, time.UTC)
past := now.Add(-time.Hour)

lp.AddTimeField(now, past, nil)
lp.EndRow()

require.NoError(t, lp.Render())
out := buf.String()
require.Contains(t, out, "Created:")
}

// Negative/edge case tests
func TestListPrinter_NoColumns(t *testing.T) {
buf := &bytes.Buffer{}
lp, _ := NewListPrinter(buf)

lp.AddField("ValueWithoutHeader")
lp.EndRow()

// Even without columns, should render with col index fallback
require.NoError(t, lp.Render())
out := buf.String()
require.Contains(t, out, "col0: ValueWithoutHeader")
}

func TestListPrinter_RenderEmpty(t *testing.T) {
buf := &bytes.Buffer{}
lp, _ := NewListPrinter(buf)

// No rows added; Render should produce no error and empty output
require.NoError(t, lp.Render())
require.Equal(t, "", buf.String())
}
Loading