v1 #1
@@ -17,12 +17,15 @@ editor = vim
|
|||||||
distance=13.42
|
distance=13.42
|
||||||
floats=0.5,2.37,6
|
floats=0.5,2.37,6
|
||||||
floatswithstring = 0.5, hello, 0.9
|
floatswithstring = 0.5, hello, 0.9
|
||||||
no missedme
|
false
|
||||||
color`)
|
color`)
|
||||||
|
|
||||||
func TestPackage(t *testing.T) {
|
func TestPackage(t *testing.T) {
|
||||||
r := bytes.NewReader(testConf)
|
r := bytes.NewReader(testConf)
|
||||||
c := parseReader(r)
|
c, err := parseReader(r)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
if _, err := c.Get("floatswithstring").Float64Slice(); err == nil {
|
if _, err := c.Get("floatswithstring").Float64Slice(); err == nil {
|
||||||
t.Log("Float64Slice accepting incorrect values")
|
t.Log("Float64Slice accepting incorrect values")
|
||||||
t.Fail()
|
t.Fail()
|
||||||
@@ -99,7 +102,7 @@ func TestPackage(t *testing.T) {
|
|||||||
t.Log("empty string erroneously converts to float")
|
t.Log("empty string erroneously converts to float")
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
if def := c.GetDefault("non-existant-key", "myvalue"); def.Value != "myvalue" {
|
if def := c.GetDefault("non-existant-key", "myvalue"); def.String() != "myvalue" {
|
||||||
t.Log("GetDefault fails to apply default value")
|
t.Log("GetDefault fails to apply default value")
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
|
69
config.go
69
config.go
@@ -56,54 +56,57 @@
|
|||||||
// quite self-explanatory.
|
// quite self-explanatory.
|
||||||
package conf
|
package conf
|
||||||
|
|
||||||
// Config holds parsed keys and values. Settings and Options can be
|
// Config holds parsed keys and values.
|
||||||
// accessed with Config.Settings and Config.Options maps directly.
|
type Conf struct {
|
||||||
type Config struct {
|
|
||||||
// Settings store key value pairs ("key = value" in config file)
|
// Settings store key value pairs ("key = value" in config file)
|
||||||
// all key value pairs found when parsing input are accumulated in this map.
|
// all key value pairs found when parsing input are accumulated in this map.
|
||||||
Settings map[string]string
|
settings map[string]string
|
||||||
// Options map stores single word options ("option" in config file)
|
|
||||||
Options map[string]struct{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find looks up a Setting and returns it. If returned error is not nil
|
// Find looks up a Setting and returns it. If returned error is not nil
|
||||||
// the requested key was not found and returned Setting has empty string in Value
|
// the requested key was not found and returned Setting has empty string in Value
|
||||||
// field.
|
// field.
|
||||||
func (c *Config) Find(key string) (s Setting, err error) {
|
func (c *Conf) Find(key string) (Setting, bool) {
|
||||||
v, ok := c.Settings[key]
|
return c.get(key, "")
|
||||||
if !ok {
|
|
||||||
err = ErrNotFound
|
|
||||||
}
|
|
||||||
s.Value = v
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get returns a Setting. If key was not found the returned Setting Value
|
// Get returns a Setting. If key was not found the returned Setting Value
|
||||||
// will be empty string.
|
// will be empty string.
|
||||||
func (c *Config) Get(key string) (s Setting) {
|
func (c *Conf) Get(key string) Setting {
|
||||||
s.Value = c.Settings[key]
|
s, _ := c.get(key, "")
|
||||||
return
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDefault looks up a Setting with requested key.
|
// GetDefault looks up a Setting with requested key.
|
||||||
// If lookup fails it returns Setting with Value field set to def.
|
// If lookup fails it returns Setting with Value field set to def.
|
||||||
func (c *Config) GetDefault(key, def string) (s Setting) {
|
func (c *Conf) GetDefault(key, def string) Setting {
|
||||||
v, ok := c.Settings[key]
|
s, _ := c.get(key, def)
|
||||||
switch ok {
|
return s
|
||||||
case true:
|
|
||||||
s.Value = v
|
|
||||||
default:
|
|
||||||
s.Value = def
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasOption returns true if line:
|
func (c *Conf) Settings() []Setting {
|
||||||
//
|
list := make([]Setting, 0, len(c.settings))
|
||||||
// "key"
|
|
||||||
//
|
for k, v := range c.settings {
|
||||||
// was found in the parsed file
|
s := Setting{
|
||||||
func (c *Config) HasOption(option string) (exists bool) {
|
Name: k,
|
||||||
_, exists = c.Options[option]
|
Value: v,
|
||||||
return
|
}
|
||||||
|
|
||||||
|
list = append(list, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conf) get(key string, def string) (Setting, bool) {
|
||||||
|
v, ok := c.settings[key]
|
||||||
|
if def != "" && !ok {
|
||||||
|
v = def
|
||||||
|
}
|
||||||
|
|
||||||
|
return Setting{
|
||||||
|
Name: key,
|
||||||
|
Value: v,
|
||||||
|
}, ok
|
||||||
}
|
}
|
||||||
|
103
parser.go
103
parser.go
@@ -2,66 +2,107 @@ package conf
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
const (
|
||||||
configKeyValueRe = regexp.MustCompile(`(\w+) *= *(.+)\b[\t| |\n]*`)
|
strComment = "#"
|
||||||
configOptionRe = regexp.MustCompile(`(\w+)`)
|
strEqSign = "="
|
||||||
configCommentedOut = "#"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ParseFile reads values from file. It returns nil and error if os.Open(filename) fails.
|
var (
|
||||||
|
ErrFormat = errors.New("conf: line does not match \"key = value\" pattern")
|
||||||
|
ErrDuplicateKey = errors.New("conf: duplicate key found")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Open reads values from file. It returns nil and error if os.Open(filename) fails.
|
||||||
// It would be wise to always check returned error.
|
// It would be wise to always check returned error.
|
||||||
// ParseFile captures two types of values: "key = value" and "option". Either key value pair or
|
// ParseFile captures two types of values: "key = value" and "option". Either key value pair or
|
||||||
// option must be put in its own line in the file.
|
// option must be put in its own line in the file.
|
||||||
// Key or option must be a single word. In example line:
|
// Key or option must be a single word. In example line:
|
||||||
// "option1 option2"
|
//
|
||||||
|
// "option1 option2"
|
||||||
|
//
|
||||||
// only option1 will be captured. option2 needs to be in a separate line in the file to take effect.
|
// only option1 will be captured. option2 needs to be in a separate line in the file to take effect.
|
||||||
// In line "key = value1,value2,value3" all of value1, value2, and value3 will be
|
// In line "key = value1,value2,value3" all of value1, value2, and value3 will be
|
||||||
// captured. They can be later accesed separately with Setting's Split() method.
|
// captured. They can be later accesed separately with Setting's Split() method.
|
||||||
func ParseFile(filename string) (*Config, error) {
|
func Open(filename string) (*Conf, error) {
|
||||||
file, err := os.Open(filename)
|
file, err := os.Open(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer file.Close()
|
|
||||||
return parseReader(file), nil
|
c, err := parseReader(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, file.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseReader reads from r and returns Config. See also ParseFile.
|
// Read reads from r and returns Config.
|
||||||
func ParseReader(r io.Reader) *Config {
|
func Read(r io.Reader) (*Conf, error) {
|
||||||
return parseReader(r)
|
return parseReader(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseReadCloser reads from r, returns Config and calls r.Close().
|
func parseReader(r io.Reader) (*Conf, error) {
|
||||||
// See also ParseFile.
|
settings := make(map[string]string)
|
||||||
func ParseReadCloser(r io.ReadCloser) *Config {
|
|
||||||
defer r.Close()
|
|
||||||
return parseReader(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseReader(r io.Reader) *Config {
|
|
||||||
var c Config
|
|
||||||
c.Settings = make(map[string]string)
|
|
||||||
c.Options = make(map[string]struct{})
|
|
||||||
scanner := bufio.NewScanner(r)
|
scanner := bufio.NewScanner(r)
|
||||||
|
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
line := scanner.Text()
|
line := scanner.Text()
|
||||||
if strings.HasPrefix(line, configCommentedOut) {
|
|
||||||
|
line = trim(stripComment(line))
|
||||||
|
if line == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
switch {
|
|
||||||
case configKeyValueRe.MatchString(line):
|
key, value, ok := toKV(line)
|
||||||
kvpair := configKeyValueRe.FindStringSubmatch(line)
|
if !ok {
|
||||||
c.Settings[kvpair[1]] = kvpair[2]
|
return nil, ErrFormat
|
||||||
case configOptionRe.MatchString(line):
|
|
||||||
opt := configOptionRe.FindString(line)
|
|
||||||
c.Options[opt] = struct{}{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if _, ok := settings[key]; ok {
|
||||||
|
return nil, fmt.Errorf("%w: %s", ErrDuplicateKey, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
settings[key] = value
|
||||||
}
|
}
|
||||||
return &c
|
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Conf{
|
||||||
|
settings: settings,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func toKV(s string) (string, string, bool) {
|
||||||
|
k, v, ok := strings.Cut(s, strEqSign)
|
||||||
|
if !ok || k == "" || v == "" {
|
||||||
|
return "", "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
return trim(k), trim(v), true
|
||||||
|
}
|
||||||
|
|
||||||
|
func trim(s string) string {
|
||||||
|
s = strings.TrimSpace(s)
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func stripComment(s string) string {
|
||||||
|
idx := strings.Index(s, strComment)
|
||||||
|
if idx > -1 {
|
||||||
|
s = s[:idx]
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
}
|
}
|
||||||
|
159
parser_test.go
Normal file
159
parser_test.go
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
package conf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_trim(t *testing.T) {
|
||||||
|
tc := []struct {
|
||||||
|
name string
|
||||||
|
in string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty",
|
||||||
|
in: "",
|
||||||
|
want: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "comment",
|
||||||
|
in: "# some comment",
|
||||||
|
want: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "comment",
|
||||||
|
in: "#some comment",
|
||||||
|
want: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "comment",
|
||||||
|
in: " #some comment",
|
||||||
|
want: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "key value",
|
||||||
|
in: "key = value",
|
||||||
|
want: "key = value",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "key value with whitespace",
|
||||||
|
in: " key = value ",
|
||||||
|
want: "key = value",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "key value with whitespace",
|
||||||
|
in: "\tkey = value \t",
|
||||||
|
want: "key = value",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "key value with whitespace",
|
||||||
|
in: "\tkey = value \t\n",
|
||||||
|
want: "key = value",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "key value with comment",
|
||||||
|
in: "key = value #comment",
|
||||||
|
want: "key = value",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "option",
|
||||||
|
in: "option \n",
|
||||||
|
want: "option",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "option with comment",
|
||||||
|
in: "option # comment \n",
|
||||||
|
want: "option",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range tc {
|
||||||
|
t.Run(c.name, func(t *testing.T) {
|
||||||
|
have := trim(c.in)
|
||||||
|
if have != c.want {
|
||||||
|
t.Errorf("want: %s, have: %s", have, c.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRead(t *testing.T) {
|
||||||
|
tc := []struct {
|
||||||
|
name string
|
||||||
|
in []byte
|
||||||
|
res *Conf
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty",
|
||||||
|
in: []byte{},
|
||||||
|
res: &Conf{
|
||||||
|
settings: make(map[string]string),
|
||||||
|
options: map[string]struct{}{},
|
||||||
|
},
|
||||||
|
err: nil, // if the reader is empty its not out fault
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "single key with spaces",
|
||||||
|
in: []byte("key = value"),
|
||||||
|
res: &Conf{
|
||||||
|
settings: map[string]string{
|
||||||
|
"key": "value",
|
||||||
|
},
|
||||||
|
options: make(map[string]struct{}),
|
||||||
|
},
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "single key no spaces",
|
||||||
|
in: []byte("key=value"),
|
||||||
|
res: &Conf{
|
||||||
|
settings: map[string]string{
|
||||||
|
"key": "value",
|
||||||
|
},
|
||||||
|
options: make(map[string]struct{}),
|
||||||
|
},
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "single key with newline",
|
||||||
|
in: []byte("key=value\n"),
|
||||||
|
res: &Conf{
|
||||||
|
settings: map[string]string{
|
||||||
|
"key": "value",
|
||||||
|
},
|
||||||
|
options: make(map[string]struct{}),
|
||||||
|
},
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "single key with comment and newline",
|
||||||
|
in: []byte("key =value # this is a comment\n"),
|
||||||
|
res: &Conf{
|
||||||
|
settings: map[string]string{
|
||||||
|
"key": "value",
|
||||||
|
},
|
||||||
|
options: make(map[string]struct{}),
|
||||||
|
},
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range tc {
|
||||||
|
t.Run(c.name, func(t *testing.T) {
|
||||||
|
r := bytes.NewReader(c.in)
|
||||||
|
|
||||||
|
conf, err := Read(r)
|
||||||
|
if !errors.Is(err, c.err) {
|
||||||
|
t.Fatalf("want error: %v have: %v", c.err, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(c.res, conf) {
|
||||||
|
t.Fatalf("want: %+v, have: %+v", c.res, conf)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
136
settings.go
136
settings.go
@@ -7,38 +7,23 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrNotFound = errors.New("key not found")
|
ErrNotFound = errors.New("conf: key not found")
|
||||||
ErrParsingBool = errors.New("value can not be interpreted as bool")
|
ErrCouldNotConvert = errors.New("conf: could not cast one or more values to required type")
|
||||||
ErrCouldNotConvert = errors.New("could not cast one or more values to required type")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var valuesSeparator = ","
|
const valuesSeparator = ","
|
||||||
|
|
||||||
var boolMap = map[string]bool{
|
|
||||||
// what evaluates to true
|
|
||||||
"true": true,
|
|
||||||
"t": true,
|
|
||||||
"1": true,
|
|
||||||
"yes": true,
|
|
||||||
"on": true,
|
|
||||||
// what evaluates to false
|
|
||||||
"false": false,
|
|
||||||
"f": false,
|
|
||||||
"0": false,
|
|
||||||
"no": false,
|
|
||||||
"off": false,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setting represents key-value pair read from config file.
|
// Setting represents key-value pair read from config file.
|
||||||
// It's Value field holds the value of key parsed from the configuration
|
// It's Value field holds the value of key parsed from the configuration
|
||||||
type Setting struct {
|
type Setting struct {
|
||||||
|
Name string
|
||||||
Value string
|
Value string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Int converts Setting Value to int. Returned error
|
// Int converts Setting Value to int. Returned error
|
||||||
// will be non nil if convesion failed.
|
// will be non nil if convesion failed.
|
||||||
func (st Setting) Int() (int, error) {
|
func (s Setting) Int() (int, error) {
|
||||||
return parseInt(st.Value)
|
return parseValue(s.Value, strconv.Atoi)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IntSlice splits Setting Value (separator is ",") and adds
|
// IntSlice splits Setting Value (separator is ",") and adds
|
||||||
@@ -46,19 +31,14 @@ func (st Setting) Int() (int, error) {
|
|||||||
// If one or more values can not be converted to float64 those will be dropped
|
// If one or more values can not be converted to float64 those will be dropped
|
||||||
// and method will return conf.ErrCouldNotConvert.
|
// and method will return conf.ErrCouldNotConvert.
|
||||||
// Check error to be sure that all required values were parsed.
|
// Check error to be sure that all required values were parsed.
|
||||||
func (st Setting) IntSlice() ([]int, error) {
|
func (s Setting) IntSlice() ([]int, error) {
|
||||||
return parseIntSlice(st.Value, valuesSeparator)
|
return parseSlice(s.Value, strconv.Atoi)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* func (st Setting) split(sep string) Setting {
|
|
||||||
st.sep = sep //Choose separator to split values ?
|
|
||||||
return st
|
|
||||||
} */
|
|
||||||
|
|
||||||
// Float64 converts Setting Value to float64. Returned error
|
// Float64 converts Setting Value to float64. Returned error
|
||||||
// will be non nil if convesion failed.
|
// will be non nil if convesion failed.
|
||||||
func (st Setting) Float64() (float64, error) {
|
func (s Setting) Float64() (float64, error) {
|
||||||
return parseFloat64(st.Value)
|
return parseValue(s.Value, parseFloat64)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Float64Slice splits Setting Value (separator is ",") and adds
|
// Float64Slice splits Setting Value (separator is ",") and adds
|
||||||
@@ -66,92 +46,64 @@ func (st Setting) Float64() (float64, error) {
|
|||||||
// If one or more values can not be converted to float64 those will be dropped
|
// If one or more values can not be converted to float64 those will be dropped
|
||||||
// and method will return conf.ErrCouldNotConvert.
|
// and method will return conf.ErrCouldNotConvert.
|
||||||
// Check error to be sure that all required values were parsed.
|
// Check error to be sure that all required values were parsed.
|
||||||
func (st Setting) Float64Slice() ([]float64, error) {
|
func (s Setting) Float64Slice() ([]float64, error) {
|
||||||
return parseFloat64Slice(st.Value, valuesSeparator)
|
return parseSlice(s.Value, parseFloat64)
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns option Value as string
|
// String returns option Value as string
|
||||||
// This method also implements Stringer interface from fmt module
|
// This method also implements Stringer interface from fmt module
|
||||||
func (st Setting) String() string {
|
func (s Setting) String() string {
|
||||||
return st.Value
|
return s.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
// StringSlice splits Setting's Value (separator is ",") and adds
|
// StringSlice splits Setting's Value (separator is ",") and adds
|
||||||
// each of resulting values to []string trimming leading and trailing spaces
|
// each of resulting values to []string trimming leading and trailing spaces
|
||||||
// from each string.
|
// from each string.
|
||||||
func (st Setting) StringSlice() []string {
|
func (s Setting) StringSlice() []string {
|
||||||
return tidySplit(st.Value, valuesSeparator)
|
return tidySplit(s.Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bool tries to interpret Setting's Value as bool
|
// Bool tries to interpret Setting's Value as bool
|
||||||
// "1", "true", "on", "yes" (case insensitive) yields true
|
// "1", "t", "T", true", "True", "TRUE" yields true
|
||||||
// "0", "false", "off", "no" (case insensitive) yields false
|
// "0", "f", "F, "false", "False", "FALSE" yields false
|
||||||
// If nothing matches will return false and conf.ErrParsingBool
|
// If nothing matches will return false and conf.ErrCouldNotConvert.
|
||||||
func (st Setting) Bool() (bool, error) {
|
func (s Setting) Bool() (bool, error) {
|
||||||
return parseBool(st.Value)
|
return parseValue(s.Value, strconv.ParseBool)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseInt(s string) (n int, err error) {
|
func parseSlice[T any](s string, f func(string) (T, error)) ([]T, error) {
|
||||||
n, err = strconv.Atoi(s)
|
split := tidySplit(s)
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseIntSlice(s, sep string) ([]int, error) {
|
list := make([]T, 0, len(split))
|
||||||
var (
|
for _, str := range split {
|
||||||
n int
|
v, err := parseValue(str, f)
|
||||||
err error
|
|
||||||
slice []int
|
|
||||||
digits []string
|
|
||||||
)
|
|
||||||
digits = tidySplit(s, sep)
|
|
||||||
for _, d := range digits {
|
|
||||||
n, err = strconv.Atoi(d)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = ErrCouldNotConvert
|
return list, err
|
||||||
break
|
|
||||||
}
|
}
|
||||||
slice = append(slice, n)
|
|
||||||
|
list = append(list, v)
|
||||||
}
|
}
|
||||||
return slice, err
|
|
||||||
|
return list, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseFloat64(s string) (n float64, err error) {
|
func parseValue[T any](s string, f func(string) (T, error)) (T, error) {
|
||||||
n, err = strconv.ParseFloat(s, 64)
|
v, err := f(s)
|
||||||
return
|
if err != nil {
|
||||||
}
|
return v, errors.Join(ErrCouldNotConvert, err)
|
||||||
|
|
||||||
func parseFloat64Slice(s, sep string) ([]float64, error) {
|
|
||||||
var (
|
|
||||||
n float64
|
|
||||||
err error
|
|
||||||
slice []float64
|
|
||||||
digits []string
|
|
||||||
)
|
|
||||||
digits = tidySplit(s, sep)
|
|
||||||
for _, d := range digits {
|
|
||||||
n, err = strconv.ParseFloat(d, 64)
|
|
||||||
if err != nil {
|
|
||||||
err = ErrCouldNotConvert
|
|
||||||
break
|
|
||||||
}
|
|
||||||
slice = append(slice, n)
|
|
||||||
}
|
}
|
||||||
return slice, err
|
|
||||||
|
return v, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseBool(s string) (value bool, err error) {
|
func tidySplit(s string) []string {
|
||||||
s = strings.ToLower(s)
|
splitted := strings.Split(s, valuesSeparator)
|
||||||
value, ok := boolMap[s]
|
|
||||||
if !ok {
|
|
||||||
err = ErrParsingBool
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func tidySplit(s, sep string) []string {
|
|
||||||
splitted := strings.Split(s, sep)
|
|
||||||
for i, str := range splitted {
|
for i, str := range splitted {
|
||||||
splitted[i] = strings.Trim(str, " ")
|
splitted[i] = strings.TrimSpace(str)
|
||||||
}
|
}
|
||||||
return splitted
|
return splitted
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseFloat64(s string) (float64, error) {
|
||||||
|
return strconv.ParseFloat(s, 64)
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user