From 9cd0a9f663a05492e0e6fff7cfd7bd45f1d1d7f5 Mon Sep 17 00:00:00 2001 From: patdhlk Date: Fri, 11 Dec 2015 23:51:11 +0100 Subject: [PATCH 1/4] add support for hcl --- util.go | 10 +++++++++ viper.go | 4 ++-- viper_test.go | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 2 deletions(-) diff --git a/util.go b/util.go index dc60a44..0cc4553 100644 --- a/util.go +++ b/util.go @@ -22,6 +22,7 @@ import ( "unicode" "github.com/BurntSushi/toml" + "github.com/hashicorp/hcl" "github.com/magiconair/properties" "github.com/spf13/cast" jww "github.com/spf13/jwalterweatherman" @@ -144,6 +145,15 @@ func unmarshallConfigReader(in io.Reader, c map[string]interface{}, configType s return ConfigParseError{err} } + case "hcl": + obj, err := hcl.Parse(string(buf.Bytes())) + if err != nil { + return ConfigParseError{err} + } + if err = hcl.DecodeObject(&c, obj); err != nil { + return ConfigParseError{err} + } + case "toml": if _, err := toml.Decode(buf.String(), &c); err != nil { return ConfigParseError{err} diff --git a/viper.go b/viper.go index 9727f48..0f3d258 100644 --- a/viper.go +++ b/viper.go @@ -179,7 +179,7 @@ func New() *Viper { // can use it in their testing as well. func Reset() { v = New() - SupportedExts = []string{"json", "toml", "yaml", "yml"} + SupportedExts = []string{"json", "toml", "yaml", "yml", "hcl"} SupportedRemoteProviders = []string{"etcd", "consul"} } @@ -218,7 +218,7 @@ type RemoteProvider interface { } // Universally supported extensions. -var SupportedExts []string = []string{"json", "toml", "yaml", "yml", "properties", "props", "prop"} +var SupportedExts []string = []string{"json", "toml", "yaml", "yml", "properties", "props", "prop", "hcl"} // Universally supported remote providers. var SupportedRemoteProviders []string = []string{"etcd", "consul"} diff --git a/viper_test.go b/viper_test.go index d9aae00..2112de6 100644 --- a/viper_test.go +++ b/viper_test.go @@ -60,6 +60,24 @@ var jsonExample = []byte(`{ } }`) +var hclExample = []byte(` +id = "0001" +type = "donut" +name = "Cake" +ppu = 0.55 +batter { + type = "Regular" +} +batter { + type = "Chocolate" +} +batter { + type = "Blueberry" +} +batter { + type = "Devil's Food" +}`) + var propertiesExample = []byte(` p_id: 0001 p_type: donut @@ -95,6 +113,10 @@ func initConfigs() { SetConfigType("json") remote := bytes.NewReader(remoteExample) unmarshalReader(remote, v.kvstore) + + SetConfigType("hcl") + r = bytes.NewReader(hclExample) + unmarshalReader(r, v.config) } func initYAML() { @@ -129,6 +151,14 @@ func initTOML() { unmarshalReader(r, v.config) } +func initHcl() { + Reset() + SetConfigType("hcl") + r := bytes.NewReader(hclExample) + + unmarshalReader(r, v.config) +} + // make directories for testing func initDirs(t *testing.T) (string, string, func()) { @@ -270,6 +300,36 @@ func TestTOML(t *testing.T) { assert.Equal(t, "TOML Example", Get("title")) } +func TestHCL(t *testing.T) { + initHcl() + assert.Equal(t, "0001", Get("id")) + assert.Equal(t, 0.55, Get("ppu")) + assert.Equal(t, "donut", Get("type")) + assert.Equal(t, "Cake", Get("name")) + Set("id", "0002") + assert.Equal(t, "0002", Get("id")) + assert.NotEqual(t, "cronut", Get("type")) +} + +func TestHCLList(t *testing.T) { + initHcl() + batters := []map[string]interface{}{ + map[string]interface{}{ + "type": "Regular", + }, + map[string]interface{}{ + "type": "Chocolate", + }, + map[string]interface{}{ + "type": "Blueberry", + }, + map[string]interface{}{ + "type": "Devil's Food", + }, + } + assert.Equal(t, batters, Get("batter")) +} + func TestRemotePrecedence(t *testing.T) { initJSON() From c97c5cf59e724e0c2827eb3bc7db6e37d504c22c Mon Sep 17 00:00:00 2001 From: patdhlk Date: Sat, 12 Dec 2015 00:27:07 +0100 Subject: [PATCH 2/4] changed the test hcl config --- viper_test.go | 40 +++++----------------------------------- 1 file changed, 5 insertions(+), 35 deletions(-) diff --git a/viper_test.go b/viper_test.go index 2112de6..394bf57 100644 --- a/viper_test.go +++ b/viper_test.go @@ -65,18 +65,7 @@ id = "0001" type = "donut" name = "Cake" ppu = 0.55 -batter { - type = "Regular" -} -batter { - type = "Chocolate" -} -batter { - type = "Blueberry" -} -batter { - type = "Devil's Food" -}`) +`) var propertiesExample = []byte(` p_id: 0001 @@ -102,6 +91,10 @@ func initConfigs() { r = bytes.NewReader(jsonExample) unmarshalReader(r, v.config) + SetConfigType("hcl") + r = bytes.NewReader(hclExample) + unmarshalReader(r, v.config) + SetConfigType("properties") r = bytes.NewReader(propertiesExample) unmarshalReader(r, v.config) @@ -113,10 +106,6 @@ func initConfigs() { SetConfigType("json") remote := bytes.NewReader(remoteExample) unmarshalReader(remote, v.kvstore) - - SetConfigType("hcl") - r = bytes.NewReader(hclExample) - unmarshalReader(r, v.config) } func initYAML() { @@ -311,25 +300,6 @@ func TestHCL(t *testing.T) { assert.NotEqual(t, "cronut", Get("type")) } -func TestHCLList(t *testing.T) { - initHcl() - batters := []map[string]interface{}{ - map[string]interface{}{ - "type": "Regular", - }, - map[string]interface{}{ - "type": "Chocolate", - }, - map[string]interface{}{ - "type": "Blueberry", - }, - map[string]interface{}{ - "type": "Devil's Food", - }, - } - assert.Equal(t, batters, Get("batter")) -} - func TestRemotePrecedence(t *testing.T) { initJSON() From d48326bfe9daf5a01d413327a9554f9a4c9a1238 Mon Sep 17 00:00:00 2001 From: patdhlk Date: Sun, 13 Dec 2015 21:37:32 +0100 Subject: [PATCH 3/4] add test structure for hcl --- viper_test.go | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/viper_test.go b/viper_test.go index 394bf57..18ce40e 100644 --- a/viper_test.go +++ b/viper_test.go @@ -65,7 +65,20 @@ id = "0001" type = "donut" name = "Cake" ppu = 0.55 -`) +foos { + foo { + key = 1 + } + foo { + key = 2 + } + foo { + key = 3 + } + foo { + key = 4 + } +}`) var propertiesExample = []byte(` p_id: 0001 @@ -387,9 +400,9 @@ func TestSetEnvReplacer(t *testing.T) { func TestAllKeys(t *testing.T) { initConfigs() - ks := sort.StringSlice{"title", "newkey", "owner", "name", "beard", "ppu", "batters", "hobbies", "clothing", "age", "hacker", "id", "type", "eyes", "p_id", "p_ppu", "p_batters.batter.type", "p_type", "p_name"} + ks := sort.StringSlice{"title", "newkey", "owner", "name", "beard", "ppu", "batters", "hobbies", "clothing", "age", "hacker", "id", "type", "eyes", "p_id", "p_ppu", "p_batters.batter.type", "p_type", "p_name", "foos"} dob, _ := time.Parse(time.RFC3339, "1979-05-27T07:32:00Z") - all := map[string]interface{}{"owner": map[string]interface{}{"organization": "MongoDB", "Bio": "MongoDB Chief Developer Advocate & Hacker at Large", "dob": dob}, "title": "TOML Example", "ppu": 0.55, "eyes": "brown", "clothing": map[interface{}]interface{}{"trousers": "denim", "jacket": "leather", "pants": map[interface{}]interface{}{"size": "large"}}, "id": "0001", "batters": map[string]interface{}{"batter": []interface{}{map[string]interface{}{"type": "Regular"}, map[string]interface{}{"type": "Chocolate"}, map[string]interface{}{"type": "Blueberry"}, map[string]interface{}{"type": "Devil's Food"}}}, "hacker": true, "beard": true, "hobbies": []interface{}{"skateboarding", "snowboarding", "go"}, "age": 35, "type": "donut", "newkey": "remote", "name": "Cake", "p_id": "0001", "p_ppu": "0.55", "p_name": "Cake", "p_batters.batter.type": "Regular", "p_type": "donut"} + all := map[string]interface{}{"owner": map[string]interface{}{"organization": "MongoDB", "Bio": "MongoDB Chief Developer Advocate & Hacker at Large", "dob": dob}, "title": "TOML Example", "ppu": 0.55, "eyes": "brown", "clothing": map[interface{}]interface{}{"trousers": "denim", "jacket": "leather", "pants": map[interface{}]interface{}{"size": "large"}}, "id": "0001", "batters": map[string]interface{}{"batter": []interface{}{map[string]interface{}{"type": "Regular"}, map[string]interface{}{"type": "Chocolate"}, map[string]interface{}{"type": "Blueberry"}, map[string]interface{}{"type": "Devil's Food"}}}, "hacker": true, "beard": true, "hobbies": []interface{}{"skateboarding", "snowboarding", "go"}, "age": 35, "type": "donut", "newkey": "remote", "name": "Cake", "p_id": "0001", "p_ppu": "0.55", "p_name": "Cake", "p_batters.batter.type": "Regular", "p_type": "donut", "foos": []map[string]interface{}{map[string]interface{}{"foo": []map[string]interface{}{map[string]interface{}{"key": 1}, map[string]interface{}{"key": 2}, map[string]interface{}{"key": 3}, map[string]interface{}{"key": 4}}}}} var allkeys sort.StringSlice allkeys = AllKeys() @@ -618,6 +631,24 @@ func TestFindsNestedKeys(t *testing.T) { "clothing.trousers": "denim", "owner.dob": dob, "beard": true, + "foos": []map[string]interface{}{ + map[string]interface{}{ + "foo": []map[string]interface{}{ + map[string]interface{}{ + "key": 1, + }, + map[string]interface{}{ + "key": 2, + }, + map[string]interface{}{ + "key": 3, + }, + map[string]interface{}{ + "key": 4, + }, + }, + }, + }, } for key, expectedValue := range expected { From fc04ee8c80faab511e12f11bf6d58ec84868f614 Mon Sep 17 00:00:00 2001 From: patdhlk Date: Sun, 13 Dec 2015 21:47:41 +0100 Subject: [PATCH 4/4] add HCL support to the README file --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f86abfd..5733b98 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ to work within an application, and can handle all types of configuration needs and formats. It supports: * setting defaults -* reading from JSON, TOML, and YAML config files +* reading from JSON, TOML, YAML and HCL config files * live watching and re-reading of config files (optional) * reading from environment variables * reading from remote config systems (etcd or Consul), and watching changes @@ -31,7 +31,7 @@ Viper is here to help with that. Viper does the following for you: -1. Find, load, and unmarshal a configuration file in JSON, TOML, or YAML. +1. Find, load, and unmarshal a configuration file in JSON, TOML, YAML or HCL. 2. Provide a mechanism to set default values for your different configuration options. 3. Provide a mechanism to set override values for options specified through @@ -72,7 +72,7 @@ viper.SetDefault("Taxonomies", map[string]string{"tag": "tags", "category": "cat ### Reading Config Files Viper requires minimal configuration so it knows where to look for config files. -Viper supports JSON, TOML and YAML files. Viper can search multiple paths, but +Viper supports JSON, TOML, YAML and HCL files. Viper can search multiple paths, but currently a single Viper instance only supports a single configuration file. Viper does not default to any configuration search paths leaving defaults decision to an application. @@ -242,7 +242,7 @@ package: `import _ "github.com/spf13/viper/remote"` -Viper will read a config string (as JSON, TOML, or YAML) retrieved from a path +Viper will read a config string (as JSON, TOML, YAML or HCL) retrieved from a path in a Key/Value store such as etcd or Consul. These values take precedence over default values, but are overridden by configuration values retrieved from disk, flags, or environment variables.