latest updayes

This commit is contained in:
spsobole 2024-01-01 09:55:54 -07:00
parent 8f2da7be66
commit 9f42c8f5bc
24 changed files with 1033 additions and 0 deletions

214
db/sql/db.go Normal file
View File

@ -0,0 +1,214 @@
package sql
import (
"database/sql"
"fmt"
sq "github.com/Masterminds/squirrel"
"github.com/blockloop/scan/v2"
)
type Logger interface {
Debugf(format string, args ...any)
}
type Database struct {
sql *sql.DB
log Logger
placeholderFormat sq.PlaceholderFormat
DebugEnabled bool
}
func (db *Database) builder() sq.StatementBuilderType {
return sq.StatementBuilder.PlaceholderFormat(db.placeholderFormat)
}
func (db *Database) logQuery(query string, args ...interface{}) {
if db.DebugEnabled {
db.log.Debugf("%s\n", query)
}
}
// ObjectGet retrieves 1 object based on provided criteria
func (db *Database) ObjectGet(table string, pred []interface{}, obj interface{}) (error, bool) {
sb := db.builder().Select("*").From(table)
if len(pred) == 1 {
sb = sb.Where(pred[0])
} else if len(pred) > 1 {
sb = sb.Where(pred[0], pred[1:]...)
}
query, args, err := sb.ToSql()
if err != nil {
return err, false
}
rows, err := db.sql.Query(query, args...)
if err != nil {
if err == sql.ErrNoRows {
return nil, false
}
return NewError(query, err), false
}
defer rows.Close()
err = scan.Row(obj, rows)
if err != nil {
if err == sql.ErrNoRows {
return nil, false
}
return err, false
}
return nil, true
}
func (db *Database) Insert(table string, record interface{}) error {
m := GetRecordMap(record)
query, args, err := db.builder().Insert(table).SetMap(m).ToSql()
if err != nil {
return NewError(query, err)
}
db.logQuery(query)
stmt, err := db.sql.Prepare(query)
if err != nil {
return NewError(query, err)
}
_, err = stmt.Exec(args...)
if err != nil {
return err // Concern maybe no rows?
}
defer stmt.Close()
return nil
}
// ListRows performs a generic list from a db based on the passed in parameters
func (db *Database) ListRows(table string, pred []interface{}, order []string, scanFunc func(rows *sql.Rows) error) error {
sb := db.builder().Select("*").From(table)
if len(pred) == 1 {
sb = sb.Where(pred[0])
} else if len(pred) > 1 {
sb = sb.Where(pred[0], pred[1:]...)
}
if len(order) != 0 {
sb = sb.OrderBy(order...)
}
query, args, err := sb.ToSql()
if err != nil {
return err
}
db.logQuery(query, args...)
rows, err := db.SQL().Query(query, args...)
if err != nil {
if err == sql.ErrNoRows {
return nil
}
return err
}
defer rows.Close()
return scanFunc(rows)
}
func (db *Database) Update(table string, constraint string, value interface{}, record interface{}) error {
m := GetRecordMap(record)
delete(m, constraint)
query, args, err := db.builder().Update(table).SetMap(m).Where(sq.Eq{constraint: value}).ToSql()
if err != nil {
return NewError(query, err)
}
db.logQuery(query, args...)
stmt, err := db.sql.Prepare(query)
if err != nil {
return NewError(query, err)
}
result, err := stmt.Exec(args...)
if err != nil {
return NewError(query, err)
}
defer stmt.Close()
c, err := result.RowsAffected()
if err != nil {
return err
}
if c == 0 {
return sql.ErrNoRows
}
return nil
}
func (db *Database) InsertOrUpdate(table string, constraint string, value interface{}, record interface{}) error {
err := db.Update(table, constraint, value, record)
if err == sql.ErrNoRows {
return db.Insert(table, record)
}
return err
}
// CountRows counts the number of rows matching the where clause
func (db *Database) CountRows(table string, where string) (int, error) {
query := fmt.Sprintf("select count(*) from %s", table)
if where != "" {
query += fmt.Sprintf(" WHERE %s", where)
}
query += ";"
db.logQuery(query)
count := 0
row := db.SQL().QueryRow(query)
err := row.Scan(&count)
if err != nil {
if err == sql.ErrNoRows {
return 0, nil
}
return 0, NewError(query, err)
}
return count, nil
}
// Close closes the db connection
func (db *Database) Close() error {
if db.sql != nil {
return db.sql.Close()
}
return nil
}
// SQL returns the accessor to the sql driver under the covers.. used for importing data
func (db *Database) SQL() *sql.DB {
return db.sql
}
func New(dbURN string) (*Database, error) {
if dbURN == "" {
return nil, fmt.Errorf("no database URN provided")
}
sqlDB, err := sql.Open("postgres", dbURN)
if err != nil {
return nil, err
}
err = sqlDB.Ping()
if err != nil {
_ = sqlDB.Close()
return nil, err
}
return &Database{
sql: sqlDB,
}, nil
}

