Compare commits
46 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
b7969f89b5 | ||
|
7e1daaf7d5 | ||
|
40bc496b20 | ||
|
401f5c3848 | ||
|
f0fd3f8df1 | ||
|
8de3b04032 | ||
|
f44e43174c | ||
|
fe49018479 | ||
|
94bc398ea7 | ||
|
2973912fde | ||
|
b738f78e6e | ||
|
74f9fc64f4 | ||
|
612a5c8387 | ||
|
a1e52a7399 | ||
|
79e3d1f0b9 | ||
|
8b77b5acc8 | ||
|
4665f2ccf9 | ||
|
7154029136 | ||
|
9f88dc9530 | ||
|
1ec6f3c5c3 | ||
|
390a54eb96 | ||
|
d312b58fdf | ||
|
b19ba53491 | ||
|
859849bd47 | ||
|
7cf53a2f2d | ||
|
593c8db66e | ||
|
74c99d013c | ||
|
618cdcc095 | ||
|
32337f4bc8 | ||
|
888e1b9c7d | ||
|
086c404610 | ||
|
f9a712bc33 | ||
|
dc5f8b6e5a | ||
|
62911f042d | ||
|
31cb99467c | ||
|
8a1403a35d | ||
|
07db4cf999 | ||
|
4b8fd0b78d | ||
|
ad5ed58296 | ||
|
d4a7732535 | ||
|
4fe5a53450 | ||
|
ed136ca83e | ||
|
284c8f8359 | ||
|
15ef3556ba | ||
|
85df14e0c6 | ||
|
37d0d5e98d |
10
.drone.yml
Normal file
10
.drone.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: default
|
||||
|
||||
steps:
|
||||
- name: test
|
||||
image: golang
|
||||
commands:
|
||||
- go test ./...
|
||||
- go build ./...
|
18
.gitea/workflows/build.yaml
Normal file
18
.gitea/workflows/build.yaml
Normal 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
19
Makefile
Normal 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
|
@@ -2,10 +2,12 @@
|
||||
|
||||
A mix of useful packages, some are works in progress.
|
||||
|
||||
[](https://ci.j5y.xyz/jchenry/x)
|
||||
|
||||
## Install
|
||||
|
||||
```
|
||||
go get github.com/jchenry/x
|
||||
go get git.sdf.org/jchenry/x
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
3
cache/doc.go
vendored
Normal file
3
cache/doc.go
vendored
Normal 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
6
cache/interface.go
vendored
Normal 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
40
cache/tiered.go
vendored
Normal 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
41
container/graph/topo.go
Normal 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]
|
||||
}
|
||||
}
|
@@ -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:
|
||||
|
@@ -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)
|
||||
|
@@ -12,12 +12,3 @@ func Encoder(w io.Writer, e interface{}) error {
|
||||
func Decoder(r io.Reader, e interface{}) error {
|
||||
return json.NewDecoder(r).Decode(e)
|
||||
}
|
||||
|
||||
// func Decoder(get func() interface{}) func(io.Reader) (interface{}, error) {
|
||||
// //TODO I dont like the get() function, find a better way of dealing with this
|
||||
// return func(r io.Reader) (interface{}, error) {
|
||||
// e := get()
|
||||
// err := json.NewDecoder(r).Decode(e)
|
||||
// return e, err
|
||||
// }
|
||||
// }
|
||||
|
@@ -12,13 +12,3 @@ func Encoder(w io.Writer, e interface{}) error {
|
||||
func Decoder(r io.Reader, e interface{}) error {
|
||||
return xml.NewDecoder(r).Decode(e)
|
||||
}
|
||||
|
||||
// func Decoder(get func() interface{}) func(io.Reader) (interface{}, error) {
|
||||
// //TODO I dont like the get() function, find a better way of dealing with this
|
||||
// return func(r io.Reader) (interface{}, error) {
|
||||
// e := get()
|
||||
// err := xml.NewDecoder(r).Decode(e)
|
||||
// return e, err
|
||||
// }
|
||||
// }
|
||||
//
|
||||
|
4
go.mod
4
go.mod
@@ -1,3 +1,3 @@
|
||||
module github.com/jchenry/x
|
||||
module git.sdf.org/jchenry/x
|
||||
|
||||
go 1.13
|
||||
go 1.22
|
||||
|
136
go.sum
136
go.sum
@@ -1,136 +0,0 @@
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
fyne.io/fyne v1.3.0 h1:FLlgX/JkD3Chal7tEhRL7fOONVAjQJM/yrVNA+cK/dc=
|
||||
fyne.io/fyne v1.3.0/go.mod h1:AcBUeR8hetITnnfaLvuVqioWM/lT18WPeMVAobhMbg8=
|
||||
github.com/Kodeworks/golang-image-ico v0.0.0-20141118225523-73f0f4cfade9/go.mod h1:7uhhqiBaR4CpN0k9rMjOtjpcfGd6DG2m04zQxKnWQ0I=
|
||||
github.com/PuerkitoBio/rehttp v0.0.0-20180310210549-11cf6ea5d3e9 h1:VE0eMvNSQI72dADsq4gm5KpNPmt97WgqneTfaS5MWrs=
|
||||
github.com/PuerkitoBio/rehttp v0.0.0-20180310210549-11cf6ea5d3e9/go.mod h1:ItsOiHl4XeMOV3rzbZqQRjLc3QQxbE6391/9iNG7rE8=
|
||||
github.com/PuloV/ics-golang v0.0.0-20190808201353-a3394d3bcade h1:odEkSCl2gLWPtvraEdCyBZbeYyMMTysWPLMurnB8sUY=
|
||||
github.com/PuloV/ics-golang v0.0.0-20190808201353-a3394d3bcade/go.mod h1:f1P3hjG+t54/IrnXMnnw+gRmFCDR/ryj9xSQ7MPMkQw=
|
||||
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
|
||||
github.com/channelmeter/iso8601duration v0.0.0-20150204201828-8da3af7a2a61 h1:o64h9XF42kVEUuhuer2ehqrlX8rZmvQSU0+Vpj1rF6Q=
|
||||
github.com/channelmeter/iso8601duration v0.0.0-20150204201828-8da3af7a2a61/go.mod h1:Rp8e0DCtEKwXFOC6JPJQVTz8tuGoGvw6Xfexggh/ed0=
|
||||
github.com/codegangsta/negroni v1.0.0 h1:+aYywywx4bnKXWvoWtRfJ91vC59NbEhEY03sZjQhbVY=
|
||||
github.com/codegangsta/negroni v1.0.0/go.mod h1:v0y3T5G7Y1UlFfyxFn/QLRU4a2EuNau2iZY63YTKWo0=
|
||||
github.com/coreos/go-oidc v2.1.0+incompatible h1:sdJrfw8akMnCuUlaZU3tE/uYXFgfqom8DBE9so9EBsM=
|
||||
github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
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/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fyne-io/mobile v0.0.2 h1:eGmCR5lkFxk0PnPafGppLFRD5QODJfSVdrjhLjanOVg=
|
||||
github.com/fyne-io/mobile v0.0.2/go.mod h1:/kOrWrZB6sasLbEy2JIvr4arEzQTXBTZGb3Y96yWbHY=
|
||||
github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 h1:SCYMcCJ89LjRGwEa0tRluNRiMjZHalQZrVrvTbPh+qw=
|
||||
github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4 h1:WtGNWLvXpe6ZudgnXrq0barxBImvnnJoMEhXAzcbM0I=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/godbus/dbus/v5 v5.0.3 h1:ZqHaoEF7TBzh4jzPmqVhE/5A1z9of6orkAe5uHoAeME=
|
||||
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff h1:W71vTCKoxtdXgnm1ECDFkfQnpdqAO00zzGXLA5yaEX8=
|
||||
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw=
|
||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
|
||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||
github.com/gorilla/sessions v1.2.0 h1:S7P+1Hm5V/AT9cjEcUD5uDaQSX0OE577aCXgoaKpYbQ=
|
||||
github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||
github.com/jackmordaunt/icns v0.0.0-20181231085925-4f16af745526/go.mod h1:UQkeMHVoNcyXYq9otUupF7/h/2tmHlhrS2zw7ZVvUqc=
|
||||
github.com/jchenry/jchenry v0.0.0-20200615172632-cb0bc37e6b16 h1:vVomaWqIbI/Vyb6uGE8ANmF8V3ktoLiOXdcKQLvwUc4=
|
||||
github.com/jchenry/jchenry v0.0.0-20200615172632-cb0bc37e6b16/go.mod h1:WLNY6BKAzrUIfnkPA8WCUxkKchKZss4fRSVmbKZuhMg=
|
||||
github.com/josephspurrier/goversioninfo v0.0.0-20200309025242-14b0ab84c6ca/go.mod h1:eJTEwMjXb7kZ633hO3Ln9mBUCOjX2+FlTljvpl9SYdE=
|
||||
github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lucor/goinfo v0.0.0-20200401173949-526b5363a13a/go.mod h1:ORP3/rB5IsulLEBwQZCJyyV6niqmI7P4EWSmkug+1Ng=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
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/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAmPf4YLrFg6oQUotqHQeUNWwkvo7jZp1GLU=
|
||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
|
||||
github.com/rsc/rsc v0.0.0-20180427141835-fc6202590229 h1:s5M0EEh5JyTx0PrhLGlog+CegHIkmiCd07ht20coRtA=
|
||||
github.com/rsc/rsc v0.0.0-20180427141835-fc6202590229/go.mod h1:TJRSe/n0/H37q9TsEwBtcOz32UX+UWqgapLwsXTV4jE=
|
||||
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
|
||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 h1:HunZiaEKNGVdhTRQOVpMmj5MQnGnv+e8uZNu3xFLgyM=
|
||||
github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564/go.mod h1:afMbS0qvv1m5tfENCwnOdZGOF8RGR/FsZ7bvBxQGZG4=
|
||||
github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9 h1:m59mIOBO4kfcNCEzJNy71UkeF4XIx2EVmL9KLwDQdmM=
|
||||
github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9/go.mod h1:mvWM0+15UqyrFKqdRjY6LuAVJR0HOVhJlEgZ5JWtSWU=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stripe/stripe-go v63.4.0+incompatible h1:zzZR004GZ/si7nyckn4NBhoQOViUu5VJ/sA7NT7oTSs=
|
||||
github.com/stripe/stripe-go v63.4.0+incompatible/go.mod h1:A1dQZmO/QypXmsL0T8axYZkSN/uA/T/A64pfKdBAMiY=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190927123631-a832865fa7ad h1:5E5raQxcv+6CZ11RrBYQe5WRbUIWpScjh0kvHZkZIrQ=
|
||||
golang.org/x/crypto v0.0.0-20190927123631-a832865fa7ad/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20200430140353-33d19683fad8 h1:6WW6V3x1P/jokJBpRQYUJnMHRP6isStQwCozxnU7XQw=
|
||||
golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e h1:bRhVy7zSSasaqNksaRZiA5EEI+Ei4I1nO5Jh72wfHlg=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200327173247-9dae0f8f5775 h1:TC0v2RSO1u2kn1ZugjrFXkRZAEaqMN/RW+OTZkBzmLE=
|
||||
golang.org/x/sys v0.0.0-20200327173247-9dae0f8f5775/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190808195139-e713427fea3f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200328031815-3db5fc6bac03/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
gopkg.in/auth0.v1 v1.2.7 h1:9UCE5rKFL60rqQENmmJaGdNu7/aby8r8wVcJ83Vj5oU=
|
||||
gopkg.in/auth0.v1 v1.2.7/go.mod h1:1FRtMXwYDgygZcO7Of7kj/I4mf9UjHGhMHUOqNT0d0M=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4=
|
||||
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
rsc.io/dbstore v0.1.1 h1:LI4gBJUwbejn0wHJWe0KTwgCM33zUVP3BsNz5y2fkEE=
|
||||
rsc.io/dbstore v0.1.1/go.mod h1:zI7k1PCSLg9r/T2rBM4E/SctbGmqdtt3kjQSemVh1Rs=
|
||||
rsc.io/rsc v0.0.0-20180427141835-fc6202590229 h1:6s5zUknxnRp4D3GlNb7uDzlcfFVq9G2ficO+k4Bcb6w=
|
||||
rsc.io/rsc v0.0.0-20180427141835-fc6202590229/go.mod h1:nHU4RAWoD9u1Hr+vTW0mktVbANmwCPkTwT2xNpVs/70=
|
||||
rsc.io/sqlite v0.5.0 h1:HG63YxeP0eALjqorwnJ9ENxUUOUR6NYJ4FHEKFJ7aVk=
|
||||
rsc.io/sqlite v0.5.0/go.mod h1:fqHuveM9iIqMzjD0WiZIvKYMty/WqTo2bxE9+zC54WE=
|
||||
|
@@ -1,19 +1,32 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"git.sdf.org/jchenry/x"
|
||||
)
|
||||
|
||||
func BasicAuth(h http.Handler) http.HandlerFunc {
|
||||
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()
|
||||
_, _ = s.Write([]byte(password))
|
||||
passwordSum := []byte(s.Sum(nil))
|
||||
return base64.StdEncoding.EncodeToString(passwordSum)
|
||||
}
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
user, pass, _ := r.BasicAuth()
|
||||
if !(user == os.Getenv("WIKI_USERNAME") && pass == os.Getenv("WIKI_PASSWORD")) {
|
||||
w.Header().Set("WWW-Authenticate", `Basic realm="wiki"`)
|
||||
http.Error(w, "Unauthorized.", 401)
|
||||
if pw, ok := htpasswd[user]; !ok || !strings.EqualFold(pass, sha1(pw)) {
|
||||
w.Header().Set("WWW-Authenticate", rlm)
|
||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
h.ServeHTTP(w, r)
|
||||
}
|
||||
}
|
||||
|
@@ -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 {
|
||||
NotFoundHandler.ServeHTTP(w, r)
|
||||
NotAllowedHandler.ServeHTTP(w, r)
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
@@ -11,11 +11,12 @@ type StatusHandler int
|
||||
func (s StatusHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
code := int(s)
|
||||
w.WriteHeader(code)
|
||||
io.WriteString(w, http.StatusText(code))
|
||||
_, _ = io.WriteString(w, http.StatusText(code))
|
||||
}
|
||||
|
||||
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
96
pkg.go
Normal 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,
|
||||
}
|
||||
}
|
@@ -1,107 +0,0 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/jchenry/x/encoding"
|
||||
)
|
||||
|
||||
type CollectionStore interface {
|
||||
All(params url.Values) (interface{}, error)
|
||||
Get(id string) (interface{}, error)
|
||||
Delete(id string) error
|
||||
Update(e interface{}) error
|
||||
New(e interface{}) error
|
||||
}
|
||||
|
||||
// Example: Collection(p, c, JSONEncoder, json.Decode(func()interface{}{return &foo{}}))
|
||||
|
||||
// type Decoder func(io.Reader) (interface{}, error)
|
||||
|
||||
func Collection(pool *sync.Pool, store CollectionStore, encode EntityEncoder, decode encoding.Decoder) http.HandlerFunc {
|
||||
return EntityHandler(
|
||||
collectionGet(store, encode),
|
||||
collectionPost(store, encode, decode, pool),
|
||||
collectionPut(store, encode, decode, pool),
|
||||
collectionDelete(store, encode),
|
||||
)
|
||||
}
|
||||
|
||||
func collectionGet(store CollectionStore, encode EntityEncoder) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) { // GET
|
||||
if id := filepath.Base(r.URL.Path); id != "" {
|
||||
if e, err := store.Get(id); err == nil { // handle individual entity
|
||||
encode(w, e)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
encode(w, 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 {
|
||||
// TODO: we really should write a header here, but need to figure out what it should be
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
} else {
|
||||
// encode(w, err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func collectionPost(store CollectionStore, encode EntityEncoder, decode encoding.Decoder, pool *sync.Pool) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) { // POST TODO
|
||||
e := pool.New()
|
||||
defer pool.Put(e)
|
||||
if err := decode(r.Body, e); err == nil {
|
||||
if err = store.New(e); err == nil {
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
} else {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func collectionPut(store CollectionStore, encode EntityEncoder, decode encoding.Decoder, pool *sync.Pool) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) { // PUT TODO
|
||||
e := pool.New()
|
||||
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)
|
||||
encode(w, err)
|
||||
}
|
||||
} else {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
encode(w, err)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func collectionDelete(store CollectionStore, encode EntityEncoder) 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)
|
||||
}
|
||||
} else {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
}
|
||||
}
|
||||
}
|
@@ -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 EntityHandler(
|
||||
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
|
||||
}
|
@@ -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,
|
||||
})
|
||||
}
|
@@ -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
|
||||
}
|
||||
|
@@ -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) {
|
||||
|
@@ -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
5
time/format.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package time
|
||||
|
||||
import "time"
|
||||
|
||||
const ISO8601 = time.RFC3339
|
@@ -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) {
|
||||
|
Reference in New Issue
Block a user