34 Commits
set ... v0.3.2

Author SHA1 Message Date
Colin Henry
b7969f89b5 fixed release target
All checks were successful
Go / build (1.23) (push) Successful in 17s
2024-10-27 12:28:57 -07:00
Colin Henry
7e1daaf7d5 added makefile to do releases
All checks were successful
Go / build (1.23) (push) Successful in 28s
2024-10-27 12:04:01 -07:00
Colin Henry
40bc496b20 added makefile to do releases 2024-10-27 12:03:34 -07:00
Colin Henry
401f5c3848 WIP: added container/graph
All checks were successful
Go / build (1.23) (push) Successful in 2m8s
2024-10-27 11:58:47 -07:00
Colin Henry
f0fd3f8df1 added come comments
Some checks failed
Go / build (1.23) (push) Has been cancelled
2024-10-27 11:58:07 -07:00
Colin Henry
8de3b04032 fix(ci): updated support matrix
All checks were successful
Go / build (1.23) (push) Successful in 3m3s
2024-09-29 12:02:35 -07:00
Colin Henry
f44e43174c added wrapper error that will provide file/line origin of an error
Some checks failed
Go / build (1.19) (push) Failing after 13s
2024-09-29 11:59:01 -07:00
Colin Henry
fe49018479 added new check capability and added assets to a few other packages
Some checks failed
Go / build (1.19) (push) Failing after 22s
2024-09-28 15:19:00 -07:00
Colin Henry
94bc398ea7 added assert function
Some checks failed
Go / build (1.19) (push) Failing after 4m5s
2024-09-06 20:29:29 -07:00
Colin Henry
2973912fde updated minimum go version 2024-09-06 20:28:23 -07:00
Colin Henry
b738f78e6e reomved now unnecessary libraries 2024-09-06 20:27:26 -07:00
Colin Henry
74f9fc64f4 removed deprecated log package 2024-09-05 23:36:49 -07:00
Colin Henry
612a5c8387 removing deprecated rest package 2024-09-05 22:32:04 -07:00
Colin Henry
a1e52a7399 changing module name 2024-09-05 22:30:30 -07:00
Colin Henry
79e3d1f0b9 fixed busted test step
All checks were successful
Go / build (1.19) (push) Successful in 46s
2024-04-27 15:43:57 -07:00
Colin Henry
8b77b5acc8 added gitea action
Some checks failed
Go / build (1.19) (push) Failing after 3m29s
2024-04-26 23:20:51 -07:00
Colin Henry
4665f2ccf9 deprecating packages 2023-08-11 21:59:16 -07:00
Colin Henry
7154029136 linted 2023-04-05 21:20:45 -07:00
Colin Henry
9f88dc9530 added ISO8601 constant 2023-04-04 23:19:22 -07:00
Colin Henry
1ec6f3c5c3 updated doc 2023-04-04 23:19:05 -07:00
Colin Henry
390a54eb96 updated log interface name 2023-04-04 23:17:39 -07:00
Colin Henry
d312b58fdf updated go.mod for compatability with generics, as used by the cache 2023-01-14 00:12:30 -08:00
Colin Henry
b19ba53491 added appropriate attribution
for tierd cache stub
2023-01-14 00:09:00 -08:00
Colin Henry
859849bd47 stub of tiered cache 2022-12-15 22:00:51 -08:00
Colin Henry
7cf53a2f2d sped up mappedParam by removing joins and splits 2022-09-15 21:28:19 -07:00
Colin Henry
593c8db66e fixed unit tests 2022-09-15 21:21:51 -07:00
Colin Henry
74c99d013c well, maybe a little 2022-09-14 15:43:05 -07:00
Colin Henry
618cdcc095 no mo way 2022-09-14 15:41:43 -07:00
Colin Henry
32337f4bc8 fixed non-standard return code 2022-09-14 15:41:15 -07:00
Colin Henry
888e1b9c7d new functions to extract certain types of parameters 2022-09-14 15:39:41 -07:00
Colin Henry
086c404610 changed parameter identifier to brackets over a colon, is more compatable with OpenAPI 2022-08-20 13:43:39 -07:00
Colin Henry
f9a712bc33 added to the logger interface 2022-08-05 22:04:20 -07:00
Colin Henry
dc5f8b6e5a new ServeMux based on Mat Ryer's Way. removed method support in the mux to allow it to be handled by the handlers themselves. Keeps the standard library net/http handler contract and provides better support for the handlers provided by x's net/http. 2022-07-08 19:32:45 -07:00
Colin Henry
62911f042d update library to build for 1.18 2022-06-28 20:23:08 -07:00
24 changed files with 266 additions and 216 deletions

View File

@@ -0,0 +1,18 @@
name: Go
on: [push]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
go-version: [ '1.23' ]
steps:
- uses: actions/checkout@v4
- name: Setup Go ${{ matrix.go-version }}
uses: actions/setup-go@v4
with:
go-version: ${{ matrix.go-version }}
- name: Build
run: go build -v ./...
- name: Test
run: go test -v ./...

19
Makefile Normal file
View File

@@ -0,0 +1,19 @@
# Variables
REPO_REMOTE := origin
# Helper function to get the latest tag and increment the version
define next_version
latest_tag=$$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0"); \
major=$$(echo $$latest_tag | sed -E 's/^v([0-9]+)\.[0-9]+\.[0-9]+/\1/'); \
minor=$$(echo $$latest_tag | sed -E 's/^v[0-9]+\.([0-9]+)\.[0-9]+/\1/'); \
patch=$$(echo $$latest_tag | sed -E 's/^v[0-9]+\.[0-9]+\.([0-9]+)/\1/'); \
next_patch=$$(($$patch + 1)); \
echo "v$$major.$$minor.$$next_patch"
endef
# Target to tag and push the next version
release:
@next_version=$$($(next_version)); \
echo "Tagging with version $$next_version";
git tag $$next_version; \
git push $(REPO_REMOTE) $$next_version

View File

@@ -7,7 +7,7 @@ A mix of useful packages, some are works in progress.
## Install
```
go get github.com/jchenry/x
go get git.sdf.org/jchenry/x
```
## Contributing

3
cache/doc.go vendored Normal file
View File

@@ -0,0 +1,3 @@
package cache
// Cache package cribbed from https://varunksaini.com/tiered-cache-in-go/ whom i have i high respect for as a former colleague.

6
cache/interface.go vendored Normal file
View File

@@ -0,0 +1,6 @@
package cache
type Interface[K comparable, V any] interface {
Get(key K) V
Put(key K, value V)
}

40
cache/tiered.go vendored Normal file
View File

@@ -0,0 +1,40 @@
package cache
import (
"reflect"
"git.sdf.org/jchenry/x"
)
type tieredCache[K comparable, V any] struct {
inner Interface[K, V]
outer Interface[K, V]
}
func NewTieredCache[K comparable, V any](inner, outer Interface[K, V]) Interface[K, V] {
x.Assert(inner != nil, "cache.NewTieredCache: inner cannot be nil")
x.Assert(outer != nil, "cache.NewTieredCache: outer cannot be nil")
return &tieredCache[K, V]{
inner: inner,
outer: outer,
}
}
func (t *tieredCache[K, V]) Get(key K) V {
var zero, value V
value = t.inner.Get(key)
if reflect.DeepEqual(value, zero) {
value = t.outer.Get(key)
// if required, add value to inner cache for future requests
}
return value
}
func (t *tieredCache[K, V]) Put(key K, value V) {
t.inner.Put(key, value)
// add key to outer cache asynchronously
go func(key K) {
t.outer.Put(key, value)
}(key)
}

41
container/graph/topo.go Normal file
View File

@@ -0,0 +1,41 @@
package sort
// Node is a generic interface representing a graph node.
type Node[T any] interface {
// Value returns the value of the node.
Value() T
// Adjacencies returns a slice of nodes adjacent to this node
Adjacencies() []Node[T]
}
// Topo performs a topological sort on a slice of nodes in place.
func TopoSort[T any](nodes []Node[T]) {
v := make(map[Node[T]]bool)
pos := 0
var dfs func(Node[T])
dfs = func(n Node[T]) {
v[n] = true
for _, e := range n.Adjacencies() {
if !v[e] {
dfs(e)
}
}
nodes[pos] = n
pos++
}
for _, n := range nodes {
if !v[n] {
dfs(n)
}
}
}
// RTopoSort performs a reverse topological sort on a slice of nodes in place.
func RTopoSort[T any](nodes []Node[T]) {
TopoSort(nodes)
for i, j := 0, len(nodes)-1; i < j; i, j = i+1, j-1 {
nodes[i], nodes[j] = nodes[j], nodes[i]
}
}

View File

@@ -3,6 +3,8 @@ package database
import (
"context"
"database/sql"
"git.sdf.org/jchenry/x"
)
type Func func(db *sql.DB)
@@ -13,6 +15,7 @@ type Actor struct {
}
func (a *Actor) Run(ctx context.Context) error {
x.Assert(ctx != nil, "Actor.Run: context cannot be nil")
for {
select {
case f := <-a.ActionChan:

View File

@@ -7,7 +7,7 @@ Example usage:
ctx, _ := context.WithCancel(context.Background())
dba = &db.Actor{
DB: s.db,
ActionChan: make(chan db.Func),
ActionChan: make(chan database.Func),
}
go dba.Run(ctx)

4
go.mod
View File

@@ -1,3 +1,3 @@
module github.com/jchenry/x
module git.sdf.org/jchenry/x
go 1.13
go 1.22

View File

@@ -1,15 +0,0 @@
package log
// Logger is a logging interface with only the essentials that a function that needs to log should care about. Compatible with standard Go logger.
type Logger interface {
Print(v ...interface{})
Printf(format string, v ...interface{})
Println(v ...interface{})
}
// None provides a logger that doesnt log anything
type None struct{}
func (n None) Print(v ...interface{}) {}
func (n None) Printf(format string, v ...interface{}) {}
func (n None) Println(v ...interface{}) {}

View File

@@ -6,9 +6,13 @@ import (
"fmt"
"net/http"
"strings"
"git.sdf.org/jchenry/x"
)
func BasicAuth(h http.Handler, htpasswd map[string]string, realm string) http.HandlerFunc {
x.Assert(len(htpasswd) > 0, "http.BasicAuth: htpassword cannot be empty")
x.Assert(len(realm) > 0, "http.BasicAuth: realm cannot be empty")
rlm := fmt.Sprintf(`Basic realm="%s"`, realm)
sha1 := func(password string) string {
s := sha1.New()
@@ -20,7 +24,7 @@ func BasicAuth(h http.Handler, htpasswd map[string]string, realm string) http.Ha
user, pass, _ := r.BasicAuth()
if pw, ok := htpasswd[user]; !ok || !strings.EqualFold(pass, sha1(pw)) {
w.Header().Set("WWW-Authenticate", rlm)
http.Error(w, "Unauthorized", 401)
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
h.ServeHTTP(w, r)

View File

@@ -30,7 +30,7 @@ func MutliHandler(h map[string]http.Handler) (http.HandlerFunc, error) {
if hdlr, ok := h[r.Method]; ok {
hdlr.ServeHTTP(w, r)
} else {
NotImplementedHandler.ServeHTTP(w, r)
NotAllowedHandler.ServeHTTP(w, r)
}
}, nil
}

View File

@@ -15,7 +15,8 @@ func (s StatusHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
var (
NotFoundHandler = StatusHandler(404)
NotImplementedHandler = StatusHandler(501)
NotLegalHandler = StatusHandler(451)
NotFoundHandler = StatusHandler(http.StatusNotFound)
NotImplementedHandler = StatusHandler(http.StatusNotImplemented)
NotLegalHandler = StatusHandler(http.StatusUnavailableForLegalReasons)
NotAllowedHandler = StatusHandler(http.StatusMethodNotAllowed)
)

96
pkg.go Normal file
View File

@@ -0,0 +1,96 @@
package x
import (
"errors"
"fmt"
"runtime"
)
// Assert evalueates a condition and if it fails, panics.
func Assert(cond bool, msg string) {
if !cond {
panic(errors.New(msg))
}
}
// Check evaluates a condition, adds an error to its list and continues
func Check(cond bool, err error) I {
return new(invariants).Check(cond, err)
}
// func ExampleCheck() {
// }
type I interface {
Check(cond bool, err error) I
Join() error
First() error
All() []error
}
type invariants struct {
errs []error
}
// Check evaluates a condition, adds an error to its list and continues
func (i *invariants) Check(cond bool, err error) I {
if !cond {
i.errs = append(i.errs, err)
}
return i
}
// Join returns all an error wrapping all errors that have been seen by the checks
func (i *invariants) Join() error {
return errors.Join(i.errs...)
}
// First returns the first error found by the checks
func (i *invariants) First() error {
if len(i.errs) > 0 {
return i.errs[0]
}
return nil
}
// All returns all errors found by the checks as a list of errors
func (i *invariants) All() []error {
return i.errs
}
type xError struct {
LineNo int
File string
E error
Debug bool
}
func (e *xError) Error() string {
if e.Debug {
return fmt.Sprintf(
"%s\n\t%s:%d", e.E, e.File, e.LineNo,
)
}
return e.E.Error()
}
func (e *xError) Unwrap() error {
return e.E
}
// NewError return an error that wrapped an error and optionally provides the file and line number the error occured on
func NewError(unwrapped error, debug bool) error {
_, file, line, ok := runtime.Caller(1)
if !ok {
file = "unknown"
line = 0
}
return &xError{
LineNo: line,
File: file,
E: unwrapped,
Debug: debug,
}
}

View File

@@ -1,95 +0,0 @@
package rest
import (
"net/http"
"net/url"
"path/filepath"
"sync"
"github.com/jchenry/x/encoding"
"github.com/jchenry/x/log"
)
// Example: Resource(p, c, JSONEncoder, json.Decode(func()interface{}{return &foo{}}), log.None{})
func Resource(p *sync.Pool, g Gateway, e EntityEncoder, d encoding.Decoder, l log.Logger) http.HandlerFunc {
return restVerbHandler(
GetResource(g, e, l),
PostResource(g, d, p, l),
PutResource(g, e, d, p, l),
DeleteResource(g, l),
)
}
func GetResource(store Readable, encode EntityEncoder, log log.Logger) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { // GET
if id := filepath.Base(r.URL.Path); id != "" {
if e, err := store.Read(id); err == nil { // handle individual entity
encode(w, e)
} else {
w.WriteHeader(http.StatusInternalServerError)
log.Printf("Error: %s", err)
}
} else {
if params, err := url.ParseQuery(r.URL.RawQuery); err == nil {
if e, err := store.All(params); err == nil { // handle all entities
encode(w, e)
} else {
w.WriteHeader(http.StatusInternalServerError)
log.Printf("Error: %s", err)
}
} else {
w.WriteHeader(http.StatusBadRequest)
}
}
}
}
func PostResource(store Creatable, decode encoding.Decoder, pool *sync.Pool, log log.Logger) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { // POST TODO
e := pool.Get()
defer pool.Put(e)
if err := decode(r.Body, e); err == nil {
if err = store.Create(e); err == nil {
w.WriteHeader(http.StatusCreated)
} else {
w.WriteHeader(http.StatusInternalServerError)
log.Printf("Error: %s", err)
}
} else {
w.WriteHeader(http.StatusBadRequest)
}
}
}
func PutResource(store Updatable, encode EntityEncoder, decode encoding.Decoder, pool *sync.Pool, log log.Logger) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { // PUT TODO
e := pool.Get()
defer pool.Put(e)
if err := decode(r.Body, e); err == nil {
if err = store.Update(e); err == nil {
w.WriteHeader(http.StatusAccepted)
encode(w, e)
} else {
w.WriteHeader(http.StatusInternalServerError)
log.Printf("Error: %s", err)
}
} else {
w.WriteHeader(http.StatusBadRequest)
}
}
}
func DeleteResource(store Deletable, log log.Logger) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { // DELETE TODO
if id := filepath.Base(r.URL.Path); id != "" {
if err := store.Delete(id); err == nil {
w.WriteHeader(http.StatusNoContent)
} else {
w.WriteHeader(http.StatusInternalServerError)
log.Printf("Error: %s", err)
}
} else {
w.WriteHeader(http.StatusBadRequest)
}
}
}

View File

@@ -1,23 +0,0 @@
package rest
import (
gohttp "net/http"
"github.com/jchenry/x/net/http"
)
// EntityHandler returns a handler that provides restful verbs, following a CRUD model
func restVerbHandler(
get gohttp.Handler,
post gohttp.Handler,
put gohttp.Handler,
delete gohttp.Handler,
) gohttp.HandlerFunc {
h, _ := http.MutliHandler(map[string]gohttp.Handler{
gohttp.MethodGet: get,
gohttp.MethodPost: post,
gohttp.MethodPut: put,
gohttp.MethodDelete: delete,
})
return h
}

View File

@@ -1,25 +0,0 @@
package rest
type Creatable interface {
Create(e interface{}) error
}
type Updatable interface {
Update(e interface{}) error
}
type Deletable interface {
Delete(id string) error
}
type Readable interface {
All(filters map[string][]string) (interface{}, error)
Read(id string) (interface{}, error)
}
type Gateway interface {
Creatable
Updatable
Deletable
Readable
}

View File

@@ -1,32 +0,0 @@
package rest
import (
"net/http"
"github.com/jchenry/x/encoding"
"github.com/jchenry/x/encoding/json"
"github.com/jchenry/x/encoding/xml"
)
type EntityEncoder func(w http.ResponseWriter, e interface{})
func JSONEncoder(w http.ResponseWriter, e interface{}) error {
return EntityResponseEncoder(w, "application/json", json.Encoder, e)
}
func XMLEncoder(w http.ResponseWriter, e interface{}) error {
return EntityResponseEncoder(w, "application/xml", xml.Encoder, e)
}
func EntityResponseEncoder(w http.ResponseWriter, contentType string, encoder encoding.Encoder, e interface{}) error {
w.Header().Set("content-type", contentType)
return encoder(w, e)
}
func ErrorResponseEncoder(w http.ResponseWriter, contentType string, encoder encoding.Encoder, status int, err error) error {
w.WriteHeader(status)
return EntityResponseEncoder(w, contentType, encoder, map[string]interface{}{
"status": status,
"message": err.Error,
})
}

View File

@@ -1,7 +1,6 @@
package snowflake
import (
"fmt"
"hash/fnv"
"math"
"net"
@@ -15,42 +14,48 @@ const (
nodeIDBits = 10
sequenceBits = 12
// Custom Epoch (January 1, 2015 Midnight UTC = 2015-01-01T00:00:00Z)
// Custom Epoch (January 1, 2015 Midnight UTC = 2015-01-01T00:00:00Z) .
customEpoch int64 = 1420070400000
)
var maxNodeID int64
var maxSequence int64
var (
maxNodeID int64
maxSequence int64
timestampMutex sync.Mutex
sequenceMutex sync.Mutex
nodeID int64
lastTimestamp int64 = 0
sequence int64
)
var nodeID int64
var lastTimestamp int64 = 0
var sequence int64
const two = 2
func init() {
maxNodeID = int64(math.Pow(2, nodeIDBits) - 1)
maxSequence = int64(math.Pow(2, sequenceBits) - 1)
maxNodeID = int64(math.Pow(two, nodeIDBits) - 1)
maxSequence = int64(math.Pow(two, sequenceBits) - 1)
nodeID = generateNodeID()
}
func generateNodeID() int64 {
var nodeID int64
if interfaces, err := net.Interfaces(); err == nil {
h := fnv.New32a()
for _, i := range interfaces {
h.Write(i.HardwareAddr)
}
nodeID = int64(h.Sum32())
} else {
panic("interfaces not available")
}
nodeID = nodeID & maxNodeID
return nodeID
}
var timestampMutex sync.Mutex
var sequenceMutex sync.Mutex
// Next returns the next logical snowflake
// Next returns the next logical snowflake.
func Next() int64 {
timestampMutex.Lock()
currentTimestamp := ts()
@@ -76,7 +81,6 @@ func Next() int64 {
id |= (nodeID << sequenceBits)
id |= sequence
fmt.Printf("%b\n", id)
return id
}
@@ -88,5 +92,6 @@ func waitNextMillis(currentTimestamp int64) int64 {
for currentTimestamp == lastTimestamp {
currentTimestamp = ts()
}
return currentTimestamp
}

View File

@@ -9,7 +9,6 @@ func TestNext(t *testing.T) {
fmt.Printf("node id: %b\n", generateNodeID())
fmt.Printf("timestamp: %b\n", ts())
fmt.Printf("full token: %b\n", Next())
// t.Fail()
}
func BenchmarkNext(b *testing.B) {

View File

@@ -6,7 +6,7 @@ import (
"testing"
"time"
"github.com/jchenry/x/time/arvelie"
"git.sdf.org/jchenry/x/time/arvelie"
)
func TestFromDate(t *testing.T) {

5
time/format.go Normal file
View File

@@ -0,0 +1,5 @@
package time
import "time"
const ISO8601 = time.RFC3339

View File

@@ -6,7 +6,7 @@ import (
"testing"
"time"
"github.com/jchenry/x/time/neralie"
"git.sdf.org/jchenry/x/time/neralie"
)
func TestFromTime(t *testing.T) {