update to latest gioui.org API, fix cross platform builds
This commit is contained in:
93
CLAUDE.md
Normal file
93
CLAUDE.md
Normal file
@@ -0,0 +1,93 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Build Commands
|
||||
|
||||
```bash
|
||||
# Build entire project
|
||||
go build ./...
|
||||
|
||||
# Run tests
|
||||
go test ./...
|
||||
|
||||
# Run a single test
|
||||
go test -run TestName ./path/to/package
|
||||
|
||||
# Cross-platform builds (supported targets)
|
||||
GOOS=darwin GOARCH=amd64 go build ./... # macOS
|
||||
GOOS=windows GOARCH=amd64 go build ./... # Windows
|
||||
GOOS=android GOARCH=arm64 go build ./... # Android
|
||||
GOOS=js GOARCH=wasm go build ./... # WebAssembly
|
||||
|
||||
# Run example applications
|
||||
go run ./cmd/hello
|
||||
go run ./cmd/clipboard
|
||||
go run ./cmd/iconchooser
|
||||
```
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
Prevara (formerly "gel" - Gio Elements) is a widget toolkit built on top of [Gio](https://gioui.org). It provides a fluent API for building GUIs with method chaining.
|
||||
|
||||
### Core Components
|
||||
|
||||
**Window & Theme** (`window.go`, `theme.go`): Entry points for applications. `Window` embeds both `Theme` (styling) and `app.Window` (platform integration). Create windows via `NewWindowP9(quit)` for default theme or `NewWindow(theme)` for custom themes.
|
||||
|
||||
**App Framework** (`app.go`): Multi-page application scaffold with title bar, sidebar, status bar, and page navigation. Create with `window.App(size, activePage, breakpoint)`.
|
||||
|
||||
**Fluent Widget Pattern**: All widgets follow a builder pattern where configuration methods return `*Widget` for chaining, and `Fn` is the final method that returns `l.Widget` (Gio's layout function type). Example:
|
||||
```go
|
||||
w.Flex().Vertical().SpaceEvenly().
|
||||
Rigid(w.H1("Title").Color("Primary").Fn).
|
||||
Flexed(1, bodyWidget).
|
||||
Fn
|
||||
```
|
||||
|
||||
**Widget Pool** (`pools.go`, `pooltypes.go`): Object pooling for stateful widgets (Bool, List, Clickable, Editor, IncDec, Checkable). Access via `window.WidgetPool.GetXxx()`. Call `pool.Reset()` at frame start to reclaim widgets.
|
||||
|
||||
### Layout Widgets
|
||||
|
||||
- `Flex` / `VFlex`: Horizontal/vertical flex layouts with Rigid/Flexed children
|
||||
- `Stack`: Z-axis stacking with alignment
|
||||
- `Inset`: Padding/margins
|
||||
- `Direction`: Directional alignment wrapper
|
||||
- `Fill`: Background fills with alignment
|
||||
|
||||
### Input Widgets
|
||||
|
||||
- `Clickable`: Click/press handling
|
||||
- `Bool`: Boolean state widget
|
||||
- `Editor`: Text input
|
||||
- `Checkable` / `Checkbox`: Toggle controls
|
||||
- `Slider` / `IntSlider`: Value sliders
|
||||
- `IncDec`: Increment/decrement buttons
|
||||
|
||||
### Display Widgets
|
||||
|
||||
- `Label`: Text display (with H1-H6, Body1/2, Caption, etc. presets)
|
||||
- `Icon` / `IconButton`: Icon rendering
|
||||
- `Button` / `ButtonLayout`: Clickable buttons
|
||||
- `ProgressBar` / `Indefinite`: Progress indicators
|
||||
- `List` / `WrapList`: Scrollable lists
|
||||
|
||||
### Supporting Packages
|
||||
|
||||
- `pkg/log`: Colorized structured logging. Initialize with `log.GetLogPrinterSet()` returning F, E, W, I, D, T level printers
|
||||
- `pkg/qu`: Channel utilities for quit signals (`qu.T()` creates quit channel, `qu.C` is the channel type)
|
||||
- `pkg/interrupt`: Signal handling with cross-platform restart support
|
||||
- `pkg/opts`: Configuration option types (binary, text, integer, float, duration, list)
|
||||
- `fonts/p9fonts`: Default Plan9-style font collection
|
||||
- `clipboard`: Platform-specific clipboard operations (X11 on Linux)
|
||||
|
||||
### Logging Convention
|
||||
|
||||
Each package has a `log.go` file that initializes package-level loggers:
|
||||
```go
|
||||
var F, E, W, I, D, T = log.GetLogPrinterSet(log.AddLoggerSubsystem(version.PathBase))
|
||||
```
|
||||
Use `D.Ln()` for debug, `I.Ln()` for info, `E.Chk(err)` for error checking, etc.
|
||||
|
||||
### Color System
|
||||
|
||||
Colors are referenced by name strings (e.g., "Primary", "DocBg", "DocText", "PanelBg"). The theme maintains light/dark variants toggled via `theme.Dark.Flip()`.
|
||||
41
go.sum
Normal file
41
go.sum
Normal file
@@ -0,0 +1,41 @@
|
||||
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d h1:ARo7NCVvN2NdhLlJE9xAbKweuI9L6UgfTbYb0YwPacY=
|
||||
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d/go.mod h1:OYVuxibdk9OSLX8vAqydtRPP87PyTFcT9uH3MlEGBQA=
|
||||
gioui.org v0.9.0 h1:4u7XZwnb5kzQW91Nz/vR0wKD6LdW9CaVF96r3rfy4kc=
|
||||
gioui.org v0.9.0/go.mod h1:CjNig0wAhLt9WZxOPAusgFD8x8IRvqt26LdDBa3Jvao=
|
||||
gioui.org/cpu v0.0.0-20210808092351-bfe733dd3334/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
|
||||
gioui.org/shader v1.0.8 h1:6ks0o/A+b0ne7RzEqRZK5f4Gboz2CfG+mVliciy6+qA=
|
||||
gioui.org/shader v1.0.8/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM=
|
||||
github.com/BurntSushi/xgb v0.0.0-20210121224620-deaf085860bc h1:7D+Bh06CRPCJO3gr2F7h1sriovOZ8BMhca2Rg85c2nk=
|
||||
github.com/BurntSushi/xgb v0.0.0-20210121224620-deaf085860bc/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
||||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-text/typesetting v0.3.1 h1:ESHfFntFnJOigjEeEiTc3OGXqggC1eSAAqHkG9ZB+yA=
|
||||
github.com/go-text/typesetting v0.3.1/go.mod h1:vIRUT25mLQaSh4C8H/lIsKppQz/Gdb8Pu/tNwpi52ts=
|
||||
github.com/go-text/typesetting-utils v0.0.0-20250618110550-c820a94c77b8 h1:4KCscI9qYWMGTuz6BpJtbUSRzcBrUSSE0ENMJbNSrFs=
|
||||
github.com/go-text/typesetting-utils v0.0.0-20250618110550-c820a94c77b8/go.mod h1:3/62I4La/HBRX9TcTpBj4eipLiwzf+vhI+7whTc9V7o=
|
||||
github.com/gookit/assert v0.1.1 h1:lh3GcawXe/p+cU7ESTZ5Ui3Sm/x8JWpIis4/1aF0mY0=
|
||||
github.com/gookit/assert v0.1.1/go.mod h1:jS5bmIVQZTIwk42uXl4lyj4iaaxx32tqH16CFj0VX2E=
|
||||
github.com/gookit/color v1.6.0 h1:JjJXBTk1ETNyqyilJhkTXJYYigHG24TM9Xa2M1xAhRA=
|
||||
github.com/gookit/color v1.6.0/go.mod h1:9ACFc7/1IpHGBW8RwuDm/0YEnhg3dwwXpoMsmtyHfjs=
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA=
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
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=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
|
||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
|
||||
golang.org/x/exp/shiny v0.0.0-20251125195548-87e1e737ad39 h1:fy+QQHOvRvUJ5ZJigptKDpFN332kInaZSFvlb0CrwGA=
|
||||
golang.org/x/exp/shiny v0.0.0-20251125195548-87e1e737ad39/go.mod h1:p7Wr/QhhC3SjhTsG6HN+87un+wDRHZIBEPvfbo51ToQ=
|
||||
golang.org/x/image v0.33.0 h1:LXRZRnv1+zGd5XBUVRFmYEphyyKJjQjCRiOuAP3sZfQ=
|
||||
golang.org/x/image v0.33.0/go.mod h1:DD3OsTYT9chzuzTQt+zMcOlBHgfoKQb1gry8p76Y1sc=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||
62
pkg/apputil/apputil.go
Normal file
62
pkg/apputil/apputil.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package apputil
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// EnsureDir checks a file could be written to a path, creates the directories as needed
|
||||
func EnsureDir(fileName string) {
|
||||
dirName := filepath.Dir(fileName)
|
||||
if _, serr := os.Stat(dirName); serr != nil {
|
||||
merr := os.MkdirAll(dirName, os.ModePerm)
|
||||
if merr != nil {
|
||||
panic(merr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FileExists reports whether the named file or directory exists.
|
||||
func FileExists(filePath string) bool {
|
||||
_, e := os.Stat(filePath)
|
||||
return e == nil
|
||||
}
|
||||
|
||||
// MinUint32 is a helper function to return the minimum of two uint32s. This avoids a math import and the need to cast
|
||||
// to floats.
|
||||
func MinUint32(a, b uint32) uint32 {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// PrependForWindows runs a command with a terminal
|
||||
func PrependForWindows(args []string) []string {
|
||||
if runtime.GOOS == "windows" {
|
||||
args = append(
|
||||
[]string{
|
||||
"cmd.exe",
|
||||
"/C",
|
||||
},
|
||||
args...,
|
||||
)
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
// PrependForWindowsWithStart runs a process independently
|
||||
func PrependForWindowsWithStart(args []string) []string {
|
||||
if runtime.GOOS == "windows" {
|
||||
args = append(
|
||||
[]string{
|
||||
"cmd.exe",
|
||||
"/C",
|
||||
"start",
|
||||
},
|
||||
args...,
|
||||
)
|
||||
}
|
||||
return args
|
||||
}
|
||||
2
pkg/interrupt/README.md
Normal file
2
pkg/interrupt/README.md
Normal file
@@ -0,0 +1,2 @@
|
||||
# interrupt
|
||||
Handle shutdowns cleanly easy restarts (theoretically)
|
||||
14
pkg/interrupt/cmd/ctrlctest.go
Normal file
14
pkg/interrupt/cmd/ctrlctest.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.mleku.dev/mleku/prevara/pkg/interrupt"
|
||||
)
|
||||
|
||||
func main() {
|
||||
interrupt.AddHandler(func() {
|
||||
fmt.Println("IT'S THE END OF THE WORLD!")
|
||||
})
|
||||
<-interrupt.HandlersDone
|
||||
}
|
||||
8
pkg/interrupt/log.go
Normal file
8
pkg/interrupt/log.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package interrupt
|
||||
|
||||
import (
|
||||
"git.mleku.dev/mleku/prevara/pkg/log"
|
||||
"git.mleku.dev/mleku/prevara/version"
|
||||
)
|
||||
|
||||
var F, E, W, I, D, T = log.GetLogPrinterSet(log.AddLoggerSubsystem(version.PathBase))
|
||||
152
pkg/interrupt/main.go
Normal file
152
pkg/interrupt/main.go
Normal file
@@ -0,0 +1,152 @@
|
||||
package interrupt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
uberatomic "go.uber.org/atomic"
|
||||
|
||||
"git.mleku.dev/mleku/prevara/pkg/qu"
|
||||
)
|
||||
|
||||
type HandlerWithSource struct {
|
||||
Source string
|
||||
Fn func()
|
||||
}
|
||||
|
||||
var (
|
||||
Restart bool // = true
|
||||
requested uberatomic.Bool
|
||||
// ch is used to receive SIGINT (Ctrl+C) signals.
|
||||
ch chan os.Signal
|
||||
// signals is the list of signals that cause the interrupt
|
||||
signals = []os.Signal{os.Interrupt}
|
||||
// ShutdownRequestChan is a channel that can receive shutdown requests
|
||||
ShutdownRequestChan = qu.T()
|
||||
// addHandlerChan is used to add an interrupt handler to the list of
|
||||
// handlers to be invoked on SIGINT (Ctrl+C) signals.
|
||||
addHandlerChan = make(chan HandlerWithSource)
|
||||
// HandlersDone is closed after all interrupt handlers run the first time
|
||||
// an interrupt is signaled.
|
||||
HandlersDone = make(qu.C)
|
||||
)
|
||||
|
||||
var interruptCallbacks []func()
|
||||
var interruptCallbackSources []string
|
||||
|
||||
// Listener listens for interrupt signals, registers interrupt callbacks,
|
||||
// and responds to custom shutdown signals as required
|
||||
func Listener() {
|
||||
invokeCallbacks := func() {
|
||||
D.Ln(
|
||||
"running interrupt callbacks",
|
||||
len(interruptCallbacks),
|
||||
strings.Repeat(" ", 48),
|
||||
interruptCallbackSources,
|
||||
)
|
||||
// run handlers in LIFO order.
|
||||
for i := range interruptCallbacks {
|
||||
idx := len(interruptCallbacks) - 1 - i
|
||||
D.Ln("running callback", idx, interruptCallbackSources[idx])
|
||||
interruptCallbacks[idx]()
|
||||
}
|
||||
D.Ln("interrupt handlers finished")
|
||||
HandlersDone.Q()
|
||||
if Restart {
|
||||
doRestart()
|
||||
}
|
||||
// time.Sleep(time.Second * 3)
|
||||
// os.Exit(1)
|
||||
// close(HandlersDone)
|
||||
}
|
||||
out:
|
||||
for {
|
||||
select {
|
||||
case sig := <-ch:
|
||||
// if !requested {
|
||||
// L.Printf("\r>>> received signal (%s)\n", sig)
|
||||
D.Ln("received interrupt signal", sig)
|
||||
requested.Store(true)
|
||||
invokeCallbacks()
|
||||
// pprof.Lookup("goroutine").WriteTo(os.Stderr, 2)
|
||||
// }
|
||||
break out
|
||||
case <-ShutdownRequestChan.Wait():
|
||||
// if !requested {
|
||||
W.Ln("received shutdown request - shutting down...")
|
||||
requested.Store(true)
|
||||
invokeCallbacks()
|
||||
break out
|
||||
// }
|
||||
case handler := <-addHandlerChan:
|
||||
// if !requested {
|
||||
// D.Ln("adding handler")
|
||||
interruptCallbacks = append(interruptCallbacks, handler.Fn)
|
||||
interruptCallbackSources = append(interruptCallbackSources, handler.Source)
|
||||
// }
|
||||
case <-HandlersDone.Wait():
|
||||
break out
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AddHandler adds a handler to call when a SIGINT (Ctrl+C) is received.
|
||||
func AddHandler(handler func()) {
|
||||
// Create the channel and start the main interrupt handler which invokes all other callbacks and exits if not
|
||||
// already done.
|
||||
_, loc, line, _ := runtime.Caller(1)
|
||||
msg := fmt.Sprintf("%s:%d", loc, line)
|
||||
D.Ln("handler added by:", msg)
|
||||
if ch == nil {
|
||||
ch = make(chan os.Signal)
|
||||
signal.Notify(ch, signals...)
|
||||
go Listener()
|
||||
}
|
||||
addHandlerChan <- HandlerWithSource{
|
||||
msg, handler,
|
||||
}
|
||||
}
|
||||
|
||||
// Request programmatically requests a shutdown
|
||||
func Request() {
|
||||
_, f, l, _ := runtime.Caller(1)
|
||||
D.Ln("interrupt requested", f, l, requested.Load())
|
||||
if requested.Load() {
|
||||
D.Ln("requested again")
|
||||
return
|
||||
}
|
||||
requested.Store(true)
|
||||
ShutdownRequestChan.Q()
|
||||
// qu.PrintChanState()
|
||||
var ok bool
|
||||
select {
|
||||
case _, ok = <-ShutdownRequestChan:
|
||||
default:
|
||||
}
|
||||
D.Ln("shutdownrequestchan", ok)
|
||||
if ok {
|
||||
close(ShutdownRequestChan)
|
||||
}
|
||||
}
|
||||
|
||||
// GoroutineDump returns a string with the current goroutine dump in order to show what's going on in case of timeout.
|
||||
func GoroutineDump() string {
|
||||
buf := make([]byte, 1<<18)
|
||||
n := runtime.Stack(buf, true)
|
||||
return string(buf[:n])
|
||||
}
|
||||
|
||||
// RequestRestart sets the reset flag and requests a restart
|
||||
func RequestRestart() {
|
||||
Restart = true
|
||||
D.Ln("requesting restart")
|
||||
Request()
|
||||
}
|
||||
|
||||
// Requested returns true if an interrupt has been requested
|
||||
func Requested() bool {
|
||||
return requested.Load()
|
||||
}
|
||||
8
pkg/interrupt/restart_js.go
Normal file
8
pkg/interrupt/restart_js.go
Normal file
@@ -0,0 +1,8 @@
|
||||
//go:build js || wasm
|
||||
|
||||
package interrupt
|
||||
|
||||
func doRestart() {
|
||||
// Restart is not supported in WASM/JS environment
|
||||
D.Ln("restart not supported in WASM/JS environment")
|
||||
}
|
||||
23
pkg/interrupt/restart_unix.go
Normal file
23
pkg/interrupt/restart_unix.go
Normal file
@@ -0,0 +1,23 @@
|
||||
//go:build !windows && !js && !wasm
|
||||
|
||||
package interrupt
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/kardianos/osext"
|
||||
)
|
||||
|
||||
func doRestart() {
|
||||
file, e := osext.Executable()
|
||||
if e != nil {
|
||||
E.Ln(e)
|
||||
return
|
||||
}
|
||||
D.Ln("restarting")
|
||||
e = syscall.Exec(file, os.Args, os.Environ())
|
||||
if e != nil {
|
||||
F.Ln(e)
|
||||
}
|
||||
}
|
||||
26
pkg/interrupt/restart_windows.go
Normal file
26
pkg/interrupt/restart_windows.go
Normal file
@@ -0,0 +1,26 @@
|
||||
//go:build windows
|
||||
|
||||
package interrupt
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/kardianos/osext"
|
||||
)
|
||||
|
||||
func doRestart() {
|
||||
file, e := osext.Executable()
|
||||
if e != nil {
|
||||
E.Ln(e)
|
||||
return
|
||||
}
|
||||
D.Ln("doing windows restart")
|
||||
var s []string
|
||||
s = append(s, file)
|
||||
s = append(s, os.Args[1:]...)
|
||||
cmd := exec.Command(s[0], s[1:]...)
|
||||
D.Ln("windows restart done")
|
||||
if e = cmd.Start(); E.Chk(e) {
|
||||
}
|
||||
}
|
||||
14
pkg/interrupt/sigterm.go
Normal file
14
pkg/interrupt/sigterm.go
Normal file
@@ -0,0 +1,14 @@
|
||||
//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
|
||||
// +build darwin dragonfly freebsd linux netbsd openbsd solaris
|
||||
|
||||
package interrupt
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
signals = []os.Signal{os.Interrupt, syscall.SIGTERM}
|
||||
}
|
||||
24
pkg/log/LICENSE
Normal file
24
pkg/log/LICENSE
Normal file
@@ -0,0 +1,24 @@
|
||||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
For more information, please refer to <https://unlicense.org>
|
||||
575
pkg/log/logg.go
Normal file
575
pkg/log/logg.go
Normal file
@@ -0,0 +1,575 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/gookit/color"
|
||||
uberatomic "go.uber.org/atomic"
|
||||
)
|
||||
|
||||
const (
|
||||
_Off = iota
|
||||
_Fatal
|
||||
_Error
|
||||
_Chek
|
||||
_Warn
|
||||
_Info
|
||||
_Debug
|
||||
_Trace
|
||||
)
|
||||
|
||||
type (
|
||||
// LevelPrinter defines a set of terminal printing primitives that output with
|
||||
// extra data, time, log logLevelList, and code location
|
||||
LevelPrinter struct {
|
||||
// Ln prints lists of interfaces with spaces in between
|
||||
Ln func(a ...interface{})
|
||||
// F prints like fmt.Println surrounded by log details
|
||||
F func(format string, a ...interface{})
|
||||
// S prints a spew.Sdump for an interface slice
|
||||
S func(a ...interface{})
|
||||
// C accepts a function so that the extra computation can be avoided if it is
|
||||
// not being viewed
|
||||
C func(closure func() string)
|
||||
// Chk is a shortcut for printing if there is an error, or returning true
|
||||
Chk func(e error) bool
|
||||
}
|
||||
logLevelList struct {
|
||||
Off, Fatal, Error, Check, Warn, Info, Debug, Trace int32
|
||||
}
|
||||
LevelSpec struct {
|
||||
ID int32
|
||||
Name string
|
||||
Colorizer func(format string, a ...interface{}) string
|
||||
}
|
||||
|
||||
// Entry is a log entry to be printed as json to the log file
|
||||
Entry struct {
|
||||
Time time.Time
|
||||
Level string
|
||||
Package string
|
||||
CodeLocation string
|
||||
Text string
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
logger_started = time.Now()
|
||||
App = " pod"
|
||||
AppColorizer = color.White.Sprint
|
||||
// sep is just a convenient shortcut for this very longwinded expression
|
||||
sep = string(os.PathSeparator)
|
||||
currentLevel = uberatomic.NewInt32(logLevels.Info)
|
||||
// writer can be swapped out for any io.*writer* that you want to use instead of
|
||||
// stdout.
|
||||
writer io.Writer = os.Stderr
|
||||
// allSubsystems stores all of the package subsystem names found in the current
|
||||
// application
|
||||
allSubsystems []string
|
||||
// highlighted is a text that helps visually distinguish a log entry by category
|
||||
highlighted = make(map[string]struct{})
|
||||
// logFilter specifies a set of packages that will not pr logs
|
||||
logFilter = make(map[string]struct{})
|
||||
// mutexes to prevent concurrent map accesses
|
||||
highlightMx, _logFilterMx sync.Mutex
|
||||
// logLevels is a shorthand access that minimises possible Name collisions in the
|
||||
// dot import
|
||||
logLevels = logLevelList{
|
||||
Off: _Off,
|
||||
Fatal: _Fatal,
|
||||
Error: _Error,
|
||||
Check: _Chek,
|
||||
Warn: _Warn,
|
||||
Info: _Info,
|
||||
Debug: _Debug,
|
||||
Trace: _Trace,
|
||||
}
|
||||
// LevelSpecs specifies the id, string name and color-printing function
|
||||
LevelSpecs = []LevelSpec{
|
||||
{logLevels.Off, "off ", color.Bit24(0, 0, 0, false).Sprintf},
|
||||
{logLevels.Fatal, "fatal", color.Bit24(128, 0, 0, false).Sprintf},
|
||||
{logLevels.Error, "error", color.Bit24(255, 0, 0, false).Sprintf},
|
||||
{logLevels.Check, "check", color.Bit24(255, 255, 0, false).Sprintf},
|
||||
{logLevels.Warn, "warn ", color.Bit24(0, 255, 0, false).Sprintf},
|
||||
{logLevels.Info, "info ", color.Bit24(255, 255, 0, false).Sprintf},
|
||||
{logLevels.Debug, "debug", color.Bit24(0, 128, 255, false).Sprintf},
|
||||
{logLevels.Trace, "trace", color.Bit24(128, 0, 255, false).Sprintf},
|
||||
}
|
||||
Levels = []string{
|
||||
Off,
|
||||
Fatal,
|
||||
Error,
|
||||
Check,
|
||||
Warn,
|
||||
Info,
|
||||
Debug,
|
||||
Trace,
|
||||
}
|
||||
LogChanDisabled = uberatomic.NewBool(true)
|
||||
LogChan chan Entry
|
||||
)
|
||||
|
||||
const (
|
||||
Off = "off"
|
||||
Fatal = "fatal"
|
||||
Error = "error"
|
||||
Warn = "warn"
|
||||
Info = "info"
|
||||
Check = "check"
|
||||
Debug = "debug"
|
||||
Trace = "trace"
|
||||
)
|
||||
|
||||
// AddLogChan adds a channel that log entries are sent to
|
||||
func AddLogChan() (ch chan Entry) {
|
||||
LogChanDisabled.Store(false)
|
||||
if LogChan != nil {
|
||||
panic("warning warning")
|
||||
}
|
||||
// L.Writer.Write.Store( false
|
||||
LogChan = make(chan Entry)
|
||||
return LogChan
|
||||
}
|
||||
|
||||
// GetLogPrinterSet returns a set of LevelPrinter with their subsystem preloaded
|
||||
func GetLogPrinterSet(subsystem string) (Fatal, Error, Warn, Info, Debug, Trace LevelPrinter) {
|
||||
return _getOnePrinter(_Fatal, subsystem),
|
||||
_getOnePrinter(_Error, subsystem),
|
||||
_getOnePrinter(_Warn, subsystem),
|
||||
_getOnePrinter(_Info, subsystem),
|
||||
_getOnePrinter(_Debug, subsystem),
|
||||
_getOnePrinter(_Trace, subsystem)
|
||||
}
|
||||
|
||||
func _getOnePrinter(level int32, subsystem string) LevelPrinter {
|
||||
return LevelPrinter{
|
||||
Ln: _ln(level, subsystem),
|
||||
F: _f(level, subsystem),
|
||||
S: _s(level, subsystem),
|
||||
C: _c(level, subsystem),
|
||||
Chk: _chk(level, subsystem),
|
||||
}
|
||||
}
|
||||
|
||||
// SetLogLevel sets the log level via a string, which can be truncated down to
|
||||
// one character, similar to nmcli's argument processor, as the first letter is
|
||||
// unique. This could be used with a linter to make larger command sets.
|
||||
func SetLogLevel(l string) {
|
||||
if l == "" {
|
||||
l = "info"
|
||||
}
|
||||
// fmt.Fprintln(os.Stderr, "setting log level", l)
|
||||
lvl := logLevels.Info
|
||||
for i := range LevelSpecs {
|
||||
if LevelSpecs[i].Name[:1] == l[:1] {
|
||||
lvl = LevelSpecs[i].ID
|
||||
}
|
||||
}
|
||||
currentLevel.Store(lvl)
|
||||
}
|
||||
|
||||
// SetLogWriter atomically changes the log io.Writer interface
|
||||
func SetLogWriter(wr io.Writer) {
|
||||
// w := unsafe.Pointer(writer)
|
||||
// c := unsafe.Pointer(wr)
|
||||
// atomic.SwapPointer(&w, c)
|
||||
writer = wr
|
||||
}
|
||||
|
||||
func SetLogWriteToFile(path, appName string) (e error) {
|
||||
// copy existing log file to dated log file as we will truncate it per
|
||||
// session
|
||||
path = filepath.Join(path, "log"+appName)
|
||||
if _, e = os.Stat(path); e == nil {
|
||||
var b []byte
|
||||
b, e = ioutil.ReadFile(path)
|
||||
if e == nil {
|
||||
ioutil.WriteFile(path+fmt.Sprint(time.Now().Unix()), b, 0600)
|
||||
}
|
||||
}
|
||||
var fileWriter *os.File
|
||||
if fileWriter, e = os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC,
|
||||
0600); e != nil {
|
||||
fmt.Fprintln(os.Stderr, "unable to write log to", path, "error:", e)
|
||||
return
|
||||
}
|
||||
mw := io.MultiWriter(os.Stderr, fileWriter)
|
||||
fileWriter.Write([]byte("logging to file '" + path + "'\n"))
|
||||
mw.Write([]byte("logging to file '" + path + "'\n"))
|
||||
SetLogWriter(mw)
|
||||
return
|
||||
}
|
||||
|
||||
// SortSubsystemsList sorts the list of subsystems, to keep the data read-only,
|
||||
// call this function right at the top of the main, which runs after
|
||||
// declarations and main/init. Really this is just here to alert the reader.
|
||||
func SortSubsystemsList() {
|
||||
sort.Strings(allSubsystems)
|
||||
// fmt.Fprintln(
|
||||
// os.Stderr,
|
||||
// spew.Sdump(allSubsystems),
|
||||
// spew.Sdump(highlighted),
|
||||
// spew.Sdump(logFilter),
|
||||
// )
|
||||
}
|
||||
|
||||
// AddLoggerSubsystem adds a subsystem to the list of known subsystems and returns the
|
||||
// string so it is nice and neat in the package logg.go file
|
||||
func AddLoggerSubsystem(pathBase string) (subsystem string) {
|
||||
// var split []string
|
||||
var ok bool
|
||||
var file string
|
||||
_, file, _, ok = runtime.Caller(1)
|
||||
if ok {
|
||||
r := strings.Split(file, pathBase)
|
||||
// fmt.Fprintln(os.Stderr, version.PathBase, r)
|
||||
fromRoot := filepath.Base(file)
|
||||
if len(r) > 1 {
|
||||
fromRoot = r[1]
|
||||
}
|
||||
split := strings.Split(fromRoot, "/")
|
||||
// fmt.Fprintln(os.Stderr, version.PathBase, "file", file, r, fromRoot, split)
|
||||
subsystem = strings.Join(split[:len(split)-1], "/")
|
||||
// fmt.Fprintln(os.Stderr, "adding subsystem", subsystem)
|
||||
allSubsystems = append(allSubsystems, subsystem)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// StoreHighlightedSubsystems sets the list of subsystems to highlight
|
||||
func StoreHighlightedSubsystems(highlights []string) (found bool) {
|
||||
highlightMx.Lock()
|
||||
highlighted = make(map[string]struct{}, len(highlights))
|
||||
for i := range highlights {
|
||||
highlighted[highlights[i]] = struct{}{}
|
||||
}
|
||||
highlightMx.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// LoadHighlightedSubsystems returns a copy of the map of highlighted subsystems
|
||||
func LoadHighlightedSubsystems() (o []string) {
|
||||
highlightMx.Lock()
|
||||
o = make([]string, len(logFilter))
|
||||
var counter int
|
||||
for i := range logFilter {
|
||||
o[counter] = i
|
||||
counter++
|
||||
}
|
||||
highlightMx.Unlock()
|
||||
sort.Strings(o)
|
||||
return
|
||||
}
|
||||
|
||||
// StoreSubsystemFilter sets the list of subsystems to filter
|
||||
func StoreSubsystemFilter(filter []string) {
|
||||
_logFilterMx.Lock()
|
||||
logFilter = make(map[string]struct{}, len(filter))
|
||||
for i := range filter {
|
||||
logFilter[filter[i]] = struct{}{}
|
||||
}
|
||||
_logFilterMx.Unlock()
|
||||
}
|
||||
|
||||
// LoadSubsystemFilter returns a copy of the map of filtered subsystems
|
||||
func LoadSubsystemFilter() (o []string) {
|
||||
_logFilterMx.Lock()
|
||||
o = make([]string, len(logFilter))
|
||||
var counter int
|
||||
for i := range logFilter {
|
||||
o[counter] = i
|
||||
counter++
|
||||
}
|
||||
_logFilterMx.Unlock()
|
||||
sort.Strings(o)
|
||||
return
|
||||
}
|
||||
|
||||
// _isHighlighted returns true if the subsystem is in the list to have attention
|
||||
// getters added to them
|
||||
func _isHighlighted(subsystem string) (found bool) {
|
||||
highlightMx.Lock()
|
||||
_, found = highlighted[subsystem]
|
||||
highlightMx.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// AddHighlightedSubsystem adds a new subsystem Name to the highlighted list
|
||||
func AddHighlightedSubsystem(hl string) struct{} {
|
||||
highlightMx.Lock()
|
||||
highlighted[hl] = struct{}{}
|
||||
highlightMx.Unlock()
|
||||
return struct{}{}
|
||||
}
|
||||
|
||||
// _isSubsystemFiltered returns true if the subsystem should not pr logs
|
||||
func _isSubsystemFiltered(subsystem string) (found bool) {
|
||||
_logFilterMx.Lock()
|
||||
_, found = logFilter[subsystem]
|
||||
_logFilterMx.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// AddFilteredSubsystem adds a new subsystem Name to the highlighted list
|
||||
func AddFilteredSubsystem(hl string) struct{} {
|
||||
_logFilterMx.Lock()
|
||||
logFilter[hl] = struct{}{}
|
||||
_logFilterMx.Unlock()
|
||||
return struct{}{}
|
||||
}
|
||||
|
||||
func getTimeText(level int32) string {
|
||||
// since := time.Now().Sub(logger_started).Round(time.Millisecond).String()
|
||||
// diff := 12 - len(since)
|
||||
// if diff > 0 {
|
||||
// since = strings.Repeat(" ", diff) + since + " "
|
||||
// }
|
||||
return color.Bit24(99, 99, 99, false).Sprint(time.Now().
|
||||
Format(time.StampMilli))
|
||||
}
|
||||
|
||||
func _ln(level int32, subsystem string) func(a ...interface{}) {
|
||||
return func(a ...interface{}) {
|
||||
if level <= currentLevel.Load() && !_isSubsystemFiltered(subsystem) {
|
||||
printer := fmt.Sprintf
|
||||
if _isHighlighted(subsystem) {
|
||||
printer = color.Bold.Sprintf
|
||||
}
|
||||
fmt.Fprintf(
|
||||
writer,
|
||||
printer(
|
||||
"%-58v%s%s%-6v %s\n",
|
||||
getLoc(2, level, subsystem),
|
||||
getTimeText(level),
|
||||
color.Bit24(20, 20, 20, true).
|
||||
Sprint(AppColorizer(" "+App)),
|
||||
LevelSpecs[level].Colorizer(
|
||||
color.Bit24(20, 20, 20, true).
|
||||
Sprint(" "+LevelSpecs[level].Name+" "),
|
||||
),
|
||||
AppColorizer(joinStrings(" ", a...)),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func _f(level int32, subsystem string) func(format string, a ...interface{}) {
|
||||
return func(format string, a ...interface{}) {
|
||||
if level <= currentLevel.Load() && !_isSubsystemFiltered(subsystem) {
|
||||
printer := fmt.Sprintf
|
||||
if _isHighlighted(subsystem) {
|
||||
printer = color.Bold.Sprintf
|
||||
}
|
||||
fmt.Fprintf(
|
||||
writer,
|
||||
printer(
|
||||
"%-58v%s%s%-6v %s\n",
|
||||
getLoc(2, level, subsystem),
|
||||
getTimeText(level),
|
||||
color.Bit24(20, 20, 20, true).
|
||||
Sprint(AppColorizer(" "+App)),
|
||||
LevelSpecs[level].Colorizer(
|
||||
color.Bit24(20, 20, 20, true).
|
||||
Sprint(" "+LevelSpecs[level].Name+" "),
|
||||
),
|
||||
AppColorizer(fmt.Sprintf(format, a...)),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func _s(level int32, subsystem string) func(a ...interface{}) {
|
||||
return func(a ...interface{}) {
|
||||
if level <= currentLevel.Load() && !_isSubsystemFiltered(subsystem) {
|
||||
printer := fmt.Sprintf
|
||||
if _isHighlighted(subsystem) {
|
||||
printer = color.Bold.Sprintf
|
||||
}
|
||||
fmt.Fprintf(
|
||||
writer,
|
||||
printer(
|
||||
"%-58v%s%s%s%s%s\n",
|
||||
getLoc(2, level, subsystem),
|
||||
getTimeText(level),
|
||||
color.Bit24(20, 20, 20, true).
|
||||
Sprint(AppColorizer(" "+App)),
|
||||
LevelSpecs[level].Colorizer(
|
||||
color.Bit24(20, 20, 20, true).
|
||||
Sprint(" "+LevelSpecs[level].Name+" "),
|
||||
),
|
||||
AppColorizer(
|
||||
" spew:",
|
||||
),
|
||||
fmt.Sprint(
|
||||
color.Bit24(20, 20, 20, true).Sprint("\n\n"+spew.Sdump(a)),
|
||||
"\n",
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func _c(level int32, subsystem string) func(closure func() string) {
|
||||
return func(closure func() string) {
|
||||
if level <= currentLevel.Load() && !_isSubsystemFiltered(subsystem) {
|
||||
printer := fmt.Sprintf
|
||||
if _isHighlighted(subsystem) {
|
||||
printer = color.Bold.Sprintf
|
||||
}
|
||||
fmt.Fprintf(
|
||||
writer,
|
||||
printer(
|
||||
"%-58v%s%s%-6v %s\n",
|
||||
getLoc(2, level, subsystem),
|
||||
getTimeText(level),
|
||||
color.Bit24(20, 20, 20, true).
|
||||
Sprint(AppColorizer(" "+App)),
|
||||
LevelSpecs[level].Colorizer(
|
||||
color.Bit24(20, 20, 20, true).
|
||||
Sprint(" "+LevelSpecs[level].Name+" "),
|
||||
),
|
||||
AppColorizer(closure()),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func _chk(level int32, subsystem string) func(e error) bool {
|
||||
return func(e error) bool {
|
||||
if level <= currentLevel.Load() && !_isSubsystemFiltered(subsystem) {
|
||||
if e != nil {
|
||||
printer := fmt.Sprintf
|
||||
if _isHighlighted(subsystem) {
|
||||
printer = color.Bold.Sprintf
|
||||
}
|
||||
fmt.Fprintf(
|
||||
writer,
|
||||
printer(
|
||||
"%-58v%s%s%-6v %s\n",
|
||||
getLoc(2, level, subsystem),
|
||||
getTimeText(level),
|
||||
color.Bit24(20, 20, 20, true).
|
||||
Sprint(AppColorizer(" "+App)),
|
||||
LevelSpecs[level].Colorizer(
|
||||
color.Bit24(20, 20, 20, true).
|
||||
Sprint(" "+LevelSpecs[level].Name+" "),
|
||||
),
|
||||
LevelSpecs[level].Colorizer(joinStrings(" ", e.Error())),
|
||||
),
|
||||
)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// joinStrings constructs a string from an slice of interface same as Println but
|
||||
// without the terminal newline
|
||||
func joinStrings(sep string, a ...interface{}) (o string) {
|
||||
for i := range a {
|
||||
o += fmt.Sprint(a[i])
|
||||
if i < len(a)-1 {
|
||||
o += sep
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// getLoc calls runtime.Caller and formats as expected by source code editors
|
||||
// for terminal hyperlinks
|
||||
//
|
||||
// Regular expressions and the substitution texts to make these clickable in
|
||||
// Tilix and other RE hyperlink configurable terminal emulators:
|
||||
//
|
||||
// This matches the shortened paths generated in this command and printed at
|
||||
// the very beginning of the line as this logger prints:
|
||||
//
|
||||
// ^((([\/a-zA-Z@0-9-_.]+/)+([a-zA-Z@0-9-_.]+)):([0-9]+))
|
||||
//
|
||||
// goland --line $5 $GOPATH/src/github.com/p9c/matrjoska/$2
|
||||
//
|
||||
// I have used a shell variable there but tilix doesn't expand them,
|
||||
// so put your GOPATH in manually, and obviously change the repo subpath.
|
||||
//
|
||||
// Change the path to use with another repository's logging output (
|
||||
// someone with more time on their hands could probably come up with
|
||||
// something, but frankly the custom links feature of Tilix has the absolute
|
||||
// worst UX I have encountered since the 90s...
|
||||
// Maybe in the future this library will be expanded with a tool that more
|
||||
// intelligently sets the path, ie from CWD or other cleverness.
|
||||
//
|
||||
// This matches full paths anywhere on the commandline delimited by spaces:
|
||||
//
|
||||
// ([/](([\/a-zA-Z@0-9-_.]+/)+([a-zA-Z@0-9-_.]+)):([0-9]+))
|
||||
//
|
||||
// goland --line $5 /$2
|
||||
//
|
||||
// Adapt the invocation to open your preferred editor if it has the capability,
|
||||
// the above is for Jetbrains Goland
|
||||
func getLoc(skip int, level int32, subsystem string) (output string) {
|
||||
_, file, line, _ := runtime.Caller(skip)
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Fprintln(os.Stderr, "getloc panic on subsystem", subsystem, file)
|
||||
}
|
||||
}()
|
||||
split := strings.Split(file, subsystem)
|
||||
if len(split) < 2 {
|
||||
output = fmt.Sprint(
|
||||
color.White.Sprint(subsystem),
|
||||
color.Gray.Sprint(
|
||||
file, ":", line,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
output = fmt.Sprint(
|
||||
color.White.Sprint(subsystem),
|
||||
color.Gray.Sprint(
|
||||
split[1], ":", line,
|
||||
),
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DirectionString is a helper function that returns a string that represents the direction of a connection (inbound or outbound).
|
||||
func DirectionString(inbound bool) string {
|
||||
if inbound {
|
||||
return "inbound"
|
||||
}
|
||||
return "outbound"
|
||||
}
|
||||
|
||||
func PickNoun(n int, singular, plural string) string {
|
||||
if n == 1 {
|
||||
return singular
|
||||
}
|
||||
return plural
|
||||
}
|
||||
|
||||
func FileExists(filePath string) bool {
|
||||
_, e := os.Stat(filePath)
|
||||
return e == nil
|
||||
}
|
||||
|
||||
func Caller(comment string, skip int) string {
|
||||
_, file, line, _ := runtime.Caller(skip + 1)
|
||||
o := fmt.Sprintf("%s: %s:%d", comment, file, line)
|
||||
// L.Debug(o)
|
||||
return o
|
||||
}
|
||||
23
pkg/log/readme.md
Normal file
23
pkg/log/readme.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# logg
|
||||
|
||||
This is a very simple, but practical library for
|
||||
logging in applications. Currenty in process of
|
||||
superseding the logi library, once the pipe logger
|
||||
is converted, as it is integrated into the block
|
||||
sync progress logger.
|
||||
|
||||
To use it, create a file in a package you want
|
||||
to add the logger to with the name `log.go`, and then:
|
||||
|
||||
```bash
|
||||
go run ./pkg/logg/deploy/.
|
||||
```
|
||||
|
||||
from the root of the github.com/p9c/p9 repository
|
||||
and it replicates its template with the altered
|
||||
package name to match the folder name - so you need
|
||||
to adhere to the rule that package names and the folder
|
||||
name are the same.
|
||||
|
||||
The library includes functions to toggle the filtering,
|
||||
highlight and filtering sets while it is running.
|
||||
24
pkg/opts/LICENSE
Normal file
24
pkg/opts/LICENSE
Normal file
@@ -0,0 +1,24 @@
|
||||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
For more information, please refer to <https://unlicense.org>
|
||||
7
pkg/opts/README.md
Normal file
7
pkg/opts/README.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# opts
|
||||
Types for use with a generator for easy to use CLI and environment variables
|
||||
reading and concurrent safe configuration system for applications.
|
||||
|
||||
With the generator and these types you can maintain *one* specification for
|
||||
your application's configuration system and update with one click or go
|
||||
generate command.
|
||||
154
pkg/opts/binary/binary.go
Normal file
154
pkg/opts/binary/binary.go
Normal file
@@ -0,0 +1,154 @@
|
||||
package binary
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
uberatomic "go.uber.org/atomic"
|
||||
|
||||
"git.mleku.dev/mleku/prevara/pkg/opts/meta"
|
||||
"git.mleku.dev/mleku/prevara/pkg/opts/opt"
|
||||
)
|
||||
|
||||
// Opt stores an boolean configuration value
|
||||
type Opt struct {
|
||||
meta.Data
|
||||
hook []Hook
|
||||
value *uberatomic.Bool
|
||||
Def bool
|
||||
}
|
||||
|
||||
type Hook func(b bool) error
|
||||
|
||||
// New creates a new Opt with default values set
|
||||
func New(m meta.Data, def bool, hook ...Hook) *Opt {
|
||||
return &Opt{value: uberatomic.NewBool(def), Data: m, Def: def, hook: hook}
|
||||
}
|
||||
|
||||
// SetName sets the name for the generator
|
||||
func (x *Opt) SetName(name string) {
|
||||
x.Data.Option = strings.ToLower(name)
|
||||
x.Data.Name = name
|
||||
}
|
||||
|
||||
// Type returns the receiver wrapped in an interface for identifying its type
|
||||
func (x *Opt) Type() interface{} {
|
||||
return x
|
||||
}
|
||||
|
||||
// GetMetadata returns the metadata of the opt type
|
||||
func (x *Opt) GetMetadata() *meta.Data {
|
||||
return &x.Data
|
||||
}
|
||||
|
||||
// ReadInput sets the value from a string.
|
||||
// The value can be right up against the keyword or separated by a '='.
|
||||
func (x *Opt) ReadInput(input string) (o opt.Option, e error) {
|
||||
// if the input is empty, the user intends the opposite of the default
|
||||
if input == "" {
|
||||
x.value.Store(!x.Def)
|
||||
return
|
||||
}
|
||||
if strings.HasPrefix(input, "=") {
|
||||
// the following removes leading and trailing characters
|
||||
input = strings.Join(strings.Split(input, "=")[1:], "=")
|
||||
}
|
||||
input = strings.ToLower(input)
|
||||
switch input {
|
||||
case "t", "true", "+":
|
||||
e = x.Set(true)
|
||||
case "f", "false", "-":
|
||||
e = x.Set(false)
|
||||
default:
|
||||
e = fmt.Errorf("input on opt %s: '%s' is not valid for a boolean flag", x.Name(), input)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// LoadInput sets the value from a string (this is the same as the above but differs for Strings)
|
||||
func (x *Opt) LoadInput(input string) (o opt.Option, e error) {
|
||||
return x.ReadInput(input)
|
||||
}
|
||||
|
||||
// Name returns the name of the opt
|
||||
func (x *Opt) Name() string {
|
||||
return x.Data.Option
|
||||
}
|
||||
|
||||
// AddHooks appends callback hooks to be run when the value is changed
|
||||
func (x *Opt) AddHooks(hook ...Hook) {
|
||||
x.hook = append(x.hook, hook...)
|
||||
}
|
||||
|
||||
// SetHooks sets a new slice of hooks
|
||||
func (x *Opt) SetHooks(hook ...Hook) {
|
||||
x.hook = hook
|
||||
}
|
||||
|
||||
// True returns whether the value is set to true (it returns the value)
|
||||
func (x *Opt) True() bool {
|
||||
return x.value.Load()
|
||||
}
|
||||
|
||||
// False returns whether the value is false (it returns the inverse of the value)
|
||||
func (x *Opt) False() bool {
|
||||
return !x.value.Load()
|
||||
}
|
||||
|
||||
// Flip changes the value to its opposite
|
||||
func (x *Opt) Flip() {
|
||||
I.Ln("flipping", x.Name(), "to", !x.value.Load())
|
||||
x.value.Toggle()
|
||||
}
|
||||
|
||||
func (x *Opt) runHooks(b bool) (e error) {
|
||||
for i := range x.hook {
|
||||
if e = x.hook[i](b); E.Chk(e) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Set changes the value currently stored
|
||||
func (x *Opt) Set(b bool) (e error) {
|
||||
if e = x.runHooks(b); E.Chk(e) {
|
||||
I.Ln("setting", x.Name(), "to", b)
|
||||
x.value.Store(b)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// String returns a string form of the value
|
||||
func (x *Opt) String() string {
|
||||
return fmt.Sprint(x.Data.Option, ": ", x.True())
|
||||
}
|
||||
|
||||
// T sets the value to true
|
||||
func (x *Opt) T() *Opt {
|
||||
x.value.Store(true)
|
||||
return x
|
||||
}
|
||||
|
||||
// F sets the value to false
|
||||
func (x *Opt) F() *Opt {
|
||||
x.value.Store(false)
|
||||
return x
|
||||
}
|
||||
|
||||
// MarshalJSON returns the json representation of a Opt
|
||||
func (x *Opt) MarshalJSON() (b []byte, e error) {
|
||||
v := x.value.Load()
|
||||
return json.Marshal(&v)
|
||||
}
|
||||
|
||||
// UnmarshalJSON decodes a JSON representation of a Opt
|
||||
func (x *Opt) UnmarshalJSON(data []byte) (e error) {
|
||||
v := x.value.Load()
|
||||
if e = json.Unmarshal(data, &v); E.Chk(e) {
|
||||
return
|
||||
}
|
||||
e = x.Set(v)
|
||||
return
|
||||
}
|
||||
44
pkg/opts/binary/log.go
Normal file
44
pkg/opts/binary/log.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package binary
|
||||
|
||||
import (
|
||||
"git.mleku.dev/mleku/prevara/pkg/log"
|
||||
|
||||
"git.mleku.dev/mleku/prevara/version"
|
||||
)
|
||||
|
||||
var subsystem = log.AddLoggerSubsystem(version.PathBase)
|
||||
var F, E, W, I, D, T log.LevelPrinter = log.GetLogPrinterSet(subsystem)
|
||||
|
||||
func init() {
|
||||
// to filter out this package, uncomment the following
|
||||
// var _ = logg.AddFilteredSubsystem(subsystem)
|
||||
|
||||
// to highlight this package, uncomment the following
|
||||
// var _ = logg.AddHighlightedSubsystem(subsystem)
|
||||
|
||||
// these are here to test whether they are working
|
||||
// F.Ln("F.Ln")
|
||||
// E.Ln("E.Ln")
|
||||
// W.Ln("W.Ln")
|
||||
// I.Ln("I.Ln")
|
||||
// D.Ln("D.Ln")
|
||||
// F.Ln("T.Ln")
|
||||
// F.F("%s", "F.F")
|
||||
// E.F("%s", "E.F")
|
||||
// W.F("%s", "W.F")
|
||||
// I.F("%s", "I.F")
|
||||
// D.F("%s", "D.F")
|
||||
// T.F("%s", "T.F")
|
||||
// F.C(func() string { return "F.C" })
|
||||
// E.C(func() string { return "E.C" })
|
||||
// W.C(func() string { return "W.C" })
|
||||
// I.C(func() string { return "I.C" })
|
||||
// D.C(func() string { return "D.C" })
|
||||
// T.C(func() string { return "T.C" })
|
||||
// F.C(func() string { return "F.C" })
|
||||
// E.Chk(errors.New("E.Chk"))
|
||||
// W.Chk(errors.New("W.Chk"))
|
||||
// I.Chk(errors.New("I.Chk"))
|
||||
// D.Chk(errors.New("D.Chk"))
|
||||
// T.Chk(errors.New("T.Chk"))
|
||||
}
|
||||
106
pkg/opts/cmds/commands.go
Normal file
106
pkg/opts/cmds/commands.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package cmds
|
||||
|
||||
// Commands are a slice of Command entries
|
||||
type Commands []Command
|
||||
|
||||
// Command is a specification for a command and can include any number of subcommands
|
||||
type Command struct {
|
||||
Name string
|
||||
Title string
|
||||
Description string
|
||||
Entrypoint func(c interface{}) error
|
||||
Commands Commands
|
||||
Colorizer func(a ...interface{}) string
|
||||
AppText string
|
||||
Parent *Command
|
||||
}
|
||||
|
||||
func (c Commands) PopulateParents(parent *Command) {
|
||||
if parent != nil {
|
||||
T.Ln("backlinking children of", parent.Name)
|
||||
}
|
||||
for i := range c {
|
||||
c[i].Parent = parent
|
||||
c[i].Commands.PopulateParents(&c[i])
|
||||
}
|
||||
}
|
||||
|
||||
// GetAllCommands returns all of the available command names
|
||||
func (c Commands) GetAllCommands() (o []string) {
|
||||
c.ForEach(func(cm Command) bool {
|
||||
o = append(o, cm.Name)
|
||||
o = append(o, cm.Commands.GetAllCommands()...)
|
||||
return true
|
||||
}, 0, 0,
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
var tabs = "\t\t\t\t\t"
|
||||
|
||||
// Find the Command you are looking for. Note that the namespace is assumed to be flat, no duplicated names on different
|
||||
// levels, as it returns on the first one it finds, which goes depth-first recursive
|
||||
func (c Commands) Find(
|
||||
name string, hereDepth, hereDist int, skipFirst bool,
|
||||
) (found bool, depth, dist int, cm *Command, e error) {
|
||||
if c == nil {
|
||||
dist = hereDist
|
||||
depth = hereDepth
|
||||
return
|
||||
}
|
||||
if hereDist == 0 {
|
||||
D.Ln("searching for command:", name)
|
||||
}
|
||||
depth = hereDepth + 1
|
||||
T.Ln(tabs[:depth]+"->", depth)
|
||||
dist = hereDist
|
||||
for i := range c {
|
||||
T.Ln(tabs[:depth]+"walking", c[i].Name, depth, dist)
|
||||
dist++
|
||||
if c[i].Name == name {
|
||||
if skipFirst {
|
||||
continue
|
||||
}
|
||||
dist--
|
||||
T.Ln(tabs[:depth]+"found", name, "at depth", depth, "distance", dist)
|
||||
found = true
|
||||
cm = &c[i]
|
||||
e = nil
|
||||
return
|
||||
}
|
||||
if found, depth, dist, cm, e = c[i].Commands.Find(name, depth, dist, false); E.Chk(e) {
|
||||
T.Ln(tabs[:depth]+"error", c[i].Name)
|
||||
return
|
||||
}
|
||||
if found {
|
||||
return
|
||||
}
|
||||
}
|
||||
T.Ln(tabs[:hereDepth]+"<-", hereDepth)
|
||||
if hereDepth == 0 {
|
||||
D.Ln("search text", name, "not found")
|
||||
}
|
||||
depth--
|
||||
return
|
||||
}
|
||||
|
||||
func (c Commands) ForEach(fn func(Command) bool, hereDepth, hereDist int) (ret bool, depth, dist int, e error) {
|
||||
if c == nil {
|
||||
dist = hereDist
|
||||
depth = hereDepth
|
||||
return
|
||||
}
|
||||
depth = hereDepth + 1
|
||||
T.Ln(tabs[:depth]+"->", depth)
|
||||
dist = hereDist
|
||||
for i := range c {
|
||||
T.Ln(tabs[:depth]+"walking", c[i].Name, depth, dist)
|
||||
if !fn(c[i]) {
|
||||
// if the closure returns false break out of the loop
|
||||
return
|
||||
}
|
||||
}
|
||||
T.Ln(tabs[:hereDepth]+"<-", hereDepth)
|
||||
depth--
|
||||
return
|
||||
}
|
||||
60
pkg/opts/cmds/commands_test.go
Normal file
60
pkg/opts/cmds/commands_test.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package cmds
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCommands_GetAllCommands(t *testing.T) {
|
||||
cm := GetCommands()
|
||||
I.S(cm.GetAllCommands())
|
||||
}
|
||||
|
||||
// GetCommands returns available subcommands in Parallelcoin Pod
|
||||
func GetCommands() (c Commands) {
|
||||
c = Commands{
|
||||
{Name: "gui", Title: "ParallelCoin GUI Wallet/Miner/Explorer",
|
||||
Entrypoint: func(c interface{}) error { return nil },
|
||||
},
|
||||
{Name: "version", Title: "print version and exit",
|
||||
Entrypoint: func(c interface{}) error { return nil },
|
||||
},
|
||||
{Name: "ctl", Title: "command line wallet and chain RPC client",
|
||||
Entrypoint: func(c interface{}) error { return nil },
|
||||
},
|
||||
{Name: "node", Title: "ParallelCoin blockchain node",
|
||||
Entrypoint: func(c interface{}) error { return nil },
|
||||
Commands: []Command{
|
||||
{Name: "dropaddrindex", Title: "drop the address database index",
|
||||
Entrypoint: func(c interface{}) error { return nil },
|
||||
},
|
||||
{Name: "droptxindex", Title: "drop the transaction database index",
|
||||
Entrypoint: func(c interface{}) error { return nil },
|
||||
},
|
||||
{Name: "dropcfindex", Title: "drop the cfilter database index",
|
||||
Entrypoint: func(c interface{}) error { return nil },
|
||||
},
|
||||
{Name: "dropindexes", Title: "drop all of the indexes",
|
||||
Entrypoint: func(c interface{}) error { return nil },
|
||||
},
|
||||
{Name: "resetchain", Title: "deletes the current blockchain cache to force redownload",
|
||||
Entrypoint: func(c interface{}) error { return nil },
|
||||
},
|
||||
},
|
||||
},
|
||||
{Name: "wallet", Title: "run the wallet server (requires a chain node to function)",
|
||||
Entrypoint: func(c interface{}) error { return nil },
|
||||
Commands: []Command{
|
||||
{Name: "drophistory", Title: "reset the wallet transaction history",
|
||||
Entrypoint: func(c interface{}) error { return nil },
|
||||
},
|
||||
},
|
||||
},
|
||||
{Name: "kopach", Title: "standalone multicast miner for easy mining farm deployment",
|
||||
Entrypoint: func(c interface{}) error { return nil },
|
||||
},
|
||||
{Name: "worker", Title: "single thread worker process, normally started by kopach",
|
||||
Entrypoint: func(c interface{}) error { return nil },
|
||||
},
|
||||
}
|
||||
return
|
||||
}
|
||||
44
pkg/opts/cmds/log.go
Normal file
44
pkg/opts/cmds/log.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package cmds
|
||||
|
||||
import (
|
||||
"git.mleku.dev/mleku/prevara/pkg/log"
|
||||
|
||||
"git.mleku.dev/mleku/prevara/version"
|
||||
)
|
||||
|
||||
var subsystem = log.AddLoggerSubsystem(version.PathBase)
|
||||
var F, E, W, I, D, T log.LevelPrinter = log.GetLogPrinterSet(subsystem)
|
||||
|
||||
func init() {
|
||||
// to filter out this package, uncomment the following
|
||||
// var _ = logg.AddFilteredSubsystem(subsystem)
|
||||
|
||||
// to highlight this package, uncomment the following
|
||||
// var _ = logg.AddHighlightedSubsystem(subsystem)
|
||||
|
||||
// these are here to test whether they are working
|
||||
// F.Ln("F.Ln")
|
||||
// E.Ln("E.Ln")
|
||||
// W.Ln("W.Ln")
|
||||
// I.Ln("I.Ln")
|
||||
// D.Ln("D.Ln")
|
||||
// F.Ln("T.Ln")
|
||||
// F.F("%s", "F.F")
|
||||
// E.F("%s", "E.F")
|
||||
// W.F("%s", "W.F")
|
||||
// I.F("%s", "I.F")
|
||||
// D.F("%s", "D.F")
|
||||
// T.F("%s", "T.F")
|
||||
// F.C(func() string { return "F.C" })
|
||||
// E.C(func() string { return "E.C" })
|
||||
// W.C(func() string { return "W.C" })
|
||||
// I.C(func() string { return "I.C" })
|
||||
// D.C(func() string { return "D.C" })
|
||||
// T.C(func() string { return "T.C" })
|
||||
// F.C(func() string { return "F.C" })
|
||||
// E.Chk(errors.New("E.Chk"))
|
||||
// W.Chk(errors.New("W.Chk"))
|
||||
// I.Chk(errors.New("I.Chk"))
|
||||
// D.Chk(errors.New("D.Chk"))
|
||||
// T.Chk(errors.New("T.Chk"))
|
||||
}
|
||||
136
pkg/opts/duration/duration.go
Normal file
136
pkg/opts/duration/duration.go
Normal file
@@ -0,0 +1,136 @@
|
||||
package duration
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
uberatomic "go.uber.org/atomic"
|
||||
|
||||
"git.mleku.dev/mleku/prevara/pkg/opts/meta"
|
||||
"git.mleku.dev/mleku/prevara/pkg/opts/opt"
|
||||
"git.mleku.dev/mleku/prevara/pkg/opts/sanitizers"
|
||||
)
|
||||
|
||||
// Opt stores an time.Duration configuration value
|
||||
type Opt struct {
|
||||
meta.Data
|
||||
hook []Hook
|
||||
clamp func(input time.Duration) (result time.Duration)
|
||||
Min, Max time.Duration
|
||||
Value *uberatomic.Duration
|
||||
Def time.Duration
|
||||
}
|
||||
|
||||
type Hook func(d time.Duration) error
|
||||
|
||||
// New creates a new Opt with a given default value set
|
||||
func New(m meta.Data, def time.Duration, min, max time.Duration, hook ...Hook) *Opt {
|
||||
return &Opt{
|
||||
Value: uberatomic.NewDuration(def),
|
||||
Data: m,
|
||||
Def: def,
|
||||
Min: min,
|
||||
Max: max,
|
||||
hook: hook,
|
||||
clamp: sanitizers.ClampDuration(min, max),
|
||||
}
|
||||
}
|
||||
|
||||
// SetName sets the name for the generator
|
||||
func (x *Opt) SetName(name string) {
|
||||
x.Data.Option = strings.ToLower(name)
|
||||
x.Data.Name = name
|
||||
}
|
||||
|
||||
// Type returns the receiver wrapped in an interface for identifying its type
|
||||
func (x *Opt) Type() interface{} {
|
||||
return x
|
||||
}
|
||||
|
||||
// GetMetadata returns the metadata of the opt type
|
||||
func (x *Opt) GetMetadata() *meta.Data {
|
||||
return &x.Data
|
||||
}
|
||||
|
||||
// ReadInput sets the value from a string
|
||||
func (x *Opt) ReadInput(input string) (o opt.Option, e error) {
|
||||
if input == "" {
|
||||
e = fmt.Errorf("duration opt %s %v may not be empty", x.Name(), x.Data.Aliases)
|
||||
return
|
||||
}
|
||||
if strings.HasPrefix(input, "=") {
|
||||
// the following removes leading and trailing '='
|
||||
input = strings.Join(strings.Split(input, "=")[1:], "=")
|
||||
}
|
||||
var v time.Duration
|
||||
if v, e = time.ParseDuration(input); E.Chk(e) {
|
||||
return
|
||||
}
|
||||
if e = x.Set(v); E.Chk(e) {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// LoadInput sets the value from a string (this is the same as the above but differs for Strings)
|
||||
func (x *Opt) LoadInput(input string) (o opt.Option, e error) {
|
||||
return x.ReadInput(input)
|
||||
}
|
||||
|
||||
// Name returns the name of the opt
|
||||
func (x *Opt) Name() string {
|
||||
return x.Data.Option
|
||||
}
|
||||
|
||||
// AddHooks appends callback hooks to be run when the value is changed
|
||||
func (x *Opt) AddHooks(hook ...Hook) {
|
||||
x.hook = append(x.hook, hook...)
|
||||
}
|
||||
|
||||
// SetHooks sets a new slice of hooks
|
||||
func (x *Opt) SetHooks(hook ...Hook) {
|
||||
x.hook = hook
|
||||
}
|
||||
|
||||
// V returns the value stored
|
||||
func (x *Opt) V() time.Duration {
|
||||
return x.Value.Load()
|
||||
}
|
||||
|
||||
func (x *Opt) runHooks(d time.Duration) (e error) {
|
||||
for i := range x.hook {
|
||||
if e = x.hook[i](d); E.Chk(e) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Set the value stored
|
||||
func (x *Opt) Set(d time.Duration) (e error) {
|
||||
d = x.clamp(d)
|
||||
if e = x.runHooks(d); !E.Chk(e) {
|
||||
x.Value.Store(d)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// String returns a string representation of the value
|
||||
func (x *Opt) String() string {
|
||||
return fmt.Sprintf("%s: %v", x.Data.Option, x.V())
|
||||
}
|
||||
|
||||
// MarshalJSON returns the json representation
|
||||
func (x *Opt) MarshalJSON() (b []byte, e error) {
|
||||
v := x.Value.Load()
|
||||
return json.Marshal(&v)
|
||||
}
|
||||
|
||||
// UnmarshalJSON decodes a JSON representation
|
||||
func (x *Opt) UnmarshalJSON(data []byte) (e error) {
|
||||
v := x.Value.Load()
|
||||
e = json.Unmarshal(data, &v)
|
||||
e = x.Set(v)
|
||||
return
|
||||
}
|
||||
44
pkg/opts/duration/log.go
Normal file
44
pkg/opts/duration/log.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package duration
|
||||
|
||||
import (
|
||||
"git.mleku.dev/mleku/prevara/pkg/log"
|
||||
|
||||
"git.mleku.dev/mleku/prevara/version"
|
||||
)
|
||||
|
||||
var subsystem = log.AddLoggerSubsystem(version.PathBase)
|
||||
var F, E, W, I, D, T log.LevelPrinter = log.GetLogPrinterSet(subsystem)
|
||||
|
||||
func init() {
|
||||
// to filter out this package, uncomment the following
|
||||
// var _ = logg.AddFilteredSubsystem(subsystem)
|
||||
|
||||
// to highlight this package, uncomment the following
|
||||
// var _ = logg.AddHighlightedSubsystem(subsystem)
|
||||
|
||||
// these are here to test whether they are working
|
||||
// F.Ln("F.Ln")
|
||||
// E.Ln("E.Ln")
|
||||
// W.Ln("W.Ln")
|
||||
// I.Ln("I.Ln")
|
||||
// D.Ln("D.Ln")
|
||||
// F.Ln("T.Ln")
|
||||
// F.F("%s", "F.F")
|
||||
// E.F("%s", "E.F")
|
||||
// W.F("%s", "W.F")
|
||||
// I.F("%s", "I.F")
|
||||
// D.F("%s", "D.F")
|
||||
// T.F("%s", "T.F")
|
||||
// F.C(func() string { return "F.C" })
|
||||
// E.C(func() string { return "E.C" })
|
||||
// W.C(func() string { return "W.C" })
|
||||
// I.C(func() string { return "I.C" })
|
||||
// D.C(func() string { return "D.C" })
|
||||
// T.C(func() string { return "T.C" })
|
||||
// F.C(func() string { return "F.C" })
|
||||
// E.Chk(errors.New("E.Chk"))
|
||||
// W.Chk(errors.New("W.Chk"))
|
||||
// I.Chk(errors.New("I.Chk"))
|
||||
// D.Chk(errors.New("D.Chk"))
|
||||
// T.Chk(errors.New("T.Chk"))
|
||||
}
|
||||
136
pkg/opts/float/float.go
Normal file
136
pkg/opts/float/float.go
Normal file
@@ -0,0 +1,136 @@
|
||||
package float
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"git.mleku.dev/mleku/prevara/pkg/opts/meta"
|
||||
"git.mleku.dev/mleku/prevara/pkg/opts/opt"
|
||||
"git.mleku.dev/mleku/prevara/pkg/opts/sanitizers"
|
||||
|
||||
uberatomic "go.uber.org/atomic"
|
||||
)
|
||||
|
||||
// Opt stores an float64 configuration value
|
||||
type Opt struct {
|
||||
meta.Data
|
||||
hook []Hook
|
||||
Min, Max float64
|
||||
clamp func(input float64) (result float64)
|
||||
Value *uberatomic.Float64
|
||||
Def float64
|
||||
}
|
||||
|
||||
type Hook func(f float64) error
|
||||
|
||||
// New returns a new Opt value set to a default value
|
||||
func New(m meta.Data, def float64, min, max float64, hook ...Hook) *Opt {
|
||||
return &Opt{
|
||||
Value: uberatomic.NewFloat64(def),
|
||||
Data: m,
|
||||
Def: def,
|
||||
Min: min,
|
||||
Max: max,
|
||||
hook: hook,
|
||||
clamp: sanitizers.ClampFloat(min, max),
|
||||
}
|
||||
}
|
||||
|
||||
// SetName sets the name for the generator
|
||||
func (x *Opt) SetName(name string) {
|
||||
x.Data.Option = strings.ToLower(name)
|
||||
x.Data.Name = name
|
||||
}
|
||||
|
||||
// Type returns the receiver wrapped in an interface for identifying its type
|
||||
func (x *Opt) Type() interface{} {
|
||||
return x
|
||||
}
|
||||
|
||||
// GetMetadata returns the metadata of the opt type
|
||||
func (x *Opt) GetMetadata() *meta.Data {
|
||||
return &x.Data
|
||||
}
|
||||
|
||||
// ReadInput sets the value from a string
|
||||
func (x *Opt) ReadInput(input string) (o opt.Option, e error) {
|
||||
if input == "" {
|
||||
e = fmt.Errorf("floating point number opt %s %v may not be empty", x.Name(), x.Data.Aliases)
|
||||
return
|
||||
}
|
||||
if strings.HasPrefix(input, "=") {
|
||||
// the following removes leading and trailing '='
|
||||
input = strings.Join(strings.Split(input, "=")[1:], "=")
|
||||
}
|
||||
var v float64
|
||||
if v, e = strconv.ParseFloat(input, 64); E.Chk(e) {
|
||||
return
|
||||
}
|
||||
if e = x.Set(v); E.Chk(e) {
|
||||
}
|
||||
return x, e
|
||||
}
|
||||
|
||||
// LoadInput sets the value from a string (this is the same as the above but differs for Strings)
|
||||
func (x *Opt) LoadInput(input string) (o opt.Option, e error) {
|
||||
return x.ReadInput(input)
|
||||
}
|
||||
|
||||
// Name returns the name of the opt
|
||||
func (x *Opt) Name() string {
|
||||
return x.Data.Option
|
||||
}
|
||||
|
||||
// AddHooks appends callback hooks to be run when the value is changed
|
||||
func (x *Opt) AddHooks(hook ...Hook) {
|
||||
x.hook = append(x.hook, hook...)
|
||||
}
|
||||
|
||||
// SetHooks sets a new slice of hooks
|
||||
func (x *Opt) SetHooks(hook ...Hook) {
|
||||
x.hook = hook
|
||||
}
|
||||
|
||||
// V returns the value stored
|
||||
func (x *Opt) V() float64 {
|
||||
return x.Value.Load()
|
||||
}
|
||||
|
||||
func (x *Opt) runHooks(f float64) (e error) {
|
||||
for i := range x.hook {
|
||||
if e = x.hook[i](f); E.Chk(e) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Set the value stored
|
||||
func (x *Opt) Set(f float64) (e error) {
|
||||
f = x.clamp(f)
|
||||
if e = x.runHooks(f); !E.Chk(e) {
|
||||
x.Value.Store(f)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// String returns a string representation of the value
|
||||
func (x *Opt) String() string {
|
||||
return fmt.Sprintf("%s: %0.8f", x.Data.Option, x.V())
|
||||
}
|
||||
|
||||
// MarshalJSON returns the json representation of
|
||||
func (x *Opt) MarshalJSON() (b []byte, e error) {
|
||||
v := x.Value.Load()
|
||||
return json.Marshal(&v)
|
||||
}
|
||||
|
||||
// UnmarshalJSON decodes a JSON representation of
|
||||
func (x *Opt) UnmarshalJSON(data []byte) (e error) {
|
||||
v := x.Value.Load()
|
||||
e = json.Unmarshal(data, &v)
|
||||
e = x.Set(v)
|
||||
return
|
||||
}
|
||||
44
pkg/opts/float/log.go
Normal file
44
pkg/opts/float/log.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package float
|
||||
|
||||
import (
|
||||
"git.mleku.dev/mleku/prevara/pkg/log"
|
||||
|
||||
"git.mleku.dev/mleku/prevara/version"
|
||||
)
|
||||
|
||||
var subsystem = log.AddLoggerSubsystem(version.PathBase)
|
||||
var F, E, W, I, D, T log.LevelPrinter = log.GetLogPrinterSet(subsystem)
|
||||
|
||||
func init() {
|
||||
// to filter out this package, uncomment the following
|
||||
// var _ = logg.AddFilteredSubsystem(subsystem)
|
||||
|
||||
// to highlight this package, uncomment the following
|
||||
// var _ = logg.AddHighlightedSubsystem(subsystem)
|
||||
|
||||
// these are here to test whether they are working
|
||||
// F.Ln("F.Ln")
|
||||
// E.Ln("E.Ln")
|
||||
// W.Ln("W.Ln")
|
||||
// I.Ln("I.Ln")
|
||||
// D.Ln("D.Ln")
|
||||
// F.Ln("T.Ln")
|
||||
// F.F("%s", "F.F")
|
||||
// E.F("%s", "E.F")
|
||||
// W.F("%s", "W.F")
|
||||
// I.F("%s", "I.F")
|
||||
// D.F("%s", "D.F")
|
||||
// T.F("%s", "T.F")
|
||||
// F.C(func() string { return "F.C" })
|
||||
// E.C(func() string { return "E.C" })
|
||||
// W.C(func() string { return "W.C" })
|
||||
// I.C(func() string { return "I.C" })
|
||||
// D.C(func() string { return "D.C" })
|
||||
// T.C(func() string { return "T.C" })
|
||||
// F.C(func() string { return "F.C" })
|
||||
// E.Chk(errors.New("E.Chk"))
|
||||
// W.Chk(errors.New("W.Chk"))
|
||||
// I.Chk(errors.New("I.Chk"))
|
||||
// D.Chk(errors.New("D.Chk"))
|
||||
// T.Chk(errors.New("T.Chk"))
|
||||
}
|
||||
136
pkg/opts/integer/int.go
Normal file
136
pkg/opts/integer/int.go
Normal file
@@ -0,0 +1,136 @@
|
||||
package integer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
uberatomic "go.uber.org/atomic"
|
||||
|
||||
"git.mleku.dev/mleku/prevara/pkg/opts/meta"
|
||||
"git.mleku.dev/mleku/prevara/pkg/opts/opt"
|
||||
"git.mleku.dev/mleku/prevara/pkg/opts/sanitizers"
|
||||
)
|
||||
|
||||
// Opt stores an int configuration value
|
||||
type Opt struct {
|
||||
meta.Data
|
||||
hook []Hook
|
||||
Min, Max int
|
||||
clamp func(input int) (result int)
|
||||
Value *uberatomic.Int64
|
||||
Def int64
|
||||
}
|
||||
|
||||
type Hook func(i int) error
|
||||
|
||||
// New creates a new Opt with a given default value
|
||||
func New(m meta.Data, def int64, min, max int, hook ...Hook) *Opt {
|
||||
return &Opt{
|
||||
Value: uberatomic.NewInt64(def),
|
||||
Data: m,
|
||||
Def: def,
|
||||
Min: min,
|
||||
Max: max,
|
||||
hook: hook,
|
||||
clamp: sanitizers.ClampInt(min, max),
|
||||
}
|
||||
}
|
||||
|
||||
// SetName sets the name for the generator
|
||||
func (x *Opt) SetName(name string) {
|
||||
x.Data.Option = strings.ToLower(name)
|
||||
x.Data.Name = name
|
||||
}
|
||||
|
||||
// Type returns the receiver wrapped in an interface for identifying its type
|
||||
func (x *Opt) Type() interface{} {
|
||||
return x
|
||||
}
|
||||
|
||||
// GetMetadata returns the metadata of the opt type
|
||||
func (x *Opt) GetMetadata() *meta.Data {
|
||||
return &x.Data
|
||||
}
|
||||
|
||||
// ReadInput sets the value from a string
|
||||
func (x *Opt) ReadInput(input string) (o opt.Option, e error) {
|
||||
if input == "" {
|
||||
e = fmt.Errorf("integer number opt %s %v may not be empty", x.Name(), x.Data.Aliases)
|
||||
return
|
||||
}
|
||||
if strings.HasPrefix(input, "=") {
|
||||
// the following removes leading and trailing '='
|
||||
input = strings.Join(strings.Split(input, "=")[1:], "=")
|
||||
}
|
||||
var v int64
|
||||
if v, e = strconv.ParseInt(input, 10, 64); E.Chk(e) {
|
||||
return
|
||||
}
|
||||
if e = x.Set(int(v)); E.Chk(e) {
|
||||
}
|
||||
return x, e
|
||||
}
|
||||
|
||||
// LoadInput sets the value from a string (this is the same as the above but differs for Strings)
|
||||
func (x *Opt) LoadInput(input string) (o opt.Option, e error) {
|
||||
return x.ReadInput(input)
|
||||
}
|
||||
|
||||
// Name returns the name of the opt
|
||||
func (x *Opt) Name() string {
|
||||
return x.Data.Option
|
||||
}
|
||||
|
||||
// AddHooks appends callback hooks to be run when the value is changed
|
||||
func (x *Opt) AddHooks(hook ...Hook) {
|
||||
x.hook = append(x.hook, hook...)
|
||||
}
|
||||
|
||||
// SetHooks sets a new slice of hooks
|
||||
func (x *Opt) SetHooks(hook ...Hook) {
|
||||
x.hook = hook
|
||||
}
|
||||
|
||||
// V returns the stored int
|
||||
func (x *Opt) V() int {
|
||||
return int(x.Value.Load())
|
||||
}
|
||||
|
||||
func (x *Opt) runHooks(ii int) (e error) {
|
||||
for i := range x.hook {
|
||||
if e = x.hook[i](ii); E.Chk(e) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Set the value stored
|
||||
func (x *Opt) Set(i int) (e error) {
|
||||
i = x.clamp(i)
|
||||
if e = x.runHooks(i); !E.Chk(e) {
|
||||
x.Value.Store(int64(i))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// String returns the string stored
|
||||
func (x *Opt) String() string {
|
||||
return fmt.Sprintf("%s: %d", x.Data.Option, x.V())
|
||||
}
|
||||
|
||||
// MarshalJSON returns the json representation of
|
||||
func (x *Opt) MarshalJSON() (b []byte, e error) {
|
||||
v := x.Value.Load()
|
||||
return json.Marshal(&v)
|
||||
}
|
||||
|
||||
// UnmarshalJSON decodes a JSON representation of
|
||||
func (x *Opt) UnmarshalJSON(data []byte) (e error) {
|
||||
v := x.Value.Load()
|
||||
e = json.Unmarshal(data, &v)
|
||||
e = x.Set(int(v))
|
||||
return
|
||||
}
|
||||
44
pkg/opts/integer/log.go
Normal file
44
pkg/opts/integer/log.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package integer
|
||||
|
||||
import (
|
||||
"git.mleku.dev/mleku/prevara/pkg/log"
|
||||
|
||||
"git.mleku.dev/mleku/prevara/version"
|
||||
)
|
||||
|
||||
var subsystem = log.AddLoggerSubsystem(version.PathBase)
|
||||
var F, E, W, I, D, T log.LevelPrinter = log.GetLogPrinterSet(subsystem)
|
||||
|
||||
func init() {
|
||||
// to filter out this package, uncomment the following
|
||||
// var _ = logg.AddFilteredSubsystem(subsystem)
|
||||
|
||||
// to highlight this package, uncomment the following
|
||||
// var _ = logg.AddHighlightedSubsystem(subsystem)
|
||||
|
||||
// these are here to test whether they are working
|
||||
// F.Ln("F.Ln")
|
||||
// E.Ln("E.Ln")
|
||||
// W.Ln("W.Ln")
|
||||
// I.Ln("I.Ln")
|
||||
// D.Ln("D.Ln")
|
||||
// F.Ln("T.Ln")
|
||||
// F.F("%s", "F.F")
|
||||
// E.F("%s", "E.F")
|
||||
// W.F("%s", "W.F")
|
||||
// I.F("%s", "I.F")
|
||||
// D.F("%s", "D.F")
|
||||
// T.F("%s", "T.F")
|
||||
// F.C(func() string { return "F.C" })
|
||||
// E.C(func() string { return "E.C" })
|
||||
// W.C(func() string { return "W.C" })
|
||||
// I.C(func() string { return "I.C" })
|
||||
// D.C(func() string { return "D.C" })
|
||||
// T.C(func() string { return "T.C" })
|
||||
// F.C(func() string { return "F.C" })
|
||||
// E.Chk(errors.New("E.Chk"))
|
||||
// W.Chk(errors.New("W.Chk"))
|
||||
// I.Chk(errors.New("I.Chk"))
|
||||
// D.Chk(errors.New("D.Chk"))
|
||||
// T.Chk(errors.New("T.Chk"))
|
||||
}
|
||||
44
pkg/opts/list/log.go
Normal file
44
pkg/opts/list/log.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package list
|
||||
|
||||
import (
|
||||
"git.mleku.dev/mleku/prevara/pkg/log"
|
||||
|
||||
"git.mleku.dev/mleku/prevara/version"
|
||||
)
|
||||
|
||||
var subsystem = log.AddLoggerSubsystem(version.PathBase)
|
||||
var F, E, W, I, D, T log.LevelPrinter = log.GetLogPrinterSet(subsystem)
|
||||
|
||||
func init() {
|
||||
// to filter out this package, uncomment the following
|
||||
// var _ = logg.AddFilteredSubsystem(subsystem)
|
||||
|
||||
// to highlight this package, uncomment the following
|
||||
// var _ = logg.AddHighlightedSubsystem(subsystem)
|
||||
|
||||
// these are here to test whether they are working
|
||||
// F.Ln("F.Ln")
|
||||
// E.Ln("E.Ln")
|
||||
// W.Ln("W.Ln")
|
||||
// I.Ln("I.Ln")
|
||||
// D.Ln("D.Ln")
|
||||
// F.Ln("T.Ln")
|
||||
// F.F("%s", "F.F")
|
||||
// E.F("%s", "E.F")
|
||||
// W.F("%s", "W.F")
|
||||
// I.F("%s", "I.F")
|
||||
// D.F("%s", "D.F")
|
||||
// T.F("%s", "T.F")
|
||||
// F.C(func() string { return "F.C" })
|
||||
// E.C(func() string { return "E.C" })
|
||||
// W.C(func() string { return "W.C" })
|
||||
// I.C(func() string { return "I.C" })
|
||||
// D.C(func() string { return "D.C" })
|
||||
// T.C(func() string { return "T.C" })
|
||||
// F.C(func() string { return "F.C" })
|
||||
// E.Chk(errors.New("E.Chk"))
|
||||
// W.Chk(errors.New("W.Chk"))
|
||||
// I.Chk(errors.New("I.Chk"))
|
||||
// D.Chk(errors.New("D.Chk"))
|
||||
// T.Chk(errors.New("T.Chk"))
|
||||
}
|
||||
166
pkg/opts/list/strings.go
Normal file
166
pkg/opts/list/strings.go
Normal file
@@ -0,0 +1,166 @@
|
||||
package list
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
|
||||
"git.mleku.dev/mleku/prevara/pkg/opts/normalize"
|
||||
|
||||
"git.mleku.dev/mleku/prevara/pkg/opts/meta"
|
||||
"git.mleku.dev/mleku/prevara/pkg/opts/opt"
|
||||
"git.mleku.dev/mleku/prevara/pkg/opts/sanitizers"
|
||||
)
|
||||
|
||||
// Opt stores a string slice configuration value
|
||||
type Opt struct {
|
||||
meta.Data
|
||||
hook []Hook
|
||||
Value *atomic.Value
|
||||
Def []string
|
||||
}
|
||||
|
||||
type Hook func(s []string) error
|
||||
|
||||
// New creates a new Opt with default values set
|
||||
func New(m meta.Data, def []string, hook ...Hook) *Opt {
|
||||
as := &atomic.Value{}
|
||||
as.Store(def)
|
||||
return &Opt{Value: as, Data: m, Def: def, hook: hook}
|
||||
}
|
||||
|
||||
// SetName sets the name for the generator
|
||||
func (x *Opt) SetName(name string) {
|
||||
x.Data.Option = strings.ToLower(name)
|
||||
x.Data.Name = name
|
||||
}
|
||||
|
||||
// Type returns the receiver wrapped in an interface for identifying its type
|
||||
func (x *Opt) Type() interface{} {
|
||||
return x
|
||||
}
|
||||
|
||||
// GetMetadata returns the metadata of the opt type
|
||||
func (x *Opt) GetMetadata() *meta.Data {
|
||||
return &x.Data
|
||||
}
|
||||
|
||||
// ReadInput adds the value from a string. For this opt this means appending to the list
|
||||
func (x *Opt) ReadInput(input string) (o opt.Option, e error) {
|
||||
if input == "" {
|
||||
e = fmt.Errorf("string opt %s %v may not be empty", x.Name(), x.Data.Aliases)
|
||||
return
|
||||
}
|
||||
if strings.HasPrefix(input, "=") {
|
||||
input = strings.Join(strings.Split(input, "=")[1:], "=")
|
||||
}
|
||||
// if value has a comma in it, it's a list of items, so split them and append them
|
||||
slice := x.S()
|
||||
if strings.Contains(input, ",") {
|
||||
split := strings.Split(input, ",")
|
||||
for i := range split {
|
||||
var cleaned string
|
||||
if cleaned, e = sanitizers.StringType(x.Data.Type, split[i], x.Data.DefaultPort); E.Chk(e) {
|
||||
return
|
||||
}
|
||||
if cleaned != "" {
|
||||
I.Ln("setting value for", x.Data.Name, cleaned)
|
||||
split[i] = cleaned
|
||||
}
|
||||
}
|
||||
e = x.Set(append(slice, split...))
|
||||
} else {
|
||||
var cleaned string
|
||||
if cleaned, e = sanitizers.StringType(x.Data.Type, input, x.Data.DefaultPort); E.Chk(e) {
|
||||
return
|
||||
}
|
||||
if cleaned != "" {
|
||||
I.Ln("setting value for", x.Data.Name, cleaned)
|
||||
input = cleaned
|
||||
}
|
||||
if e = x.Set(append(slice, input)); E.Chk(e) {
|
||||
}
|
||||
|
||||
}
|
||||
// ensure there is no duplicates
|
||||
e = x.Set(normalize.RemoveDuplicateAddresses(x.V()))
|
||||
return x, e
|
||||
}
|
||||
|
||||
// LoadInput sets the value from a string. For this opt this replacing the list
|
||||
func (x *Opt) LoadInput(input string) (o opt.Option, e error) {
|
||||
old := x.V()
|
||||
_ = x.Set([]string{})
|
||||
if o, e = x.ReadInput(input); E.Chk(e) {
|
||||
// if input failed to parse, restore its prior state
|
||||
_ = x.Set(old)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Name returns the name of the opt
|
||||
func (x *Opt) Name() string {
|
||||
return x.Data.Option
|
||||
}
|
||||
|
||||
// AddHooks appends callback hooks to be run when the value is changed
|
||||
func (x *Opt) AddHooks(hook ...Hook) {
|
||||
x.hook = append(x.hook, hook...)
|
||||
}
|
||||
|
||||
// SetHooks sets a new slice of hooks
|
||||
func (x *Opt) SetHooks(hook ...Hook) {
|
||||
x.hook = hook
|
||||
}
|
||||
|
||||
// V returns the stored value
|
||||
func (x *Opt) V() []string {
|
||||
return x.Value.Load().([]string)
|
||||
}
|
||||
|
||||
// Len returns the length of the slice of strings
|
||||
func (x *Opt) Len() int {
|
||||
return len(x.S())
|
||||
}
|
||||
|
||||
func (x *Opt) runHooks(s []string) (e error) {
|
||||
for i := range x.hook {
|
||||
if e = x.hook[i](s); E.Chk(e) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Set the slice of strings stored
|
||||
func (x *Opt) Set(ss []string) (e error) {
|
||||
if e = x.runHooks(ss); !E.Chk(e) {
|
||||
x.Value.Store(ss)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// S returns the value as a slice of string
|
||||
func (x *Opt) S() []string {
|
||||
return x.Value.Load().([]string)
|
||||
}
|
||||
|
||||
// String returns a string representation of the value
|
||||
func (x *Opt) String() string {
|
||||
return fmt.Sprint(x.Data.Option, ": ", x.S())
|
||||
}
|
||||
|
||||
// MarshalJSON returns the json representation of
|
||||
func (x *Opt) MarshalJSON() (b []byte, e error) {
|
||||
xs := x.Value.Load().([]string)
|
||||
return json.Marshal(xs)
|
||||
}
|
||||
|
||||
// UnmarshalJSON decodes a JSON representation of
|
||||
func (x *Opt) UnmarshalJSON(data []byte) (e error) {
|
||||
var v []string
|
||||
e = json.Unmarshal(data, &v)
|
||||
x.Value.Store(v)
|
||||
return
|
||||
}
|
||||
24
pkg/opts/meta/data.go
Normal file
24
pkg/opts/meta/data.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package meta
|
||||
|
||||
type (
|
||||
// Data is the information about the opt to be used by interface code and other presentations of the data
|
||||
Data struct {
|
||||
Option string
|
||||
Aliases []string
|
||||
Group string
|
||||
Tags []string
|
||||
Label string
|
||||
Description string
|
||||
Documentation string
|
||||
Type string
|
||||
Options []string
|
||||
OmitEmpty bool
|
||||
Name string
|
||||
DefaultPort int
|
||||
}
|
||||
)
|
||||
|
||||
func (m Data) GetAllOptionStrings() (opts []string) {
|
||||
opts = append([]string{m.Option}, m.Aliases...)
|
||||
return opts
|
||||
}
|
||||
44
pkg/opts/meta/log.go
Normal file
44
pkg/opts/meta/log.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package meta
|
||||
|
||||
import (
|
||||
"git.mleku.dev/mleku/prevara/pkg/log"
|
||||
|
||||
"git.mleku.dev/mleku/prevara/version"
|
||||
)
|
||||
|
||||
var subsystem = log.AddLoggerSubsystem(version.PathBase)
|
||||
var F, E, W, I, D, T log.LevelPrinter = log.GetLogPrinterSet(subsystem)
|
||||
|
||||
func init() {
|
||||
// to filter out this package, uncomment the following
|
||||
// var _ = logg.AddFilteredSubsystem(subsystem)
|
||||
|
||||
// to highlight this package, uncomment the following
|
||||
// var _ = logg.AddHighlightedSubsystem(subsystem)
|
||||
|
||||
// these are here to test whether they are working
|
||||
// F.Ln("F.Ln")
|
||||
// E.Ln("E.Ln")
|
||||
// W.Ln("W.Ln")
|
||||
// I.Ln("I.Ln")
|
||||
// D.Ln("D.Ln")
|
||||
// F.Ln("T.Ln")
|
||||
// F.F("%s", "F.F")
|
||||
// E.F("%s", "E.F")
|
||||
// W.F("%s", "W.F")
|
||||
// I.F("%s", "I.F")
|
||||
// D.F("%s", "D.F")
|
||||
// T.F("%s", "T.F")
|
||||
// F.C(func() string { return "F.C" })
|
||||
// E.C(func() string { return "E.C" })
|
||||
// W.C(func() string { return "W.C" })
|
||||
// I.C(func() string { return "I.C" })
|
||||
// D.C(func() string { return "D.C" })
|
||||
// T.C(func() string { return "T.C" })
|
||||
// F.C(func() string { return "F.C" })
|
||||
// E.Chk(errors.New("E.Chk"))
|
||||
// W.Chk(errors.New("W.Chk"))
|
||||
// I.Chk(errors.New("I.Chk"))
|
||||
// D.Chk(errors.New("D.Chk"))
|
||||
// T.Chk(errors.New("T.Chk"))
|
||||
}
|
||||
44
pkg/opts/normalize/log.go
Normal file
44
pkg/opts/normalize/log.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package normalize
|
||||
|
||||
import (
|
||||
"git.mleku.dev/mleku/prevara/pkg/log"
|
||||
|
||||
"git.mleku.dev/mleku/prevara/version"
|
||||
)
|
||||
|
||||
var subsystem = log.AddLoggerSubsystem(version.PathBase)
|
||||
var F, E, W, I, D, T log.LevelPrinter = log.GetLogPrinterSet(subsystem)
|
||||
|
||||
func init() {
|
||||
// to filter out this package, uncomment the following
|
||||
// var _ = logg.AddFilteredSubsystem(subsystem)
|
||||
|
||||
// to highlight this package, uncomment the following
|
||||
// var _ = logg.AddHighlightedSubsystem(subsystem)
|
||||
|
||||
// these are here to test whether they are working
|
||||
// F.Ln("F.Ln")
|
||||
// E.Ln("E.Ln")
|
||||
// W.Ln("W.Ln")
|
||||
// I.Ln("I.Ln")
|
||||
// D.Ln("D.Ln")
|
||||
// F.Ln("T.Ln")
|
||||
// F.F("%s", "F.F")
|
||||
// E.F("%s", "E.F")
|
||||
// W.F("%s", "W.F")
|
||||
// I.F("%s", "I.F")
|
||||
// D.F("%s", "D.F")
|
||||
// T.F("%s", "T.F")
|
||||
// F.C(func() string { return "F.C" })
|
||||
// E.C(func() string { return "E.C" })
|
||||
// W.C(func() string { return "W.C" })
|
||||
// I.C(func() string { return "I.C" })
|
||||
// D.C(func() string { return "D.C" })
|
||||
// T.C(func() string { return "T.C" })
|
||||
// F.C(func() string { return "F.C" })
|
||||
// E.Chk(errors.New("E.Chk"))
|
||||
// W.Chk(errors.New("W.Chk"))
|
||||
// I.Chk(errors.New("I.Chk"))
|
||||
// D.Chk(errors.New("D.Chk"))
|
||||
// T.Chk(errors.New("T.Chk"))
|
||||
}
|
||||
44
pkg/opts/normalize/normalize.go
Normal file
44
pkg/opts/normalize/normalize.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package normalize
|
||||
|
||||
import (
|
||||
"net"
|
||||
)
|
||||
|
||||
// address returns addr with the passed default port appended if there is not
|
||||
// already a port specified.
|
||||
func address(addr, defaultPort string) string {
|
||||
var e error
|
||||
if _, _, e = net.SplitHostPort(addr); E.Chk(e) {
|
||||
return net.JoinHostPort(addr, defaultPort)
|
||||
}
|
||||
return addr
|
||||
}
|
||||
|
||||
// Addresses returns a new slice with all the passed peer addresses normalized
|
||||
// with the given default port, and all duplicates removed.
|
||||
func Addresses(addrs []string, defaultPort string) []string {
|
||||
for i := range addrs {
|
||||
addrs[i] = address(addrs[i], defaultPort)
|
||||
}
|
||||
return RemoveDuplicateAddresses(addrs)
|
||||
}
|
||||
|
||||
// RemoveDuplicateAddresses returns a new slice with all duplicate entries in
|
||||
// addrs removed.
|
||||
func RemoveDuplicateAddresses(addrs []string) (result []string) {
|
||||
result = make([]string, 0, len(addrs))
|
||||
seen := map[string]struct{}{}
|
||||
for _, val := range addrs {
|
||||
if _, ok := seen[val]; !ok {
|
||||
result = append(result, val)
|
||||
seen[val] = struct{}{}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// StringSliceAddresses normalizes a slice of addresses
|
||||
func StringSliceAddresses(a []string, port string) {
|
||||
variable := a
|
||||
a = Addresses(variable, port)
|
||||
}
|
||||
44
pkg/opts/opt/log.go
Normal file
44
pkg/opts/opt/log.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package opt
|
||||
|
||||
import (
|
||||
"git.mleku.dev/mleku/prevara/pkg/log"
|
||||
|
||||
"git.mleku.dev/mleku/prevara/version"
|
||||
)
|
||||
|
||||
var subsystem = log.AddLoggerSubsystem(version.PathBase)
|
||||
var F, E, W, I, D, T log.LevelPrinter = log.GetLogPrinterSet(subsystem)
|
||||
|
||||
func init() {
|
||||
// to filter out this package, uncomment the following
|
||||
// var _ = logg.AddFilteredSubsystem(subsystem)
|
||||
|
||||
// to highlight this package, uncomment the following
|
||||
// var _ = logg.AddHighlightedSubsystem(subsystem)
|
||||
|
||||
// these are here to test whether they are working
|
||||
// F.Ln("F.Ln")
|
||||
// E.Ln("E.Ln")
|
||||
// W.Ln("W.Ln")
|
||||
// I.Ln("I.Ln")
|
||||
// D.Ln("D.Ln")
|
||||
// F.Ln("T.Ln")
|
||||
// F.F("%s", "F.F")
|
||||
// E.F("%s", "E.F")
|
||||
// W.F("%s", "W.F")
|
||||
// I.F("%s", "I.F")
|
||||
// D.F("%s", "D.F")
|
||||
// T.F("%s", "T.F")
|
||||
// F.C(func() string { return "F.C" })
|
||||
// E.C(func() string { return "E.C" })
|
||||
// W.C(func() string { return "W.C" })
|
||||
// I.C(func() string { return "I.C" })
|
||||
// D.C(func() string { return "D.C" })
|
||||
// T.C(func() string { return "T.C" })
|
||||
// F.C(func() string { return "F.C" })
|
||||
// E.Chk(errors.New("E.Chk"))
|
||||
// W.Chk(errors.New("W.Chk"))
|
||||
// I.Chk(errors.New("I.Chk"))
|
||||
// D.Chk(errors.New("D.Chk"))
|
||||
// T.Chk(errors.New("T.Chk"))
|
||||
}
|
||||
21
pkg/opts/opt/option.go
Normal file
21
pkg/opts/opt/option.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package opt
|
||||
|
||||
import (
|
||||
"git.mleku.dev/mleku/prevara/pkg/opts/meta"
|
||||
)
|
||||
|
||||
type (
|
||||
// Option is an interface to simplify concurrent-safe access to a variety of types of configuration item
|
||||
Option interface {
|
||||
LoadInput(input string) (o Option, e error)
|
||||
ReadInput(input string) (o Option, e error)
|
||||
GetMetadata() *meta.Data
|
||||
Name() string
|
||||
String() string
|
||||
MarshalJSON() (b []byte, e error)
|
||||
UnmarshalJSON(data []byte) (e error)
|
||||
GetAllOptionStrings() []string
|
||||
Type() interface{}
|
||||
SetName(string)
|
||||
}
|
||||
)
|
||||
44
pkg/opts/sanitizers/log.go
Normal file
44
pkg/opts/sanitizers/log.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package sanitizers
|
||||
|
||||
import (
|
||||
"git.mleku.dev/mleku/prevara/pkg/log"
|
||||
|
||||
"git.mleku.dev/mleku/prevara/version"
|
||||
)
|
||||
|
||||
var subsystem = log.AddLoggerSubsystem(version.PathBase)
|
||||
var F, E, W, I, D, T log.LevelPrinter = log.GetLogPrinterSet(subsystem)
|
||||
|
||||
func init() {
|
||||
// to filter out this package, uncomment the following
|
||||
// var _ = logg.AddFilteredSubsystem(subsystem)
|
||||
|
||||
// to highlight this package, uncomment the following
|
||||
// var _ = logg.AddHighlightedSubsystem(subsystem)
|
||||
|
||||
// these are here to test whether they are working
|
||||
// F.Ln("F.Ln")
|
||||
// E.Ln("E.Ln")
|
||||
// W.Ln("W.Ln")
|
||||
// I.Ln("I.Ln")
|
||||
// D.Ln("D.Ln")
|
||||
// F.Ln("T.Ln")
|
||||
// F.F("%s", "F.F")
|
||||
// E.F("%s", "E.F")
|
||||
// W.F("%s", "W.F")
|
||||
// I.F("%s", "I.F")
|
||||
// D.F("%s", "D.F")
|
||||
// T.F("%s", "T.F")
|
||||
// F.C(func() string { return "F.C" })
|
||||
// E.C(func() string { return "E.C" })
|
||||
// W.C(func() string { return "W.C" })
|
||||
// I.C(func() string { return "I.C" })
|
||||
// D.C(func() string { return "D.C" })
|
||||
// T.C(func() string { return "T.C" })
|
||||
// F.C(func() string { return "F.C" })
|
||||
// E.Chk(errors.New("E.Chk"))
|
||||
// W.Chk(errors.New("W.Chk"))
|
||||
// I.Chk(errors.New("I.Chk"))
|
||||
// D.Chk(errors.New("D.Chk"))
|
||||
// T.Chk(errors.New("T.Chk"))
|
||||
}
|
||||
41
pkg/opts/sanitizers/numbers.go
Normal file
41
pkg/opts/sanitizers/numbers.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package sanitizers
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func ClampInt(min, max int) func(input int) (result int) {
|
||||
return func(input int) (result int) {
|
||||
if input > max {
|
||||
return max
|
||||
}
|
||||
if input < min {
|
||||
return min
|
||||
}
|
||||
return input
|
||||
}
|
||||
}
|
||||
|
||||
func ClampFloat(min, max float64) func(input float64) (result float64) {
|
||||
return func(input float64) (result float64) {
|
||||
if input > max {
|
||||
return max
|
||||
}
|
||||
if input < min {
|
||||
return min
|
||||
}
|
||||
return input
|
||||
}
|
||||
}
|
||||
|
||||
func ClampDuration(min, max time.Duration) func(input time.Duration) (result time.Duration) {
|
||||
return func(input time.Duration) (result time.Duration) {
|
||||
if input > max {
|
||||
return max
|
||||
}
|
||||
if input < min {
|
||||
return min
|
||||
}
|
||||
return input
|
||||
}
|
||||
}
|
||||
72
pkg/opts/sanitizers/strings.go
Normal file
72
pkg/opts/sanitizers/strings.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package sanitizers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
NetAddress = "netaddress"
|
||||
Password = "password"
|
||||
FilePath = "filepath"
|
||||
Directory = "directory"
|
||||
)
|
||||
|
||||
func StringType(typ, input string, defaultPort int) (cleaned string, e error) {
|
||||
switch typ {
|
||||
case NetAddress:
|
||||
var h, p string
|
||||
if h, p, e = net.SplitHostPort(input); E.Chk(e) {
|
||||
e = fmt.Errorf("address value '%s' not a valid address", input)
|
||||
return
|
||||
}
|
||||
if p == "" {
|
||||
cleaned = net.JoinHostPort(h, fmt.Sprint(defaultPort))
|
||||
}
|
||||
case Password:
|
||||
// password type is mainly here for the input method of the app using this config library
|
||||
case FilePath:
|
||||
if strings.HasPrefix(input, "~") {
|
||||
var homeDir string
|
||||
var usr *user.User
|
||||
var e error
|
||||
if usr, e = user.Current(); e == nil {
|
||||
homeDir = usr.HomeDir
|
||||
}
|
||||
// Fall back to standard HOME environment variable that works for most POSIX OSes if the directory from the Go
|
||||
// standard lib failed.
|
||||
if e != nil || homeDir == "" {
|
||||
homeDir = os.Getenv("HOME")
|
||||
}
|
||||
|
||||
input = strings.Replace(input, "~", homeDir, 1)
|
||||
}
|
||||
if cleaned, e = filepath.Abs(filepath.Clean(input)); E.Chk(e) {
|
||||
}
|
||||
case Directory:
|
||||
if strings.HasPrefix(input, "~") {
|
||||
var homeDir string
|
||||
var usr *user.User
|
||||
var e error
|
||||
if usr, e = user.Current(); e == nil {
|
||||
homeDir = usr.HomeDir
|
||||
}
|
||||
// Fall back to standard HOME environment variable that works for most POSIX OSes if the directory from the Go
|
||||
// standard lib failed.
|
||||
if e != nil || homeDir == "" {
|
||||
homeDir = os.Getenv("HOME")
|
||||
}
|
||||
|
||||
input = strings.Replace(input, "~", homeDir, 1)
|
||||
}
|
||||
if cleaned, e = filepath.Abs(filepath.Clean(input)); E.Chk(e) {
|
||||
}
|
||||
default:
|
||||
cleaned = input
|
||||
}
|
||||
return
|
||||
}
|
||||
44
pkg/opts/text/log.go
Normal file
44
pkg/opts/text/log.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package text
|
||||
|
||||
import (
|
||||
"git.mleku.dev/mleku/prevara/pkg/log"
|
||||
|
||||
"git.mleku.dev/mleku/prevara/version"
|
||||
)
|
||||
|
||||
var subsystem = log.AddLoggerSubsystem(version.PathBase)
|
||||
var F, E, W, I, D, T log.LevelPrinter = log.GetLogPrinterSet(subsystem)
|
||||
|
||||
func init() {
|
||||
// to filter out this package, uncomment the following
|
||||
// var _ = logg.AddFilteredSubsystem(subsystem)
|
||||
|
||||
// to highlight this package, uncomment the following
|
||||
// var _ = logg.AddHighlightedSubsystem(subsystem)
|
||||
|
||||
// these are here to test whether they are working
|
||||
// F.Ln("F.Ln")
|
||||
// E.Ln("E.Ln")
|
||||
// W.Ln("W.Ln")
|
||||
// I.Ln("I.Ln")
|
||||
// D.Ln("D.Ln")
|
||||
// F.Ln("T.Ln")
|
||||
// F.F("%s", "F.F")
|
||||
// E.F("%s", "E.F")
|
||||
// W.F("%s", "W.F")
|
||||
// I.F("%s", "I.F")
|
||||
// D.F("%s", "D.F")
|
||||
// T.F("%s", "T.F")
|
||||
// F.C(func() string { return "F.C" })
|
||||
// E.C(func() string { return "E.C" })
|
||||
// W.C(func() string { return "W.C" })
|
||||
// I.C(func() string { return "I.C" })
|
||||
// D.C(func() string { return "D.C" })
|
||||
// T.C(func() string { return "T.C" })
|
||||
// F.C(func() string { return "F.C" })
|
||||
// E.Chk(errors.New("E.Chk"))
|
||||
// W.Chk(errors.New("W.Chk"))
|
||||
// I.Chk(errors.New("I.Chk"))
|
||||
// D.Chk(errors.New("D.Chk"))
|
||||
// T.Chk(errors.New("T.Chk"))
|
||||
}
|
||||
187
pkg/opts/text/string.go
Normal file
187
pkg/opts/text/string.go
Normal file
@@ -0,0 +1,187 @@
|
||||
package text
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
|
||||
"git.mleku.dev/mleku/prevara/pkg/opts/meta"
|
||||
"git.mleku.dev/mleku/prevara/pkg/opts/opt"
|
||||
"git.mleku.dev/mleku/prevara/pkg/opts/sanitizers"
|
||||
)
|
||||
|
||||
// Opt stores a string configuration value
|
||||
type Opt struct {
|
||||
meta.Data
|
||||
hook []Hook
|
||||
Value *atomic.Value
|
||||
Def string
|
||||
}
|
||||
|
||||
type Hook func(s []byte) error
|
||||
|
||||
// New creates a new Opt with a given default value set
|
||||
func New(m meta.Data, def string, hook ...Hook) *Opt {
|
||||
v := &atomic.Value{}
|
||||
v.Store([]byte(def))
|
||||
return &Opt{Value: v, Data: m, Def: def, hook: hook}
|
||||
}
|
||||
|
||||
// SetName sets the name for the generator
|
||||
func (x *Opt) SetName(name string) {
|
||||
x.Data.Option = strings.ToLower(name)
|
||||
x.Data.Name = name
|
||||
}
|
||||
|
||||
// Type returns the receiver wrapped in an interface for identifying its type
|
||||
func (x *Opt) Type() interface{} {
|
||||
return x
|
||||
}
|
||||
|
||||
// GetMetadata returns the metadata of the opt type
|
||||
func (x *Opt) GetMetadata() *meta.Data {
|
||||
return &x.Data
|
||||
}
|
||||
|
||||
// ReadInput sets the value from a string
|
||||
func (x *Opt) ReadInput(input string) (o opt.Option, e error) {
|
||||
if input == "" {
|
||||
e = fmt.Errorf("string opt %s %v may not be empty", x.Name(), x.Data.Aliases)
|
||||
return
|
||||
}
|
||||
if strings.HasPrefix(input, "=") {
|
||||
// the following removes leading `=` and retains any following instances of `=`
|
||||
input = strings.Join(strings.Split(input, "=")[1:], "=")
|
||||
}
|
||||
if x.Data.Options != nil {
|
||||
var matched string
|
||||
e = fmt.Errorf("option value not found '%s'", input)
|
||||
for _, i := range x.Data.Options {
|
||||
op := i
|
||||
if len(i) >= len(input) {
|
||||
op = i[:len(input)]
|
||||
}
|
||||
if input == op {
|
||||
if e == nil {
|
||||
return x, fmt.Errorf("ambiguous short option value '%s' matches multiple options: %s, %s", input, matched, i)
|
||||
}
|
||||
matched = i
|
||||
e = nil
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if E.Chk(e) {
|
||||
return
|
||||
}
|
||||
input = matched
|
||||
} else {
|
||||
var cleaned string
|
||||
if cleaned, e = sanitizers.StringType(x.Data.Type, input, x.Data.DefaultPort); E.Chk(e) {
|
||||
return
|
||||
}
|
||||
if cleaned != "" {
|
||||
I.Ln("setting value for", x.Data.Name, cleaned)
|
||||
input = cleaned
|
||||
}
|
||||
}
|
||||
e = x.Set(input)
|
||||
return x, e
|
||||
}
|
||||
|
||||
// LoadInput sets the value from a string
|
||||
func (x *Opt) LoadInput(input string) (o opt.Option, e error) {
|
||||
return x.ReadInput(input)
|
||||
}
|
||||
|
||||
// Name returns the name of the opt
|
||||
func (x *Opt) Name() string {
|
||||
return x.Data.Option
|
||||
}
|
||||
|
||||
// AddHooks appends callback hooks to be run when the value is changed
|
||||
func (x *Opt) AddHooks(hook ...Hook) {
|
||||
x.hook = append(x.hook, hook...)
|
||||
}
|
||||
|
||||
// SetHooks sets a new slice of hooks
|
||||
func (x *Opt) SetHooks(hook ...Hook) {
|
||||
x.hook = hook
|
||||
}
|
||||
|
||||
// V returns the stored string
|
||||
func (x *Opt) V() string {
|
||||
return string(x.Value.Load().([]byte))
|
||||
}
|
||||
|
||||
// Empty returns true if the string is empty
|
||||
func (x *Opt) Empty() bool {
|
||||
return len(x.Value.Load().([]byte)) == 0
|
||||
}
|
||||
|
||||
// Bytes returns the raw bytes in the underlying storage
|
||||
// note that this returns a copy because anything done to the slice affects
|
||||
// all accesses afterwards, thus there is also a zero function
|
||||
// todo: make an option for the byte buffer to be MMU fenced to prevent
|
||||
//
|
||||
// elevated privilege processes from accessing this memory.
|
||||
func (x *Opt) Bytes() []byte {
|
||||
byt := x.Value.Load().([]byte)
|
||||
o := make([]byte, len(byt))
|
||||
copy(o, byt)
|
||||
return o
|
||||
}
|
||||
|
||||
// Zero the bytes
|
||||
func (x *Opt) Zero() {
|
||||
byt := x.Value.Load().([]byte)
|
||||
for i := range byt {
|
||||
byt[i] = 0
|
||||
}
|
||||
x.Value.Store(byt)
|
||||
}
|
||||
|
||||
func (x *Opt) runHooks(s []byte) (e error) {
|
||||
for i := range x.hook {
|
||||
if e = x.hook[i](s); E.Chk(e) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Set the value stored
|
||||
func (x *Opt) Set(s string) (e error) {
|
||||
if e = x.runHooks([]byte(s)); !E.Chk(e) {
|
||||
x.Value.Store([]byte(s))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// SetBytes sets the string from bytes
|
||||
func (x *Opt) SetBytes(s []byte) (e error) {
|
||||
if e = x.runHooks(s); !E.Chk(e) {
|
||||
x.Value.Store(s)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Opt returns a string representation of the value
|
||||
func (x *Opt) String() string {
|
||||
return fmt.Sprintf("%s: '%s'", x.Data.Option, x.V())
|
||||
}
|
||||
|
||||
// MarshalJSON returns the json representation
|
||||
func (x *Opt) MarshalJSON() (b []byte, e error) {
|
||||
v := string(x.Value.Load().([]byte))
|
||||
return json.Marshal(&v)
|
||||
}
|
||||
|
||||
// UnmarshalJSON decodes a JSON representation
|
||||
func (x *Opt) UnmarshalJSON(data []byte) (e error) {
|
||||
v := x.Value.Load().([]byte)
|
||||
e = json.Unmarshal(data, &v)
|
||||
x.Value.Store(v)
|
||||
return
|
||||
}
|
||||
24
pkg/qu/LICENSE
Normal file
24
pkg/qu/LICENSE
Normal file
@@ -0,0 +1,24 @@
|
||||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
For more information, please refer to <https://unlicense.org>
|
||||
8
pkg/qu/README.md
Normal file
8
pkg/qu/README.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# qu
|
||||
### observable signal channels
|
||||
|
||||
This is a wrapper around `chan struct{}` that forgives some common mistakes
|
||||
like sending on closed channels and closing cllosed channels, as well as
|
||||
printing logs about when channels are created, waited on, sent to and closed.
|
||||
|
||||
This library makes debugging concurrent code a lot easier. IMHO. YMMV.
|
||||
44
pkg/qu/log.go
Normal file
44
pkg/qu/log.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package qu
|
||||
|
||||
import (
|
||||
"git.mleku.dev/mleku/prevara/pkg/log"
|
||||
|
||||
"git.mleku.dev/mleku/prevara/version"
|
||||
)
|
||||
|
||||
var subsystem = log.AddLoggerSubsystem(version.PathBase)
|
||||
var F, E, W, I, D, _T log.LevelPrinter = log.GetLogPrinterSet(subsystem)
|
||||
|
||||
func init() {
|
||||
// to filter out this package, uncomment the following
|
||||
// var _ = log.AddFilteredSubsystem(subsystem)
|
||||
|
||||
// to highlight this package, uncomment the following
|
||||
// var _ = log.AddHighlightedSubsystem(subsystem)
|
||||
|
||||
// these are here to test whether they are working
|
||||
// F.Ln("F.Ln")
|
||||
// E.Ln("E.Ln")
|
||||
// W.Ln("W.Ln")
|
||||
// I.Ln("I.Ln")
|
||||
// D.Ln("D.Ln")
|
||||
// F.Ln("T.Ln")
|
||||
// F.F("%s", "F.F")
|
||||
// E.F("%s", "E.F")
|
||||
// W.F("%s", "W.F")
|
||||
// I.F("%s", "I.F")
|
||||
// D.F("%s", "D.F")
|
||||
// T.F("%s", "T.F")
|
||||
// F.C(func() string { return "F.C" })
|
||||
// E.C(func() string { return "E.C" })
|
||||
// W.C(func() string { return "W.C" })
|
||||
// I.C(func() string { return "I.C" })
|
||||
// D.C(func() string { return "D.C" })
|
||||
// T.C(func() string { return "T.C" })
|
||||
// F.C(func() string { return "F.C" })
|
||||
// E.Chk(errors.New("E.Chk"))
|
||||
// W.Chk(errors.New("W.Chk"))
|
||||
// I.Chk(errors.New("I.Chk"))
|
||||
// D.Chk(errors.New("D.Chk"))
|
||||
// T.Chk(errors.New("T.Chk"))
|
||||
}
|
||||
178
pkg/qu/quit.go
Normal file
178
pkg/qu/quit.go
Normal file
@@ -0,0 +1,178 @@
|
||||
package qu
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.mleku.dev/mleku/prevara/pkg/log"
|
||||
"go.uber.org/atomic"
|
||||
)
|
||||
|
||||
// C is your basic empty struct signalling channel
|
||||
type C chan struct{}
|
||||
|
||||
var (
|
||||
createdList []string
|
||||
createdChannels []C
|
||||
mx sync.Mutex
|
||||
logEnabled = atomic.NewBool(false)
|
||||
)
|
||||
|
||||
// SetLogging switches on and off the channel logging
|
||||
func SetLogging(on bool) {
|
||||
logEnabled.Store(on)
|
||||
}
|
||||
|
||||
func l(a ...interface{}) {
|
||||
if logEnabled.Load() {
|
||||
D.Ln(a...)
|
||||
}
|
||||
}
|
||||
|
||||
// T creates an unbuffered chan struct{} for trigger and quit signalling (momentary and breaker switches)
|
||||
func T() C {
|
||||
mx.Lock()
|
||||
defer mx.Unlock()
|
||||
msg := log.Caller("chan from", 1)
|
||||
l("created", msg)
|
||||
createdList = append(createdList, msg)
|
||||
o := make(C)
|
||||
createdChannels = append(createdChannels, o)
|
||||
return o
|
||||
}
|
||||
|
||||
// Ts creates a buffered chan struct{} which is specifically intended for signalling without blocking, generally one is
|
||||
// the size of buffer to be used, though there might be conceivable cases where the channel should accept more signals
|
||||
// without blocking the caller
|
||||
func Ts(n int) C {
|
||||
mx.Lock()
|
||||
defer mx.Unlock()
|
||||
msg := log.Caller("buffered chan from", 1)
|
||||
l("created", msg)
|
||||
createdList = append(createdList, msg)
|
||||
o := make(C, n)
|
||||
createdChannels = append(createdChannels, o)
|
||||
return o
|
||||
}
|
||||
|
||||
// Q closes the channel, which makes it emit a nil every time it is selected
|
||||
func (c C) Q() {
|
||||
l(func() (o string) {
|
||||
loc := getLocForChan(c)
|
||||
mx.Lock()
|
||||
defer mx.Unlock()
|
||||
if !testChanIsClosed(c) {
|
||||
close(c)
|
||||
return "closing chan from " + loc + log.Caller("\n"+strings.Repeat(" ", 48)+"from", 1)
|
||||
} else {
|
||||
return "from" + log.Caller("", 1) + "\n" + strings.Repeat(" ", 48) +
|
||||
"channel " + loc + " was already closed"
|
||||
}
|
||||
}(),
|
||||
)
|
||||
}
|
||||
|
||||
// Signal sends struct{}{} on the channel which functions as a momentary switch, useful in pairs for stop/start
|
||||
func (c C) Signal() {
|
||||
l(func() (o string) { return "signalling " + getLocForChan(c) }())
|
||||
c <- struct{}{}
|
||||
}
|
||||
|
||||
// Wait should be placed with a `<-` in a select case in addition to the channel variable name
|
||||
func (c C) Wait() <-chan struct{} {
|
||||
l(func() (o string) { return "waiting on " + getLocForChan(c) + log.Caller("at", 1) }())
|
||||
return c
|
||||
}
|
||||
|
||||
// testChanIsClosed allows you to see whether the channel has been closed so you can avoid a panic by trying to close or
|
||||
// signal on it
|
||||
func testChanIsClosed(ch C) (o bool) {
|
||||
if ch == nil {
|
||||
return true
|
||||
}
|
||||
select {
|
||||
case <-ch:
|
||||
o = true
|
||||
default:
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// getLocForChan finds which record connects to the channel in question
|
||||
func getLocForChan(c C) (s string) {
|
||||
s = "not found"
|
||||
mx.Lock()
|
||||
for i := range createdList {
|
||||
if i >= len(createdChannels) {
|
||||
break
|
||||
}
|
||||
if createdChannels[i] == c {
|
||||
s = createdList[i]
|
||||
}
|
||||
}
|
||||
mx.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// once a minute clean up the channel cache to remove closed channels no longer in use
|
||||
func init() {
|
||||
go func() {
|
||||
for {
|
||||
<-time.After(time.Minute)
|
||||
D.Ln("cleaning up closed channels")
|
||||
var c []C
|
||||
var ll []string
|
||||
mx.Lock()
|
||||
for i := range createdChannels {
|
||||
if i >= len(createdList) {
|
||||
break
|
||||
}
|
||||
if testChanIsClosed(createdChannels[i]) {
|
||||
} else {
|
||||
c = append(c, createdChannels[i])
|
||||
ll = append(ll, createdList[i])
|
||||
}
|
||||
}
|
||||
createdChannels = c
|
||||
createdList = ll
|
||||
mx.Unlock()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// PrintChanState creates an output showing the current state of the channels being monitored
|
||||
// This is a function for use by the programmer while debugging
|
||||
func PrintChanState() {
|
||||
mx.Lock()
|
||||
for i := range createdChannels {
|
||||
if i >= len(createdList) {
|
||||
break
|
||||
}
|
||||
if testChanIsClosed(createdChannels[i]) {
|
||||
_T.Ln(">>> closed", createdList[i])
|
||||
} else {
|
||||
_T.Ln("<<< open", createdList[i])
|
||||
}
|
||||
}
|
||||
mx.Unlock()
|
||||
}
|
||||
|
||||
// GetOpenChanCount returns the number of qu channels that are still open
|
||||
// todo: this needs to only apply to unbuffered type
|
||||
func GetOpenChanCount() (o int) {
|
||||
mx.Lock()
|
||||
var c int
|
||||
for i := range createdChannels {
|
||||
if i >= len(createdChannels) {
|
||||
break
|
||||
}
|
||||
if testChanIsClosed(createdChannels[i]) {
|
||||
c++
|
||||
} else {
|
||||
o++
|
||||
}
|
||||
}
|
||||
mx.Unlock()
|
||||
return
|
||||
}
|
||||
16
version/logversion.go
Normal file
16
version/logversion.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package version
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"git.mleku.dev/mleku/prevara/pkg/log"
|
||||
)
|
||||
|
||||
var F, E, W, I, D, T log.LevelPrinter
|
||||
|
||||
func init() {
|
||||
_, file, _, _ := runtime.Caller(0)
|
||||
verPath := filepath.Dir(file) + "/"
|
||||
F, E, W, I, D, T = log.GetLogPrinterSet(log.AddLoggerSubsystem(verPath))
|
||||
}
|
||||
48
version/version.go
Normal file
48
version/version.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package version
|
||||
|
||||
//go:generate go run ./update/.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
// URL is the git URL for the repository
|
||||
URL = "github.com/p9c/p9"
|
||||
// GitRef is the gitref, as in refs/heads/branchname
|
||||
GitRef = "refs/heads/main"
|
||||
// GitCommit is the commit hash of the current HEAD
|
||||
GitCommit = "40a7d8327e70dca9576a7257ad320106d62ee72f"
|
||||
// BuildTime stores the time when the current binary was built
|
||||
BuildTime = "2021-05-03T14:08:30+02:00"
|
||||
// Tag lists the Tag on the build, adding a + to the newest Tag if the commit is
|
||||
// not that commit
|
||||
Tag = "v0.0.2+"
|
||||
// PathBase is the path base returned from runtime caller
|
||||
PathBase = "/home/loki/src/github.com/p9c/p9/"
|
||||
// Major is the major number from the tag
|
||||
Major = 0
|
||||
// Minor is the minor number from the tag
|
||||
Minor = 0
|
||||
// Patch is the patch version number from the tag
|
||||
Patch = 2
|
||||
// Meta is the extra arbitrary string field from Semver spec
|
||||
Meta = ""
|
||||
)
|
||||
|
||||
// Get returns a pretty printed version information string
|
||||
func Get() string {
|
||||
return fmt.Sprint(
|
||||
"\nRepository Information\n"+
|
||||
"\tGit repository: "+URL+"\n",
|
||||
"\tBranch: "+GitRef+"\n"+
|
||||
"\tCommit: "+GitCommit+"\n"+
|
||||
"\tBuilt: "+BuildTime+"\n"+
|
||||
"\tTag: "+Tag+"\n",
|
||||
"\tMajor:", Major, "\n",
|
||||
"\tMinor:", Minor, "\n",
|
||||
"\tPatch:", Patch, "\n",
|
||||
"\tMeta: ", Meta, "\n",
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user