latest updayes
This commit is contained in:
parent
8f2da7be66
commit
9f42c8f5bc
|
@ -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
|
||||||
|
}
|
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
package sql
|
||||||
|
|
||||||
|
/*
|
||||||
|
type Where struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Where) And() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Where) Or() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Where) String() string {
|
||||||
|
}
|
||||||
|
*/
|
|
@ -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
8
go.mod
|
@ -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
|
||||||
|
)
|
||||||
|
|
|
@ -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=
|
|
@ -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)
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package log
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPrintf(t *testing.T) {
|
||||||
|
Printf("This is a test\n")
|
||||||
|
Printf("Another test\n")
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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{}) {
|
||||||
|
}
|
|
@ -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")
|
||||||
|
}
|
|
@ -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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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())
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()
|
||||||
|
}
|
|
@ -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())
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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())
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
package table
|
Loading…
Reference in New Issue