Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support to save file with no extension #813

Merged
merged 4 commits into from Feb 19, 2020
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 11 additions & 4 deletions viper.go
Expand Up @@ -1418,11 +1418,18 @@ func (v *Viper) SafeWriteConfigAs(filename string) error {

func (v *Viper) writeConfig(filename string, force bool) error {
jww.INFO.Println("Attempting to write configuration to file.")
ext := filepath.Ext(filename)
if len(ext) <= 1 {
return fmt.Errorf("filename: %s requires valid extension", filename)
var configType string

if v.configType != "" {
configType = v.configType
} else {
ext := filepath.Ext(filename)
if len(ext) <= 1 {
return fmt.Errorf("filename: %s requires valid extension", filename)
}
configType = ext[1:]
}
configType := ext[1:]

if !stringInSlice(configType, SupportedExts) {
return UnsupportedConfigError(configType)
}
Expand Down
336 changes: 193 additions & 143 deletions viper_test.go
Expand Up @@ -1261,26 +1261,6 @@ var hclWriteExpected = []byte(`"foos" = {

"type" = "donut"`)

func TestWriteConfigHCL(t *testing.T) {
v := New()
fs := afero.NewMemMapFs()
v.SetFs(fs)
v.SetConfigName("c")
v.SetConfigType("hcl")
err := v.ReadConfig(bytes.NewBuffer(hclExample))
if err != nil {
t.Fatal(err)
}
if err := v.WriteConfigAs("c.hcl"); err != nil {
t.Fatal(err)
}
read, err := afero.ReadFile(fs, "c.hcl")
if err != nil {
t.Fatal(err)
}
assert.Equal(t, hclWriteExpected, read)
}

var jsonWriteExpected = []byte(`{
"batters": {
"batter": [
Expand All @@ -1304,122 +1284,13 @@ var jsonWriteExpected = []byte(`{
"type": "donut"
}`)

func TestWriteConfigJson(t *testing.T) {
v := New()
fs := afero.NewMemMapFs()
v.SetFs(fs)
v.SetConfigName("c")
v.SetConfigType("json")
err := v.ReadConfig(bytes.NewBuffer(jsonExample))
if err != nil {
t.Fatal(err)
}
if err := v.WriteConfigAs("c.json"); err != nil {
t.Fatal(err)
}
read, err := afero.ReadFile(fs, "c.json")
if err != nil {
t.Fatal(err)
}
assert.Equal(t, jsonWriteExpected, read)
}

var propertiesWriteExpected = []byte(`p_id = 0001
p_type = donut
p_name = Cake
p_ppu = 0.55
p_batters.batter.type = Regular
`)

func TestWriteConfigProperties(t *testing.T) {
v := New()
fs := afero.NewMemMapFs()
v.SetFs(fs)
v.SetConfigName("c")
v.SetConfigType("properties")
err := v.ReadConfig(bytes.NewBuffer(propertiesExample))
if err != nil {
t.Fatal(err)
}
if err := v.WriteConfigAs("c.properties"); err != nil {
t.Fatal(err)
}
read, err := afero.ReadFile(fs, "c.properties")
if err != nil {
t.Fatal(err)
}
assert.Equal(t, propertiesWriteExpected, read)
}

func TestWriteConfigTOML(t *testing.T) {
fs := afero.NewMemMapFs()
v := New()
v.SetFs(fs)
v.SetConfigName("c")
v.SetConfigType("toml")
err := v.ReadConfig(bytes.NewBuffer(tomlExample))
if err != nil {
t.Fatal(err)
}
if err := v.WriteConfigAs("c.toml"); err != nil {
t.Fatal(err)
}

// The TOML String method does not order the contents.
// Therefore, we must read the generated file and compare the data.
v2 := New()
v2.SetFs(fs)
v2.SetConfigName("c")
v2.SetConfigType("toml")
v2.SetConfigFile("c.toml")
err = v2.ReadInConfig()
if err != nil {
t.Fatal(err)
}

assert.Equal(t, v.GetString("title"), v2.GetString("title"))
assert.Equal(t, v.GetString("owner.bio"), v2.GetString("owner.bio"))
assert.Equal(t, v.GetString("owner.dob"), v2.GetString("owner.dob"))
assert.Equal(t, v.GetString("owner.organization"), v2.GetString("owner.organization"))
}

var dotenvWriteExpected = []byte(`
TITLE="DotEnv Write Example"
NAME=Oreo
KIND=Biscuit
`)

func TestWriteConfigDotEnv(t *testing.T) {
fs := afero.NewMemMapFs()
v := New()
v.SetFs(fs)
v.SetConfigName("c")
v.SetConfigType("env")
err := v.ReadConfig(bytes.NewBuffer(dotenvWriteExpected))
if err != nil {
t.Fatal(err)
}
if err := v.WriteConfigAs("c.env"); err != nil {
t.Fatal(err)
}

// The TOML String method does not order the contents.
// Therefore, we must read the generated file and compare the data.
v2 := New()
v2.SetFs(fs)
v2.SetConfigName("c")
v2.SetConfigType("env")
v2.SetConfigFile("c.env")
err = v2.ReadInConfig()
if err != nil {
t.Fatal(err)
}

assert.Equal(t, v.GetString("title"), v2.GetString("title"))
assert.Equal(t, v.GetString("type"), v2.GetString("type"))
assert.Equal(t, v.GetString("kind"), v2.GetString("kind"))
}

var yamlWriteExpected = []byte(`age: 35
beard: true
clothing:
Expand All @@ -1436,24 +1307,203 @@ hobbies:
name: steve
`)

func TestWriteConfigYAML(t *testing.T) {
v := New()
func TestWriteConfig(t *testing.T) {
fs := afero.NewMemMapFs()
v.SetFs(fs)
v.SetConfigName("c")
v.SetConfigType("yaml")
err := v.ReadConfig(bytes.NewBuffer(yamlExample))
if err != nil {
t.Fatal(err)
testCases := map[string]struct {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice refactor!

Currently, the config type is always inferred from the file name, not the original config type. So if the config type was yaml, but I provided a json file name, the output content was in JSON.

Can you please add a test case that verifies this is still the case?

Because (unfortunately) as far as I can tell, it is not.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just making sure I got the right idea here, are you saying to default on writeConfig to the extension file and if this is blank then use the setting for config type?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup. At least, that's compatible with the current behaviour, taking the original configType by default is not.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha 👍

configName string
configType string
fileName string
input []byte
expectedContent []byte
}{
"hcl with file extension": {
configName: "c",
configType: "hcl",
fileName: "c.hcl",
input: hclExample,
expectedContent: hclWriteExpected,
},
"hcl without file extension": {
configName: "c",
configType: "hcl",
fileName: "c",
input: hclExample,
expectedContent: hclWriteExpected,
},
"json with file extension": {
configName: "c",
configType: "json",
fileName: "c.json",
input: jsonExample,
expectedContent: jsonWriteExpected,
},
"json without file extension": {
configName: "c",
configType: "json",
fileName: "c",
input: jsonExample,
expectedContent: jsonWriteExpected,
},
"properties with file extension": {
configName: "c",
configType: "properties",
fileName: "c.properties",
input: propertiesExample,
expectedContent: propertiesWriteExpected,
},
"properties without file extension": {
configName: "c",
configType: "properties",
fileName: "c",
input: propertiesExample,
expectedContent: propertiesWriteExpected,
},
"yaml with file extension": {
configName: "c",
configType: "yaml",
fileName: "c.yaml",
input: yamlExample,
expectedContent: yamlWriteExpected,
},
"yaml without file extension": {
configName: "c",
configType: "yaml",
fileName: "c",
input: yamlExample,
expectedContent: yamlWriteExpected,
},
}
if err := v.WriteConfigAs("c.yaml"); err != nil {
t.Fatal(err)
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
v := New()
v.SetFs(fs)
v.SetConfigName(tc.fileName)
v.SetConfigType(tc.configType)

err := v.ReadConfig(bytes.NewBuffer(tc.input))
if err != nil {
t.Fatal(err)
}
if err := v.WriteConfigAs(tc.fileName); err != nil {
t.Fatal(err)
}
read, err := afero.ReadFile(fs, tc.fileName)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, tc.expectedContent, read)
})
}
read, err := afero.ReadFile(fs, "c.yaml")
if err != nil {
t.Fatal(err)
}

func TestWriteConfigTOML(t *testing.T) {
fs := afero.NewMemMapFs()

testCases := map[string]struct {
configName string
configType string
fileName string
input []byte
}{
"with file extension": {
configName: "c",
configType: "toml",
fileName: "c.toml",
input: tomlExample,
},
"without file extension": {
configName: "c",
configType: "toml",
fileName: "c",
input: tomlExample,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
v := New()
v.SetFs(fs)
v.SetConfigName(tc.configName)
v.SetConfigType(tc.configType)
err := v.ReadConfig(bytes.NewBuffer(tc.input))
if err != nil {
t.Fatal(err)
}
if err := v.WriteConfigAs(tc.fileName); err != nil {
t.Fatal(err)
}

// The TOML String method does not order the contents.
// Therefore, we must read the generated file and compare the data.
v2 := New()
v2.SetFs(fs)
v2.SetConfigName(tc.configName)
v2.SetConfigType(tc.configType)
v2.SetConfigFile(tc.fileName)
err = v2.ReadInConfig()
if err != nil {
t.Fatal(err)
}

assert.Equal(t, v.GetString("title"), v2.GetString("title"))
assert.Equal(t, v.GetString("owner.bio"), v2.GetString("owner.bio"))
assert.Equal(t, v.GetString("owner.dob"), v2.GetString("owner.dob"))
assert.Equal(t, v.GetString("owner.organization"), v2.GetString("owner.organization"))
})
}
}

func TestWriteConfigDotEnv(t *testing.T) {
fs := afero.NewMemMapFs()
testCases := map[string]struct {
configName string
configType string
fileName string
input []byte
}{
"with file extension": {
configName: "c",
configType: "env",
fileName: "c.env",
input: dotenvExample,
},
"without file extension": {
configName: "c",
configType: "env",
fileName: "c",
input: dotenvExample,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
v := New()
v.SetFs(fs)
v.SetConfigName(tc.configName)
v.SetConfigType(tc.configType)
err := v.ReadConfig(bytes.NewBuffer(tc.input))
if err != nil {
t.Fatal(err)
}
if err := v.WriteConfigAs(tc.fileName); err != nil {
t.Fatal(err)
}

// The TOML String method does not order the contents.
// Therefore, we must read the generated file and compare the data.
v2 := New()
v2.SetFs(fs)
v2.SetConfigName(tc.configName)
v2.SetConfigType(tc.configType)
v2.SetConfigFile(tc.fileName)
err = v2.ReadInConfig()
if err != nil {
t.Fatal(err)
}

assert.Equal(t, v.GetString("title_dotenv"), v2.GetString("title_dotenv"))
assert.Equal(t, v.GetString("type_dotenv"), v2.GetString("type_dotenv"))
assert.Equal(t, v.GetString("kind_dotenv"), v2.GetString("kind_dotenv"))
})
}
assert.Equal(t, yamlWriteExpected, read)
}

func TestSafeWriteConfig(t *testing.T) {
Expand Down