🔀 Merge branch 'feature/drop' into develop
✨ Add drop command
This commit is contained in:
commit
b8271227b2
15 changed files with 403 additions and 44 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -123,3 +123,5 @@ Temporary Items
|
||||||
|
|
||||||
# End of https://www.toptal.com/developers/gitignore/api/go,linux,emacs,macos
|
# End of https://www.toptal.com/developers/gitignore/api/go,linux,emacs,macos
|
||||||
my-log
|
my-log
|
||||||
|
cover.html
|
||||||
|
cmd/test.go
|
||||||
|
|
|
||||||
78
cmd/drop.go
78
cmd/drop.go
|
|
@ -17,36 +17,76 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"codeberg.org/danjones000/my-log/files"
|
||||||
|
"codeberg.org/danjones000/my-log/models"
|
||||||
|
"codeberg.org/danjones000/my-log/tools"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var dateStr string
|
||||||
|
var fields map[string]string
|
||||||
|
var j Json
|
||||||
|
|
||||||
// dropCmd represents the drop command
|
// dropCmd represents the drop command
|
||||||
var dropCmd = &cobra.Command{
|
var dropCmd = &cobra.Command{
|
||||||
Use: "drop",
|
Use: "drop log title",
|
||||||
Short: "A brief description of your command",
|
Short: "Add a new log entry",
|
||||||
Long: `A longer description that spans multiple lines and likely contains examples
|
// Long: ``,
|
||||||
and usage of using your command. For example:
|
Args: cobra.ExactArgs(2),
|
||||||
|
SilenceUsage: true,
|
||||||
Cobra is a CLI library for Go that empowers applications.
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
This application is a tool to generate the needed files
|
log := args[0]
|
||||||
to quickly create a Cobra application.`,
|
title := args[1]
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
e := models.PartialEntry()
|
||||||
fmt.Println("drop called")
|
if len(j.RawMessage) > 8 {
|
||||||
|
err := json.Unmarshal([]byte(j.RawMessage), &e)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for k, v := range fields {
|
||||||
|
e.Fields = append(e.Fields, models.Meta{k, tools.ParseString(v)})
|
||||||
|
}
|
||||||
|
e.Title = title
|
||||||
|
e.Date = time.Now().Local() // @todo parse date
|
||||||
|
l := models.Log{log, []models.Entry{e}}
|
||||||
|
err := files.Append(l)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
by, err := e.MarshalText()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Fprintf(cmd.OutOrStdout(), "%s\n", by)
|
||||||
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.AddCommand(dropCmd)
|
rootCmd.AddCommand(dropCmd)
|
||||||
|
|
||||||
// Here you will define your flags and configuration settings.
|
dropCmd.Flags().StringVarP(&dateStr, "date", "d", time.Now().Local().Format(time.RFC3339), "Date for log entry")
|
||||||
|
dropCmd.Flags().StringToStringVarP(&fields, "fields", "f", nil, "Fields you add to entry")
|
||||||
// Cobra supports Persistent Flags which will work for this command
|
dropCmd.Flags().VarP(&j, "json", "j", "Entire entry as json")
|
||||||
// and all subcommands, e.g.:
|
}
|
||||||
// dropCmd.PersistentFlags().String("foo", "", "A help for foo")
|
|
||||||
|
type Json struct {
|
||||||
// Cobra supports local flags which will only run when this command
|
json.RawMessage
|
||||||
// is called directly, e.g.:
|
}
|
||||||
// dropCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
|
||||||
|
func (j *Json) String() string {
|
||||||
|
return string(j.RawMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *Json) Set(in string) error {
|
||||||
|
return json.Unmarshal([]byte(in), &j.RawMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *Json) Type() string {
|
||||||
|
return "json"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ func Execute() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
cobra.OnInitialize(initConfig)
|
//cobra.OnInitialize(initConfig)
|
||||||
|
|
||||||
// Here you will define your flags and configuration settings.
|
// Here you will define your flags and configuration settings.
|
||||||
// Cobra supports persistent flags, which, if defined here,
|
// Cobra supports persistent flags, which, if defined here,
|
||||||
|
|
@ -56,9 +56,3 @@ func init() {
|
||||||
// when this action is called directly.
|
// when this action is called directly.
|
||||||
// rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
// rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
||||||
}
|
}
|
||||||
|
|
||||||
// initConfig reads in config file and ENV variables if set.
|
|
||||||
func initConfig() {
|
|
||||||
// @todo
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var ConfigPath string
|
var ConfigPath string
|
||||||
var Overrides map[string]string
|
var Overrides = map[string]string{}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
conf, _ := os.UserConfigDir()
|
conf, _ := os.UserConfigDir()
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ package config
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
//fp "path/filepath"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
|
||||||
42
files/append.go
Normal file
42
files/append.go
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
package files
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
fp "path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"codeberg.org/danjones000/my-log/config"
|
||||||
|
"codeberg.org/danjones000/my-log/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Append(l models.Log) error {
|
||||||
|
conf, err := config.Load()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := fmt.Sprintf("%s.%s", strings.ReplaceAll(l.Name, ".", string(os.PathSeparator)), conf.Input.Ext)
|
||||||
|
path := fp.Join(conf.Input.Path, filename)
|
||||||
|
dir := fp.Dir(path)
|
||||||
|
err = os.MkdirAll(dir, 0750)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0640)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
for _, e := range l.Entries {
|
||||||
|
by, err := e.MarshalText()
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
f.Write(by)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
96
files/append_test.go
Normal file
96
files/append_test.go
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
package files
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"codeberg.org/danjones000/my-log/config"
|
||||||
|
"codeberg.org/danjones000/my-log/models"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAppend(t *testing.T) {
|
||||||
|
suite.Run(t, new(AppendTestSuite))
|
||||||
|
}
|
||||||
|
|
||||||
|
type AppendTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
dir string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AppendTestSuite) SetupSuite() {
|
||||||
|
s.dir, _ = os.MkdirTemp("", "append-test")
|
||||||
|
config.Overrides["input.path"] = s.dir
|
||||||
|
config.Overrides["input.ext"] = "log"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AppendTestSuite) TearDownSuite() {
|
||||||
|
os.RemoveAll(s.dir)
|
||||||
|
delete(config.Overrides, "input.path")
|
||||||
|
delete(config.Overrides, "input.ext")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AppendTestSuite) TestSuccess() {
|
||||||
|
when := time.Now().Local()
|
||||||
|
e := models.Entry{
|
||||||
|
Title: "Jimmy",
|
||||||
|
Date: when,
|
||||||
|
Fields: []models.Meta{
|
||||||
|
{"foo", 42},
|
||||||
|
{"bar", true},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
l := models.Log{
|
||||||
|
Name: "test",
|
||||||
|
Entries: []models.Entry{e},
|
||||||
|
}
|
||||||
|
err := Append(l)
|
||||||
|
s.Require().NoError(err)
|
||||||
|
s.Require().FileExists(s.dir + "/test.log")
|
||||||
|
by, err := os.ReadFile(s.dir + "/test.log")
|
||||||
|
st := string(by)
|
||||||
|
s.Require().NoError(err)
|
||||||
|
s.Assert().Contains(st, "Jimmy\n")
|
||||||
|
s.Assert().Contains(st, "\n@foo 42")
|
||||||
|
s.Assert().Contains(st, "\n@bar true")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AppendTestSuite) TestConfLoadErr() {
|
||||||
|
currConf := config.ConfigPath
|
||||||
|
tmp, _ := os.CreateTemp("", "app-conf-*.toml")
|
||||||
|
fname := tmp.Name()
|
||||||
|
defer tmp.Close()
|
||||||
|
defer os.Remove(fname)
|
||||||
|
fmt.Fprintln(tmp, `{"not":"toml"}`)
|
||||||
|
config.ConfigPath = fname
|
||||||
|
defer func(path string) {
|
||||||
|
config.ConfigPath = path
|
||||||
|
}(currConf)
|
||||||
|
err := Append(models.Log{})
|
||||||
|
s.Assert().ErrorContains(err, "toml")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AppendTestSuite) TestMkdirErr() {
|
||||||
|
// Don't run this test as root
|
||||||
|
config.Overrides["input.path"] = "/root/my-logs-test"
|
||||||
|
defer func(path string) {
|
||||||
|
config.Overrides["input.path"] = path
|
||||||
|
}(s.dir)
|
||||||
|
err := Append(models.Log{})
|
||||||
|
s.Assert().ErrorContains(err, "permission denied")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AppendTestSuite) TestOpenErr() {
|
||||||
|
l := models.Log{
|
||||||
|
Name: "test-open-err",
|
||||||
|
}
|
||||||
|
fname := s.dir + "/test-open-err.log"
|
||||||
|
os.MkdirAll(s.dir, 0750)
|
||||||
|
f, _ := os.Create(fname)
|
||||||
|
f.Close()
|
||||||
|
os.Chmod(fname, 0400) // read only
|
||||||
|
err := Append(l)
|
||||||
|
s.Assert().ErrorContains(err, "permission denied")
|
||||||
|
}
|
||||||
12
go.mod
12
go.mod
|
|
@ -5,6 +5,7 @@ go 1.21.5
|
||||||
require (
|
require (
|
||||||
github.com/BurntSushi/toml v1.3.2
|
github.com/BurntSushi/toml v1.3.2
|
||||||
github.com/caarlos0/env/v10 v10.0.0
|
github.com/caarlos0/env/v10 v10.0.0
|
||||||
|
github.com/markusmobius/go-dateparser v1.2.1
|
||||||
github.com/mitchellh/mapstructure v1.5.0
|
github.com/mitchellh/mapstructure v1.5.0
|
||||||
github.com/spf13/cobra v1.8.0
|
github.com/spf13/cobra v1.8.0
|
||||||
github.com/stretchr/testify v1.8.4
|
github.com/stretchr/testify v1.8.4
|
||||||
|
|
@ -12,10 +13,21 @@ require (
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||||
|
github.com/elliotchance/pie/v2 v2.7.0 // indirect
|
||||||
|
github.com/hablullah/go-hijri v1.0.2 // indirect
|
||||||
|
github.com/hablullah/go-juliandays v1.0.0 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
|
github.com/jalaali/go-jalaali v0.0.0-20210801064154-80525e88d958 // indirect
|
||||||
github.com/kr/pretty v0.3.1 // indirect
|
github.com/kr/pretty v0.3.1 // indirect
|
||||||
|
github.com/magefile/mage v1.14.0 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
|
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
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
replace github.com/markusmobius/go-dateparser => github.com/goodevilgenius/go-dateparser v1.2.2
|
||||||
|
|
|
||||||
22
go.sum
22
go.sum
|
|
@ -6,12 +6,24 @@ github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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=
|
||||||
|
github.com/hablullah/go-juliandays v1.0.0/go.mod h1:0JOYq4oFOuDja+oospuc61YoX+uNEn7Z6uHYTbBzdGc=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
|
github.com/jalaali/go-jalaali v0.0.0-20210801064154-80525e88d958 h1:qxLoi6CAcXVzjfvu+KXIXJOAsQB62LXjsfbOaErsVzE=
|
||||||
|
github.com/jalaali/go-jalaali v0.0.0-20210801064154-80525e88d958/go.mod h1:Wqfu7mjUHj9WDzSSPI5KfBclTTEnLveRUFr/ujWnTgE=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
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/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
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/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||||
|
|
@ -26,6 +38,16 @@ 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/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 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
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=
|
||||||
|
github.com/wasilibs/go-re2 v1.3.0/go.mod h1:AafrCXVvGRJJOImMajgJ2M7rVmWyisVK7sFshbxnVrg=
|
||||||
|
github.com/wasilibs/nottinygc v0.4.0 h1:h1TJMihMC4neN6Zq+WKpLxgd9xCFMw7O9ETLwY2exJQ=
|
||||||
|
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=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
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 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,11 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"codeberg.org/danjones000/my-log/tools"
|
||||||
)
|
)
|
||||||
|
|
||||||
const DateFormat = "January 02, 2006 at 03:04:05PM -0700"
|
const DateFormat = tools.DateFormat
|
||||||
|
|
||||||
type Entry struct {
|
type Entry struct {
|
||||||
Title string
|
Title string
|
||||||
|
|
@ -20,6 +22,10 @@ type Entry struct {
|
||||||
skipMissing bool
|
skipMissing bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func PartialEntry() Entry {
|
||||||
|
return Entry{skipMissing: true}
|
||||||
|
}
|
||||||
|
|
||||||
type metaRes struct {
|
type metaRes struct {
|
||||||
out []byte
|
out []byte
|
||||||
err error
|
err error
|
||||||
|
|
@ -68,7 +74,7 @@ func (e Entry) MarshalText() ([]byte, error) {
|
||||||
}
|
}
|
||||||
ch := e.getFieldMarshalChan()
|
ch := e.getFieldMarshalChan()
|
||||||
buff := &bytes.Buffer{}
|
buff := &bytes.Buffer{}
|
||||||
buff.WriteString("@begin ")
|
buff.WriteString("\n@begin ")
|
||||||
buff.WriteString(e.Date.Format(DateFormat))
|
buff.WriteString(e.Date.Format(DateFormat))
|
||||||
buff.WriteString(" - ")
|
buff.WriteString(" - ")
|
||||||
buff.WriteString(e.Title)
|
buff.WriteString(e.Title)
|
||||||
|
|
@ -103,13 +109,10 @@ func (m *Entry) UnmarshalText(in []byte) error {
|
||||||
if date == "" {
|
if date == "" {
|
||||||
return ErrorMissingDate
|
return ErrorMissingDate
|
||||||
}
|
}
|
||||||
d, e := time.Parse(time.RFC3339, date)
|
d, e := tools.ParseDate(date)
|
||||||
if e != nil {
|
|
||||||
d, e = time.Parse(DateFormat, date)
|
|
||||||
if e != nil {
|
if e != nil {
|
||||||
return newParsingError(e)
|
return newParsingError(e)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
m.Date = d
|
m.Date = d
|
||||||
|
|
||||||
for meta := range ch {
|
for meta := range ch {
|
||||||
|
|
@ -264,7 +267,7 @@ func (e *Entry) UnmarshalJSON(in []byte) error {
|
||||||
if (!ok || dates == "") && !e.skipMissing {
|
if (!ok || dates == "") && !e.skipMissing {
|
||||||
return ErrorMissingDate
|
return ErrorMissingDate
|
||||||
}
|
}
|
||||||
date, err := time.Parse(time.RFC3339, dates)
|
date, err := tools.ParseDate(dates)
|
||||||
if err != nil && !e.skipMissing {
|
if err != nil && !e.skipMissing {
|
||||||
return newParsingError(err)
|
return newParsingError(err)
|
||||||
}
|
}
|
||||||
|
|
@ -274,7 +277,7 @@ func (e *Entry) UnmarshalJSON(in []byte) error {
|
||||||
if m.Key == "title" || m.Key == "date" {
|
if m.Key == "title" || m.Key == "date" {
|
||||||
continue
|
continue
|
||||||
} else if vs, ok := m.Value.(string); ok {
|
} else if vs, ok := m.Value.(string); ok {
|
||||||
if vd, err := time.Parse(time.RFC3339, vs); err == nil {
|
if vd, err := tools.ParseDate(vs); err == nil {
|
||||||
m.Value = vd
|
m.Value = vd
|
||||||
} else {
|
} else {
|
||||||
m.Value = vs
|
m.Value = vs
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Type assertions
|
// Type assertions
|
||||||
|
|
@ -17,6 +18,19 @@ var _ encoding.TextUnmarshaler = new(Entry)
|
||||||
var _ json.Marshaler = Entry{}
|
var _ json.Marshaler = Entry{}
|
||||||
var _ json.Unmarshaler = new(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) {
|
func TestEntryMarshal(t *testing.T) {
|
||||||
when := time.Now()
|
when := time.Now()
|
||||||
whens := when.Format(DateFormat)
|
whens := when.Format(DateFormat)
|
||||||
|
|
@ -77,12 +91,12 @@ func getEntryMarshalTestRunner(title string, date time.Time, fields []Meta, firs
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(lines) == 0 {
|
if len(lines) == 0 {
|
||||||
assert.Equal(t, first, string(o))
|
assert.Equal(t, "\n"+first, string(o))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
os := string(o)
|
os := string(o)
|
||||||
assert.Regexp(t, "^"+first, os)
|
assert.Regexp(t, "^\n"+first, os)
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
assert.Regexp(t, "(?m)^"+line, os)
|
assert.Regexp(t, "(?m)^"+line, os)
|
||||||
}
|
}
|
||||||
|
|
@ -266,7 +280,7 @@ func TestEntryJsonUnmarshal(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"date-field",
|
"date-field",
|
||||||
`{"title":"A Title","date":"` + whens + `","posted":"` + when.Add(-time.Hour).Format(time.RFC3339) + `"}`,
|
`{"title":"A Title","date":"` + whens + `","posted":"` + when.Add(-time.Hour).In(time.UTC).Format(time.RFC3339) + `"}`,
|
||||||
"A Title",
|
"A Title",
|
||||||
when,
|
when,
|
||||||
[]Meta{{"posted", when.Add(-time.Hour)}},
|
[]Meta{{"posted", when.Add(-time.Hour)}},
|
||||||
|
|
@ -310,6 +324,7 @@ func getEntryJsonUnmarshalTestRunner(in, title string, date time.Time, fields []
|
||||||
assert.Len(t, e.Fields, len(fields))
|
assert.Len(t, e.Fields, len(fields))
|
||||||
for _, f := range fields {
|
for _, f := range fields {
|
||||||
got := false
|
got := false
|
||||||
|
fTime, isTime := f.Value.(time.Time)
|
||||||
for _, m := range e.Fields {
|
for _, m := range e.Fields {
|
||||||
var mVal any = m.Value
|
var mVal any = m.Value
|
||||||
var fVal any = f.Value
|
var fVal any = f.Value
|
||||||
|
|
@ -323,6 +338,13 @@ func getEntryJsonUnmarshalTestRunner(in, title string, date time.Time, fields []
|
||||||
got = true
|
got = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
if isTime && m.Key == f.Key {
|
||||||
|
mTime, _ := mVal.(time.Time)
|
||||||
|
if assert.WithinRange(t, mTime, fTime.Add(-2*time.Second), fTime.Add(2*time.Second)) {
|
||||||
|
got = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
assert.Truef(t, got, "Couldn't find field %+v. We have %+v", f, e.Fields)
|
assert.Truef(t, got, "Couldn't find field %+v. We have %+v", f, e.Fields)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func ParseBytes(in []byte) any {
|
func ParseBytes(in []byte) any {
|
||||||
|
|
@ -34,7 +33,7 @@ func ParseString(in string) any {
|
||||||
return i
|
return i
|
||||||
} else if f, err := strconv.ParseFloat(s, 64); err == nil {
|
} else if f, err := strconv.ParseFloat(s, 64); err == nil {
|
||||||
return f
|
return f
|
||||||
} else if t, err := time.Parse(time.RFC3339, s); err == nil {
|
} else if t, err := ParseDate(s); err == nil {
|
||||||
return t
|
return t
|
||||||
} else if err := json.Unmarshal([]byte(s), &j); err == nil {
|
} else if err := json.Unmarshal([]byte(s), &j); err == nil {
|
||||||
return j
|
return j
|
||||||
|
|
|
||||||
57
tools/parse_date.go
Normal file
57
tools/parse_date.go
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
package tools
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
dp "github.com/markusmobius/go-dateparser"
|
||||||
|
"github.com/markusmobius/go-dateparser/date"
|
||||||
|
)
|
||||||
|
|
||||||
|
const DateFormat = "January 02, 2006 at 03:04:05PM -0700"
|
||||||
|
|
||||||
|
// These are somewhat arbitrary, but reasonably useful min and max times
|
||||||
|
var (
|
||||||
|
MinTime = time.Unix(-2208988800, 0) // Jan 1, 1900
|
||||||
|
MaxTime = MinTime.Add(1<<63 - 1)
|
||||||
|
)
|
||||||
|
|
||||||
|
func ParseDate(in string) (t time.Time, err error) {
|
||||||
|
if in == "min" {
|
||||||
|
return MinTime, nil
|
||||||
|
}
|
||||||
|
if in == "max" {
|
||||||
|
return MaxTime, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
conf := dp.Configuration{
|
||||||
|
CurrentTime: time.Now().Local(),
|
||||||
|
ReturnTimeAsPeriod: true,
|
||||||
|
Languages: []string{"en"},
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
case date.Minute:
|
||||||
|
t = t.Truncate(time.Minute)
|
||||||
|
case date.Hour:
|
||||||
|
t = time.Date(y, mon, day, h, 0, 0, 0, loc)
|
||||||
|
case date.Day:
|
||||||
|
t = time.Date(y, mon, day, 0, 0, 0, 0, loc)
|
||||||
|
case date.Month:
|
||||||
|
t = time.Date(y, mon, 1, 0, 0, 0, 0, loc)
|
||||||
|
case date.Year:
|
||||||
|
t = time.Date(y, 1, 1, 0, 0, 0, 0, loc)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
68
tools/parse_date_test.go
Normal file
68
tools/parse_date_test.go
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
package tools
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
const day = time.Hour * 24
|
||||||
|
|
||||||
|
func TestParseDate(t *testing.T) {
|
||||||
|
now := time.Now().Local()
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
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))
|
||||||
|
var ts int64 = 1708876012
|
||||||
|
tsd := time.Unix(ts, 0)
|
||||||
|
ent := "February 25, 2024 at 04:00:13AM +0230"
|
||||||
|
entd, _ := time.Parse(DateFormat, ent)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
exp time.Time
|
||||||
|
err string
|
||||||
|
}{
|
||||||
|
{"now", sec, ""},
|
||||||
|
{"today", today, ""},
|
||||||
|
{"tomorrow", tomorrow, ""},
|
||||||
|
{"yesterday", yesterday, ""},
|
||||||
|
{"in two minutes", twoMin, ""},
|
||||||
|
{"in two hours", twoHour, ""},
|
||||||
|
{"this month", firstMonth, ""},
|
||||||
|
{"this year", firstYear, ""},
|
||||||
|
{"min", MinTime, ""},
|
||||||
|
{"max", MaxTime, ""},
|
||||||
|
{exact, exactd, ""},
|
||||||
|
{fmt.Sprint(ts), tsd, ""},
|
||||||
|
{ent, entd, ""},
|
||||||
|
{"not a date", now, fmt.Sprintf(`failed to parse "%s": unknown format`, "not a date")},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, getDateTest(tt.name, tt.exp, tt.err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDateTest(in string, exp time.Time, err string) func(t *testing.T) {
|
||||||
|
return func(t *testing.T) {
|
||||||
|
out, er := ParseDate(in)
|
||||||
|
if err != "" {
|
||||||
|
assert.ErrorContains(t, er, err)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, er)
|
||||||
|
|
||||||
|
assert.Equal(t, exp, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -10,6 +10,7 @@ import (
|
||||||
|
|
||||||
func TestParse(t *testing.T) {
|
func TestParse(t *testing.T) {
|
||||||
when := time.Now()
|
when := time.Now()
|
||||||
|
now := when.Local().Truncate(time.Second)
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
in string
|
in string
|
||||||
|
|
@ -22,6 +23,8 @@ func TestParse(t *testing.T) {
|
||||||
{"false", "false", false},
|
{"false", "false", false},
|
||||||
{"nil", "nil", nil},
|
{"nil", "nil", nil},
|
||||||
{"time", when.Format(time.RFC3339), when},
|
{"time", when.Format(time.RFC3339), when},
|
||||||
|
{"now", "now", now},
|
||||||
|
{"DateFormat", now.Format(DateFormat), now},
|
||||||
{"json-obj", `{"foo":"bar","baz":"quux"}`, json.RawMessage(`{"foo":"bar","baz":"quux"}`)},
|
{"json-obj", `{"foo":"bar","baz":"quux"}`, json.RawMessage(`{"foo":"bar","baz":"quux"}`)},
|
||||||
{"json-arr", `["foo",42,"bar", null,"quux", true]`, json.RawMessage(`["foo",42,"bar", null,"quux", true]`)},
|
{"json-arr", `["foo",42,"bar", null,"quux", true]`, json.RawMessage(`["foo",42,"bar", null,"quux", true]`)},
|
||||||
{"empty", "", ""},
|
{"empty", "", ""},
|
||||||
|
|
@ -48,7 +51,7 @@ func getParseTestRunner(in string, exp any) func(*testing.T) {
|
||||||
if expT, ok := exp.(time.Time); ok {
|
if expT, ok := exp.(time.Time); ok {
|
||||||
ti, gotTime := out.(time.Time)
|
ti, gotTime := out.(time.Time)
|
||||||
if assert.True(t, gotTime, "Should have gotten a time.Time, but didn't") {
|
if assert.True(t, gotTime, "Should have gotten a time.Time, but didn't") {
|
||||||
assert.WithinRange(t, expT, ti.Add(-time.Second), ti.Add(time.Second))
|
assert.WithinRange(t, expT, ti.Add(-2*time.Second), ti.Add(2*time.Second))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
assert.Equal(t, exp, out)
|
assert.Equal(t, exp, out)
|
||||||
|
|
@ -57,7 +60,7 @@ func getParseTestRunner(in string, exp any) func(*testing.T) {
|
||||||
if expT, ok := exp.(time.Time); ok {
|
if expT, ok := exp.(time.Time); ok {
|
||||||
ti, gotTime := out.(time.Time)
|
ti, gotTime := out.(time.Time)
|
||||||
if assert.True(t, gotTime, "Should have gotten a time.Time, but didn't") {
|
if assert.True(t, gotTime, "Should have gotten a time.Time, but didn't") {
|
||||||
assert.WithinRange(t, expT, ti.Add(-time.Second), ti.Add(time.Second))
|
assert.WithinRange(t, expT, ti.Add(-2*time.Second), ti.Add(2*time.Second))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
assert.Equal(t, exp, out)
|
assert.Equal(t, exp, out)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue