Move to github.com/tkw1536/pkglib package
This commit removes various modules that can be migrated to the github.com/tkw1536/pkglib package without any code changes (beyond module renamings).
This commit is contained in:
parent
30c25b8e2a
commit
c3ca8e2974
65 changed files with 103 additions and 1254 deletions
|
|
@ -1,70 +0,0 @@
|
|||
package cancel
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/FAU-CDI/wisski-distillery/pkg/timex"
|
||||
)
|
||||
|
||||
// Anyways behaves like context.WithTimeout, except that if the Done() channel of ctx is closed before Anyways is called, the returned context's Done() channel is only closed after timeout.
|
||||
func Anyways(ctx context.Context, timeout time.Duration) (context.Context, context.CancelFunc) {
|
||||
// context is not yet cancelled => return as-is
|
||||
if err := ctx.Err(); err == nil {
|
||||
return context.WithTimeout(ctx, timeout)
|
||||
}
|
||||
|
||||
// create a new anyways
|
||||
any := &anyways{
|
||||
done: make(chan struct{}),
|
||||
parent: ctx,
|
||||
deadline: time.Now().Add(timeout),
|
||||
}
|
||||
|
||||
// start waiting for the timer (or the cancel to be called)
|
||||
finish := make(chan struct{})
|
||||
go func() {
|
||||
t := timex.NewTimer()
|
||||
t.Reset(timeout)
|
||||
defer timex.ReleaseTimer(t)
|
||||
|
||||
defer close(any.done)
|
||||
|
||||
select {
|
||||
case <-t.C:
|
||||
case <-finish:
|
||||
}
|
||||
}()
|
||||
|
||||
return any, func() {
|
||||
close(finish)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type anyways struct {
|
||||
done chan struct{}
|
||||
|
||||
parent context.Context
|
||||
deadline time.Time
|
||||
}
|
||||
|
||||
func (a anyways) Deadline() (deadline time.Time, ok bool) {
|
||||
return a.deadline, true
|
||||
}
|
||||
|
||||
func (a anyways) Done() <-chan struct{} {
|
||||
return a.done
|
||||
}
|
||||
func (a anyways) Err() error {
|
||||
select {
|
||||
case <-a.done:
|
||||
return context.DeadlineExceeded
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (a anyways) Value(key any) any {
|
||||
return a.parent.Done()
|
||||
}
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
package cancel
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
// WithContext executes f and returns the returns the return value and nil.
|
||||
//
|
||||
// If the context is closed before f returns, invokes cancel and returns f(), ctx.Err().
|
||||
//
|
||||
// In general, WithContext always waits for f() to return even if cancel was called.
|
||||
// As a special case if a closed context is passed, f is not invoked.
|
||||
//
|
||||
// allowcancel must be called by f exactly once, as soon as the cancel function may be invoked.
|
||||
func WithContext[T any](ctx context.Context, f func(allowcancel func()) T, cancel func()) (t T, err error) {
|
||||
t, _, err = WithContext2(ctx, func(start func()) (T, struct{}) {
|
||||
return f(start), struct{}{}
|
||||
}, cancel)
|
||||
return
|
||||
}
|
||||
|
||||
// WithContext2 is exactly like WithContext, but takes a function returning two parameters.
|
||||
func WithContext2[T1, T2 any](ctx context.Context, f func(start func()) (T1, T2), cancel func()) (t1 T1, t2 T2, err error) {
|
||||
// context is already closed, don't even try invoking it.
|
||||
if err := ctx.Err(); err != nil {
|
||||
return t1, t2, err
|
||||
}
|
||||
|
||||
cancelable := make(chan struct{}, 1)
|
||||
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
defer close(done)
|
||||
defer close(cancelable)
|
||||
|
||||
t1, t2 = f(func() {
|
||||
cancelable <- struct{}{}
|
||||
})
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
// the function has exited regularly
|
||||
// nothing to be done
|
||||
case <-ctx.Done():
|
||||
|
||||
// context was cancelled
|
||||
<-cancelable
|
||||
cancel()
|
||||
|
||||
// still wait for it to be done!
|
||||
<-done
|
||||
err = ctx.Err()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
package cancel
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
type SetDeadline interface {
|
||||
SetDeadline(t time.Time)
|
||||
}
|
||||
|
||||
type SetReadDeadline interface {
|
||||
SetReadDeadline(t time.Time) error
|
||||
}
|
||||
|
||||
type SetWriteDeadline interface {
|
||||
SetWriteDeadline(t time.Time) error
|
||||
}
|
||||
|
||||
// Copy reads from src, and copies to dst.
|
||||
//
|
||||
// If the context is closed before src is closed, attempts to close the underlying reader and writer.
|
||||
func Copy(ctx context.Context, dst io.Writer, src io.Reader) (written int64, err error) {
|
||||
|
||||
// if the context has a deadline, the propanate that deadline to the underyling file.
|
||||
// this might cause the read call to not block.
|
||||
if deadline, ok := ctx.Deadline(); ok {
|
||||
var zero time.Time
|
||||
|
||||
if file, ok := src.(SetReadDeadline); ok {
|
||||
file.SetReadDeadline(deadline)
|
||||
defer file.SetReadDeadline(zero)
|
||||
} else if file, ok := src.(SetDeadline); ok {
|
||||
file.SetDeadline(deadline)
|
||||
defer file.SetDeadline(zero)
|
||||
}
|
||||
|
||||
if file, ok := dst.(SetWriteDeadline); ok {
|
||||
file.SetWriteDeadline(deadline)
|
||||
defer file.SetWriteDeadline(zero)
|
||||
} else if file, ok := dst.(SetDeadline); ok {
|
||||
file.SetDeadline(deadline)
|
||||
defer file.SetDeadline(zero)
|
||||
}
|
||||
}
|
||||
|
||||
written, err, _ = WithContext2(ctx, func(start func()) (int64, error) {
|
||||
start()
|
||||
return io.Copy(dst, src)
|
||||
}, func() {
|
||||
if closer, ok := src.(io.Closer); ok {
|
||||
closer.Close()
|
||||
}
|
||||
})
|
||||
return written, err
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
package cancel
|
||||
|
||||
import "context"
|
||||
|
||||
// ValuesOf returns a new context that has the same deadline and cancelation behviour as parent.
|
||||
// However when requesting values from the context, checks the values in context first.
|
||||
func ValuesOf(parent, values context.Context) context.Context {
|
||||
return &valuesOf{
|
||||
Context: parent,
|
||||
values: values,
|
||||
}
|
||||
}
|
||||
|
||||
type valuesOf struct {
|
||||
context.Context
|
||||
values context.Context
|
||||
}
|
||||
|
||||
func (vv *valuesOf) Value(key any) any {
|
||||
if value := vv.values.Value(key); value != nil {
|
||||
return value
|
||||
}
|
||||
return vv.Context.Value(key)
|
||||
}
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
package countwriter
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// CountWriter wraps an io.Writer, see [NewCountWriter].
|
||||
//
|
||||
// It is intended to be used to count different writes to an underlying writer.
|
||||
// Once an error occurs, no more writes are passed through, and the underlying error is returned instead.
|
||||
// This means that in practice, calls to write can be continued and are ignored silently.
|
||||
//
|
||||
// The underlying sum of bytes written and error can be seen using [Sum].
|
||||
type CountWriter struct {
|
||||
w io.Writer
|
||||
|
||||
n int
|
||||
err error
|
||||
}
|
||||
|
||||
// NewCountWriter creates a new [CountWriter] that delegates to w.
|
||||
func NewCountWriter(w io.Writer) *CountWriter {
|
||||
return &CountWriter{w: w}
|
||||
}
|
||||
|
||||
// write performs the write operation w on this writer.
|
||||
func (cw *CountWriter) write(w func() (int, error)) (int, error) {
|
||||
// if there was an error, return it and don't do a write
|
||||
if cw.err != nil {
|
||||
return 0, cw.err
|
||||
}
|
||||
|
||||
// call the writer
|
||||
n, err := w()
|
||||
|
||||
// update the underling state
|
||||
cw.n += n
|
||||
cw.err = err
|
||||
|
||||
// and return
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Write implements [io.Writer]
|
||||
func (cw *CountWriter) Write(p []byte) (int, error) {
|
||||
return cw.write(func() (int, error) {
|
||||
return cw.w.Write(p)
|
||||
})
|
||||
}
|
||||
|
||||
// WriteString implements [io.WriteString].
|
||||
// See [Write].
|
||||
func (cw *CountWriter) WriteString(s string) (int, error) {
|
||||
return cw.write(func() (int, error) {
|
||||
return io.WriteString(cw.w, s)
|
||||
})
|
||||
}
|
||||
|
||||
// Sum returns the state, that is the total number of bytes written and any error
|
||||
func (cw *CountWriter) Sum() (int, error) {
|
||||
return cw.n, cw.err
|
||||
}
|
||||
|
|
@ -6,8 +6,8 @@ import (
|
|||
"io/fs"
|
||||
"os"
|
||||
|
||||
"github.com/FAU-CDI/wisski-distillery/pkg/pools"
|
||||
"github.com/tkw1536/goprogram/stream"
|
||||
"github.com/tkw1536/pkglib/pools"
|
||||
)
|
||||
|
||||
// ExecCommandError is returned by Exec when a command could not be executed.
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
package errorx
|
||||
|
||||
import "github.com/tkw1536/goprogram/lib/collection"
|
||||
|
||||
// First returns the first non-nil error, or nil otherwise.
|
||||
func First(errors ...error) error {
|
||||
return collection.First(errors, func(err error) bool { return err != nil })
|
||||
}
|
||||
|
|
@ -6,8 +6,8 @@ import (
|
|||
"io/fs"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/FAU-CDI/wisski-distillery/pkg/cancel"
|
||||
"github.com/FAU-CDI/wisski-distillery/pkg/environment"
|
||||
"github.com/tkw1536/pkglib/contextx"
|
||||
)
|
||||
|
||||
var ErrCopySameFile = errors.New("src and dst must be different")
|
||||
|
|
@ -47,7 +47,7 @@ func CopyFile(ctx context.Context, env environment.Environment, dst, src string)
|
|||
defer dstFile.Close()
|
||||
|
||||
// and do the copy!
|
||||
_, err = cancel.Copy(ctx, dstFile, srcFile)
|
||||
_, err = contextx.Copy(ctx, dstFile, srcFile)
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,34 +0,0 @@
|
|||
// Package hostname provides the hostname.
|
||||
package hostname
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/FAU-CDI/wisski-distillery/pkg/environment"
|
||||
"github.com/Showmax/go-fqdn"
|
||||
)
|
||||
|
||||
// FQDN attempts to return the fully qualified domain name of the host system.
|
||||
// If an error occurs, may fall back to the empty string.
|
||||
func FQDN(env environment.Environment) string {
|
||||
// TODO: Pass this through!
|
||||
|
||||
// try the hostname function
|
||||
{
|
||||
fqdn, err := fqdn.FqdnHostname()
|
||||
if err == nil {
|
||||
return fqdn
|
||||
}
|
||||
}
|
||||
|
||||
// fallback to os hostname
|
||||
{
|
||||
hostname, err := os.Hostname()
|
||||
if err == nil {
|
||||
return hostname
|
||||
}
|
||||
}
|
||||
|
||||
// use the empty string
|
||||
return ""
|
||||
}
|
||||
|
|
@ -7,8 +7,8 @@ import (
|
|||
_ "embed"
|
||||
|
||||
"github.com/FAU-CDI/wisski-distillery/pkg/httpx/field"
|
||||
"github.com/FAU-CDI/wisski-distillery/pkg/pools"
|
||||
"github.com/gorilla/csrf"
|
||||
"github.com/tkw1536/pkglib/pools"
|
||||
)
|
||||
|
||||
// Form provides a form that a user can submit via a http POST method call.
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ import (
|
|||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/FAU-CDI/wisski-distillery/pkg/timex"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/tkw1536/pkglib/timex"
|
||||
)
|
||||
|
||||
const HTMLFlushInterval = time.Second / 10
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package lazy
|
|||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/tkw1536/goprogram/lib/reflectx"
|
||||
"github.com/tkw1536/pkglib/reflectx"
|
||||
)
|
||||
|
||||
// Pool represents a pool of laziliy initialized and potentially referencing Component instances.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package lazy
|
|||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/tkw1536/goprogram/lib/collection"
|
||||
"github.com/tkw1536/pkglib/collection"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ package lazy
|
|||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/tkw1536/goprogram/lib/collection"
|
||||
"github.com/tkw1536/goprogram/lib/reflectx"
|
||||
"github.com/tkw1536/pkglib/collection"
|
||||
"github.com/tkw1536/pkglib/reflectx"
|
||||
)
|
||||
|
||||
// getMeta gets the component belonging to a component type
|
||||
|
|
|
|||
154
pkg/mux/mux.go
154
pkg/mux/mux.go
|
|
@ -1,154 +0,0 @@
|
|||
// Package mux provides mux
|
||||
package mux
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Mux represents a mux that can handle different requests
|
||||
type Mux[C any] struct {
|
||||
prefixes map[string][]handler
|
||||
exacts map[string][]handler
|
||||
|
||||
Context func(r *http.Request) C // called to set context on the given request
|
||||
|
||||
Panic func(panic any, w http.ResponseWriter, r *http.Request) // called on panic
|
||||
NotFound http.Handler // optional handler to be called in case of a not found
|
||||
}
|
||||
|
||||
type contextKey struct{}
|
||||
|
||||
var theContextKey = contextKey{}
|
||||
|
||||
type handler struct {
|
||||
Predicate Predicate
|
||||
http.Handler
|
||||
}
|
||||
|
||||
func (mux *Mux[T]) Prepare(r *http.Request) *http.Request {
|
||||
if mux == nil || mux.Context == nil {
|
||||
return r
|
||||
}
|
||||
|
||||
ctx := context.WithValue(r.Context(), theContextKey, mux.Context(r))
|
||||
return r.WithContext(ctx)
|
||||
}
|
||||
|
||||
func (mux *Mux[T]) ContextOf(r *http.Request) (t T) {
|
||||
value, ok := r.Context().Value(theContextKey).(T)
|
||||
if !ok {
|
||||
return t
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// Add adds a handler for the given path
|
||||
func (mux *Mux[T]) Add(path string, predicate Predicate, exact bool, h http.Handler) {
|
||||
if mux.exacts == nil {
|
||||
mux.exacts = make(map[string][]handler)
|
||||
}
|
||||
if mux.prefixes == nil {
|
||||
mux.prefixes = make(map[string][]handler)
|
||||
}
|
||||
|
||||
mPath := NormalizePath(path)
|
||||
mHandler := handler{Predicate: predicate, Handler: h}
|
||||
if exact {
|
||||
mux.exacts[mPath] = append(mux.exacts[mPath], mHandler)
|
||||
} else {
|
||||
mux.prefixes[mPath] = append(mux.prefixes[mPath], mHandler)
|
||||
}
|
||||
}
|
||||
|
||||
// Match returns the handler to be applied for the given request.
|
||||
func (mux *Mux[T]) Match(r *http.Request, prepare bool) (http.Handler, bool) {
|
||||
if mux == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
if prepare {
|
||||
r = mux.Prepare(r)
|
||||
}
|
||||
|
||||
candidate := NormalizePath(r.URL.Path)
|
||||
|
||||
// match the exact path first
|
||||
for _, h := range mux.exacts[candidate] {
|
||||
if h.Predicate.Call(r) {
|
||||
return h.Handler, true
|
||||
}
|
||||
}
|
||||
|
||||
// iterate over path segment candidates
|
||||
for {
|
||||
// check the current candidate
|
||||
for _, h := range mux.prefixes[candidate] {
|
||||
if h.Predicate.Call(r) {
|
||||
return h.Handler, true
|
||||
}
|
||||
}
|
||||
|
||||
// if the candidate is the root url, we can bail out now
|
||||
if len(candidate) == 0 || candidate == "/" {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// move to the parent segment
|
||||
candidate = parentSegment(candidate)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (mux *Mux[T]) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// handle panics with the panic handler
|
||||
defer func() {
|
||||
caught := recover()
|
||||
if caught == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if mux == nil || mux.Panic == nil {
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// silently ignore any panic()s in the panic handler
|
||||
defer func() {
|
||||
recover()
|
||||
}()
|
||||
|
||||
// call the panic handler
|
||||
mux.Panic(caught, w, r)
|
||||
}()
|
||||
|
||||
// prepare the request
|
||||
r = mux.Prepare(r)
|
||||
|
||||
// find the right handler
|
||||
// or go into 404 mode
|
||||
handler, ok := mux.Match(r, false)
|
||||
if !ok {
|
||||
if mux == nil || mux.NotFound == nil {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
mux.NotFound.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// call the actual handling
|
||||
handler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// Predicate represents a matching predicate for a given request.
|
||||
// The nil predicate always matches
|
||||
type Predicate func(r *http.Request) bool
|
||||
|
||||
// Call checks if this predicate matches the given request.
|
||||
func (p Predicate) Call(r *http.Request) bool {
|
||||
if p == nil {
|
||||
return true
|
||||
}
|
||||
return p(r)
|
||||
}
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
package mux
|
||||
|
||||
import (
|
||||
"path"
|
||||
)
|
||||
|
||||
// NormalizePath normalizes the provided path.
|
||||
// It ensures that there is both a leading and trailing slash.
|
||||
func NormalizePath(value string) string {
|
||||
value = path.Clean(value)
|
||||
if value != "/" {
|
||||
value = value + "/"
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// parentSegment returns the parent segment of the provided path
|
||||
// it assumes that normalizePath has been called on value.
|
||||
func parentSegment(value string) string {
|
||||
if value == "" || value == "/" {
|
||||
return "/"
|
||||
}
|
||||
parent := path.Dir(value[:len(value)-1])
|
||||
if parent != "/" {
|
||||
parent = parent + "/"
|
||||
}
|
||||
return parent
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@ import (
|
|||
"crypto/rand"
|
||||
"math/big"
|
||||
|
||||
"github.com/FAU-CDI/wisski-distillery/pkg/pools"
|
||||
"github.com/tkw1536/pkglib/pools"
|
||||
)
|
||||
|
||||
// NOTE(twiesing): A bunch of scripts cannot properly handle the extra characters in the password.
|
||||
|
|
|
|||
|
|
@ -1,34 +0,0 @@
|
|||
// Package pools holds various pools for reuse
|
||||
package pools
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var builders = sync.Pool{
|
||||
New: func() any { return new(strings.Builder) },
|
||||
}
|
||||
|
||||
func GetBuilder() *strings.Builder {
|
||||
return builders.Get().(*strings.Builder)
|
||||
}
|
||||
|
||||
func ReleaseBuilder(builder *strings.Builder) {
|
||||
builder.Reset()
|
||||
builders.Put(builder)
|
||||
}
|
||||
|
||||
var buffers = sync.Pool{
|
||||
New: func() any { return new(bytes.Buffer) },
|
||||
}
|
||||
|
||||
func GetBuffer() *bytes.Buffer {
|
||||
return buffers.Get().(*bytes.Buffer)
|
||||
}
|
||||
|
||||
func ReleaseBuffer(buffer *bytes.Buffer) {
|
||||
buffer.Reset()
|
||||
buffers.Put(buffer)
|
||||
}
|
||||
|
|
@ -1,108 +0,0 @@
|
|||
package spawner
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Spawner[T io.Closer] struct {
|
||||
Spawn func() T
|
||||
Alive func(t T) bool
|
||||
|
||||
Limit int
|
||||
|
||||
initOnce sync.Once
|
||||
wg sync.WaitGroup
|
||||
tasks chan task[T]
|
||||
|
||||
instances []T
|
||||
alive []bool
|
||||
}
|
||||
|
||||
type task[T any] struct {
|
||||
f func(t T)
|
||||
done chan<- struct{}
|
||||
}
|
||||
|
||||
func (tt task[T]) Do(t T) {
|
||||
defer close(tt.done)
|
||||
tt.f(t)
|
||||
}
|
||||
|
||||
func (spawner *Spawner[T]) worker(i int) {
|
||||
spawner.wg.Add(1)
|
||||
go func() {
|
||||
defer spawner.wg.Done()
|
||||
|
||||
var zero T
|
||||
for task := range spawner.tasks {
|
||||
// spawn a new instance once finished
|
||||
if !spawner.alive[i] {
|
||||
log.Println("respawning instance ", i)
|
||||
spawner.instances[i] = spawner.Spawn()
|
||||
spawner.alive[i] = true
|
||||
}
|
||||
|
||||
task.Do(spawner.instances[i])
|
||||
|
||||
// if the instance has died during execution
|
||||
// then do a close (to deallocate)
|
||||
if !spawner.Alive(spawner.instances[i]) {
|
||||
spawner.instances[i].Close()
|
||||
spawner.instances[i] = zero
|
||||
spawner.alive[i] = false
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (spawner *Spawner[T]) start() {
|
||||
spawner.initOnce.Do(func() {
|
||||
limit := spawner.Limit
|
||||
if limit < 1 {
|
||||
limit = 1
|
||||
}
|
||||
|
||||
spawner.tasks = make(chan task[T], limit)
|
||||
spawner.alive = make([]bool, limit)
|
||||
spawner.instances = make([]T, limit)
|
||||
for i := 0; i < limit; i++ {
|
||||
spawner.worker(i)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Do performs f on an unspecified object from the spawner.
|
||||
// If no objects are available within time.Duration, a new object is spawned as there are at most limit objects.
|
||||
func (spawner *Spawner[T]) Do(f func(t T)) {
|
||||
spawner.start()
|
||||
|
||||
done := make(chan struct{})
|
||||
spawner.tasks <- task[T]{
|
||||
f: f,
|
||||
done: done,
|
||||
}
|
||||
<-done
|
||||
}
|
||||
|
||||
func (spawner *Spawner[T]) Close() {
|
||||
close(spawner.tasks)
|
||||
spawner.wg.Wait()
|
||||
|
||||
spawner.wg.Add(len(spawner.alive))
|
||||
|
||||
var zero T
|
||||
for i := range spawner.alive {
|
||||
go func(i int) {
|
||||
defer spawner.wg.Done()
|
||||
|
||||
if spawner.alive[i] {
|
||||
spawner.instances[i].Close()
|
||||
spawner.instances[i] = zero
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
spawner.wg.Wait()
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -1,12 +0,0 @@
|
|||
package sqle
|
||||
|
||||
import (
|
||||
"github.com/feiin/sqlstring"
|
||||
)
|
||||
|
||||
// TODO: This is really unsafe and shouldn't be used at all.
|
||||
|
||||
// Format formats the provided query with the given parameters.
|
||||
func Format(query string, params ...interface{}) string {
|
||||
return sqlstring.Format(query, params...)
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
package sshx
|
||||
|
||||
import "github.com/gliderlabs/ssh"
|
||||
|
||||
// ParseAllKeys parses all keys from the list of bytes
|
||||
func ParseAllKeys(bytes []byte) (keys []ssh.PublicKey) {
|
||||
var key ssh.PublicKey
|
||||
var err error
|
||||
for {
|
||||
key, _, _, bytes, err = ssh.ParseAuthorizedKey(bytes)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
keys = append(keys, key)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
// Package timex provides Interval and Wait
|
||||
package timex
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var tPool = sync.Pool{
|
||||
New: func() any {
|
||||
timer := time.NewTimer(time.Second)
|
||||
StopTimer(timer)
|
||||
return timer
|
||||
},
|
||||
}
|
||||
|
||||
// NewTimer returns an unusued timer from an internal timer pool.
|
||||
// The timer is guaranteed to be stopped; meaning a call to timer.Reset() should be made before using it.
|
||||
func NewTimer() *time.Timer {
|
||||
return tPool.Get().(*time.Timer)
|
||||
}
|
||||
|
||||
// StopTimer stops the given timer and drains the underlying channel.
|
||||
// This prevents it from firing, until a call to Reset() is made.
|
||||
//
|
||||
// If the timer is not running, StopTimer does nothing.
|
||||
func StopTimer(t *time.Timer) {
|
||||
t.Stop()
|
||||
|
||||
// try to stop
|
||||
select {
|
||||
case <-t.C:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// ReleaseTimer stops t and returns it to the pool of timers.
|
||||
func ReleaseTimer(t *time.Timer) {
|
||||
StopTimer(t)
|
||||
tPool.Put(t)
|
||||
}
|
||||
|
||||
// TickContext is like [time.Tick], but closes the returned channel once the context closes.
|
||||
// As such it can be recovered by the garbage collector; see [time.TickContext].
|
||||
//
|
||||
// Unlike [time.Tick], immediatly send the current time on the given channel.
|
||||
func TickContext(c context.Context, d time.Duration) <-chan time.Time {
|
||||
if d < 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
ticker := make(chan time.Time, 1)
|
||||
ticker <- time.Now()
|
||||
go func() {
|
||||
defer close(ticker)
|
||||
|
||||
timer := NewTimer()
|
||||
defer ReleaseTimer(timer)
|
||||
|
||||
for {
|
||||
timer.Reset(d)
|
||||
|
||||
select {
|
||||
case tick := <-timer.C:
|
||||
ticker <- tick
|
||||
case <-c.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
return ticker
|
||||
}
|
||||
|
||||
// TickUntilFunc invokes f every d until either context is closed, or f returns true.
|
||||
// f is invoked once immediatly when the timer starts.
|
||||
//
|
||||
// TickUntilFunc blocks until f is no longer invoked.
|
||||
//
|
||||
// Returns the error of the context (if any).
|
||||
func TickUntilFunc(f func(t time.Time) bool, c context.Context, d time.Duration) error {
|
||||
context, cancel := context.WithCancel(c)
|
||||
defer cancel()
|
||||
|
||||
for t := range TickContext(context, d) {
|
||||
if f(t) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return c.Err()
|
||||
}
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
package validator
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
const (
|
||||
validateTag = "validate"
|
||||
recurseTag = "recurse"
|
||||
dfltTag = "default"
|
||||
)
|
||||
|
||||
// Validate validates an object of type T, setting defaults where appropriate.
|
||||
//
|
||||
// T must be a struct type, when this is not the case, returns ErrNotAStruct.
|
||||
// validators should contain a set of validators.
|
||||
//
|
||||
// Validate iterates over the fields and tags of those fields as follows:
|
||||
// - If the 'validate' tag is not the empty string, read the appropriate validator from the map, and call the function.
|
||||
// If the element in the validators map does not exist, returns an error that unwraps to type UnknownValidator.
|
||||
// If the element in the validators map is not a validator, returns an error that unwraps to type NotAValidator.
|
||||
// If the type of validator function does not match the field type, returns an error that unwraps to type IncompatibleValidator.
|
||||
// - If the 'recurse' tag is not the empty string, recurse into the struct type by calling Validate on it.
|
||||
// If the annotated field is not a struct, return an error.
|
||||
//
|
||||
// Any error is wrapped in a FieldError, indicating the field they occured in.
|
||||
// Recursive validate calls may result in FieldError wraps.
|
||||
// For a description of struct tags, see [reflect.StructTag].
|
||||
func Validate[T any](data *T, validators map[string]any) error {
|
||||
return validate(reflect.ValueOf(data).Elem(), validators)
|
||||
}
|
||||
|
||||
// FieldError wraps an error to indicate which field it occured in.
|
||||
type FieldError struct {
|
||||
Field string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (fe FieldError) Error() string {
|
||||
return fmt.Sprintf("field %q: %s", fe.Field, fe.Err)
|
||||
}
|
||||
|
||||
func (fe FieldError) Unwrap() error {
|
||||
return fe.Err
|
||||
}
|
||||
|
||||
var ErrNotAStruct = errors.New("validate called on non-struct type")
|
||||
|
||||
func validate(datum reflect.Value, validators Collection) error {
|
||||
// make sure that we have a struct type
|
||||
typ := datum.Type()
|
||||
if typ.Kind() != reflect.Struct {
|
||||
return ErrNotAStruct
|
||||
}
|
||||
|
||||
fieldC := typ.NumField()
|
||||
for i := 0; i < fieldC; i++ {
|
||||
field := typ.Field(i)
|
||||
|
||||
// if the recurse tag is set, do the recursion!
|
||||
if field.Tag.Get(recurseTag) != "" {
|
||||
if err := validate(datum.FieldByName(field.Name), validators); err != nil {
|
||||
return FieldError{Field: field.Name, Err: err}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// check if there is a validator associated with this tag
|
||||
// and if not, skip it!
|
||||
validator := field.Tag.Get(validateTag)
|
||||
if validator == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// call the actual validator
|
||||
if err := validators.Call(
|
||||
validator,
|
||||
datum.FieldByName(field.Name),
|
||||
field.Tag.Get(dfltTag),
|
||||
); err != nil {
|
||||
return FieldError{Field: field.Name, Err: err}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,123 +0,0 @@
|
|||
package validator
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func ExampleValidate() {
|
||||
var value struct {
|
||||
Number int `validate:"positive" default:"234"`
|
||||
String string `validate:"nonempty" default:"stuff"`
|
||||
Recursive struct {
|
||||
Number int `validate:"positive" default:"45"`
|
||||
String string `validate:"nonempty" default:"more"`
|
||||
} `recurse:"true"`
|
||||
}
|
||||
|
||||
collection := make(Collection, 2)
|
||||
Add(collection, "positive", func(value *int, dflt string) error {
|
||||
if *value == 0 {
|
||||
i, err := strconv.ParseInt(dflt, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*value = int(i)
|
||||
return nil
|
||||
}
|
||||
if *value < 0 {
|
||||
return errors.New("not positive")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
Add(collection, "nonempty", func(value *string, dflt string) error {
|
||||
if *value == "" {
|
||||
*value = dflt
|
||||
}
|
||||
if *value == "" {
|
||||
return errors.New("empty string")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
err := Validate(&value, collection)
|
||||
fmt.Printf("%v\n", value)
|
||||
fmt.Println(err)
|
||||
// Output: {234 stuff {45 more}}
|
||||
// <nil>
|
||||
}
|
||||
|
||||
func ExampleValidate_fail() {
|
||||
var value struct {
|
||||
Number int `validate:"positive" default:"12"`
|
||||
String string `validate:"nonempty" default:"stuff"`
|
||||
Recursive struct {
|
||||
Number int `validate:"positive" default:"12"`
|
||||
String string `validate:"nonempty"`
|
||||
} `recurse:"true"`
|
||||
}
|
||||
|
||||
collection := make(Collection, 2)
|
||||
Add(collection, "positive", func(value *int, dflt string) error {
|
||||
if *value == 0 {
|
||||
i, err := strconv.ParseInt(dflt, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*value = int(i)
|
||||
return nil
|
||||
}
|
||||
if *value < 0 {
|
||||
return errors.New("not positive")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
Add(collection, "nonempty", func(value *string, dflt string) error {
|
||||
if *value == "" {
|
||||
*value = dflt
|
||||
}
|
||||
if *value == "" {
|
||||
return errors.New("empty string")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
err := Validate(&value, collection)
|
||||
fmt.Printf("%v\n", value)
|
||||
fmt.Println(err)
|
||||
// Output: {12 stuff {12 }}
|
||||
// field "Recursive": field "String": empty string
|
||||
}
|
||||
|
||||
func ExampleValidate_notastruct() {
|
||||
var value int
|
||||
err := Validate(&value, nil)
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
func ExampleValidate_notavalidator() {
|
||||
var value struct {
|
||||
Field int `validate:"generic"`
|
||||
}
|
||||
collection := make(Collection, 2)
|
||||
collection["generic"] = func(x, y int) error {
|
||||
panic("never reached")
|
||||
}
|
||||
err := Validate(&value, collection)
|
||||
fmt.Println(err)
|
||||
// Output: field "Field": entry "generic" in validators is not a valiator
|
||||
}
|
||||
|
||||
func ExampleValidate_invalid() {
|
||||
var value struct {
|
||||
Field int `validate:"string"`
|
||||
}
|
||||
collection := make(Collection, 2)
|
||||
collection["string"] = func(value *string, dflt string) error {
|
||||
panic("never reached")
|
||||
}
|
||||
err := Validate(&value, collection)
|
||||
fmt.Println(err)
|
||||
// Output: field "Field": validator "string": got type string, expected type int
|
||||
}
|
||||
|
|
@ -1,131 +0,0 @@
|
|||
package validator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/tkw1536/goprogram/lib/reflectx"
|
||||
)
|
||||
|
||||
// Collection represents a set of validators.
|
||||
// The zero value is not ready to use; it should be created using make().
|
||||
//
|
||||
// A validator is a non-nil function with signature func(value *F, dflt string) error.
|
||||
// Here F is the type of a value of a field.
|
||||
// The value is the initialized value to be validated.
|
||||
// The validator may perform abitrary normalization on the value.
|
||||
// dflt is the default value (read from the 'default' tag).
|
||||
// error should be an appropriate error that occured.
|
||||
//
|
||||
// A validator function is applied by calling it.
|
||||
type Collection map[string]any
|
||||
|
||||
// Add adds a Validator to the provided collection of validators.
|
||||
// Any previously validator of the same name is overwritten.
|
||||
func Add[F any](coll Collection, name string, validator func(value *F, dflt string) error) {
|
||||
coll[name] = validator
|
||||
}
|
||||
|
||||
// AddSlice adds a Validator to the provided collection of validators that validates a slice of the given type. The default is seperated by seperator.
|
||||
func AddSlice[F any](coll Collection, name string, sep string, validator func(value *F, dflt string) error) {
|
||||
Add(coll, name, func(value *[]F, dflt string) error {
|
||||
// some value is set, so we do not need to set the default!
|
||||
if *value != nil {
|
||||
for i := range *value {
|
||||
if err := validator(&(*value)[i], ""); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// no default provided => set if to an empty slice
|
||||
if dflt == "" {
|
||||
*value = make([]F, 0)
|
||||
return nil
|
||||
}
|
||||
|
||||
// some default provided => iterate over the underlying validator
|
||||
dflts := strings.Split(dflt, sep)
|
||||
*value = make([]F, len(dflts))
|
||||
for i := range *value {
|
||||
if err := validator(&(*value)[i], dflts[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
var (
|
||||
errTyp = reflectx.TypeOf[error]()
|
||||
strTyp = reflectx.TypeOf[string]()
|
||||
)
|
||||
|
||||
// UnknownValidator is an error returned from Validate if a validator does not exist
|
||||
type UnknownValidator string
|
||||
|
||||
func (uv UnknownValidator) Error() string {
|
||||
return fmt.Sprintf("unknown validator %q", string(uv))
|
||||
}
|
||||
|
||||
// NotAValidator is an error returned from Validate if an entry in the validators map is not a validator
|
||||
type NotAValidator string
|
||||
|
||||
func (nv NotAValidator) Error() string {
|
||||
return fmt.Sprintf("entry %q in validators is not a valiator", string(nv))
|
||||
}
|
||||
|
||||
// IncompatibleValidator is returned when a validator in the validators map is incompatible
|
||||
type IncompatibleValidator struct {
|
||||
Validator string
|
||||
GotType reflect.Type
|
||||
ExpectedType reflect.Type
|
||||
}
|
||||
|
||||
func (iv IncompatibleValidator) Error() string {
|
||||
return fmt.Sprintf("validator %q: got type %s, expected type %s", iv.Validator, iv.GotType, iv.ExpectedType)
|
||||
}
|
||||
|
||||
// Call calls the validator with the given name, on the given value, and with the provided default.
|
||||
// See documentation of [Validate] for details.
|
||||
func (coll Collection) Call(name string, field reflect.Value, dflt string) error {
|
||||
validator, ok := coll[name]
|
||||
if !ok {
|
||||
return UnknownValidator(name)
|
||||
}
|
||||
|
||||
// get the type of the validator
|
||||
vFunc := reflect.ValueOf(validator)
|
||||
vTyp := vFunc.Type()
|
||||
|
||||
// ensure that vTyp is of type func(*F,string) error
|
||||
// where T is the type of the field
|
||||
//
|
||||
// - the first if assumes checks for some type F
|
||||
// - the second if checks if the F is the right one
|
||||
if validator == nil || vTyp.Kind() != reflect.Func || // func
|
||||
vTyp.NumIn() != 2 || vTyp.In(0).Kind() != reflect.Pointer || vTyp.In(1) != strTyp || // (*F,string)
|
||||
vTyp.NumOut() != 1 || vTyp.Out(0) != errTyp { // error
|
||||
return NotAValidator(name)
|
||||
}
|
||||
if vTyp.In(0).Elem() != field.Type() { // the correct *F
|
||||
return IncompatibleValidator{
|
||||
Validator: name,
|
||||
GotType: vTyp.In(0).Elem(),
|
||||
ExpectedType: field.Type(),
|
||||
}
|
||||
}
|
||||
|
||||
// call the validator function, and return an error
|
||||
results := vFunc.Call([]reflect.Value{field.Addr(), reflect.ValueOf(dflt)})
|
||||
|
||||
// turn the result into an error
|
||||
// NOTE: We can't just .(error) here because that panic()s on err == nil
|
||||
err := results[0].Interface()
|
||||
if err, ok := err.(error); ok {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue