update to latest gioui.org API, fix cross platform builds

This commit is contained in:
2025-12-03 08:53:03 +00:00
parent a10c3ad0fe
commit 41e5290dc7
46 changed files with 3181 additions and 0 deletions

93
CLAUDE.md Normal file
View 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
View 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
View 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
View File

@@ -0,0 +1,2 @@
# interrupt
Handle shutdowns cleanly easy restarts (theoretically)

View 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
View 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
View 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()
}

View 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")
}

View 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)
}
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
}

View 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
View 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"))
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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"))
}

View 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
View 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
View 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)
}
)

View 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"))
}

View 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
}
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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",
)
}