Skip to content

Commit 8b6edb7

Browse files
Improve README
1 parent a80cd58 commit 8b6edb7

5 files changed

Lines changed: 121 additions & 297 deletions

File tree

README.md

Lines changed: 121 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,37 @@
11
# Verdeter
22

3-
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.
3+
[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/ditrit/verdeter/CI.yml?branch=main&style=flat-square)](https://github.com/spf13/viper/actions?query=workflow%3ACI)
4+
[![Go Report Card](https://goreportcard.com/badge/github.com/ditrit/verdeter?style=flat-square)](https://goreportcard.com/report/github.com/ditrit/verdeter)
5+
![Go Version](https://img.shields.io/badge/go%20version-%3E=1.18-61CFDD.svg?style=flat-square)
6+
[![PkgGoDev](https://pkg.go.dev/badge/mod/github.com/ditrit/verdeter)](https://pkg.go.dev/mod/github.com/ditrit/verdeter)
47

5-
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.
69

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).
1012
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))
1316
- 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.
1518

19+
- [Verdeter](#verdeter)
20+
- [How Verdeter differ from viper in handling configuration values](#how-verdeter-differ-from-viper-in-handling-configuration-values)
21+
- [Get Started](#get-started)
22+
- [Normalization](#normalization)
23+
- [Validation](#validation)
24+
- [Licence](#licence)
25+
- [Contributing Guidelines](#contributing-guidelines)
1626

17-
## How Verdeter differ from viper in handling configuration value
27+
## How Verdeter differ from viper in handling configuration values
1828

1929
Verdeter uses the following precedence order. Each item takes precedence over the item below it:
2030

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.
2234

23-
`viper.Set(key)` set the key to a fixed value
24-
2535
*Example: `viper.Set("age", 25)` will set the key "**age**" to `25`*
2636

2737
2. POSIX flags
@@ -35,20 +45,21 @@ Verdeter uses the following precedence order. Each item takes precedence over th
3545
Environment Variable are handled by viper (read more [here](https://github.com/spf13/viper#working-with-environment-variables))
3646

3747
*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-
3948

4049
4. Value in a config file
4150

4251
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.
4352

4453
*Example:*
4554
Let's say the "**config_path**" is set to `./conf.yml` and the file looks like below
55+
4656
```yml
4757
# conf.yml
4858
author:
4959
name: bob
5060
age: 25
5161
```
62+
5263
Then you would use `viper.Get("author.name")` to access the value `bob` and `viper.Get("age")` to access the value `25`.
5364

5465
5. Dynamic default values (*computed values*)
@@ -72,19 +83,14 @@ Verdeter uses the following precedence order. Each item takes precedence over th
7283

7384
Then the value can be retreived easily using `viper.Get("time")` as usual
7485

75-
7686
6. static default
7787

7888
Static defaults can be set using verdeter
89+
7990
```go
8091
// of course here the value is static
8192
(*VerdeterCommand).SetDefault("time", 1661957668)
8293
```
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-
8894

8995
7. type default (0 for an integer)
9096

@@ -93,102 +99,57 @@ Verdeter uses the following precedence order. Each item takes precedence over th
9399
*Example:* let's say thay we **did not** call `(*VerdeterCommand).SetRequired("time")` to set the key "time" as required.
94100
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").
95101

102+
## Get Started
96103

97-
## Basic Example
104+
Get the library.
98105

99-
Let's create a rootCommand named "myApp"
100-
```go
101-
102-
var rootCommand = verdeter.NewConfigCmd(
103-
// Name of the app
104-
"myApp",
105-
106-
// A short description
107-
"myApp is an amazing piece of software",
108-
109-
// A longer description
110-
`myApp is an amazing piece of software,
111-
that everyone can use thanks to verdeter`,
112-
113-
// Callback
114-
func(cfg *verdeter.VerdeterCommand, args []string) {
115-
key := "author.name"
116-
fmt.Printf("value for %q is %q\n", key, viper.GetString(key))
117-
})
106+
```shell
107+
go get github.com/ditrit/verdeter
118108
```
119109

120-
You might to receive args on the command line, set the number of args you want.
121-
If more are provided, Cobra will throw an error.
110+
Let's create an app that print stuff to the terminal. Let's start with the classic "hello world!"
122111

123112
```go
124-
// only 2 args please
125-
rootCommand.SetNbArgs(2)
126-
```
127113
128-
Then I want to add configuration to this command, for example to bind an address and a port to myApp.
114+
import "github.com/ditrit/verdeter"
115+
116+
var helloCommand = verdeter.BuildVerdeterCommand(verdeter.VerdeterConfig{
117+
Use: "hello",
118+
Long: "hello is an app that says hello",
119+
Run: func(cmd *cobra.Command, args []string) {
120+
fmt.Printf("Hello World! (from: %q)\n", viper.GetString("from"))
121+
},
122+
})
123+
```
124+
125+
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.
129128

130129
```go
131130
// 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")
134132
135-
/* if you want sub commands to inherit this flag,
133+
/* if you want sub commands to inherit this flag/config key,
136134
use (*verdeter.VerdeterCommand).GKey instead */
137135
```
138136

139137
> The config types availables are `verdeter.IsStr`, `verdeter.IsInt`, `verdeter.IsUint` and `verdeter.IsBool`.
140138

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]().
142140

143141
```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")
146144
```
147145

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-
if len(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.
181147

182-
// verdeter comes with some predefined validators
183-
rootCommand.SetValidator("port", verdeter.validators.CheckTCPHighPort)
184-
```
185-
186-
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.
187148
```go
188-
rootCommand.SetRequired("addr")
149+
rootCommand.SetRequired("from")
189150
```
190151

191-
To actually run the command, use this code in your main.go
152+
To actually run the command, use the `Execute()` method.
192153

193154
```go
194155
func main() {
@@ -199,11 +160,78 @@ func main() {
199160
*/
200161
201162
// 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
203172

173+
```go
174+
import "github.com/ditrit/verdeter/models"
175+
176+
var LowerString models.NormalizationFunction = func(val interface{}) interface{} {
177+
strVal, ok := val.(string)
178+
if !ok {
179+
return val
180+
}
181+
return strings.ToLower(strVal)
204182
}
183+
184+
verdeterCommand.SetNormalize("keyname", LowerString)
185+
```
186+
187+
---
188+
189+
*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)
219+
}
220+
}
221+
222+
// Then register the validator for the config key
223+
verdeterCommand.AddValidator("port", validatorTCPHighPort)
205224
```
206225

226+
---
227+
228+
*The `CheckTCPHighPort` validator is actually available in verdeter at `verdeter.validators.CheckTCPHighPort`*
229+
230+
231+
## Licence
232+
233+
Verdeter is licenced under the Mozilla Public License Version 2.0: see [LICENSE](LICENSE).
234+
207235
## Contributing Guidelines
208236

209-
See [CONTRIBUTING](CONTRIBUTING.md)
237+
See [CONTRIBUTING](CONTRIBUTING.md).

docs/normalization/normalization.md

Lines changed: 0 additions & 21 deletions
This file was deleted.

docs/using_it_for_real/conf/verdeterapp.yml

Lines changed: 0 additions & 1 deletion
This file was deleted.

docs/using_it_for_real/main.go

Lines changed: 0 additions & 37 deletions
This file was deleted.

0 commit comments

Comments
 (0)