Compare commits

...

17 Commits

Author SHA1 Message Date
be4de07eb8 v0.0.77 2023-02-03 00:59:54 +01:00
36ed474bfe v0.0.76 2023-01-31 23:46:35 +01:00
fdc590c8c3 v0.0.75 2023-01-31 22:41:12 +01:00
1990e5d32d v0.0.74 2023-01-31 11:01:45 +01:00
72883cf6bd v0.0.73 2023-01-31 10:56:30 +01:00
ff08d5f180 v0.0.72 2023-01-30 19:55:55 +01:00
72d6b538f7 v0.0.71 2023-01-29 22:28:08 +01:00
48dd30fb94 v0.0.70 2023-01-29 22:07:28 +01:00
b7c5756f11 v0.0.69 2023-01-29 22:00:40 +01:00
2070a432a5 v0.0.68 2023-01-29 21:27:55 +01:00
34e6d1819d v0.0.67 2023-01-29 20:42:02 +01:00
87fa6021e4 v0.0.66 2023-01-29 06:02:58 +01:00
297d6c52a8 v0.0.65 2023-01-29 05:45:29 +01:00
b9c46947d2 v0.0.64 2023-01-29 01:10:14 +01:00
412277b3e0 v0.0.63 2023-01-28 22:29:45 +01:00
e46f8019ec v0.0.62 2023-01-28 22:29:21 +01:00
ae952b2166 v0.0.61 2023-01-28 22:28:20 +01:00
22 changed files with 1943 additions and 125 deletions

View File

@@ -1,6 +1,7 @@
<component name="InspectionProjectProfileManager"> <component name="InspectionProjectProfileManager">
<profile version="1.0"> <profile version="1.0">
<option name="myName" value="Project Default" /> <option name="myName" value="Project Default" />
<inspection_tool class="GoMixedReceiverTypes" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="LanguageDetectionInspection" enabled="false" level="WARNING" enabled_by_default="false" /> <inspection_tool class="LanguageDetectionInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false"> <inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
<option name="processCode" value="true" /> <option name="processCode" value="true" />

73
cmdext/builder.go Normal file
View File

@@ -0,0 +1,73 @@
package cmdext
import (
"fmt"
"time"
)
type CommandRunner struct {
program string
args []string
timeout *time.Duration
env []string
listener []CommandListener
}
func Runner(program string) *CommandRunner {
return &CommandRunner{
program: program,
args: make([]string, 0),
timeout: nil,
env: make([]string, 0),
listener: make([]CommandListener, 0),
}
}
func (r *CommandRunner) Arg(arg string) *CommandRunner {
r.args = append(r.args, arg)
return r
}
func (r *CommandRunner) Args(arg []string) *CommandRunner {
r.args = append(r.args, arg...)
return r
}
func (r *CommandRunner) Timeout(timeout time.Duration) *CommandRunner {
r.timeout = &timeout
return r
}
func (r *CommandRunner) Env(key, value string) *CommandRunner {
r.env = append(r.env, fmt.Sprintf("%s=%s", key, value))
return r
}
func (r *CommandRunner) RawEnv(env string) *CommandRunner {
r.env = append(r.env, env)
return r
}
func (r *CommandRunner) Envs(env []string) *CommandRunner {
r.env = append(r.env, env...)
return r
}
func (r *CommandRunner) Listen(lstr CommandListener) *CommandRunner {
r.listener = append(r.listener, lstr)
return r
}
func (r *CommandRunner) ListenStdout(lstr func(string)) *CommandRunner {
r.listener = append(r.listener, genericCommandListener{_readStdoutLine: &lstr})
return r
}
func (r *CommandRunner) ListenStderr(lstr func(string)) *CommandRunner {
r.listener = append(r.listener, genericCommandListener{_readStderrLine: &lstr})
return r
}
func (r *CommandRunner) Run() (CommandResult, error) {
return run(*r)
}

View File

