Skip to content

Commit 098be06

Browse files
authored
Easy-to-notice type name of config struct (#22)
* fix: stylecheck ST1017 - no Yoda conditions - ST1017: don't use Yoda conditions () * update: go.mod (Cobra version) * feat: enable more linters (golangci-lint) * feat: TConfigApp -> TConfigFile * feat: linter (gospec, prealloc and predeclared) * chore: add reference URL for lint errors
1 parent 9df541f commit 098be06

File tree

11 files changed

+101
-70
lines changed

11 files changed

+101
-70
lines changed

.github/golangci.yml

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ run:
77
# include test files or not, default is true
88
tests: true
99

10+
fast: true
11+
1012
# list of build tags, all linters use it. Default is empty list.
1113
build-tags:
1214
- golangci
@@ -38,9 +40,10 @@ output:
3840
# enabled linters. Conf settings follows.
3941
linters:
4042
enable:
43+
- deadcode # Finds unused code
4144
- dogsled
4245
- dupl
43-
- errcheck
46+
- errcheck # Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases
4447
- funlen
4548
- gocognit
4649
- gocritic
@@ -50,8 +53,11 @@ linters:
5053
- gofmt
5154
- gofumpt # Stricter gofmt
5255
- golint
53-
- govet
56+
- gosec # Inspects source code for security problems
57+
- gosimple # Linter for Go source code that specializes in simplifying a code
58+
- govet # Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string
5459
- ifshort
60+
- ineffassign # Detects when assignments to existing variables are not used
5561
- lll
5662
- maligned
5763
- megacheck # gosimple, staticcheck, unused
@@ -60,9 +66,17 @@ linters:
6066
- nestif # Limit if statements nested deep
6167
- nolintlint # Don't allow 'nolint' annotation w/out a description
6268
- nlreturn # Requires a new line before return
69+
- prealloc # Finds slice declarations that could potentially be preallocated
70+
- predeclared # Find code that shadows one of Go's predeclared identifiers
71+
- staticcheck # Staticcheck is a go vet on steroids, applying a ton of static analysis checks
72+
- structcheck # Finds unused struct fields
73+
- stylecheck
74+
- typecheck # Like the front-end of a Go compiler, parses and type-checks Go code
6375
- unconvert # Detect unnecessary type conversions
76+
- unused # Checks Go code for unused constants, variables, functions and types
6477
#- testpackage # Limit use unexported functions and methods from source code in tests
6578
- thelper # Enforce to call 't.Helper()' in the test helper function
79+
- varcheck # Finds unused global variables and constants
6680
- whitespace # Limit unnecessary newlines at the start and end of functions, if, for, etc
6781
- wsl # Enforce empty lines at the right places to make code more readable.
6882

.github/run-tests-lint.sh

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
# Constants
1111
# -----------------------------------------------------------------------------
1212
PATH_DIR_PARENT="$(dirname "$(cd "$(dirname "${BASH_SOURCE:-$0}")" && pwd)")"
13+
PATH_FILE_CONF_GOLANGCILINT="${PATH_DIR_PARENT}/.github/golangci.yml"
1314
SUCCESS=0
1415
FAILURE=1
1516

@@ -24,11 +25,16 @@ cd "${PATH_DIR_PARENT}" || {
2425
exit $FAILURE
2526
}
2627

27-
if ! golangci-lint run --config ./.github/golangci.yml ./...; then
28+
echo "${@}" | grep -e "-l" -e "--linters" >/dev/null && {
29+
golangci-lint linters --config "$PATH_FILE_CONF_GOLANGCILINT" ./...
30+
}
31+
32+
if ! golangci-lint run --config "$PATH_FILE_CONF_GOLANGCILINT" ./...; then
2833
echo >&2
29-
echo >&2 'References for debugging:'
30-
echo >&2 ' wsl : https://github.com/bombsimon/wsl/blob/master/doc/rules.md'
31-
echo >&2 ' gofumpt: https://github.com/mvdan/gofumpt#added-rules'
34+
echo >&2 'References for debugging lint error:'
35+
echo >&2 ' wsl : https://github.com/bombsimon/wsl/blob/master/doc/rules.md'
36+
echo >&2 ' gofumpt : https://github.com/mvdan/gofumpt#added-rules'
37+
echo >&2 ' * for other lint errors see: https://golangci-lint.run/usage/linters/'
3238
exit $FAILURE
3339
fi
3440

cmd/cmd_helloExtended.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,14 @@ func getMsgToGreet(args []string) string {
6363
argJoined string = strings.TrimSpace(strings.Join(args, " ")) // get arg value
6464
)
6565

66-
if "" != greetTo && "" != argJoined {
66+
if greetTo != "" && argJoined != "" {
6767
to = greetTo + " and " + argJoined
6868
} else {
69-
if "" != greetTo {
69+
if greetTo != "" {
7070
to = greetTo
7171
}
7272

73-
if "" != argJoined {
73+
if argJoined != "" {
7474
to = argJoined
7575
}
7676
}

cmd/cmd_helloExtended_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,16 +113,16 @@ func convertShellArgsToSlice(t *testing.T, str string) []string {
113113
t.Fatalf("args parse error: %+v\n", err)
114114
}
115115

116-
if 0 == len(cmdArgs) {
116+
if len(cmdArgs) == 0 {
117117
t.Fatalf("args parse error. Command contains fatal strings: %+v\n", str)
118118
}
119119

120120
// `hello cmd` dependent check
121-
if "hello" != cmdArgs[0] {
121+
if cmdArgs[0] != "hello" {
122122
t.Fatal("format error. The command must start with 'hello'.")
123123
}
124124

125-
if "ext" != cmdArgs[1] {
125+
if cmdArgs[1] != "ext" {
126126
t.Fatal("format error. The command must start with 'hello cmd'.")
127127
}
128128

cmd/cmd_root.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,12 +78,12 @@ func init() {}
7878

7979
// loadConfig sets the object in the arg with the results exits with an error if user defined conf file didn't exist.
8080
// Otherwise searches the default file and if not found then use the default value.
81-
func loadConfig(configApp *conf.TConfigApp, configUser interface{}) {
81+
func loadConfig(configApp *conf.TConfigFile, configUser interface{}) {
8282
// Overwrite "configUser" with conf file value if file found.
83-
if err := conf.LoadConfig(*configApp, &configUser); err != nil {
83+
if err := conf.LoadFile(*configApp, &configUser); err != nil {
8484
// Exits if user defined conf file fails to read
85-
if "" != configApp.PathFileConf {
86-
msg := fmt.Errorf("Failed to read configuration file.\n Error msg: %v", err)
85+
if configApp.PathFileConf != "" {
86+
msg := fmt.Errorf("failed to read configuration file.\n Error msg: %v", err)
8787
osExit(EchoStdErrIfError(msg))
8888
}
8989
// Conf file not found. Using default. Set flag to true.

cmd/cmd_root_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ func Test_loadConfig_UserDefinedPath_Fails(t *testing.T) {
1919
expectExitCode int
2020
actualExitCode int = 0 // This should turn into 1
2121

22-
confAppDummy conf.TConfigApp
22+
confAppDummy conf.TConfigFile
2323
confUserDummy struct {
2424
NameToGreet string `mapstructure:"name_to_greet"` // // Dont'f forget to define `mapstructure`
2525
}
@@ -32,7 +32,7 @@ func Test_loadConfig_UserDefinedPath_Fails(t *testing.T) {
3232

3333
var capturedMsg string = capturer.CaptureStderr(func() {
3434
// Test user defined bad (non-existing) file path
35-
confAppDummy = conf.TConfigApp{
35+
confAppDummy = conf.TConfigFile{
3636
PathFileConf: "./foobar.json",
3737
PathDirConf: "",
3838
NameFileConf: "",
@@ -48,7 +48,7 @@ func Test_loadConfig_UserDefinedPath_Fails(t *testing.T) {
4848
"If user defined path doesn't exist then should exit with 1. Captured STDERR: "+capturedMsg,
4949
)
5050
// containing error message assertion
51-
assert.Contains(t, strings.TrimSpace(capturedMsg), "Failed to read configuration file")
51+
assert.Contains(t, strings.TrimSpace(capturedMsg), "failed to read configuration file")
5252
}
5353

5454
func Test_loadConfig_UseDefault(t *testing.T) {
@@ -63,7 +63,7 @@ func Test_loadConfig_UseDefault(t *testing.T) {
6363
expectFlag bool
6464
actualFlag bool
6565

66-
confAppDummy conf.TConfigApp
66+
confAppDummy conf.TConfigFile
6767
confUserDummy struct {
6868
NameToGreet string `mapstructure:"name_to_greet"` // // Dont'f forget to define `mapstructure`
6969
}
@@ -76,7 +76,7 @@ func Test_loadConfig_UseDefault(t *testing.T) {
7676

7777
var capturedMsg string = capturer.CaptureStderr(func() {
7878
// Test app defined non-existing file path
79-
confAppDummy = conf.TConfigApp{
79+
confAppDummy = conf.TConfigFile{
8080
PathFileConf: "",
8181
PathDirConf: ".",
8282
NameFileConf: "dummy_config",

cmd/common.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ type TConfUser struct {
3535

3636
var (
3737
// ConfApp is the basic app settings.
38-
ConfApp = conf.TConfigApp{
38+
ConfApp = conf.TConfigFile{
3939
PathDirConf: ".",
4040
NameFileConf: "config",
4141
NameTypeConf: "json",

conf/README.md

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
# Package conf
1+
# package conf
22

3-
Directory of file-configuration package using [Viper](https://github.com/spf13/viper).
3+
Package `conf` reads values from a configuration file such as JSON, TOML, YAML and etc using [Viper](https://github.com/spf13/viper).
44

5-
- Note that this package may be used as JSON/YAML file reader but it's not designed to load MEGA sized file.
5+
- Note: Eventhough this package may be used as a file loader of JSON/YAML/etc but it's NOT designed to load a huge-size-file.
66

7-
## Sample usage in other package
7+
## Sample usage
88

99
Pretend the user config file (`userConfig.json`) was as below:
1010

@@ -28,13 +28,13 @@ type TDataUser struct {
2828
}
2929

3030
var (
31-
configFile = conf.TConfigApp{
31+
configFile = conf.TConfigFile {
3232
PathDirConf: ".",
3333
NameFileConf: "userConfig",
3434
NameTypeConf: "json",
3535
}
3636

37-
userValues = TDataUser{
37+
userValues = TDataUser {
3838
MyValue: "default value",
3939
}
4040
)
@@ -47,6 +47,11 @@ if err := conf.LoadConfig(*configFile, &userValues); err != nil {
4747
// Use loaded value from the conf file
4848
myValue := userValues.MyValue
4949

50-
// `myValue` expects to be "Cobra"
50+
// `myValue` expects to be "Cobra". If "./userConfig.json" didn't exist
51+
// then the value should be "default value".
5152

5253
```
54+
55+
## TODO
56+
57+
- [ ] Map the values from ENV variables that match

conf/config.go

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Package conf config.go defines functions to read file configuration using Viper.
2+
Package conf reads values from a configuration file such as JSON, TOML, YAML and etc using Viper.
33
44
It was separated to a different package from `cmd` to ease testing and re-use.
55
@@ -19,24 +19,29 @@ import (
1919
"github.com/spf13/viper"
2020
)
2121

22-
// TConfigApp defines the data structure to store basic config of the app.
23-
type TConfigApp struct {
24-
// one-liner-conf
22+
// TConfigApp is an alias of TConfigFile for backward compatibility and it will be removed in v1.3.0.
23+
// Deprecated: as of v1.2.1. Use TConfigFile instead.
24+
type TConfigApp = TConfigFile
25+
26+
// TConfigFile defines the data structure to store the information of the file to load.
27+
type TConfigFile struct {
28+
/* one-liner-conf of file path */
2529

2630
PathFileConf string // File path of config. If set, will have priority than NameFileConf and PathDirConf.
2731

28-
// parted conf
32+
/* parted conf of file path */
2933

3034
PathDirConf string // Dir path of config to search.
3135
NameFileConf string // File name of config file. May or may not have an extension.
3236
NameTypeConf string // File extension. REQUIRED if the conf file does not have the extension.
3337

38+
/* configuration of it's state */
3439
IsUsingDefaultConf bool // Flag to determine if the app is using the default value or conf file value.
3540
}
3641

37-
// GetNameConf is a method of TConfigApp that returns the config file name.
38-
func (c TConfigApp) GetNameConf() string {
39-
if "" != c.PathFileConf {
42+
// GetNameConf is a method of TConfigFile that returns the config file name.
43+
func (c TConfigFile) GetNameConf() string {
44+
if c.PathFileConf != "" {
4045
return filepath.Base(c.PathFileConf)
4146
}
4247

@@ -47,19 +52,23 @@ func (c TConfigApp) GetNameConf() string {
4752
return c.NameFileConf + "." + c.NameTypeConf
4853
}
4954

50-
// LoadConfig() stores values from the config file or env variables to userConfig.
55+
// LoadConfig is an alias of LoadFile for backward compatibility and it will be removed in v1.3.0.
56+
// Deprecated: as of v1.2.1. Use LoadFile() instead.
57+
var LoadConfig = LoadFile
58+
59+
// LoadFile() stores values from the config file to userConfig.
5160
//
5261
// @args
53-
// appConfig TConfigApp : Basic configuration to read the conf file.
54-
// userConfig struct : An object to store values from conf file.
62+
// appConfig TConfigFile : Basic configuration to read the conf file.
63+
// userConfig struct : An object to store values from conf file.
5564
// @return
56-
// err error : If fails to read/store values from conf file returns error othersise nil.
65+
// err error : If fails to read/store values from conf file returns error othersise nil.
5766
// Usage:
5867
// type TConfUser struct {
5968
// MyValue string `mapstructure:"my_value"`
6069
// }
6170
// var (
62-
// configApp = conf.TConfigApp{
71+
// configApp = conf.TConfigFile{
6372
// PathDirConf: ".",
6473
// NameFileConf: "userConfig",
6574
// NameTypeConf: "json",
@@ -68,33 +77,30 @@ func (c TConfigApp) GetNameConf() string {
6877
// MyValue: "",
6978
// }
7079
// )
71-
// if err := conf.LoadConfig(*configApp, &configUser); err != nil {
80+
// if err := conf.LoadFile(*configApp, &configUser); err != nil {
7281
// // do something with the error
7382
// }
7483
// myValue := configUser.MyValue
75-
func LoadConfig(appConfig TConfigApp, userConfig interface{}) (err error) {
76-
// Reset current stored values in viper
77-
viper.Reset()
78-
79-
// Read values from the ENV variable if set
80-
viper.AutomaticEnv()
84+
func LoadFile(appConfig TConfigFile, userConfig interface{}) (err error) {
85+
// pitViper is a temporary viper instance
86+
pitViper := viper.New()
8187

8288
// Set file path to search
83-
if "" != appConfig.PathFileConf {
89+
if appConfig.PathFileConf != "" {
8490
// Set one-liner file path
85-
viper.SetConfigFile(appConfig.PathFileConf)
91+
pitViper.SetConfigFile(appConfig.PathFileConf)
8692
} else {
8793
// Set inidividual file path info
88-
viper.AddConfigPath(appConfig.PathDirConf)
89-
viper.SetConfigName(appConfig.NameFileConf)
90-
viper.SetConfigType(appConfig.NameTypeConf)
94+
pitViper.AddConfigPath(appConfig.PathDirConf)
95+
pitViper.SetConfigName(appConfig.NameFileConf)
96+
pitViper.SetConfigType(appConfig.NameTypeConf)
9197
}
9298

9399
// Search and read values from the config file and stores to "userConfig"
94-
err = viper.ReadInConfig()
100+
err = pitViper.ReadInConfig()
95101
if err == nil {
96102
// Map the read config values
97-
err = viper.Unmarshal(&userConfig)
103+
err = pitViper.Unmarshal(&userConfig)
98104
}
99105

100106
return err // return error if viper fails to read or map the values

0 commit comments

Comments
 (0)