frame out cli tool
This commit is contained in:
80
cmd/internal/config/config.go
Normal file
80
cmd/internal/config/config.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/caarlos0/env/v11"
|
||||
yaml "github.com/oasdiff/yaml3"
|
||||
)
|
||||
|
||||
type ClientsConfig struct {
|
||||
LogLevel string `json:"logLevel" yaml:"logLevel" env:"LOG_LEVEL" envDefault:"warn"`
|
||||
LogFormat string `json:"logFormat" yaml:"logFormat" env:"LOG_FORMAT" envDefault:"console"`
|
||||
|
||||
Clients []ClientConfig `json:"clients" yaml:"clients" envPrefix:"CLIENT_"`
|
||||
}
|
||||
|
||||
type ClientType string
|
||||
|
||||
const (
|
||||
TypeEdgeOS ClientType = "edgeOS"
|
||||
TypeToughSwitch ClientType = "toughSwitch"
|
||||
)
|
||||
|
||||
type ClientConfig struct {
|
||||
Type ClientType `json:"type" yaml:"type" env:"TYPE"`
|
||||
Name string `json:"name" yaml:"name" env:"NAME"`
|
||||
Host string `json:"host" yaml:"host" env:"HOST"`
|
||||
Scheme string `json:"scheme" yaml:"scheme" env:"SCHEME" envDefault:"https"`
|
||||
User string `json:"user" yaml:"user" env:"USER"`
|
||||
Pass string `json:"pass" yaml:"pass" env:"PASS"`
|
||||
Insecure bool `json:"insecure" yaml:"insecure" env:"INSECURE" envDefault:"false"`
|
||||
Timeout time.Duration `json:"timeout" yaml:"timeout" env:"TIMEOUT" envDefault:"10s"`
|
||||
}
|
||||
|
||||
// LoadConfig will load a file if given, layering env on-top of the config
|
||||
// if present. Environment variables take the form:
|
||||
// - LOG_LEVEL, LOG_FORMAT for top-level settings
|
||||
// - CLIENT_0_NAME, CLIENT_0_HOST, CLIENT_0_TYPE, etc. for client array
|
||||
func LoadConfig(configPath *string) (*ClientsConfig, error) {
|
||||
conf, err := env.ParseAs[ClientsConfig]()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not parse env config: %w", err)
|
||||
}
|
||||
|
||||
if configPath != nil && *configPath != "" {
|
||||
file, err := os.Open(*configPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not open config file: %w", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
decoder := yaml.NewDecoder(file)
|
||||
if err := decoder.Decode(&conf); err != nil {
|
||||
return nil, fmt.Errorf("could not decode config file: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return &conf, nil
|
||||
}
|
||||
|
||||
// GetClientByName returns a client config by its name, or nil if not found.
|
||||
func (c *ClientsConfig) GetClientByName(name string) *ClientConfig {
|
||||
for i := range c.Clients {
|
||||
if c.Clients[i].Name == name {
|
||||
return &c.Clients[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetClientNames returns a list of all configured client names.
|
||||
func (c *ClientsConfig) GetClientNames() []string {
|
||||
names := make([]string, len(c.Clients))
|
||||
for i, client := range c.Clients {
|
||||
names[i] = client.Name
|
||||
}
|
||||
return names
|
||||
}
|
||||
12
cmd/internal/util/constants.go
Normal file
12
cmd/internal/util/constants.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package util
|
||||
|
||||
const (
|
||||
FLAG_CONFIG_FILE = "config"
|
||||
FLAG_CONFIG_FILE_P = "f"
|
||||
|
||||
FLAG_PRETTY = "pretty"
|
||||
FLAG_PRETTY_P = "p"
|
||||
|
||||
FLAG_COLOR = "color"
|
||||
FLAG_COLOR_P = "c"
|
||||
)
|
||||
43
cmd/internal/util/context.go
Normal file
43
cmd/internal/util/context.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gitea.libretechconsulting.com/rmcguire/ubiquiti-clients/cmd/internal/config"
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
type CmdContextVal uint8
|
||||
|
||||
const (
|
||||
CTX_CONFIG CmdContextVal = iota
|
||||
CTX_LOGGER
|
||||
)
|
||||
|
||||
func ContextWithConfig(baseCtx context.Context, config *config.ClientsConfig) context.Context {
|
||||
return context.WithValue(baseCtx, CTX_CONFIG, config)
|
||||
}
|
||||
|
||||
func ConfigFromContext(ctx context.Context) *config.ClientsConfig {
|
||||
val := ctx.Value(CTX_CONFIG)
|
||||
conf, ok := val.(*config.ClientsConfig)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
return conf
|
||||
}
|
||||
|
||||
func ContextWithLogger(baseCtx context.Context, logger *zerolog.Logger) context.Context {
|
||||
return context.WithValue(baseCtx, CTX_LOGGER, logger)
|
||||
}
|
||||
|
||||
func LoggerFromContext(ctx context.Context) *zerolog.Logger {
|
||||
val := ctx.Value(CTX_LOGGER)
|
||||
logger, ok := val.(*zerolog.Logger)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
return logger
|
||||
}
|
||||
101
cmd/internal/util/output.go
Normal file
101
cmd/internal/util/output.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"io"
|
||||
"regexp"
|
||||
|
||||
"github.com/fatih/color"
|
||||
yaml "github.com/oasdiff/yaml3"
|
||||
)
|
||||
|
||||
// YAMLOutput writes data as YAML to the given writer.
|
||||
// If pretty is true, output is indented with 2 spaces.
|
||||
// If colorize is true, YAML syntax is colorized.
|
||||
func YAMLOutput(w io.Writer, data any, pretty, colorize bool) error {
|
||||
encoder := yaml.NewEncoder(w)
|
||||
defer encoder.Close()
|
||||
|
||||
if pretty {
|
||||
encoder.SetIndent(2)
|
||||
}
|
||||
|
||||
if !colorize {
|
||||
return encoder.Encode(data)
|
||||
}
|
||||
|
||||
// For colored output, encode to bytes first, then colorize
|
||||
var buf []byte
|
||||
bufWriter := &byteWriter{buf: &buf}
|
||||
bufEncoder := yaml.NewEncoder(bufWriter)
|
||||
if pretty {
|
||||
bufEncoder.SetIndent(2)
|
||||
}
|
||||
if err := bufEncoder.Encode(data); err != nil {
|
||||
return err
|
||||
}
|
||||
bufEncoder.Close()
|
||||
|
||||
colorized := colorizeYAML(string(buf))
|
||||
_, err := w.Write([]byte(colorized))
|
||||
return err
|
||||
}
|
||||
|
||||
type byteWriter struct {
|
||||
buf *[]byte
|
||||
}
|
||||
|
||||
func (b *byteWriter) Write(p []byte) (n int, err error) {
|
||||
*b.buf = append(*b.buf, p...)
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
// colorizeYAML applies ANSI colors to YAML output.
|
||||
func colorizeYAML(input string) string {
|
||||
// Color definitions - wrap to match ReplaceAllStringFunc signature
|
||||
keyColor := func(s string) string { return color.New(color.FgCyan).Sprint(s) }
|
||||
stringColor := func(s string) string { return color.New(color.FgGreen).Sprint(s) }
|
||||
numberColor := func(s string) string { return color.New(color.FgYellow).Sprint(s) }
|
||||
boolColor := func(s string) string { return color.New(color.FgMagenta).Sprint(s) }
|
||||
nullColor := func(s string) string { return color.New(color.FgRed).Sprint(s) }
|
||||
|
||||
// Patterns for YAML elements
|
||||
keyPattern := regexp.MustCompile(`(?m)^(\s*)([a-zA-Z_][a-zA-Z0-9_-]*)(:)`)
|
||||
stringPattern := regexp.MustCompile(`:\s*"([^"]*)"`)
|
||||
quotedStringPattern := regexp.MustCompile(`:\s*'([^']*)'`)
|
||||
numberPattern := regexp.MustCompile(`:\s*(-?\d+\.?\d*)\s*$`)
|
||||
boolPattern := regexp.MustCompile(`:\s*(true|false)\s*$`)
|
||||
nullPattern := regexp.MustCompile(`:\s*(null|~)\s*$`)
|
||||
|
||||
result := input
|
||||
|
||||
// Apply colors in order (specific patterns first)
|
||||
result = nullPattern.ReplaceAllStringFunc(result, func(s string) string {
|
||||
return regexp.MustCompile(`(null|~)`).ReplaceAllStringFunc(s, nullColor)
|
||||
})
|
||||
|
||||
result = boolPattern.ReplaceAllStringFunc(result, func(s string) string {
|
||||
return regexp.MustCompile(`(true|false)`).ReplaceAllStringFunc(s, boolColor)
|
||||
})
|
||||
|
||||
result = numberPattern.ReplaceAllStringFunc(result, func(s string) string {
|
||||
return regexp.MustCompile(`(-?\d+\.?\d*)\s*$`).ReplaceAllStringFunc(s, numberColor)
|
||||
})
|
||||
|
||||
result = stringPattern.ReplaceAllStringFunc(result, func(s string) string {
|
||||
return regexp.MustCompile(`"([^"]*)"`).ReplaceAllStringFunc(s, stringColor)
|
||||
})
|
||||
|
||||
result = quotedStringPattern.ReplaceAllStringFunc(result, func(s string) string {
|
||||
return regexp.MustCompile(`'([^']*)'`).ReplaceAllStringFunc(s, stringColor)
|
||||
})
|
||||
|
||||
result = keyPattern.ReplaceAllStringFunc(result, func(s string) string {
|
||||
matches := keyPattern.FindStringSubmatch(s)
|
||||
if len(matches) >= 4 {
|
||||
return matches[1] + keyColor(matches[2]) + matches[3]
|
||||
}
|
||||
return s
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
Reference in New Issue
Block a user