21
db/sql/error.go Normal file
View File

@ -0,0 +1,21 @@
package sql
import (
"fmt"
)
type SQLError struct {
Query string
Err error
}
func (e *SQLError) Error() string {
return fmt.Sprintf("%s [%s]", e.Err, e.Query)
}
func NewError(query string, err error) *SQLError {
return &SQLError{
Query: query,
Err: err,
}
}

31
db/sql/utility.go Normal file
View File

@ -0,0 +1,31 @@
package sql
import (
"reflect"
)
func GetRecordMap(s interface{}) map[string]interface{} {
rv := reflect.ValueOf(s)
rt := reflect.TypeOf(s)
if rt.Kind() == reflect.Ptr {
rv = rv.Elem()
rt = rt.Elem()
} else {
panic("must be ptr")
}
fields := make(map[string]interface{})
// rt := reflect.TypeOf(s)
//rv := reflect.ValueOf(s)
for i := 0; i < rt.NumField(); i++ {
field := rt.Field(i)
dbKey := field.Tag.Get("db")
if dbKey == "" {
continue
}
fields[dbKey] = rv.Field(i).Interface()
}
return fields
}

15
db/sql/where.go Normal file
View File

@ -0,0 +1,15 @@
package sql
/*
type Where struct {
}
func (w *Where) And() {
}
func (w *Where) Or() {
}
func (w *Where) String() string {
}
*/

22
generics/map.go Normal file
View File

@ -0,0 +1,22 @@
package generics
// FlattenMap takes a map and flattens it to an array
func FlattenMap[K comparable, V any](m map[K]V) []V {
slice := make([]V, 0, len(m))
for k := range m {
slice = append(slice, m[k])
}
return slice
}
// FlattenMapOrdered takes a map content in a specific order
func FlattenMapOrdered[K comparable, V any](m map[K]V, orderBy []K) []V {
slice := make([]V, 0, len(orderBy))
for _, key := range orderBy {
if v, ok := m[key]; ok {
slice = append(slice, v)
}
}
return slice
}

8
go.mod
View File

@ -1,3 +1,11 @@
module git.twelvetwelve.org/library/core module git.twelvetwelve.org/library/core
go 1.20 go 1.20
require (
github.com/Masterminds/squirrel v1.5.4 // indirect
github.com/blockloop/scan/v2 v2.5.0 // indirect
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
golang.org/x/text v0.12.0 // indirect
)

13
go.sum Normal file
View File

@ -0,0 +1,13 @@
github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM=
github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10=
github.com/blockloop/scan/v2 v2.5.0 h1:/yNcCwftYn3wf5BJsJFO9E9P48l45wThdUnM3WcDF+o=
github.com/blockloop/scan/v2 v2.5.0/go.mod h1:OFYyMocUdRW3DUWehPI/fSsnpNMUNiyUaYXRMY5NMIY=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw=
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o=
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk=
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=

31
iox/writer_progress.go Normal file
View File

@ -0,0 +1,31 @@
package iox
import (
"io"
"time"
)
type WriteProgressFunc func(sofar int64)
type WriteProgress struct {
WriteTo io.Writer
last time.Time
count int64
Progress WriteProgressFunc
}
func (wc *WriteProgress) Done() {
wc.Progress(wc.count)
}
func (wc *WriteProgress) Write(p []byte) (int, error) {
wc.count += int64(len(p))
now := time.Now()
if now.Sub(wc.last) > time.Duration(time.Second*1) {
wc.last = now
wc.Progress(wc.count)
}
return wc.WriteTo.Write(p)
}

26
json/io.go Normal file
View File

@ -0,0 +1,26 @@
package json
import (
"encoding/json"
"io/fs"
"os"
)
func ReadFromFile(filename string, v interface{}) error {
raw, err := os.ReadFile(filename)
if err != nil {
return err
}
return json.Unmarshal(raw, v)
}
func WriteToFile(filename string, v interface{}, mode fs.FileMode) error {
b, err := json.Marshal(v)
if err != nil {
return err
}
err = os.WriteFile(filename, b, mode)
return err
}

36
json/io_test.go Normal file
View File

@ -0,0 +1,36 @@
package json
import (
"os"
"testing"
"git.twelvetwelve.org/library/core/testutil/assert"
)
type testIoStruct struct {
Value string
Count int
}
func TestIo(t *testing.T) {
tmpFile := "io_test.tmp"
v := testIoStruct{
Value: "test",
Count: 999,
}
defer os.Remove(tmpFile)
err := ReadFromFile(tmpFile, &v)
assert.NotNil(t, err)
err = WriteToFile(tmpFile, &v, 0644)
assert.NoError(t, err)
read := testIoStruct{}
err = ReadFromFile(tmpFile, &read)
assert.NoError(t, err)
assert.Equal(t, v, read)
}

10
log/log_test.go Normal file
View File

@ -0,0 +1,10 @@
package log
import (
"testing"
)
func TestPrintf(t *testing.T) {
Printf("This is a test\n")
Printf("Another test\n")
}

19
reflectutils/utils.go Normal file
View File

@ -0,0 +1,19 @@
package reflectutils
import (
"fmt"
"reflect"
)
func GetTagValueRequired(t reflect.StructField, name string) (string, error) {
tag, ok := t.Tag.Lookup(name)
if !ok {
return "", fmt.Errorf("field missing required tag: %s", name)
}
return tag, nil
}
func GetTagValue(t reflect.StructField, name string) string {
tag, _ := t.Tag.Lookup(name)
return tag
}

109
slog/log.go Normal file
View File

@ -0,0 +1,109 @@
package slog
import (
"encoding/json"
"io"
"os"
"strings"
)
type LogWriter interface {
WriteLog(msg string, kvs map[string]interface{})
}
func Printf(format string, args ...any) {
formatLoc := format
optNum := 0
for bofs := strings.Index(formatLoc, "{{"); bofs != -1; bofs = strings.Index(formatLoc, "{{") {
if optNum > len(args) {
break
}
os.Stdout.Write([]byte(formatLoc[0:bofs]))
formatLoc = formatLoc[bofs+2:]
eofs := strings.Index(formatLoc, "}}")
if eofs == -1 {
break
}
b, _ := json.Marshal(args[optNum])
os.Stdout.Write(b)
optNum++
formatLoc = formatLoc[eofs+2:]
}
os.Stdout.Write([]byte(formatLoc))
}
type Logger struct {
w LogWriter
}
type KV struct {
Key string
Value any
}
func (p *Logger) Printf(format string, args ...interface{}) {
formatLoc := format
optNum := 0
kvs := make(map[string]interface{})
for bofs := strings.Index(formatLoc, "{{"); bofs != -1; bofs = strings.Index(formatLoc, "{{") {
if optNum > len(args) {
break
}
formatLoc = formatLoc[bofs+2:]
eofs := strings.Index(formatLoc, "}}")
if eofs == -1 {
break
}
key := formatLoc[0:eofs]
kvs[key] = args[optNum]
optNum++
formatLoc = formatLoc[eofs+2:]
}
p.w.WriteLog(format, kvs)
}
func NewLogger(w LogWriter) *Logger {
return &Logger{
w: w,
}
}
type JsonAdapter struct {
w io.Writer
}
func NewJsonAdapter(w io.Writer) LogWriter {
return &JsonAdapter{w: w}
}
func (j *JsonAdapter) WriteLog(msg string, kvs map[string]interface{}) {
kvs["Log"] = msg
b, _ := json.Marshal(kvs)
j.w.Write(b)
}
type NullAdapter struct {
w io.Writer
}
func NewNullAdapter() LogWriter {
return &NullAdapter{}
}
func (j *NullAdapter) WriteLog(msg string, kvs map[string]interface{}) {
}

