From 22387ec5a59c17400b72065db07df3c99090feb6 Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Fri, 3 May 2024 01:34:07 +0300 Subject: [PATCH 1/6] [fsutil] Code refactoring --- fsutil/fs.go | 58 ++++++++++++++++++++++++++++++++------------ fsutil/fs_windows.go | 42 +++++++++++++++++++++++++++++--- 2 files changed, 81 insertions(+), 19 deletions(-) diff --git a/fsutil/fs.go b/fsutil/fs.go index 359b00d9..cf64beda 100644 --- a/fsutil/fs.go +++ b/fsutil/fs.go @@ -51,7 +51,9 @@ var ErrEmptyPath = errors.New("Path is empty") // ////////////////////////////////////////////////////////////////////////////////// // -// CheckPerms checks many props at once +// CheckPerms checks many permissions at once +// +// Permissions: // // - F: is file // - D: is directory @@ -62,13 +64,13 @@ var ErrEmptyPath = errors.New("Path is empty") // - B: is block device // - C: is character device // - S: not empty (only for files) -func CheckPerms(props, path string) bool { - if props == "" || path == "" { +func CheckPerms(perms, path string) bool { + if perms == "" || path == "" { return false } path = PATH.Clean(path) - props = strings.ToUpper(props) + perms = strings.ToUpper(perms) var stat = &syscall.Stat_t{} @@ -84,7 +86,7 @@ func CheckPerms(props, path string) bool { return false } - for _, k := range props { + for _, k := range perms { switch k { case 'F': @@ -138,16 +140,28 @@ func CheckPerms(props, path string) bool { } // ValidatePerms validates permissions for file or directory -func ValidatePerms(props, path string) error { +// +// Permissions: +// +// - F: is file +// - D: is directory +// - X: is executable +// - L: is link +// - W: is writable +// - R: is readable +// - B: is block device +// - C: is character device +// - S: not empty (only for files) +func ValidatePerms(perms, path string) error { switch { - case props == "": + case perms == "": return errors.New("Props is empty") case path == "": return errors.New("Path is empty") } path = PATH.Clean(path) - props = strings.ToUpper(props) + perms = strings.ToUpper(perms) var stat = &syscall.Stat_t{} @@ -155,15 +169,15 @@ func ValidatePerms(props, path string) error { if err != nil { switch { - case strings.ContainsRune(props, 'F'): + case strings.ContainsRune(perms, 'F'): return fmt.Errorf("File %s doesn't exist or not accessible", path) - case strings.ContainsRune(props, 'D'): + case strings.ContainsRune(perms, 'D'): return fmt.Errorf("Directory %s doesn't exist or not accessible", path) - case strings.ContainsRune(props, 'B'): + case strings.ContainsRune(perms, 'B'): return fmt.Errorf("Block device %s doesn't exist or not accessible", path) - case strings.ContainsRune(props, 'C'): + case strings.ContainsRune(perms, 'C'): return fmt.Errorf("Character device %s doesn't exist or not accessible", path) - case strings.ContainsRune(props, 'L'): + case strings.ContainsRune(perms, 'L'): return fmt.Errorf("Link %s doesn't exist or not accessible", path) } @@ -176,7 +190,7 @@ func ValidatePerms(props, path string) error { return errors.New("Can't get information about the current user") } - for _, k := range props { + for _, k := range perms { switch k { case 'F': @@ -239,7 +253,19 @@ func ValidatePerms(props, path string) error { } // ProperPath returns the first proper path from a given slice -func ProperPath(props string, paths []string) string { +// +// Permissions: +// +// - F: is file +// - D: is directory +// - X: is executable +// - L: is link +// - W: is writable +// - R: is readable +// - B: is block device +// - C: is character device +// - S: not empty (only for files) +func ProperPath(perms string, paths []string) string { for _, path := range paths { if strings.Trim(path, " ") == "" { continue @@ -247,7 +273,7 @@ func ProperPath(props string, paths []string) string { path = PATH.Clean(path) - if CheckPerms(props, path) { + if CheckPerms(perms, path) { return path } } diff --git a/fsutil/fs_windows.go b/fsutil/fs_windows.go index bca4b87a..9d3c5253 100644 --- a/fsutil/fs_windows.go +++ b/fsutil/fs_windows.go @@ -50,20 +50,56 @@ var ErrEmptyPath = errors.New("Path is empty") // ////////////////////////////////////////////////////////////////////////////////// // -// ❗ CheckPerms check many props at once +// ❗ CheckPerms checks many permissions at once +// +// Permissions: +// +// - F: is file +// - D: is directory +// - X: is executable +// - L: is link +// - W: is writable +// - R: is readable +// - B: is block device +// - C: is character device +// - S: not empty (only for files) func CheckPerms(perms, path string) bool { panic("UNSUPPORTED") return false } // ❗ ValidatePerms validates permissions for file or directory -func ValidatePerms(props, path string) error { +// +// Permissions: +// +// - F: is file +// - D: is directory +// - X: is executable +// - L: is link +// - W: is writable +// - R: is readable +// - B: is block device +// - C: is character device +// - S: not empty (only for files) +func ValidatePerms(perms, path string) error { panic("UNSUPPORTED") return nil } // ❗ ProperPath returns the first proper path from a given slice -func ProperPath(props string, paths []string) string { +// +// Permissions: +// +// - F: is file +// - D: is directory +// - X: is executable +// - L: is link +// - W: is writable +// - R: is readable +// - B: is block device +// - C: is character device +// - S: not empty (only for files) +func ProperPath(perms string, paths []string) string { panic("UNSUPPORTED") return "" } From decfbc58861b61e4f8968c1169b8cc8637babc37 Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Sat, 4 May 2024 23:23:43 +0300 Subject: [PATCH 2/6] [csv] Improvements and new features --- CHANGELOG.md | 2 + csv/csv.go | 159 +++++++++++++++++++++++++++-- csv/csv_test.go | 103 +++++++++++++------ csv/examples_test.go | 235 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 457 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 86006ced..3c64a8a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ ### 12.121.0 - `[initsystem/sdnotify]` Added new package for sending messages to systemd +- `[csv]` Added helpers for working with CSV row +- `[csv]` Added option to skip header - `[support/deps]` Updated for compatibility with the latest version of [depsy](https://kaos.sh/depsy) - `[terminal/tty]` Improved check for systemd diff --git a/csv/csv.go b/csv/csv.go index d8dfce2e..aa7a3c50 100644 --- a/csv/csv.go +++ b/csv/csv.go @@ -10,8 +10,10 @@ package csv import ( "bufio" + "bytes" "errors" "io" + "strconv" "strings" ) @@ -19,10 +21,17 @@ import ( // Reader is CSV reader struct type Reader struct { - Comma rune - br *bufio.Reader + Comma rune + SkipHeader bool + + headerSkipped bool + + br *bufio.Reader } +// Row is CSV row +type Row []string + // ////////////////////////////////////////////////////////////////////////////////// // // ErrEmptyDest is returned by the ReadTo method if empty destination slice was given @@ -36,19 +45,26 @@ var ErrNilReader = errors.New("Reader is nil") // NewReader create new CSV reader func NewReader(r io.Reader) *Reader { return &Reader{ - Comma: ';', - br: bufio.NewReader(r), + Comma: ';', + SkipHeader: false, + + br: bufio.NewReader(r), } } // ////////////////////////////////////////////////////////////////////////////////// // // Read reads line from CSV file -func (r *Reader) Read() ([]string, error) { +func (r *Reader) Read() (Row, error) { if r == nil { return nil, ErrNilReader } + if r.SkipHeader && !r.headerSkipped { + r.br.ReadLine() + r.headerSkipped = true + } + str, _, err := r.br.ReadLine() if err != nil || len(str) == 0 { @@ -59,7 +75,7 @@ func (r *Reader) Read() ([]string, error) { } // ReadTo reads data to given slice -func (r *Reader) ReadTo(dst []string) error { +func (r *Reader) ReadTo(dst Row) error { if r == nil { return ErrNilReader } @@ -68,6 +84,11 @@ func (r *Reader) ReadTo(dst []string) error { return ErrEmptyDest } + if r.SkipHeader && !r.headerSkipped { + r.br.ReadLine() + r.headerSkipped = true + } + str, _, err := r.br.ReadLine() if err != nil { @@ -90,10 +111,128 @@ func (r *Reader) WithComma(comma rune) *Reader { return r } +// WithHeaderSkip sets header skip flag +func (r *Reader) WithHeaderSkip(flag bool) *Reader { + if r == nil { + return nil + } + + r.SkipHeader = flag + + return r +} + +// ////////////////////////////////////////////////////////////////////////////////// // + +// Size returns size of the row +func (r Row) Size() int { + return len(r) +} + +// Cells returns number of cells filled with data +func (r Row) Cells() int { + var size int + + for _, c := range r { + if len(c) > 0 { + size++ + } + } + + return size +} + +// IsEmpty returns true if all cells are empty +func (r Row) IsEmpty() bool { + return r.Cells() == 0 +} + +// Has returns true if row contains cell with given index filled with data +func (r Row) Has(index int) bool { + return index < len(r) && r[index] != "" +} + +// Get returns value of the cell with given index +func (r Row) Get(index int) string { + if index >= len(r) { + return "" + } + + return r[index] +} + +// GetB returns cell value as boolean +func (r Row) GetB(index int) bool { + switch strings.ToLower(r.Get(index)) { + case "1", "true", "t", "yes", "y", "+": + return true + } + + return false +} + +// GetI returns cell value as int +func (r Row) GetI(index int) (int, error) { + return strconv.Atoi(r.Get(index)) +} + +// GetF returns cell value as float +func (r Row) GetF(index int) (float64, error) { + return strconv.ParseFloat(r.Get(index), 10) +} + +// GetU returns cell value as uint64 +func (r Row) GetU(index int) (uint64, error) { + return strconv.ParseUint(r.Get(index), 10, 64) +} + +// ForEach executes given function for every cell in a row +func (r Row) ForEach(fn func(index int, value string) error) error { + for i, c := range r { + err := fn(i, c) + + if err != nil { + return err + } + } + + return nil +} + +// ToString returns string representation of row +func (r Row) ToString(comma rune) string { + var buf bytes.Buffer + + for i, c := range r { + buf.WriteString(c) + + if i+1 != len(r) { + buf.WriteRune(comma) + } + } + + return buf.String() +} + +// ToBytes returns representation of row as a byte slice +func (r Row) ToBytes(comma rune) []byte { + var buf bytes.Buffer + + for i, c := range r { + buf.WriteString(c) + + if i+1 != len(r) { + buf.WriteRune(comma) + } + } + + return buf.Bytes() +} + // ////////////////////////////////////////////////////////////////////////////////// // -// parseAndFill parses line -func parseAndFill(src string, dst []string, sep string) { +// parseAndFill parses CSV row and fills slice with data +func parseAndFill(src string, dst Row, sep string) { l := len(dst) if src == "" { @@ -124,12 +263,12 @@ func parseAndFill(src string, dst []string, sep string) { } if i < l-1 { - clean(dst, i, l) + clean(dst, i+1, l) } } // clean cleans destination slice -func clean(dst []string, since, to int) { +func clean(dst Row, since, to int) { for i := since; i < to; i++ { dst[i] = "" } diff --git a/csv/csv_test.go b/csv/csv_test.go index e7f3a53c..b958805d 100644 --- a/csv/csv_test.go +++ b/csv/csv_test.go @@ -8,6 +8,7 @@ package csv // ////////////////////////////////////////////////////////////////////////////////// // import ( + "errors" "io" "os" "testing" @@ -17,9 +18,11 @@ import ( // ////////////////////////////////////////////////////////////////////////////////// // -const _DATA = `123,ABC,A_C,A C, -123,ABC, -123,ABC,A_C,A C,123,ABC,A_C,A C +const _DATA = `ID,FIRST NAME,LAST NAME,BALANCE +1,John,Doe,0.34 +2,Fiammetta,Miriana,30 +3,Mathew,Timothy,34.19371,1,2 +4,Lou ` // ////////////////////////////////////////////////////////////////////////////////// // @@ -56,29 +59,29 @@ func (s *CSVSuite) TestRead(c *C) { defer fd.Close() - reader := NewReader(fd).WithComma(',') - count := 0 + line := 0 + reader := NewReader(fd).WithComma(',').WithHeaderSkip(true) for { - rec, err := reader.Read() + row, err := reader.Read() if err == io.EOF { break } - switch count { + switch line { case 0: - c.Assert(rec, HasLen, 5) - c.Assert(rec, DeepEquals, []string{"123", "ABC", "A_C", "A C", ""}) + c.Assert(row, HasLen, 4) + c.Assert(row, DeepEquals, Row{"1", "John", "Doe", "0.34"}) case 1: - c.Assert(rec, HasLen, 3) - c.Assert(rec, DeepEquals, []string{"123", "ABC", ""}) + c.Assert(row, HasLen, 4) + c.Assert(row, DeepEquals, Row{"2", "Fiammetta", "Miriana", "30"}) case 2: - c.Assert(rec, HasLen, 8) - c.Assert(rec, DeepEquals, []string{"123", "ABC", "A_C", "A C", "123", "ABC", "A_C", "A C"}) + c.Assert(row, HasLen, 6) + c.Assert(row, DeepEquals, Row{"3", "Mathew", "Timothy", "34.19371", "1", "2"}) } - count++ + line++ } } @@ -90,19 +93,17 @@ func (s *CSVSuite) TestReadTo(c *C) { defer fd.Close() - reader := NewReader(fd) - reader.Comma = ',' + line := 0 + reader := NewReader(fd).WithComma(',').WithHeaderSkip(true) - count := 0 - - var rec []string + var row Row for { - err := reader.ReadTo(rec) + err := reader.ReadTo(row) - if rec == nil { + if row == nil { c.Assert(err, NotNil) - rec = make([]string, 8) + row = make(Row, 4) continue } @@ -110,41 +111,79 @@ func (s *CSVSuite) TestReadTo(c *C) { break } - switch count { + switch line { case 0: - c.Assert(rec, DeepEquals, []string{"123", "ABC", "A_C", "A C", "", "", "", ""}) + c.Assert(row, DeepEquals, Row{"1", "John", "Doe", "0.34"}) case 1: - c.Assert(rec, DeepEquals, []string{"123", "ABC", "", "", "", "", "", ""}) + c.Assert(row, DeepEquals, Row{"2", "Fiammetta", "Miriana", "30"}) case 2: - c.Assert(rec, DeepEquals, []string{"123", "ABC", "A_C", "A C", "123", "ABC", "A_C", "A C"}) + c.Assert(row, DeepEquals, Row{"3", "Mathew", "Timothy", "34.19371"}) + case 3: + c.Assert(row, DeepEquals, Row{"4", "Lou", "", ""}) } - count++ + line++ } } func (s *CSVSuite) TestLineParser(c *C) { - data := make([]string, 2) + data := make(Row, 2) parseAndFill("ABCD", data, ";") - c.Assert(data, DeepEquals, []string{"ABCD", ""}) + c.Assert(data, DeepEquals, Row{"ABCD", ""}) parseAndFill("", data, ";") - c.Assert(data, DeepEquals, []string{"", ""}) + c.Assert(data, DeepEquals, Row{"", ""}) parseAndFill("A;B;C;D;E", data, ";") - c.Assert(data, DeepEquals, []string{"A", "B"}) + c.Assert(data, DeepEquals, Row{"A", "B"}) } func (s *CSVSuite) TestNil(c *C) { var r *Reader - var b = []string{""} + var b = Row{""} _, err := r.Read() c.Assert(err, DeepEquals, ErrNilReader) c.Assert(r.ReadTo(b), DeepEquals, ErrNilReader) c.Assert(r.WithComma('X'), IsNil) + c.Assert(r.WithHeaderSkip(false), IsNil) +} + +func (s *CSVSuite) TestRow(c *C) { + r := Row{"test", "", "1234", "12.34", "Yes"} + c.Assert(r.Size(), Equals, 5) + c.Assert(r.Cells(), Equals, 4) + c.Assert(r.IsEmpty(), Equals, false) + c.Assert(r.Has(0), Equals, true) + c.Assert(r.Has(1), Equals, false) + c.Assert(r.Get(0), Equals, "test") + c.Assert(r.Get(10), Equals, "") + + ci, err := r.GetI(2) + c.Assert(ci, Equals, 1234) + c.Assert(err, IsNil) + + cf, err := r.GetF(3) + c.Assert(cf, Equals, 12.34) + c.Assert(err, IsNil) + + cu, err := r.GetU(2) + c.Assert(cu, Equals, uint64(1234)) + c.Assert(err, IsNil) + + c.Assert(r.GetB(1), Equals, false) + c.Assert(r.GetB(4), Equals, true) + + dummyFn := func(i int, v string) error { return nil } + c.Assert(r.ForEach(dummyFn), IsNil) + + dummyFn = func(i int, v string) error { return errors.New("1") } + c.Assert(r.ForEach(dummyFn), NotNil) + + c.Assert(r.ToString(';'), Equals, "test;;1234;12.34;Yes") + c.Assert(string(r.ToBytes(';')), Equals, "test;;1234;12.34;Yes") } // ////////////////////////////////////////////////////////////////////////////////// // diff --git a/csv/examples_test.go b/csv/examples_test.go index cb5e2bce..53f5ce8e 100644 --- a/csv/examples_test.go +++ b/csv/examples_test.go @@ -15,6 +15,29 @@ import ( // ////////////////////////////////////////////////////////////////////////////////// // +func ExampleNew() { + fd, err := os.Open("file.csv") + + if err != nil { + fmt.Println(err.Error()) + return + } + + defer fd.Close() + + reader := NewReader(fd) + + for { + data, err := reader.Read() + + if err == io.EOF { + break + } + + fmt.Printf("%#v\n", data) + } +} + func ExampleReader_Read() { fd, err := os.Open("file.csv") @@ -87,3 +110,215 @@ func ExampleReader_WithComma() { fmt.Printf("%#v\n", data) } } + +func ExampleReader_WithHeaderSkip() { + fd, err := os.Open("file.csv") + + if err != nil { + fmt.Println(err.Error()) + return + } + + defer fd.Close() + + reader := NewReader(fd).WithHeaderSkip(true) + + for { + data, err := reader.Read() + + if err == io.EOF { + break + } + + fmt.Printf("%#v\n", data) + } +} + +// ////////////////////////////////////////////////////////////////////////////////// // + +func ExampleRow_Size() { + r := Row{"1", "John", "Doe", "0.34"} + + fmt.Printf("Size: %d\n", r.Size()) + // Output: + // Size: 4 +} + +func ExampleRow_Cells() { + r := Row{"1", "", "", "0.34"} + + fmt.Printf("Size: %d\n", r.Size()) + fmt.Printf("Cells: %d\n", r.Cells()) + // Output: + // Size: 4 + // Cells: 2 +} + +func ExampleRow_IsEmpty() { + r1 := Row{"1", "John", "Doe", "0.34"} + r2 := Row{"", "", "", ""} + + fmt.Printf("r1 is empty: %t\n", r1.IsEmpty()) + fmt.Printf("r2 is empty: %t\n", r2.IsEmpty()) + // Output: + // r1 is empty: false + // r2 is empty: true +} + +func ExampleRow_Has() { + r := Row{"1", "John", "", "0.34"} + + fmt.Printf("Has cell 1: %t\n", r.Has(1)) + fmt.Printf("Has cell 2: %t\n", r.Has(2)) + fmt.Printf("Has cell 100: %t\n", r.Has(100)) + // Output: + // Has cell 1: true + // Has cell 2: false + // Has cell 100: false +} + +func ExampleRow_Get() { + r := Row{"1", "John", "Doe", "0.34"} + + id, err := r.GetI(0) + + if err != nil { + panic(err.Error()) + } + + balance, err := r.GetF(3) + + if err != nil { + panic(err.Error()) + } + + fmt.Printf("ID: %d\n", id) + fmt.Printf("First name: %s\n", r.Get(1)) + fmt.Printf("Last name: %s\n", r.Get(2)) + fmt.Printf("Balance: %g\n", balance) + // Output: + // ID: 1 + // First name: John + // Last name: Doe + // Balance: 0.34 +} + +func ExampleRow_GetI() { + r := Row{"1", "John", "Doe", "0.34"} + + id, err := r.GetI(0) + + if err != nil { + panic(err.Error()) + } + + balance, err := r.GetF(3) + + if err != nil { + panic(err.Error()) + } + + fmt.Printf("ID: %d\n", id) + fmt.Printf("First name: %s\n", r.Get(1)) + fmt.Printf("Last name: %s\n", r.Get(2)) + fmt.Printf("Balance: %g\n", balance) + // Output: + // ID: 1 + // First name: John + // Last name: Doe + // Balance: 0.34 +} + +func ExampleRow_GetF() { + r := Row{"1", "John", "Doe", "0.34"} + + id, err := r.GetI(0) + + if err != nil { + panic(err.Error()) + } + + balance, err := r.GetF(3) + + if err != nil { + panic(err.Error()) + } + + fmt.Printf("ID: %d\n", id) + fmt.Printf("First name: %s\n", r.Get(1)) + fmt.Printf("Last name: %s\n", r.Get(2)) + fmt.Printf("Balance: %g\n", balance) + // Output: + // ID: 1 + // First name: John + // Last name: Doe + // Balance: 0.34 +} + +func ExampleRow_GetU() { + r := Row{"1846915341", "user@domain.com", "Yes"} + + id, err := r.GetU(0) + + if err != nil { + panic(err.Error()) + } + + fmt.Printf("ID: %d\n", id) + fmt.Printf("Email: %s\n", r.Get(1)) + fmt.Printf("Is active: %t\n", r.GetB(2)) + // Output: + // ID: 1846915341 + // Email: user@domain.com + // Is active: true +} + +func ExampleRow_GetB() { + r := Row{"1846915341", "user@domain.com", "Yes"} + + id, err := r.GetU(0) + + if err != nil { + panic(err.Error()) + } + + fmt.Printf("ID: %d\n", id) + fmt.Printf("Email: %s\n", r.Get(1)) + fmt.Printf("Is active: %t\n", r.GetB(2)) + // Output: + // ID: 1846915341 + // Email: user@domain.com + // Is active: true +} + +func ExampleRow_ForEach() { + r := Row{"John", "Do"} + + err := r.ForEach(func(index int, value string) error { + if len(value) < 3 { + return fmt.Errorf("Cell %d contains invalid value %q", index, value) + } + + return nil + }) + + fmt.Println(err) + // Output: + // Cell 1 contains invalid value "Do" +} + +func ExampleRow_ToString() { + r := Row{"1", "John", "Doe", "0.34"} + + fmt.Println(r.ToString(';')) + // Output: + // 1;John;Doe;0.34 +} + +func ExampleRow_ToBytes() { + r := Row{"1", "John", "Doe", "0.34"} + + fmt.Println(string(r.ToBytes(';'))) + // Output: + // 1;John;Doe;0.34 +} From a41b8cff928d91acbe2e79cce82319c8a786f8f4 Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Sat, 4 May 2024 23:36:43 +0300 Subject: [PATCH 3/6] [option] Remove 'Required' flag from option struct --- CHANGELOG.md | 1 + options/example_test.go | 1 - options/options.go | 12 ------------ options/options_test.go | 21 +++++---------------- 4 files changed, 6 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c64a8a0..155f3a02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - `[initsystem/sdnotify]` Added new package for sending messages to systemd - `[csv]` Added helpers for working with CSV row - `[csv]` Added option to skip header +- `[option]` Removed `Required` flag from option struct - `[support/deps]` Updated for compatibility with the latest version of [depsy](https://kaos.sh/depsy) - `[terminal/tty]` Improved check for systemd diff --git a/options/example_test.go b/options/example_test.go index 3f87407a..1f1dd176 100644 --- a/options/example_test.go +++ b/options/example_test.go @@ -68,7 +68,6 @@ func ExampleParse() { "I:int2": {Type: INT, Min: 1, Max: 10}, // Integer with limits "f:float": {Type: FLOAT, Value: 10.0}, // Float "b:boolean": {Type: BOOL}, // Boolean - "r:required": {Type: INT, Required: true}, // Some options can be marked as required "m:merg": {Type: STRING, Mergeble: true}, // Mergeble options can be defined more than one time "h:help": {Type: BOOL, Alias: "u:usage about"}, // You can define argument aliases "e:example": {Conflicts: []string{"s:string", "S:string2"}}, // Option conflicts with string and string2 (options can't be set at same time) diff --git a/options/options.go b/options/options.go index 83bf873b..225303a6 100644 --- a/options/options.go +++ b/options/options.go @@ -33,7 +33,6 @@ const ( ERROR_DUPLICATE_SHORTNAME ERROR_OPTION_IS_NIL ERROR_EMPTY_VALUE - ERROR_REQUIRED_NOT_SET ERROR_WRONG_FORMAT ERROR_CONFLICT ERROR_BOUND_NOT_SET @@ -54,7 +53,6 @@ type V struct { Conflicts any // string or slice of strings with conflicts options Bound any // string or slice of strings with bound options Mergeble bool // option supports options value merging - Required bool // option is required set bool // non-exported field @@ -650,10 +648,6 @@ func (v *V) String() string { result += "Mergeble:Yes " } - if v.Required { - result += "Required:Yes " - } - return strings.TrimRight(result, " ") + "}" } @@ -851,10 +845,6 @@ func (opts *Options) validate() Errors { errs = append(errs, OptionError{F(n), "", ERROR_UNSUPPORTED_VALUE}) } - if v.Required && v.Value == nil { - errs = append(errs, OptionError{F(n), "", ERROR_REQUIRED_NOT_SET}) - } - if v.Conflicts != "" { conflicts, ok := parseOptionsList(v.Conflicts) @@ -1094,8 +1084,6 @@ func (e OptionError) Error() string { return fmt.Sprintf("Option %q is not supported", e.Option) case ERROR_EMPTY_VALUE: return fmt.Sprintf("Non-boolean option %q is empty", e.Option) - case ERROR_REQUIRED_NOT_SET: - return fmt.Sprintf("Required option %q is not set", e.Option) case ERROR_WRONG_FORMAT: return fmt.Sprintf("Option %q has wrong format", e.Option) case ERROR_OPTION_IS_NIL: diff --git a/options/options_test.go b/options/options_test.go index c7abe5d4..79d3780c 100644 --- a/options/options_test.go +++ b/options/options_test.go @@ -218,12 +218,11 @@ func (s *OptUtilSuite) TestBound(c *C) { } func (s *OptUtilSuite) TestGetters(c *C) { - argline := "file.mp3 -SAT -s STRING --required TEST -i 320 -b -f 1.098765 -S2 100 -f1 5 -f2 1 -ms ABC --merg-string DEF -mi 6 --merg-int 6 -f3 12 -mf 10.1 -mf 10.1 -i1 5" + argline := "file.mp3 -SAT -s STRING -i 320 -b -f 1.098765 -S2 100 -f1 5 -f2 1 -ms ABC --merg-string DEF -mi 6 --merg-int 6 -f3 12 -mf 10.1 -mf 10.1 -i1 5" optMap := Map{ - "s:string": {Type: STRING, Value: "STRING"}, + "s:string": {Type: STRING, Value: "STRING", Alias: []string{"A:alias", "A2:alias2"}}, "S:empty-string": {Type: STRING}, - "r:required": {Required: true, Alias: []string{"A:alias", "A2:alias2"}}, "i:int": {Type: INT}, "i1:int-between": {Type: INT, Min: 1, Max: 3}, "I:not-set-int": {Type: INT, Value: 0}, @@ -277,10 +276,8 @@ func (s *OptUtilSuite) TestGetters(c *C) { c.Assert(opts.GetI("S2:string-as-num"), Equals, 100) c.Assert(opts.GetF("S2:string-as-num"), Equals, 100.0) - c.Assert(opts.GetS("r:required"), Equals, "TEST") - c.Assert(opts.GetS("required"), Equals, "TEST") - c.Assert(opts.GetS("A:alias"), Equals, "TEST") - c.Assert(opts.GetS("alias"), Equals, "TEST") + c.Assert(opts.GetS("A:alias"), Equals, "STRING") + c.Assert(opts.GetS("alias"), Equals, "STRING") c.Assert(opts.GetS("int"), Equals, "320") c.Assert(opts.GetB("int"), Equals, true) @@ -457,13 +454,6 @@ func (s *OptUtilSuite) TestParsing(c *C) { // //////////////////////////////////////////////////////////////////////////////// // - _, errs = NewOptions().Parse([]string{}, Map{"t:test": {Required: true}}) - - c.Assert(errs, Not(HasLen), 0) - c.Assert(errs[0], ErrorMatches, `Required option "--test" is not set`) - - // //////////////////////////////////////////////////////////////////////////////// // - _, errs = NewOptions().Parse([]string{"-t"}, Map{"t:test": {}}) c.Assert(errs, Not(HasLen), 0) @@ -570,10 +560,9 @@ func (s *OptUtilSuite) TestVString(c *C) { Min: 10, Max: 1000, Mergeble: true, - Required: true, } - c.Assert(v.String(), Equals, "String{Value:test Min:10 Max:1000 Alias:[--test2 --test6] Conflicts:--test3 Bound:--test4 Mergeble:Yes Required:Yes}") + c.Assert(v.String(), Equals, "String{Value:test Min:10 Max:1000 Alias:[--test2 --test6] Conflicts:--test3 Bound:--test4 Mergeble:Yes}") v = &V{Type: INT} c.Assert(v.String(), Equals, "Int{}") From 8e430f799b56b550c738f036fc1bafce61ab7995 Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Sat, 4 May 2024 23:38:47 +0300 Subject: [PATCH 4/6] Version bump --- CHANGELOG.md | 7 +++++-- ek.go | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 155f3a02..831210d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,14 @@ ## Changelog -### 12.121.0 +### 12.122.0 -- `[initsystem/sdnotify]` Added new package for sending messages to systemd - `[csv]` Added helpers for working with CSV row - `[csv]` Added option to skip header - `[option]` Removed `Required` flag from option struct + +### 12.121.0 + +- `[initsystem/sdnotify]` Added new package for sending messages to systemd - `[support/deps]` Updated for compatibility with the latest version of [depsy](https://kaos.sh/depsy) - `[terminal/tty]` Improved check for systemd diff --git a/ek.go b/ek.go index 11292799..2a602748 100644 --- a/ek.go +++ b/ek.go @@ -21,7 +21,7 @@ import ( // ////////////////////////////////////////////////////////////////////////////////// // // VERSION is current ek package version -const VERSION = "12.121.0" +const VERSION = "12.122.0" // ////////////////////////////////////////////////////////////////////////////////// // From 41feac25c07cb098acbd4fa9d54e8d46eea41472 Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Sat, 4 May 2024 23:44:32 +0300 Subject: [PATCH 5/6] [csv] Fix float parsing --- csv/csv.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/csv/csv.go b/csv/csv.go index aa7a3c50..86b0f786 100644 --- a/csv/csv.go +++ b/csv/csv.go @@ -178,7 +178,7 @@ func (r Row) GetI(index int) (int, error) { // GetF returns cell value as float func (r Row) GetF(index int) (float64, error) { - return strconv.ParseFloat(r.Get(index), 10) + return strconv.ParseFloat(r.Get(index), 64) } // GetU returns cell value as uint64 From fdf52080df6b57d8625e4a00231127e7a07e5cd3 Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Sat, 4 May 2024 23:52:05 +0300 Subject: [PATCH 6/6] [csv] Code refactoring --- csv/csv.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/csv/csv.go b/csv/csv.go index 86b0f786..a247840f 100644 --- a/csv/csv.go +++ b/csv/csv.go @@ -236,7 +236,7 @@ func parseAndFill(src string, dst Row, sep string) { l := len(dst) if src == "" { - clean(dst, 0, l) + clean(dst, 0) return } @@ -245,7 +245,7 @@ func parseAndFill(src string, dst Row, sep string) { if n == 0 { dst[0] = src - clean(dst, 1, l) + clean(dst, 1) return } @@ -263,13 +263,13 @@ func parseAndFill(src string, dst Row, sep string) { } if i < l-1 { - clean(dst, i+1, l) + clean(dst, i+1) } } // clean cleans destination slice -func clean(dst Row, since, to int) { - for i := since; i < to; i++ { +func clean(dst Row, from int) { + for i := from; i < len(dst); i++ { dst[i] = "" } }