Compare commits
5 Commits
30a3122202
...
624e329778
Author | SHA1 | Date | |
---|---|---|---|
624e329778 | |||
2a0068177e | |||
826fba415c | |||
0efc6f033b | |||
41bf5b86ea |
44
examples/simple/main.go
Normal file
44
examples/simple/main.go
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"git.flexabyte.io/flexabyte/go-kit/pkg/application"
|
||||||
|
"git.flexabyte.io/flexabyte/go-kit/pkg/slogd"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var err error
|
||||||
|
slogd.Init(slogd.LevelTrace, true)
|
||||||
|
|
||||||
|
config := application.Config{
|
||||||
|
Name: "main",
|
||||||
|
Title: "Main Test",
|
||||||
|
Banner: "",
|
||||||
|
Version: "0.1.0-alpha.0+metadata.20101112",
|
||||||
|
EnableGracefulShutdown: false,
|
||||||
|
OverrideRunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
fmt.Println("overrideRunE")
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
PersistentPreRunE: nil,
|
||||||
|
PersistentPostRunE: nil,
|
||||||
|
ShutdownSignals: nil,
|
||||||
|
ShutdownTimeout: 0,
|
||||||
|
SubCommands: nil,
|
||||||
|
SubCommandInitializeFunc: nil,
|
||||||
|
ValidArgs: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
var app application.Application
|
||||||
|
if app, err = application.New(config); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = app.Start(context.Background()); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
26
go.mod
26
go.mod
@ -1,3 +1,29 @@
|
|||||||
module git.flexabyte.io/flexabyte/go-kit
|
module git.flexabyte.io/flexabyte/go-kit
|
||||||
|
|
||||||
go 1.24.1
|
go 1.24.1
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/charmbracelet/lipgloss v1.1.0
|
||||||
|
github.com/samber/slog-formatter v1.2.0
|
||||||
|
github.com/samber/slog-multi v1.4.0
|
||||||
|
github.com/spf13/cobra v1.9.1
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||||
|
github.com/charmbracelet/colorprofile v0.3.0 // indirect
|
||||||
|
github.com/charmbracelet/x/ansi v0.8.0 // indirect
|
||||||
|
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
|
||||||
|
github.com/charmbracelet/x/term v0.2.1 // indirect
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
|
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||||
|
github.com/muesli/termenv v0.16.0 // indirect
|
||||||
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
|
github.com/samber/lo v1.49.1 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.6 // indirect
|
||||||
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||||
|
golang.org/x/sys v0.32.0 // indirect
|
||||||
|
golang.org/x/text v0.24.0 // indirect
|
||||||
|
)
|
||||||
|
56
go.sum
Normal file
56
go.sum
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||||
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||||
|
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
|
||||||
|
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
|
||||||
|
github.com/charmbracelet/colorprofile v0.3.0 h1:KtLh9uuu1RCt+Hml4s6Hz+kB1PfV3wi++1h5ia65yKQ=
|
||||||
|
github.com/charmbracelet/colorprofile v0.3.0/go.mod h1:oHJ340RS2nmG1zRGPmhJKJ/jf4FPNNk0P39/wBPA1G0=
|
||||||
|
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
|
||||||
|
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
|
||||||
|
github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE=
|
||||||
|
github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q=
|
||||||
|
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8=
|
||||||
|
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
|
||||||
|
github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k=
|
||||||
|
github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
|
||||||
|
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
|
||||||
|
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
|
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||||
|
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||||
|
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
|
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
|
||||||
|
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
|
||||||
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
|
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
|
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=
|
||||||
|
github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
|
||||||
|
github.com/samber/slog-formatter v1.2.0 h1:gTSHm4CxyySyhcxRkzk21CSKbGCdZVipbRMhINkNtQU=
|
||||||
|
github.com/samber/slog-formatter v1.2.0/go.mod h1:hgjhSd5Vf69XCOnVp0UW0QHCxJ8iDEm/qASjji6FNoI=
|
||||||
|
github.com/samber/slog-multi v1.3.3 h1:qhFXaYdW73FIWLt8SrXMXfPwY58NpluzKDwRdPvhWWY=
|
||||||
|
github.com/samber/slog-multi v1.3.3/go.mod h1:ACuZ5B6heK57TfMVkVknN2UZHoFfjCwRxR0Q2OXKHlo=
|
||||||
|
github.com/samber/slog-multi v1.4.0 h1:pwlPMIE7PrbTHQyKWDU+RIoxP1+HKTNOujk3/kdkbdg=
|
||||||
|
github.com/samber/slog-multi v1.4.0/go.mod h1:FsQ4Uv2L+E/8TZt+/BVgYZ1LoDWCbfCU21wVIoMMrO8=
|
||||||
|
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
|
||||||
|
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
|
||||||
|
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
||||||
|
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||||
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||||
|
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
||||||
|
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
|
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||||
|
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||||
|
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
||||||
|
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
@ -1,13 +0,0 @@
|
|||||||
package app
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
func New() *App {
|
|
||||||
return &App{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type App struct{}
|
|
||||||
|
|
||||||
func (a *App) Execute() {
|
|
||||||
fmt.Println("Hello World")
|
|
||||||
}
|
|
38
pkg/application/application.go
Normal file
38
pkg/application/application.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package application
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Application interface {
|
||||||
|
Start(ctx context.Context) error
|
||||||
|
Shutdown() error
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(c Config) (Application, error) {
|
||||||
|
var cmd *cobra.Command
|
||||||
|
var err error
|
||||||
|
if cmd, err = c.getRootCommand(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &application{
|
||||||
|
cmd: cmd,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type application struct {
|
||||||
|
cmd *cobra.Command
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *application) Start(ctx context.Context) error {
|
||||||
|
return a.cmd.ExecuteContext(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *application) Shutdown() error {
|
||||||
|
fmt.Println("Shutdown")
|
||||||
|
return nil
|
||||||
|
}
|
1
pkg/application/application_test.go
Normal file
1
pkg/application/application_test.go
Normal file
@ -0,0 +1 @@
|
|||||||
|
package application
|
28
pkg/application/commander.go
Normal file
28
pkg/application/commander.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package application
|
||||||
|
|
||||||
|
import "github.com/spf13/cobra"
|
||||||
|
|
||||||
|
type Commander interface {
|
||||||
|
Initialize(f func(c *cobra.Command)) *cobra.Command
|
||||||
|
}
|
||||||
|
|
||||||
|
type Command struct {
|
||||||
|
Command *cobra.Command
|
||||||
|
SubCommands []Commander
|
||||||
|
Configure func(c *cobra.Command)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Command) Initialize(f func(c *cobra.Command)) *cobra.Command {
|
||||||
|
if f != nil {
|
||||||
|
f(c.Command)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Configure != nil {
|
||||||
|
c.Configure(c.Command)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, sub := range c.SubCommands {
|
||||||
|
c.Command.AddCommand(sub.Initialize(f))
|
||||||
|
}
|
||||||
|
return c.Command
|
||||||
|
}
|
110
pkg/application/config.go
Normal file
110
pkg/application/config.go
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
package application
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"git.flexabyte.io/flexabyte/go-kit/pkg/semver"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Name string
|
||||||
|
Title string
|
||||||
|
Banner string
|
||||||
|
Version string
|
||||||
|
EnableGracefulShutdown bool
|
||||||
|
OverrideRunE func(cmd *cobra.Command, args []string) error
|
||||||
|
PersistentPreRunE []func(cmd *cobra.Command, args []string) error // collection of PreRunE functions
|
||||||
|
PersistentPostRunE []func(cmd *cobra.Command, args []string) error // collection of PostRunE functions
|
||||||
|
ShutdownSignals []os.Signal
|
||||||
|
ShutdownTimeout time.Duration
|
||||||
|
SubCommands []Command
|
||||||
|
SubCommandInitializeFunc func(cmd *cobra.Command)
|
||||||
|
ValidArgs []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Config) getRootCommand() (*cobra.Command, error) {
|
||||||
|
var err error
|
||||||
|
if err = c.Validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var long string
|
||||||
|
if c.Banner != "" {
|
||||||
|
long = c.Banner + "\n" + c.Title
|
||||||
|
} else {
|
||||||
|
long = c.Title
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: c.Name,
|
||||||
|
Short: c.Title,
|
||||||
|
Long: long,
|
||||||
|
PersistentPreRunE: persistentPreRunFuncE,
|
||||||
|
PersistentPostRunE: persistentPostRunFuncE,
|
||||||
|
RunE: runFuncE,
|
||||||
|
SilenceErrors: true,
|
||||||
|
SilenceUsage: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.OverrideRunE != nil {
|
||||||
|
cmd.RunE = c.OverrideRunE
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, subcommand := range c.SubCommands {
|
||||||
|
cmd.AddCommand(subcommand.Initialize(c.SubCommandInitializeFunc))
|
||||||
|
}
|
||||||
|
|
||||||
|
var v semver.Version
|
||||||
|
if v, err = c.ParseVersion(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
configureVersionFlag(cmd, v) // Configure app for version information
|
||||||
|
configureOutputFlags(cmd) // Configure verbosity
|
||||||
|
configureLoggingFlags(cmd) // Configure logging
|
||||||
|
cmd.PersistentFlags().SetNormalizeFunc(normalizeFunc) // normalize persistent flags
|
||||||
|
|
||||||
|
return cmd, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Config) ParseVersion() (semver.Version, error) {
|
||||||
|
return semver.Parse(c.Version)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Config) RegisterCommand(cmd Commander, f func(*cobra.Command)) {
|
||||||
|
appCmd.AddCommand(cmd.Initialize(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Config) RegisterCommands(cmds []Commander, f func(*cobra.Command)) {
|
||||||
|
for _, cmd := range cmds {
|
||||||
|
appCmd.AddCommand(cmd.Initialize(f))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Config) RegisterPersistentPreRunE(f func(cmd *cobra.Command, args []string) error) {
|
||||||
|
persistentPreRunE = append(persistentPreRunE, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Config) RegisterPersistentPostRunE(f func(cmd *cobra.Command, args []string) error) {
|
||||||
|
persistentPostRunE = append(persistentPostRunE, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Config) Validate() error {
|
||||||
|
if c.Name == "" {
|
||||||
|
return errors.New("name is required")
|
||||||
|
}
|
||||||
|
if c.Title == "" {
|
||||||
|
return errors.New("title is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if _, err = semver.Parse(c.Version); err != nil {
|
||||||
|
return fmt.Errorf("invalid version: %s", c.Version)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
22
pkg/application/config_test.go
Normal file
22
pkg/application/config_test.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package application
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConfig_Validate(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
config Config
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if err := tt.config.Validate(); (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
105
pkg/application/globals.go
Normal file
105
pkg/application/globals.go
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
package application
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/pflag"
|
||||||
|
|
||||||
|
"git.flexabyte.io/flexabyte/go-kit/pkg/slogd"
|
||||||
|
"git.flexabyte.io/flexabyte/go-kit/pkg/slogd_colored"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
appName string
|
||||||
|
appCmd *cobra.Command
|
||||||
|
persistentPreRunE []func(cmd *cobra.Command, args []string) error // collection of PreRunE functions
|
||||||
|
persistentPostRunE []func(cmd *cobra.Command, args []string) error // collection of PostRunE functions
|
||||||
|
outWriter io.Writer = os.Stdout
|
||||||
|
// version semver.Version
|
||||||
|
)
|
||||||
|
|
||||||
|
func helpFuncE(cmd *cobra.Command, args []string) error {
|
||||||
|
return cmd.Help()
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeFunc(f *pflag.FlagSet, name string) pflag.NormalizedName {
|
||||||
|
// switch name {
|
||||||
|
// case "no-color":
|
||||||
|
// name = "log-type"
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
return pflag.NormalizedName(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func persistentPreRunFuncE(cmd *cobra.Command, args []string) error {
|
||||||
|
slogd.SetLevel(slogd.Level(logLevelFlag))
|
||||||
|
if slogd.ActiveHandler() == slogd_colored.HandlerColor && noColorFlag {
|
||||||
|
slogd.UseHandler(slogd.HandlerText)
|
||||||
|
cmd.SetContext(slogd.WithContext(cmd.Context()))
|
||||||
|
}
|
||||||
|
|
||||||
|
slogd.FromContext(cmd.Context()).Log(cmd.Context(), slogd.LevelTrace, "starting application", slog.String("command", cmd.CommandPath()))
|
||||||
|
slogd.FromContext(cmd.Context()).Log(cmd.Context(), slogd.LevelTrace, "executing PersistentPreRun")
|
||||||
|
|
||||||
|
// Make sure we can always get the version
|
||||||
|
if versionFlag || cmd.Use == versionName {
|
||||||
|
slogd.FromContext(cmd.Context()).LogAttrs(cmd.Context(), slogd.LevelTrace, "overriding command", slog.String("old_function", runtime.FuncForPC(reflect.ValueOf(cmd.RunE).Pointer()).Name()), slog.String("new_function", runtime.FuncForPC(reflect.ValueOf(versionRunFuncE).Pointer()).Name()))
|
||||||
|
cmd.RunE = versionRunFuncE
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure that we show the app help if no commands or flags are passed
|
||||||
|
if cmd.CalledAs() == appName && runtime.FuncForPC(reflect.ValueOf(cmd.RunE).Pointer()).Name() == runtime.FuncForPC(reflect.ValueOf(runFuncE).Pointer()).Name() {
|
||||||
|
slogd.FromContext(cmd.Context()).LogAttrs(cmd.Context(), slogd.LevelTrace, "overriding command", slog.String("old_function", runtime.FuncForPC(reflect.ValueOf(cmd.RunE).Pointer()).Name()), slog.String("new_function", runtime.FuncForPC(reflect.ValueOf(helpFuncE).Pointer()).Name()))
|
||||||
|
|
||||||
|
cmd.RunE = helpFuncE
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO move to front??
|
||||||
|
if quietFlag {
|
||||||
|
slogd.FromContext(cmd.Context()).LogAttrs(cmd.Context(), slogd.LevelDebug, "activating quiet mode")
|
||||||
|
outWriter = io.Discard
|
||||||
|
}
|
||||||
|
|
||||||
|
if persistentPreRunE == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
for _, preRun := range persistentPreRunE {
|
||||||
|
slogd.FromContext(cmd.Context()).Log(cmd.Context(), slogd.LevelTrace, "executing PersistentPreRun function", slog.String("function", runtime.FuncForPC(reflect.ValueOf(preRun).Pointer()).Name()))
|
||||||
|
if err = preRun(cmd, args); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func persistentPostRunFuncE(cmd *cobra.Command, args []string) error {
|
||||||
|
defer slogd.FromContext(cmd.Context()).Log(cmd.Context(), slogd.LevelTrace, "stopping application", slog.String("command", cmd.CommandPath()))
|
||||||
|
slogd.FromContext(cmd.Context()).Log(cmd.Context(), slogd.LevelTrace, "executing PersistentPostRunE")
|
||||||
|
|
||||||
|
if persistentPostRunE == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
for _, postRun := range persistentPostRunE {
|
||||||
|
slogd.FromContext(cmd.Context()).Log(cmd.Context(), slogd.LevelTrace, "executing PersistentPostRun function", slog.String("function", runtime.FuncForPC(reflect.ValueOf(postRun).Pointer()).Name()))
|
||||||
|
if err = postRun(cmd, args); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// appRunE is an empty catch function to allow overrides through persistentPreRunE
|
||||||
|
func runFuncE(cmd *cobra.Command, args []string) error {
|
||||||
|
return nil
|
||||||
|
}
|
35
pkg/application/logging.go
Normal file
35
pkg/application/logging.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package application
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
LogOutputStdOut = "stdout"
|
||||||
|
LogOutputStdErr = "stderr"
|
||||||
|
LogOutputFile = "file"
|
||||||
|
)
|
||||||
|
|
||||||
|
var logLevelFlag string
|
||||||
|
var logOutputFlag string
|
||||||
|
var logTypeFlag string
|
||||||
|
|
||||||
|
func addLogLevelFlag(cmd *cobra.Command) {
|
||||||
|
cmd.PersistentFlags().StringVarP(&logLevelFlag, "log-level", "", "info", "Set log level (trace, debug, info, warn, error, fatal)")
|
||||||
|
}
|
||||||
|
|
||||||
|
func addLogOutputFlag(cmd *cobra.Command) {
|
||||||
|
cmd.PersistentFlags().StringVarP(&logOutputFlag, "log-outWriter", "", "stderr", "Set log outWriter (stdout, stderr, file)")
|
||||||
|
}
|
||||||
|
|
||||||
|
func addLogTypeFlag(cmd *cobra.Command) {
|
||||||
|
cmd.PersistentFlags().StringVarP(&logTypeFlag, "log-type", "", "text", "Set log type (text, json, color)")
|
||||||
|
}
|
||||||
|
|
||||||
|
func configureLoggingFlags(cmd *cobra.Command) {
|
||||||
|
addLogLevelFlag(cmd)
|
||||||
|
addLogOutputFlag(cmd)
|
||||||
|
addLogTypeFlag(cmd)
|
||||||
|
|
||||||
|
cmd.MarkFlagsMutuallyExclusive("no-color", "log-type")
|
||||||
|
}
|
36
pkg/application/output.go
Normal file
36
pkg/application/output.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package application
|
||||||
|
|
||||||
|
import "github.com/spf13/cobra"
|
||||||
|
|
||||||
|
var jsonOutputFlag bool
|
||||||
|
var noColorFlag bool
|
||||||
|
var quietFlag bool
|
||||||
|
var verboseFlag bool
|
||||||
|
|
||||||
|
func addJsonOutputFlag(cmd *cobra.Command) {
|
||||||
|
cmd.PersistentFlags().BoolVarP(&jsonOutputFlag, "json", "", false, "Enable JSON outWriter")
|
||||||
|
}
|
||||||
|
|
||||||
|
func addNoColorFlag(cmd *cobra.Command) {
|
||||||
|
cmd.PersistentFlags().BoolVarP(&noColorFlag, "no-color", "", false, "Disable color outWriter")
|
||||||
|
}
|
||||||
|
|
||||||
|
func addQuietFlag(cmd *cobra.Command) {
|
||||||
|
cmd.PersistentFlags().BoolVarP(&quietFlag, "quiet", "q", false, "Enable quiet mode")
|
||||||
|
}
|
||||||
|
|
||||||
|
func addVerboseFlag(cmd *cobra.Command) {
|
||||||
|
cmd.PersistentFlags().BoolVarP(&verboseFlag, "verbose", "v", false, "Enable verbose outWriter")
|
||||||
|
}
|
||||||
|
|
||||||
|
func configureOutputFlags(cmd *cobra.Command) {
|
||||||
|
addJsonOutputFlag(cmd)
|
||||||
|
addNoColorFlag(cmd)
|
||||||
|
addVerboseFlag(cmd)
|
||||||
|
addQuietFlag(cmd)
|
||||||
|
|
||||||
|
cmd.MarkFlagsMutuallyExclusive("verbose", "quiet", "json")
|
||||||
|
cmd.MarkFlagsMutuallyExclusive("json", "no-color")
|
||||||
|
cmd.MarkFlagsMutuallyExclusive("quiet", "no-color")
|
||||||
|
|
||||||
|
}
|
69
pkg/application/version.go
Normal file
69
pkg/application/version.go
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
package application
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"git.flexabyte.io/flexabyte/go-kit/pkg/semver"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
versionName = "version"
|
||||||
|
versionShortHand = "V"
|
||||||
|
versionUsage = "Show version information"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
version semver.Version
|
||||||
|
versionFlag bool
|
||||||
|
versionCmd = &cobra.Command{
|
||||||
|
Use: versionName,
|
||||||
|
Short: versionUsage,
|
||||||
|
RunE: versionRunFuncE,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func addVersionFlag(cmd *cobra.Command) {
|
||||||
|
cmd.PersistentFlags().BoolVarP(&versionFlag, versionName, versionShortHand, false, versionUsage)
|
||||||
|
}
|
||||||
|
|
||||||
|
func configureVersionFlag(cmd *cobra.Command, v semver.Version) {
|
||||||
|
version = v
|
||||||
|
cmd.AddCommand(versionCmd)
|
||||||
|
addVersionFlag(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func printVersion(v semver.Version) string {
|
||||||
|
var output string
|
||||||
|
if !verboseFlag {
|
||||||
|
output = v.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
if jsonOutputFlag {
|
||||||
|
var b []byte
|
||||||
|
b, _ = json.Marshal(v)
|
||||||
|
output = string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
if output != "" {
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"Full: %s\nVersion: %s\nChannel: %s\nCommit: %s\nDate: %s",
|
||||||
|
v.String(),
|
||||||
|
v.Number(),
|
||||||
|
v.Release(),
|
||||||
|
v.Commit(),
|
||||||
|
v.Date(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func versionRunFuncE(cmd *cobra.Command, args []string) error {
|
||||||
|
if _, err := fmt.Fprintln(outWriter, printVersion(version)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
23
pkg/semver/metadata.go
Normal file
23
pkg/semver/metadata.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package semver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
validMetadata = `^(?P<commit>[0-9a-zA-Z]{8}).(?P<date>[0-9]{8})$`
|
||||||
|
)
|
||||||
|
|
||||||
|
var regexMetadata = regexp.MustCompile(validMetadata)
|
||||||
|
|
||||||
|
type Metadata string
|
||||||
|
|
||||||
|
func SplitMetadata(m Metadata) (string, string, error) {
|
||||||
|
if !regexMetadata.MatchString(string(m)) {
|
||||||
|
return "", "", fmt.Errorf("invalid metadata: %s", m)
|
||||||
|
}
|
||||||
|
|
||||||
|
match := regexMetadata.FindStringSubmatch(string(m))
|
||||||
|
return match[1], match[2], nil
|
||||||
|
}
|
37
pkg/semver/metadata_test.go
Normal file
37
pkg/semver/metadata_test.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package semver
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
var validMetadataTests = []struct {
|
||||||
|
name string
|
||||||
|
metadata Metadata
|
||||||
|
commit string
|
||||||
|
date string
|
||||||
|
err bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "simple",
|
||||||
|
metadata: "metadata.20101112",
|
||||||
|
commit: "metadata",
|
||||||
|
date: "20101112",
|
||||||
|
err: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSplitMetadata(t *testing.T) {
|
||||||
|
for _, tt := range validMetadataTests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, got1, err := SplitMetadata(tt.metadata)
|
||||||
|
if (err != nil) != tt.err {
|
||||||
|
t.Errorf("SplitMetadata() error = %v, wantErr %v", err, tt.err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if got != tt.commit {
|
||||||
|
t.Errorf("SplitMetadata() got = %v, want %v", got, tt.commit)
|
||||||
|
}
|
||||||
|
if got1 != tt.date {
|
||||||
|
t.Errorf("SplitMetadata() got1 = %v, want %v", got1, tt.date)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
3
pkg/semver/prerelease.go
Normal file
3
pkg/semver/prerelease.go
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
package semver
|
||||||
|
|
||||||
|
type PreRelease string
|
117
pkg/semver/version.go
Normal file
117
pkg/semver/version.go
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
package semver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// https://semver.org/ && https://regex101.com/r/Ly7O1x/3/
|
||||||
|
validSemVer = `^(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`
|
||||||
|
)
|
||||||
|
|
||||||
|
var regexSemver = regexp.MustCompile(validSemVer)
|
||||||
|
|
||||||
|
type Version struct {
|
||||||
|
Major int64
|
||||||
|
Minor int64
|
||||||
|
Patch int64
|
||||||
|
PreRelease PreRelease
|
||||||
|
Metadata Metadata
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Version) Commit() string {
|
||||||
|
commit, _, err := SplitMetadata(v.Metadata)
|
||||||
|
if err != nil {
|
||||||
|
return string(v.Metadata)
|
||||||
|
}
|
||||||
|
return commit
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Version) Date() string {
|
||||||
|
_, date, err := SplitMetadata(v.Metadata)
|
||||||
|
if err != nil {
|
||||||
|
return string(v.Metadata)
|
||||||
|
}
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Version) Release() string {
|
||||||
|
switch v.PreRelease {
|
||||||
|
case "":
|
||||||
|
return fmt.Sprint("stable")
|
||||||
|
default:
|
||||||
|
return string(v.PreRelease)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Version) String() string {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
fmt.Fprintf(&buf, "%d.%d.%d", v.Major, v.Minor, v.Patch)
|
||||||
|
|
||||||
|
if v.PreRelease != "" {
|
||||||
|
fmt.Fprintf(&buf, "-%s", v.PreRelease)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.Metadata != "" {
|
||||||
|
fmt.Fprintf(&buf, "+%s", v.Metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Version) Number() string {
|
||||||
|
return fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Parse(v string) (Version, error) {
|
||||||
|
if !regexSemver.MatchString(v) {
|
||||||
|
return Version{}, fmt.Errorf("invalid version: %s", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
match := regexSemver.FindStringSubmatch(v)
|
||||||
|
matchMap := make(map[string]string)
|
||||||
|
for i, name := range regexSemver.SubexpNames() {
|
||||||
|
if i != 0 && name != "" {
|
||||||
|
matchMap[name] = match[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var major int64
|
||||||
|
var minor int64
|
||||||
|
var patch int64
|
||||||
|
var preRelease PreRelease
|
||||||
|
var metadata Metadata
|
||||||
|
|
||||||
|
if major, err = strconv.ParseInt(matchMap["major"], 10, 64); err != nil {
|
||||||
|
return Version{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if minor, err = strconv.ParseInt(matchMap["minor"], 10, 64); err != nil {
|
||||||
|
return Version{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if patch, err = strconv.ParseInt(matchMap["patch"], 10, 64); err != nil {
|
||||||
|
return Version{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if matchMap["prerelease"] != "" {
|
||||||
|
preRelease = PreRelease(matchMap["prerelease"])
|
||||||
|
}
|
||||||
|
|
||||||
|
if matchMap["buildmetadata"] != "" {
|
||||||
|
metadata = Metadata(matchMap["buildmetadata"])
|
||||||
|
}
|
||||||
|
|
||||||
|
return Version{
|
||||||
|
Major: major,
|
||||||
|
Minor: minor,
|
||||||
|
Patch: patch,
|
||||||
|
PreRelease: preRelease,
|
||||||
|
Metadata: metadata,
|
||||||
|
}, nil
|
||||||
|
}
|
210
pkg/semver/version_test.go
Normal file
210
pkg/semver/version_test.go
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
package semver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var validVersionTests = []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
version Version
|
||||||
|
inputErr bool
|
||||||
|
commit string
|
||||||
|
date string
|
||||||
|
release string
|
||||||
|
full string
|
||||||
|
number string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "stable",
|
||||||
|
input: "0.1.0",
|
||||||
|
version: Version{
|
||||||
|
Major: 0,
|
||||||
|
Minor: 1,
|
||||||
|
},
|
||||||
|
inputErr: false,
|
||||||
|
commit: "",
|
||||||
|
date: "",
|
||||||
|
release: "stable",
|
||||||
|
full: "0.1.0",
|
||||||
|
number: "0.1.0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "stable.1",
|
||||||
|
input: "0.1.1",
|
||||||
|
version: Version{
|
||||||
|
Major: 0,
|
||||||
|
Minor: 1,
|
||||||
|
Patch: 1,
|
||||||
|
},
|
||||||
|
inputErr: false,
|
||||||
|
commit: "",
|
||||||
|
date: "",
|
||||||
|
release: "stable",
|
||||||
|
full: "0.1.1",
|
||||||
|
number: "0.1.1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "stable.1+metadata",
|
||||||
|
input: "0.1.1+metadata",
|
||||||
|
version: Version{
|
||||||
|
Major: 0,
|
||||||
|
Minor: 1,
|
||||||
|
Patch: 1,
|
||||||
|
Metadata: "metadata",
|
||||||
|
},
|
||||||
|
inputErr: false,
|
||||||
|
commit: "metadata",
|
||||||
|
date: "metadata",
|
||||||
|
release: "stable",
|
||||||
|
full: "0.1.1+metadata",
|
||||||
|
number: "0.1.1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "stable.1+metadata.date",
|
||||||
|
input: "0.1.1+metadata.20101112",
|
||||||
|
version: Version{
|
||||||
|
Major: 0,
|
||||||
|
Minor: 1,
|
||||||
|
Patch: 1,
|
||||||
|
Metadata: "metadata.20101112",
|
||||||
|
},
|
||||||
|
inputErr: false,
|
||||||
|
commit: "metadata",
|
||||||
|
date: "20101112",
|
||||||
|
release: "stable",
|
||||||
|
full: "0.1.1+metadata.20101112",
|
||||||
|
number: "0.1.1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "alpha",
|
||||||
|
input: "0.1.1-alpha",
|
||||||
|
version: Version{
|
||||||
|
Major: 0,
|
||||||
|
Minor: 1,
|
||||||
|
Patch: 1,
|
||||||
|
PreRelease: "alpha",
|
||||||
|
},
|
||||||
|
inputErr: false,
|
||||||
|
commit: "",
|
||||||
|
release: "alpha",
|
||||||
|
full: "0.1.1-alpha",
|
||||||
|
number: "0.1.1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "alpha.1",
|
||||||
|
input: "0.1.1-alpha.1",
|
||||||
|
version: Version{
|
||||||
|
Major: 0,
|
||||||
|
Minor: 1,
|
||||||
|
Patch: 1,
|
||||||
|
PreRelease: "alpha.1",
|
||||||
|
},
|
||||||
|
inputErr: false,
|
||||||
|
commit: "",
|
||||||
|
release: "alpha.1",
|
||||||
|
full: "0.1.1-alpha.1",
|
||||||
|
number: "0.1.1",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParse(t *testing.T) {
|
||||||
|
for _, tt := range validVersionTests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := Parse(tt.input)
|
||||||
|
if (err != nil) != tt.inputErr {
|
||||||
|
t.Errorf("Parse() error = %v, wantErr %v", err, tt.inputErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, tt.version) {
|
||||||
|
t.Errorf("Parse() got = %v, want %v", got, tt.version)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVersion_Commit(t *testing.T) {
|
||||||
|
for _, tt := range validVersionTests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
v := Version{
|
||||||
|
Major: tt.version.Major,
|
||||||
|
Minor: tt.version.Minor,
|
||||||
|
Patch: tt.version.Patch,
|
||||||
|
PreRelease: tt.version.PreRelease,
|
||||||
|
Metadata: tt.version.Metadata,
|
||||||
|
}
|
||||||
|
if got := v.Commit(); got != tt.commit {
|
||||||
|
t.Errorf("Commit() = %v, want %v", got, tt.commit)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVersion_Date(t *testing.T) {
|
||||||
|
for _, tt := range validVersionTests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
v := Version{
|
||||||
|
Major: tt.version.Major,
|
||||||
|
Minor: tt.version.Minor,
|
||||||
|
Patch: tt.version.Patch,
|
||||||
|
PreRelease: tt.version.PreRelease,
|
||||||
|
Metadata: tt.version.Metadata,
|
||||||
|
}
|
||||||
|
if got := v.Date(); got != tt.date {
|
||||||
|
t.Errorf("Date() = %v, want %v", got, tt.date)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVersion_Release(t *testing.T) {
|
||||||
|
for _, tt := range validVersionTests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
v := Version{
|
||||||
|
Major: tt.version.Major,
|
||||||
|
Minor: tt.version.Minor,
|
||||||
|
Patch: tt.version.Patch,
|
||||||
|
PreRelease: tt.version.PreRelease,
|
||||||
|
Metadata: tt.version.Metadata,
|
||||||
|
}
|
||||||
|
if got := v.Release(); got != tt.release {
|
||||||
|
t.Errorf("Release() = %v, want %v", got, tt.release)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVersion_String(t *testing.T) {
|
||||||
|
for _, tt := range validVersionTests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
v := Version{
|
||||||
|
Major: tt.version.Major,
|
||||||
|
Minor: tt.version.Minor,
|
||||||
|
Patch: tt.version.Patch,
|
||||||
|
PreRelease: tt.version.PreRelease,
|
||||||
|
Metadata: tt.version.Metadata,
|
||||||
|
}
|
||||||
|
if got := v.String(); got != tt.full {
|
||||||
|
t.Errorf("String() = %v, want %v", got, tt.full)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVersion_VersionNumber(t *testing.T) {
|
||||||
|
for _, tt := range validVersionTests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
v := Version{
|
||||||
|
Major: tt.version.Major,
|
||||||
|
Minor: tt.version.Minor,
|
||||||
|
Patch: tt.version.Patch,
|
||||||
|
PreRelease: tt.version.PreRelease,
|
||||||
|
Metadata: tt.version.Metadata,
|
||||||
|
}
|
||||||
|
if got := v.Number(); got != tt.number {
|
||||||
|
t.Errorf("Number() = %v, want %v", got, tt.number)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
32
pkg/slogd/disabledHandler.go
Normal file
32
pkg/slogd/disabledHandler.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package slogd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log/slog"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newDisabledHandler() slog.Handler {
|
||||||
|
return &disabledHandler{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerDisabledHandler(activate bool) {
|
||||||
|
RegisterSink(handlerDisabled, newDisabledHandler(), activate)
|
||||||
|
}
|
||||||
|
|
||||||
|
type disabledHandler struct{}
|
||||||
|
|
||||||
|
func (h *disabledHandler) Handle(ctx context.Context, r slog.Record) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *disabledHandler) Enabled(ctx context.Context, level slog.Level) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *disabledHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *disabledHandler) WithGroup(group string) slog.Handler {
|
||||||
|
return h
|
||||||
|
}
|
10
pkg/slogd/jsonHandler.go
Normal file
10
pkg/slogd/jsonHandler.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package slogd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"log/slog"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RegisterJSONHandler(w io.Writer, activate bool) {
|
||||||
|
RegisterSink(HandlerJSON, slog.NewJSONHandler(w, HandlerOptions()), activate)
|
||||||
|
}
|
56
pkg/slogd/level.go
Normal file
56
pkg/slogd/level.go
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
package slogd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log/slog"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
LevelTrace = slog.Level(-8)
|
||||||
|
LevelDebug = slog.LevelDebug
|
||||||
|
LevelInfo = slog.LevelInfo
|
||||||
|
LevelNotice = slog.Level(2)
|
||||||
|
LevelWarn = slog.LevelWarn
|
||||||
|
LevelError = slog.LevelError
|
||||||
|
LevelFatal = slog.Level(12)
|
||||||
|
LevelDefault = LevelInfo
|
||||||
|
)
|
||||||
|
|
||||||
|
var levelNames = map[slog.Leveler]string{
|
||||||
|
LevelTrace: "TRACE",
|
||||||
|
LevelDebug: "DEBUG",
|
||||||
|
LevelInfo: "INFO",
|
||||||
|
LevelNotice: "NOTICE",
|
||||||
|
LevelWarn: "WARN",
|
||||||
|
LevelError: "ERROR",
|
||||||
|
LevelFatal: "FATAL",
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReplaceAttrs(groups []string, a slog.Attr) slog.Attr {
|
||||||
|
if a.Key == slog.LevelKey {
|
||||||
|
a.Value = slog.StringValue(LevelName(a.Value.Any().(slog.Level)))
|
||||||
|
}
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func Level(l string) slog.Level {
|
||||||
|
mux.Lock()
|
||||||
|
defer mux.Unlock()
|
||||||
|
for k, v := range levelNames {
|
||||||
|
if strings.ToUpper(l) == v {
|
||||||
|
return k.Level()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return LevelDefault
|
||||||
|
}
|
||||||
|
|
||||||
|
func LevelName(l slog.Level) string {
|
||||||
|
mux.Lock()
|
||||||
|
defer mux.Unlock()
|
||||||
|
for k, v := range levelNames {
|
||||||
|
if k == l {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return levelNames[LevelDefault]
|
||||||
|
}
|
140
pkg/slogd/slogd.go
Normal file
140
pkg/slogd/slogd.go
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
package slogd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log/slog"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
slogformatter "github.com/samber/slog-formatter"
|
||||||
|
slogmulti "github.com/samber/slog-multi"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
HandlerText string = "text"
|
||||||
|
HandlerJSON string = "json"
|
||||||
|
handlerDisabled string = "disabled"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
FlowFanOut Flow = iota
|
||||||
|
FlowPipeline
|
||||||
|
FlowRouting
|
||||||
|
FlowFailOver
|
||||||
|
FlowLoadBalancing
|
||||||
|
)
|
||||||
|
|
||||||
|
type Flow int
|
||||||
|
|
||||||
|
var (
|
||||||
|
ctxKey = contextKey{}
|
||||||
|
)
|
||||||
|
var (
|
||||||
|
handlers = make(map[string]slog.Handler)
|
||||||
|
activeHandler string
|
||||||
|
level = new(slog.LevelVar)
|
||||||
|
formatters []slogformatter.Formatter
|
||||||
|
middlewares []slogmulti.Middleware
|
||||||
|
source bool
|
||||||
|
logger *slog.Logger
|
||||||
|
mux = &sync.Mutex{}
|
||||||
|
)
|
||||||
|
|
||||||
|
func ActiveHandler() string {
|
||||||
|
mux.Lock()
|
||||||
|
defer mux.Unlock()
|
||||||
|
return activeHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
func Key() contextKey {
|
||||||
|
return ctxKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func Disable() {
|
||||||
|
UseHandler(handlerDisabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
func FromContext(ctx context.Context) *slog.Logger {
|
||||||
|
if l, ok := ctx.Value(Key()).(*slog.Logger); ok {
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
return Logger()
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandlerOptions() *slog.HandlerOptions {
|
||||||
|
return &slog.HandlerOptions{
|
||||||
|
AddSource: source,
|
||||||
|
Level: level,
|
||||||
|
ReplaceAttr: ReplaceAttrs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Init(l slog.Level, addSource bool) {
|
||||||
|
level.Set(l)
|
||||||
|
source = addSource
|
||||||
|
}
|
||||||
|
|
||||||
|
func Logger() *slog.Logger {
|
||||||
|
mux.Lock()
|
||||||
|
defer mux.Unlock()
|
||||||
|
|
||||||
|
if logger == nil {
|
||||||
|
logger = slog.New(handlers[handlerDisabled])
|
||||||
|
}
|
||||||
|
return logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetLevel(l slog.Level) {
|
||||||
|
mux.Lock()
|
||||||
|
defer mux.Unlock()
|
||||||
|
level.Set(l)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterFormatter(f slogformatter.Formatter) {
|
||||||
|
mux.Lock()
|
||||||
|
defer mux.Unlock()
|
||||||
|
formatters = append(formatters, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterMiddleware(h slogmulti.Middleware) {
|
||||||
|
mux.Lock()
|
||||||
|
defer mux.Unlock()
|
||||||
|
middlewares = append(middlewares, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterSink(name string, h slog.Handler, activate bool) {
|
||||||
|
mux.Lock()
|
||||||
|
handlers[name] = h
|
||||||
|
mux.Unlock()
|
||||||
|
|
||||||
|
if activate {
|
||||||
|
UseHandler(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func UseHandler(name string) {
|
||||||
|
mux.Lock()
|
||||||
|
defer mux.Unlock()
|
||||||
|
if _, ok := handlers[name]; !ok {
|
||||||
|
Logger().LogAttrs(context.Background(), LevelError, "could not find handler", slog.String("name", name))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
formatterPipe := slogformatter.NewFormatterMiddleware(formatters...)
|
||||||
|
pipe := slogmulti.Pipe(middlewares...).Pipe(formatterPipe)
|
||||||
|
handler := slogmulti.Fanout(handlers[name])
|
||||||
|
|
||||||
|
logger = slog.New(pipe.Handler(handler))
|
||||||
|
activeHandler = name
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithContext(ctx context.Context) context.Context {
|
||||||
|
return context.WithValue(ctx, Key(), Logger())
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// RegisterFormatter(LevelFormatter())
|
||||||
|
// RegisterMiddleware(NewLevelMiddleware())
|
||||||
|
registerDisabledHandler(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
type contextKey struct{}
|
10
pkg/slogd/textHandler.go
Normal file
10
pkg/slogd/textHandler.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package slogd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"log/slog"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RegisterTextHandler(w io.Writer, activate bool) {
|
||||||
|
RegisterSink(HandlerText, slog.NewTextHandler(w, HandlerOptions()), activate)
|
||||||
|
}
|
90
pkg/slogd_colored/coloredTextHandler.go
Normal file
90
pkg/slogd_colored/coloredTextHandler.go
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
package slogd_colored
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log/slog"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/charmbracelet/lipgloss"
|
||||||
|
|
||||||
|
"git.flexabyte.io/flexabyte/go-kit/pkg/slogd"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
HandlerColor string = "color"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ColouredTextHandler struct {
|
||||||
|
handler slog.Handler
|
||||||
|
w io.Writer
|
||||||
|
mux *sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ColouredTextHandler) Handle(ctx context.Context, r slog.Record) error {
|
||||||
|
var err error
|
||||||
|
levelName := slogd.LevelName(r.Level)
|
||||||
|
|
||||||
|
switch r.Level {
|
||||||
|
case slogd.LevelTrace:
|
||||||
|
levelName = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("#FF7F50")).Render(levelName) // coral
|
||||||
|
case slogd.LevelDebug:
|
||||||
|
levelName = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("#00FFFF")).Render(levelName) // cyan
|
||||||
|
case slogd.LevelInfo:
|
||||||
|
levelName = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("#00FF00")).Render(levelName) // green
|
||||||
|
case slogd.LevelNotice:
|
||||||
|
levelName = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("#FFFF00")).Render(levelName) // yellow
|
||||||
|
case slogd.LevelWarn:
|
||||||
|
levelName = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("#FFA500")).Render(levelName) // orange
|
||||||
|
case slogd.LevelError:
|
||||||
|
levelName = lipgloss.NewStyle().Blink(true).Bold(true).Foreground(lipgloss.Color("#FF0000")).Render(levelName) // red
|
||||||
|
case slogd.LevelFatal:
|
||||||
|
levelName = lipgloss.NewStyle().Blink(true).Bold(true).Foreground(lipgloss.Color("#FF00FF")).Render(levelName) // magenta
|
||||||
|
|
||||||
|
}
|
||||||
|
fields := make(map[string]interface{}, r.NumAttrs())
|
||||||
|
r.Attrs(func(a slog.Attr) bool {
|
||||||
|
fields[a.Key] = a.Value.Any()
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
var fieldStrings []string
|
||||||
|
for k, v := range fields {
|
||||||
|
fieldStrings = append(fieldStrings, k+"="+fmt.Sprintf("%v", v))
|
||||||
|
}
|
||||||
|
|
||||||
|
timeStr := r.Time.Format("[15:05:05.0000]")
|
||||||
|
msg := lipgloss.NewStyle().Foreground(lipgloss.NoColor{}).Render(r.Message) // white
|
||||||
|
|
||||||
|
h.mux.Lock()
|
||||||
|
defer h.mux.Unlock()
|
||||||
|
_, err = h.w.Write([]byte(strings.Join([]string{timeStr, levelName, msg, lipgloss.NewStyle().Foreground(lipgloss.Color("#FFFFFF")).Render(strings.Join(fieldStrings, " "))}, " ") + "\n"))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ColouredTextHandler) Enabled(ctx context.Context, level slog.Level) bool {
|
||||||
|
return h.handler.Enabled(ctx, level)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ColouredTextHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
||||||
|
return h.handler.WithAttrs(attrs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ColouredTextHandler) WithGroup(group string) slog.Handler {
|
||||||
|
return h.handler.WithGroup(group)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewColoredTextHandler(w io.Writer, opts *slog.HandlerOptions) slog.Handler {
|
||||||
|
return &ColouredTextHandler{
|
||||||
|
handler: slog.NewTextHandler(w, opts),
|
||||||
|
w: w,
|
||||||
|
mux: &sync.Mutex{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterColoredTextHandler(w io.Writer, activate bool) {
|
||||||
|
slogd.RegisterSink(HandlerColor, NewColoredTextHandler(w, slogd.HandlerOptions()), activate)
|
||||||
|
}
|
Reference in New Issue
Block a user