✨ 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