🔀 Merge branch 'feature/formatters' into develop
This commit is contained in:
commit
2fc60c16c6
15 changed files with 456 additions and 42 deletions
11
cmd/drop.go
11
cmd/drop.go
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"codeberg.org/danjones000/my-log/files"
|
"codeberg.org/danjones000/my-log/files"
|
||||||
|
"codeberg.org/danjones000/my-log/formatters"
|
||||||
"codeberg.org/danjones000/my-log/models"
|
"codeberg.org/danjones000/my-log/models"
|
||||||
"codeberg.org/danjones000/my-log/tools"
|
"codeberg.org/danjones000/my-log/tools"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
@ -58,11 +59,17 @@ var dropCmd = &cobra.Command{
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
by, err := e.MarshalText()
|
|
||||||
|
form, err := formatters.New("plain")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Fprintf(cmd.OutOrStdout(), "%s\n", by)
|
out, err := form.Log(l)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Fprintf(cmd.OutOrStdout(), "%s\n", out)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,14 @@ dotFolder = true
|
||||||
[output.stdout]
|
[output.stdout]
|
||||||
enabled = true
|
enabled = true
|
||||||
[output.stdout.config]
|
[output.stdout.config]
|
||||||
# Whether to output as JSON. Maybe useful to pipe elsewhere.
|
# Formatter to use when outputting to stdout
|
||||||
json = false
|
formatter = "plain"
|
||||||
|
|
||||||
|
[formatters]
|
||||||
|
|
||||||
|
[formatters.json]
|
||||||
|
# Set to true to pretty print JSON output
|
||||||
|
pretty_print = false
|
||||||
|
|
||||||
`
|
`
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ func Load() (Config, error) {
|
||||||
}
|
}
|
||||||
env.Parse(&c)
|
env.Parse(&c)
|
||||||
c.Outputs["stdout"] = loadStdout(c.Outputs["stdout"])
|
c.Outputs["stdout"] = loadStdout(c.Outputs["stdout"])
|
||||||
|
c.Formatters["json"] = loadJsonFormat(c.Formatters["json"])
|
||||||
|
|
||||||
l := ""
|
l := ""
|
||||||
for k, v := range Overrides {
|
for k, v := range Overrides {
|
||||||
|
|
@ -77,3 +78,21 @@ func (oo Outputs) Stdout() (s Stdout, enabled bool) {
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func loadJsonFormat(c map[string]any) map[string]any {
|
||||||
|
jf := JsonFormat{}
|
||||||
|
mapst.Decode(c, &jf)
|
||||||
|
env.Parse(&jf)
|
||||||
|
mapst.Decode(jf, &c)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ff Formatters) Json() (jf JsonFormat) {
|
||||||
|
o, ok := ff["json"]
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mapst.Decode(o, &jf)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,12 @@ func TestOverrideJson(t *testing.T) {
|
||||||
assert.Equal(t, "txt", c.Input.Ext)
|
assert.Equal(t, "txt", c.Input.Ext)
|
||||||
}
|
}
|
||||||
|
|
||||||
// @todo test time
|
func TestTimeParse(t *testing.T) {
|
||||||
|
Overrides = map[string]string{"input.ext": "now"}
|
||||||
|
c, err := Load()
|
||||||
|
assert.ErrorContains(t, err, "incompatible types: TOML value has type time.Time; destination has type string")
|
||||||
|
assert.Equal(t, "txt", c.Input.Ext)
|
||||||
|
}
|
||||||
|
|
||||||
func TestStdoutMissing(t *testing.T) {
|
func TestStdoutMissing(t *testing.T) {
|
||||||
var oo Outputs = map[string]Output{}
|
var oo Outputs = map[string]Output{}
|
||||||
|
|
@ -65,12 +70,25 @@ func TestStdoutMissing(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStdoutLoad(t *testing.T) {
|
func TestStdoutLoad(t *testing.T) {
|
||||||
os.Setenv("LOG_STDOUT_JSON", "true")
|
os.Setenv("LOG_STDOUT_FORMATTER", "json")
|
||||||
defer os.Unsetenv("LOG_STDOUT_JSON")
|
defer os.Unsetenv("LOG_STDOUT_FORMATTER")
|
||||||
os.Setenv("LOG_STDOUT_ENABLED", "true")
|
os.Setenv("LOG_STDOUT_ENABLED", "true")
|
||||||
defer os.Unsetenv("LOG_STDOUT_ENABLED")
|
defer os.Unsetenv("LOG_STDOUT_ENABLED")
|
||||||
c, _ := Load()
|
c, _ := Load()
|
||||||
std, en := c.Outputs.Stdout()
|
std, en := c.Outputs.Stdout()
|
||||||
assert.True(t, en)
|
assert.True(t, en)
|
||||||
assert.True(t, std.Json)
|
assert.Equal(t, "json", std.Formatter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFormatJson(t *testing.T) {
|
||||||
|
ff := Formatters{
|
||||||
|
"json": map[string]any{"pretty_print": true},
|
||||||
|
}
|
||||||
|
|
||||||
|
js := ff.Json()
|
||||||
|
assert.True(t, js.PrettyPrint)
|
||||||
|
|
||||||
|
ff = Formatters{}
|
||||||
|
js = ff.Json()
|
||||||
|
assert.False(t, js.PrettyPrint)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package config
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Input Input
|
Input Input
|
||||||
Outputs Outputs `toml:"output"`
|
Outputs Outputs `toml:"output"`
|
||||||
|
Formatters Formatters
|
||||||
}
|
}
|
||||||
|
|
||||||
type Input struct {
|
type Input struct {
|
||||||
|
|
@ -20,9 +21,15 @@ type Output struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Stdout struct {
|
type Stdout struct {
|
||||||
Json bool `env:"LOG_STDOUT_JSON" mapstructure:"json"`
|
Formatter string `env:"LOG_STDOUT_FORMATTER" mapstructure:"formatter"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type stdoutEnabled struct {
|
type stdoutEnabled struct {
|
||||||
Enabled bool `env:"LOG_STDOUT_ENABLED"`
|
Enabled bool `env:"LOG_STDOUT_ENABLED"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Formatters map[string]map[string]any
|
||||||
|
|
||||||
|
type JsonFormat struct {
|
||||||
|
PrettyPrint bool `env:"LOG_JSON_PRETTY_PRINT" mapstructure:"pretty_print"`
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,21 @@ func (s *AppendTestSuite) TestSuccess() {
|
||||||
s.Assert().Contains(st, "\n@bar true")
|
s.Assert().Contains(st, "\n@bar true")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *AppendTestSuite) TestFailEntry() {
|
||||||
|
e := models.Entry{
|
||||||
|
Title: "Jimmy",
|
||||||
|
}
|
||||||
|
l := models.Log{
|
||||||
|
Name: "test",
|
||||||
|
Entries: []models.Entry{e},
|
||||||
|
}
|
||||||
|
err := Append(l)
|
||||||
|
s.Assert().NoError(err)
|
||||||
|
s.Require().FileExists(s.dir + "/test.log")
|
||||||
|
by, _ := os.ReadFile(s.dir + "/test.log")
|
||||||
|
s.Assert().Equal([]byte{}, by)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *AppendTestSuite) TestDotFolder() {
|
func (s *AppendTestSuite) TestDotFolder() {
|
||||||
config.Overrides["input.dotFolder"] = "true"
|
config.Overrides["input.dotFolder"] = "true"
|
||||||
e := models.Entry{
|
e := models.Entry{
|
||||||
|
|
|
||||||
10
formatters/interface.go
Normal file
10
formatters/interface.go
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
package formatters
|
||||||
|
|
||||||
|
import "codeberg.org/danjones000/my-log/models"
|
||||||
|
|
||||||
|
type Formatter interface {
|
||||||
|
Name() string
|
||||||
|
Log(models.Log) (out []byte, err error)
|
||||||
|
Entry(models.Entry) (out []byte, err error)
|
||||||
|
Meta(models.Meta) (out []byte, err error)
|
||||||
|
}
|
||||||
34
formatters/new.go
Normal file
34
formatters/new.go
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
package formatters
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"codeberg.org/danjones000/my-log/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
type formatMaker func(config.Formatters) (Formatter, error)
|
||||||
|
|
||||||
|
var formatterMap = map[string]formatMaker{
|
||||||
|
"plain": newPlain,
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(kind string) (f Formatter, err error) {
|
||||||
|
conf, err := config.Load()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if make, ok := formatterMap[kind]; ok {
|
||||||
|
return make(conf.Formatters)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("unimplemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Kinds() []string {
|
||||||
|
r := []string{}
|
||||||
|
for kind, _ := range formatterMap {
|
||||||
|
r = append(r, kind)
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
35
formatters/new_test.go
Normal file
35
formatters/new_test.go
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
package formatters
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"codeberg.org/danjones000/my-log/config"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestKinds(t *testing.T) {
|
||||||
|
assert.Equal(t, []string{"plain"}, Kinds())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewUnsupported(t *testing.T) {
|
||||||
|
f, err := New("nope")
|
||||||
|
assert.Nil(t, f)
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewCantGetConfig(t *testing.T) {
|
||||||
|
f, _ := os.CreateTemp("", "test")
|
||||||
|
oldConf := config.ConfigPath
|
||||||
|
config.ConfigPath = f.Name()
|
||||||
|
defer f.Close()
|
||||||
|
defer func() {
|
||||||
|
config.ConfigPath = oldConf
|
||||||
|
}()
|
||||||
|
|
||||||
|
fmt.Fprint(f, `{"not":"toml"}`)
|
||||||
|
form, err := New("plain")
|
||||||
|
assert.Nil(t, form)
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
88
formatters/plain.go
Normal file
88
formatters/plain.go
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
package formatters
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
|
||||||
|
"codeberg.org/danjones000/my-log/config"
|
||||||
|
"codeberg.org/danjones000/my-log/models"
|
||||||
|
"codeberg.org/danjones000/my-log/tools"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newPlain(ff config.Formatters) (Formatter, error) {
|
||||||
|
return &PlainText{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type PlainText struct {
|
||||||
|
// config might go here some day
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pt *PlainText) Name() string {
|
||||||
|
return "plain"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pt *PlainText) Log(log models.Log) (out []byte, err error) {
|
||||||
|
if len(log.Entries) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buff := &bytes.Buffer{}
|
||||||
|
buff.WriteString(log.Name)
|
||||||
|
buff.WriteString("\n#######")
|
||||||
|
written := false
|
||||||
|
for _, e := range log.Entries {
|
||||||
|
bb := pt.entryBuffer(e)
|
||||||
|
if bb.Len() > 0 {
|
||||||
|
buff.WriteByte(10)
|
||||||
|
buff.WriteByte(10)
|
||||||
|
buff.ReadFrom(bb)
|
||||||
|
written = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if written {
|
||||||
|
out = buff.Bytes()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pt *PlainText) entryBuffer(entry models.Entry) *bytes.Buffer {
|
||||||
|
buff := &bytes.Buffer{}
|
||||||
|
buff.WriteString("Title: ")
|
||||||
|
buff.WriteString(entry.Title)
|
||||||
|
buff.WriteByte(10)
|
||||||
|
buff.WriteString("Date: ")
|
||||||
|
buff.WriteString(entry.Date.Format(tools.DateFormat))
|
||||||
|
for _, m := range entry.Fields {
|
||||||
|
bb, err := pt.metaBuffer(m)
|
||||||
|
if (bb.Len() > 0) && (err == nil) {
|
||||||
|
buff.WriteByte(10)
|
||||||
|
buff.ReadFrom(bb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return buff
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pt *PlainText) Entry(entry models.Entry) ([]byte, error) {
|
||||||
|
buff := pt.entryBuffer(entry)
|
||||||
|
return buff.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pt *PlainText) metaBuffer(meta models.Meta) (*bytes.Buffer, error) {
|
||||||
|
buff := &bytes.Buffer{}
|
||||||
|
buff.WriteString(meta.Key)
|
||||||
|
buff.WriteString(": ")
|
||||||
|
n, err := tools.WriteValue(buff, meta.Value)
|
||||||
|
if n == 0 || err != nil {
|
||||||
|
return &bytes.Buffer{}, err
|
||||||
|
}
|
||||||
|
return buff, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pt *PlainText) Meta(meta models.Meta) (out []byte, err error) {
|
||||||
|
buff, err := pt.metaBuffer(meta)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
out = buff.Bytes()
|
||||||
|
return
|
||||||
|
}
|
||||||
109
formatters/plain_test.go
Normal file
109
formatters/plain_test.go
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
package formatters
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"codeberg.org/danjones000/my-log/models"
|
||||||
|
"codeberg.org/danjones000/my-log/tools"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPlainLog(t *testing.T) {
|
||||||
|
m := []models.Meta{
|
||||||
|
{"foo", "bar"},
|
||||||
|
{"baz", 42},
|
||||||
|
}
|
||||||
|
e := []models.Entry{
|
||||||
|
{Title: "one", Date: time.Now(), Fields: m},
|
||||||
|
{Title: "small", Date: time.Now()},
|
||||||
|
}
|
||||||
|
l := models.Log{"stuff", e}
|
||||||
|
|
||||||
|
f, err := New("plain")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
out, err := f.Log(l)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
read := bytes.NewReader(out)
|
||||||
|
scan := bufio.NewScanner(read)
|
||||||
|
|
||||||
|
scan.Scan()
|
||||||
|
line := scan.Text()
|
||||||
|
assert.Equal(t, l.Name, line)
|
||||||
|
|
||||||
|
scan.Scan()
|
||||||
|
line = scan.Text()
|
||||||
|
assert.Equal(t, "#######", line)
|
||||||
|
|
||||||
|
scan.Scan()
|
||||||
|
scan.Scan()
|
||||||
|
line = scan.Text()
|
||||||
|
assert.Equal(t, "Title: "+e[0].Title, line)
|
||||||
|
|
||||||
|
scan.Scan()
|
||||||
|
line = scan.Text()
|
||||||
|
assert.Equal(t, "Date: "+e[0].Date.Format(tools.DateFormat), line)
|
||||||
|
|
||||||
|
scan.Scan()
|
||||||
|
line = scan.Text()
|
||||||
|
assert.Equal(t, "foo: bar", line)
|
||||||
|
|
||||||
|
scan.Scan()
|
||||||
|
line = scan.Text()
|
||||||
|
assert.Equal(t, "baz: 42", line)
|
||||||
|
|
||||||
|
scan.Scan()
|
||||||
|
scan.Scan()
|
||||||
|
line = scan.Text()
|
||||||
|
assert.Equal(t, "Title: "+e[1].Title, line)
|
||||||
|
|
||||||
|
scan.Scan()
|
||||||
|
line = scan.Text()
|
||||||
|
assert.Equal(t, "Date: "+e[1].Date.Format(tools.DateFormat), line)
|
||||||
|
|
||||||
|
more := scan.Scan()
|
||||||
|
assert.False(t, more)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPlainName(t *testing.T) {
|
||||||
|
f, _ := New("plain")
|
||||||
|
assert.Equal(t, "plain", f.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPlainLogNoEntries(t *testing.T) {
|
||||||
|
f, _ := New("plain")
|
||||||
|
out, err := f.Log(models.Log{Name: "foo"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, out, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPlainMetaEmpty(t *testing.T) {
|
||||||
|
f, _ := New("plain")
|
||||||
|
out, err := f.Meta(models.Meta{"foo", ""})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, out, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPlainMetaError(t *testing.T) {
|
||||||
|
f, _ := New("plain")
|
||||||
|
out, err := f.Meta(models.Meta{"foo", make(chan bool)})
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Len(t, out, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPlainEntry(t *testing.T) {
|
||||||
|
f, _ := New("plain")
|
||||||
|
now := time.Now()
|
||||||
|
out, err := f.Entry(models.Entry{
|
||||||
|
Title: "foo",
|
||||||
|
Date: now,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, fmt.Sprintf("Title: foo\nDate: %s", now.Format(tools.DateFormat)), string(out))
|
||||||
|
}
|
||||||
|
|
@ -2,12 +2,9 @@ package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"codeberg.org/danjones000/my-log/tools"
|
"codeberg.org/danjones000/my-log/tools"
|
||||||
)
|
)
|
||||||
|
|
@ -25,33 +22,9 @@ func (m Meta) MarshalText() ([]byte, error) {
|
||||||
buff.WriteRune('@')
|
buff.WriteRune('@')
|
||||||
buff.WriteString(m.Key)
|
buff.WriteString(m.Key)
|
||||||
buff.WriteRune(' ')
|
buff.WriteRune(' ')
|
||||||
switch v := m.Value.(type) {
|
n, err := tools.WriteValue(buff, m.Value)
|
||||||
default:
|
if n == 0 || err != nil {
|
||||||
return nil, fmt.Errorf("Unknown type %T", v)
|
return []byte{}, err
|
||||||
case nil:
|
|
||||||
return []byte{}, nil
|
|
||||||
case string:
|
|
||||||
buff.WriteString(v)
|
|
||||||
case int:
|
|
||||||
buff.WriteString(strconv.Itoa(v))
|
|
||||||
case int64:
|
|
||||||
buff.WriteString(strconv.FormatInt(v, 10))
|
|
||||||
case float64:
|
|
||||||
buff.WriteString(strconv.FormatFloat(v, 'f', -1, 64))
|
|
||||||
case json.Number:
|
|
||||||
buff.WriteString(v.String())
|
|
||||||
case json.RawMessage:
|
|
||||||
buff.Write(v)
|
|
||||||
case []byte:
|
|
||||||
buff.Write(v)
|
|
||||||
case byte:
|
|
||||||
buff.WriteByte(v)
|
|
||||||
case rune:
|
|
||||||
buff.WriteString(string(v))
|
|
||||||
case bool:
|
|
||||||
buff.WriteString(strconv.FormatBool(v))
|
|
||||||
case time.Time:
|
|
||||||
buff.WriteString(v.Format(time.RFC3339))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return buff.Bytes(), nil
|
return buff.Bytes(), nil
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ func TestMeta(t *testing.T) {
|
||||||
{"byte", "byteme", byte(67), "@byteme C", nil, "C"},
|
{"byte", "byteme", byte(67), "@byteme C", nil, "C"},
|
||||||
{"json-obj", "obj", json.RawMessage(`{"foo":"bar","baz":"quux"}`), `@obj {"foo":"bar","baz":"quux"}`, nil, json.RawMessage(`{"foo":"bar","baz":"quux"}`)},
|
{"json-obj", "obj", json.RawMessage(`{"foo":"bar","baz":"quux"}`), `@obj {"foo":"bar","baz":"quux"}`, nil, json.RawMessage(`{"foo":"bar","baz":"quux"}`)},
|
||||||
{"json-arr", "arr", json.RawMessage(`["foo",42,"bar", null,"quux", true]`), `@arr ["foo",42,"bar", null,"quux", true]`, nil, json.RawMessage(`["foo",42,"bar", null,"quux", true]`)},
|
{"json-arr", "arr", json.RawMessage(`["foo",42,"bar", null,"quux", true]`), `@arr ["foo",42,"bar", null,"quux", true]`, nil, json.RawMessage(`["foo",42,"bar", null,"quux", true]`)},
|
||||||
{"chan", "nope", make(chan bool), "", errors.New("Unknown type chan bool"), ""},
|
{"chan", "nope", make(chan bool), "", errors.New("Unsupported type chan bool"), ""},
|
||||||
{"whitespace-key", "no space", "hi", "", errors.New("whitespace is not allowed in key: no space"), ""},
|
{"whitespace-key", "no space", "hi", "", errors.New("whitespace is not allowed in key: no space"), ""},
|
||||||
{"empty-mar", "nope", skipMarshalTest, "", nil, ErrorParsing},
|
{"empty-mar", "nope", skipMarshalTest, "", nil, ErrorParsing},
|
||||||
{"no-key-mar", "nope", skipMarshalTest, "nope", nil, ErrorParsing},
|
{"no-key-mar", "nope", skipMarshalTest, "nope", nil, ErrorParsing},
|
||||||
|
|
|
||||||
44
tools/write_buffer.go
Normal file
44
tools/write_buffer.go
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
package tools
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func WriteValue(buff *bytes.Buffer, val any) (n int, err error) {
|
||||||
|
switch v := val.(type) {
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("Unsupported type %T", v)
|
||||||
|
case nil:
|
||||||
|
return
|
||||||
|
case string:
|
||||||
|
return buff.WriteString(v)
|
||||||
|
case int:
|
||||||
|
return buff.WriteString(strconv.Itoa(v))
|
||||||
|
case int64:
|
||||||
|
return buff.WriteString(strconv.FormatInt(v, 10))
|
||||||
|
case float64:
|
||||||
|
return buff.WriteString(strconv.FormatFloat(v, 'f', -1, 64))
|
||||||
|
case json.Number:
|
||||||
|
return buff.WriteString(v.String())
|
||||||
|
case json.RawMessage:
|
||||||
|
return buff.Write(v)
|
||||||
|
case []byte:
|
||||||
|
return buff.Write(v)
|
||||||
|
case byte:
|
||||||
|
err = buff.WriteByte(v)
|
||||||
|
if err == nil {
|
||||||
|
n = 1
|
||||||
|
}
|
||||||
|
case rune:
|
||||||
|
return buff.WriteString(string(v))
|
||||||
|
case bool:
|
||||||
|
return buff.WriteString(strconv.FormatBool(v))
|
||||||
|
case time.Time:
|
||||||
|
return buff.WriteString(v.Format(time.RFC3339))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
49
tools/write_buffer_test.go
Normal file
49
tools/write_buffer_test.go
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
package tools
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestWriteBuffer(t *testing.T) {
|
||||||
|
when := time.Now()
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
value any
|
||||||
|
out string
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{"nil", nil, "", nil},
|
||||||
|
{"string", "hi", "hi", nil},
|
||||||
|
{"bytes", []byte{104, 105}, "hi", nil},
|
||||||
|
{"byte", byte(104), "h", nil},
|
||||||
|
{"rune", 'h', "h", nil},
|
||||||
|
{"int", 42, "42", nil},
|
||||||
|
{"int64", int64(42), "42", nil},
|
||||||
|
{"float", 42.13, "42.13", nil},
|
||||||
|
{"bool", false, "false", nil},
|
||||||
|
{"json.Number", json.Number("42.13"), "42.13", nil},
|
||||||
|
{"json.RawMessage", json.RawMessage("{}"), "{}", nil},
|
||||||
|
{"time", when, when.Format(time.RFC3339), nil},
|
||||||
|
{"struct", struct{}{}, "", errors.New("Unsupported type struct {}")},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, getWriteTestRunner(tt.value, tt.out, tt.err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getWriteTestRunner(value any, out string, err error) func(*testing.T) {
|
||||||
|
return func(t *testing.T) {
|
||||||
|
buff := &bytes.Buffer{}
|
||||||
|
n, er := WriteValue(buff, value)
|
||||||
|
assert.Equal(t, len(out), n)
|
||||||
|
assert.Equal(t, err, er)
|
||||||
|
assert.Equal(t, out, buff.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue