This commit is contained in:
2025-04-20 21:05:14 +03:00
parent 27cb532ec4
commit de63cc9e04
14 changed files with 1191 additions and 670 deletions

210
README.md
View File

@@ -1,180 +1,56 @@
# module conf
# package conf
**go get code.uint32.ru/tiny/conf** to download.
**import "code.uint32.ru/tiny/conf"** to use in your code.
conf implements a very simple config parser with permissive input syntax.
Module conf implements a very simple config parser with two types of
values: key value pairs and single word options. Each of these must be
put on separate line in a file like this:
```
key1 = value
key2 = value1, value2, value3
option1
option2
```
Values can also be read from any io.Reader or io.ReadCloser. Note that
values in key value pairs mey either be separated with spaces or not.
It parses input file or io.Reader looking for key value pairs separated by equal
sign into conf.Map which is a map[string]conf.Value under the hood.
Typical use case would look like this:
Input syntax rules are as follows:
1. Lines starting with "#" are considered to be comments and are discarded. If a line starts with any number of whitespace characters followed by "#" it is also discarded as comment.
2. If a line is not a comment it must follow the "key = value" pattern. Any of the following is valid: "key=value", "key= value", "key =value". Leading and trailing whitespace characters are trimmed before parsing line. Whitespaces surrounding
key and value strings are also trimmed.
3. Keys in the input must be unique, ohterwise Read / ReadFile methods will return an error.
Map object has Find, Get and GetDefault methods used to retrieve Value from Map by corresponding key.
Value is essentially a string which can be converted into several types using Value's methods:
**String()** returns Value as string
**StringSlice()** interprets Value as a comma-separated list (whitespace around elements is trimmed)
**Int()** converts Value to int
**IntSlice()** tries to convert Value to []int
**Float64()** converts Value to float64
**Float64Slice()** tries to convert to []float64
**Map()** tries to interpret Value as comma-separated list of key-value pairs like in "key1: value1, key2: value2, key3: value3"
**URL()** calls url.Parse to convert Value to *url.URL
See documentation for description of all available methods.
Example usage:
```go
config, err := conf.ParseFile("filename")
m, _ := conf.ReadFile("myfile")
retries, err := m.Get("retries").Int()
if err != nil {
// Means we failed to read from file
// config variable is now nil and unusable
// handle err
}
value, err := config.Find("mykey")
addr, err := m.Get("addr").URL()
if err != nil {
// Means that key has not been found
}
// value now holds conf.Setting type which can be accessed directly.
fmt.Println(value.Value)
n, err := value.Float64() // tries to parse float from Setting.Value field.
// if err is nil then n holds float64.
```
Shorter syntax is also available with Get() method which drops errors if
key was not found and simply returns Setting with empty Value field:
```go
listnumbers, err := config.Get("numbers").IntSlice()
// listnumbers holds slice of ints. If err is nil all of found values
// have converted successfully.
```
Note that we'd still like to check for errors in above example even if
we're sure the key exists. This way we'll configrm that all values have been converted.
There is also a GetDefault() method:
```go
def := config.GetDefault("non-existant-key", "myvalue")
fmt.Println(def.Value) // "myvalue"
```
To check whether single-word options were found use:
```go
if config.HasOption("wordoption") {
// do something
// handle err
}
```
See description of module's types and methods which are
quite self-explanatory.
See also **[https://pkg.go.dev/code.uint32.ru/tiny/conf](https://pkg.go.dev/code.uint32.ru/tiny/conf)** for a complete description of module's
functions.
Below is listing of a working example of a program parsing config (also found **example/main.go** in the repository).
```go
package main
import (
"bytes"
"fmt"
"code.uint32.ru/tiny/conf"
)
var testConf = []byte(`
# commented
port=10000
servers = 10.0.0.1, 10.0.0.2, 10.0.0.3
bool0=0
booltrue = true
distance=13.42
iamsure = hopefully you're not mistaken
float = 13.1984
color`)
func main() {
r := bytes.NewReader(testConf) // creating io.Reader from []byte()
config := conf.ParseReader(r) // we could call conf.ParseFile("filename") here
// First of all we can access parsed values directly:
for key, value := range config.Settings {
fmt.Println(key, value)
}
for opt := range config.Options {
fmt.Println(opt)
}
fmt.Println()
// Find( key string) returns instance of Setting and an
// error if key was not found
//
// type Setting struct {
// Value string // value if found
// }
//
// You can access Setting's Value field (type string) directly.
port, err := config.Find("port")
fmt.Printf("variable port has type: %T, port.Value == %v, type of port.Value is: %T, error returned: %v\n", port, port.Value, port.Value, err)
// We can cast Setting.Value to a desired type including int, float64,
// bool and string. Method will return an error if Setting Value field
// can not be interpreted as desired type.
n, err := port.Int()
fmt.Printf("n has value: %v, type: %T, err: %v\n", n, n, err)
// Another syntax for getting Setting instance is to use Get() method
// which never returns errors. Get will return Setting with empty string
// in Value filed if requested key was now found.
d := config.Get("distance")
distance, err := d.Float64()
fmt.Printf("var distance has value: %v, type: %T, error value: %v\n", distance, distance, err)
// Get() syntax can be is slightly shorter if we're "sure" that key exists in
// the config.
sure := config.Get("iamsure").String() // String method never returns errors
// or simply sure := config.Get("iamsure").Value:
fmt.Println(sure)
// or like this:
fl, _ := config.Get("float").Float64() // we're dropping error here. Bad if value fails to convert.
fmt.Println("fl, _ := config.Get(\"float\").Float64() Value of var fl is:", fl)
// We can access comma-separated values of key like this:
servers := config.Get("servers").StringSlice()
fmt.Println("Found servers:")
for i, s := range servers {
fmt.Println(i+1, "\t", s)
}
// There is also a GetDefault() method which sets output Setting value
// to requested default if key was not found.
def := config.GetDefault("non-existant-key", "myvalue")
fmt.Println(def.Value) // "myvalue"
// You can use HasOption method to find whether single-word options were
// present in the the config
if config.HasOption("color") {
fmt.Println("Hooray, we've found option \"color\"!")
// do something useful
}
// Below code finds two keys with bool values in the Config
// and outputs those.
var t, f bool
t, _ = config.Get("booltrue").Bool()
f, _ = config.Get("bool0").Bool()
fmt.Printf("t's type is: %T, value: %v, f's type is: %T, value: %v\n\n", t, t, f, f)
}
```
Here is the full output of the above code:
```
distance 13.42
iamsure hopefully you're not mistaken
float 13.1984
port 10000
servers 10.0.0.1, 10.0.0.2, 10.0.0.3
bool0 0
booltrue true
color
variable port has type: conf.Setting, port.Value == 10000, type of port.Value is: string, error returned: <nil>
n has value: 10000, type: int, err: <nil>
var distance has value: 13.42, type: float64, error value: <nil>
hopefully you're not mistaken
fl, _ := config.Get("float").Float64() Value of var fl is: 13.1984
Found servers:
1 10.0.0.1
2 10.0.0.2
3 10.0.0.3
Hooray, we've found option "color"!
t's type is: bool, value: true, f's type is: bool, value: false
```
See example folder.