@@ -1,7 +1,8 @@
package cmdext package cmdext
import ( import (
"bufio" "gogs.mikescher.com/BlackForestBytes/goext/mathext"
"gogs.mikescher.com/BlackForestBytes/goext/syncext"
"os/exec" "os/exec"
"time" "time"
) )
@@ -14,9 +15,10 @@ type CommandResult struct {
CommandTimedOut bool CommandTimedOut bool
} }
func RunCommand(program string, args []string, timeout *time.Duration) (CommandResult, error) { func run(opt CommandRunner) (CommandResult, error) {
cmd := exec.Command(opt.program, opt.args...)
cmd := exec.Command(program, args...) cmd.Env = append(cmd.Env, opt.env...)
stdoutPipe, err := cmd.StdoutPipe() stdoutPipe, err := cmd.StdoutPipe()
if err != nil { if err != nil {
@@ -28,115 +30,100 @@ func RunCommand(program string, args []string, timeout *time.Duration) (CommandR
return CommandResult{}, err return CommandResult{}, err
} }
preader := pipeReader{
stdout: stdoutPipe,
stderr: stderrPipe,
}
err = cmd.Start() err = cmd.Start()
if err != nil { if err != nil {
return CommandResult{}, err return CommandResult{}, err
} }
errch := make(chan error, 1) type resultObj struct {
go func() { errch <- cmd.Wait() }() stdout string
stderr string
stdcombined string
err error
}
combch := make(chan string, 32) outputChan := make(chan resultObj)
stopCombch := make(chan bool)
stdout := ""
go func() { go func() {
scanner := bufio.NewScanner(stdoutPipe) // we need to first fully read the pipes and then call Wait
for scanner.Scan() { // see https://pkg.go.dev/os/exec#Cmd.StdoutPipe
txt := scanner.Text()
stdout += txt stdout, stderr, stdcombined, err := preader.Read(opt.listener)
combch <- txt if err != nil {
outputChan <- resultObj{stdout, stderr, stdcombined, err}
} }
}()
stderr := "" err = cmd.Wait()
go func() { if err != nil {
scanner := bufio.NewScanner(stderrPipe) outputChan <- resultObj{stdout, stderr, stdcombined, err}
for scanner.Scan() {
txt := scanner.Text()
stderr += txt
combch <- txt
} }
outputChan <- resultObj{stdout, stderr, stdcombined, nil}
}() }()
defer func() { var timeoutChan <-chan time.Time = make(chan time.Time, 1)
stopCombch <- true if opt.timeout != nil {
}() timeoutChan = time.After(*opt.timeout)
}
stdcombined := "" select {
go func() {
for { case <-timeoutChan:
select { _ = cmd.Process.Kill()
case txt := <-combch: for _, lstr := range opt.listener {
stdcombined += txt lstr.Timeout()
case <-stopCombch:
return
}
} }
}()
if timeout != nil { if fallback, ok := syncext.ReadChannelWithTimeout(outputChan, mathext.Min(32*time.Millisecond, *opt.timeout)); ok {
// most of the time the cmd.Process.Kill() should also ahve finished the pipereader
select { // and we can at least return the already collected stdout, stderr, etc
case <-time.After(*timeout):
_ = cmd.Process.Kill()
return CommandResult{ return CommandResult{
StdOut: stdout, StdOut: fallback.stdout,
StdErr: stderr, StdErr: fallback.stderr,
StdCombined: stdcombined, StdCombined: fallback.stdcombined,
ExitCode: -1,
CommandTimedOut: true,
}, nil
} else {
return CommandResult{
StdOut: "",
StdErr: "",
StdCombined: "",
ExitCode: -1, ExitCode: -1,
CommandTimedOut: true, CommandTimedOut: true,
}, nil }, nil
case err := <-errch:
if exiterr, ok := err.(*exec.ExitError); ok {
return CommandResult{
StdOut: stdout,
StdErr: stderr,
StdCombined: stdcombined,
ExitCode: exiterr.ExitCode(),
CommandTimedOut: false,
}, nil
} else if err != nil {
return CommandResult{}, err
} else {
return CommandResult{
StdOut: stdout,
StdErr: stderr,
StdCombined: stdcombined,
ExitCode: 0,
CommandTimedOut: false,
}, nil
}
} }
} else { case outobj := <-outputChan:
if exiterr, ok := outobj.err.(*exec.ExitError); ok {
select { excode := exiterr.ExitCode()
for _, lstr := range opt.listener {
case err := <-errch: lstr.Finished(excode)
if exiterr, ok := err.(*exec.ExitError); ok {
return CommandResult{
StdOut: stdout,
StdErr: stderr,
StdCombined: stdcombined,
ExitCode: exiterr.ExitCode(),
CommandTimedOut: false,
}, nil
} else if err != nil {
return CommandResult{}, err
} else {
return CommandResult{
StdOut: stdout,
StdErr: stderr,
StdCombined: stdcombined,
ExitCode: 0,
CommandTimedOut: false,
}, nil
} }
return CommandResult{
StdOut: outobj.stdout,
StdErr: outobj.stderr,
StdCombined: outobj.stdcombined,
ExitCode: excode,
CommandTimedOut: false,
}, nil
} else if err != nil {
return CommandResult{}, err
} else {
for _, lstr := range opt.listener {
lstr.Finished(0)
}
return CommandResult{
StdOut: outobj.stdout,
StdErr: outobj.stderr,
StdCombined: outobj.stdcombined,
ExitCode: 0,
CommandTimedOut: false,
}, nil
} }
} }
} }

226
cmdext/cmdrunner_test.go Normal file
View File

@@ -0,0 +1,226 @@
package cmdext
import (
"fmt"
"testing"
"time"
)
func TestStdout(t *testing.T) {
res1, err := Runner("printf").Arg("hello").Run()
if err != nil {
t.Errorf("%v", err)
}
if res1.StdErr != "" {
t.Errorf("res1.StdErr == '%v'", res1.StdErr)
}
if res1.StdOut != "hello" {
t.Errorf("res1.StdOut == '%v'", res1.StdOut)
}
if res1.StdCombined != "hello\n" {
t.Errorf("res1.StdCombined == '%v'", res1.StdCombined)
}
}
func TestStderr(t *testing.T) {
res1, err := Runner("python").Arg("-c").Arg("import sys; print(\"error\", file=sys.stderr, end='')").Run()
if err != nil {
t.Errorf("%v", err)
}
if res1.StdErr != "error" {
t.Errorf("res1.StdErr == '%v'", res1.StdErr)
}
if res1.StdOut != "" {
t.Errorf("res1.StdOut == '%v'", res1.StdOut)
}
if res1.StdCombined != "error\n" {
t.Errorf("res1.StdCombined == '%v'", res1.StdCombined)
}
}
func TestStdcombined(t *testing.T) {
res1, err := Runner("python").
Arg("-c").
Arg("import sys; import time; print(\"1\", file=sys.stderr, flush=True); time.sleep(0.1); print(\"2\", file=sys.stdout, flush=True); time.sleep(0.1); print(\"3\", file=sys.stderr, flush=True)").
Run()
if err != nil {
t.Errorf("%v", err)
}
if res1.StdErr != "1\n3\n" {
t.Errorf("res1.StdErr == '%v'", res1.StdErr)
}
if res1.StdOut != "2\n" {
t.Errorf("res1.StdOut == '%v'", res1.StdOut)
}
if res1.StdCombined != "1\n2\n3\n" {
t.Errorf("res1.StdCombined == '%v'", res1.StdCombined)
}
}
func TestPartialRead(t *testing.T) {
res1, err := Runner("python").
Arg("-c").
Arg("import sys; import time; print(\"first message\", flush=True); time.sleep(5); print(\"cant see me\", flush=True);").
Timeout(100 * time.Millisecond).
Run()
if err != nil {
t.Errorf("%v", err)
}
if !res1.CommandTimedOut {
t.Errorf("!CommandTimedOut")
}
if res1.StdErr != "" {
t.Errorf("res1.StdErr == '%v'", res1.StdErr)
}
if res1.StdOut != "first message\n" {
t.Errorf("res1.StdOut == '%v'", res1.StdOut)
}
if res1.StdCombined != "first message\n" {
t.Errorf("res1.StdCombined == '%v'", res1.StdCombined)
}
}
func TestPartialReadStderr(t *testing.T) {
res1, err := Runner("python").
Arg("-c").
Arg("import sys; import time; print(\"first message\", file=sys.stderr, flush=True); time.sleep(5); print(\"cant see me\", file=sys.stderr, flush=True);").
Timeout(100 * time.Millisecond).
Run()
if err != nil {
t.Errorf("%v", err)
}
if !res1.CommandTimedOut {
t.Errorf("!CommandTimedOut")
}
if res1.StdErr != "first message\n" {
t.Errorf("res1.StdErr == '%v'", res1.StdErr)
}
if res1.StdOut != "" {
t.Errorf("res1.StdOut == '%v'", res1.StdOut)
}
if res1.StdCombined != "first message\n" {
t.Errorf("res1.StdCombined == '%v'", res1.StdCombined)
}
}
func TestReadUnflushedStdout(t *testing.T) {
res1, err := Runner("python").Arg("-c").Arg("import sys; print(\"message101\", file=sys.stdout, end='')").Run()
if err != nil {
t.Errorf("%v", err)
}
if res1.StdErr != "" {
t.Errorf("res1.StdErr == '%v'", res1.StdErr)
}
if res1.StdOut != "message101" {
t.Errorf("res1.StdOut == '%v'", res1.StdOut)
}
if res1.StdCombined != "message101\n" {
t.Errorf("res1.StdCombined == '%v'", res1.StdCombined)
}
}
func TestReadUnflushedStderr(t *testing.T) {
res1, err := Runner("python").Arg("-c").Arg("import sys; print(\"message101\", file=sys.stderr, end='')").Run()
if err != nil {
t.Errorf("%v", err)
}
if res1.StdErr != "message101" {
t.Errorf("res1.StdErr == '%v'", res1.StdErr)
}
if res1.StdOut != "" {
t.Errorf("res1.StdOut == '%v'", res1.StdOut)
}
if res1.StdCombined != "message101\n" {
t.Errorf("res1.StdCombined == '%v'", res1.StdCombined)
}
}
func TestPartialReadUnflushed(t *testing.T) {
t.SkipNow()
res1, err := Runner("python").
Arg("-c").
Arg("import sys; import time; print(\"first message\", end=''); time.sleep(5); print(\"cant see me\", end='');").
Timeout(100 * time.Millisecond).
Run()
if err != nil {
t.Errorf("%v", err)
}
if !res1.CommandTimedOut {
t.Errorf("!CommandTimedOut")
}
if res1.StdErr != "" {
t.Errorf("res1.StdErr == '%v'", res1.StdErr)
}
if res1.StdOut != "first message" {
t.Errorf("res1.StdOut == '%v'", res1.StdOut)
}
if res1.StdCombined != "first message" {
t.Errorf("res1.StdCombined == '%v'", res1.StdCombined)
}
}
func TestPartialReadUnflushedStderr(t *testing.T) {
t.SkipNow()
res1, err := Runner("python").
Arg("-c").
Arg("import sys; import time; print(\"first message\", file=sys.stderr, end=''); time.sleep(5); print(\"cant see me\", file=sys.stderr, end='');").
Timeout(100 * time.Millisecond).
Run()
if err != nil {
t.Errorf("%v", err)
}
if !res1.CommandTimedOut {
t.Errorf("!CommandTimedOut")
}
if res1.StdErr != "first message" {
t.Errorf("res1.StdErr == '%v'", res1.StdErr)
}
if res1.StdOut != "" {
t.Errorf("res1.StdOut == '%v'", res1.StdOut)
}
if res1.StdCombined != "first message" {
t.Errorf("res1.StdCombined == '%v'", res1.StdCombined)
}
}
func TestListener(t *testing.T) {
_, err := Runner("python").
Arg("-c").
Arg("import sys;" +
"import time;" +
"print(\"message 1\", flush=True);" +
"time.sleep(1);" +
"print(\"message 2\", flush=True);" +
"time.sleep(1);" +
"print(\"message 3\", flush=True);" +
"time.sleep(1);" +
"print(\"message 4\", file=sys.stderr, flush=True);" +
"time.sleep(1);" +
"print(\"message 5\", flush=True);" +
"time.sleep(1);" +
"print(\"final\");").
ListenStdout(func(s string) { fmt.Printf("@@STDOUT <<- %v (%v)\n", s, time.Now().Format(time.RFC3339Nano)) }).
ListenStderr(func(s string) { fmt.Printf("@@STDERR <<- %v (%v)\n", s, time.Now().Format(time.RFC3339Nano)) }).
Timeout(10 * time.Second).
Run()
if err != nil {
panic(err)
}
}

12
cmdext/helper.go Normal file
View File

@@ -0,0 +1,12 @@
package cmdext
import "time"
func RunCommand(program string, args []string, timeout *time.Duration) (CommandResult, error) {
b := Runner(program)
b = b.Args(args)
if timeout != nil {
b = b.Timeout(*timeout)
}
return b.Run()
}

57
cmdext/listener.go Normal file
View File

@@ -0,0 +1,57 @@
package cmdext
type CommandListener interface {
ReadRawStdout([]byte)
ReadRawStderr([]byte)
ReadStdoutLine(string)
ReadStderrLine(string)
Finished(int)
Timeout()
}
type genericCommandListener struct {
_readRawStdout *func([]byte)
_readRawStderr *func([]byte)
_readStdoutLine *func(string)
_readStderrLine *func(string)
_finished *func(int)
_timeout *func()
}
func (g genericCommandListener) ReadRawStdout(v []byte) {
if g._readRawStdout != nil {
(*g._readRawStdout)(v)
}
}
func (g genericCommandListener) ReadRawStderr(v []byte) {
if g._readRawStderr != nil {
(*g._readRawStderr)(v)
}
}
func (g genericCommandListener) ReadStdoutLine(v string) {
if g._readStdoutLine != nil {
(*g._readStdoutLine)(v)
}
}
func (g genericCommandListener) ReadStderrLine(v string) {
if g._readStderrLine != nil {
(*g._readStderrLine)(v)
}
}
func (g genericCommandListener) Finished(v int) {
if g._finished != nil {
(*g._finished)(v)
}
}
func (g genericCommandListener) Timeout() {
if g._timeout != nil {
(*g._timeout)()
}
}

146
cmdext/pipereader.go Normal file
View File

@@ -0,0 +1,146 @@
package cmdext
import (
"bufio"
"gogs.mikescher.com/BlackForestBytes/goext/syncext"
"io"
"sync"
)
type pipeReader struct {
stdout io.ReadCloser
stderr io.ReadCloser
}
// Read ready stdout and stdin until finished
// also splits both pipes into lines and calld the listener
func (pr *pipeReader) Read(listener []CommandListener) (string, string, string, error) {
type combevt struct {
line string
stop bool
}
errch := make(chan error, 8)
wg := sync.WaitGroup{}
// [1] read raw stdout
wg.Add(1)
stdoutBufferReader, stdoutBufferWriter := io.Pipe()
stdout := ""
go func() {
buf := make([]byte, 128)
for true {
n, out := pr.stdout.Read(buf)
if n > 0 {
txt := string(buf[:n])
stdout += txt
_, _ = stdoutBufferWriter.Write(buf[:n])
for _, lstr := range listener {
lstr.ReadRawStdout(buf[:n])
}
}
if out == io.EOF {
break
}
if out != nil {
errch <- out
break
}
}
_ = stdoutBufferWriter.Close()
wg.Done()
}()
// [2] read raw stderr
wg.Add(1)
stderrBufferReader, stderrBufferWriter := io.Pipe()
stderr := ""
go func() {
buf := make([]byte, 128)
for true {
n, err := pr.stderr.Read(buf)
if n > 0 {
txt := string(buf[:n])
stderr += txt
_, _ = stderrBufferWriter.Write(buf[:n])
for _, lstr := range listener {
lstr.ReadRawStderr(buf[:n])
}
}
if err == io.EOF {
break
}
if err != nil {
errch <- err
break
}
}
_ = stderrBufferWriter.Close()
wg.Done()
}()
combch := make(chan combevt, 32)
// [3] collect stdout line-by-line
wg.Add(1)
go func() {
scanner := bufio.NewScanner(stdoutBufferReader)
for scanner.Scan() {
txt := scanner.Text()
for _, lstr := range listener {
lstr.ReadStdoutLine(txt)
}
combch <- combevt{txt, false}
}
combch <- combevt{"", true}
wg.Done()
}()
// [4] collect stderr line-by-line
wg.Add(1)
go func() {
scanner := bufio.NewScanner(stderrBufferReader)
for scanner.Scan() {
txt := scanner.Text()
for _, lstr := range listener {
lstr.ReadStderrLine(txt)
}
combch <- combevt{txt, false}
}
combch <- combevt{"", true}
wg.Done()
}()
// [5] combine stdcombined
wg.Add(1)
stdcombined := ""
go func() {
stopctr := 0
for stopctr < 2 {
vvv := <-combch
if vvv.stop {
stopctr++
} else {
stdcombined += vvv.line + "\n" // this comes from bufio.Scanner and has no newlines...
}
}
wg.Done()
}()
// wait for all (5) goroutines to finish
wg.Wait()
if err, ok := syncext.ReadNonBlocking(errch); ok {
return "", "", "", err
}
return stdout, stderr, stdcombined, nil
}

130
cryptext/aes.go Normal file
View File

@@ -0,0 +1,130 @@
package cryptext
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"encoding/base32"
"encoding/json"
"errors"
"golang.org/x/crypto/scrypt"
"io"
)
// https://stackoverflow.com/a/18819040/1761622
type aesPayload struct {
Salt []byte `json:"s"`
IV []byte `json:"i"`
Data []byte `json:"d"`
Rounds int `json:"r"`
Version uint `json:"v"`
Algorithm string `json:"a"`
}
func EncryptAESSimple(password []byte, data []byte, rounds int) (string, error) {
salt := make([]byte, 8)
_, err := io.ReadFull(rand.Reader, salt)
if err != nil {
return "", err
}
key, err := scrypt.Key(password, salt, rounds, 8, 1, 32)
if err != nil {
return "", err
}
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
h := sha256.New()
h.Write(data)
checksum := h.Sum(nil)
if len(checksum) != 32 {
return "", errors.New("wrong cs size")
}
ciphertext := make([]byte, 32+len(data))
iv := make([]byte, aes.BlockSize)
_, err = io.ReadFull(rand.Reader, iv)
if err != nil {
return "", err
}
combinedData := make([]byte, 0, 32+len(data))
combinedData = append(combinedData, checksum...)
combinedData = append(combinedData, data...)
cfb := cipher.NewCFBEncrypter(block, iv)
cfb.XORKeyStream(ciphertext, combinedData)
pl := aesPayload{
Salt: salt,
IV: iv,
Data: ciphertext,
Version: 1,
Rounds: rounds,
Algorithm: "AES",
}
jbin, err := json.Marshal(pl)
if err != nil {
return "", err
}
res := base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(jbin)
return res, nil
}
func DecryptAESSimple(password []byte, encText string) ([]byte, error) {
jbin, err := base32.StdEncoding.WithPadding(base32.NoPadding).DecodeString(encText)
if err != nil {
return nil, err
}
var pl aesPayload
err = json.Unmarshal(jbin, &pl)
if err != nil {
return nil, err
}
key, err := scrypt.Key(password, pl.Salt, pl.Rounds, 8, 1, 32) // this is not 100% correct, rounds too low and salt is missing
if err != nil {
return nil, err
}
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
dest := make([]byte, len(pl.Data))
cfb := cipher.NewCFBDecrypter(block, pl.IV)
cfb.XORKeyStream(dest, pl.Data)
if len(dest) < 32 {
return nil, errors.New("payload too small")
}
chck := dest[:32]
data := dest[32:]
h := sha256.New()
h.Write(data)
chck2 := h.Sum(nil)
if !bytes.Equal(chck, chck2) {
return nil, errors.New("checksum mismatch")
}
return data, nil
}

