✨ Drop command
This commit is contained in:
parent
a062e71a29
commit
cc9e8f6167
6 changed files with 214 additions and 23 deletions
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"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
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
|
||||||
|
}
|
||||||
91
files/append_test.go
Normal file
91
files/append_test.go
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
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")
|
||||||
|
// @todo test file contents
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
@ -20,6 +20,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 +72,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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue