From fd5d315164fdb4ad8775186f98df10d5447fdec2 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Sat, 2 Mar 2024 17:10:06 -0600 Subject: [PATCH 01/33] =?UTF-8?q?=F0=9F=93=9D=20Add=20README?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 148 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..0751900 --- /dev/null +++ b/README.md @@ -0,0 +1,148 @@ +# my-log + +`my-log` is a tool for generating and parsing log files for whatever you want. This is early in development. Check our Roadmap before for what's working. + +I originally wrote [DropLogger](https://github.com/goodevilgenius/droplogger) to serve this purpose. DropLogger was originally designed to work primarily with IFTTT and Dropbox. Due to IFTTT changing their service significantly since I originally signed up, I no longer use it. Even without IFTTT, DropLogger is a great tool. + +So, why did I decide to completely rewrite it? Mainly because DropLogger is written in Python. While Python is a great language, I haven't used it seriously for many years, and I didn't find a whole lot of motivation to add new features to DropLogger, due to this. But I've been working in go for the past six months, and have kind of fell in love with the language. I'd been considering a rewrite of DropLogger for a while, so I decided to help myself get more practice in go by rewriting DropLogger in it. + +So, how does this work? + +Currently, it mostly doesn't. `my-log` is still in its early stages, and DropLogger is still needed for most of the functionality. So, how will it work? + +## Log files + +We start with the individual log files. These were designed to be very flexible so that they could be written using a number of different tools. Originally, IFTTT recipes were created that would write to files in Dropbox, but this could be adapted to a number of other automation tools to automatically write as things happen. + +What things? Well, maybe you use Tasker to trigger an action when you get home. You might want to keep a log of whenever you arrive at your house. Or, maybe you use [Last.FM](https://www.last.fm/home) to keep track of your music listening habits, and you want to log whenever you listen to a music track. You could create a Zap in Zapier that responds to new scrobbles on Last.FM, and adds those scrobbles to a file. + +### Log format + +As I mentioned, the format is intended to be very easy to write. Here's a sample: + +``` +@begin January 12, 2024 at 2:34PM - Title +@key value +@longKey this entry is long, and +spans multiple lines +@number 4 +@bool true +@end +``` + +So, each entry starts with `@begin` and ends with `@end`. It must have a date and a title. It may also have additional data which is indicated by an `@` at the beginning of the line. If I were to convert this to JSON (which `my-log` can do for you), it would look like: + +```json +{ + "title": "Title", + "date": "2024-01-12T14:34:00Z", + "key": "value", + "longKey": "this entry is long, and\nspans multiple lines", + "number": 4, + "bool": true +} +``` + +A couple things to note: +- When outputting JSON, the date is converted to ISO-8601 format. The timezone used (if none was given in the original log) is your own local time. +- The newline in the `longKey` was preserved +- Different types are recognized and parsed correctly. It supports the following types: + + string (default) + + numbers + + boolean values (true or false) + + dates and times + + A null value (the string "null", "nil", "none", or "~") + + A raw JSON object/array + +Since the extra fields are optional, the simplest log entry can be on a single line. For example, you might have a log file called `notes.txt` with this: + +``` +@begin February 3, 2015 at 01:33PM - Remember to call Mom @end +@begin February 4, 2015 at 07:45AM - Breakfast today was great! @end +``` + +As JSON, that would be: + +```json +[{ + "title":"Rember to call Mom", + "date":"2015-02-03T13:33:00Z" +},{ + "title":"Breakfase today was great!", + "date":"2015-02-04T07:45:00Z" +}] +``` + +### Adding log entries + +As was previously noted, the idea is that you can figure out the best way for you to add to the log file. But, `my-log` also comes with a command to add them from the command line. Run `my-log drop --help` for instructions on how to use it. But, here's a few examples: + +```shell +my-log drop notes "Hello" +# Adds "@begin - Hello @end" to notes.txt file + +my-log drop -d "yesterday" calls "Talked with Jeremy" -f phone_number=+1-555-867-5309 +# If today is January 2, 2024, adds the follow entry to calls.txt +# @begin January 1, 2024 at 12:00:00AM UTC - Talked with Jeremy +# @phone_number +1-555-867-5309 @end + +my-log drop -d "1999-12-31T23:59:59Z" events "The end of the world" -f notes="As we know it" -j '{"artist":"R.E.M","slaps":true}' +# Adds the following entry to events.txt +# @begin December 31, 1999 at 11:59:59PM UTC - The end of the world +# @notes As we know it +# @artist R.E.M +# @slaps true @end +``` + +## Output + +This is a work in progress. More info coming soon. Short version is, we want to be able to output to multiple formats in multiple places. + +Check [DropLogger's documentation](https://github.com/goodevilgenius/droplogger?tab=readme-ov-file#output) for info on how we want to make it work. + +## Configuration + +We use a TOML file for configuration. The default location on Linux is ~/.config/my-log/config.toml. You can find the exact location by doing `my-log -h` and looking at the help for the `--config` flag. Running `my-log config` will save the default config file to the default location. The file is intended to be edited by hand. There is no mechanism within the program to modify the file, aside from saving the default one. + +The default one has comments to help you out, but here's the options: + +### `[input]` + +- `path`: The path to where the logs are located. This is usually ~/my-log, but if you want to store it in Dropbox, you might want it to be ~/Dropbox/my-log +- `ext`: The file extension for log files. This is usually txt, which makes it easier to work with multiple tools, but you can change it to log, or my-log, if you want. If you set it to an empty string, no extension will be used, which also means that when parsing the log files, it will look at all files in the folder. +- `recurse`: Whether to look in sub-folders. + +### `[output.which-one]` + +Each separate output has its own set of configuration. So, replace `which-one` with the output name. + +- `enabled`: if set to false, will skip that output when running. +- `config`: This is an output-specific set of settings + +#### `[output.stdout.config]` + +*This section may change in the near future. We're considering supporting multiple formats.* + +- `json`: Outputs as JSON + +## Roadmap + +- [x] `drop` command. This is functional, and supports all the features of `drop-a-log` + + [ ] Don't add an extra blank line before new entries + + [ ] Add a new line at the end +- [ ] Output log entries + + [ ] A single date + + [ ] a specific period of time + + [ ] filter to specific logs + + [ ] stdout + - [ ] plain text + - [ ] JSON + - [ ] YAML + - [ ] Other formats? Submit an issue! + + [ ] file output + - [ ] Any format that stdout supports + - [ ] Multiple formats at once + - [ ] RSS + - [ ] ATOM + + [ ] sqlite database +- [ ] Maybe: plug-in system to add formats or output destinations From 286ac4557dbfa0abca5e9912080d73d37a4df051 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Sun, 3 Mar 2024 13:56:48 -0600 Subject: [PATCH 02/33] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Add=20tools.WriteVal?= =?UTF-8?q?ue?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- models/meta.go | 33 +++---------------------- models/meta_test.go | 2 +- tools/write_buffer.go | 44 ++++++++++++++++++++++++++++++++++ tools/write_buffer_test.go | 49 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 97 insertions(+), 31 deletions(-) create mode 100644 tools/write_buffer.go create mode 100644 tools/write_buffer_test.go diff --git a/models/meta.go b/models/meta.go index c737f95..dba99b2 100644 --- a/models/meta.go +++ b/models/meta.go @@ -2,12 +2,9 @@ package models import ( "bytes" - "encoding/json" "errors" "fmt" "regexp" - "strconv" - "time" "codeberg.org/danjones000/my-log/tools" ) @@ -25,33 +22,9 @@ func (m Meta) MarshalText() ([]byte, error) { buff.WriteRune('@') buff.WriteString(m.Key) buff.WriteRune(' ') - switch v := m.Value.(type) { - default: - return nil, fmt.Errorf("Unknown type %T", v) - 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)) + n, err := tools.WriteValue(buff, m.Value) + if n == 0 || err != nil { + return []byte{}, err } return buff.Bytes(), nil diff --git a/models/meta_test.go b/models/meta_test.go index 2dae977..1e0888b 100644 --- a/models/meta_test.go +++ b/models/meta_test.go @@ -40,7 +40,7 @@ func TestMeta(t *testing.T) { {"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-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"), ""}, {"empty-mar", "nope", skipMarshalTest, "", nil, ErrorParsing}, {"no-key-mar", "nope", skipMarshalTest, "nope", nil, ErrorParsing}, diff --git a/tools/write_buffer.go b/tools/write_buffer.go new file mode 100644 index 0000000..e67d219 --- /dev/null +++ b/tools/write_buffer.go @@ -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 +} diff --git a/tools/write_buffer_test.go b/tools/write_buffer_test.go new file mode 100644 index 0000000..1a4a103 --- /dev/null +++ b/tools/write_buffer_test.go @@ -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()) + } +} From 89e6c2b3bd961dd2af40b45aee607f53237cdcc2 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Thu, 7 Mar 2024 10:10:54 -0600 Subject: [PATCH 03/33] =?UTF-8?q?=E2=9C=A8=20Add=20plain=20text=20formatte?= =?UTF-8?q?r?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- formatters/interface.go | 10 ++++ formatters/new.go | 34 ++++++++++++ formatters/new_test.go | 35 +++++++++++++ formatters/plain.go | 88 +++++++++++++++++++++++++++++++ formatters/plain_test.go | 109 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 276 insertions(+) create mode 100644 formatters/interface.go create mode 100644 formatters/new.go create mode 100644 formatters/new_test.go create mode 100644 formatters/plain.go create mode 100644 formatters/plain_test.go diff --git a/formatters/interface.go b/formatters/interface.go new file mode 100644 index 0000000..4276aed --- /dev/null +++ b/formatters/interface.go @@ -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) +} diff --git a/formatters/new.go b/formatters/new.go new file mode 100644 index 0000000..8cd567f --- /dev/null +++ b/formatters/new.go @@ -0,0 +1,34 @@ +package formatters + +import ( + "errors" + + "codeberg.org/danjones000/my-log/config" +) + +type formatMaker func(oo config.Outputs) (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.Outputs) + } + + return nil, errors.New("unimplemented") +} + +func Kinds() []string { + r := []string{} + for kind, _ := range formatterMap { + r = append(r, kind) + } + return r +} diff --git a/formatters/new_test.go b/formatters/new_test.go new file mode 100644 index 0000000..bf91000 --- /dev/null +++ b/formatters/new_test.go @@ -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) +} diff --git a/formatters/plain.go b/formatters/plain.go new file mode 100644 index 0000000..420b008 --- /dev/null +++ b/formatters/plain.go @@ -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(oo config.Outputs) (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 +} diff --git a/formatters/plain_test.go b/formatters/plain_test.go new file mode 100644 index 0000000..e384101 --- /dev/null +++ b/formatters/plain_test.go @@ -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)) +} From 99f6dc3f8cabcda89adf31a21d33ad4af7759984 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Thu, 7 Mar 2024 10:48:41 -0600 Subject: [PATCH 04/33] =?UTF-8?q?=E2=9C=A8=20Use=20plain=20formatter=20to?= =?UTF-8?q?=20output=20entry=20from=20drop?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/drop.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/cmd/drop.go b/cmd/drop.go index f7fc43d..2b33750 100644 --- a/cmd/drop.go +++ b/cmd/drop.go @@ -22,6 +22,7 @@ import ( "time" "codeberg.org/danjones000/my-log/files" + "codeberg.org/danjones000/my-log/formatters" "codeberg.org/danjones000/my-log/models" "codeberg.org/danjones000/my-log/tools" "github.com/spf13/cobra" @@ -58,11 +59,17 @@ var dropCmd = &cobra.Command{ if err != nil { return err } - by, err := e.MarshalText() + + form, err := formatters.New("plain") if err != nil { 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 }, } From da3b5249259b4b1b879eb9aebb120de83fb8418a Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Thu, 7 Mar 2024 21:19:45 -0600 Subject: [PATCH 05/33] =?UTF-8?q?=E2=9C=A8=20Separate=20formatters=20in=20?= =?UTF-8?q?config?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/default.go | 10 ++++++++-- config/load.go | 19 +++++++++++++++++++ config/load_test.go | 19 ++++++++++++++++--- config/types.go | 13 ++++++++++--- formatters/new.go | 4 ++-- formatters/plain.go | 2 +- 6 files changed, 56 insertions(+), 11 deletions(-) diff --git a/config/default.go b/config/default.go index 461f5c3..76ef8d2 100644 --- a/config/default.go +++ b/config/default.go @@ -27,8 +27,14 @@ dotFolder = true [output.stdout] enabled = true [output.stdout.config] -# Whether to output as JSON. Maybe useful to pipe elsewhere. -json = false +# Formatter to use when outputting to stdout +formatter = "plain" + +[formatters] + +[formatters.json] +# Set to true to pretty print JSON output +pretty_print = false ` diff --git a/config/load.go b/config/load.go index 04fd95d..3de0a19 100644 --- a/config/load.go +++ b/config/load.go @@ -32,6 +32,7 @@ func Load() (Config, error) { } env.Parse(&c) c.Outputs["stdout"] = loadStdout(c.Outputs["stdout"]) + c.Formatters["json"] = loadJsonFormat(c.Formatters["json"]) l := "" for k, v := range Overrides { @@ -77,3 +78,21 @@ func (oo Outputs) Stdout() (s Stdout, enabled bool) { 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 +} diff --git a/config/load_test.go b/config/load_test.go index 7399316..ca6a0b7 100644 --- a/config/load_test.go +++ b/config/load_test.go @@ -65,12 +65,25 @@ func TestStdoutMissing(t *testing.T) { } func TestStdoutLoad(t *testing.T) { - os.Setenv("LOG_STDOUT_JSON", "true") - defer os.Unsetenv("LOG_STDOUT_JSON") + os.Setenv("LOG_STDOUT_FORMATTER", "json") + defer os.Unsetenv("LOG_STDOUT_FORMATTER") os.Setenv("LOG_STDOUT_ENABLED", "true") defer os.Unsetenv("LOG_STDOUT_ENABLED") c, _ := Load() std, en := c.Outputs.Stdout() 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) } diff --git a/config/types.go b/config/types.go index a3a3681..a950a80 100644 --- a/config/types.go +++ b/config/types.go @@ -1,8 +1,9 @@ package config type Config struct { - Input Input - Outputs Outputs `toml:"output"` + Input Input + Outputs Outputs `toml:"output"` + Formatters Formatters } type Input struct { @@ -20,9 +21,15 @@ type Output struct { } type Stdout struct { - Json bool `env:"LOG_STDOUT_JSON" mapstructure:"json"` + Formatter string `env:"LOG_STDOUT_FORMATTER" mapstructure:"formatter"` } type stdoutEnabled struct { 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"` +} diff --git a/formatters/new.go b/formatters/new.go index 8cd567f..33084e3 100644 --- a/formatters/new.go +++ b/formatters/new.go @@ -6,7 +6,7 @@ import ( "codeberg.org/danjones000/my-log/config" ) -type formatMaker func(oo config.Outputs) (Formatter, error) +type formatMaker func(config.Formatters) (Formatter, error) var formatterMap = map[string]formatMaker{ "plain": newPlain, @@ -19,7 +19,7 @@ func New(kind string) (f Formatter, err error) { } if make, ok := formatterMap[kind]; ok { - return make(conf.Outputs) + return make(conf.Formatters) } return nil, errors.New("unimplemented") diff --git a/formatters/plain.go b/formatters/plain.go index 420b008..ed8ee82 100644 --- a/formatters/plain.go +++ b/formatters/plain.go @@ -8,7 +8,7 @@ import ( "codeberg.org/danjones000/my-log/tools" ) -func newPlain(oo config.Outputs) (Formatter, error) { +func newPlain(ff config.Formatters) (Formatter, error) { return &PlainText{}, nil } From f68aebdedb9033b9106d0b1dc2c4e84222d58c6e Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Thu, 7 Mar 2024 21:50:51 -0600 Subject: [PATCH 06/33] =?UTF-8?q?=E2=9C=85=20=F0=9F=92=AF%?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/load_test.go | 7 ++++++- files/append_test.go | 15 +++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/config/load_test.go b/config/load_test.go index ca6a0b7..51e024e 100644 --- a/config/load_test.go +++ b/config/load_test.go @@ -55,7 +55,12 @@ func TestOverrideJson(t *testing.T) { 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) { var oo Outputs = map[string]Output{} diff --git a/files/append_test.go b/files/append_test.go index 487acfd..c31ec63 100644 --- a/files/append_test.go +++ b/files/append_test.go @@ -57,6 +57,21 @@ func (s *AppendTestSuite) TestSuccess() { 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() { config.Overrides["input.dotFolder"] = "true" e := models.Entry{ From 5b8e4696ea7381ef24b2ca7d382a34bc087fd769 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Sat, 9 Mar 2024 15:38:34 -0600 Subject: [PATCH 07/33] =?UTF-8?q?=E2=9C=A8=20Only=20add=20newline=20to=20f?= =?UTF-8?q?ile=20when=20needed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- files/append.go | 17 +++++++++++- files/append_test.go | 62 ++++++++++++++++++++++++++++++++++++++++++++ models/entry.go | 2 +- models/entry_test.go | 6 ++--- 4 files changed, 82 insertions(+), 5 deletions(-) diff --git a/files/append.go b/files/append.go index bd9f4ee..cb62d9b 100644 --- a/files/append.go +++ b/files/append.go @@ -2,6 +2,7 @@ package files import ( "fmt" + "io" "os" fp "path/filepath" "strings" @@ -31,18 +32,32 @@ func Append(l models.Log) error { return err } - f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0640) + f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0640) if err != nil { return err } defer f.Close() + f.Seek(-1, os.SEEK_END) + last := make([]byte, 1, 1) + n, err := f.Read(last) + if err != nil && err != io.EOF { + return err + } + + if err == nil && n > 0 { + if last[0] != 10 { + f.Write([]byte{10}) + } + } + for _, e := range l.Entries { by, err := e.MarshalText() if err != nil { continue } f.Write(by) + f.Write([]byte{10}) } return nil diff --git a/files/append_test.go b/files/append_test.go index c31ec63..0976363 100644 --- a/files/append_test.go +++ b/files/append_test.go @@ -33,6 +33,7 @@ func (s *AppendTestSuite) TearDownSuite() { } func (s *AppendTestSuite) TestSuccess() { + defer os.Remove(s.dir + "/test.log") when := time.Now().Local() e := models.Entry{ Title: "Jimmy", @@ -57,7 +58,68 @@ func (s *AppendTestSuite) TestSuccess() { s.Assert().Contains(st, "\n@bar true") } +func (s *AppendTestSuite) TestTwoEntries() { + defer os.Remove(s.dir + "/test.log") + when := time.Now().Local() + whens := when.Format(models.DateFormat) + e := []models.Entry{ + {Title: "one", Date: when}, + {Title: "two", Date: when}, + } + l := models.Log{ + Name: "test", + Entries: e, + } + err := Append(l) + s.Assert().NoError(err) + s.Require().FileExists(s.dir + "/test.log") + by, _ := os.ReadFile(s.dir + "/test.log") + exp := fmt.Sprintf("@begin %s - one @end\n@begin %s - two @end\n", whens, whens) + s.Assert().Equal(exp, string(by)) +} + +func (s *AppendTestSuite) TestAddNewLine() { + defer os.Remove(s.dir + "/test.log") + os.WriteFile(s.dir+"/test.log", []byte("foo"), 0644) + when := time.Now().Local() + whens := when.Format(models.DateFormat) + e := []models.Entry{ + {Title: "one", Date: when}, + } + l := models.Log{ + Name: "test", + Entries: e, + } + err := Append(l) + s.Assert().NoError(err) + s.Require().FileExists(s.dir + "/test.log") + by, _ := os.ReadFile(s.dir + "/test.log") + exp := fmt.Sprintf("foo\n@begin %s - one @end\n", whens) + s.Assert().Equal(exp, string(by)) +} + +func (s *AppendTestSuite) TestDontAddNewLine() { + defer os.Remove(s.dir + "/test.log") + os.WriteFile(s.dir+"/test.log", []byte("foo\n"), 0644) + when := time.Now().Local() + whens := when.Format(models.DateFormat) + e := []models.Entry{ + {Title: "one", Date: when}, + } + l := models.Log{ + Name: "test", + Entries: e, + } + err := Append(l) + s.Assert().NoError(err) + s.Require().FileExists(s.dir + "/test.log") + by, _ := os.ReadFile(s.dir + "/test.log") + exp := fmt.Sprintf("foo\n@begin %s - one @end\n", whens) + s.Assert().Equal(exp, string(by)) +} + func (s *AppendTestSuite) TestFailEntry() { + defer os.Remove(s.dir + "/test.log") e := models.Entry{ Title: "Jimmy", } diff --git a/models/entry.go b/models/entry.go index d504536..5a19933 100644 --- a/models/entry.go +++ b/models/entry.go @@ -74,7 +74,7 @@ func (e Entry) MarshalText() ([]byte, error) { } ch := e.getFieldMarshalChan() buff := &bytes.Buffer{} - buff.WriteString("\n@begin ") + buff.WriteString("@begin ") buff.WriteString(e.Date.Format(DateFormat)) buff.WriteString(" - ") buff.WriteString(e.Title) diff --git a/models/entry_test.go b/models/entry_test.go index a8bec44..aefecc9 100644 --- a/models/entry_test.go +++ b/models/entry_test.go @@ -90,13 +90,13 @@ func getEntryMarshalTestRunner(title string, date time.Time, fields []Meta, firs if first == "" { return } + os := string(o) if len(lines) == 0 { - assert.Equal(t, "\n"+first, string(o)) + assert.Equal(t, first, os) return } - os := string(o) - assert.Regexp(t, "^\n"+first, os) + assert.Regexp(t, first, os) for _, line := range lines { assert.Regexp(t, "(?m)^"+line, os) } From 11dea95ce2b27cb45ac1486e5c7f76249e48c237 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Sat, 9 Mar 2024 15:42:29 -0600 Subject: [PATCH 08/33] =?UTF-8?q?=F0=9F=93=9D=20Update=20README=20with=20n?= =?UTF-8?q?ew=20features?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0751900..15d8a20 100644 --- a/README.md +++ b/README.md @@ -128,14 +128,14 @@ Each separate output has its own set of configuration. So, replace `which-one` w ## Roadmap - [x] `drop` command. This is functional, and supports all the features of `drop-a-log` - + [ ] Don't add an extra blank line before new entries - + [ ] Add a new line at the end + + [x] Don't add an extra blank line before new entries + + [x] Add a new line at the end - [ ] Output log entries + [ ] A single date + [ ] a specific period of time + [ ] filter to specific logs + [ ] stdout - - [ ] plain text + - [x] plain text - [ ] JSON - [ ] YAML - [ ] Other formats? Submit an issue! From febbce8a6be26e7057fa2368c492ea7acf54cbb4 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Sat, 9 Mar 2024 16:05:59 -0600 Subject: [PATCH 09/33] =?UTF-8?q?=E2=9C=A8=20Add=20formatters.Preferred?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/drop.go | 2 +- cmd/root.go | 2 +- formatters/new.go | 10 ++++++++++ formatters/new_test.go | 10 ++++++++++ 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/cmd/drop.go b/cmd/drop.go index 2b33750..658cb7c 100644 --- a/cmd/drop.go +++ b/cmd/drop.go @@ -60,7 +60,7 @@ var dropCmd = &cobra.Command{ return err } - form, err := formatters.New("plain") + form, err := formatters.Preferred() if err != nil { return err } diff --git a/cmd/root.go b/cmd/root.go index 954a3a7..7e7bbbd 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.json=true") + 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") // Cobra also supports local flags, which will only run // when this action is called directly. diff --git a/formatters/new.go b/formatters/new.go index 33084e3..d88a9d0 100644 --- a/formatters/new.go +++ b/formatters/new.go @@ -12,6 +12,16 @@ var formatterMap = map[string]formatMaker{ "plain": newPlain, } +func Preferred() (f Formatter, err error) { + conf, err := config.Load() + if err != nil { + return + } + std, _ := conf.Outputs.Stdout() + kind := std.Formatter + return New(kind) +} + func New(kind string) (f Formatter, err error) { conf, err := config.Load() if err != nil { diff --git a/formatters/new_test.go b/formatters/new_test.go index bf91000..04b40fb 100644 --- a/formatters/new_test.go +++ b/formatters/new_test.go @@ -32,4 +32,14 @@ func TestNewCantGetConfig(t *testing.T) { form, err := New("plain") assert.Nil(t, form) assert.Error(t, err) + + form, err = Preferred() + assert.Nil(t, form) + assert.Error(t, err) +} + +func TestPreferred(t *testing.T) { + form, err := Preferred() + assert.NotNil(t, form) + assert.NoError(t, err) } From 17da5b66ea4586440f302e8a29b52fb417304876 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Sat, 9 Mar 2024 16:27:32 -0600 Subject: [PATCH 10/33] =?UTF-8?q?=F0=9F=93=9D=20Add=20formatter=20info=20t?= =?UTF-8?q?o=20README?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 15d8a20..24eae91 100644 --- a/README.md +++ b/README.md @@ -123,7 +123,15 @@ 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.* -- `json`: Outputs as JSON +- `formatter`: Which formatter to use when outputting data. This value is used by `my-log drop` to output the new entry. + +### `[formatters]` + +Some formatters may have custom configuration. + +#### `[formatters.json]` + +- `pretty_print`: If true, JSON output will be pretty printed. If false, it will be printed to a single line. ## Roadmap From 33fbdf7ecb06054d0a71bd21e8abb1387bd50339 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Sat, 9 Mar 2024 17:38:35 -0600 Subject: [PATCH 11/33] =?UTF-8?q?=F0=9F=93=9D=20Add=20to=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 122a538..30e3b58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## [0.0.2] - 2024-03-09 + +- ✨ Use plain formatter to output entry from drop +- ✨ Add newline to file when needed + ## [0.0.1] - 2024-03-02 🎉 Initial release. From 7e54d6e46cfa5d0d864a6d519a0222fe9360574f Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Sun, 10 Mar 2024 09:43:56 -0500 Subject: [PATCH 12/33] =?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 13/33] =?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 14/33] =?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 15/33] =?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 16/33] =?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 17/33] =?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 18/33] =?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 19/33] =?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 20/33] =?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 21/33] =?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 22/33] =?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 From bddf8e1458406e43415228337c5df57f0b2257f5 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Wed, 8 May 2024 15:04:18 -0500 Subject: [PATCH 23/33] =?UTF-8?q?=E2=9C=A8=20Add=20-p=20flag=20to=20config?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/config.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cmd/config.go b/cmd/config.go index dc28dd7..1407796 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -32,6 +32,11 @@ var configCmd = &cobra.Command{ //Long: ``, SilenceUsage: true, RunE: func(cmd *cobra.Command, args []string) (err error) { + print, _ := cmd.Flags().GetBool("print") + if print { + fmt.Fprintln(cmd.OutOrStdout(), config.ConfigPath) + return nil + } force, _ := cmd.Flags().GetBool("force") if !force { _, err = os.Stat(config.ConfigPath) @@ -60,4 +65,5 @@ func init() { rootCmd.AddCommand(configCmd) configCmd.Flags().BoolP("force", "f", false, "Force overwrite") + configCmd.Flags().BoolP("print", "p", false, "Print path only") } From b456bcbfcc90b6cb5a82e114ebd06fd348040369 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Wed, 8 May 2024 15:06:22 -0500 Subject: [PATCH 24/33] =?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 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1dadceb..19ef977 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## [0.0.4] - 2024-05-08 + +- ✨ Add -p flag to config to print config path + ## [0.0.3] - 2024-03-11 - ✨ Add JSON formatter From 02a7babb36c23935006041d2f8753ea99246013b Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Mon, 7 Oct 2024 14:15:28 -0500 Subject: [PATCH 25/33] =?UTF-8?q?=E2=9C=A8=20Syntactic=20sugar=20in=20drop?= =?UTF-8?q?=20command=20for=20outputting=20json?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/drop.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/cmd/drop.go b/cmd/drop.go index d74917e..2c246e6 100644 --- a/cmd/drop.go +++ b/cmd/drop.go @@ -21,6 +21,7 @@ import ( "fmt" "time" + "codeberg.org/danjones000/my-log/config" "codeberg.org/danjones000/my-log/files" "codeberg.org/danjones000/my-log/formatters" "codeberg.org/danjones000/my-log/models" @@ -31,6 +32,7 @@ import ( var d Date var fields map[string]string var j Json +var outJson bool // dropCmd represents the drop command var dropCmd = &cobra.Command{ @@ -40,6 +42,10 @@ var dropCmd = &cobra.Command{ Args: cobra.ExactArgs(2), SilenceUsage: true, RunE: func(cmd *cobra.Command, args []string) error { + if outJson { + config.Overrides["output.stdout.config.format"] = "json" + } + log := args[0] title := args[1] ms := &models.Metas{} @@ -83,6 +89,7 @@ func init() { dropCmd.Flags().VarP(&d, "date", "d", "Date for log entry") dropCmd.Flags().StringToStringVarP(&fields, "fields", "f", nil, "Fields you add to entry") dropCmd.Flags().VarP(&j, "json", "j", "Entire entry as json") + dropCmd.Flags().BoolVarP(&outJson, "output_json", "o", false, "Output result as JSON") } type Json struct { From 9980ae8cc7af45365deefed68a2fed0ac343b2d0 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Mon, 7 Oct 2024 14:18:32 -0500 Subject: [PATCH 26/33] =?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 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 19ef977..092e30b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## [0.0.5] - 2024-10-07 + +- Small change: adds --output_json to drop command. + ## [0.0.4] - 2024-05-08 - ✨ Add -p flag to config to print config path From 306b2c597db1ea83e8730c287e540e14fff48be2 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Mon, 7 Oct 2024 15:49:13 -0500 Subject: [PATCH 27/33] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20Upgrade=20go-datepar?= =?UTF-8?q?ser=20to=20new=20version?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Don't need my replaced version anymore --- go.mod | 8 +++----- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/go.mod b/go.mod index 4928514..b67cb17 100644 --- a/go.mod +++ b/go.mod @@ -5,10 +5,10 @@ go 1.21.5 require ( github.com/BurntSushi/toml v1.3.2 github.com/caarlos0/env/v10 v10.0.0 - github.com/markusmobius/go-dateparser v1.2.1 + github.com/markusmobius/go-dateparser v1.2.3 github.com/mitchellh/mapstructure v1.5.0 github.com/spf13/cobra v1.8.0 - github.com/stretchr/testify v1.8.4 + github.com/stretchr/testify v1.9.0 ) require ( @@ -25,9 +25,7 @@ require ( github.com/tetratelabs/wazero v1.2.1 // indirect github.com/wasilibs/go-re2 v1.3.0 // indirect golang.org/x/exp v0.0.0-20220321173239-a90fa8a75705 // indirect - golang.org/x/text v0.10.0 // indirect + golang.org/x/text v0.14.0 // indirect gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) - -replace github.com/markusmobius/go-dateparser => github.com/goodevilgenius/go-dateparser v1.2.2 diff --git a/go.sum b/go.sum index 350aa50..f782cde 100644 --- a/go.sum +++ b/go.sum @@ -8,8 +8,6 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/elliotchance/pie/v2 v2.7.0 h1:FqoIKg4uj0G/CrLGuMS9ejnFKa92lxE1dEgBD3pShXg= github.com/elliotchance/pie/v2 v2.7.0/go.mod h1:18t0dgGFH006g4eVdDtWfgFZPQEgl10IoEO8YWEq3Og= -github.com/goodevilgenius/go-dateparser v1.2.2 h1:Up9KokPx/h07mesQGAZQg3Xi/8yrDVn1638h3k/lRyk= -github.com/goodevilgenius/go-dateparser v1.2.2/go.mod h1:5xYsZ1h7iB3sE1BSu8bkjYpbFST7EU1/AFxcyO3mgYg= github.com/hablullah/go-hijri v1.0.2 h1:drT/MZpSZJQXo7jftf5fthArShcaMtsal0Zf/dnmp6k= github.com/hablullah/go-hijri v1.0.2/go.mod h1:OS5qyYLDjORXzK4O1adFw9Q5WfhOcMdAKglDkcTxgWQ= github.com/hablullah/go-juliandays v1.0.0 h1:A8YM7wIj16SzlKT0SRJc9CD29iiaUzpBLzh5hr0/5p0= @@ -24,6 +22,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo= github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= +github.com/markusmobius/go-dateparser v1.2.3 h1:TvrsIvr5uk+3v6poDjaicnAFJ5IgtFHgLiuMY2Eb7Nw= +github.com/markusmobius/go-dateparser v1.2.3/go.mod h1:cMwQRrBUQlK1UI5TIFHEcvpsMbkWrQLXuaPNMFzuYLk= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -36,8 +36,8 @@ github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tetratelabs/wazero v1.2.1 h1:J4X2hrGzJvt+wqltuvcSjHQ7ujQxA9gb6PeMs4qlUWs= github.com/tetratelabs/wazero v1.2.1/go.mod h1:wYx2gNRg8/WihJfSDxA1TIL8H+GkfLYm+bIfbblu9VQ= github.com/wasilibs/go-re2 v1.3.0 h1:LFhBNzoStM3wMie6rN2slD1cuYH2CGiHpvNL3UtcsMw= @@ -46,8 +46,8 @@ github.com/wasilibs/nottinygc v0.4.0 h1:h1TJMihMC4neN6Zq+WKpLxgd9xCFMw7O9ETLwY2e github.com/wasilibs/nottinygc v0.4.0/go.mod h1:oDcIotskuYNMpqMF23l7Z8uzD4TC0WXHK8jetlB3HIo= golang.org/x/exp v0.0.0-20220321173239-a90fa8a75705 h1:ba9YlqfDGTTQ5aZ2fwOoQ1hf32QySyQkR6ODGDzHlnE= golang.org/x/exp v0.0.0-20220321173239-a90fa8a75705/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= -golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58= -golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From e9c1bf7070a50ed3bda1e25b487fa09257ab9f47 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Mon, 7 Oct 2024 15:50:02 -0500 Subject: [PATCH 28/33] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Return=20bool=20dire?= =?UTF-8?q?ctly?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Silly me --- tools/parse.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tools/parse.go b/tools/parse.go index 021c6a2..e69c476 100644 --- a/tools/parse.go +++ b/tools/parse.go @@ -24,11 +24,7 @@ func ParseString(in string) any { if null.MatchString(s) { return nil } else if yesno.MatchString(s) { - if yes.MatchString(s) { - return true - } else { - return false - } + return yes.MatchString(s) } else if i, err := strconv.Atoi(s); err == nil { return i } else if f, err := strconv.ParseFloat(s, 64); err == nil { From ece9256745a26834afbf04cd5028bd632fd01b70 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Mon, 7 Oct 2024 15:50:33 -0500 Subject: [PATCH 29/33] =?UTF-8?q?=F0=9F=91=BD=EF=B8=8F=20Support=20changes?= =?UTF-8?q?=20in=20go-dateparser?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tools/parse_date.go | 17 +++++++++++++++-- tools/parse_date_test.go | 2 +- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/tools/parse_date.go b/tools/parse_date.go index d060299..0fa117a 100644 --- a/tools/parse_date.go +++ b/tools/parse_date.go @@ -1,6 +1,7 @@ package tools import ( + "strconv" "time" dp "github.com/markusmobius/go-dateparser" @@ -23,6 +24,19 @@ func ParseDate(in string) (t time.Time, err error) { return MaxTime, nil } + var er error + for _, format := range []string{time.RFC3339, DateFormat} { + if t, er = time.ParseInLocation(format, in, nil); er == nil { + return + } + } + + var ts int64 + if ts, er = strconv.ParseInt(in, 10, 0); er == nil { + t = time.Unix(ts, 0) + return + } + conf := dp.Configuration{ CurrentTime: time.Now().Local(), ReturnTimeAsPeriod: true, @@ -32,12 +46,11 @@ func ParseDate(in string) (t time.Time, err error) { d, err := dp.Parse(&conf, in) t = d.Time if err != nil { - d, err = dp.Parse(&conf, in, DateFormat) - t = d.Time return } y, mon, day, h, loc := t.Year(), t.Month(), t.Day(), t.Hour(), t.Location() + switch d.Period { case date.Second: t = t.Truncate(time.Second) diff --git a/tools/parse_date_test.go b/tools/parse_date_test.go index 7aa30b2..3099b88 100644 --- a/tools/parse_date_test.go +++ b/tools/parse_date_test.go @@ -23,7 +23,7 @@ func TestParseDate(t *testing.T) { firstMonth := time.Date(y, mon, 1, 0, 0, 0, 0, loc) firstYear := time.Date(y, 1, 1, 0, 0, 0, 0, loc) exact := "2075-02-12T12:13:54.536-02:00" - exactd, _ := time.ParseInLocation(time.RFC3339, exact, time.FixedZone("UTC-02:00", -7200)) + exactd, _ := time.ParseInLocation(time.RFC3339, exact, nil) var ts int64 = 1708876012 tsd := time.Unix(ts, 0) ent := "February 25, 2024 at 04:00:13AM +0230" From 5f6ce7ca21cb8a28e16a5b519f88019c550a078d Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Mon, 7 Oct 2024 15:52:37 -0500 Subject: [PATCH 30/33] =?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 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 092e30b..90a3f53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## [0.0.6] - 2024-10-07 + +- Update external dependency: go-dateparser + ## [0.0.5] - 2024-10-07 - Small change: adds --output_json to drop command. From da35bf4bcf719c568dbf7c42d8948b4649b2253f Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Wed, 9 Oct 2024 15:08:37 -0500 Subject: [PATCH 31/33] =?UTF-8?q?=E2=9C=85=20Fix=20TestMkdirErr=20on=20Dar?= =?UTF-8?q?win?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- files/append_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/append_test.go b/files/append_test.go index 0976363..03f0253 100644 --- a/files/append_test.go +++ b/files/append_test.go @@ -211,7 +211,7 @@ func (s *AppendTestSuite) TestConfLoadErr() { func (s *AppendTestSuite) TestMkdirErr() { // Don't run this test as root - config.Overrides["input.path"] = "/root/my-logs-test" + config.Overrides["input.path"] = "/var/my-logs-test" defer func(path string) { config.Overrides["input.path"] = path }(s.dir) From 85671a076cf0c4c530c25424548a98d26c0b72dd Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Wed, 9 Oct 2024 15:13:34 -0500 Subject: [PATCH 32/33] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Fix=20Makefile=20so?= =?UTF-8?q?=20it=20doesn't=20re-run=20test=20and=20fmt=20when=20it=20doesn?= =?UTF-8?q?'t=20need=20to?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Makefile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 35491dc..c65ce26 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,8 @@ test: ## Test application and generate coverage report $(MAKE) clean $(MAKE) $(COVEROUT) -$(COVEROUT): $(SOURCES) fmt +$(COVEROUT): $(SOURCES) + $(MAKE) fmt go test ./... -race -cover -coverprofile $@ $(COVERHTML): $(COVEROUT) @@ -33,7 +34,7 @@ $(COVERHTML): $(COVEROUT) report: $(COVERHTML) ## Generate a coverage report .PHONY: open-report -open-report: report ## Open the coverage report in the default browser +open-report: $(COVERHTML) ## Open the coverage report in the default browser xdg-open $(COVERHTML) .PHONY: build From fba5551bb3e0b5a2b7f914e3e4e1ee6c75a2155e Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Wed, 9 Oct 2024 15:14:37 -0500 Subject: [PATCH 33/33] =?UTF-8?q?=F0=9F=94=A8=20Fix=20make=20open-report?= =?UTF-8?q?=20for=20Darwin?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Makefile | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index c65ce26..a3d8581 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,12 @@ OUT=my-log GOBIN=$(shell go env GOBIN) COVEROUT=cover.out COVERHTML=cover.html +OPEN=xdg-open +OS=$(shell uname -s) + +ifeq ($(OS),Darwin) +OPEN=open +endif .PHONY: help help: ## Show help for documented recipes @@ -35,7 +41,7 @@ report: $(COVERHTML) ## Generate a coverage report .PHONY: open-report open-report: $(COVERHTML) ## Open the coverage report in the default browser - xdg-open $(COVERHTML) + $(OPEN) $< .PHONY: build build: $(OUT) ## Builds the application