35
cryptext/aes_test.go Normal file
View File

@@ -0,0 +1,35 @@
package cryptext
import (
"fmt"
"testing"
)
func TestEncryptAESSimple(t *testing.T) {
pw := []byte("hunter12")
str1 := []byte("Hello World")
str2, err := EncryptAESSimple(pw, str1, 512)
if err != nil {
panic(err)
}
fmt.Printf("%s\n", str2)
str3, err := DecryptAESSimple(pw, str2)
if err != nil {
panic(err)
}
assertEqual(t, string(str1), string(str3))
str4, err := EncryptAESSimple(pw, str3, 512)
if err != nil {
panic(err)
}
assertNotEqual(t, string(str2), string(str4))
}

View File

@@ -23,3 +23,9 @@ func assertEqual(t *testing.T, actual string, expected string) {
t.Errorf("values differ: Actual: '%v', Expected: '%v'", actual, expected) t.Errorf("values differ: Actual: '%v', Expected: '%v'", actual, expected)
} }
} }
func assertNotEqual(t *testing.T, actual string, expected string) {
if actual == expected {
t.Errorf("values do not differ: Actual: '%v', Expected: '%v'", actual, expected)
}
}

116
dataext/stack.go Normal file
View File

@@ -0,0 +1,116 @@
package dataext
import (
"errors"
"gogs.mikescher.com/BlackForestBytes/goext/langext"
"sync"
)
var ErrEmptyStack = errors.New("stack is empty")
type Stack[T any] struct {
lock *sync.Mutex
data []T
}
func NewStack[T any](threadsafe bool, initialCapacity int) *Stack[T] {
var lck *sync.Mutex = nil
if threadsafe {
lck = &sync.Mutex{}
}
return &Stack[T]{
lock: lck,
data: make([]T, 0, initialCapacity),
}
}
func (s *Stack[T]) Push(v T) {
if s.lock != nil {
s.lock.Lock()
defer s.lock.Unlock()
}
s.data = append(s.data, v)
}
func (s *Stack[T]) Pop() (T, error) {
if s.lock != nil {
s.lock.Lock()
defer s.lock.Unlock()
}
l := len(s.data)
if l == 0 {
return *new(T), ErrEmptyStack
}
result := s.data[l-1]
s.data = s.data[:l-1]
return result, nil
}
func (s *Stack[T]) OptPop() *T {
if s.lock != nil {
s.lock.Lock()
defer s.lock.Unlock()
}
l := len(s.data)
if l == 0 {
return nil
}
result := s.data[l-1]
s.data = s.data[:l-1]
return langext.Ptr(result)
}
func (s *Stack[T]) Peek() (T, error) {
if s.lock != nil {
s.lock.Lock()
defer s.lock.Unlock()
}
l := len(s.data)
if l == 0 {
return *new(T), ErrEmptyStack
}
return s.data[l-1], nil
}
func (s *Stack[T]) OptPeek() *T {
if s.lock != nil {
s.lock.Lock()
defer s.lock.Unlock()
}
l := len(s.data)
if l == 0 {
return nil
}
return langext.Ptr(s.data[l-1])
}
func (s *Stack[T]) Length() int {
if s.lock != nil {
s.lock.Lock()
defer s.lock.Unlock()
}
return len(s.data)
}
func (s *Stack[T]) Empty() bool {
if s.lock != nil {
s.lock.Lock()
defer s.lock.Unlock()
}
return len(s.data) == 0
}

View File

@@ -265,6 +265,16 @@ func ArrMap[T1 any, T2 any](arr []T1, conv func(v T1) T2) []T2 {
return r return r
} }
func ArrFilterMap[T1 any, T2 any](arr []T1, filter func(v T1) bool, conv func(v T1) T2) []T2 {
r := make([]T2, 0, len(arr))
for _, v := range arr {
if filter(v) {
r = append(r, conv(v))
}
}
return r
}
func ArrSum[T NumberConstraint](arr []T) T { func ArrSum[T NumberConstraint](arr []T) T {
var r T = 0 var r T = 0
for _, v := range arr { for _, v := range arr {

View File

@@ -7,3 +7,11 @@ func MapKeyArr[T comparable, V any](v map[T]V) []T {
} }
return result return result
} }
func ArrToMap[T comparable, V any](a []V, keyfunc func(V) T) map[T]V {
result := make(map[T]V, len(a))
for _, v := range a {
result[keyfunc(v)] = v
}
return result
}

View File

@@ -37,3 +37,21 @@ func IsSliceSorted[T any](arr []T, less func(v1, v2 T) bool) bool {
return less(arr[i1], arr[i2]) return less(arr[i1], arr[i2])
}) })
} }
func SortBy[TElem any, TSel OrderedConstraint](arr []TElem, selector func(v TElem) TSel) {
sort.Slice(arr, func(i1, i2 int) bool {
return selector(arr[i1]) < selector(arr[i2])
})
}
func SortByStable[TElem any, TSel OrderedConstraint](arr []TElem, selector func(v TElem) TSel) {
sort.SliceStable(arr, func(i1, i2 int) bool {
return selector(arr[i1]) < selector(arr[i2])
})
}
func IsSortedBy[TElem any, TSel OrderedConstraint](arr []TElem, selector func(v TElem) TSel) {
sort.SliceStable(arr, func(i1, i2 int) bool {
return selector(arr[i1]) < selector(arr[i2])
})
}

45
rfctime/interface.go Normal file
View File

@@ -0,0 +1,45 @@
package rfctime
import "time"
type RFCTime interface {
Time() time.Time
Serialize() string
UnmarshalJSON(bytes []byte) error
MarshalJSON() ([]byte, error)
MarshalBinary() ([]byte, error)
UnmarshalBinary(data []byte) error
GobEncode() ([]byte, error)
GobDecode(data []byte) error
MarshalText() ([]byte, error)
UnmarshalText(data []byte) error
After(u RFCTime) bool
Before(u RFCTime) bool
Equal(u RFCTime) bool
IsZero() bool
Date() (year int, month time.Month, day int)
Year() int
Month() time.Month
Day() int
Weekday() time.Weekday
ISOWeek() (year, week int)
Clock() (hour, min, sec int)
Hour() int
Minute() int
Second() int
Nanosecond() int
YearDay() int
Sub(u RFCTime) time.Duration
Unix() int64
UnixMilli() int64
UnixMicro() int64
UnixNano() int64
Format(layout string) string
GoString() string
String() string
}

182
rfctime/rfc3339.go Normal file
View File

@@ -0,0 +1,182 @@
package rfctime
import (
"encoding/json"
"time"
)
type RFC3339Time time.Time
func (t RFC3339Time) Time() time.Time {
return time.Time(t)
}
func (t RFC3339Time) MarshalBinary() ([]byte, error) {
return (time.Time)(t).MarshalBinary()
}
func (t *RFC3339Time) UnmarshalBinary(data []byte) error {
return (*time.Time)(t).UnmarshalBinary(data)
}
func (t RFC3339Time) GobEncode() ([]byte, error) {
return (time.Time)(t).GobEncode()
}
func (t *RFC3339Time) GobDecode(data []byte) error {
return (*time.Time)(t).GobDecode(data)
}
func (t *RFC3339Time) UnmarshalJSON(data []byte) error {
str := ""
if err := json.Unmarshal(data, &str); err != nil {
return err
}
t0, err := time.Parse(t.FormatStr(), str)
if err != nil {
return err
}
*t = RFC3339Time(t0)
return nil
}
func (t RFC3339Time) MarshalJSON() ([]byte, error) {
str := t.Time().Format(t.FormatStr())
return json.Marshal(str)
}
func (t RFC3339Time) MarshalText() ([]byte, error) {
b := make([]byte, 0, len(t.FormatStr()))
return t.Time().AppendFormat(b, t.FormatStr()), nil
}
func (t *RFC3339Time) UnmarshalText(data []byte) error {
var err error
v, err := time.Parse(t.FormatStr(), string(data))
if err != nil {
return err
}
tt := RFC3339Time(v)
*t = tt
return nil
}
func (t RFC3339Time) Serialize() string {
return t.Time().Format(t.FormatStr())
}
func (t RFC3339Time) FormatStr() string {
return time.RFC3339
}
func (t RFC3339Time) After(u RFCTime) bool {
return t.Time().After(u.Time())
}
func (t RFC3339Time) Before(u RFCTime) bool {
return t.Time().Before(u.Time())
}
func (t RFC3339Time) Equal(u RFCTime) bool {
return t.Time().Equal(u.Time())
}
func (t RFC3339Time) IsZero() bool {
return t.Time().IsZero()
}
func (t RFC3339Time) Date() (year int, month time.Month, day int) {
return t.Time().Date()
}
func (t RFC3339Time) Year() int {
return t.Time().Year()
}
func (t RFC3339Time) Month() time.Month {
return t.Time().Month()
}
func (t RFC3339Time) Day() int {
return t.Time().Day()
}
func (t RFC3339Time) Weekday() time.Weekday {
return t.Time().Weekday()
}
func (t RFC3339Time) ISOWeek() (year, week int) {
return t.Time().ISOWeek()
}
func (t RFC3339Time) Clock() (hour, min, sec int) {
return t.Time().Clock()
}
func (t RFC3339Time) Hour() int {
return t.Time().Hour()
}
func (t RFC3339Time) Minute() int {
return t.Time().Minute()
}
func (t RFC3339Time) Second() int {
return t.Time().Second()
}
func (t RFC3339Time) Nanosecond() int {
return t.Time().Nanosecond()
}
func (t RFC3339Time) YearDay() int {
return t.Time().YearDay()
}
func (t RFC3339Time) Add(d time.Duration) RFC3339Time {
return RFC3339Time(t.Time().Add(d))
}
func (t RFC3339Time) Sub(u RFCTime) time.Duration {
return t.Time().Sub(u.Time())
}
func (t RFC3339Time) AddDate(years int, months int, days int) RFC3339Time {
return RFC3339Time(t.Time().AddDate(years, months, days))
}
func (t RFC3339Time) Unix() int64 {
return t.Time().Unix()
}
func (t RFC3339Time) UnixMilli() int64 {
return t.Time().UnixMilli()
}
func (t RFC3339Time) UnixMicro() int64 {
return t.Time().UnixMicro()
}
func (t RFC3339Time) UnixNano() int64 {
return t.Time().UnixNano()
}
func (t RFC3339Time) Format(layout string) string {
return t.Time().Format(layout)
}
func (t RFC3339Time) GoString() string {
return t.Time().GoString()
}
func (t RFC3339Time) String() string {
return t.Time().String()
}
func NewRFC3339(t time.Time) RFC3339Time {
return RFC3339Time(t)
}
func NowRFC3339() RFC3339Time {
return RFC3339Time(time.Now())
}

182
rfctime/rfc3339Nano.go Normal file
View File

@@ -0,0 +1,182 @@
package rfctime
import (
"encoding/json"
"time"
)
type RFC3339NanoTime time.Time
func (t RFC3339NanoTime) Time() time.Time {
return time.Time(t)
}
func (t RFC3339NanoTime) MarshalBinary() ([]byte, error) {
return (time.Time)(t).MarshalBinary()
}
func (t *RFC3339NanoTime) UnmarshalBinary(data []byte) error {
return (*time.Time)(t).UnmarshalBinary(data)
}
func (t RFC3339NanoTime) GobEncode() ([]byte, error) {
return (time.Time)(t).GobEncode()
}
func (t *RFC3339NanoTime) GobDecode(data []byte) error {
return (*time.Time)(t).GobDecode(data)
}
func (t *RFC3339NanoTime) UnmarshalJSON(data []byte) error {
str := ""
if err := json.Unmarshal(data, &str); err != nil {
return err
}
t0, err := time.Parse(t.FormatStr(), str)
if err != nil {
return err
}
*t = RFC3339NanoTime(t0)
return nil
}
func (t RFC3339NanoTime) MarshalJSON() ([]byte, error) {
str := t.Time().Format(t.FormatStr())
return json.Marshal(str)
}
func (t RFC3339NanoTime) MarshalText() ([]byte, error) {
b := make([]byte, 0, len(t.FormatStr()))
return t.Time().AppendFormat(b, t.FormatStr()), nil
}
func (t *RFC3339NanoTime) UnmarshalText(data []byte) error {
var err error
v, err := time.Parse(t.FormatStr(), string(data))
if err != nil {
return err
}
tt := RFC3339NanoTime(v)
*t = tt
return nil
}
func (t RFC3339NanoTime) Serialize() string {
return t.Time().Format(t.FormatStr())
}
func (t RFC3339NanoTime) FormatStr() string {
return time.RFC3339Nano
}
func (t RFC3339NanoTime) After(u RFCTime) bool {
return t.Time().After(u.Time())
}
func (t RFC3339NanoTime) Before(u RFCTime) bool {
return t.Time().Before(u.Time())
}
func (t RFC3339NanoTime) Equal(u RFCTime) bool {
return t.Time().Equal(u.Time())
}
func (t RFC3339NanoTime) IsZero() bool {
return t.Time().IsZero()
}
func (t RFC3339NanoTime) Date() (year int, month time.Month, day int) {
return t.Time().Date()
}
func (t RFC3339NanoTime) Year() int {
return t.Time().Year()
}
func (t RFC3339NanoTime) Month() time.Month {
return t.Time().Month()
}
func (t RFC3339NanoTime) Day() int {
return t.Time().Day()
}
func (t RFC3339NanoTime) Weekday() time.Weekday {
return t.Time().Weekday()
}
func (t RFC3339NanoTime) ISOWeek() (year, week int) {
return t.Time().ISOWeek()
}
func (t RFC3339NanoTime) Clock() (hour, min, sec int) {
return t.Time().Clock()
}
func (t RFC3339NanoTime) Hour() int {
return t.Time().Hour()
}
func (t RFC3339NanoTime) Minute() int {
return t.Time().Minute()
}
func (t RFC3339NanoTime) Second() int {
return t.Time().Second()
}
func (t RFC3339NanoTime) Nanosecond() int {
return t.Time().Nanosecond()
}
func (t RFC3339NanoTime) YearDay() int {
return t.Time().YearDay()
}
func (t RFC3339NanoTime) Add(d time.Duration) RFC3339NanoTime {
return RFC3339NanoTime(t.Time().Add(d))
}
func (t RFC3339NanoTime) Sub(u RFCTime) time.Duration {
return t.Time().Sub(u.Time())
}
func (t RFC3339NanoTime) AddDate(years int, months int, days int) RFC3339NanoTime {
return RFC3339NanoTime(t.Time().AddDate(years, months, days))
}
func (t RFC3339NanoTime) Unix() int64 {
return t.Time().Unix()
}
func (t RFC3339NanoTime) UnixMilli() int64 {
return t.Time().UnixMilli()
}
func (t RFC3339NanoTime) UnixMicro() int64 {
return t.Time().UnixMicro()
}
func (t RFC3339NanoTime) UnixNano() int64 {
return t.Time().UnixNano()
}
func (t RFC3339NanoTime) Format(layout string) string {
return t.Time().Format(layout)
}
func (t RFC3339NanoTime) GoString() string {
return t.Time().GoString()
}
func (t RFC3339NanoTime) String() string {
return t.Time().String()
}
func NewRFC3339Nano(t time.Time) RFC3339NanoTime {
return RFC3339NanoTime(t)
}
func NowRFC3339Nano() RFC3339NanoTime {
return RFC3339NanoTime(time.Now())
}

View File

@@ -0,0 +1,51 @@
package rfctime
import (
"encoding/json"
"testing"
"time"
)
func TestRoundtrip(t *testing.T) {
type Wrap struct {
Value RFC3339NanoTime `json:"v"`
}
val1 := NewRFC3339Nano(time.Now())
w1 := Wrap{val1}
jstr1, err := json.Marshal(w1)
if err != nil {
panic(err)
}
if string(jstr1) != "{\"v\":\"2023-01-29T20:32:36.149692117+01:00\"}" {
t.Errorf("repr differs")
}
w2 := Wrap{}
err = json.Unmarshal(jstr1, &w2)
if err != nil {
panic(err)
}
jstr2, err := json.Marshal(w2)
if err != nil {
panic(err)
}
assertEqual(t, string(jstr1), string(jstr2))
if !w1.Value.Equal(&w2.Value) {
t.Errorf("time differs")
}
}
func assertEqual(t *testing.T, actual string, expected string) {
if actual != expected {
t.Errorf("values differ: Actual: '%v', Expected: '%v'", actual, expected)
}
}

176
rfctime/unix.go Normal file
View File

@@ -0,0 +1,176 @@
package rfctime
import (
"encoding/json"
"strconv"
"time"
)
type UnixTime time.Time
func (t UnixTime) Time() time.Time {
return time.Time(t)
}
func (t UnixTime) MarshalBinary() ([]byte, error) {
return (time.Time)(t).MarshalBinary()
}
func (t *UnixTime) UnmarshalBinary(data []byte) error {
return (*time.Time)(t).UnmarshalBinary(data)
}
func (t UnixTime) GobEncode() ([]byte, error) {
return (time.Time)(t).GobEncode()
}
func (t *UnixTime) GobDecode(data []byte) error {
return (*time.Time)(t).GobDecode(data)
}
func (t *UnixTime) UnmarshalJSON(data []byte) error {
str := ""
if err := json.Unmarshal(data, &str); err != nil {
return err
}
t0, err := strconv.ParseInt(str, 10, 64)
if err != nil {
return err
}
*t = UnixTime(time.Unix(t0, 0))
return nil
}
func (t UnixTime) MarshalJSON() ([]byte, error) {
str := strconv.FormatInt(t.Time().Unix(), 10)
return json.Marshal(str)
}
func (t UnixTime) MarshalText() ([]byte, error) {
return []byte(strconv.FormatInt(t.Time().Unix(), 10)), nil
}
func (t *UnixTime) UnmarshalText(data []byte) error {
t0, err := strconv.ParseInt(string(data), 10, 64)
if err != nil {
return err
}
*t = UnixTime(time.Unix(t0, 0))
return nil
}
func (t UnixTime) Serialize() string {
return strconv.FormatInt(t.Time().Unix(), 10)
}
func (t UnixTime) After(u RFCTime) bool {
return t.Time().After(u.Time())
}
func (t UnixTime) Before(u RFCTime) bool {
return t.Time().Before(u.Time())
}
func (t UnixTime) Equal(u RFCTime) bool {
return t.Time().Equal(u.Time())
}
func (t UnixTime) IsZero() bool {
return t.Time().IsZero()
}
func (t UnixTime) Date() (year int, month time.Month, day int) {
return t.Time().Date()
}
func (t UnixTime) Year() int {
return t.Time().Year()
}
func (t UnixTime) Month() time.Month {
return t.Time().Month()
}
func (t UnixTime) Day() int {
return t.Time().Day()
}
func (t UnixTime) Weekday() time.Weekday {
return t.Time().Weekday()
}
func (t UnixTime) ISOWeek() (year, week int) {
return t.Time().ISOWeek()
}
func (t UnixTime) Clock() (hour, min, sec int) {
return t.Time().Clock()
}
func (t UnixTime) Hour() int {
return t.Time().Hour()
}
func (t UnixTime) Minute() int {
return t.Time().Minute()
}
func (t UnixTime) Second() int {
return t.Time().Second()
}
func (t UnixTime) Nanosecond() int {
return t.Time().Nanosecond()
}
func (t UnixTime) YearDay() int {
return t.Time().YearDay()
}
func (t UnixTime) Add(d time.Duration) UnixTime {
return UnixTime(t.Time().Add(d))
}
func (t UnixTime) Sub(u RFCTime) time.Duration {
return t.Time().Sub(u.Time())
}
func (t UnixTime) AddDate(years int, months int, days int) UnixTime {
return UnixTime(t.Time().AddDate(years, months, days))
}
func (t UnixTime) Unix() int64 {
return t.Time().Unix()
}
func (t UnixTime) UnixMilli() int64 {
return t.Time().UnixMilli()
}
func (t UnixTime) UnixMicro() int64 {
return t.Time().UnixMicro()
}
func (t UnixTime) UnixNano() int64 {
return t.Time().UnixNano()
}
func (t UnixTime) Format(layout string) string {
return t.Time().Format(layout)
}
func (t UnixTime) GoString() string {
return t.Time().GoString()
}
func (t UnixTime) String() string {
return t.Time().String()
}
func NewUnix(t time.Time) UnixTime {
return UnixTime(t)
}
func NowUnix() UnixTime {
return UnixTime(time.Now())
}

