ParseDate should handle DateFormat

This commit is contained in:
Dan Jones 2024-02-25 12:36:43 -06:00
commit 391452e3d9
6 changed files with 17 additions and 940 deletions

2
.gitignore vendored
View file

@ -123,3 +123,5 @@ Temporary Items
# End of https://www.toptal.com/developers/gitignore/api/go,linux,emacs,macos
my-log
cover.html
cmd/test.go

View file

@ -1,73 +0,0 @@
/*
Copyright © 2024 Dan Jones <danjones@goodevilgenius.org>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cmd
import (
"fmt"
"codeberg.org/danjones000/my-log/config"
"github.com/spf13/cobra"
// "github.com/BurntSushi/toml"
dp "github.com/markusmobius/go-dateparser"
)
// testCmd represents the test command
var testCmd = &cobra.Command{
Use: "test",
Short: "A brief description of your command",
//Long: ``,
Run: func(cmd *cobra.Command, args []string) {
c, err := config.Load()
fmt.Println("error", err)
fmt.Printf("%+v\n", c)
//ne := "output.stdout.config.json = true"
//toml.Decode(ne, &c)
st, _ := c.Outputs.Stdout()
fmt.Printf("%+v\n", st)
//nc := "\n[input]\nrecurse = false\n[output.stdout.config]\njson = true"
//toml.Decode(nc, &c)
//fmt.Printf("%+v\n", c)
d, err := dp.Parse(nil, "now")
fmt.Println(d.Time, d.Period)
d, err = dp.Parse(nil, "today")
fmt.Println(d.Time, d.Period)
d, err = dp.Parse(nil, "2 days, 3 minutes ago")
fmt.Println(d.Time, d.Period)
d, err = dp.Parse(nil, "in 2 decades, 5 days, 3 minutes")
fmt.Println(d.Time, d.Period)
d, err = dp.Parse(nil, "3 years ago")
fmt.Println(d.Time, d.Period)
d, err = dp.Parse(nil, "1707711800")
fmt.Println(d.Time, d.Period)
d, err = dp.Parse(nil, "@1707711800")
fmt.Println(d.Time, d.Period)
},
}
func init() {
rootCmd.AddCommand(testCmd)
// Here you will define your flags and configuration settings.
// Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.:
// testCmd.PersistentFlags().String("foo", "", "A help for foo")
// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
// testCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}

View file

@ -1,863 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>config: Go Coverage Report</title>
<style>
body {
background: black;
color: rgb(80, 80, 80);
}
body, pre, #legend span {
font-family: Menlo, monospace;
font-weight: bold;
}
#topbar {
background: black;
position: fixed;
top: 0; left: 0; right: 0;
height: 42px;
border-bottom: 1px solid rgb(80, 80, 80);
}
#content {
margin-top: 50px;
}
#nav, #legend {
float: left;
margin-left: 10px;
}
#legend {
margin-top: 12px;
}
#nav {
margin-top: 10px;
}
#legend span {
margin: 0 5px;
}
.cov0 { color: rgb(192, 0, 0) }
.cov1 { color: rgb(128, 128, 128) }
.cov2 { color: rgb(116, 140, 131) }
.cov3 { color: rgb(104, 152, 134) }
.cov4 { color: rgb(92, 164, 137) }
.cov5 { color: rgb(80, 176, 140) }
.cov6 { color: rgb(68, 188, 143) }
.cov7 { color: rgb(56, 200, 146) }
.cov8 { color: rgb(44, 212, 149) }
.cov9 { color: rgb(32, 224, 152) }
.cov10 { color: rgb(20, 236, 155) }
</style>
</head>
<body>
<div id="topbar">
<div id="nav">
<select id="files">
<option value="file0">codeberg.org/danjones000/my-log/config/default.go (100.0%)</option>
<option value="file1">codeberg.org/danjones000/my-log/config/load.go (97.4%)</option>
<option value="file2">codeberg.org/danjones000/my-log/files/append.go (94.7%)</option>
<option value="file3">codeberg.org/danjones000/my-log/models/entry.go (100.0%)</option>
<option value="file4">codeberg.org/danjones000/my-log/models/errors.go (100.0%)</option>
<option value="file5">codeberg.org/danjones000/my-log/models/log.go (100.0%)</option>
<option value="file6">codeberg.org/danjones000/my-log/models/meta.go (100.0%)</option>
<option value="file7">codeberg.org/danjones000/my-log/tools/parse.go (100.0%)</option>
<option value="file8">codeberg.org/danjones000/my-log/tools/parse_date.go (86.7%)</option>
</select>
</div>
<div id="legend">
<span>not tracked</span>
<span class="cov0">no coverage</span>
<span class="cov1">low coverage</span>
<span class="cov2">*</span>
<span class="cov3">*</span>
<span class="cov4">*</span>
<span class="cov5">*</span>
<span class="cov6">*</span>
<span class="cov7">*</span>
<span class="cov8">*</span>
<span class="cov9">*</span>
<span class="cov10">high coverage</span>
</div>
</div>
<div id="content">
<pre class="file" id="file0" style="display: none">package config
import (
"fmt"
"os"
fp "path/filepath"
"github.com/BurntSushi/toml"
)
const ConfigStr = `# Configuration for my-log
[input]
# Path to where the log files are stored
path = "%s"
# File extension for log files
ext = "txt"
# Whether to look in sub-folders
recurse = true
# config for output types
[output]
# This one just prints the logs to stdout when run
[output.stdout]
enabled = true
[output.stdout.config]
# Whether to output as JSON. Maybe useful to pipe elsewhere.
json = false
`
func DefaultStr() string <span class="cov10" title="8">{
home, _ := os.UserHomeDir()
inDir := fp.Join(home, "my-log")
return fmt.Sprintf(ConfigStr, inDir)
}</span>
func DefaultConfig() (Config, error) <span class="cov10" title="8">{
s := DefaultStr()
c := Config{}
_, err := toml.Decode(s, &amp;c)
return c, err
}</span>
</pre>
<pre class="file" id="file1" style="display: none">package config
import (
"encoding/json"
"fmt"
"os"
fp "path/filepath"
"time"
"codeberg.org/danjones000/my-log/tools"
"github.com/BurntSushi/toml"
"github.com/caarlos0/env/v10"
mapst "github.com/mitchellh/mapstructure"
)
var ConfigPath string
var Overrides = map[string]string{}
func init() <span class="cov1" title="1">{
conf, _ := os.UserConfigDir()
ConfigPath = fp.Join(conf, "my-log", "config.toml")
}</span>
func Load() (Config, error) <span class="cov10" title="6">{
c, _ := DefaultConfig()
_, err := os.Stat(ConfigPath)
if !os.IsNotExist(err) </span><span class="cov4" title="2">{
_, err = toml.DecodeFile(ConfigPath, &amp;c)
if err != nil </span><span class="cov1" title="1">{
return c, err
}</span>
}
<span class="cov9" title="5">env.Parse(&amp;c)
c.Outputs["stdout"] = loadStdout(c.Outputs["stdout"])
l := ""
for k, v := range Overrides </span><span class="cov7" title="4">{
val := tools.ParseString(v)
if val == nil </span><span class="cov1" title="1">{
continue</span>
}
<span class="cov6" title="3">if _, isJson := val.(json.RawMessage); isJson </span><span class="cov4" title="2">{
continue</span>
}
<span class="cov1" title="1">valout := fmt.Sprintf("%v", val)
if vals, isString := val.(string); isString </span><span class="cov1" title="1">{
valout = fmt.Sprintf(`"%s"`, vals)
}</span>
<span class="cov1" title="1">if valt, isTime := val.(time.Time); isTime </span><span class="cov0" title="0">{
valout = valt.Format(time.RFC3339)
}</span>
<span class="cov1" title="1">l = l + "\n" + fmt.Sprintf("%s = %s", k, valout)</span>
}
<span class="cov9" title="5">_, err = toml.Decode(l, &amp;c)
return c, err</span>
}
func loadStdout(stdout Output) Output <span class="cov9" title="5">{
st := stdoutEnabled{stdout.Enabled}
env.Parse(&amp;st)
stdout.Enabled = st.Enabled
var std Stdout
mapst.Decode(stdout.Config, &amp;std)
env.Parse(&amp;std)
mapst.Decode(std, &amp;stdout.Config)
return stdout
}</span>
func (oo Outputs) Stdout() (s Stdout, enabled bool) <span class="cov4" title="2">{
o, ok := oo["stdout"]
if !ok </span><span class="cov1" title="1">{
return s, false
}</span>
<span class="cov1" title="1">enabled = o.Enabled
mapst.Decode(o.Config, &amp;s)
return</span>
}
</pre>
<pre class="file" id="file2" style="display: none">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 <span class="cov10" title="4">{
conf, err := config.Load()
if err != nil </span><span class="cov1" title="1">{
return err
}</span>
<span class="cov8" title="3">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 </span><span class="cov1" title="1">{
return err
}</span>
<span class="cov5" title="2">f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0640)
if err != nil </span><span class="cov1" title="1">{
return err
}</span>
<span class="cov1" title="1">defer f.Close()
for _, e := range l.Entries </span><span class="cov1" title="1">{
by, err := e.MarshalText()
if err != nil </span><span class="cov0" title="0">{
continue</span>
}
<span class="cov1" title="1">f.Write(by)</span>
}
<span class="cov1" title="1">return nil</span>
}
</pre>
<pre class="file" id="file3" style="display: none">package models
import (
"bufio"
"bytes"
"encoding/json"
"errors"
"regexp"
"strings"
"sync"
"time"
)
const DateFormat = "January 02, 2006 at 03:04:05PM -0700"
type Entry struct {
Title string
Date time.Time
Fields []Meta
skipMissing bool
}
func PartialEntry() Entry <span class="cov1" title="1">{
return Entry{skipMissing: true}
}</span>
type metaRes struct {
out []byte
err error
}
func (e Entry) getFieldMarshalChan() chan metaRes <span class="cov3" title="4">{
size := len(e.Fields)
ch := make(chan metaRes, size)
var wg sync.WaitGroup
for i := 0; i &lt; size; i++ </span><span class="cov4" title="5">{
wg.Add(1)
go func(m Meta) </span><span class="cov4" title="5">{
defer wg.Done()
if m.Key == "json" </span><span class="cov1" title="1">{
if j, ok := m.Value.(json.RawMessage); ok </span><span class="cov1" title="1">{
sub := Entry{skipMissing: true}
json.Unmarshal(j, &amp;sub)
for _, subM := range sub.Fields </span><span class="cov3" title="3">{
o, er := subM.MarshalText()
ch &lt;- metaRes{o, er}
}</span>
}
} else<span class="cov3" title="4"> {
o, er := m.MarshalText()
ch &lt;- metaRes{o, er}
}</span>
}(e.Fields[i])
}
<span class="cov3" title="4">go func() </span><span class="cov3" title="4">{
wg.Wait()
close(ch)
}</span>()
<span class="cov3" title="4">return ch</span>
}
func (e Entry) MarshalText() ([]byte, error) <span class="cov4" title="6">{
e.Title = strings.TrimSpace(e.Title)
if e.Title == "" </span><span class="cov1" title="1">{
return []byte{}, ErrorMissingTitle
}</span>
<span class="cov4" title="5">if e.Date == (time.Time{}) </span><span class="cov1" title="1">{
return []byte{}, ErrorMissingDate
}</span>
<span class="cov3" title="4">ch := e.getFieldMarshalChan()
buff := &amp;bytes.Buffer{}
buff.WriteString("\n@begin ")
buff.WriteString(e.Date.Format(DateFormat))
buff.WriteString(" - ")
buff.WriteString(e.Title)
for res := range ch </span><span class="cov5" title="7">{
if res.err == nil &amp;&amp; len(res.out) &gt; 0 </span><span class="cov5" title="7">{
buff.WriteString("\n")
buff.Write(res.out)
}</span>
}
<span class="cov3" title="4">buff.WriteString(" @end")
return buff.Bytes(), nil</span>
}
func (m *Entry) UnmarshalText(in []byte) error <span class="cov7" title="18">{
re := regexp.MustCompile("(?s)^@begin (.+) - (.+?)[ \n]@")
match := re.FindSubmatch(in)
if len(match) == 0 </span><span class="cov2" title="2">{
return newParsingError(errors.New("Failed to find title and date"))
}</span>
<span class="cov6" title="16">ch := m.getFieldUnarshalChan(in)
title := bytes.TrimSpace(match[2])
if len(title) == 0 </span><span class="cov1" title="1">{
return ErrorMissingTitle
}</span>
<span class="cov6" title="15">m.Title = string(title)
date := string(bytes.TrimSpace(match[1]))
if date == "" </span><span class="cov1" title="1">{
return ErrorMissingDate
}</span>
<span class="cov6" title="14">d, e := time.Parse(time.RFC3339, date)
if e != nil </span><span class="cov6" title="11">{
d, e = time.Parse(DateFormat, date)
if e != nil </span><span class="cov1" title="1">{
return newParsingError(e)
}</span>
}
<span class="cov6" title="13">m.Date = d
for meta := range ch </span><span class="cov6" title="11">{
m.Fields = append(m.Fields, meta)
}</span>
<span class="cov6" title="13">return nil</span>
}
func scanEntry(data []byte, atEOF bool) (advance int, token []byte, err error) <span class="cov10" title="65">{
if atEOF &amp;&amp; len(data) == 0 </span><span class="cov7" title="17">{
return 0, nil, nil
}</span>
<span class="cov9" title="48">if i := bytes.Index(data, []byte{10, 64}); i &gt; 0 </span><span class="cov6" title="14">{
return i + 1, data[0:i], nil
}</span>
<span class="cov8" title="34">if atEOF </span><span class="cov7" title="17">{
end := []byte{32, 64, 101, 110, 100}
token = data
if i := bytes.Index(data, end); i &gt;= 0 </span><span class="cov6" title="15">{
token = data[0:i]
}</span>
<span class="cov7" title="17">return len(data), token, nil</span>
}
// Request more data.
<span class="cov7" title="17">return 0, nil, nil</span>
}
func (e *Entry) getFieldUnarshalChan(in []byte) chan Meta <span class="cov6" title="16">{
size := len(in) / 3 // rough estimation
ch := make(chan Meta, size)
var wg sync.WaitGroup
read := bytes.NewReader(in)
scan := bufio.NewScanner(read)
scan.Split(scanEntry)
scan.Scan() // throw out first line
for scan.Scan() </span><span class="cov6" title="12">{
wg.Add(1)
go func(field []byte) </span><span class="cov6" title="12">{
defer wg.Done()
m := new(Meta)
err := m.UnmarshalText(field)
if err == nil </span><span class="cov5" title="10">{
if m.Key == "json" </span><span class="cov1" title="1">{
if j, ok := m.Value.(json.RawMessage); ok </span><span class="cov1" title="1">{
sub := Entry{skipMissing: true}
json.Unmarshal(j, &amp;sub)
for _, subM := range sub.Fields </span><span class="cov2" title="2">{
ch &lt;- subM
}</span>
}
} else<span class="cov5" title="9"> {
ch &lt;- *m
}</span>
}
}(scan.Bytes())
}
<span class="cov6" title="16">go func() </span><span class="cov6" title="16">{
wg.Wait()
close(ch)
}</span>()
<span class="cov6" title="16">return ch</span>
}
func (e Entry) MarshalJSON() ([]byte, error) <span class="cov6" title="11">{
if e.Title == "" </span><span class="cov1" title="1">{
return []byte{}, ErrorMissingTitle
}</span>
<span class="cov5" title="10">if e.Date == (time.Time{}) </span><span class="cov1" title="1">{
return []byte{}, ErrorMissingDate
}</span>
<span class="cov5" title="9">out := map[string]any{}
out["title"] = e.Title
out["date"] = e.Date.Format(time.RFC3339)
for _, f := range e.Fields </span><span class="cov5" title="10">{
if _, ok := out[f.Key]; !ok </span><span class="cov5" title="7">{
if f.Key == "json" </span><span class="cov1" title="1">{
ob := map[string]any{}
if j, ok := f.Value.(json.RawMessage); ok </span><span class="cov1" title="1">{
json.Unmarshal(j, &amp;ob)
}</span>
// If we couldn't get valid data from there, this will just be empty
<span class="cov1" title="1">for k, v := range ob </span><span class="cov3" title="3">{
if k != "title" &amp;&amp; k != "date" </span><span class="cov3" title="3">{
out[k] = v
}</span>
}
} else<span class="cov4" title="6"> {
out[f.Key] = f.Value
if vt, ok := f.Value.(time.Time); ok </span><span class="cov1" title="1">{
out[f.Key] = vt.Format(time.RFC3339)
}</span>
}
}
}
<span class="cov5" title="9">return json.Marshal(out)</span>
}
func (e *Entry) unmarshalJsonChanHelper(m map[string]any, ch chan Meta, wg *sync.WaitGroup) <span class="cov6" title="11">{
for k, v := range m </span><span class="cov8" title="32">{
wg.Add(1)
go func(key string, value any) </span><span class="cov8" title="32">{
defer wg.Done()
if key != "json" </span><span class="cov8" title="30">{
ch &lt;- Meta{key, value}
return
}</span>
<span class="cov2" title="2">subM := map[string]any{}
if s, ok := value.(string); ok </span><span class="cov1" title="1">{
dec := json.NewDecoder(strings.NewReader(s))
dec.UseNumber()
dec.Decode(&amp;subM)
}</span> else<span class="cov1" title="1"> {
subM = value.(map[string]any)
}</span>
<span class="cov2" title="2">e.unmarshalJsonChanHelper(subM, ch, wg)</span>
}(k, v)
}
}
func (e *Entry) getUnmarshalJsonChan(m map[string]any) chan Meta <span class="cov5" title="9">{
ch := make(chan Meta, len(m))
var wg sync.WaitGroup
e.unmarshalJsonChanHelper(m, ch, &amp;wg)
go func() </span><span class="cov5" title="9">{
wg.Wait()
close(ch)
}</span>()
<span class="cov5" title="9">return ch</span>
}
func (e *Entry) UnmarshalJSON(in []byte) error <span class="cov6" title="15">{
out := map[string]any{}
dec := json.NewDecoder(bytes.NewReader(in))
dec.UseNumber()
err := dec.Decode(&amp;out)
if err != nil </span><span class="cov1" title="1">{
return newParsingError(err)
}</span>
<span class="cov6" title="14">title, ok := out["title"].(string)
if (!ok || title == "") &amp;&amp; !e.skipMissing </span><span class="cov2" title="2">{
return ErrorMissingTitle
}</span>
<span class="cov6" title="12">e.Title = title
dates, ok := out["date"].(string)
if (!ok || dates == "") &amp;&amp; !e.skipMissing </span><span class="cov2" title="2">{
return ErrorMissingDate
}</span>
<span class="cov5" title="10">date, err := time.Parse(time.RFC3339, dates)
if err != nil &amp;&amp; !e.skipMissing </span><span class="cov1" title="1">{
return newParsingError(err)
}</span>
<span class="cov5" title="9">e.Date = date
ch := e.getUnmarshalJsonChan(out)
for m := range ch </span><span class="cov8" title="30">{
if m.Key == "title" || m.Key == "date" </span><span class="cov6" title="12">{
continue</span>
} else<span class="cov7" title="18"> if vs, ok := m.Value.(string); ok </span><span class="cov5" title="7">{
if vd, err := time.Parse(time.RFC3339, vs); err == nil </span><span class="cov1" title="1">{
m.Value = vd
}</span> else<span class="cov4" title="6"> {
m.Value = vs
}</span>
} else<span class="cov6" title="11"> if n, ok := m.Value.(json.Number); ok </span><span class="cov4" title="6">{
it, _ := n.Int64()
fl, _ := n.Float64()
if float64(it) == fl </span><span class="cov4" title="5">{
m.Value = it
}</span> else<span class="cov1" title="1"> {
m.Value = fl
}</span>
}
<span class="cov7" title="18">e.Fields = append(e.Fields, m)</span>
}
<span class="cov5" title="9">return nil</span>
}
</pre>
<pre class="file" id="file4" style="display: none">package models
import (
"errors"
"fmt"
)
var ErrorMissingTitle = errors.New("Missing title")
var ErrorMissingDate = errors.New("Missing date")
var ErrorParsing = errors.New("Parsing Error")
func newParsingError(err error) error <span class="cov10" title="13">{
return fmt.Errorf("%w: %w", ErrorParsing, err)
}</span>
</pre>
<pre class="file" id="file5" style="display: none">package models
import (
"bufio"
"bytes"
"regexp"
"sync"
)
var reg = regexp.MustCompile("(?sm)^@begin .+?(^| )@end")
type Log struct {
Name string
Entries []Entry
}
func (l *Log) UnmarshalText(in []byte) error <span class="cov5" title="4">{
ch := l.getLogUnarshalChan(in)
for entry := range ch </span><span class="cov7" title="6">{
l.Entries = append(l.Entries, entry)
}</span>
<span class="cov5" title="4">return nil</span>
}
func scanLog(data []byte, atEOF bool) (advance int, token []byte, err error) <span class="cov10" title="14">{
if atEOF &amp;&amp; len(data) == 0 </span><span class="cov1" title="1">{
// done
return 0, nil, nil
}</span>
<span class="cov9" title="13">m := reg.FindIndex(data)
if len(m) == 0 &amp;&amp; atEOF </span><span class="cov4" title="3">{
// all trash
return len(data), nil, nil
}</span> else<span class="cov8" title="10"> if len(m) == 0 &amp;&amp; !atEOF </span><span class="cov4" title="3">{
// get more
return 0, nil, nil
}</span>
<span class="cov7" title="7">return m[1], data[m[0]:m[1]], nil</span>
}
func (l *Log) getLogUnarshalChan(in []byte) chan Entry <span class="cov5" title="4">{
size := len(in) / 10 // rough estimation
ch := make(chan Entry, size)
var wg sync.WaitGroup
read := bytes.NewReader(in)
scan := bufio.NewScanner(read)
scan.Split(scanLog)
for scan.Scan() </span><span class="cov7" title="7">{
wg.Add(1)
go func(field []byte) </span><span class="cov7" title="7">{
defer wg.Done()
f := new(Entry)
err := f.UnmarshalText(field)
if err != nil </span><span class="cov1" title="1">{
return
}</span>
<span class="cov7" title="6">ch &lt;- *f</span>
}(scan.Bytes())
}
<span class="cov5" title="4">go func() </span><span class="cov5" title="4">{
wg.Wait()
close(ch)
}</span>()
<span class="cov5" title="4">return ch</span>
}
</pre>
<pre class="file" id="file6" style="display: none">package models
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"regexp"
"strconv"
"time"
"codeberg.org/danjones000/my-log/tools"
)
type Meta struct {
Key string
Value any
}
func (m Meta) MarshalText() ([]byte, error) <span class="cov8" title="23">{
if regexp.MustCompile(`\s`).MatchString(m.Key) </span><span class="cov1" title="1">{
return []byte{}, fmt.Errorf("whitespace is not allowed in key: %s", m.Key)
}</span>
<span class="cov8" title="22">buff := &amp;bytes.Buffer{}
buff.WriteRune('@')
buff.WriteString(m.Key)
buff.WriteRune(' ')
switch v := m.Value.(type) </span>{
default:<span class="cov1" title="1">
return nil, fmt.Errorf("Unknown type %T", v)</span>
case nil:<span class="cov1" title="1">
return []byte{}, nil</span>
case string:<span class="cov3" title="3">
buff.WriteString(v)</span>
case int:<span class="cov3" title="3">
buff.WriteString(strconv.Itoa(v))</span>
case int64:<span class="cov2" title="2">
buff.WriteString(strconv.FormatInt(v, 10))</span>
case float64:<span class="cov1" title="1">
buff.WriteString(strconv.FormatFloat(v, 'f', -1, 64))</span>
case json.Number:<span class="cov1" title="1">
buff.WriteString(v.String())</span>
case json.RawMessage:<span class="cov2" title="2">
buff.Write(v)</span>
case []byte:<span class="cov1" title="1">
buff.Write(v)</span>
case byte:<span class="cov1" title="1">
buff.WriteByte(v)</span>
case rune:<span class="cov1" title="1">
buff.WriteString(string(v))</span>
case bool:<span class="cov4" title="4">
buff.WriteString(strconv.FormatBool(v))</span>
case time.Time:<span class="cov1" title="1">
buff.WriteString(v.Format(time.RFC3339))</span>
}
<span class="cov8" title="20">return buff.Bytes(), nil</span>
}
func (m *Meta) UnmarshalText(in []byte) error <span class="cov10" title="39">{
if len(in) == 0 </span><span class="cov2" title="2">{
return newParsingError(errors.New("Unable to Unmarshal empty string"))
}</span>
<span class="cov9" title="37">re := regexp.MustCompile("(?s)^@([^ ]+) (.*)( @end)?$")
match := re.FindSubmatch(in)
if len(match) == 0 </span><span class="cov3" title="3">{
return newParsingError(fmt.Errorf("Failed to match %s", in))
}</span>
<span class="cov9" title="34">m.Key = string(match[1])
return m.processMeta(match[2])</span>
}
func (m *Meta) processMeta(in []byte) error <span class="cov9" title="34">{
if len(in) == 0 </span><span class="cov1" title="1">{
return newParsingError(errors.New("No value found"))
}</span>
<span class="cov9" title="33">v := tools.ParseBytes(in)
if v == "" </span><span class="cov2" title="2">{
return newParsingError(errors.New("No value found"))
}</span>
<span class="cov9" title="31">m.Value = v
return nil</span>
}
</pre>
<pre class="file" id="file7" style="display: none">package tools
import (
"encoding/json"
"regexp"
"strconv"
"strings"
"time"
)
func ParseBytes(in []byte) any <span class="cov8" title="20">{
return ParseString(string(in))
}</span>
func ParseString(in string) any <span class="cov10" title="40">{
s := strings.TrimSpace(in)
if s == "" </span><span class="cov5" title="6">{
return s
}</span>
<span class="cov9" title="34">yesno := regexp.MustCompile("^(y|Y|yes|Yes|YES|n|N|no|No|NO|true|True|TRUE|false|False|FALSE|on|On|ON|off|Off|OFF)$")
yes := regexp.MustCompile("^(y|Y|yes|Yes|YES|true|True|TRUE|on|On|ON)$")
null := regexp.MustCompile("^(~|null|Null|NULL|none|None|NONE|nil|Nil|NIL)$")
var j json.RawMessage
if null.MatchString(s) </span><span class="cov6" title="10">{
return nil
}</span> else<span class="cov8" title="24"> if yesno.MatchString(s) </span><span class="cov7" title="12">{
if yes.MatchString(s) </span><span class="cov5" title="6">{
return true
}</span> else<span class="cov5" title="6"> {
return false
}</span>
} else<span class="cov7" title="12"> if i, err := strconv.Atoi(s); err == nil </span><span class="cov2" title="2">{
return i
}</span> else<span class="cov6" title="10"> if f, err := strconv.ParseFloat(s, 64); err == nil </span><span class="cov2" title="2">{
return f
}</span> else<span class="cov6" title="8"> if t, err := time.Parse(time.RFC3339, s); err == nil </span><span class="cov2" title="2">{
return t
}</span> else<span class="cov5" title="6"> if err := json.Unmarshal([]byte(s), &amp;j); err == nil </span><span class="cov4" title="4">{
return j
}</span>
<span class="cov2" title="2">return s</span>
}
</pre>
<pre class="file" id="file8" style="display: none">package tools
import (
"time"
dp "github.com/markusmobius/go-dateparser"
"github.com/markusmobius/go-dateparser/date"
)
const (
day = time.Hour * 24
)
// These are somewhat arbitrary, but reasonably useful min and max times
var (
MinTime = time.Unix(-2208988800, 0) // Jan 1, 1900
MaxTime = MinTime.Add(1&lt;&lt;63 - 1)
)
func ParseDate(in string) (t time.Time, err error) <span class="cov10" title="7">{
if in == "min" </span><span class="cov1" title="1">{
return MinTime, nil
}</span>
<span class="cov9" title="6">if in == "max" </span><span class="cov1" title="1">{
return MaxTime, nil
}</span>
<span class="cov8" title="5">d, err := dp.Parse(nil, in)
if err != nil </span><span class="cov1" title="1">{
return
}</span>
<span class="cov7" title="4">t = d.Time.Local()
trunc := time.Second
switch d.Period </span>{
case date.Minute:<span class="cov0" title="0">
trunc = time.Minute</span>
case date.Hour:<span class="cov0" title="0">
trunc = time.Hour</span>
case date.Day:<span class="cov6" title="3">
trunc = day</span>
// @todo Handle other cases separately
}
<span class="cov7" title="4">t = t.Truncate(trunc)
return</span>
}
</pre>
</div>
</body>
<script>
(function() {
var files = document.getElementById('files');
var visible;
files.addEventListener('change', onChange, false);
function select(part) {
if (visible)
visible.style.display = 'none';
visible = document.getElementById(part);
if (!visible)
return;
files.value = part;
visible.style.display = 'block';
location.hash = part;
}
function onChange() {
select(files.value);
window.scrollTo(0, 0);
}
if (location.hash != "") {
select(location.hash.substr(1));
}
if (!visible) {
select("file0");
}
})();
</script>
</html>

View file

@ -9,9 +9,11 @@ import (
"strings"
"sync"
"time"
"codeberg.org/danjones000/my-log/tools"
)
const DateFormat = "January 02, 2006 at 03:04:05PM -0700"
const DateFormat = tools.DateFormat
type Entry struct {
Title string

View file

@ -7,6 +7,8 @@ import (
"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
@ -21,12 +23,16 @@ func ParseDate(in string) (t time.Time, err error) {
return MaxTime, nil
}
d, err := dp.Parse(&dp.Configuration{
conf := dp.Configuration{
CurrentTime: time.Now().Local(),
ReturnTimeAsPeriod: true,
}, in)
}
d, err := dp.Parse(&conf, in)
t = d.Time
if err != nil {
d, err = dp.Parse(&conf, in, DateFormat)
t = d.Time
return
}

View file

@ -23,9 +23,11 @@ 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", -2*60*60))
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
@ -44,6 +46,7 @@ func TestParseDate(t *testing.T) {
{"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 {