Documentation
¶
Overview ¶
package clic is short for CLI Config. It implements a framework to load/parse cli configuration from a file, environment or flags.
Usage:
- Register functions (Register and RegisterCallback) should be called in `func init()` in packages, or before calling Parse.
- Parse function should be called at the beginning of "main()", before calling functions in other packages.
- Parse function must not be called in `func init()`, because other sub-packages may not finish initialization at that time.
See examples for the usage.
Example ¶
package main
import (
"context"
"flag"
"fmt"
"log/slog"
"os"
"time"
"github.com/googollee/clic"
)
func main() {
// structs
type Database struct {
Driver string `clic:"driver,sqlite3,the driver of the database [sqlite3,mysql,postgres]"`
URL string `clic:"url,./database.sqlite,the url of the database"`
}
type Log struct {
Level slog.Level `clic:"level,INFO,the level of the log [DEBUG,INFO,WARN,ERROR]"`
}
initLog := func(ctx context.Context, log *Log) error {
fmt.Println("set log level:", log.Level)
return nil
}
// args
oldArgs := os.Args
os.Args = []string{oldArgs[0], "-database.driver", "driver", "-database.url", "url", "sub_command"}
defer func() {
os.Args = oldArgs
}()
// main code
ctx := context.Background()
var db Database
clic.Register("database", &db)
clic.RegisterCallback("log", initLog)
// config should be finished in a minute.
configCtx, cancel := context.WithTimeout(ctx, time.Minute)
defer cancel()
clic.Parse(configCtx)
fmt.Println("database:", db)
fmt.Println("remain args:", flag.Args())
}
Output: set log level: INFO database: {driver url} remain args: [sub_command]
Example (EmbedStruct) ¶
package main
import (
"context"
"flag"
"fmt"
"log"
"os"
"github.com/googollee/clic"
)
func main() {
// prepare env
for _, key := range []string{"DEMO_VALUE"} {
if err := os.Setenv(key, "value_from_env"); err != nil {
log.Fatal("set env error:", err)
}
}
// code starts
type Inner struct {
Value string `clic:"value,default,the value in the inner struct"`
}
type Config struct {
Value string `clic:"value,default,the value in the outer struct"`
Inner Inner `clic:"inner"`
}
fset := flag.NewFlagSet("", flag.PanicOnError)
set := clic.NewSet(fset)
var cfg Config
set.RegisterValue("demo", &cfg)
ctx := context.Background()
if err := set.Parse(ctx, []string{"-demo.inner.value", "value_from_flag"}); err != nil {
log.Fatal("parse error:", err)
}
fmt.Println("Value:", cfg.Value)
fmt.Println("Inner.Value:", cfg.Inner.Value)
}
Output: Value: value_from_env Inner.Value: value_from_flag
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var CommandLine = NewSet(flag.CommandLine, DefaultSources...)
var DefaultSources = []source.Source{ source.Flag(source.FlagSplitter(".")), source.File(source.FilePathFlag("config"), source.FileFormat(source.JSON{})), source.Env(source.EnvSplitter("_")), }
Functions ¶
func Parse ¶
Parse parses configuration from DefaultSources and os.Args.
If any error happens during calling, "Parse()" prints that error on Stderr and calls os.Exit to exit with "125" code.
func Register ¶
Register registers a "Config" value with the "name" as the scope name. The value is filled after calling Parse function.
Example:
package main
func main() {
ctx := context.Background()
var dbCfg database.Config
clic.Register("database", &db)
clic.Parse(ctx)
db := database.New(dbCfg)
}
func RegisterCallback ¶
RegisterCallback registers a callback function with the "name" as the scope name. The callback is called after calling Parse function.
Example:
package main
type Log struct {
Level slog.Level `clic:"level,INFO,the level of log"`
}
func initLogLevel(ctx context.Context, cfg *Log) {
slog.SetLevel(cfg.Level)
}
func main() {
ctx := context.Background()
clic.RegisterCallback("log", initLogLevel)
clic.Parse(ctx)
}
func RegisterType ¶ added in v0.1.5
RegisterType registers a "Config" type with the "name" as the scope name and returns a getter function which returns a parsed Config value.
Example:
package main
func main() {
ctx := context.Background()
loadCfg := clic.RegisterType[database.Config]()
clic.Parse(ctx)
db := database.New(loadCfg())
}
Types ¶
type Set ¶
type Set struct {
// contains filtered or unexported fields
}
Example ¶
package main
import (
"context"
"flag"
"fmt"
"log"
"log/slog"
"github.com/googollee/clic"
"github.com/googollee/clic/source"
)
func main() {
// structs
type Database struct {
Driver string `clic:"driver,sqlite3,the driver of the database [sqlite3,mysql,postgres]"`
URL string `clic:"url,./database.sqlite,the url of the database"`
}
type Log struct {
Level slog.Level `clic:"level,INFO,the level of the log [DEBUG,INFO,WARN,ERROR]"`
}
initLog := func(ctx context.Context, log *Log) error {
fmt.Println("set log level:", log.Level)
return nil
}
// set with sources
fset := flag.NewFlagSet("", flag.ExitOnError)
set := clic.NewSet(fset,
// The order means srouce priority, flag > config file > env
source.Flag(source.FlagSplitter(".")),
source.File(source.FilePathFlag("config"), source.FileFormat(source.JSON{})),
source.Env(source.EnvSplitter("_")),
)
// args
args := []string{"-log.level", "WARN", "-database.driver", "driver", "-database.url", "url", "other_cmd"}
// main code
ctx := context.Background()
var db Database
set.RegisterValue("database", &db)
set.RegisterCallback("log", initLog)
if err := set.Parse(ctx, args); err != nil {
log.Fatal("parse error:", err)
}
fmt.Println("database:", db)
fmt.Println("remain args:", fset.Args())
}
Output: set log level: WARN database: {driver url} remain args: [other_cmd]
Example (ShowHelp) ¶
package main
import (
"bytes"
"context"
"errors"
"flag"
"fmt"
"log"
"log/slog"
"strings"
"github.com/googollee/clic"
"github.com/googollee/clic/source"
)
func main() {
// structs
type Database struct {
Driver string `clic:"driver,sqlite3,the driver of the database [sqlite3,mysql,postgres]"`
URL string `clic:"url,./database.sqlite,the url of the database"`
}
type Log struct {
Level slog.Level `clic:"level,INFO,the level of the log [DEBUG,INFO,WARN,ERROR]"`
}
initLog := func(ctx context.Context, log *Log) error {
fmt.Println("set log level:", log.Level)
return nil
}
// set with sources
fset := flag.NewFlagSet("", flag.ContinueOnError)
var helpOutput bytes.Buffer
fset.SetOutput(&helpOutput)
set := clic.NewSet(fset,
// The order means srouce priority, flag > config file > env
source.Flag(source.FlagSplitter(".")),
source.File(source.FilePathFlag("config"), source.FileFormat(source.JSON{})),
source.Env(source.EnvSplitter("_")),
)
// args
args := []string{"-h"}
// main code
ctx := context.Background()
var db Database
set.RegisterValue("database", &db)
set.RegisterCallback("log", initLog)
if err := set.Parse(ctx, args); !errors.Is(err, flag.ErrHelp) {
log.Fatal("parse error:", err)
}
fmt.Println(strings.ReplaceAll(helpOutput.String(), "\t", " "))
}
Output: Usage: -config string the path of the config file -database.driver value the driver of the database [sqlite3,mysql,postgres] (default sqlite3) -database.url value the url of the database (default ./database.sqlite) -log.level value the level of the log [DEBUG,INFO,WARN,ERROR] (default INFO)
Example (SourcePriorities) ¶
package main
import (
"context"
"flag"
"fmt"
"log"
"os"
"github.com/googollee/clic"
)
func main() {
// prepare env
for _, key := range []string{"DEMO_VALUE_FLAG", "DEMO_VALUE_ENV", "DEMO_VALUE_FILE"} {
if err := os.Setenv(key, "value_from_env"); err != nil {
log.Fatal("set env error:", err)
}
}
// prepare config file
cfgFile, err := os.CreateTemp("", "config_*.json")
if err != nil {
log.Fatal("create temp file error:", err)
}
defer os.Remove(cfgFile.Name())
if _, err := cfgFile.WriteString(`{
"demo": {
"value_flag": "value_from_file",
"value_file": "value_from_file"
}
}`); err != nil {
log.Fatal("write temp file error:", err)
}
if err := cfgFile.Close(); err != nil {
log.Fatal("close temp file error:", err)
}
// prepare args
args := []string{"-config", cfgFile.Name(), "-demo.value_flag", "value_from_flag"}
// code starts
type Config struct {
ValueFlag string `clic:"value_flag,default,a test value in flag"`
ValueEnv string `clic:"value_env,default,a test value in env"`
ValueFile string `clic:"value_file,default,a test value in config file"`
ValueDefault string `clic:"value_default,default,a test value by default"`
}
var cfg Config
fset := flag.NewFlagSet("", flag.PanicOnError)
set := clic.NewSet(fset, clic.DefaultSources...)
set.RegisterValue("demo", &cfg)
ctx := context.Background()
if err := set.Parse(ctx, args); err != nil {
log.Fatal("parse error:", err)
}
fmt.Println("ValueFlag:", cfg.ValueFlag)
fmt.Println("ValueEnv:", cfg.ValueEnv)
fmt.Println("ValueFile:", cfg.ValueFile)
fmt.Println("ValueDefault:", cfg.ValueDefault)
}
Output: ValueFlag: value_from_flag ValueEnv: value_from_env ValueFile: value_from_file ValueDefault: default