You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Verdeter is a library to write configuration easily with cobra and viper for distributed applications. Verdeter bring the power of cobra and viper in a single library.
It should be consider as a wrapper for cobra and viper that allow developers to code apps that are POSIX compliant by default.
8
+
Verdeter is a library to write CLIs and configuration easily by bringing the power of [cobra](https://github.com/spf13/cobra) and [viper](https://github.com/spf13/viper) in a single library.
6
9
7
-
> The api is susceptible to change at any point in time until the v1 is released.
8
-
9
-
Verdeter allow developers to bind a posix compliant flag, an environment variable and a variable in a config file to a viper key with a single line of code.
10
+
Verdeter allow developers to bind a posix compliant flag, an environment variable and a variable in a config file to a viper key with a single line of code.
11
+
Verdeter provide a consistent precedence order by extending [viper precedence order](https://github.com/spf13/viper#why-viper).
10
12
Verdeter also comes with extra features such as:
11
-
- support for [normalize function](https://github.com/ditrit/verdeter/blob/main/docs/normalization/normalization.md), ex: `LowerString` (lower the input string)
12
-
- support for [key specific checks](https://github.com/ditrit/verdeter/blob/main/docs/using_it_for_real/using_it_for_real.md), ex: `StringNotEmpty`(check if the input string is empty), `CheckIsHighPort`(check is the input integer is a high tcp port), or `AuthorizedValues`(check if the value of a config key is contained in a defined array of authorized values))
13
+
14
+
- support for [normalize function](#normalization) to normalize user inputs (ex: [`LowerString`] lower the input string)
15
+
- support for [key specific validation](#validation), ex: `StringNotEmpty`(check if the input string is empty), `CheckIsHighPort`(check is the input integer is a high tcp port), or `AuthorizedValues`(check if the value of a config key is contained in a defined array of authorized values))
13
16
- support for constraints, ex: check for specific arch
14
-
- support for dynamic default values (named *Computed values*), ex: set `time.Now().Unix()` as a default for a "time" key
17
+
- support for dynamic default values (named *Computed values*), ex: set `time.Now().Unix()` as a default for a "time" key.
15
18
19
+
-[Verdeter](#verdeter)
20
+
-[How Verdeter differ from viper in handling configuration values](#how-verdeter-differ-from-viper-in-handling-configuration-values)
## How Verdeter differ from viper in handling configuration value
27
+
## How Verdeter differ from viper in handling configuration values
18
28
19
29
Verdeter uses the following precedence order. Each item takes precedence over the item below it:
20
30
21
-
1. Explicit call to `viper.Set`:
31
+
1. Explicit call to `viper.Set`:
32
+
33
+
`viper.Set(key)` set the key to a fixed value.
22
34
23
-
`viper.Set(key)` set the key to a fixed value
24
-
25
35
*Example: `viper.Set("age", 25)` will set the key "**age**" to `25`*
26
36
27
37
2. POSIX flags
@@ -35,20 +45,21 @@ Verdeter uses the following precedence order. Each item takes precedence over th
35
45
Environment Variable are handled by viper (read more [here](https://github.com/spf13/viper#working-with-environment-variables))
36
46
37
47
*Example: running `export <APP_NAME>_age` will export an environment variable (the `<APP_NAME>` is set by verdeter). Verdeter will bind automatically the environment variable name to a viper key when the developer will define the key he needs. Then, when the developer retreive a value for the "**age**" key with a call to `viper.Get("age)`, viper get all the environment variable and find the value of `<APP_NAME>_age`.*
38
-
39
48
40
49
4. Value in a config file
41
50
42
51
Viper support reading from [JSON, TOML, YAML, HCL, envfile and Java properties config files](https://github.com/spf13/viper#what-is-viper). The developer need to set a key named "**config_path**" to set the path to the config file or the path to the config directory.
43
52
44
53
*Example:*
45
54
Let's say the "**config_path**" is set to `./conf.yml` and the file looks like below
55
+
46
56
```yml
47
57
# conf.yml
48
58
author:
49
59
name: bob
50
60
age: 25
51
61
```
62
+
52
63
Then you would use `viper.Get("author.name")` to access the value `bob` and `viper.Get("age")` to access the value `25`.
53
64
54
65
5. Dynamic default values (*computed values*)
@@ -72,19 +83,14 @@ Verdeter uses the following precedence order. Each item takes precedence over th
72
83
73
84
Then the value can be retreived easily using `viper.Get("time")` as usual
74
85
75
-
76
86
6. static default
77
87
78
88
Static defaults can be set using verdeter
89
+
79
90
```go
80
91
// of course here the value is static
81
92
(*VerdeterCommand).SetDefault("time", 1661957668)
82
93
```
83
-
Alternatively you can use viper directly to do exactly the same thing (please note that we will use `(*VerdeterCommand).SetDefault` in the rest of the documentation).
84
-
```go
85
-
viper.SetDefault("time", 1661957668)
86
-
```
87
-
88
94
89
95
7. type default (0 for an integer)
90
96
@@ -93,102 +99,57 @@ Verdeter uses the following precedence order. Each item takes precedence over th
93
99
*Example:* let's say thay we **did not** call `(*VerdeterCommand).SetRequired("time")` to set the key "time" as required.
94
100
Then a call to `viper.GetInt("time")` will return `0`. (please note that a call to `viper.Get(<key>)` returns an `interface{}` wich has no "defaut value").
If you take a look at [verdeter.VerdeterConfig's documentation]() you will observe that it's quite similar to the the original cobra.Command type. That's done on purpose to help you transition easily from your cobra app.
126
+
127
+
Then I want to add configuration to this command: let's create a config key named "from" that will represent the name of the sender.
129
128
130
129
```go
131
130
// Adding a local key.
132
-
rootCommand.LKey("addr", verdeter.IsStr, "a", "bind to IPV4 addr")
133
-
rootCommand.LKey("port", verdeter.IsInt, "p", "bind to TCP port")
131
+
helloCommand.GKey("from", verdeter.IsStr, "f", "the sender name")
134
132
135
-
/* if you want sub commands to inherit this flag,
133
+
/* if you want sub commands to inherit this flag/config key,
136
134
use (*verdeter.VerdeterCommand).GKey instead */
137
135
```
138
136
139
137
> The config types availables are `verdeter.IsStr`, `verdeter.IsInt`, `verdeter.IsUint` and `verdeter.IsBool`.
140
138
141
-
A default value can be set for each config key
139
+
A default value can be set for that config key. See [.SetDefault() doc]().
142
140
143
141
```go
144
-
rootCommand.SetDefault("addr", "127.0.0.1")
145
-
rootCommand.SetDefault("port", 7070)
142
+
// The default value of the config key "from" is nom "earth".
143
+
rootCommand.SetDefault("from", "earth")
146
144
```
147
145
148
-
A validator can be bound to a config key.
149
-
150
-
```go
151
-
// creating a validator from scratch
152
-
addrValidator:= models.Validator{
153
-
// the name of the validator
154
-
Name: "IPV4 validator",
155
-
156
-
// the actual validation function
157
-
Func: func (input interface{}) error {
158
-
valueStr, ok:= input.(string)
159
-
if !ok {
160
-
return fmt.Error("wrong input type")
161
-
}
162
-
parts:= strings.Split(".")
163
-
iflen(parts)!=4 {
164
-
return fmt.Errorf("An IPv4 is composed of four 8bit integers, fount %d", len(parts))
165
-
}
166
-
for _,p:= parts {
167
-
intVal, err:= strconv.Atoi(p)
168
-
if err != nil {
169
-
return err
170
-
}
171
-
if intVal<0 || intVal >255 {
172
-
return fmt.Error("one of the part in the string is not a byte")
173
-
}
174
-
175
-
}
176
-
},
177
-
}
178
-
179
-
// using the validator we just created
180
-
rootCommand.SetValidator("addr", addrValidator)
146
+
Config key can be marked as required. The cobra function [(* cobra.Command).PreRunE](https://pkg.go.dev/github.com/spf13/cobra#Command) will fail if the designated config key is not provided, preventing your `Run` or `RunE` function to be called.
Config key can be marked as required. The cobra function [(* cobra.Command).PreRunE](https://pkg.go.dev/github.com/spf13/cobra#Command) will fail if the designated config key is not provided, preventing the callback to run.
187
148
```go
188
-
rootCommand.SetRequired("addr")
149
+
rootCommand.SetRequired("from")
189
150
```
190
151
191
-
To actually run the command, use this code in your main.go
152
+
To actually run the command, use the `Execute()` method.
192
153
193
154
```go
194
155
func main() {
@@ -199,11 +160,78 @@ func main() {
199
160
*/
200
161
201
162
// Launch the command
202
-
rootCommand.Execute()
163
+
helloCommand.Execute()
164
+
}
165
+
```
166
+
167
+
## Normalization
168
+
169
+
Let's say you are building an app that take strings as config values. Instead of asking your user to use only lowercase strings you could set a normalizer with verdeter that will ensure that the string value you will retrieve is actually a lowercase value.
170
+
171
+
Please note that normalization functions use a specific signature
203
172
173
+
```go
174
+
import "github.com/ditrit/verdeter/models"
175
+
176
+
var LowerString models.NormalizationFunction = func(val interface{}) interface{} {
*The `LowerString` normalization function is actually available at `verdeter.normalization.LowerString`*
190
+
191
+
## Validation
192
+
193
+
Let's say you are building an app that serve content over http. You will need to bind your app to a port on the server. You will likely put that in a config key. In order to prevent configuration mistakes that would prevent the application from running, you want to make sure the port number is a TCP high port. Note that Verdeter introduce a validation step that run before the `PreRun` or `PreRunE` function, depending on wich you are using.
194
+
195
+
First write your validator using verdeter model.
196
+
197
+
```go
198
+
import "github.com/ditrit/verdeter/models"
199
+
200
+
var validatorTCPHighPort = models.Validator{
201
+
202
+
// Give your validator a name
203
+
Name: "TCP High Port Check"
204
+
205
+
// Then provide the validation function.
206
+
// (Please note that you need that exact signature)
207
+
Func: func (input interface{}) error {
208
+
// First make sure this is an integer
209
+
portNumber, ok := input.(int)
210
+
if !ok { // seems that is not an integer
211
+
return fmt.Errorf("should be an integer")
212
+
}
213
+
214
+
// Check if the port is in the correct interval
215
+
if intVal >= 1024 && intVal <= 65535 {
216
+
return nil
217
+
}
218
+
return fmt.Errorf("value (%d) is not a TCP high port ", port)
0 commit comments