Skip to content

Commit

Permalink
Merge pull request #467 from essentialkaos/develop
Browse files Browse the repository at this point in the history
Version 12.122.0
  • Loading branch information
andyone authored May 4, 2024
2 parents 46e7b7e + fdf5208 commit de98747
Show file tree
Hide file tree
Showing 10 changed files with 551 additions and 94 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
## Changelog

### 12.122.0

- `[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
Expand Down
165 changes: 152 additions & 13 deletions csv/csv.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,28 @@ package csv

import (
"bufio"
"bytes"
"errors"
"io"
"strconv"
"strings"
)

// ////////////////////////////////////////////////////////////////////////////////// //

// 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
Expand All @@ -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 {
Expand All @@ -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
}
Expand All @@ -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 {
Expand All @@ -90,14 +111,132 @@ 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), 64)
}

// 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 == "" {
clean(dst, 0, l)
clean(dst, 0)
return
}

Expand All @@ -106,7 +245,7 @@ func parseAndFill(src string, dst []string, sep string) {

if n == 0 {
dst[0] = src
clean(dst, 1, l)
clean(dst, 1)
return
}

Expand All @@ -124,13 +263,13 @@ func parseAndFill(src string, dst []string, sep string) {
}

if i < l-1 {
clean(dst, i, l)
clean(dst, i+1)
}
}

// clean cleans destination slice
func clean(dst []string, since, to int) {
for i := since; i < to; i++ {
func clean(dst Row, from int) {
for i := from; i < len(dst); i++ {
dst[i] = ""
}
}
Loading

0 comments on commit de98747

Please sign in to comment.