From 7e54d6e46cfa5d0d864a6d519a0222fe9360574f Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Sun, 10 Mar 2024 09:43:56 -0500 Subject: [PATCH 01/11] =?UTF-8?q?=E2=9C=85=20Fix=20parse=5Fdate=5Ftest=20f?= =?UTF-8?q?or=20DST?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tools/parse_date_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/parse_date_test.go b/tools/parse_date_test.go index cfaadd1..7aa30b2 100644 --- a/tools/parse_date_test.go +++ b/tools/parse_date_test.go @@ -16,8 +16,8 @@ func TestParseDate(t *testing.T) { y, mon, d, h, loc := now.Year(), now.Month(), now.Day(), now.Hour(), now.Location() sec := now.Truncate(time.Second) today := time.Date(y, mon, d, 0, 0, 0, 0, loc) - tomorrow := today.Add(day) - yesterday := today.Add(-day) + tomorrow := time.Date(y, mon, d+1, 0, 0, 0, 0, loc) + yesterday := time.Date(y, mon, d-1, 0, 0, 0, 0, loc) twoMin := now.Add(2 * time.Minute).Truncate(time.Minute) twoHour := time.Date(y, mon, d, h+2, 0, 0, 0, loc) firstMonth := time.Date(y, mon, 1, 0, 0, 0, 0, loc) From 820a2de26968c314eb1a2818655b1d67f07cadaf Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Sun, 10 Mar 2024 15:26:00 -0500 Subject: [PATCH 02/11] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Get=20rid=20of=20Par?= =?UTF-8?q?tialEntry?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/drop.go | 9 +++---- models/entry.go | 49 +++++++++------------------------ models/entry_test.go | 18 ++----------- models/meta_test.go | 28 +++++++++++++++++++ models/metas.go | 64 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 111 insertions(+), 57 deletions(-) create mode 100644 models/metas.go diff --git a/cmd/drop.go b/cmd/drop.go index 658cb7c..a268d20 100644 --- a/cmd/drop.go +++ b/cmd/drop.go @@ -42,18 +42,17 @@ var dropCmd = &cobra.Command{ RunE: func(cmd *cobra.Command, args []string) error { log := args[0] title := args[1] - e := models.PartialEntry() + ms := models.Metas{} if len(j.RawMessage) > 8 { - err := json.Unmarshal([]byte(j.RawMessage), &e) + err := json.Unmarshal([]byte(j.RawMessage), &ms) if err != nil { return err } } for k, v := range fields { - e.Fields = append(e.Fields, models.Meta{k, tools.ParseString(v)}) + ms = append(ms, models.Meta{k, tools.ParseString(v)}) } - e.Title = title - e.Date = d.t + e := models.Entry{title, d.t, ms} l := models.Log{log, []models.Entry{e}} err := files.Append(l) if err != nil { diff --git a/models/entry.go b/models/entry.go index 5a19933..bca992c 100644 --- a/models/entry.go +++ b/models/entry.go @@ -16,14 +16,9 @@ import ( const DateFormat = tools.DateFormat type Entry struct { - Title string - Date time.Time - Fields []Meta - skipMissing bool -} - -func PartialEntry() Entry { - return Entry{skipMissing: true} + Title string + Date time.Time + Fields Metas } type metaRes struct { @@ -42,9 +37,9 @@ func (e Entry) getFieldMarshalChan() chan metaRes { defer wg.Done() if m.Key == "json" { if j, ok := m.Value.(json.RawMessage); ok { - sub := Entry{skipMissing: true} + sub := Metas{} json.Unmarshal(j, &sub) - for _, subM := range sub.Fields { + for _, subM := range sub { o, er := subM.MarshalText() ch <- metaRes{o, er} } @@ -160,9 +155,9 @@ func (e *Entry) getFieldUnarshalChan(in []byte) chan Meta { if err == nil { if m.Key == "json" { if j, ok := m.Value.(json.RawMessage); ok { - sub := Entry{skipMissing: true} - json.Unmarshal(j, &sub) - for _, subM := range sub.Fields { + ms := Metas{} + json.Unmarshal(j, &ms) + for _, subM := range ms { ch <- subM } } @@ -191,26 +186,8 @@ func (e Entry) MarshalJSON() ([]byte, error) { out := map[string]any{} out["title"] = e.Title out["date"] = e.Date.Format(time.RFC3339) - for _, f := range e.Fields { - if _, ok := out[f.Key]; !ok { - if f.Key == "json" { - ob := map[string]any{} - if j, ok := f.Value.(json.RawMessage); ok { - json.Unmarshal(j, &ob) - } - // If we couldn't get valid data from there, this will just be empty - for k, v := range ob { - if k != "title" && k != "date" { - out[k] = v - } - } - } else { - out[f.Key] = f.Value - if vt, ok := f.Value.(time.Time); ok { - out[f.Key] = vt.Format(time.RFC3339) - } - } - } + for k, v := range e.Fields.toMap() { + out[k] = v } return json.Marshal(out) } @@ -259,16 +236,16 @@ func (e *Entry) UnmarshalJSON(in []byte) error { return newParsingError(err) } title, ok := out["title"].(string) - if (!ok || title == "") && !e.skipMissing { + if !ok || title == "" { return ErrorMissingTitle } e.Title = title dates, ok := out["date"].(string) - if (!ok || dates == "") && !e.skipMissing { + if !ok || dates == "" { return ErrorMissingDate } date, err := tools.ParseDate(dates) - if err != nil && !e.skipMissing { + if err != nil { return newParsingError(err) } e.Date = date diff --git a/models/entry_test.go b/models/entry_test.go index aefecc9..01c65a7 100644 --- a/models/entry_test.go +++ b/models/entry_test.go @@ -9,7 +9,6 @@ import ( "time" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) // Type assertions @@ -18,19 +17,6 @@ var _ encoding.TextUnmarshaler = new(Entry) var _ json.Marshaler = Entry{} var _ json.Unmarshaler = new(Entry) -func TestPartialEntry(t *testing.T) { - e := PartialEntry() - assert.True(t, e.skipMissing) - err := json.Unmarshal([]byte(`{"a":42}`), &e) - assert.NoError(t, err) - assert.Equal(t, "", e.Title) - assert.Equal(t, time.Time{}, e.Date) - require.Len(t, e.Fields, 1) - f := e.Fields[0] - assert.Equal(t, "a", f.Key) - assert.Equal(t, int64(42), f.Value) -} - func TestEntryMarshal(t *testing.T) { when := time.Now() whens := when.Format(DateFormat) @@ -84,7 +70,7 @@ func TestEntryMarshal(t *testing.T) { func getEntryMarshalTestRunner(title string, date time.Time, fields []Meta, first string, lines []string, err error) func(*testing.T) { return func(t *testing.T) { - en := Entry{title, date, fields, false} + en := Entry{title, date, fields} o, er := en.MarshalText() assert.Equal(t, err, er) if first == "" { @@ -232,7 +218,7 @@ func TestEntryJsonMarshal(t *testing.T) { func getEntryJsonMarshalTestRunner(title string, date time.Time, fields []Meta, out string, err error) func(t *testing.T) { return func(t *testing.T) { - e := Entry{title, date, fields, false} + e := Entry{title, date, fields} o, er := json.Marshal(e) if err == nil { assert.JSONEq(t, out, string(o)) diff --git a/models/meta_test.go b/models/meta_test.go index 1e0888b..3f623d0 100644 --- a/models/meta_test.go +++ b/models/meta_test.go @@ -97,3 +97,31 @@ func getMetaTestRunner(key string, value any, out string, err error, newVal any) } } } + +func TestMetasJson(t *testing.T) { + ms := Metas{{"me", 41}, {"you", false}} + exp := `{"me":41,"you":false}` + o, err := json.Marshal(ms) + assert.NoError(t, err) + assert.JSONEq(t, exp, string(o)) +} + +func TestMetasJsonUnmarshal(t *testing.T) { + ms := Metas{} + in := `{"me":"cool","you":false}` + err := json.Unmarshal([]byte(in), &ms) + assert.NoError(t, err) + assert.Len(t, ms, 2) + assert.ElementsMatch(t, Metas{ + {"me", "cool"}, + {"you", false}, + }, ms) +} + +func TestMetasJsonError(t *testing.T) { + ms := Metas{} + in := "not json" + err := (&ms).UnmarshalJSON([]byte(in)) + assert.Error(t, err) + assert.Len(t, ms, 0) +} diff --git a/models/metas.go b/models/metas.go new file mode 100644 index 0000000..7a2602f --- /dev/null +++ b/models/metas.go @@ -0,0 +1,64 @@ +package models + +import ( + //"bufio" + //"bytes" + "encoding/json" + //"fmt" + //"errors" + //"regexp" + //"strings" + //"sync" + "time" + //"codeberg.org/danjones000/my-log/tools" +) + +type Metas []Meta + +func (ms Metas) toMap() map[string]any { + out := map[string]any{} + for _, f := range ms { + if _, found := out[f.Key]; found || f.Key == "title" || f.Key == "date" { + continue + } + if f.Key == "json" { + ob := map[string]any{} + if j, ok := f.Value.(json.RawMessage); ok { + json.Unmarshal(j, &ob) + } + // If we couldn't get valid data from there, this will just be empty + for k, v := range ob { + if k != "title" && k != "date" { + out[k] = v + } + } + } else { + out[f.Key] = f.Value + if vt, ok := f.Value.(time.Time); ok { + out[f.Key] = vt.Format(time.RFC3339) + } + } + } + return out +} + +func (ms Metas) MarshalJSON() ([]byte, error) { + return json.Marshal(ms.toMap()) +} + +func (ms *Metas) UnmarshalJSON(in []byte) error { + old := (*ms).toMap() + out := map[string]any{} + err := json.Unmarshal(in, &out) + if err != nil { + return err + } + ret := *ms + for k, v := range out { + if _, found := old[k]; k != "title" && k != "date" && !found { + ret = append(ret, Meta{k, v}) + } + } + *ms = ret + return nil +} From 59634f6c3ff069b004d027374492e94e73f3a5e5 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Sun, 10 Mar 2024 15:49:36 -0500 Subject: [PATCH 03/11] =?UTF-8?q?=F0=9F=90=9B=20WriteValue=20can=20handle?= =?UTF-8?q?=20slices=20and=20maps?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tools/write_buffer.go | 12 ++++++++++++ tools/write_buffer_test.go | 2 ++ 2 files changed, 14 insertions(+) diff --git a/tools/write_buffer.go b/tools/write_buffer.go index e67d219..8ae9386 100644 --- a/tools/write_buffer.go +++ b/tools/write_buffer.go @@ -14,6 +14,18 @@ func WriteValue(buff *bytes.Buffer, val any) (n int, err error) { err = fmt.Errorf("Unsupported type %T", v) case nil: return + case []any: + var o []byte + o, err = json.Marshal(v) + if err == nil { + return buff.Write(o) + } + case map[string]any: + var o []byte + o, err = json.Marshal(v) + if err == nil { + return buff.Write(o) + } case string: return buff.WriteString(v) case int: diff --git a/tools/write_buffer_test.go b/tools/write_buffer_test.go index 1a4a103..aa7e203 100644 --- a/tools/write_buffer_test.go +++ b/tools/write_buffer_test.go @@ -30,6 +30,8 @@ func TestWriteBuffer(t *testing.T) { {"json.Number", json.Number("42.13"), "42.13", nil}, {"json.RawMessage", json.RawMessage("{}"), "{}", nil}, {"time", when, when.Format(time.RFC3339), nil}, + {"slice", []any{1, 2, "foo"}, `[1,2,"foo"]`, nil}, + {"map", map[string]any{"baz": 42, "foo": "bar"}, `{"baz":42,"foo":"bar"}`, nil}, {"struct", struct{}{}, "", errors.New("Unsupported type struct {}")}, } From d1b3604e1ef1b0720a72a2921550759d06734af4 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Sun, 10 Mar 2024 11:31:07 -0500 Subject: [PATCH 04/11] =?UTF-8?q?=E2=9C=A8=20Add=20Formatter.Logs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- formatters/interface.go | 1 + formatters/plain.go | 20 ++++++++++++++++++++ formatters/plain_test.go | 35 +++++++++++++++++++++++++++++++++-- 3 files changed, 54 insertions(+), 2 deletions(-) diff --git a/formatters/interface.go b/formatters/interface.go index 4276aed..5478572 100644 --- a/formatters/interface.go +++ b/formatters/interface.go @@ -4,6 +4,7 @@ import "codeberg.org/danjones000/my-log/models" type Formatter interface { Name() string + Logs([]models.Log) (out []byte, err error) Log(models.Log) (out []byte, err error) Entry(models.Entry) (out []byte, err error) Meta(models.Meta) (out []byte, err error) diff --git a/formatters/plain.go b/formatters/plain.go index ed8ee82..2b79f00 100644 --- a/formatters/plain.go +++ b/formatters/plain.go @@ -20,6 +20,26 @@ func (pt *PlainText) Name() string { return "plain" } +func (pt *PlainText) Logs(logs []models.Log) (out []byte, err error) { + if len(logs) == 0 { + return + } + + buff := &bytes.Buffer{} + first := true + for _, log := range logs { + o, _ := pt.Log(log) + if !first { + buff.WriteByte(10) + buff.WriteByte(10) + } + first = false + buff.Write(o) + } + out = buff.Bytes() + return +} + func (pt *PlainText) Log(log models.Log) (out []byte, err error) { if len(log.Entries) == 0 { return diff --git a/formatters/plain_test.go b/formatters/plain_test.go index e384101..487550f 100644 --- a/formatters/plain_test.go +++ b/formatters/plain_test.go @@ -13,7 +13,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestPlainLog(t *testing.T) { +func TestPlainLogs(t *testing.T) { m := []models.Meta{ {"foo", "bar"}, {"baz", 42}, @@ -23,11 +23,17 @@ func TestPlainLog(t *testing.T) { {Title: "small", Date: time.Now()}, } l := models.Log{"stuff", e} + e2 := models.Entry{ + Title: "three", + Date: time.Now(), + } + l2 := models.Log{"more-stuff", []models.Entry{e2}} + logs := []models.Log{l, l2} f, err := New("plain") require.NoError(t, err) - out, err := f.Log(l) + out, err := f.Logs(logs) require.NoError(t, err) read := bytes.NewReader(out) @@ -67,6 +73,24 @@ func TestPlainLog(t *testing.T) { line = scan.Text() assert.Equal(t, "Date: "+e[1].Date.Format(tools.DateFormat), line) + scan.Scan() + scan.Scan() + line = scan.Text() + assert.Equal(t, l2.Name, line) + + scan.Scan() + line = scan.Text() + assert.Equal(t, "#######", line) + + scan.Scan() + scan.Scan() + line = scan.Text() + assert.Equal(t, "Title: "+e2.Title, line) + + scan.Scan() + line = scan.Text() + assert.Equal(t, "Date: "+e2.Date.Format(tools.DateFormat), line) + more := scan.Scan() assert.False(t, more) } @@ -76,6 +100,13 @@ func TestPlainName(t *testing.T) { assert.Equal(t, "plain", f.Name()) } +func TestPlainLogNone(t *testing.T) { + f, _ := New("plain") + out, err := f.Logs([]models.Log{}) + assert.NoError(t, err) + assert.Len(t, out, 0) +} + func TestPlainLogNoEntries(t *testing.T) { f, _ := New("plain") out, err := f.Log(models.Log{Name: "foo"}) From 4c0edcd1a58a2c39e7043b1646cbe930aa52f9b4 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Sun, 10 Mar 2024 11:33:51 -0500 Subject: [PATCH 05/11] =?UTF-8?q?=E2=9C=A8=20Add=20JSON=20formatter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- formatters/json.go | 63 ++++++++++++++++++++++++++++++++++++++ formatters/json_test.go | 67 +++++++++++++++++++++++++++++++++++++++++ formatters/new.go | 1 + formatters/new_test.go | 2 +- 4 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 formatters/json.go create mode 100644 formatters/json_test.go diff --git a/formatters/json.go b/formatters/json.go new file mode 100644 index 0000000..b3b2d97 --- /dev/null +++ b/formatters/json.go @@ -0,0 +1,63 @@ +package formatters + +import ( + "encoding/json" + "time" + + "codeberg.org/danjones000/my-log/config" + "codeberg.org/danjones000/my-log/models" + //"codeberg.org/danjones000/my-log/tools" +) + +func newJson(ff config.Formatters) (Formatter, error) { + // @todo pretty print + return &Json{ff.Json()}, nil +} + +type Json struct { + jf config.JsonFormat +} + +func (js *Json) Name() string { + return "json" +} + +func (js *Json) Meta(m models.Meta) ([]byte, error) { + o := map[string]any{m.Key: m.Value} + return json.Marshal(o) +} + +func (js *Json) entryMap(e models.Entry) map[string]any { + o := map[string]any{ + "title": e.Title, + "date": e.Date.Format(time.RFC3339), + } + for _, m := range e.Fields { + o[m.Key] = m.Value + } + return o +} + +func (js *Json) Entry(e models.Entry) ([]byte, error) { + return json.Marshal(js.entryMap(e)) +} + +func (js *Json) Log(l models.Log) ([]byte, error) { + return js.Logs([]models.Log{l}) +} + +func (js *Json) Logs(logs []models.Log) (out []byte, err error) { + if len(logs) == 0 { + return + } + + o := map[string][]map[string]any{} + for _, l := range logs { + es := []map[string]any{} + for _, e := range l.Entries { + es = append(es, js.entryMap(e)) + } + o[l.Name] = es + } + return json.Marshal(o) +} diff --git a/formatters/json_test.go b/formatters/json_test.go new file mode 100644 index 0000000..a81d4ed --- /dev/null +++ b/formatters/json_test.go @@ -0,0 +1,67 @@ +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 TestJsonName(t *testing.T) { + f, _ := New("json") + assert.Equal(t, "json", f.Name()) +} + +func TestJsonMeta(t *testing.T) { + f, _ := New("json") + m := models.Meta{"foo", 42} + exp := `{"foo":42}` + o, err := f.Meta(m) + assert.NoError(t, err) + assert.JSONEq(t, exp, string(o)) +} + +func TestJsonEntry(t *testing.T) { + when := time.Now() + f, _ := New("json") + m := models.Meta{"foo", 42} + e := models.Entry{ + Title: "Homer", + Date: when, + Fields: []models.Meta{m}, + } + exp := fmt.Sprintf(`{"title":"%s","date":"%s","foo":42}`, e.Title, when.Format(time.RFC3339)) + o, err := f.Entry(e) + assert.NoError(t, err) + assert.JSONEq(t, exp, string(o)) +} + +func TestJsonLog(t *testing.T) { + when := time.Now() + f, _ := New("json") + m := models.Meta{"foo", 42} + e := models.Entry{ + Title: "Homer", + Date: when, + Fields: []models.Meta{m}, + } + l := models.Log{"stuff", []models.Entry{e}} + exp := fmt.Sprintf(`{"%s":[{"title":"%s","date":"%s","foo":42}]}`, l.Name, e.Title, when.Format(time.RFC3339)) + o, err := f.Log(l) + assert.NoError(t, err) + assert.JSONEq(t, exp, string(o)) +} + +func TestJsonNoLogs(t *testing.T) { + f, _ := New("json") + o, err := f.Logs([]models.Log{}) + var exp []byte + assert.NoError(t, err) + assert.Equal(t, exp, o) +} diff --git a/formatters/new.go b/formatters/new.go index d88a9d0..37d8a96 100644 --- a/formatters/new.go +++ b/formatters/new.go @@ -10,6 +10,7 @@ type formatMaker func(config.Formatters) (Formatter, error) var formatterMap = map[string]formatMaker{ "plain": newPlain, + "json": newJson, } func Preferred() (f Formatter, err error) { diff --git a/formatters/new_test.go b/formatters/new_test.go index 04b40fb..c6d5e56 100644 --- a/formatters/new_test.go +++ b/formatters/new_test.go @@ -10,7 +10,7 @@ import ( ) func TestKinds(t *testing.T) { - assert.Equal(t, []string{"plain"}, Kinds()) + assert.ElementsMatch(t, []string{"plain", "json"}, Kinds()) } func TestNewUnsupported(t *testing.T) { From 1962e1db502211815f0ae8f49c3c84de718e2004 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Sun, 10 Mar 2024 12:14:21 -0500 Subject: [PATCH 06/11] =?UTF-8?q?=E2=9C=A8=20Pretty=20print=20JSON?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- formatters/json.go | 28 +++++++++++++++++++++------- formatters/json_test.go | 22 ++++++++++++++++++---- 2 files changed, 39 insertions(+), 11 deletions(-) diff --git a/formatters/json.go b/formatters/json.go index b3b2d97..d56854d 100644 --- a/formatters/json.go +++ b/formatters/json.go @@ -1,30 +1,44 @@ package formatters import ( + "bytes" "encoding/json" "time" "codeberg.org/danjones000/my-log/config" "codeberg.org/danjones000/my-log/models" - //"codeberg.org/danjones000/my-log/tools" ) func newJson(ff config.Formatters) (Formatter, error) { - // @todo pretty print - return &Json{ff.Json()}, nil + return &Json{ff.Json().PrettyPrint}, nil } type Json struct { - jf config.JsonFormat + prettPrint bool } func (js *Json) Name() string { return "json" } +func (js *Json) marshal(v any) (o []byte, err error) { + o, err = json.Marshal(v) + if err != nil { + return + } + if js.prettPrint { + buff := &bytes.Buffer{} + err = json.Indent(buff, o, "", "\t") + if err == nil { + o = buff.Bytes() + } + } + return +} + func (js *Json) Meta(m models.Meta) ([]byte, error) { o := map[string]any{m.Key: m.Value} - return json.Marshal(o) + return js.marshal(o) } func (js *Json) entryMap(e models.Entry) map[string]any { @@ -39,7 +53,7 @@ func (js *Json) entryMap(e models.Entry) map[string]any { } func (js *Json) Entry(e models.Entry) ([]byte, error) { - return json.Marshal(js.entryMap(e)) + return js.marshal(js.entryMap(e)) } func (js *Json) Log(l models.Log) ([]byte, error) { @@ -59,5 +73,5 @@ func (js *Json) Logs(logs []models.Log) (out []byte, err error) { } o[l.Name] = es } - return json.Marshal(o) + return js.marshal(o) } diff --git a/formatters/json_test.go b/formatters/json_test.go index a81d4ed..da6f111 100644 --- a/formatters/json_test.go +++ b/formatters/json_test.go @@ -1,16 +1,12 @@ 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 TestJsonName(t *testing.T) { @@ -65,3 +61,21 @@ func TestJsonNoLogs(t *testing.T) { assert.NoError(t, err) assert.Equal(t, exp, o) } + +func TestJsonErr(t *testing.T) { + f, _ := New("json") + o, err := f.Meta(models.Meta{"foo", make(chan bool)}) + var exp []byte + assert.Error(t, err) + assert.Equal(t, exp, o) +} + +func TestJsonPretty(t *testing.T) { + f := Json{true} + o, err := f.Meta(models.Meta{"foo", 42}) + exp := `{ + "foo": 42 +}` + assert.NoError(t, err) + assert.Equal(t, exp, string(o)) +} From d6482952a40a56ffc7ee36ec3f0e19cede2817c2 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Sun, 10 Mar 2024 20:38:41 -0500 Subject: [PATCH 07/11] =?UTF-8?q?=F0=9F=93=9D=20Update=20README=20with=20J?= =?UTF-8?q?SON=20format?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 24eae91..bcf705a 100644 --- a/README.md +++ b/README.md @@ -144,7 +144,7 @@ Some formatters may have custom configuration. + [ ] filter to specific logs + [ ] stdout - [x] plain text - - [ ] JSON + - [x] JSON - [ ] YAML - [ ] Other formats? Submit an issue! + [ ] file output From 8086029b03da57ef45acb0e90313d464d09aecbb Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Sun, 10 Mar 2024 21:08:34 -0500 Subject: [PATCH 08/11] =?UTF-8?q?=F0=9F=9A=9A=20Rename=20stdout.config.for?= =?UTF-8?q?matter=20to=20format?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- cmd/root.go | 2 +- config/default.go | 2 +- config/load_test.go | 6 +++--- config/types.go | 2 +- formatters/new.go | 3 +-- 6 files changed, 8 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index bcf705a..8b4aa0c 100644 --- a/README.md +++ b/README.md @@ -123,7 +123,7 @@ Each separate output has its own set of configuration. So, replace `which-one` w *This section may change in the near future. We're considering supporting multiple formats.* -- `formatter`: Which formatter to use when outputting data. This value is used by `my-log drop` to output the new entry. +- `format`: Which formatter to use when outputting data. This value is also used by `my-log drop` to output the new entry. ### `[formatters]` diff --git a/cmd/root.go b/cmd/root.go index 7e7bbbd..44de5f0 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -50,7 +50,7 @@ func init() { // will be global for your application. rootCmd.PersistentFlags().StringVarP(&config.ConfigPath, "config", "c", config.ConfigPath, "config file") - rootCmd.PersistentFlags().StringToStringVarP(&config.Overrides, "config-value", "v", config.Overrides, "Override config values. Use dot syntax to specify key. E.g. -v output.stdout.config.formatter=json") + rootCmd.PersistentFlags().StringToStringVarP(&config.Overrides, "config-value", "v", config.Overrides, "Override config values. Use dot syntax to specify key. E.g. -v output.stdout.config.format=json") // Cobra also supports local flags, which will only run // when this action is called directly. diff --git a/config/default.go b/config/default.go index 76ef8d2..8f93873 100644 --- a/config/default.go +++ b/config/default.go @@ -28,7 +28,7 @@ dotFolder = true enabled = true [output.stdout.config] # Formatter to use when outputting to stdout -formatter = "plain" +format = "plain" [formatters] diff --git a/config/load_test.go b/config/load_test.go index 51e024e..eab7c94 100644 --- a/config/load_test.go +++ b/config/load_test.go @@ -70,14 +70,14 @@ func TestStdoutMissing(t *testing.T) { } func TestStdoutLoad(t *testing.T) { - os.Setenv("LOG_STDOUT_FORMATTER", "json") - defer os.Unsetenv("LOG_STDOUT_FORMATTER") + os.Setenv("LOG_STDOUT_FORMAT", "json") + defer os.Unsetenv("LOG_STDOUT_FORMAT") os.Setenv("LOG_STDOUT_ENABLED", "true") defer os.Unsetenv("LOG_STDOUT_ENABLED") c, _ := Load() std, en := c.Outputs.Stdout() assert.True(t, en) - assert.Equal(t, "json", std.Formatter) + assert.Equal(t, "json", std.Format) } func TestFormatJson(t *testing.T) { diff --git a/config/types.go b/config/types.go index a950a80..58fb2c4 100644 --- a/config/types.go +++ b/config/types.go @@ -21,7 +21,7 @@ type Output struct { } type Stdout struct { - Formatter string `env:"LOG_STDOUT_FORMATTER" mapstructure:"formatter"` + Format string `env:"LOG_STDOUT_FORMAT" mapstructure:"format"` } type stdoutEnabled struct { diff --git a/formatters/new.go b/formatters/new.go index 37d8a96..3cfdb3c 100644 --- a/formatters/new.go +++ b/formatters/new.go @@ -19,8 +19,7 @@ func Preferred() (f Formatter, err error) { return } std, _ := conf.Outputs.Stdout() - kind := std.Formatter - return New(kind) + return New(std.Format) } func New(kind string) (f Formatter, err error) { From 79fa957d02a6025a9e725e42a3fc8f0169c99105 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Mon, 11 Mar 2024 16:04:29 -0500 Subject: [PATCH 09/11] =?UTF-8?q?=E2=9C=A8=20Some=20convenience=20methods?= =?UTF-8?q?=20for=20Metas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/drop.go | 8 ++++---- models/entry.go | 2 +- models/meta_test.go | 16 ++++++++++++++++ models/metas.go | 29 ++++++++++++++++++----------- 4 files changed, 39 insertions(+), 16 deletions(-) diff --git a/cmd/drop.go b/cmd/drop.go index a268d20..45491e9 100644 --- a/cmd/drop.go +++ b/cmd/drop.go @@ -42,17 +42,17 @@ var dropCmd = &cobra.Command{ RunE: func(cmd *cobra.Command, args []string) error { log := args[0] title := args[1] - ms := models.Metas{} + ms := &models.Metas{} if len(j.RawMessage) > 8 { - err := json.Unmarshal([]byte(j.RawMessage), &ms) + err := json.Unmarshal([]byte(j.RawMessage), ms) if err != nil { return err } } for k, v := range fields { - ms = append(ms, models.Meta{k, tools.ParseString(v)}) + ms.AppendTo(k, tools.ParseString(v)) } - e := models.Entry{title, d.t, ms} + e := models.Entry{title, d.t, *ms} l := models.Log{log, []models.Entry{e}} err := files.Append(l) if err != nil { diff --git a/models/entry.go b/models/entry.go index bca992c..2a11fd2 100644 --- a/models/entry.go +++ b/models/entry.go @@ -186,7 +186,7 @@ func (e Entry) MarshalJSON() ([]byte, error) { out := map[string]any{} out["title"] = e.Title out["date"] = e.Date.Format(time.RFC3339) - for k, v := range e.Fields.toMap() { + for k, v := range e.Fields.Map() { out[k] = v } return json.Marshal(out) diff --git a/models/meta_test.go b/models/meta_test.go index 3f623d0..2d4cea8 100644 --- a/models/meta_test.go +++ b/models/meta_test.go @@ -13,6 +13,8 @@ import ( // Type assertions var _ encoding.TextMarshaler = Meta{} var _ encoding.TextUnmarshaler = new(Meta) +var _ json.Marshaler = Metas{} +var _ json.Unmarshaler = new(Metas) var skipMarshalTest = errors.New("skip marshal") @@ -125,3 +127,17 @@ func TestMetasJsonError(t *testing.T) { assert.Error(t, err) assert.Len(t, ms, 0) } + +func TestMetasAppend(t *testing.T) { + ms := Metas{} + ms = ms.Append("foo", 42) + assert.Len(t, ms, 1) + assert.Equal(t, Meta{"foo", 42}, ms[0]) +} + +func TestMetasAppendTo(t *testing.T) { + ms := &Metas{} + ms.AppendTo("foo", 42) + assert.Len(t, *ms, 1) + assert.Equal(t, Meta{"foo", 42}, (*ms)[0]) +} diff --git a/models/metas.go b/models/metas.go index 7a2602f..4db9d46 100644 --- a/models/metas.go +++ b/models/metas.go @@ -1,21 +1,15 @@ package models import ( - //"bufio" - //"bytes" "encoding/json" - //"fmt" - //"errors" - //"regexp" - //"strings" - //"sync" "time" - //"codeberg.org/danjones000/my-log/tools" ) +// A slice of Meta type Metas []Meta -func (ms Metas) toMap() map[string]any { +// Returns a single map containing all the Meta. Is useful when encoding to JSON +func (ms Metas) Map() map[string]any { out := map[string]any{} for _, f := range ms { if _, found := out[f.Key]; found || f.Key == "title" || f.Key == "date" { @@ -42,12 +36,14 @@ func (ms Metas) toMap() map[string]any { return out } +// Implements json.Marshaler func (ms Metas) MarshalJSON() ([]byte, error) { - return json.Marshal(ms.toMap()) + return json.Marshal(ms.Map()) } +// Implements json.Unmarshaler func (ms *Metas) UnmarshalJSON(in []byte) error { - old := (*ms).toMap() + old := (*ms).Map() out := map[string]any{} err := json.Unmarshal(in, &out) if err != nil { @@ -62,3 +58,14 @@ func (ms *Metas) UnmarshalJSON(in []byte) error { *ms = ret return nil } + +// Returns a new Metas with a new Meta appended +func (ms Metas) Append(k string, v any) Metas { + return append(ms, Meta{k, v}) +} + +// Appends a new Meta to this Metas +func (ms *Metas) AppendTo(k string, v any) { + n := (*ms).Append(k, v) + *ms = n +} From 632c7143f114a09b7b4053fbd49b7206092cef9f Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Mon, 11 Mar 2024 21:18:52 -0500 Subject: [PATCH 10/11] =?UTF-8?q?=E2=9C=A8=20Add=20null=20formatter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/drop.go | 5 ++++- formatters/new.go | 1 + formatters/new_test.go | 2 +- formatters/null.go | 32 +++++++++++++++++++++++++++++ formatters/null_test.go | 45 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 formatters/null.go create mode 100644 formatters/null_test.go diff --git a/cmd/drop.go b/cmd/drop.go index 45491e9..d74917e 100644 --- a/cmd/drop.go +++ b/cmd/drop.go @@ -67,7 +67,10 @@ var dropCmd = &cobra.Command{ if err != nil { return err } - fmt.Fprintf(cmd.OutOrStdout(), "%s\n", out) + if len(out) > 0 && out[len(out)-1] != 10 { + out = append(out, 10) + } + fmt.Fprintf(cmd.OutOrStdout(), "%s", out) return nil }, diff --git a/formatters/new.go b/formatters/new.go index 3cfdb3c..bade49b 100644 --- a/formatters/new.go +++ b/formatters/new.go @@ -11,6 +11,7 @@ type formatMaker func(config.Formatters) (Formatter, error) var formatterMap = map[string]formatMaker{ "plain": newPlain, "json": newJson, + "zero": newNull, } func Preferred() (f Formatter, err error) { diff --git a/formatters/new_test.go b/formatters/new_test.go index c6d5e56..ed56be3 100644 --- a/formatters/new_test.go +++ b/formatters/new_test.go @@ -10,7 +10,7 @@ import ( ) func TestKinds(t *testing.T) { - assert.ElementsMatch(t, []string{"plain", "json"}, Kinds()) + assert.ElementsMatch(t, []string{"plain", "json", "zero"}, Kinds()) } func TestNewUnsupported(t *testing.T) { diff --git a/formatters/null.go b/formatters/null.go new file mode 100644 index 0000000..4dd1775 --- /dev/null +++ b/formatters/null.go @@ -0,0 +1,32 @@ +package formatters + +import ( + "codeberg.org/danjones000/my-log/config" + "codeberg.org/danjones000/my-log/models" +) + +func newNull(ff config.Formatters) (Formatter, error) { + return &Null{}, nil +} + +type Null struct{} + +func (n *Null) Name() string { + return "zero" +} + +func (n *Null) Meta(m models.Meta) (o []byte, err error) { + return +} + +func (n *Null) Entry(e models.Entry) (o []byte, err error) { + return +} + +func (n *Null) Log(l models.Log) (o []byte, err error) { + return +} + +func (n *Null) Logs(logs []models.Log) (out []byte, err error) { + return +} diff --git a/formatters/null_test.go b/formatters/null_test.go new file mode 100644 index 0000000..f7c26c1 --- /dev/null +++ b/formatters/null_test.go @@ -0,0 +1,45 @@ +package formatters + +import ( + "testing" + "time" + + "codeberg.org/danjones000/my-log/models" + "github.com/stretchr/testify/assert" +) + +var empty []byte + +func TestNullName(t *testing.T) { + f, err := New("zero") + assert.NoError(t, err) + assert.Equal(t, "zero", f.Name()) +} + +func TestNullMeta(t *testing.T) { + f, _ := New("zero") + o, err := f.Meta(models.Meta{"foo", 42}) + assert.NoError(t, err) + assert.Equal(t, empty, o) +} + +func TestNullEntry(t *testing.T) { + f, _ := New("zero") + o, err := f.Entry(models.Entry{"title", time.Now(), models.Metas{}}) + assert.NoError(t, err) + assert.Equal(t, empty, o) +} + +func TestNullLog(t *testing.T) { + f, _ := New("zero") + o, err := f.Log(models.Log{"jim", []models.Entry{{"title", time.Now(), models.Metas{}}}}) + assert.NoError(t, err) + assert.Equal(t, empty, o) +} + +func TestNullLogs(t *testing.T) { + f, _ := New("zero") + o, err := f.Logs([]models.Log{{"jim", []models.Entry{{"title", time.Now(), models.Metas{}}}}}) + assert.NoError(t, err) + assert.Equal(t, empty, o) +} From 821ba6117c40de6f4afab809579861a3b7d48524 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Mon, 11 Mar 2024 21:28:22 -0500 Subject: [PATCH 11/11] =?UTF-8?q?=F0=9F=93=9D=20Update=20CHANGELOG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30e3b58..1dadceb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## [0.0.3] - 2024-03-11 + +- ✨ Add JSON formatter +- 💥 Breaking change: renamed output.stdout.config value formatter to format + ## [0.0.2] - 2024-03-09 - ✨ Use plain formatter to output entry from drop