176
rfctime/unixMilli.go Normal file
View File

@@ -0,0 +1,176 @@
package rfctime
import (
"encoding/json"
"strconv"
"time"
)
type UnixMilliTime time.Time
func (t UnixMilliTime) Time() time.Time {
return time.Time(t)
}
func (t UnixMilliTime) MarshalBinary() ([]byte, error) {
return (time.Time)(t).MarshalBinary()
}
func (t *UnixMilliTime) UnmarshalBinary(data []byte) error {
return (*time.Time)(t).UnmarshalBinary(data)
}
func (t UnixMilliTime) GobEncode() ([]byte, error) {
return (time.Time)(t).GobEncode()
}
func (t *UnixMilliTime) GobDecode(data []byte) error {
return (*time.Time)(t).GobDecode(data)
}
func (t *UnixMilliTime) UnmarshalJSON(data []byte) error {
str := ""
if err := json.Unmarshal(data, &str); err != nil {
return err
}
t0, err := strconv.ParseInt(str, 10, 64)
if err != nil {
return err
}
*t = UnixMilliTime(time.UnixMilli(t0))
return nil
}
func (t UnixMilliTime) MarshalJSON() ([]byte, error) {
str := strconv.FormatInt(t.Time().UnixMilli(), 10)
return json.Marshal(str)
}
func (t UnixMilliTime) MarshalText() ([]byte, error) {
return []byte(strconv.FormatInt(t.Time().UnixMilli(), 10)), nil
}
func (t *UnixMilliTime) UnmarshalText(data []byte) error {
t0, err := strconv.ParseInt(string(data), 10, 64)
if err != nil {
return err
}
*t = UnixMilliTime(time.UnixMilli(t0))
return nil
}
func (t UnixMilliTime) Serialize() string {
return strconv.FormatInt(t.Time().UnixMilli(), 10)
}
func (t UnixMilliTime) After(u RFCTime) bool {
return t.Time().After(u.Time())
}
func (t UnixMilliTime) Before(u RFCTime) bool {
return t.Time().Before(u.Time())
}
func (t UnixMilliTime) Equal(u RFCTime) bool {
return t.Time().Equal(u.Time())
}
func (t UnixMilliTime) IsZero() bool {
return t.Time().IsZero()
}
func (t UnixMilliTime) Date() (year int, month time.Month, day int) {
return t.Time().Date()
}
func (t UnixMilliTime) Year() int {
return t.Time().Year()
}
func (t UnixMilliTime) Month() time.Month {
return t.Time().Month()
}
func (t UnixMilliTime) Day() int {
return t.Time().Day()
}
func (t UnixMilliTime) Weekday() time.Weekday {
return t.Time().Weekday()
}
func (t UnixMilliTime) ISOWeek() (year, week int) {
return t.Time().ISOWeek()
}
func (t UnixMilliTime) Clock() (hour, min, sec int) {
return t.Time().Clock()
}
func (t UnixMilliTime) Hour() int {
return t.Time().Hour()
}
func (t UnixMilliTime) Minute() int {
return t.Time().Minute()
}
func (t UnixMilliTime) Second() int {
return t.Time().Second()
}
func (t UnixMilliTime) Nanosecond() int {
return t.Time().Nanosecond()
}
func (t UnixMilliTime) YearDay() int {
return t.Time().YearDay()
}
func (t UnixMilliTime) Add(d time.Duration) UnixMilliTime {
return UnixMilliTime(t.Time().Add(d))
}
func (t UnixMilliTime) Sub(u RFCTime) time.Duration {
return t.Time().Sub(u.Time())
}
func (t UnixMilliTime) AddDate(years int, months int, days int) UnixMilliTime {
return UnixMilliTime(t.Time().AddDate(years, months, days))
}
func (t UnixMilliTime) Unix() int64 {
return t.Time().Unix()
}
func (t UnixMilliTime) UnixMilli() int64 {
return t.Time().UnixMilli()
}
func (t UnixMilliTime) UnixMicro() int64 {
return t.Time().UnixMicro()
}
func (t UnixMilliTime) UnixNano() int64 {
return t.Time().UnixNano()
}
func (t UnixMilliTime) Format(layout string) string {
return t.Time().Format(layout)
}
func (t UnixMilliTime) GoString() string {
return t.Time().GoString()
}
func (t UnixMilliTime) String() string {
return t.Time().String()
}
func NewUnixMilli(t time.Time) UnixMilliTime {
return UnixMilliTime(t)
}
func NowUnixMilli() UnixMilliTime {
return UnixMilliTime(time.Now())
}

176
rfctime/unixNano.go Normal file
View File