9
slog/log_test.go Normal file
View File

@ -0,0 +1,9 @@
package slog
import (
"testing"
)
func TestPrintf(t *testing.T) {
Printf("This is the {{Test}} for {{Idea}} stuff\n", 1, "Fancy Logging")
}

View File

@ -28,3 +28,9 @@ func NotNil(t *testing.T, v interface{}) {
t.Fatalf("nil") t.Fatalf("nil")
} }
} }
func NoError(t *testing.T, err error) {
if err != nil {
t.Fatalf(err.Error())
}
}

48
user/userconfig.go Normal file
View File

@ -0,0 +1,48 @@
package user
import (
"encoding/json"
"os"
"path"
"github.com/mitchellh/go-homedir"
)
func ReadUserConfig(appName, configName string, config interface{}) error {
dir, err := homedir.Dir()
if err != nil {
return err
}
filePath := path.Join(dir, ".config", appName, configName)
data, err := os.ReadFile(filePath)
if err != nil {
return err
}
return json.Unmarshal(data, config)
}
func WriteUserConfig(appName, configName string, config interface{}) error {
dir, err := homedir.Dir()
if err != nil {
return err
}
data, err := json.Marshal(config)
if err != nil {
return err
}
filePath := path.Join(dir, ".config", appName)
err = os.MkdirAll(filePath, 0700)
if err != nil {
return err
}
filePath = path.Join(filePath, configName)
return os.WriteFile(filePath, data, 0700)
}

48
writer/table/csv.go Normal file
View File

@ -0,0 +1,48 @@
package table
import (
"fmt"
"io"
)
type CSVPrinter struct {
writer io.Writer
}
// Headers specify the table headers
func (p *CSVPrinter) Headers(headers ...string) error {
for _, header := range headers {
if _, err := fmt.Fprintf(p.writer, "%s,", header); err != nil {
return err
}
}
_, err := fmt.Fprintf(p.writer, "\n")
return err
}
// Fields add another row
func (p *CSVPrinter) Fields(fields ...interface{}) error {
for _, field := range fields {
if _, err := fmt.Fprintf(p.writer, "%v,", field); err != nil {
return err
}
}
_, err := fmt.Fprintf(p.writer, "\n")
return err
}
func (p *CSVPrinter) StringFields(fields ...string) error {
for _, field := range fields {
if _, err := fmt.Fprintf(p.writer, "%v,", field); err != nil {
return err
}
}
_, err := fmt.Fprintf(p.writer, "\n")
return err
}
// Flush implements the flush interface but for CVS printer is a noop
func (p *CSVPrinter) Flush() error {
return nil
}

51
writer/table/csv_test.go Normal file
View File

@ -0,0 +1,51 @@
package table
import (
"bytes"
"testing"
)
func TestCSVHeaders(t *testing.T) {
buffer := new(bytes.Buffer)
printer := &CSVPrinter{writer: buffer}
err := printer.Headers("Name", "Age")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected := "Name,Age,\n"
if buffer.String() != expected {
t.Errorf("Expected '%s', got '%s'", expected, buffer.String())
}
}
func TestCSVFields(t *testing.T) {
buffer := new(bytes.Buffer)
printer := &CSVPrinter{writer: buffer}
err := printer.Fields("John Doe", 30)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected := "John Doe,30,\n"
if buffer.String() != expected {
t.Errorf("Expected '%s', got '%s'", expected, buffer.String())
}
}
func TestCSVStringFields(t *testing.T) {
buffer := new(bytes.Buffer)
printer := &CSVPrinter{writer: buffer}
err := printer.StringFields("John Doe", "30")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected := "John Doe,30,\n"
if buffer.String() != expected {
t.Errorf("Expected '%s', got '%s'", expected, buffer.String())
}
}

