Repository: spf13/viper
Pull request #722 (2019-06-20 20:35:38)

Support config files with no extensions
Support config files with no extension.

Closes: spf13/viper#390
Closes: spf13/viper#336

Similar to https://github.com/spf13/viper/pull/357/files which appears to have been forgotten. 

Difference is: attempt a no extension right away instead of messing with the extension append and iterating through the extensions.

CLAassistant: [![CLA assistant check](https://cla-assistant.io/pull/badge/not_signed)](https://cla-assistant.io/spf13/viper?pullRequest=722) <br/>Thank you for your submission, we really appreciate it. Like many open source projects, we ask that you sign our [Contributor License Agreement](https://cla-assistant.io/spf13/viper?pullRequest=722) before we can accept your contribution.<br/><sub>You have signed the CLA already but the status is still pending? Let us [recheck](https://cla-assistant.io/check/spf13/viper?pullRequest=722) it.</sub>
GJRTimmer: Hi, I really want this, any news when it will be merged ?

@pedromss maybe a update of the README is required ?
Maybe some note that if using a config file without extension `viper.SetConfigType(...)` is required for viper to deserialize the config ?
pedromss: @GJRTimmer I don't know what else is required to get this approved. Seems like this repo isn't maintained judging by the state of the PR and issue section 

¯\_(ツ)_/¯
dmacvicar: I like this approach as well.
Please merge either this or my own https://github.com/spf13/viper/pull/357, which is open for more than 2 years.
sagikazarmark: @pedromss Just for me to understand: you want to explicitly set a config name with an extension?

```go
viper.SetConfigNam("config.yaml")
```

And you want to search for that specific file within the configured search paths?

Is that correct?
pedromss: @sagikazarmark no. The idea is to be able to have configuration files that don't have an extension in their name. As is many times the case with tools which have config files in the home folder for example.

So supporting `.toolconf` instead of `.toolconf.yaml`  is the goal of this PR
sagikazarmark: @pedromss  I see. Thanks for the explanation.
pedromss: @sagikazarmark please take another look now
sagikazarmark: Thanks @pedromss !
sagikazarmark: @pedromss actually, I wonder if the condition should be moved after the the loop, to make sure existing behavior isn't broken (eg. there is a config and a config.yaml file in a folder. this PR would break existing code in those cases).

WDYT?
pedromss: @sagikazarmark I had thought to resolve it immediately without "unnecessarily" walking the files in the folder. But what you say makes more sense.

Changed it.
sagikazarmark: Awesome, thanks!

This is a one time step, so I wouldn't worry about unnecessary checks for non-existing files.

Patch:
diff --git a/README.md b/README.md
index 171f51c..fe292d0 100644
--- a/README.md
+++ b/README.md
@@ -121,6 +121,8 @@ if err := viper.ReadInConfig(); err != nil {
 // Config file found and successfully parsed
 ```
 
+*NOTE:* You can also have a file without an extension and specify the format programmaticaly. For those configuration files that lie in the home of the user without any extension like `.bashrc`
+
 ### Writing Config Files
 
 Reading from config files is useful, but at times you want to store all modifications made at run time.
diff --git a/viper.go b/viper.go
index e9010bc..c64094a 100644
--- a/viper.go
+++ b/viper.go
@@ -1881,6 +1881,10 @@ func (v *Viper) searchInPath(in string) (filename string) {
 		}
 	}
 
+	if b, _ := exists(v.fs, filepath.Join(in, v.configName)); b {
+		return filepath.Join(in, v.configName)
+	}
+
 	return ""
 }
 


Test patch:
diff --git a/viper_test.go b/viper_test.go
index f8364a0..8dcfd1a 100644
--- a/viper_test.go
+++ b/viper_test.go
@@ -278,6 +278,22 @@ func TestBasics(t *testing.T) {
 	assert.NoError(t, err)
 }
 
+func TestSearchInPath(t *testing.T) {
+	filename := ".dotfilenoext"
+	path := "/tmp"
+	file := filepath.Join(path, filename)
+	SetConfigName(filename)
+	AddConfigPath(path)
+	_, createErr := v.fs.Create(file)
+	defer func() {
+		_ = v.fs.Remove(file)
+	}()
+	assert.NoError(t, createErr)
+	filename, err := v.getConfigFile()
+	assert.Equal(t, file, filename)
+	assert.NoError(t, err)
+}
+
 func TestDefault(t *testing.T) {
 	SetDefault("age", 45)
 	assert.Equal(t, 45, Get("age"))