Skip to content

Commit

Permalink
Merge pull request #6 from golobby/yaml-feeder
Browse files Browse the repository at this point in the history
adds yaml feeder
  • Loading branch information
miladrahimi authored Jul 23, 2020
2 parents 48c2c22 + 6db5eb3 commit 0ee4600
Show file tree
Hide file tree
Showing 6 changed files with 198 additions and 19 deletions.
86 changes: 86 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Feeders provide content of the configuration. Currently, these feeders exist out
* `Map`: Feeds a simple `map[string]interface{}`.
* `Json`: Feeds a JSON file.
* `JsonDirectory`: Feeds a directory of JSON files.
* `Yaml`: Feeds a Yaml file.

Of course, you are free to implement your feeders by implementing the `Feeder` interface.

Expand Down Expand Up @@ -126,6 +127,47 @@ v, err := c.Get("version") // 3.14

v, err := c.Get("numbers.2") // 3

v, err := c.Get("users.0.address.city") // Delfan
```
#### Feeding using Yaml feeder

Yaml files are a trend these days, so why not store configurations in them ?

`config.yaml`:

```yaml
name: MyAppUsingGoLobbyConfig
version: 3.14
numbers:
- 1
- 2
- 3
users:
- name: Milad Rahimi
year: 1993
address:
country: Iran
state: Lorestan
city: Delfan
- name: Amirreza Askarpour
year: 1998
address:
country: Iran
state: Khouzestan
city: Ahvaz
```
Go code:
```go
c, err := config.New(config.Options{
Feeder: feeder.Yaml{Path: "path/to/config.yaml"},
})

v, err := c.Get("version") // 3.14

v, err := c.Get("numbers.2") // 3

v, err := c.Get("users.0.address.city") // Delfan
```

Expand Down Expand Up @@ -172,6 +214,50 @@ v, err := c.Get("app.version") // 3.14
v, err := c.Get("db.mysql.host") // localhost
```

#### Feeding using YamlDirectory

If you have many configuration data and it doesn't fit in a single YAML file.
In this case, you can use multiple YAML files and feed them using YamlDirectory feeder like this example:

Sample project directory structure:

```
- main.go
- config
- - app.yaml
- - db.yaml
```

`app.yaml`:

```yaml
name: MyApp
version: 3.14
```
`db.yaml`:

```yaml
sqlite:
path: app.db
mysql:
host: localhost
user: root
pass: secret
```

Go code:

```go
c, err := config.New(config.Options{
Feeder: feeder.YamlDirectory{Path: "config"},
})
v, err := c.Get("app.version") // 3.14
v, err := c.Get("db.mysql.host") // localhost
```
### OS variables and environment files

#### OS variables
Expand Down
60 changes: 42 additions & 18 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ package config

import (
"errors"
"github.com/golobby/config/env"
"fmt"
"os"
"os/signal"
"strconv"
"strings"
"sync"
"syscall"

"github.com/golobby/config/env"
)

// Feeder is an interface for config feeders that provide content of a config instance.
Expand All @@ -23,7 +25,21 @@ type Options struct {
Feeder Feeder // Feeder is the feeder that is going to feed the Config instance.
Env string // Env is the file path that locates the environment file.
}

//NotFoundError happens when you try to access a key which is not defined in the configuration files.
type NotFoundError struct{
key string
}
func (n *NotFoundError) Error() string {
return fmt.Sprintf("value not found for the key %s", n.key)
}
//TypeError happens when you try to access a key using a helper function that casts value to a type which can't be done.
type TypeError struct {
value interface{}
wanted string
}
func (t *TypeError) Error() string {
return fmt.Sprintf("value %s (%T) is not %s", t.value, t.value, t.wanted)
}
// Config keeps all the Config instance data.
type Config struct {
env struct {
Expand Down Expand Up @@ -64,7 +80,7 @@ func (c *Config) ReloadEnv() error {
}

// GetEnv returns the environment variable value for the given environment variable key.
func (c Config) GetEnv(key string) string {
func (c *Config) GetEnv(key string) string {
c.env.sync.RLock()
defer c.env.sync.RUnlock()

Expand All @@ -78,7 +94,7 @@ func (c Config) GetEnv(key string) string {
}

// GetAllEnvs returns all the environment variables (key/values)
func (c Config) GetAllEnvs() map[string]string {
func (c *Config) GetAllEnvs() map[string]string {
return c.env.items
}

Expand Down Expand Up @@ -154,7 +170,7 @@ func (c *Config) Set(key string, value interface{}) {
// The return type is "interface{}".
// It probably needs to be cast to the related data type.
// It returns an error if there is no value for the given key.
func (c Config) Get(key string) (interface{}, error) {
func (c *Config) Get(key string) (interface{}, error) {
c.sync.RLock()
defer c.sync.RUnlock()

Expand All @@ -165,7 +181,7 @@ func (c Config) Get(key string) (interface{}, error) {
}

if strings.Contains(key, ".") == false {
return nil, errors.New("value not found for the key " + key)
return nil, &NotFoundError{key: key}
}

v, err := lookup(c.items, key)
Expand All @@ -174,15 +190,15 @@ func (c Config) Get(key string) (interface{}, error) {
}

// GetAll returns all the configuration items (key/values).
func (c Config) GetAll() map[string]interface{} {
func (c *Config) GetAll() map[string]interface{} {
return c.items
}

// GetString returns the value of the given key.
// It also casts the value type to string internally.
// It returns an error if the related value is not a string.
// It returns an error if there is no value for the given key.
func (c Config) GetString(key string) (string, error) {
func (c *Config) GetString(key string) (string, error) {
v, err := c.Get(key)
if err != nil {
return "", err
Expand All @@ -192,14 +208,14 @@ func (c Config) GetString(key string) (string, error) {
return v, nil
}

return "", errors.New("value for " + key + " is not string")
return "", &TypeError{value: v, wanted: "string"}
}

// GetInt returns the value of the given key.
// It also casts the value type to int internally.
// It returns an error if the related value is not an int.
// It returns an error if there is no value for the given key.
func (c Config) GetInt(key string) (int, error) {
func (c *Config) GetInt(key string) (int, error) {
v, err := c.Get(key)
if err != nil {
return 0, err
Expand All @@ -209,14 +225,14 @@ func (c Config) GetInt(key string) (int, error) {
return v, nil
}

return 0, errors.New("value for " + key + " is not int")
return 0, &TypeError{value: v, wanted: "int"}
}

// GetFloat returns the value of the given key.
// It also casts the value type to float64 internally.
// It returns an error if the related value is not a float64.
// It returns an error if there is no value for the given key.
func (c Config) GetFloat(key string) (float64, error) {
func (c *Config) GetFloat(key string) (float64, error) {
v, err := c.Get(key)
if err != nil {
return 0, err
Expand All @@ -226,15 +242,15 @@ func (c Config) GetFloat(key string) (float64, error) {
return v, nil
}

return 0, errors.New("value for " + key + " is not float")
return 0, &TypeError{value: v, wanted: "float"}
}

// GetBool returns the value of the given key.
// It also casts the value type to bool internally.
// It converts the "true" and "false" string values to related boolean values.
// It returns an error if the related value is not a bool.
// It returns an error if there is no value for the given key.
func (c Config) GetBool(key string) (bool, error) {
func (c *Config) GetBool(key string) (bool, error) {
v, err := c.Get(key)
if err != nil {
return false, err
Expand All @@ -252,15 +268,15 @@ func (c Config) GetBool(key string) (bool, error) {
}
}

return false, errors.New("value for " + key + " is not bool")
return false, &TypeError{value: v, wanted: "bool"}
}

// GetStrictBool returns the value of the given key.
// It also casts the value type to bool internally.
// It doesn't convert the "true" and "false" string values to related boolean values.
// It returns an error if the related value is not a bool.
// It returns an error if there is no value for the given key.
func (c Config) GetStrictBool(key string) (bool, error) {
func (c *Config) GetStrictBool(key string) (bool, error) {
v, err := c.Get(key)
if err != nil {
return false, err
Expand All @@ -270,11 +286,11 @@ func (c Config) GetStrictBool(key string) (bool, error) {
return v, nil
}

return false, errors.New("value for " + key + " is not bool")
return false, &TypeError{value: v, wanted: "bool"}
}

// parse replaces the placeholders with environment and OS variables.
func (c Config) parse(value interface{}) interface{} {
func (c *Config) parse(value interface{}) interface{} {
if stmt, ok := value.(string); ok {
if len(stmt) > 3 && stmt[0:2] == "${" && stmt[len(stmt)-1:] == "}" {
pipe := strings.Index(stmt, "|")
Expand All @@ -297,6 +313,10 @@ func (c Config) parse(value interface{}) interface{} {
}

return collection
} else if collection, ok := value.(map[interface{}]interface{}); ok {
for k, v := range collection {
collection[k] = c.parse(v)
}
}

return value
Expand All @@ -321,6 +341,10 @@ func lookup(collection interface{}, key string) (interface{}, error) {
// find returns the value of given key in the given 1D collection
func find(collection interface{}, key string) (interface{}, error) {
switch collection.(type) {
case map[interface{}]interface{}:
if v, ok := collection.(map[interface{}]interface{})[key]; ok {
return v, nil
}
case map[string]interface{}:
if v, ok := collection.(map[string]interface{})[key]; ok {
return v, nil
Expand Down
24 changes: 24 additions & 0 deletions feeder/yaml.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package feeder

import (
"gopkg.in/yaml.v2"
"io/ioutil"
)

type Yaml struct {
Path string
}

func (y *Yaml) Feed() (map[string]interface{}, error) {
bs, err := ioutil.ReadFile(y.Path)
if err != nil {
return nil, err
}
items := make(map[string]interface{})

err = yaml.Unmarshal(bs, items)
if err != nil {
return nil, err
}
return items, nil
}
40 changes: 40 additions & 0 deletions feeder/yaml_directory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package feeder

import (
"io/ioutil"
"path/filepath"
"strings"
)

// YamlDirectory is a feeder that feeds using a directory of yaml files.
type YamlDirectory struct {
Path string
}

// Feed returns all the content.
func (yd YamlDirectory) Feed() (map[string]interface{}, error) {
files, err := ioutil.ReadDir(yd.Path)
if err != nil {
return nil, err
}

all := map[string]interface{}{}

for _, f := range files {
if f.IsDir() {
continue
}

j := Yaml{Path: filepath.Join(yd.Path, string(filepath.Separator), f.Name())}

items, err := j.Feed()
if err != nil {
return nil, err
}

k := strings.Split(f.Name(), ".")[0]
all[k] = items
}

return all, nil
}
5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,7 @@ module github.com/golobby/config

go 1.11

require github.com/stretchr/testify v1.4.0
require (
github.com/stretchr/testify v1.4.0
gopkg.in/yaml.v2 v2.3.0
)
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

0 comments on commit 0ee4600

Please sign in to comment.