Skip to content

Commit

Permalink
documentation improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
m-szalik committed Mar 3, 2024
1 parent 9bcf769 commit a2cc001
Show file tree
Hide file tree
Showing 16 changed files with 198 additions and 64 deletions.
98 changes: 93 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,97 @@
# goutils
# Project goutils
[![Go](https://github.com/m-szalik/goutils/actions/workflows/go.yml/badge.svg)](https://github.com/m-szalik/goutils/actions/workflows/go.yml)
Simple and useful functions for go.

## Usage
## Package: goutils
```shell
go get github.com/m-szalik/goutils
```
import (
"github.com/m-szalik/goutils"
)
### Converters
* `BoolToStr(b bool, trueVal, falseVal string) string`
BoolToStr - return string for true or false bool value
* `BoolTo[T interface{}](b bool, trueVal, falseVal T) T`
BoolTo - return T object for true or false bool value
* `HexToInt(hex string) (int, error)`
HexToInt convert hex representation to int
* `ParseBool(str string) (bool, error)`
ParseBool - return bool. True is one of "true", "1", "on", false is one of "false", "0", "off"
* `ParseValue(str string) interface{}`
ParseValue - converts string to one of int64, flot64, string, bool, nil.
If impossible to covert the same string is returned.
* `AsFloat64(i interface{}) (float64, error)`
AsFloat64 - convert multiple types (float32,int,int32,int64,string,[]byte) to float64.
* `RoundFloat(val float64, precision uint) float64`
RoundFloat round float64 number

### Slices
* `SliceIndexOf(slice []interface{}, e interface{}) int`
SliceIndexOf find an element in a slice.
Returns index of the element in a slice or -1 if not found.
* `SliceContains(slice []interface{}, e interface{}) bool`
SliceContains check if slice contains the element
* `SliceRemove[T comparable](slice []T, e any) ([]T, int)`
SliceRemove remove the element from a slice.
Returns []T = new slice, int number of removed elements.
* `SliceMap[I interface{}, O interface{}](inputData []I, mapper func(I) O) []O`
Map slice to slice of different object.
Example:
```go
sliceOfStrings := goutils.SliceMap[int, string]([]int{2, 7, -11}, func(i int) string { return fmt.Sprint(i) })
```

### Other utils
* `ExitOnError(err error, code int, message string, messageArgs ...interface{})`
* `Env(name string, def string) string`
* `EnvInt(name string, def int) int`
* `CloseQuietly(closer io.Closer)`
* `Root(x, n float64) float64`
Root - this function calculate approximation of n-th root of a number.

### TimeProvider
An abstraction for `time.Now()` that allows independent testing.
```go
type TimeProvider interface {
Now() time.Time
}
```

Create TimeProvider:
```go
tpMock := goutils.NewMockTimeProvider()
tpSystem := goutils.SystemTimeProvider()
```

### StopWatch
```go
sw := goutils.NewStopWatch()
sw.Start()
// some task here
sw.Stop()
fmt.Printf("execution of a task took %s", sw)
```


## Package: collector
```shell
go get github.com/m-szalik/goutils/collector
```
Few implementation of collections including:
* `NewRollingCollection` - Collection that keeps last N added elements.
* `NewSimpleCollection` - Collection that keeps all elements, slice that grows when needed.
* `NewTimedCollection` - Collection that keeps elements for defined duration only.
* `NewDataPointsCollector` - Collection that can calculate Avg, Max or Min over a time window.
* `NewStack` - implementation of stack data structure.


## Package: throttle
```shell
go get github.com/m-szalik/goutils/throttle
```
Few implementation throttling.

## Package: pubsub
```shell
go get github.com/m-szalik/goutils/pubsub
```
Simple Publish-Subscribe implementation based on channels.

1 change: 1 addition & 0 deletions close.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package goutils

import "io"

// CloseQuietly close quietly. Ignore an error.
func CloseQuietly(closer io.Closer) {
_ = closer.Close()
}
2 changes: 1 addition & 1 deletion collector/collections-simple.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ func (c *simpleCollection[T]) String() string {
return strings.Join(strs, ",")
}

// NewSimpleCollection - collection that keeps elements
// NewSimpleCollection - collection that keeps all elements, slice that grows when needed
func NewSimpleCollection[T comparable]() Collection[T] {
return &simpleCollection[T]{
lock: sync.Mutex{},
Expand Down
1 change: 1 addition & 0 deletions collector/data-points-collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ func newDataPointsCollectorInternal(maxSamples int, tp goutils.TimeProvider) Dat
}
}

// NewDataPointsCollector collection that can calculate Avg, Max or Min over a time window.
func NewDataPointsCollector(maxSamples int) DataPointsCollector {
return newDataPointsCollectorInternal(maxSamples, goutils.SystemTimeProvider())
}
14 changes: 13 additions & 1 deletion converters.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ import (
var parseValueIntRegEx, _ = regexp.Compile("^-?\\d+$")
var parseValueFloatRegEx, _ = regexp.Compile("^-?\\d+\\.\\d+$")

// BoolToStr - return string for true or false bool value
func BoolToStr(b bool, trueVal, falseVal string) string {
return BoolTo(b, trueVal, falseVal)
}

// BoolTo - return T object for true or false bool value
func BoolTo[T interface{}](b bool, trueVal, falseVal T) T {
if b {
return trueVal
Expand All @@ -23,6 +25,7 @@ func BoolTo[T interface{}](b bool, trueVal, falseVal T) T {
}
}

// HexToInt convert hex representation to int
func HexToInt(hex string) (int, error) {
hex = strings.Replace(hex, "0x", "", -1)
hex = strings.Replace(hex, "0X", "", -1)
Expand All @@ -46,7 +49,8 @@ func ParseBool(str string) (bool, error) {
}
}

// ParseValue - returns one of int64, flot64, string, bool, nil
// ParseValue - converts string to one of int64, flot64, string, bool, nil.
// If impossible to covert the same string is returned.
func ParseValue(str string) interface{} {
if b, err := ParseBool(str); err == nil {
return b
Expand Down Expand Up @@ -77,6 +81,7 @@ func ParseValue(str string) interface{} {
return str
}

// AsFloat64 - convert multiple types (float32,int,int32,int64,string,[]byte) to float64
func AsFloat64(i interface{}) (float64, error) {
if i == nil {
return 0, fmt.Errorf("cannot convert nil to float64")
Expand All @@ -92,6 +97,12 @@ func AsFloat64(i interface{}) (float64, error) {
return float64(v), nil
case int64:
return float64(v), nil
case []byte:
f, err := strconv.ParseFloat(strings.TrimSpace(string(v)), 64)
if err != nil {
return 0, fmt.Errorf("cannot convert '%s' to float64 - %w", v, err)
}
return f, nil
case string:
f, err := strconv.ParseFloat(strings.TrimSpace(v), 64)
if err != nil {
Expand All @@ -103,6 +114,7 @@ func AsFloat64(i interface{}) (float64, error) {
}
}

// RoundFloat round float64 number
func RoundFloat(val float64, precision uint) float64 {
ratio := math.Pow(10, float64(precision))
return math.Round(val*ratio) / ratio
Expand Down
12 changes: 0 additions & 12 deletions mapper.go

This file was deleted.

28 changes: 0 additions & 28 deletions mapper_test.go

This file was deleted.

3 changes: 3 additions & 0 deletions os.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ func ExitNow(code int, message string, messageArgs ...interface{}) {
os.Exit(code)
}

// ExitOnError exit the program if error occur
func ExitOnError(err error, code int, message string, messageArgs ...interface{}) {
if err != nil {
ExitNow(code, message, messageArgs...)
}
}

// Env retrieves the value of the environment variable named by the key. If not defined then default value is returned.
func Env(name string, def string) string {
s := os.Getenv(name)
if s == "" {
Expand All @@ -27,6 +29,7 @@ func Env(name string, def string) string {
return s
}

// EnvInt retrieves the value as int of the environment variable named by the key. If not defined then default value is returned.
func EnvInt(name string, def int) int {
s := os.Getenv(name)
if s == "" {
Expand Down
18 changes: 18 additions & 0 deletions slices.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package goutils

// SliceIndexOf find an element in a slice
// return index of the element in a slice or -1 if not found
func SliceIndexOf(slice []interface{}, e interface{}) int {
for i, a := range slice {
if a == e {
Expand All @@ -9,10 +11,13 @@ func SliceIndexOf(slice []interface{}, e interface{}) int {
return -1
}

// SliceContains check if slice contains the element
func SliceContains(slice []interface{}, e interface{}) bool {
return SliceIndexOf(slice, e) >= 0
}

// SliceRemove remove the element from a slice.
// return []T = new slice, int number of removed elements
func SliceRemove[T comparable](slice []T, e any) ([]T, int) {
mySlice := slice
size := len(slice)
Expand All @@ -30,3 +35,16 @@ func SliceRemove[T comparable](slice []T, e any) ([]T, int) {
return slice, removed
}
}

// SliceMap - map slice to slice of different object.
// sliceOfStrings := SliceMap[int, string]([]int{2, 7, -11}, func(i int) string { return fmt.Sprint(i) })
func SliceMap[I interface{}, O interface{}](inputData []I, mapper func(I) O) []O {
if inputData == nil {
return nil
}
outputData := make([]O, len(inputData))
for i := 0; i < len(inputData); i++ {
outputData[i] = mapper(inputData[i])
}
return outputData
}
27 changes: 27 additions & 0 deletions slices_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package goutils

import (
"fmt"
"github.com/stretchr/testify/assert"
"strings"
"testing"
)

Expand Down Expand Up @@ -92,3 +94,28 @@ func TestSliceRemove(t *testing.T) {
})

}

func Test_SliceMap(t *testing.T) {
for _, vSlice := range [][]string{
{"a", "shd ", "faaz"},
{"xxkjd ", "!@#", "faz"},
{"xxkjd ", "a(z@", ""},
{},
} {
t.Run(fmt.Sprintf("MapSlice_%v", vSlice), func(t *testing.T) {
res := SliceMap[string, string](vSlice, func(s string) string {
return strings.ToUpper(s)
})
assert.Equal(t, len(vSlice), len(res))
for i := 0; i < len(vSlice); i++ {
upper := strings.ToUpper(vSlice[i])
assert.Equal(t, upper, res[i])
}
})
}
}

func TestSliceMapIntToString(t *testing.T) {
sliceOfStrings := SliceMap[int, string]([]int{2, 7, -11}, func(i int) string { return fmt.Sprint(i) })
assert.Equal(t, []string{"2", "7", "-11"}, sliceOfStrings)
}
25 changes: 16 additions & 9 deletions stopwatch.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"time"
)

// StopWatch - stopWatch utility
type StopWatch interface {
Start() StopWatch
Stop()
Expand All @@ -14,15 +15,16 @@ type StopWatch interface {
}

type stopWatch struct {
lock sync.Mutex
duration time.Duration
lastStart *time.Time
timeProvider TimeProvider
lock sync.Mutex
duration time.Duration
lastStart *time.Time
}

func (s *stopWatch) Start() StopWatch {
s.lock.Lock()
defer s.lock.Unlock()
now := time.Now()
now := s.timeProvider.Now()
s.lastStart = &now
return s
}
Expand All @@ -31,7 +33,7 @@ func (s *stopWatch) Stop() {
s.lock.Lock()
defer s.lock.Unlock()
if s.lastStart != nil {
dur := time.Now().Sub(*s.lastStart)
dur := s.timeProvider.Now().Sub(*s.lastStart)
s.lastStart = nil
s.duration += dur
}
Expand All @@ -58,17 +60,22 @@ func (s *stopWatch) durationCalculation() (time.Duration, bool) {
s.lock.Lock()
defer s.lock.Unlock()
if s.lastStart != nil {
dur := time.Now().Sub(*s.lastStart)
dur := s.timeProvider.Now().Sub(*s.lastStart)
return s.duration + dur, true
} else {
return s.duration, false
}
}

func NewStopWatch() StopWatch {
return NewStopWatchWithTimeProvider(SystemTimeProvider())
}

func NewStopWatchWithTimeProvider(timeProvider TimeProvider) StopWatch {
return &stopWatch{
lock: sync.Mutex{},
duration: time.Duration(0),
lastStart: nil,
timeProvider: timeProvider,
lock: sync.Mutex{},
duration: time.Duration(0),
lastStart: nil,
}
}
Loading

0 comments on commit a2cc001

Please sign in to comment.