48
writer/table/human.go Normal file
View File

@ -0,0 +1,48 @@
package table
import (
"fmt"
"text/tabwriter"
)
// HumanPrinter prints tabulated data as a table
type HumanPrinter struct {
writer *tabwriter.Writer
fields int
}
// Headers specify the table headers
func (p *HumanPrinter) Headers(headers ...string) error {
for _, header := range headers {
if _, err := fmt.Fprintf(p.writer, "%s\t", header); err != nil {
return nil
}
}
_, err := fmt.Fprintf(p.writer, "\n")
return err
}
func (p *HumanPrinter) Fields(fields ...interface{}) error {
for _, header := range fields {
if _, err := fmt.Fprintf(p.writer, "%v\t", header); err != nil {
return err
}
}
_, err := fmt.Fprintf(p.writer, "\n")
return err
}
func (p *HumanPrinter) StringFields(fields ...string) error {
for _, header := range fields {
if _, err := fmt.Fprintf(p.writer, "%v\t", header); err != nil {
return err
}
}
_, err := fmt.Fprintf(p.writer, "\n")
return err
}
// Flush prints the data set
func (p *HumanPrinter) Flush() error {
return p.writer.Flush()
}

View File

@ -0,0 +1,58 @@
package table
import (
"bytes"
"testing"
"text/tabwriter"
)
func TestHeaders(t *testing.T) {
buffer := new(bytes.Buffer)
writer := tabwriter.NewWriter(buffer, 0, 0, 1, ' ', 0)
printer := &HumanPrinter{writer: writer}
err := printer.Headers("Name", "Age")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
printer.Flush()
expected := "Name Age \n"
if buffer.String() != expected {
t.Errorf("Expected '%s', got '%s'", expected, buffer.String())
}
}
func TestFields(t *testing.T) {
buffer := new(bytes.Buffer)
writer := tabwriter.NewWriter(buffer, 0, 0, 1, ' ', 0)
printer := &HumanPrinter{writer: writer}
err := printer.Fields("John Doe", 30)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
printer.Flush()
expected := "John Doe 30 \n"
if buffer.String() != expected {
t.Errorf("Expected '%s', got '%s'", expected, buffer.String())
}
}
func TestStringFields(t *testing.T) {
buffer := new(bytes.Buffer)
writer := tabwriter.NewWriter(buffer, 0, 0, 1, ' ', 0)
printer := &HumanPrinter{writer: writer}
err := printer.StringFields("John Doe", "30")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
printer.Flush()
expected := "John Doe 30 \n"
if buffer.String() != expected {
t.Errorf("Expected '%s', got '%s'", expected, buffer.String())
}
}

93
writer/table/json.go Normal file
View File

