-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
b1f2c30
commit 9c17ee1
Showing
5 changed files
with
418 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,212 @@ | ||
# Verdeter | ||
|
||
Verdeter project is a tool to write configuration easily with cobra and viper. Verdeter | ||
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 faster. | ||
|
||
> The api is susceptible to change at any point in time until the v1 is released. | ||
Verdeter allow developers to bind a posix complient flag, an environment variable and a variable in a config file to a viper key with a single line of code. | ||
Verdeter also comes with extra features such as: | ||
- support for [normalize function](https://github.com/ditrit/verdeter/blob/main/docs/normalization/normalization.md), ex: `LowerString` (lower the input string) | ||
- 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)) | ||
- support for constraints, ex: check for specific arch | ||
- support for dynamic default values (named *Computed values*), ex: set `time.Now().Unix()` as a default for a "time" key | ||
|
||
|
||
## How Verdeter differ from viper in handling configuration value | ||
|
||
Verdeter uses the following precedence order. Each item takes precedence over the item below it: | ||
|
||
1. Explicit call to `viper.Set`: | ||
|
||
`viper.Set(key)` set the key to a fixed value | ||
|
||
*Example: `viper.Set("age", 25)` will set the key "**age**" to `25`* | ||
|
||
2. POSIX flags | ||
|
||
Cli flags are handled by cobra using [pflag](https://github.com/spf13/pflag) | ||
|
||
*Example: appending the flag `--age 25` will set the key "**age**" to `25`* | ||
|
||
3. Environment variables | ||
|
||
Environment Variable are handled by viper (read more [here](https://github.com/spf13/viper#working-with-environment-variables)) | ||
|
||
*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`.* | ||
|
||
|
||
4. Value in a config file | ||
|
||
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. | ||
|
||
*Example:* | ||
Let's say the "**config_path**" is set to `./conf.yml` and the file looks like below | ||
```yml | ||
# conf.yml | ||
author: | ||
name: bob | ||
age: 25 | ||
``` | ||
Then you would use `viper.Get("author.name")` to access the value `bob` and `viper.Get("age")` to access the value `25`. | ||
|
||
5. Dynamic default values (*computed values*) | ||
|
||
Verdeter allow the user of "*computed values*" as dynamic default values. It means that the developer can values returned by functions as default values. | ||
|
||
*Example:* | ||
The function `defaultTime` will provide a unix time integer. | ||
|
||
```go | ||
var defaultTime verdeter.models.DefaultValueFunction := func () interface{} { | ||
return time.Now().Unix() | ||
} | ||
``` | ||
|
||
We bind this function to the key time using verdeter. | ||
|
||
```go | ||
(*VerdeterCommand).SetComputedValue("time", defaultTime) | ||
``` | ||
|
||
Then the value can be retreived easily using `viper.Get("time")` as usual | ||
|
||
|
||
6. static default | ||
|
||
Static defaults can be set using verdeter | ||
```go | ||
// of course here the value is static | ||
(*VerdeterCommand).SetDefault("time", 1661957668) | ||
``` | ||
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). | ||
```go | ||
viper.SetDefault("time", 1661957668) | ||
``` | ||
|
||
|
||
7. type default (0 for an integer) | ||
|
||
If a key is *not set* and *not marked as required (using `(*VerdeterCommand).SetRequired(<key_name>)`)*, then a call to `viper.Get<Type>(<key_name>)` will return the default value for this `<Type>`. | ||
|
||
*Example:* let's say thay we **did not** call `(*VerdeterCommand).SetRequired("time")` to set the key "time" as required. | ||
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"). | ||
|
||
|
||
## Basic Example | ||
|
||
Let's create a rootCommand named "myApp" | ||
```go | ||
var rootCommand = verdeter.NewConfigCmd( | ||
// Name of the app | ||
"myApp", | ||
// A short description | ||
"myApp is an amazing piece of software", | ||
// A longer description | ||
`myApp is an amazing piece of software, | ||
that everyone can use thanks to verdeter`, | ||
|
||
// Callback | ||
func(cfg *verdeter.VerdeterCommand, args []string) { | ||
key := "author.name" | ||
fmt.Printf("value for %q is %q\n", key, viper.GetString(key)) | ||
}) | ||
``` | ||
|
||
You might to receive args on the command line, set the number of args you want. | ||
If more are provided, Cobra will throw an error. | ||
|
||
```go | ||
// only 2 args please | ||
rootCommand.SetNbArgs(2) | ||
``` | ||
|
||
Then I want to add configuration to this command, for example to bind an address and a port to myApp. | ||
|
||
```go | ||
// Adding a local key. | ||
// if you want sub command to inherit this flag, use (*verdeter.VerdeterCommand).GKey instead | ||
rootCommand.LKey("addr", verdeter.IsStr, "a", "bind to IPV4 addr") | ||
rootCommand.LKey("port", verdeter.IsInt, "p", "bind to TCP port") | ||
``` | ||
|
||
A default value can be set for each config key | ||
|
||
```go | ||
rootCommand.SetDefault("addr", "127.0.0.1") | ||
rootCommand.SetDefault("port", 7070) | ||
``` | ||
|
||
A validator can be bound to a config key. | ||
|
||
```go | ||
// creating a validator from scratch | ||
addrValidator := models.Validator{ | ||
// the name of the validator | ||
Name: "IPV4 validator", | ||
|
||
// the actual validation function | ||
Func: func (input interface{}) error { | ||
valueStr, ok := input.(string) | ||
if !ok { | ||
return fmt.Error("wrong input type") | ||
} | ||
parts := strings.Split(".") | ||
if len(parts)!=4 { | ||
return fmt.Errorf("An IPv4 is composed of four 8bit integers, fount %d", len(parts)) | ||
} | ||
for _,p := parts { | ||
intVal, err := strconv.Atoi(p) | ||
if err != nil { | ||
return err | ||
} | ||
if intVal<0 || intVal >255 { | ||
return fmt.Error("one of the part in the string is not a byte") | ||
} | ||
|
||
} | ||
}, | ||
} | ||
|
||
// using the validator we just created | ||
rootCommand.SetValidator("addr", addrValidator) | ||
|
||
// verdeter comes with some predefined validators | ||
rootCommand.SetValidator("port", verdeter.validators.CheckTCPHighPort) | ||
``` | ||
|
||
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. | ||
```go | ||
rootCommand.SetRequired("addr") | ||
``` | ||
|
||
To actually run the command, use this code in your main.go | ||
|
||
```go | ||
func init(){ | ||
// Initialize the command | ||
rootCommand.Initialize() | ||
// setup keys | ||
// rootCommand.LKey("port", ve...... | ||
|
||
} | ||
func main() { | ||
|
||
|
||
/* | ||
YOUR CODE HERE | ||
*/ | ||
|
||
// Launch the command | ||
rootCommand.Execute() | ||
|
||
} | ||
``` | ||
|
||
## Contributing Guidelines | ||
|
||
See [CONTRIBUTING](CONTRIBUTING.md) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
# Normalization | ||
|
||
Verdeter support normalization functions. | ||
|
||
Let's say you are building an app that take strings as config values. Instead of asking you 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. | ||
|
||
```go | ||
var LowerString models.NormalizationFunction = func(val interface{}) interface{} { | ||
strVal, ok := val.(string) | ||
if !ok { | ||
return val | ||
} | ||
return strings.ToLower(strVal) | ||
} | ||
|
||
verdeterCommand.SetNormalize("keyname", LowerString) | ||
``` | ||
|
||
--- | ||
|
||
*The `LowerString` normalization function is actually available at `verdeter.normalization.LowerString`* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
time: 1661865582 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"time" | ||
|
||
"github.com/ditrit/verdeter" | ||
"github.com/spf13/viper" | ||
) | ||
|
||
var verdeterRootCmd = verdeter.NewVerdeterCommand( | ||
"verdeterapp", | ||
"verdeterapp print formated time to the terminal", | ||
"/* Insert a longer description here*/", | ||
func(cfg *verdeter.VerdeterCommand, args []string) error { | ||
timeStamp := viper.GetInt("time") | ||
t := time.Unix(int64(timeStamp), 0) | ||
fmt.Println(t) | ||
|
||
// no error to return | ||
return nil | ||
}) | ||
|
||
func main() { | ||
// Initialize the command | ||
verdeterRootCmd.Initialize() | ||
|
||
viper.Set("config_path", "./conf/") | ||
|
||
// Set a new key named "time" with a shortcut named "t" | ||
verdeterRootCmd.GKey("time", verdeter.IsInt, "t", "the time") | ||
|
||
// If the value of time is not set, run this function and set "time" to it's output | ||
verdeterRootCmd.SetComputedValue("time", func() interface{} { | ||
return time.Now().Unix() | ||
}) | ||
|
||
verdeterRootCmd.Execute() | ||
} |
Oops, something went wrong.