diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 11cd647..bf7fd76 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,7 +14,7 @@ jobs: test: strategy: matrix: - go-version: [1.19.x] + go-version: [stable,oldstable] os: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.os }} steps: @@ -34,7 +34,7 @@ jobs: fetch-depth: 2 - uses: actions/setup-go@v2 with: - go-version: '1.19.x' + go-version: 'stable' - name: Run coverage run: go test -race -coverprofile=coverage.txt -covermode=atomic - name: Upload coverage to Codecov diff --git a/README.md b/README.md index 05a5684..40a7a94 100644 --- a/README.md +++ b/README.md @@ -70,27 +70,6 @@ w.MustPrint(struct { // {"FirstName":"Bob","LastName":"Ross"} ``` -### Cobra Integration - -To simplify using this in new projects, you can use the `NewWithCobraCmd` -method. Example: - -```go -// By default, look for a field called 'format' -w, err := NewWithCobraCmd(cmd, nil) -``` - -```go -// Or pass a configuration object with what the field is called -w, err := NewWithCobraCmd(cmd, &gout.CobraCmdConfig{ - FormatField: "my-special-name-field", -}) -``` - -By default, the gout will use os.Stdout as the default writer. - -See [_examples](_examples/) for more example usage - ## Built-in Formatters ### YAML diff --git a/_examples/basic/main.go b/_examples/basic/main.go index 30f6908..aefdc13 100644 --- a/_examples/basic/main.go +++ b/_examples/basic/main.go @@ -1,19 +1,19 @@ package main import ( + "fmt" "os" gout "github.com/drewstinnett/gout/v2" - "github.com/drewstinnett/gout/v2/formats/json" + "github.com/drewstinnett/gout/v2/formats" + _ "github.com/drewstinnett/gout/v2/formats/builtin" ) func main() { - w, err := gout.New() - if err != nil { - panic(err) - } + w := gout.New() + fmt.Printf("Active Formatters: %v\n", formats.Names()) // By Default, the YAML format is use, Let's change it to json though - w.SetFormatter(json.Formatter{}) + // w.SetFormatter(json.Formatter{}) // By Default, print to stdout. Let's change it to stderr though w.SetWriter(os.Stderr) diff --git a/_examples/formats/main.go b/_examples/formats/main.go index f832128..d320659 100644 --- a/_examples/formats/main.go +++ b/_examples/formats/main.go @@ -4,6 +4,7 @@ import ( "fmt" gout "github.com/drewstinnett/gout/v2" + "github.com/drewstinnett/gout/v2/formats" ) type sample struct { @@ -21,19 +22,17 @@ func main() { sample{FirstName: "Freddy", LastName: "Krueger", Age: 35}, sample{FirstName: "Michael", LastName: "Myers", Age: 13}, } - c, _ := gout.New() - for formatN, formatF := range gout.BuiltInFormatters { - if formatN != "gotemplate" { - fmt.Printf("# Format: %v\n", formatN) - c.SetFormatter(formatF) - // CSV Formatter won't work on a single object, has to be iterable - if formatN != "csv" { - fmt.Println("## Person") - c.MustPrint(person) - } - fmt.Println("## People") - c.MustPrint(people) - fmt.Println() + g := gout.New() + for formatN, formatG := range formats.Formats { + fmt.Printf("# Format: %v\n", formatN) + g.SetFormatter(formatG()) + // CSV Formatter won't work on a single object, has to be iterable + if formatN != "csv" { + fmt.Println("## Person") + g.MustPrint(person) } + fmt.Println("## People") + g.MustPrint(people) + fmt.Println() } } diff --git a/_examples/gotemplate/main.go b/_examples/gotemplate/main.go index 1c418f6..fafc325 100644 --- a/_examples/gotemplate/main.go +++ b/_examples/gotemplate/main.go @@ -4,6 +4,7 @@ import ( "fmt" gout "github.com/drewstinnett/gout/v2" + "github.com/drewstinnett/gout/v2/config" "github.com/drewstinnett/gout/v2/formats/gotemplate" ) @@ -21,12 +22,15 @@ func main() { sample{FirstName: "Freddy", LastName: "Krueger", Age: 35}, sample{FirstName: "Michael", LastName: "Myers", Age: 13}, } - c, _ := gout.New() - c.SetFormatter(gotemplate.Formatter{ - Template: "{{ range . }}{{ .FirstName }} {{ .LastName }} is {{ .Age }} years old\n{{ end }}", + g := gout.New() + g.SetFormatter(gotemplate.Formatter{ + Opts: config.FormatterOpts{ + "template": "{{ range . }}{{ .FirstName }} {{ .LastName }} is {{ .Age }} years old\n{{ end }}", + }, }) + //"template": "{{ range . }}{{ .FirstName }} {{ .LastName }} is {{ .Age }} years old\n{{ end }}", fmt.Printf("# Format: gotemplate\n## People\n") - c.MustPrint(people) + g.MustPrint(people) fmt.Println() // fmt.Println(string(b)) diff --git a/formats/builtin/builtin.go b/formats/builtin/builtin.go new file mode 100644 index 0000000..8d00854 --- /dev/null +++ b/formats/builtin/builtin.go @@ -0,0 +1,10 @@ +/* +Package builtin is a helper to register all the built in formatters +*/ +package builtin + +import ( + _ "github.com/drewstinnett/gout/v2/formats/gotemplate" + _ "github.com/drewstinnett/gout/v2/formats/json" + _ "github.com/drewstinnett/gout/v2/formats/yaml" +) diff --git a/formats/gotemplate/gotemplate.go b/formats/gotemplate/gotemplate.go index b60fdc4..0e1a400 100644 --- a/formats/gotemplate/gotemplate.go +++ b/formats/gotemplate/gotemplate.go @@ -6,6 +6,7 @@ import ( "text/template" "github.com/drewstinnett/gout/v2/config" + "github.com/drewstinnett/gout/v2/formats" ) type Formatter struct { @@ -35,22 +36,8 @@ func (w Formatter) Format(v interface{}) ([]byte, error) { return doc.Bytes(), nil } -/* -func (w Formatter) formatWithOpts(v interface{}, o config.FormatterOpts) ([]byte, error) { - if _, ok := o["template"]; !ok { - return nil, errors.New("Must pass 'template' in to options") - } - - tpl := o["template"].(string) - var doc bytes.Buffer - tmpl, err := template.New("item").Parse(tpl) - if err != nil { - return nil, err - } - err = tmpl.Execute(&doc, v) - if err != nil { - return nil, err - } - return doc.Bytes(), nil +func init() { + formats.Add("gotemplate", func() formats.Formatter { + return &Formatter{} + }) } -*/ diff --git a/formats/json/json.go b/formats/json/json.go index 72c7588..2218a07 100644 --- a/formats/json/json.go +++ b/formats/json/json.go @@ -3,6 +3,8 @@ package json import ( "context" ujson "encoding/json" + + "github.com/drewstinnett/gout/v2/formats" ) type Formatter struct { @@ -30,3 +32,9 @@ func (w *Formatter) withContext(ctx context.Context) *Formatter { w.ctx = ctx return w } + +func init() { + formats.Add("json", func() formats.Formatter { + return &Formatter{} + }) +} diff --git a/formats/registry.go b/formats/registry.go new file mode 100644 index 0000000..b541d73 --- /dev/null +++ b/formats/registry.go @@ -0,0 +1,27 @@ +package formats + +import "sort" + +type Creator func() Formatter + +var Formats = map[string]Creator{} + +// Add a new format creator +func Add(name string, creator Creator) { + Formats[name] = creator +} + +// Names returns a slice of the names of the formatters +func Names() []string { + ret := []string{} + for k := range Formats { + ret = append(ret, k) + } + sort.Strings(ret) + return ret +} + +// Formatter interface that defines how a thing can be formatted for output +type Formatter interface { + Format(interface{}) ([]byte, error) +} diff --git a/formats/registry_test.go b/formats/registry_test.go new file mode 100644 index 0000000..00035a6 --- /dev/null +++ b/formats/registry_test.go @@ -0,0 +1,17 @@ +package formats_test + +import ( + "testing" + + "github.com/drewstinnett/gout/v2/formats" + _ "github.com/drewstinnett/gout/v2/formats/builtin" + "github.com/stretchr/testify/require" +) + +func TestBuiltinRegistry(t *testing.T) { + require.Equal( + t, + []string{"gotemplate", "json", "yaml"}, + formats.Names(), + ) +} diff --git a/formats/yaml/yaml.go b/formats/yaml/yaml.go index 3baa612..8099519 100644 --- a/formats/yaml/yaml.go +++ b/formats/yaml/yaml.go @@ -1,6 +1,7 @@ package yaml import ( + "github.com/drewstinnett/gout/v2/formats" uyaml "gopkg.in/yaml.v3" ) @@ -9,3 +10,9 @@ type Formatter struct{} func (w Formatter) Format(v interface{}) ([]byte, error) { return uyaml.Marshal(v) } + +func init() { + formats.Add("yaml", func() formats.Formatter { + return &Formatter{} + }) +} diff --git a/formatters.go b/formatters.go index d9c80f5..c8518d7 100644 --- a/formatters.go +++ b/formatters.go @@ -1,17 +1,8 @@ package gout -import ( - "github.com/drewstinnett/gout/v2/formats/csv" - "github.com/drewstinnett/gout/v2/formats/gotemplate" - "github.com/drewstinnett/gout/v2/formats/json" - "github.com/drewstinnett/gout/v2/formats/plain" - "github.com/drewstinnett/gout/v2/formats/toml" - "github.com/drewstinnett/gout/v2/formats/xml" - "github.com/drewstinnett/gout/v2/formats/yaml" -) - // BuiltInFormatters is a map of all formatters that we ship -var BuiltInFormatters = map[string]Formatter{ +/* +var BuiltInFormatters = map[string]formats.Formatter{ "json": json.Formatter{}, "yaml": yaml.Formatter{}, "plain": plain.Formatter{}, @@ -20,3 +11,5 @@ var BuiltInFormatters = map[string]Formatter{ "xml": xml.Formatter{}, "gotemplate": gotemplate.Formatter{}, } + +*/ diff --git a/formatters_test.go b/formatters_test.go index d8f07a0..8dac72e 100644 --- a/formatters_test.go +++ b/formatters_test.go @@ -1,12 +1,8 @@ package gout -import ( - "testing" - - "github.com/stretchr/testify/require" -) - +/* func TestBuiltInFormatters(t *testing.T) { require.Contains(t, BuiltInFormatters, "yaml") require.NotContains(t, BuiltInFormatters, "never-exist") } +*/ diff --git a/gout.go b/gout.go index f49e462..165b00c 100644 --- a/gout.go +++ b/gout.go @@ -6,27 +6,15 @@ import ( "io" "os" + "github.com/drewstinnett/gout/v2/formats" "github.com/drewstinnett/gout/v2/formats/yaml" ) -// Formatter interface that defines how a thing can be formatted for output -type Formatter interface { - Format(interface{}) ([]byte, error) - // FormatWithOpts(interface{}, config.FormatterOpts) ([]byte, error) - // FormatWithContext(context.Context, interface{}) ([]byte, error) -} - -// FormatterOpts is an arbitrary configuration map to interface. Pass useful -// format specific options in here -type FormatterOpts map[string]interface{} - // Gout is a structure you can use that contains a formatter, and a target // io.Writer type Gout struct { - // The format! - Formatter Formatter - // Target io.Writer output - Writer io.Writer + formatter formats.Formatter + writer io.Writer } // Use this for doing things without explicitely creating a new gout, similar to @@ -34,11 +22,11 @@ type Gout struct { var gi *Gout func init() { - gi = MustNew() + gi = New() } -// GetGout gets the default Gout instance -func GetGout() *Gout { +// Get gets the default Gout instance +func Get() *Gout { return gi } @@ -47,14 +35,14 @@ func GetGout() *Gout { func SetWriter(i io.Writer) { gi.SetWriter(i) } func (g *Gout) SetWriter(i io.Writer) { - g.Writer = i + g.writer = i } // SetFormatter sets the Formatter to use for the text. -func SetFormatter(f Formatter) { gi.SetFormatter(f) } +func SetFormatter(f formats.Formatter) { gi.SetFormatter(f) } -func (g *Gout) SetFormatter(f Formatter) { - g.Formatter = f +func (g *Gout) SetFormatter(f formats.Formatter) { + g.formatter = f } // Print print an interface using the given Formatter and io.Writer @@ -71,7 +59,7 @@ func (g *Gout) Print(v interface{}) (err error) { if err != nil { return err } - fmt.Fprint(g.Writer, string(b)) + fmt.Fprint(g.writer, string(b)) return err } @@ -91,7 +79,7 @@ func (g *Gout) PrintMulti(v ...interface{}) (err error) { if err != nil { return err } - fmt.Fprint(g.Writer, string(b)) + fmt.Fprint(g.writer, string(b)) return err } @@ -123,45 +111,35 @@ type Option func(*Gout) // output func WithWriter(w io.Writer) Option { return func(g *Gout) { - g.Writer = w + g.writer = w } } // WithFormatter can be passed to New(), specifying which Formatter should be // used for output -func WithFormatter(f Formatter) Option { +func WithFormatter(f formats.Formatter) Option { return func(g *Gout) { - g.Formatter = f + g.formatter = f } } // New creates a pointer to a new Gout, with some sensible defaults -func New(opts ...Option) (*Gout, error) { - defaultFormatter := yaml.Formatter{} - defaultWriter := os.Stdout +func New(opts ...Option) *Gout { g := &Gout{ - Formatter: defaultFormatter, - Writer: defaultWriter, + formatter: yaml.Formatter{}, + writer: os.Stdout, } for _, opt := range opts { opt(g) } - return g, nil -} - -func MustNew(opts ...Option) *Gout { - g, err := New(opts...) - if err != nil { - panic(err) - } return g } func (g *Gout) itemizedFormatter(v ...interface{}) ([]byte, error) { var buf bytes.Buffer for _, item := range v { - bi, err := g.Formatter.Format(item) + bi, err := g.formatter.Format(item) if err != nil { return nil, err } diff --git a/gout_test.go b/gout_test.go index c608904..fb096cd 100644 --- a/gout_test.go +++ b/gout_test.go @@ -12,14 +12,11 @@ import ( ) func TestNewWriter(t *testing.T) { - c, err := New() - require.NoError(t, err) - require.NotNil(t, c) + require.NotNil(t, New()) } func TestSetGoutWriter(t *testing.T) { - c, err := New() - require.NoError(t, err) + c := New() var buf bytes.Buffer c.SetWriter(&buf) c.Print(struct{ Foo string }{Foo: "bar"}) @@ -27,13 +24,12 @@ func TestSetGoutWriter(t *testing.T) { // Make sure we can change the formatter c.SetFormatter(plain.Formatter{}) - require.IsType(t, plain.Formatter{}, c.Formatter) + require.IsType(t, plain.Formatter{}, c.formatter) } func TestNewGoutWithWriter(t *testing.T) { var b bytes.Buffer - c, err := New(WithWriter(&b)) - require.NoError(t, err) + c := New(WithWriter(&b)) require.NotNil(t, c) c.Print(struct{ Foo string }{Foo: "bar"}) require.Equal(t, "foo: bar\n", b.String()) @@ -41,18 +37,15 @@ func TestNewGoutWithWriter(t *testing.T) { func TestNewGoutWithFormatter(t *testing.T) { var b bytes.Buffer - c, err := New(WithWriter(&b), WithFormatter(plain.Formatter{})) - require.NoError(t, err) + c := New(WithWriter(&b), WithFormatter(plain.Formatter{})) require.NotNil(t, c) c.Print(struct{ Foo string }{Foo: "bar"}) require.Equal(t, "{Foo:bar}\n", b.String()) } func TestPrintError(t *testing.T) { - c, err := New() - require.NoError(t, err) - err = c.Print(make(chan int)) - require.Error(t, err) + c := New() + require.Error(t, c.Print(make(chan int))) unprintable := make(chan int) require.Panics(t, func() { c.MustPrint(unprintable) }) @@ -73,26 +66,23 @@ func TestBuiltinGout(t *testing.T) { require.NotPanics(t, func() { MustPrintMulti("foo", "bar") }) - got := GetGout() + got := Get() require.NotNil(t, got) } func TestWriterPrinterMulti(t *testing.T) { - c, err := New() - require.NoError(t, err) + c := New() var buf bytes.Buffer c.SetWriter(&buf) - err = c.PrintMulti( + require.NoError(t, c.PrintMulti( struct{ Foo string }{Foo: "bar"}, struct{ Year int }{Year: 1978}, - ) - require.NoError(t, err) + )) require.Equal(t, "- foo: bar\n- year: 1978\n", buf.String()) } func TestWriterAddNewlines(t *testing.T) { - c, err := New() - require.NoError(t, err) + c := New() c.SetFormatter(json.Formatter{}) var buf bytes.Buffer c.SetWriter(&buf)