@ -0,0 +1,93 @@
package table
import (
"fmt"
"io"
"reflect"
)
type JSONPrinter struct {
headers []string
writer io.Writer
continued bool
}
// Headers specify the table headers
func (p *JSONPrinter) Headers(headers ...string) error {
p.headers = make([]string, len(headers), len(headers))
for idx := range headers {
p.headers[idx] = headers[idx]
}
_, err := fmt.Fprintf(p.writer, "[\n")
return err
}
func (p *JSONPrinter) preamble() error {
if p.continued {
if _, err := fmt.Fprintf(p.writer, ",\n"); err != nil {
return err
}
}
if _, err := fmt.Fprintf(p.writer, "{\n"); err != nil {
return err
}
return nil
}
// Fields add another row
func (p *JSONPrinter) Fields(fields ...interface{}) error {
if err := p.preamble(); err != nil {
return err
}
eol := ",\n"
for idx := range fields {
var err error
if idx == len(fields)-1 {
eol = "\n"
}
field := fields[idx]
switch field.(type) {
case string:
_, err = fmt.Fprintf(p.writer, "\"%s\":\"%v\"%s", p.headers[idx], field, eol)
case int, uint, int8, uint8, int32, uint32, int64, uint64, float32, float64:
_, err = fmt.Fprintf(p.writer, "\"%s\": %v%s", p.headers[idx], field, eol)
default:
err = fmt.Errorf("unsupported field type : %s", reflect.TypeOf(field).Name())
}
if err != nil {
return err
}
}
p.continued = true
_, err := fmt.Fprintf(p.writer, "}\n")
return err
}
func (p *JSONPrinter) StringFields(fields ...string) error {
if err := p.preamble(); err != nil {
return err
}
eol := ",\n"
for idx, field := range fields {
if idx == len(fields)-1 {
eol = "\n"
}
_, err := fmt.Fprintf(p.writer, "\"%s\":\"%v\"%s", p.headers[idx], field, eol)
if err != nil {
return err
}
}
p.continued = true
_, err := fmt.Fprintf(p.writer, "}\n")
return err
}
// Flush implements the flush interface but for CVS printer is a noop
func (p *JSONPrinter) Flush() error {
_, err := fmt.Fprintf(p.writer, "]\n")
return err
}

66
writer/table/json_test.go Normal file
View File

@ -0,0 +1,66 @@
package table
import (
"bytes"
"testing"
)
func TestJSONHeaders(t *testing.T) {
buffer := new(bytes.Buffer)
printer := &JSONPrinter{writer: buffer}
err := printer.Headers("Name", "Age")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected := "[\n"
if buffer.String() != expected {
t.Errorf("Expected '%s', got '%s'", expected, buffer.String())
}
}
func TestJSONFields(t *testing.T) {
buffer := new(bytes.Buffer)
printer := &JSONPrinter{writer: buffer, headers: []string{"Name", "Age"}}
err := printer.Fields("John Doe", 30)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected := "{\n\"Name\":\"John Doe\",\n\"Age\": 30\n}\n"
if buffer.String() != expected {
t.Errorf("Expected '%s', got '%s'", expected, buffer.String())
}
}
func TestJSONStringFields(t *testing.T) {
buffer := new(bytes.Buffer)
printer := &JSONPrinter{writer: buffer, headers: []string{"Name", "Age"}}
err := printer.StringFields("John Doe", "30")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected := "{\n\"Name\":\"John Doe\",\n\"Age\":\"30\"\n}\n"
if buffer.String() != expected {
t.Errorf("Expected '%s', got '%s'", expected, buffer.String())
}
}
func TestJSONFlush(t *testing.T) {
buffer := new(bytes.Buffer)
printer := &JSONPrinter{writer: buffer}
err := printer.Flush()
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected := "]\n"
if buffer.String() != expected {
t.Errorf("Expected '%s', got '%s'", expected, buffer.String())
}
}

50
writer/table/printer.go Normal file
View File

@ -0,0 +1,50 @@
package table
import (
"io"
"os"
"text/tabwriter"
)
// TabulatedPrinter defines an interface we can use for a common way to print tabulated data
//
// ie we canb use the same interface to display results as table, csv, json, etc
type TabulatedPrinter interface {
// Headers defines the headers for the values
Headers(...string) error
//Fields appends fields to the printer
Fields(...interface{}) error
StringFields(...string) error
// Flush outputs the tabulated data
Flush() error
}
// NewPrinter returns a new tabulated printer type based on the format specified
// format: can be human, csv
func NewPrinter(format string, w io.Writer) TabulatedPrinter {
if w == nil {
w = os.Stdout
}
switch format {
case "csv":
return &CSVPrinter{
writer: w,
}
case "json":
return &JSONPrinter{
writer: w,
}
case "human":
p := &HumanPrinter{
writer: new(tabwriter.Writer),
}
p.writer.Init(w, 0, 8, 2, ' ', 0)
return p
}
return nil
}

View File

@ -0,0 +1 @@
package table