@@ -0,0 +1,176 @@
package rfctime
import (
"encoding/json"
"strconv"
"time"
)
type UnixNanoTime time.Time
func (t UnixNanoTime) Time() time.Time {
return time.Time(t)
}
func (t UnixNanoTime) MarshalBinary() ([]byte, error) {
return (time.Time)(t).MarshalBinary()
}
func (t *UnixNanoTime) UnmarshalBinary(data []byte) error {
return (*time.Time)(t).UnmarshalBinary(data)
}
func (t UnixNanoTime) GobEncode() ([]byte, error) {
return (time.Time)(t).GobEncode()
}
func (t *UnixNanoTime) GobDecode(data []byte) error {
return (*time.Time)(t).GobDecode(data)
}
func (t *UnixNanoTime) UnmarshalJSON(data []byte) error {
str := ""
if err := json.Unmarshal(data, &str); err != nil {
return err
}
t0, err := strconv.ParseInt(str, 10, 64)
if err != nil {
return err
}
*t = UnixNanoTime(time.Unix(0, t0))
return nil
}
func (t UnixNanoTime) MarshalJSON() ([]byte, error) {
str := strconv.FormatInt(t.Time().UnixNano(), 10)
return json.Marshal(str)
}
func (t UnixNanoTime) MarshalText() ([]byte, error) {
return []byte(strconv.FormatInt(t.Time().UnixNano(), 10)), nil
}
func (t *UnixNanoTime) UnmarshalText(data []byte) error {
t0, err := strconv.ParseInt(string(data), 10, 64)
if err != nil {
return err
}
*t = UnixNanoTime(time.Unix(0, t0))
return nil
}
func (t UnixNanoTime) Serialize() string {
return strconv.FormatInt(t.Time().UnixNano(), 10)
}
func (t UnixNanoTime) After(u RFCTime) bool {
return t.Time().After(u.Time())
}
func (t UnixNanoTime) Before(u RFCTime) bool {
return t.Time().Before(u.Time())
}
func (t UnixNanoTime) Equal(u RFCTime) bool {
return t.Time().Equal(u.Time())
}
func (t UnixNanoTime) IsZero() bool {
return t.Time().IsZero()
}
func (t UnixNanoTime) Date() (year int, month time.Month, day int) {
return t.Time().Date()
}
func (t UnixNanoTime) Year() int {
return t.Time().Year()
}
func (t UnixNanoTime) Month() time.Month {
return t.Time().Month()
}
func (t UnixNanoTime) Day() int {
return t.Time().Day()
}
func (t UnixNanoTime) Weekday() time.Weekday {
return t.Time().Weekday()
}
func (t UnixNanoTime) ISOWeek() (year, week int) {
return t.Time().ISOWeek()
}
func (t UnixNanoTime) Clock() (hour, min, sec int) {
return t.Time().Clock()
}
func (t UnixNanoTime) Hour() int {
return t.Time().Hour()
}
func (t UnixNanoTime) Minute() int {
return t.Time().Minute()
}
func (t UnixNanoTime) Second() int {
return t.Time().Second()
}
func (t UnixNanoTime) Nanosecond() int {
return t.Time().Nanosecond()
}
func (t UnixNanoTime) YearDay() int {
return t.Time().YearDay()
}
func (t UnixNanoTime) Add(d time.Duration) UnixNanoTime {
return UnixNanoTime(t.Time().Add(d))
}
func (t UnixNanoTime) Sub(u RFCTime) time.Duration {
return t.Time().Sub(u.Time())
}
func (t UnixNanoTime) AddDate(years int, months int, days int) UnixNanoTime {
return UnixNanoTime(t.Time().AddDate(years, months, days))
}
func (t UnixNanoTime) Unix() int64 {
return t.Time().Unix()
}
func (t UnixNanoTime) UnixMilli() int64 {
return t.Time().UnixNano()
}
func (t UnixNanoTime) UnixMicro() int64 {
return t.Time().UnixMicro()
}
func (t UnixNanoTime) UnixNano() int64 {
return t.Time().UnixNano()
}
func (t UnixNanoTime) Format(layout string) string {
return t.Time().Format(layout)
}
func (t UnixNanoTime) GoString() string {
return t.Time().GoString()
}
func (t UnixNanoTime) String() string {
return t.Time().String()
}
func NewUnixNano(t time.Time) UnixNanoTime {
return UnixNanoTime(t)
}
func NowUnixNano() UnixNanoTime {
return UnixNanoTime(time.Now())
}

View File

@@ -2,58 +2,50 @@ package syncext
import ( import (
"context" "context"
"sync/atomic" "gogs.mikescher.com/BlackForestBytes/goext/langext"
"sync"
"time" "time"
) )
type AtomicBool struct { type AtomicBool struct {
v int32 v bool
waiter chan bool // unbuffered listener map[string]chan bool
lock sync.Mutex
} }
func NewAtomicBool(value bool) *AtomicBool { func NewAtomicBool(value bool) *AtomicBool {
if value { return &AtomicBool{
return &AtomicBool{v: 1, waiter: make(chan bool)} v: value,
} else { listener: make(map[string]chan bool),
return &AtomicBool{v: 0, waiter: make(chan bool)} lock: sync.Mutex{},
} }
} }
func (a *AtomicBool) Get() bool { func (a *AtomicBool) Get() bool {
return atomic.LoadInt32(&a.v) == 1 a.lock.Lock()
defer a.lock.Unlock()
return a.v
} }
func (a *AtomicBool) Set(value bool) { func (a *AtomicBool) Set(value bool) {
if value { a.lock.Lock()
atomic.StoreInt32(&a.v, 1) defer a.lock.Unlock()
} else {
atomic.StoreInt32(&a.v, 0)
}
select { a.v = value
case a.waiter <- value:
// message sent for k, v := range a.listener {
default: select {
// no receiver on channel case v <- value:
// message sent
default:
// no receiver on channel
delete(a.listener, k)
}
} }
} }
func (a *AtomicBool) Wait(waitFor bool) { func (a *AtomicBool) Wait(waitFor bool) {
if a.Get() == waitFor { _ = a.WaitWithContext(context.Background(), waitFor)
return
}
for {
if v, ok := ReadChannelWithTimeout(a.waiter, 128*time.Millisecond); ok {
if v == waitFor {
return
}
} else {
if a.Get() == waitFor {
return
}
}
}
} }
func (a *AtomicBool) WaitWithTimeout(timeout time.Duration, waitFor bool) error { func (a *AtomicBool) WaitWithTimeout(timeout time.Duration, waitFor bool) error {
@@ -71,12 +63,25 @@ func (a *AtomicBool) WaitWithContext(ctx context.Context, waitFor bool) error {
return nil return nil
} }
uuid, _ := langext.NewHexUUID()
waitchan := make(chan bool)
a.lock.Lock()
a.listener[uuid] = waitchan
a.lock.Unlock()
defer func() {
a.lock.Lock()
delete(a.listener, uuid)
a.lock.Unlock()
}()
for { for {
if err := ctx.Err(); err != nil { if err := ctx.Err(); err != nil {
return err return err
} }
timeOut := 128 * time.Millisecond timeOut := 1024 * time.Millisecond
if dl, ok := ctx.Deadline(); ok { if dl, ok := ctx.Deadline(); ok {
timeOutMax := dl.Sub(time.Now()) timeOutMax := dl.Sub(time.Now())
@@ -87,7 +92,7 @@ func (a *AtomicBool) WaitWithContext(ctx context.Context, waitFor bool) error {
} }
} }
if v, ok := ReadChannelWithTimeout(a.waiter, timeOut); ok { if v, ok := ReadChannelWithTimeout(waitchan, timeOut); ok {
if v == waitFor { if v == waitFor {
return nil return nil
} }