pax_global_header00006660000000000000000000000064127444152600014517gustar00rootroot0000000000000052 comment=0cdb66aea5b843822af6fdffc21286b8fe8379c4 goutils-0cdb66aea5b843822af6fdffc21286b8fe8379c4/000077500000000000000000000000001274441526000204445ustar00rootroot00000000000000goutils-0cdb66aea5b843822af6fdffc21286b8fe8379c4/README.md000066400000000000000000000007641274441526000217320ustar00rootroot00000000000000# Miscellaneous Go utilities This repository contains miscellaneous Go utility packages. Mature packages get migrated to here from my holditall, [degoutils](https://github.com/hlandau/degoutils), or to their own package. Currently, all packages in here depend only on the standard library. In the future, non-standard library dependencies in this repository should be kept minimized, to mitigate the impact on tools, such as vendorization tools, which work on a repository and not a package level. goutils-0cdb66aea5b843822af6fdffc21286b8fe8379c4/clock/000077500000000000000000000000001274441526000215375ustar00rootroot00000000000000goutils-0cdb66aea5b843822af6fdffc21286b8fe8379c4/clock/clock.go000066400000000000000000000067421274441526000231720ustar00rootroot00000000000000package clock import ( "sync" "time" ) type Ticker interface { C() <-chan time.Time Stop() } type Clock interface { Now() time.Time Sleep(time.Duration) After(time.Duration) <-chan time.Time NewTicker(time.Duration) Ticker } var Real Clock func init() { Real = realClock{} } type realClock struct{} func (realClock) Now() time.Time { return time.Now() } func (realClock) Sleep(d time.Duration) { time.Sleep(d) } func (realClock) After(d time.Duration) <-chan time.Time { return time.After(d) } func (realClock) NewTicker(d time.Duration) Ticker { return realTicker{time.NewTicker(d)} } type realTicker struct { *time.Ticker } func (rt realTicker) C() <-chan time.Time { return rt.Ticker.C } type Fake interface { Clock Advance(time.Duration) } // A fast fake clock returns from Sleep calls immediately. // // Any waiting operation appears to complete immediately, as though time is // running infinitely fast, but only when waiting. func NewFast(from Clock) Fake { if from == nil { from = Real } return NewFastAt(from.Now()) } func NewFastAt(t time.Time) Fake { return &fastFake{t: t} } type fastFake struct { t time.Time mutex sync.RWMutex } func (f *fastFake) Now() time.Time { f.mutex.RLock() defer f.mutex.RUnlock() t := f.t return t } func (f *fastFake) Sleep(d time.Duration) { f.mutex.Lock() defer f.mutex.Unlock() f.t = f.t.Add(d) } func (f *fastFake) Advance(d time.Duration) { f.Sleep(d) } func (f *fastFake) After(d time.Duration) <-chan time.Time { f.Sleep(d) c := make(chan time.Time, 1) c <- f.Now() return c } func (f *fastFake) NewTicker(d time.Duration) Ticker { return newFakeTicker(f, d) } // A slow clock doesn't return from Sleep calls until Advance has been called // enough. func NewSlow(from Clock) Fake { if from == nil { from = Real } return NewSlowAt(from.Now()) } func NewSlowAt(t time.Time) Fake { return &slowFake{t: t} } type slowFake struct { t time.Time mutex sync.RWMutex sleepers []*slowSleeper } type slowSleeper struct { until time.Time done chan<- time.Time } func (f *slowFake) Now() time.Time { f.mutex.RLock() defer f.mutex.RUnlock() t := f.t return t } func (f *slowFake) Sleep(d time.Duration) { <-f.After(d) } func (f *slowFake) Advance(d time.Duration) { f.mutex.Lock() defer f.mutex.Unlock() t2 := f.t.Add(d) var newSleepers []*slowSleeper for _, s := range f.sleepers { if t2.Sub(s.until) >= 0 { s.done <- t2 } else { newSleepers = append(newSleepers, s) } } f.sleepers = newSleepers f.t = t2 } func (f *slowFake) After(d time.Duration) <-chan time.Time { f.mutex.Lock() defer f.mutex.Unlock() done := make(chan time.Time, 1) if d == 0 { done <- f.t return done } s := &slowSleeper{ until: f.t.Add(d), done: done, } f.sleepers = append(f.sleepers, s) return done } func (f *slowFake) NewTicker(d time.Duration) Ticker { return newFakeTicker(f, d) } type fakeTicker struct { clock Clock c chan time.Time stopChan chan struct{} } func newFakeTicker(c Clock, d time.Duration) Ticker { ft := &fakeTicker{ clock: c, c: make(chan time.Time, 1), stopChan: make(chan struct{}), } go ft.tickLoop(d) return ft } func (ft *fakeTicker) tickLoop(d time.Duration) { for { ft.clock.Sleep(d) select { case ft.c <- ft.clock.Now(): case <-ft.stopChan: return } } } func (ft *fakeTicker) C() <-chan time.Time { return ft.c } func (ft *fakeTicker) Stop() { close(ft.stopChan) } // © 2015 Jonathan Boulle Apache 2.0 License goutils-0cdb66aea5b843822af6fdffc21286b8fe8379c4/net/000077500000000000000000000000001274441526000212325ustar00rootroot00000000000000goutils-0cdb66aea5b843822af6fdffc21286b8fe8379c4/net/backoff.go000066400000000000000000000050751274441526000231630ustar00rootroot00000000000000package net import "math" import "math/rand" import "time" var randr *rand.Rand func init() { t := time.Now() s := rand.NewSource(t.Unix() ^ t.UnixNano()) randr = rand.New(s) } // Expresses a backoff and retry specification. // // The nil value of this structure results in sensible defaults being used. type Backoff struct { // The maximum number of attempts which may be made. // If this is 0, the number of attempts is unlimited. MaxTries int // The initial delay, in milliseconds. This is the delay used after the first // failed attempt. InitialDelay time.Duration // ms // The maximum delay, in milliseconds. This is the maximum delay between // attempts. MaxDelay time.Duration // ms // Determines when the maximum delay should be reached. If this is 5, the // maximum delay will be reached after 5 attempts have been made. MaxDelayAfterTries int // Positive float expressing the maximum factor by which the delay value may // be randomly inflated. e.g. specify 0.05 for a 5% variation. Set to zero to // disable jitter. Jitter float64 // The current try. You should not need to set this yourself. CurrentTry int } // Initialises any nil field in Backoff with sensible defaults. You // normally do not need to call this method yourself, as it will be called // automatically. func (rc *Backoff) InitDefaults() { if rc.InitialDelay == 0 { rc.InitialDelay = 5 * time.Second } if rc.MaxDelay == 0 { rc.MaxDelay = 120 * time.Second } if rc.MaxDelayAfterTries == 0 { rc.MaxDelayAfterTries = 10 } } // Gets the next delay in milliseconds and increments the internal try counter. func (rc *Backoff) NextDelay() time.Duration { rc.InitDefaults() if rc.MaxTries != 0 && rc.CurrentTry >= rc.MaxTries { return time.Duration(0) } initialDelay := float64(rc.InitialDelay) maxDelay := float64(rc.MaxDelay) maxDelayAfterTries := float64(rc.MaxDelayAfterTries) currentTry := float64(rc.CurrentTry) // [from backoff.c] k := math.Log2(maxDelay/initialDelay) / maxDelayAfterTries d := time.Duration(initialDelay * math.Exp2(currentTry*k)) rc.CurrentTry++ if d > rc.MaxDelay { d = rc.MaxDelay } if rc.Jitter != 0 { f := (randr.Float64() - 0.5) * 2 // random value in range [-1,1) d = time.Duration(float64(d) * (1 + rc.Jitter*f)) } return d } // Sleep for the duration returned by NextDelay(). func (rc *Backoff) Sleep() bool { d := rc.NextDelay() if d != 0 { time.Sleep(d) return true } return false } // Sets the internal try counter to zero; the next delay returned will be // InitialDelay again. func (rc *Backoff) Reset() { rc.CurrentTry = 0 } goutils-0cdb66aea5b843822af6fdffc21286b8fe8379c4/net/backoff_test.go000066400000000000000000000011171274441526000242130ustar00rootroot00000000000000package net_test import "testing" import "github.com/hlandau/goutils/net" import "time" func TestBackoff(t *testing.T) { b := net.Backoff{} eq := func(d time.Duration, ms int) { a := int(d / time.Millisecond) if a != ms { t.Errorf("Backoff #%d: %v should be %v", b.CurrentTry, a, ms) } } eq(b.NextDelay(), 5000) eq(b.NextDelay(), 6870) eq(b.NextDelay(), 9440) eq(b.NextDelay(), 12972) eq(b.NextDelay(), 17826) eq(b.NextDelay(), 24494) eq(b.NextDelay(), 33658) eq(b.NextDelay(), 46250) eq(b.NextDelay(), 63553) eq(b.NextDelay(), 87329) eq(b.NextDelay(), 120000) } goutils-0cdb66aea5b843822af6fdffc21286b8fe8379c4/net/json.go000066400000000000000000000017451274441526000225410ustar00rootroot00000000000000package net import "encoding/base64" import "encoding/json" import "strings" type Base64 []byte func (b *Base64) UnmarshalJSON(data []byte) error { var s string err := json.Unmarshal(data, &s) if err != nil { return err } *b, err = base64.StdEncoding.DecodeString(s) return err } func (b Base64) MarshalJSON() ([]byte, error) { return json.Marshal(base64.StdEncoding.EncodeToString(b)) } type Base64up []byte func (b *Base64up) UnmarshalJSON(data []byte) error { var s string err := json.Unmarshal(data, &s) if err != nil { return err } *b, err = base64.URLEncoding.DecodeString(RepadBase64(s)) return err } func (b Base64up) MarshalJSON() ([]byte, error) { return json.Marshal(strings.TrimRight(base64.URLEncoding.EncodeToString(b), "=")) } func RepadBase64(s string) string { if m := len(s) % 4; m >= 2 { return s + "=="[m-2:] } return s } // untested func RepadBase32(s string) string { if m := len(s) % 8; m >= 6 { return s + "======"[m-6:] } return s } goutils-0cdb66aea5b843822af6fdffc21286b8fe8379c4/net/limitread.go000066400000000000000000000016321274441526000235350ustar00rootroot00000000000000package net import ( "errors" "io" ) type limitReader struct { r io.Reader remaining int } // Error returned if the number of bytes read from a LimitReader exceeds the // limit. var ErrLimitExceeded = errors.New("reader size limit exceeded") func (lr *limitReader) Read(b []byte) (int, error) { n, err := lr.r.Read(b) lr.remaining = lr.remaining - n if lr.remaining < 0 { err = ErrLimitExceeded } return n, err } // Creates a limit reader. This is similar to io.LimitReader, except that if // more than limit bytes are read from the stream, an error is returned rather // than EOF. Thus, accidental truncation of oversized byte streams is avoided // in favour of a hard error. // // Returns ErrLimitExceeded once more than limit bytes are read. The read // operation still occurs. func LimitReader(r io.Reader, limit int) io.Reader { return &limitReader{ r: r, remaining: limit, } } goutils-0cdb66aea5b843822af6fdffc21286b8fe8379c4/os/000077500000000000000000000000001274441526000210655ustar00rootroot00000000000000goutils-0cdb66aea5b843822af6fdffc21286b8fe8379c4/os/doc.go000066400000000000000000000002041274441526000221550ustar00rootroot00000000000000// Package os provides additional OS abstraction functionality for functions // not provided by the Go standard library. package os goutils-0cdb66aea5b843822af6fdffc21286b8fe8379c4/os/error.go000066400000000000000000000004371274441526000225510ustar00rootroot00000000000000package os import "errors" var ErrNotEmpty = errors.New("directory not empty") // Returns true if the error is ErrNotEmpty or the underlying POSIX error code // error is ENOTEMPTY. Currently always returns false on Windows. func IsNotEmpty(err error) bool { return isNotEmpty(err) } goutils-0cdb66aea5b843822af6fdffc21286b8fe8379c4/os/error_unix.go000066400000000000000000000004311274441526000236060ustar00rootroot00000000000000// +build !windows package os import "os" import "syscall" func isNotEmpty(err error) bool { switch pe := err.(type) { default: return false case *os.PathError: err = pe.Err case *os.LinkError: err = pe.Err } return err == syscall.ENOTEMPTY || err == ErrNotEmpty } goutils-0cdb66aea5b843822af6fdffc21286b8fe8379c4/os/error_windows.go000066400000000000000000000001311274441526000243120ustar00rootroot00000000000000// +build windows package os func isNotEmpty(err error) bool { return false // TODO } goutils-0cdb66aea5b843822af6fdffc21286b8fe8379c4/os/exitcode.go000066400000000000000000000012451274441526000232220ustar00rootroot00000000000000package os import ( "fmt" "os/exec" "syscall" ) // Given an error, determines if that error is an os/exec ExitError. If it is, // and the platform is supported, returns the exitCode and nil error. // Otherwise, passes through the error, and exitCode is undefined. func GetExitCode(exitErr error) (exitCode int, err error) { if e, ok := exitErr.(*exec.ExitError); ok { return getExitCodeSys(e.ProcessState.Sys()) } return 0, err } func getExitCodeSys(sys interface{}) (exitCode int, err error) { ws, ok := sys.(syscall.WaitStatus) if !ok { // Should never happen. panic(fmt.Sprintf("unknown system exit code type: %T", sys)) } return ws.ExitStatus(), nil } goutils-0cdb66aea5b843822af6fdffc21286b8fe8379c4/os/fqdn-nunix.go000066400000000000000000000001421274441526000235000ustar00rootroot00000000000000// +build windows plan9 package os func machineFQDN() (string, error) { return os.Hostname() } goutils-0cdb66aea5b843822af6fdffc21286b8fe8379c4/os/fqdn-unix.go000066400000000000000000000012621274441526000233260ustar00rootroot00000000000000// +build !windows,!plan9 package os import ( "bytes" "os" "os/exec" "regexp" "strings" ) var reValidHostname = regexp.MustCompile(`^([0-9a-zA-Z-]+\.)[0-9a-zA-Z-]+$`) func machineFQDN() (string, error) { hn, err := os.Hostname() if err != nil { return "", err } hn = strings.TrimSuffix(hn, ".") n := strings.Count(hn, ".") if n > 0 { return hn, nil } buf := bytes.Buffer{} cmd := exec.Command("hostname", "-f") cmd.Stdout = &buf err = cmd.Run() if err != nil { return hn, err } fqdn := strings.TrimSuffix(strings.TrimSpace(buf.String()), ".") if !reValidHostname.MatchString(fqdn) || strings.Count(fqdn, ".") < n { return hn, nil } return fqdn, nil } goutils-0cdb66aea5b843822af6fdffc21286b8fe8379c4/os/fqdn.go000066400000000000000000000002131274441526000223400ustar00rootroot00000000000000package os // Returns the machine hostname as a fully-qualified domain name. func MachineFQDN() (string, error) { return machineFQDN() } goutils-0cdb66aea5b843822af6fdffc21286b8fe8379c4/os/nosymlink-nunix.go000066400000000000000000000005511274441526000245770ustar00rootroot00000000000000// +build !linux,!openbsd,!freebsd,!netbsd,!dragonfly,!solaris,!darwin package os import ( "errors" "os" ) var errNoSymlinksNotSupported = errors.New("opening files without following symlinks is not supported on this platform") func openFileNoSymlinks(path string, flags int, mode os.FileMode) (*os.File, error) { return nil, errNoSymlinksNotSupported } goutils-0cdb66aea5b843822af6fdffc21286b8fe8379c4/os/nosymlink-unix.go000066400000000000000000000003741274441526000244240ustar00rootroot00000000000000// +build linux freebsd openbsd netbsd dragonfly solaris darwin package os import ( "os" "syscall" ) func openFileNoSymlinks(path string, flags int, mode os.FileMode) (*os.File, error) { return os.OpenFile(path, flags|syscall.O_NOFOLLOW, mode) } goutils-0cdb66aea5b843822af6fdffc21286b8fe8379c4/os/nosymlink.go000066400000000000000000000005211274441526000234350ustar00rootroot00000000000000package os import "os" // Opens a file but does not follow symlinks. func OpenFileNoSymlinks(path string, flags int, mode os.FileMode) (*os.File, error) { return openFileNoSymlinks(path, flags, mode) } // See OpenFileNoSymlinks. func OpenNoSymlinks(path string) (*os.File, error) { return OpenFileNoSymlinks(path, os.O_RDONLY, 0) } goutils-0cdb66aea5b843822af6fdffc21286b8fe8379c4/os/remove.go000066400000000000000000000012141274441526000227070ustar00rootroot00000000000000package os import gos "os" import "path/filepath" // Delete a tree of empty directories. Returns non-nil error if // an empty directory cannot be deleted. Does not return an error // if there are non-empty directories. This function can be used // to prune empty directories from a tree. func RemoveEmpty(path string) error { var dirs []string return filepath.Walk(path, func(path string, info gos.FileInfo, err error) error { if info.Mode().IsDir() { dirs = append(dirs, path) } return nil }) for i := len(dirs) - 1; i >= 0; i-- { err := gos.Remove(dirs[i]) if err != nil && !IsNotEmpty(err) { return err } } return nil } goutils-0cdb66aea5b843822af6fdffc21286b8fe8379c4/os/uid.go000066400000000000000000000007751274441526000222060ustar00rootroot00000000000000// +build !windows package os import ( "fmt" "os" "syscall" ) // Returns the UID for a file. func GetFileUID(fi os.FileInfo) (int, error) { st, ok := fi.Sys().(*syscall.Stat_t) if !ok { return 0, fmt.Errorf("unknown file info type: %T", fi.Sys()) } return int(st.Uid), nil } // Returns the GID for a file. func GetFileGID(fi os.FileInfo) (int, error) { st, ok := fi.Sys().(*syscall.Stat_t) if !ok { return 0, fmt.Errorf("unknown file info type: %T", fi.Sys()) } return int(st.Gid), nil } goutils-0cdb66aea5b843822af6fdffc21286b8fe8379c4/test/000077500000000000000000000000001274441526000214235ustar00rootroot00000000000000goutils-0cdb66aea5b843822af6fdffc21286b8fe8379c4/test/httpmock.go000066400000000000000000000025221274441526000236040ustar00rootroot00000000000000package test import ( "bytes" "io/ioutil" "net/http" "net/http/httptest" ) type httpMockResponse struct { http.Response Data []byte } func (r *httpMockResponse) ServeHTTP(http.ResponseWriter, *http.Request) { } type HTTPMockTransport struct { mux *http.ServeMux } func (t *HTTPMockTransport) Clear() { t.mux = http.NewServeMux() } func (t *HTTPMockTransport) Add(path string, res *http.Response, data []byte) { if t.mux == nil { t.mux = http.NewServeMux() } t.mux.Handle(path, &httpMockResponse{ Response: *res, Data: data, }) } func (t *HTTPMockTransport) AddHandlerFunc(path string, hf http.HandlerFunc) { if t.mux == nil { t.mux = http.NewServeMux() } t.mux.HandleFunc(path, hf) } func (t *HTTPMockTransport) RoundTrip(req *http.Request) (*http.Response, error) { h, ptn := t.mux.Handler(req) var res *httpMockResponse if ptn == "" { res = &httpMockResponse{ Response: http.Response{ StatusCode: 404, }, } } else { var ok bool res, ok = h.(*httpMockResponse) if !ok { rw := httptest.NewRecorder() h.ServeHTTP(rw, req) res := &http.Response{ StatusCode: rw.Code, Header: rw.HeaderMap, Body: ioutil.NopCloser(bytes.NewReader(rw.Body.Bytes())), } return res, nil } } res.Response.Body = ioutil.NopCloser(bytes.NewReader(res.Data)) return &res.Response, nil } goutils-0cdb66aea5b843822af6fdffc21286b8fe8379c4/text/000077500000000000000000000000001274441526000214305ustar00rootroot00000000000000goutils-0cdb66aea5b843822af6fdffc21286b8fe8379c4/text/parsebool.go000066400000000000000000000015101274441526000237420ustar00rootroot00000000000000package text import ( "regexp" "strings" ) var re_noForgiving = regexp.MustCompile(`^\s*(00*|[nN][oO]?|[fF]([aA][lL][sS][eE])?|)\s*$`) func ParseBoolForgiving(s string) (value, ok bool) { return !re_noForgiving.MatchString(s), true } func ParseBoolUser(s string) (value, ok bool) { return parseBoolUser(s, nil) } var ( yes = true no = false ) func ParseBoolUserDefaultYes(s string) (value, ok bool) { return parseBoolUser(s, &yes) } func ParseBoolUserDefaultNo(s string) (value, ok bool) { return parseBoolUser(s, &no) } func parseBoolUser(s string, dflt *bool) (value, ok bool) { s = strings.ToLower(strings.TrimSpace(s)) if s == "y" || s == "yes" { return true, true } else if s == "n" || s == "no" { return false, true } else if s == "" && dflt != nil { return *dflt, true } else { return false, false } }