Compare commits

...
13 Commits
Author SHA1 Message Date
dudaodong c88fd3db86 fix: fix go vet issue, method Unwrap() []error 2024-02-20 11:41:58 +08:00
dudaodong 27d19d1717 fix: rename Seek to SeekOffset fix go vet check issue 2024-02-20 11:39:41 +08:00
dudaodong da24bae6b4 doc: add doc for Optional type 2024-02-20 11:22:39 +08:00
donutloop 3cd9d6b68c Add functional predicate (#171)
Enable the execution of assertion functions in a functional manner.
2024-02-20 09:55:39 +08:00
dudaodong 874d09f331 refactor: make stream struct exported 2024-02-19 15:55:08 +08:00
dudaodong fdf251ac98 add govet check to github action file 2024-02-19 15:50:19 +08:00
dudaodong 7ec2533b7a feat: add MapToStruct 2024-02-19 13:50:06 +08:00
dudaodong 9fd0603f4a fix: fix copylocks warning in Optional struct methods 2024-02-19 10:22:28 +08:00
dudaodong 9f7b416a8d Merge branch 'main' into v2 2024-02-19 10:00:21 +08:00
donutloop bf4b2b5fd6 Add optional (#170)
* Add optional

Wrapper container with easy to understand helper methods

* Add test and rewrite test

* Add panic test

* Add TestOrElseGetHappyPath
2024-02-19 09:59:42 +08:00
dudaodong 22af59565e fix: fix issue #168 2024-02-06 16:59:01 +08:00
dudaodong f9e047f190 feat: add SubInBetween 2024-02-06 16:47:30 +08:00
dudaodong fa298b740d add playground demo 2024-02-01 10:41:09 +08:00
27 changed files with 1531 additions and 72 deletions
+3 -3
View File
@@ -3,11 +3,9 @@ on:
push: push:
branches: branches:
- main - main
# - v2
pull_request: pull_request:
branches: branches:
- main - main
# - v2
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -17,8 +15,10 @@ jobs:
fetch-depth: 2 fetch-depth: 2
- uses: actions/setup-go@v2 - uses: actions/setup-go@v2
with: with:
go-version: "1.18" go-version: "1.20"
- name: Run coverage - name: Run coverage
run: go test -v ./... -coverprofile=coverage.txt -covermode=atomic run: go test -v ./... -coverprofile=coverage.txt -covermode=atomic
- name: Run govet
run: go vet -v ./...
- name: Upload coverage to Codecov - name: Upload coverage to Codecov
run: bash <(curl -s https://codecov.io/bash) run: bash <(curl -s https://codecov.io/bash)
+3 -1
View File
@@ -708,7 +708,7 @@ import "github.com/duke-git/lancet/v2/fileutil"
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/fileutil.md#WriteCsvFile)] [[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/fileutil.md#WriteCsvFile)]
- **<big>WriteMapsToCsv</big>** : write slice of map to csv file. - **<big>WriteMapsToCsv</big>** : write slice of map to csv file.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/fileutil.md#WriteMapsToCsv)] [[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/fileutil.md#WriteMapsToCsv)]
[[play](https://go.dev/play/p/dAXm58Q5U1o)] [[play](https://go.dev/play/p/umAIomZFV1c)]
- **<big>WriteBytesToFile</big>** : write bytes to target file. - **<big>WriteBytesToFile</big>** : write bytes to target file.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/fileutil.md#WriteBytesToFile)] [[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/fileutil.md#WriteBytesToFile)]
[[play](https://go.dev/play/p/s7QlDxMj3P8)] [[play](https://go.dev/play/p/s7QlDxMj3P8)]
@@ -1182,8 +1182,10 @@ import "github.com/duke-git/lancet/v2/slice"
[[play](https://go.dev/play/p/v2U2deugKuV)] [[play](https://go.dev/play/p/v2U2deugKuV)]
- **<big>DeleteAt</big>** : delete the element of slice at index. - **<big>DeleteAt</big>** : delete the element of slice at index.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#DeleteAt)] [[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#DeleteAt)]
[[play](https://go.dev/play/p/800B1dPBYyd)]
- **<big>DeleteRange</big>** : delete the element of slice from start index to end indexexclude). - **<big>DeleteRange</big>** : delete the element of slice from start index to end indexexclude).
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#DeleteRange)] [[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#DeleteRange)]
[[play](https://go.dev/play/p/945HwiNrnle)]
- **<big>Drop</big>** : drop n elements from the start of a slice. - **<big>Drop</big>** : drop n elements from the start of a slice.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#Drop)] [[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#Drop)]
[[play](https://go.dev/play/p/jnPO2yQsT8H)] [[play](https://go.dev/play/p/jnPO2yQsT8H)]
+3 -1
View File
@@ -709,7 +709,7 @@ import "github.com/duke-git/lancet/v2/fileutil"
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/fileutil.md#WriteCsvFile)] [[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/fileutil.md#WriteCsvFile)]
- **<big>WriteMapsToCsv</big>** : 将map切片写入csv文件中。 - **<big>WriteMapsToCsv</big>** : 将map切片写入csv文件中。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/fileutil.md#WriteMapsToCsv)] [[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/fileutil.md#WriteMapsToCsv)]
[[play](https://go.dev/play/p/dAXm58Q5U1o)] [[play](https://go.dev/play/p/umAIomZFV1c)]
- **<big>WriteBytesToFile</big>** : 将 bytes 写入文件。 - **<big>WriteBytesToFile</big>** : 将 bytes 写入文件。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/fileutil.md#WriteBytesToFile)] [[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/fileutil.md#WriteBytesToFile)]
[[play](https://go.dev/play/p/s7QlDxMj3P8)] [[play](https://go.dev/play/p/s7QlDxMj3P8)]
@@ -1181,8 +1181,10 @@ import "github.com/duke-git/lancet/v2/slice"
[[play](https://go.dev/play/p/v2U2deugKuV)] [[play](https://go.dev/play/p/v2U2deugKuV)]
- **<big>DeleteAt</big>** : 删除切片中指定索引到的元素。 - **<big>DeleteAt</big>** : 删除切片中指定索引到的元素。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#DeleteAt)] [[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#DeleteAt)]
[[play](https://go.dev/play/p/800B1dPBYyd)]
- **<big>DeleteRange</big>** : 删除切片中指定开始索引到结束索引的元素。 - **<big>DeleteRange</big>** : 删除切片中指定开始索引到结束索引的元素。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#DeleteRange)] [[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#DeleteRange)]
[[play](https://go.dev/play/p/945HwiNrnle)]
- **<big>Drop</big>** : 从切片头部删除 n 个元素。 - **<big>Drop</big>** : 从切片头部删除 n 个元素。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#Drop)] [[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#Drop)]
[[play](https://go.dev/play/p/jnPO2yQsT8H)] [[play](https://go.dev/play/p/jnPO2yQsT8H)]
+108
View File
@@ -0,0 +1,108 @@
package optional
import (
"sync"
)
// Optional is a type that may or may not contain a non-nil value.
type Optional[T any] struct {
value *T
mu *sync.RWMutex
}
// Empty returns an empty Optional instance.
func Empty[T any]() Optional[T] {
return Optional[T]{mu: &sync.RWMutex{}}
}
// Of returns an Optional with a non-nil value.
func Of[T any](value T) Optional[T] {
return Optional[T]{value: &value, mu: &sync.RWMutex{}}
}
// OfNullable returns an Optional for a given value, which may be nil.
func OfNullable[T any](value *T) Optional[T] {
if value == nil {
return Empty[T]()
}
return Optional[T]{value: value, mu: &sync.RWMutex{}}
}
// IsPresent checks if there is a value present.
func (o Optional[T]) IsPresent() bool {
o.mu.RLock()
defer o.mu.RUnlock()
return o.value != nil
}
// IsEmpty checks if the Optional is empty.
func (o Optional[T]) IsEmpty() bool {
return !o.IsPresent()
}
// IfPresent performs the given action with the value if a value is present.
func (o Optional[T]) IfPresent(action func(value T)) {
o.mu.RLock()
defer o.mu.RUnlock()
if o.value != nil {
action(*o.value)
}
}
// IfPresentOrElse performs the action with the value if present, otherwise performs the empty-based action.
func (o Optional[T]) IfPresentOrElse(action func(value T), emptyAction func()) {
o.mu.RLock()
defer o.mu.RUnlock()
if o.value != nil {
action(*o.value)
} else {
emptyAction()
}
}
// Get returns the value if present, otherwise panics.
func (o Optional[T]) Get() T {
o.mu.RLock()
defer o.mu.RUnlock()
if o.value == nil {
panic("Optional.Get: no value present")
}
return *o.value
}
// OrElse returns the value if present, otherwise returns other.
func (o Optional[T]) OrElse(other T) T {
o.mu.RLock()
defer o.mu.RUnlock()
if o.value != nil {
return *o.value
}
return other
}
// OrElseGet returns the value if present, otherwise invokes supplier and returns the result.
func (o Optional[T]) OrElseGet(supplier func() T) T {
o.mu.RLock()
defer o.mu.RUnlock()
if o.value != nil {
return *o.value
}
return supplier()
}
// OrElseThrow returns the value if present, otherwise returns an error.
func (o Optional[T]) OrElseThrow(errorSupplier func() error) (T, error) {
o.mu.RLock()
defer o.mu.RUnlock()
if o.value == nil {
return *new(T), errorSupplier()
}
return *o.value, nil
}
+151
View File
@@ -0,0 +1,151 @@
package optional
import (
"errors"
"testing"
"github.com/duke-git/lancet/v2/internal"
)
func TestEmpty(t *testing.T) {
assert := internal.NewAssert(t, "TestEmpty")
opt := Empty[int]()
assert.ShouldBeTrue(opt.IsEmpty())
}
func TestOf(t *testing.T) {
assert := internal.NewAssert(t, "TestOf")
value := 42
opt := Of(value)
assert.ShouldBeTrue(opt.IsPresent())
assert.Equal(opt.Get(), value)
}
func TestOfNullable(t *testing.T) {
assert := internal.NewAssert(t, "TestOfNullable")
var value *int = nil
opt := OfNullable(value)
assert.ShouldBeFalse(opt.IsPresent())
value = new(int)
*value = 42
opt = OfNullable(value)
assert.ShouldBeTrue(opt.IsPresent())
}
func TestOrElse(t *testing.T) {
assert := internal.NewAssert(t, "TestOrElse")
optEmpty := Empty[int]()
defaultValue := 100
val := optEmpty.OrElse(defaultValue)
assert.Equal(val, defaultValue)
optWithValue := Of(42)
val = optWithValue.OrElse(defaultValue)
assert.Equal(val, 42)
}
func TestOrElseGetHappyPath(t *testing.T) {
assert := internal.NewAssert(t, "TestOrElseGetHappyPath")
optWithValue := Of(42)
supplier := func() int { return 100 }
val := optWithValue.OrElseGet(supplier)
assert.Equal(val, 42)
}
func TestOrElseGet(t *testing.T) {
assert := internal.NewAssert(t, "TestOrElseGet")
optEmpty := Empty[int]()
supplier := func() int { return 100 }
val := optEmpty.OrElseGet(supplier)
assert.Equal(val, supplier())
}
func TestOrElseThrow(t *testing.T) {
assert := internal.NewAssert(t, "TestOrElseThrow")
optEmpty := Empty[int]()
_, err := optEmpty.OrElseThrow(func() error { return errors.New("no value") })
assert.Equal(err.Error(), "no value")
optWithValue := Of(42)
val, err := optWithValue.OrElseThrow(func() error { return errors.New("no value") })
assert.IsNil(err)
assert.Equal(val, 42)
}
func TestIfPresent(t *testing.T) {
assert := internal.NewAssert(t, "TestIfPresent")
called := false
action := func(value int) { called = true }
optEmpty := Empty[int]()
optEmpty.IfPresent(action)
assert.ShouldBeFalse(called)
called = false // Reset for next test
optWithValue := Of(42)
optWithValue.IfPresent(action)
assert.ShouldBeTrue(called)
}
func TestIfPresentOrElse(t *testing.T) {
assert := internal.NewAssert(t, "TestIfPresentOrElse")
// Test when value is present
calledWithValue := false
valueAction := func(value int) { calledWithValue = true }
emptyAction := func() { t.Errorf("Empty action should not be called when value is present") }
optWithValue := Of(42)
optWithValue.IfPresentOrElse(valueAction, emptyAction)
assert.ShouldBeTrue(calledWithValue)
// Test when value is not present
calledWithEmpty := false
valueAction = func(value int) { t.Errorf("Value action should not be called when value is not present") }
emptyAction = func() { calledWithEmpty = true }
optEmpty := Empty[int]()
optEmpty.IfPresentOrElse(valueAction, emptyAction)
assert.ShouldBeTrue(calledWithEmpty)
}
func TestGetWithPanicStandard(t *testing.T) {
assert := internal.NewAssert(t, "TestGetWithPanicStandard")
// Test when value is present
optWithValue := Of(42)
func() {
defer func() {
r := recover()
assert.IsNil(r)
}()
val := optWithValue.Get()
if val != 42 {
t.Errorf("Expected Get to return 42, got %v", val)
}
}()
// Test when value is not present
optEmpty := Empty[int]()
func() {
defer func() {
r := recover()
assert.IsNotNil(r)
}()
_ = optEmpty.Get()
}()
}
+422
View File
@@ -0,0 +1,422 @@
# Optional
Optional类型代表一个可选的值,它要么包含一个实际值,要么为空。
<div STYLE="page-break-after: always;"></div>
## 源码
- [https://github.com/duke-git/lancet/blob/main/datastructure/optional/optional.go](https://github.com/duke-git/lancet/blob/main/datastructure/optional/optional.go)
<div STYLE="page-break-after: always;"></div>
## 用法
```go
import (
optional "github.com/duke-git/lancet/v2/datastructure/optional"
)
```
<div STYLE="page-break-after: always;"></div>
## 目录
- [Of](#Of)
- [OfNullable](#OfNullable)
- [Empty](#Empty)
- [IsPresent](#IsPresent)
- [IsEmpty](#IsEmpty)
- [IfPresent](#IfPresent)
- [IfPresentOrElse](#IfPresentOrElse)
- [Get](#Get)
- [OrElse](#OrElse)
- [OrElseGet](#OrElseGet)
- [OrElseThrow](#OrElseThrow)
<div STYLE="page-break-after: always;"></div>
## 文档
### <span id="Of">Of</span>
<p>返回一个包含非空值的Optional。</p>
<b>函数签名:</b>
```go
func Of[T any](value T) Optional[T]
```
<b>示例:</b>
```go
package main
import (
"fmt"
optional "github.com/duke-git/lancet/v2/datastructure/optional"
)
func main() {
value := 42
opt := optional.Of(value)
fmt.Println(opt.Get())
// Output:
// 42
}
```
### <span id="OfNullable">OfNullable</span>
<p>返回一个包含给定值的Optional,该值可能为空 (nil)。</p>
<b>函数签名:</b>
```go
func OfNullable[T any](value *T) Optional[T]
```
<b>示例:</b>
```go
package main
import (
"fmt"
optional "github.com/duke-git/lancet/v2/datastructure/optional"
)
func main() {
var value *int = nil
opt := optional.OfNullable(value)
fmt.Println(opt.IsPresent())
value = new(int)
*value = 42
opt = optional.OfNullable(value)
fmt.Println(opt.IsPresent())
// Output:
// false
// true
}
```
### <span id="Empty">Empty</span>
<p>返回一个空Optional实例。</p>
<b>函数签名:</b>
```go
func Empty[T any]() Optional[T]
```
<b>示例:</b>
```go
package main
import (
"fmt"
optional "github.com/duke-git/lancet/v2/datastructure/optional"
)
func main() {
optEmpty := OfNullable.Empty[int]()
fmt.Println(optEmpty.IsEmpty())
// Output:
// true
}
```
### <span id="IsEmpty">IsEmpty</span>
<p>验证Optional是否为空。</p>
<b>函数签名:</b>
```go
func (o Optional[T]) IsEmpty() bool
```
<b>示例:</b>
```go
package main
import (
"fmt"
optional "github.com/duke-git/lancet/v2/datastructure/optional"
)
func main() {
optEmpty := OfNullable.Empty[int]()
fmt.Println(optEmpty.IsEmpty())
// Output:
// true
}
```
### <span id="IsPresent">IsPresent</span>
<p>检查当前Optional内是否存在值。</p>
<b>函数签名:</b>
```go
func (o Optional[T]) IsPresent() bool
```
<b>示例:</b>
```go
package main
import (
"fmt"
optional "github.com/duke-git/lancet/v2/datastructure/optional"
)
func main() {
var value *int = nil
opt := optional.OfNullable(value)
fmt.Println(opt.IsPresent())
value = new(int)
*value = 42
opt = optional.OfNullable(value)
fmt.Println(opt.IsPresent())
// Output:
// false
// true
}
```
### <span id="IfPresent">IfPresent</span>
<p>如果值存在,则使用action方法执行给定的操作。</p>
<b>函数签名:</b>
```go
func (o Optional[T]) IfPresent(action func(value T))
```
<b>示例:</b>
```go
package main
import (
"fmt"
optional "github.com/duke-git/lancet/v2/datastructure/optional"
)
func main() {
called := false
action := func(value int) { called = true }
optEmpty := optional.Empty[int]()
optEmpty.IfPresent(action)
fmt.Println(called)
called = false // Reset for next test
optWithValue := optional.Of(42)
optWithValue.IfPresent(action)
fmt.Println(optWithValue.IsPresent())
// Output:
// false
// true
}
```
### <span id="IfPresentOrElse">IfPresentOrElse</span>
<p>根据是否存在值执行相应的操作:有值则执行指定操作,没有值则执行默认操作。</p>
<b>函数签名:</b>
```go
func (o Optional[T]) IfPresentOrElse(action func(value T), emptyAction func())
```
<b>示例:</b>
```go
package main
import (
"fmt"
optional "github.com/duke-git/lancet/v2/datastructure/optional"
)
func main() {
calledWithValue := false
valueAction := func(value int) { calledWithValue = true }
emptyAction := func() { t.Errorf("Empty action should not be called when value is present") }
optWithValue := optional.Of(42)
optWithValue.IfPresentOrElse(valueAction, emptyAction)
fmt.Println(calledWithValue)
calledWithEmpty := false
valueAction = func(value int) { t.Errorf("Value action should not be called when value is not present") }
emptyAction = func() { calledWithEmpty = true }
optEmpty := optional.Empty[int]()
optEmpty.IfPresentOrElse(valueAction, emptyAction)
fmt.Println(calledWithEmpty)
// Output:
// true
// true
}
```
### <span id="Get">Get</span>
<p>如果存在,返回该值,否则引发panic。</p>
<b>函数签名:</b>
```go
func (o Optional[T]) Get() T
```
<b>示例:</b>
```go
package main
import (
"fmt"
optional "github.com/duke-git/lancet/v2/datastructure/optional"
)
func main() {
value := 42
opt := optional.Of(value)
fmt.Println(opt.Get())
// Output:
// 42
}
```
### <span id="OrElse">OrElse</span>
<p>检查Optional值是否存在,如果存在,则直接返回该值。如果不存在,返回参数other值。</p>
<b>函数签名:</b>
```go
func (o Optional[T]) OrElse(other T) T
```
<b>示例:</b>
```go
package main
import (
"fmt"
optional "github.com/duke-git/lancet/v2/datastructure/optional"
)
func main() {
optEmpty := optional.Empty[int]()
val := optEmpty.OrElse(100)
fmt.Println(val)
optWithValue := optional.Of(42)
val = optWithValue.OrElse(100)
fmt.Println(val)
// Output:
// 100
// 42
}
```
### <span id="OrElseGet">OrElseGet</span>
<p>检查Optional值是否存在,如果存在,则直接返回该值。如果不存在,则调用一个提供的函数 (supplier),并返回该函数的执行结果。</p>
<b>函数签名:</b>
```go
func (o Optional[T]) OrElseGet(supplier func() T) T
```
<b>示例:</b>
```go
package main
import (
"fmt"
optional "github.com/duke-git/lancet/v2/datastructure/optional"
)
func main() {
optEmpty := optional.Empty[int]()
supplier := func() int { return 100 }
val := optEmpty.OrElseGet(supplier)
fmt.Println(val)
// Output:
// 100
}
```
### <span id="OrElseThrow">OrElseThrow</span>
<p>检查Optional值是否存在,如果存在,则直接返回该值,否则返回错误。</p>
<b>函数签名:</b>
```go
func (o Optional[T]) OrElseThrow(errorSupplier func() error) (T, error)
```
<b>示例:</b>
```go
package main
import (
"fmt"
optional "github.com/duke-git/lancet/v2/datastructure/optional"
)
func main() {
optEmpty := optional.Empty[int]()
_, err := optEmpty.OrElseThrow(func() error { return errors.New("no value") })
fmt.Println(err.Error())
optWithValue := optional.Of(42)
val, err := optWithValue.OrElseThrow(func() error { return errors.New("no value") })
fmt.Println(val)
fmt.Println(err)
// Output:
// no value
// 42
// nil
}
```
+2 -2
View File
@@ -759,7 +759,7 @@ func main() {
func WriteMapsToCsv(filepath string, records []map[string]any, appendToExistingFile bool, delimiter rune, headers ...[]string) error func WriteMapsToCsv(filepath string, records []map[string]any, appendToExistingFile bool, delimiter rune, headers ...[]string) error
``` ```
<b>示例:</b> <b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/umAIomZFV1c)</span></b>
```go ```go
package main package main
@@ -782,7 +782,7 @@ func main() {
} }
headers := []string{"Name", "Age", "Gender"} headers := []string{"Name", "Age", "Gender"}
err := WriteMapsToCsv(csvFilePath, records, false, ';', headers) err := fileutil.WriteMapsToCsv(csvFilePath, records, false, ';', headers)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
+2 -2
View File
@@ -525,7 +525,7 @@ func main() {
func DeleteAt[T any](slice []T, index int) []T func DeleteAt[T any](slice []T, index int) []T
``` ```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/pJ-d6MUWcvK)</span></b> <b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/800B1dPBYyd)</span></b>
```go ```go
import ( import (
@@ -565,7 +565,7 @@ func main() {
func DeleteRange[T any](slice []T, start, end int) []T func DeleteRange[T any](slice []T, start, end int) []T
``` ```
<b>示例:</b> <b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/945HwiNrnle)</span></b>
```go ```go
import ( import (
+36 -2
View File
@@ -60,6 +60,7 @@ import (
- [ContainsAll](#ContainsAll) - [ContainsAll](#ContainsAll)
- [ContainsAny](#ContainsAny) - [ContainsAny](#ContainsAny)
- [RemoveWhiteSpace](#RemoveWhiteSpace) - [RemoveWhiteSpace](#RemoveWhiteSpace)
- [SubInBetween](#SubInBetween)
<div STYLE="page-break-after: always;"></div> <div STYLE="page-break-after: always;"></div>
@@ -1096,10 +1097,10 @@ import (
func main() { func main() {
result1 := strutil.IsNotBlank("") result1 := strutil.IsNotBlank("")
result2 := strutil.IsNotBlank(" ") result2 := strutil.IsNotBlank(" ")
result3 := strutil.IsNotBlank("\t\v\f\n") result3 := strutil.IsNotBlank("\t\v\f\n")
result4 := strutil.IsNotBlank(" 中文") result4 := strutil.IsNotBlank(" 中文")
result5 := strutil.IsNotBlank(" world ") result5 := strutil.IsNotBlank(" world ")
fmt.Println(result1) fmt.Println(result1)
fmt.Println(result2) fmt.Println(result2)
@@ -1462,3 +1463,36 @@ func main() {
// hello world // hello world
} }
``` ```
### <span id="SubInBetween">SubInBetween</span>
<p>获取字符串中指定的起始字符串start和终止字符串end直接的子字符串。</p>
<b>函数签名:</b>
```go
func SubInBetween(str string, start string, end string) string
```
<b>示例:</b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/strutil"
)
func main() {
str := "abcde"
result1 := strutil.SubInBetween(str, "", "de")
result2 := strutil.SubInBetween(str, "a", "d")
fmt.Println(result1)
fmt.Println(result2)
// Output:
// abc
// bc
}
```
@@ -0,0 +1,416 @@
# Optional
Optional is a type that may or may not contain a non-nil value.
<div STYLE="page-break-after: always;"></div>
## Source
- [https://github.com/duke-git/lancet/blob/main/datastructure/optional/optional.go](https://github.com/duke-git/lancet/blob/main/datastructure/optional/optional.go)
<div STYLE="page-break-after: always;"></div>
## Usage
```go
import (
optional "github.com/duke-git/lancet/v2/datastructure/optional"
)
```
<div STYLE="page-break-after: always;"></div>
## Index
- [Of](#Of)
- [OfNullable](#OfNullable)
- [Empty](#Empty)
- [IsPresent](#IsPresent)
- [IsEmpty](#IsEmpty)
- [IfPresent](#IfPresent)
- [IfPresentOrElse](#IfPresentOrElse)
- [Get](#Get)
- [OrElse](#OrElse)
- [OrElseGet](#OrElseGet)
- [OrElseThrow](#OrElseThrow)
<div STYLE="page-break-after: always;"></div>
## Documentation
### <span id="Of">Of</span>
<p>Returns an Optional with a non-nil value.</p>
<b>Signature:</b>
```go
func Of[T any](value T) Optional[T]
```
<b>Example:</b>
```go
package main
import (
"fmt"
optional "github.com/duke-git/lancet/v2/datastructure/optional"
)
func main() {
value := 42
opt := optional.Of(value)
fmt.Println(opt.Get())
// Output:
// 42
}
```
### <span id="OfNullable">OfNullable</span>
<p>Returns an Optional for a given value, which may be nil.</p>
<b>Signature:</b>
```go
func OfNullable[T any](value *T) Optional[T]
```
<b>Example:</b>
```go
package main
import (
"fmt"
optional "github.com/duke-git/lancet/v2/datastructure/optional"
)
func main() {
var value *int = nil
opt := optional.OfNullable(value)
fmt.Println(opt.IsPresent())
value = new(int)
*value = 42
opt = optional.OfNullable(value)
fmt.Println(opt.IsPresent())
// Output:
// false
// true
}
```
### <span id="Empty">Empty</span>
<p>Returns an empty Optional instance.</p>
<b>Signature:</b>
```go
func Empty[T any]() Optional[T]
```
<b>Example:</b>
```go
package main
import (
"fmt"
optional "github.com/duke-git/lancet/v2/datastructure/optional"
)
func main() {
optEmpty := OfNullable.Empty[int]()
fmt.Println(optEmpty.IsEmpty())
// Output:
// true
}
```
### <span id="IsEmpty">IsEmpty</span>
<p>Checks if the Optional is empty.</p>
<b>Signature:</b>
```go
func (o Optional[T]) IsEmpty() bool
```
<b>Example:</b>
```go
package main
import (
"fmt"
optional "github.com/duke-git/lancet/v2/datastructure/optional"
)
func main() {
optEmpty := OfNullable.Empty[int]()
fmt.Println(optEmpty.IsEmpty())
// Output:
// true
}
```
### <span id="IsPresent">IsPresent</span>
<p>Checks if there is a value present.</p>
<b>Signature:</b>
```go
func (o Optional[T]) IsPresent() bool
```
<b>Example:</b>
```go
package main
import (
"fmt"
optional "github.com/duke-git/lancet/v2/datastructure/optional"
)
func main() {
var value *int = nil
opt := optional.OfNullable(value)
fmt.Println(opt.IsPresent())
value = new(int)
*value = 42
opt = optional.OfNullable(value)
fmt.Println(opt.IsPresent())
// Output:
// false
// true
}
```
### <span id="IfPresent">IfPresent</span>
<p>Performs the given action with the value if a value is present.</p>
<b>Signature:</b>
```go
func (o Optional[T]) IfPresent(action func(value T))
```
<b>Example:</b>
```go
package main
import (
"fmt"
optional "github.com/duke-git/lancet/v2/datastructure/optional"
)
func main() {
called := false
action := func(value int) { called = true }
optEmpty := optional.Empty[int]()
optEmpty.IfPresent(action)
fmt.Println(called)
called = false // Reset for next test
optWithValue := optional.Of(42)
optWithValue.IfPresent(action)
fmt.Println(optWithValue.IsPresent())
// Output:
// false
// true
}
```
### <span id="IfPresentOrElse">IfPresentOrElse</span>
<p>Performs the action with the value if present, otherwise performs the empty-based action.</p>
<b>Signature:</b>
```go
func (o Optional[T]) IfPresentOrElse(action func(value T), emptyAction func())
```
<b>Example:</b>
```go
package main
import (
"fmt"
optional "github.com/duke-git/lancet/v2/datastructure/optional"
)
func main() {
calledWithValue := false
valueAction := func(value int) { calledWithValue = true }
emptyAction := func() { t.Errorf("Empty action should not be called when value is present") }
optWithValue := optional.Of(42)
optWithValue.IfPresentOrElse(valueAction, emptyAction)
fmt.Println(calledWithValue)
calledWithEmpty := false
valueAction = func(value int) { t.Errorf("Value action should not be called when value is not present") }
emptyAction = func() { calledWithEmpty = true }
optEmpty := optional.Empty[int]()
optEmpty.IfPresentOrElse(valueAction, emptyAction)
fmt.Println(calledWithEmpty)
// Output:
// true
// true
}
```
### <span id="Get">Get</span>
<p>Returns the value if present, otherwise panics.</p>
<b>Signature:</b>
```go
func (o Optional[T]) Get() T
```
<b>Example:</b>
```go
package main
import (
"fmt"
optional "github.com/duke-git/lancet/v2/datastructure/optional"
)
func main() {
value := 42
opt := optional.Of(value)
fmt.Println(opt.Get())
// Output:
// 42
}
```
### <span id="OrElse">OrElse</span>
<p>Returns the value if present, otherwise returns other.</p>
<b>Signature:</b>
```go
func (o Optional[T]) OrElse(other T) T
```
<b>Example:</b>
```go
package main
import (
"fmt"
optional "github.com/duke-git/lancet/v2/datastructure/optional"
)
func main() {
optEmpty := optional.Empty[int]()
val := optEmpty.OrElse(100)
fmt.Println(val)
optWithValue := optional.Of(42)
val = optWithValue.OrElse(100)
fmt.Println(val)
// Output:
// 100
// 42
}
```
### <span id="OrElseGet">OrElseGet</span>
<p>Returns the value if present, otherwise invokes supplier and returns the result.</p>
<b>Signature:</b>
```go
func (o Optional[T]) OrElseGet(supplier func() T) T
```
<b>Example:</b>
```go
package main
import (
"fmt"
optional "github.com/duke-git/lancet/v2/datastructure/optional"
)
func main() {
optEmpty := optional.Empty[int]()
supplier := func() int { return 100 }
val := optEmpty.OrElseGet(supplier)
fmt.Println(val)
// Output:
// 100
}
```
### <span id="OrElseThrow">OrElseThrow</span>
<p>Returns the value if present, otherwise returns an error.</p>
<b>Signature:</b>
```go
func (o Optional[T]) OrElseThrow(errorSupplier func() error) (T, error)
```
<b>Example:</b>
```go
package main
import (
"fmt"
optional "github.com/duke-git/lancet/v2/datastructure/optional"
)
func main() {
optEmpty := optional.Empty[int]()
_, err := optEmpty.OrElseThrow(func() error { return errors.New("no value") })
fmt.Println(err.Error())
optWithValue := optional.Of(42)
val, err := optWithValue.OrElseThrow(func() error { return errors.New("no value") })
fmt.Println(val)
fmt.Println(err)
// Output:
// no value
// 42
// nil
}
```
+1 -2
View File
@@ -45,7 +45,6 @@ import (
- [Sha](#Sha) - [Sha](#Sha)
- [ReadCsvFile](#ReadCsvFile) - [ReadCsvFile](#ReadCsvFile)
- [WriteCsvFile](#WriteCsvFile) - [WriteCsvFile](#WriteCsvFile)
- [WriteCsvFile](#WriteCsvFile)
- [WriteMapsToCsv](#WriteMapsToCsv) - [WriteMapsToCsv](#WriteMapsToCsv)
- [WriteStringToFile](#WriteStringToFile) - [WriteStringToFile](#WriteStringToFile)
- [WriteBytesToFile](#WriteBytesToFile) - [WriteBytesToFile](#WriteBytesToFile)
@@ -760,7 +759,7 @@ func main() {
func WriteMapsToCsv(filepath string, records []map[string]any, appendToExistingFile bool, delimiter rune, headers ...[]string) error func WriteMapsToCsv(filepath string, records []map[string]any, appendToExistingFile bool, delimiter rune, headers ...[]string) error
``` ```
<b>Example:</b> <b>Example:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/umAIomZFV1c)</span></b>
```go ```go
package main package main
+7 -7
View File
@@ -524,7 +524,7 @@ func main() {
func DeleteAt[T any](slice []T, index int) func DeleteAt[T any](slice []T, index int)
``` ```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/pJ-d6MUWcvK)</span></b> <b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/800B1dPBYyd)</span></b>
```go ```go
import ( import (
@@ -563,7 +563,7 @@ func main() {
func DeleteRange[T any](slice []T, start, end int) []T func DeleteRange[T any](slice []T, start, end int) []T
``` ```
<b>Example:</b> <b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/945HwiNrnle)</span></b>
```go ```go
import ( import (
@@ -574,11 +574,11 @@ import (
func main() { func main() {
chars := []string{"a", "b", "c", "d", "e"} chars := []string{"a", "b", "c", "d", "e"}
result1 := DeleteRange(chars, 0, 0) result1 := slice.DeleteRange(chars, 0, 0)
result2 := DeleteRange(chars, 0, 1) result2 := slice.DeleteRange(chars, 0, 1)
result3 := DeleteRange(chars, 0, 3) result3 := slice.DeleteRange(chars, 0, 3)
result4 := DeleteRange(chars, 0, 4) result4 := slice.DeleteRange(chars, 0, 4)
result5 := DeleteRange(chars, 0, 5) result5 := slice.DeleteRange(chars, 0, 5)
fmt.Println(result1) fmt.Println(result1)
fmt.Println(result2) fmt.Println(result2)
+34
View File
@@ -1463,3 +1463,37 @@ func main() {
// hello world // hello world
} }
``` ```
### <span id="SubInBetween">SubInBetween</span>
<p>Return substring between the start and end position(excluded) of source string.</p>
<b>Signature:</b>
```go
func SubInBetween(str string, start string, end string) string
```
<b>Example:</b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/strutil"
)
func main() {
str := "abcde"
result1 := strutil.SubInBetween(str, "", "de")
result2 := strutil.SubInBetween(str, "a", "d")
fmt.Println(result1)
fmt.Println(result2)
// Output:
// abc
// bc
}
```
+3 -3
View File
@@ -65,8 +65,8 @@ func (f *FileReader) Offset() int64 {
return f.offset return f.offset
} }
// Seek sets the current offset of the reading // SeekOffset sets the current offset of the reading
func (f *FileReader) Seek(offset int64) error { func (f *FileReader) SeekOffset(offset int64) error {
_, err := f.file.Seek(offset, 0) _, err := f.file.Seek(offset, 0)
if err != nil { if err != nil {
return err return err
@@ -754,7 +754,7 @@ func escapeCSVField(field string, delimiter rune) string {
} }
// WriteMapsToCsv write slice of map to csv file. // WriteMapsToCsv write slice of map to csv file.
// Play: todo // Play: https://go.dev/play/p/umAIomZFV1c
// filepath: Path to the CSV file. // filepath: Path to the CSV file.
// records: Slice of maps to be written. the value of map should be basic type. // records: Slice of maps to be written. the value of map should be basic type.
// the maps will be sorted by key in alphabeta order, then be written into csv file. // the maps will be sorted by key in alphabeta order, then be written into csv file.
+1 -1
View File
@@ -534,7 +534,7 @@ func TestReadlineFile(t *testing.T) {
if !ok { if !ok {
t.Fail() t.Fail()
} }
if err = reader.Seek(offset); err != nil { if err = reader.SeekOffset(offset); err != nil {
t.Fail() t.Fail()
} }
lineRead, err := reader.ReadLine() lineRead, err := reader.ReadLine()
+34
View File
@@ -0,0 +1,34 @@
package function
// And returns a composed predicate that represents the logical AND of a list of predicates.
// It evaluates to true only if all predicates evaluate to true for the given value.
func And[T any](predicates ...func(T) bool) func(T) bool {
return func(value T) bool {
for _, predicate := range predicates {
if !predicate(value) {
return false // Short-circuit on the first false predicate
}
}
return true // True if all predicates are true
}
}
// Negate returns a predicate that represents the logical negation of this predicate.
func Negate[T any](predicate func(T) bool) func(T) bool {
return func(value T) bool {
return !predicate(value)
}
}
// Or returns a composed predicate that represents the logical OR of a list of predicates.
// It evaluates to true if at least one of the predicates evaluates to true for the given value.
func Or[T any](predicates ...func(T) bool) func(T) bool {
return func(value T) bool {
for _, predicate := range predicates {
if predicate(value) {
return true // Short-circuit on the first true predicate
}
}
return false // False if all predicates are false
}
}
+75
View File
@@ -0,0 +1,75 @@
package function
import (
"strings"
"testing"
"github.com/duke-git/lancet/v2/internal"
)
func TestPredicatesNegatePure(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestPredicatesNegatePure")
// Define some simple predicates for demonstration
isUpperCase := func(s string) bool {
return strings.ToUpper(s) == s
}
isLowerCase := func(s string) bool {
return strings.ToLower(s) == s
}
isMixedCase := Negate(Or(isUpperCase, isLowerCase))
assert.ShouldBeFalse(isMixedCase("ABC"))
assert.ShouldBeTrue(isMixedCase("AbC"))
}
func TestPredicatesOrPure(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestPredicatesOrPure")
containsDigitOrSpecialChar := Or(
func(s string) bool { return strings.ContainsAny(s, "0123456789") },
func(s string) bool { return strings.ContainsAny(s, "!@#$%") },
)
assert.ShouldBeTrue(containsDigitOrSpecialChar("hello!"))
assert.ShouldBeFalse(containsDigitOrSpecialChar("hello"))
}
func TestPredicatesAndPure(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestPredicatesAndPure")
isNumericAndLength5 := And(
func(s string) bool { return strings.ContainsAny(s, "0123456789") },
func(s string) bool { return len(s) == 5 },
)
assert.ShouldBeTrue(isNumericAndLength5("12345"))
assert.ShouldBeFalse(isNumericAndLength5("1234"))
assert.ShouldBeFalse(isNumericAndLength5("abcde"))
}
func TestPredicatesMix(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestPredicatesMix")
a := Or(
func(s string) bool { return strings.ContainsAny(s, "0123456789") },
func(s string) bool { return strings.ContainsAny(s, "!") },
)
b := And(
func(s string) bool { return strings.ContainsAny(s, "hello") },
func(s string) bool { return strings.ContainsAny(s, "!") },
)
c := Negate(And(a, b))
assert.ShouldBeFalse(c("hello!"))
}
+14
View File
@@ -37,6 +37,20 @@ func (a *Assert) Equal(expected, actual any) {
} }
} }
// ShouldBeFalse check if expected is false
func (a *Assert) ShouldBeFalse(actual any) {
if compare(false, actual) != compareEqual {
makeTestFailed(a.T, a.CaseName, false, actual)
}
}
// ShouldBeTrue check if expected is true
func (a *Assert) ShouldBeTrue(actual any) {
if compare(true, actual) != compareEqual {
makeTestFailed(a.T, a.CaseName, true, actual)
}
}
// NotEqual check if expected is not equal with actual // NotEqual check if expected is not equal with actual
func (a *Assert) NotEqual(expected, actual any) { func (a *Assert) NotEqual(expected, actual any) {
if compare(expected, actual) == compareEqual { if compare(expected, actual) == compareEqual {
+77
View File
@@ -5,6 +5,7 @@
package maputil package maputil
import ( import (
"fmt"
"reflect" "reflect"
"github.com/duke-git/lancet/v2/slice" "github.com/duke-git/lancet/v2/slice"
@@ -303,3 +304,79 @@ func HasKey[K comparable, V any](m map[K]V, key K) bool {
_, haskey := m[key] _, haskey := m[key]
return haskey return haskey
} }
// MapToStruct converts map to struct
// Play: todo
func MapToStruct(m map[string]interface{}, structObj interface{}) error {
for k, v := range m {
err := setStructField(structObj, k, v)
if err != nil {
return err
}
}
return nil
}
func setStructField(structObj any, fieldName string, fieldValue any) error {
structVal := reflect.ValueOf(structObj).Elem()
fName := getFieldNameByJsonTag(structObj, fieldName)
if fName == "" {
return fmt.Errorf("Struct field json tag don't match map key : %s in obj", fieldName)
}
fieldVal := structVal.FieldByName(fName)
if !fieldVal.IsValid() {
return fmt.Errorf("No such field: %s in obj", fieldName)
}
if !fieldVal.CanSet() {
return fmt.Errorf("Cannot set %s field value", fieldName)
}
val := reflect.ValueOf(fieldValue)
if fieldVal.Type() != val.Type() {
if m, ok := fieldValue.(map[string]interface{}); ok {
if fieldVal.Kind() == reflect.Struct {
return MapToStruct(m, fieldVal.Addr().Interface())
}
if fieldVal.Kind() == reflect.Ptr && fieldVal.Type().Elem().Kind() == reflect.Struct {
if fieldVal.IsNil() {
fieldVal.Set(reflect.New(fieldVal.Type().Elem()))
}
return MapToStruct(m, fieldVal.Interface())
}
}
return fmt.Errorf("Map value type don't match struct field type")
}
fieldVal.Set(val)
return nil
}
func getFieldNameByJsonTag(structObj any, jsonTag string) string {
s := reflect.TypeOf(structObj).Elem()
for i := 0; i < s.NumField(); i++ {
field := s.Field(i)
tag := field.Tag
name := tag.Get("json")
if name == jsonTag {
return field.Name
}
}
return ""
}
+39
View File
@@ -472,3 +472,42 @@ func TestHasKey(t *testing.T) {
assert.Equal(true, HasKey(m, "a")) assert.Equal(true, HasKey(m, "a"))
assert.Equal(false, HasKey(m, "c")) assert.Equal(false, HasKey(m, "c"))
} }
func TestMapToStruct(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestMapToStruct")
type (
Person struct {
Name string `json:"name"`
Age int `json:"age"`
Phone string `json:"phone"`
Addr *Address `json:"address"`
}
Address struct {
Street string `json:"street"`
Number int `json:"number"`
}
)
m := map[string]interface{}{
"name": "Nothin",
"age": 28,
"phone": "123456789",
"address": map[string]interface{}{
"street": "test",
"number": 1,
},
}
var p Person
err := MapToStruct(m, &p)
assert.IsNil(err)
assert.Equal(m["name"], p.Name)
assert.Equal(m["age"], p.Age)
assert.Equal(m["phone"], p.Phone)
assert.Equal("test", p.Addr.Street)
assert.Equal(1, p.Addr.Number)
}
+3
View File
@@ -121,6 +121,9 @@ func convertSlice(src reflect.Value, dst reflect.Value) error {
func convertMap(src reflect.Value, dst reflect.Value) error { func convertMap(src reflect.Value, dst reflect.Value) error {
if src.Kind() != reflect.Map || dst.Kind() != reflect.Struct { if src.Kind() != reflect.Map || dst.Kind() != reflect.Struct {
// if src.Kind() == reflect.Map {
// return convertMap(src, dst)
// } else
if src.Kind() == reflect.Interface { if src.Kind() == reflect.Interface {
return convertMap(src.Elem(), dst) return convertMap(src.Elem(), dst)
} else { } else {
+6 -6
View File
@@ -8,10 +8,10 @@ import (
type ( type (
Person struct { Person struct {
Name string `json:"name"` Name string `json:"name"`
Age int `json:"age"` Age int `json:"age"`
Phone string `json:"phone"` Phone string `json:"phone"`
Addr Address `json:"address"` Address Address `json:"address"`
} }
Address struct { Address struct {
@@ -41,8 +41,8 @@ func TestStructType(t *testing.T) {
assert.Equal(src["name"], p.Name) assert.Equal(src["name"], p.Name)
assert.Equal(src["age"], p.Age) assert.Equal(src["age"], p.Age)
assert.Equal(src["phone"], p.Phone) assert.Equal(src["phone"], p.Phone)
assert.Equal("test", p.Addr.Street) assert.Equal("test", p.Address.Street)
assert.Equal(1, p.Addr.Number) assert.Equal(1, p.Address.Number)
} }
func TestBaseType(t *testing.T) { func TestBaseType(t *testing.T) {
+2 -2
View File
@@ -619,7 +619,7 @@ func IntSlice(slice any) []int {
} }
// DeleteAt delete the element of slice at index. // DeleteAt delete the element of slice at index.
// Play: https://go.dev/play/p/pJ-d6MUWcvK // Play: https://go.dev/play/p/800B1dPBYyd
func DeleteAt[T any](slice []T, index int) []T { func DeleteAt[T any](slice []T, index int) []T {
if index >= len(slice) { if index >= len(slice) {
index = len(slice) - 1 index = len(slice) - 1
@@ -633,7 +633,7 @@ func DeleteAt[T any](slice []T, index int) []T {
} }
// DeleteRange delete the element of slice from start index to end indexexclude). // DeleteRange delete the element of slice from start index to end indexexclude).
// Play: todo // Play: https://go.dev/play/p/945HwiNrnle
func DeleteRange[T any](slice []T, start, end int) []T { func DeleteRange[T any](slice []T, start, end int) []T {
result := make([]T, 0, len(slice)-(end-start)) result := make([]T, 0, len(slice)-(end-start))
+28 -28
View File
@@ -47,19 +47,19 @@ import (
// Concat(streams ...StreamI[T]) StreamI[T] // Concat(streams ...StreamI[T]) StreamI[T]
// } // }
type stream[T any] struct { type Stream[T any] struct {
source []T source []T
} }
// Of creates a stream whose elements are the specified values. // Of creates a stream whose elements are the specified values.
// Play: https://go.dev/play/p/jI6_iZZuVFE // Play: https://go.dev/play/p/jI6_iZZuVFE
func Of[T any](elems ...T) stream[T] { func Of[T any](elems ...T) Stream[T] {
return FromSlice(elems) return FromSlice(elems)
} }
// Generate stream where each element is generated by the provided generater function // Generate stream where each element is generated by the provided generater function
// Play: https://go.dev/play/p/rkOWL1yA3j9 // Play: https://go.dev/play/p/rkOWL1yA3j9
func Generate[T any](generator func() func() (item T, ok bool)) stream[T] { func Generate[T any](generator func() func() (item T, ok bool)) Stream[T] {
source := make([]T, 0) source := make([]T, 0)
var zeroValue T var zeroValue T
@@ -76,13 +76,13 @@ func Generate[T any](generator func() func() (item T, ok bool)) stream[T] {
// FromSlice creates stream from slice. // FromSlice creates stream from slice.
// Play: https://go.dev/play/p/wywTO0XZtI4 // Play: https://go.dev/play/p/wywTO0XZtI4
func FromSlice[T any](source []T) stream[T] { func FromSlice[T any](source []T) Stream[T] {
return stream[T]{source: source} return Stream[T]{source: source}
} }
// FromChannel creates stream from channel. // FromChannel creates stream from channel.
// Play: https://go.dev/play/p/9TZYugGMhXZ // Play: https://go.dev/play/p/9TZYugGMhXZ
func FromChannel[T any](source <-chan T) stream[T] { func FromChannel[T any](source <-chan T) Stream[T] {
s := make([]T, 0) s := make([]T, 0)
for v := range source { for v := range source {
@@ -94,7 +94,7 @@ func FromChannel[T any](source <-chan T) stream[T] {
// FromRange creates a number stream from start to end. both start and end are included. [start, end] // FromRange creates a number stream from start to end. both start and end are included. [start, end]
// Play: https://go.dev/play/p/9Ex1-zcg-B- // Play: https://go.dev/play/p/9Ex1-zcg-B-
func FromRange[T constraints.Integer | constraints.Float](start, end, step T) stream[T] { func FromRange[T constraints.Integer | constraints.Float](start, end, step T) Stream[T] {
if end < start { if end < start {
panic("stream.FromRange: param start should be before param end") panic("stream.FromRange: param start should be before param end")
} else if step <= 0 { } else if step <= 0 {
@@ -113,7 +113,7 @@ func FromRange[T constraints.Integer | constraints.Float](start, end, step T) st
// Concat creates a lazily concatenated stream whose elements are all the elements of the first stream followed by all the elements of the second stream. // Concat creates a lazily concatenated stream whose elements are all the elements of the first stream followed by all the elements of the second stream.
// Play: https://go.dev/play/p/HM4OlYk_OUC // Play: https://go.dev/play/p/HM4OlYk_OUC
func Concat[T any](a, b stream[T]) stream[T] { func Concat[T any](a, b Stream[T]) Stream[T] {
source := make([]T, 0) source := make([]T, 0)
source = append(source, a.source...) source = append(source, a.source...)
@@ -124,7 +124,7 @@ func Concat[T any](a, b stream[T]) stream[T] {
// Distinct returns a stream that removes the duplicated items. // Distinct returns a stream that removes the duplicated items.
// Play: https://go.dev/play/p/eGkOSrm64cB // Play: https://go.dev/play/p/eGkOSrm64cB
func (s stream[T]) Distinct() stream[T] { func (s Stream[T]) Distinct() Stream[T] {
source := make([]T, 0) source := make([]T, 0)
distinct := map[string]bool{} distinct := map[string]bool{}
@@ -153,7 +153,7 @@ func hashKey(data any) string {
// Filter returns a stream consisting of the elements of this stream that match the given predicate. // Filter returns a stream consisting of the elements of this stream that match the given predicate.
// Play: https://go.dev/play/p/MFlSANo-buc // Play: https://go.dev/play/p/MFlSANo-buc
func (s stream[T]) Filter(predicate func(item T) bool) stream[T] { func (s Stream[T]) Filter(predicate func(item T) bool) Stream[T] {
source := make([]T, 0) source := make([]T, 0)
for _, v := range s.source { for _, v := range s.source {
@@ -167,7 +167,7 @@ func (s stream[T]) Filter(predicate func(item T) bool) stream[T] {
// Map returns a stream consisting of the elements of this stream that apply the given function to elements of stream. // Map returns a stream consisting of the elements of this stream that apply the given function to elements of stream.
// Play: https://go.dev/play/p/OtNQUImdYko // Play: https://go.dev/play/p/OtNQUImdYko
func (s stream[T]) Map(mapper func(item T) T) stream[T] { func (s Stream[T]) Map(mapper func(item T) T) Stream[T] {
source := make([]T, s.Count()) source := make([]T, s.Count())
for i, v := range s.source { for i, v := range s.source {
@@ -179,7 +179,7 @@ func (s stream[T]) Map(mapper func(item T) T) stream[T] {
// Peek returns a stream consisting of the elements of this stream, additionally performing the provided action on each element as elements are consumed from the resulting stream. // Peek returns a stream consisting of the elements of this stream, additionally performing the provided action on each element as elements are consumed from the resulting stream.
// Play: https://go.dev/play/p/u1VNzHs6cb2 // Play: https://go.dev/play/p/u1VNzHs6cb2
func (s stream[T]) Peek(consumer func(item T)) stream[T] { func (s Stream[T]) Peek(consumer func(item T)) Stream[T] {
for _, v := range s.source { for _, v := range s.source {
consumer(v) consumer(v)
} }
@@ -190,7 +190,7 @@ func (s stream[T]) Peek(consumer func(item T)) stream[T] {
// Skip returns a stream consisting of the remaining elements of this stream after discarding the first n elements of the stream. // Skip returns a stream consisting of the remaining elements of this stream after discarding the first n elements of the stream.
// If this stream contains fewer than n elements then an empty stream will be returned. // If this stream contains fewer than n elements then an empty stream will be returned.
// Play: https://go.dev/play/p/fNdHbqjahum // Play: https://go.dev/play/p/fNdHbqjahum
func (s stream[T]) Skip(n int) stream[T] { func (s Stream[T]) Skip(n int) Stream[T] {
if n <= 0 { if n <= 0 {
return s return s
} }
@@ -211,7 +211,7 @@ func (s stream[T]) Skip(n int) stream[T] {
// Limit returns a stream consisting of the elements of this stream, truncated to be no longer than maxSize in length. // Limit returns a stream consisting of the elements of this stream, truncated to be no longer than maxSize in length.
// Play: https://go.dev/play/p/qsO4aniDcGf // Play: https://go.dev/play/p/qsO4aniDcGf
func (s stream[T]) Limit(maxSize int) stream[T] { func (s Stream[T]) Limit(maxSize int) Stream[T] {
if s.source == nil { if s.source == nil {
return s return s
} }
@@ -231,7 +231,7 @@ func (s stream[T]) Limit(maxSize int) stream[T] {
// AllMatch returns whether all elements of this stream match the provided predicate. // AllMatch returns whether all elements of this stream match the provided predicate.
// Play: https://go.dev/play/p/V5TBpVRs-Cx // Play: https://go.dev/play/p/V5TBpVRs-Cx
func (s stream[T]) AllMatch(predicate func(item T) bool) bool { func (s Stream[T]) AllMatch(predicate func(item T) bool) bool {
for _, v := range s.source { for _, v := range s.source {
if !predicate(v) { if !predicate(v) {
return false return false
@@ -243,7 +243,7 @@ func (s stream[T]) AllMatch(predicate func(item T) bool) bool {
// AnyMatch returns whether any elements of this stream match the provided predicate. // AnyMatch returns whether any elements of this stream match the provided predicate.
// Play: https://go.dev/play/p/PTCnWn4OxSn // Play: https://go.dev/play/p/PTCnWn4OxSn
func (s stream[T]) AnyMatch(predicate func(item T) bool) bool { func (s Stream[T]) AnyMatch(predicate func(item T) bool) bool {
for _, v := range s.source { for _, v := range s.source {
if predicate(v) { if predicate(v) {
return true return true
@@ -255,13 +255,13 @@ func (s stream[T]) AnyMatch(predicate func(item T) bool) bool {
// NoneMatch returns whether no elements of this stream match the provided predicate. // NoneMatch returns whether no elements of this stream match the provided predicate.
// Play: https://go.dev/play/p/iWS64pL1oo3 // Play: https://go.dev/play/p/iWS64pL1oo3
func (s stream[T]) NoneMatch(predicate func(item T) bool) bool { func (s Stream[T]) NoneMatch(predicate func(item T) bool) bool {
return !s.AnyMatch(predicate) return !s.AnyMatch(predicate)
} }
// ForEach performs an action for each element of this stream. // ForEach performs an action for each element of this stream.
// Play: https://go.dev/play/p/Dsm0fPqcidk // Play: https://go.dev/play/p/Dsm0fPqcidk
func (s stream[T]) ForEach(action func(item T)) { func (s Stream[T]) ForEach(action func(item T)) {
for _, v := range s.source { for _, v := range s.source {
action(v) action(v)
} }
@@ -269,7 +269,7 @@ func (s stream[T]) ForEach(action func(item T)) {
// Reduce performs a reduction on the elements of this stream, using an associative accumulation function, and returns an Optional describing the reduced value, if any. // Reduce performs a reduction on the elements of this stream, using an associative accumulation function, and returns an Optional describing the reduced value, if any.
// Play: https://go.dev/play/p/6uzZjq_DJLU // Play: https://go.dev/play/p/6uzZjq_DJLU
func (s stream[T]) Reduce(initial T, accumulator func(a, b T) T) T { func (s Stream[T]) Reduce(initial T, accumulator func(a, b T) T) T {
for _, v := range s.source { for _, v := range s.source {
initial = accumulator(initial, v) initial = accumulator(initial, v)
} }
@@ -279,13 +279,13 @@ func (s stream[T]) Reduce(initial T, accumulator func(a, b T) T) T {
// Count returns the count of elements in the stream. // Count returns the count of elements in the stream.
// Play: https://go.dev/play/p/r3koY6y_Xo- // Play: https://go.dev/play/p/r3koY6y_Xo-
func (s stream[T]) Count() int { func (s Stream[T]) Count() int {
return len(s.source) return len(s.source)
} }
// FindFirst returns the first element of this stream and true, or zero value and false if the stream is empty. // FindFirst returns the first element of this stream and true, or zero value and false if the stream is empty.
// Play: https://go.dev/play/p/9xEf0-6C1e3 // Play: https://go.dev/play/p/9xEf0-6C1e3
func (s stream[T]) FindFirst() (T, bool) { func (s Stream[T]) FindFirst() (T, bool) {
var result T var result T
if s.source == nil || len(s.source) == 0 { if s.source == nil || len(s.source) == 0 {
@@ -297,7 +297,7 @@ func (s stream[T]) FindFirst() (T, bool) {
// FindLast returns the last element of this stream and true, or zero value and false if the stream is empty. // FindLast returns the last element of this stream and true, or zero value and false if the stream is empty.
// Play: https://go.dev/play/p/WZD2rDAW-2h // Play: https://go.dev/play/p/WZD2rDAW-2h
func (s stream[T]) FindLast() (T, bool) { func (s Stream[T]) FindLast() (T, bool) {
var result T var result T
if s.source == nil || len(s.source) == 0 { if s.source == nil || len(s.source) == 0 {
@@ -309,7 +309,7 @@ func (s stream[T]) FindLast() (T, bool) {
// Reverse returns a stream whose elements are reverse order of given stream. // Reverse returns a stream whose elements are reverse order of given stream.
// Play: https://go.dev/play/p/A8_zkJnLHm4 // Play: https://go.dev/play/p/A8_zkJnLHm4
func (s stream[T]) Reverse() stream[T] { func (s Stream[T]) Reverse() Stream[T] {
l := len(s.source) l := len(s.source)
source := make([]T, l) source := make([]T, l)
@@ -321,7 +321,7 @@ func (s stream[T]) Reverse() stream[T] {
// Range returns a stream whose elements are in the range from start(included) to end(excluded) original stream. // Range returns a stream whose elements are in the range from start(included) to end(excluded) original stream.
// Play: https://go.dev/play/p/indZY5V2f4j // Play: https://go.dev/play/p/indZY5V2f4j
func (s stream[T]) Range(start, end int) stream[T] { func (s Stream[T]) Range(start, end int) Stream[T] {
if start < 0 { if start < 0 {
start = 0 start = 0
} }
@@ -347,7 +347,7 @@ func (s stream[T]) Range(start, end int) stream[T] {
// Sorted returns a stream consisting of the elements of this stream, sorted according to the provided less function. // Sorted returns a stream consisting of the elements of this stream, sorted according to the provided less function.
// Play: https://go.dev/play/p/XXtng5uonFj // Play: https://go.dev/play/p/XXtng5uonFj
func (s stream[T]) Sorted(less func(a, b T) bool) stream[T] { func (s Stream[T]) Sorted(less func(a, b T) bool) Stream[T] {
source := []T{} source := []T{}
source = append(source, s.source...) source = append(source, s.source...)
@@ -359,7 +359,7 @@ func (s stream[T]) Sorted(less func(a, b T) bool) stream[T] {
// Max returns the maximum element of this stream according to the provided less function. // Max returns the maximum element of this stream according to the provided less function.
// less: a > b // less: a > b
// Play: https://go.dev/play/p/fm-1KOPtGzn // Play: https://go.dev/play/p/fm-1KOPtGzn
func (s stream[T]) Max(less func(a, b T) bool) (T, bool) { func (s Stream[T]) Max(less func(a, b T) bool) (T, bool) {
var max T var max T
if len(s.source) == 0 { if len(s.source) == 0 {
@@ -377,7 +377,7 @@ func (s stream[T]) Max(less func(a, b T) bool) (T, bool) {
// Min returns the minimum element of this stream according to the provided less function. // Min returns the minimum element of this stream according to the provided less function.
// less: a < b // less: a < b
// Play: https://go.dev/play/p/vZfIDgGNRe_0 // Play: https://go.dev/play/p/vZfIDgGNRe_0
func (s stream[T]) Min(less func(a, b T) bool) (T, bool) { func (s Stream[T]) Min(less func(a, b T) bool) (T, bool) {
var min T var min T
if len(s.source) == 0 { if len(s.source) == 0 {
@@ -395,6 +395,6 @@ func (s stream[T]) Min(less func(a, b T) bool) (T, bool) {
// ToSlice return the elements in the stream. // ToSlice return the elements in the stream.
// Play: https://go.dev/play/p/jI6_iZZuVFE // Play: https://go.dev/play/p/jI6_iZZuVFE
func (s stream[T]) ToSlice() []T { func (s Stream[T]) ToSlice() []T {
return s.source return s.source
} }
+28 -8
View File
@@ -121,40 +121,48 @@ func UpperSnakeCase(s string) string {
// Before returns the substring of the source string up to the first occurrence of the specified string. // Before returns the substring of the source string up to the first occurrence of the specified string.
// Play: https://go.dev/play/p/JAWTZDS4F5w // Play: https://go.dev/play/p/JAWTZDS4F5w
func Before(s, char string) string { func Before(s, char string) string {
if s == "" || char == "" { i := strings.Index(s, char)
if s == "" || char == "" || i == -1 {
return s return s
} }
i := strings.Index(s, char)
return s[0:i] return s[0:i]
} }
// BeforeLast returns the substring of the source string up to the last occurrence of the specified string. // BeforeLast returns the substring of the source string up to the last occurrence of the specified string.
// Play: https://go.dev/play/p/pJfXXAoG_Te // Play: https://go.dev/play/p/pJfXXAoG_Te
func BeforeLast(s, char string) string { func BeforeLast(s, char string) string {
if s == "" || char == "" { i := strings.LastIndex(s, char)
if s == "" || char == "" || i == -1 {
return s return s
} }
i := strings.LastIndex(s, char)
return s[0:i] return s[0:i]
} }
// After returns the substring after the first occurrence of a specified string in the source string. // After returns the substring after the first occurrence of a specified string in the source string.
// Play: https://go.dev/play/p/RbCOQqCDA7m // Play: https://go.dev/play/p/RbCOQqCDA7m
func After(s, char string) string { func After(s, char string) string {
if s == "" || char == "" { i := strings.Index(s, char)
if s == "" || char == "" || i == -1 {
return s return s
} }
i := strings.Index(s, char)
return s[i+len(char):] return s[i+len(char):]
} }
// AfterLast returns the substring after the last occurrence of a specified string in the source string. // AfterLast returns the substring after the last occurrence of a specified string in the source string.
// Play: https://go.dev/play/p/1TegARrb8Yn // Play: https://go.dev/play/p/1TegARrb8Yn
func AfterLast(s, char string) string { func AfterLast(s, char string) string {
if s == "" || char == "" { i := strings.LastIndex(s, char)
if s == "" || char == "" || i == -1 {
return s return s
} }
i := strings.LastIndex(s, char)
return s[i+len(char):] return s[i+len(char):]
} }
@@ -574,3 +582,15 @@ func RemoveWhiteSpace(str string, repalceAll bool) string {
return strings.TrimSpace(str) return strings.TrimSpace(str)
} }
// SubInBetween return substring between the start and end position(excluded) of source string.
// Play: todo
func SubInBetween(str string, start string, end string) string {
if _, after, ok := strings.Cut(str, start); ok {
if before, _, ok := strings.Cut(after, end); ok {
return before
}
}
return ""
}
+14
View File
@@ -653,3 +653,17 @@ func ExampleRemoveWhiteSpace() {
// helloworld // helloworld
// hello world // hello world
} }
func ExampleSubInBetween() {
str := "abcde"
result1 := SubInBetween(str, "", "de")
result2 := SubInBetween(str, "a", "d")
fmt.Println(result1)
fmt.Println(result2)
// Output:
// abc
// bc
}
+19 -4
View File
@@ -230,6 +230,8 @@ func TestBefore(t *testing.T) {
assert.Equal("lancet", Before("lancet", "")) assert.Equal("lancet", Before("lancet", ""))
assert.Equal("", Before("lancet", "lancet")) assert.Equal("", Before("lancet", "lancet"))
assert.Equal("lancet", Before("lancet", "abcdef"))
assert.Equal("github.com", Before("github.com/test/lancet", "/")) assert.Equal("github.com", Before("github.com/test/lancet", "/"))
assert.Equal("github.com/", Before("github.com/test/lancet", "test")) assert.Equal("github.com/", Before("github.com/test/lancet", "test"))
} }
@@ -240,10 +242,10 @@ func TestBeforeLast(t *testing.T) {
assert := internal.NewAssert(t, "TestBeforeLast") assert := internal.NewAssert(t, "TestBeforeLast")
assert.Equal("lancet", BeforeLast("lancet", "")) assert.Equal("lancet", BeforeLast("lancet", ""))
assert.Equal("lancet", BeforeLast("lancet", "abcdef"))
assert.Equal("github.com/test", BeforeLast("github.com/test/lancet", "/")) assert.Equal("github.com/test", BeforeLast("github.com/test/lancet", "/"))
assert.Equal("github.com/test/", BeforeLast("github.com/test/test/lancet", "test")) assert.Equal("github.com/test/", BeforeLast("github.com/test/test/lancet", "test"))
assert.NotEqual("github.com/", BeforeLast("github.com/test/test/lancet", "test"))
} }
func TestAfter(t *testing.T) { func TestAfter(t *testing.T) {
@@ -255,6 +257,8 @@ func TestAfter(t *testing.T) {
assert.Equal("", After("lancet", "lancet")) assert.Equal("", After("lancet", "lancet"))
assert.Equal("test/lancet", After("github.com/test/lancet", "/")) assert.Equal("test/lancet", After("github.com/test/lancet", "/"))
assert.Equal("/lancet", After("github.com/test/lancet", "test")) assert.Equal("/lancet", After("github.com/test/lancet", "test"))
assert.Equal("lancet", After("lancet", "abcdef"))
} }
func TestAfterLast(t *testing.T) { func TestAfterLast(t *testing.T) {
@@ -266,8 +270,7 @@ func TestAfterLast(t *testing.T) {
assert.Equal("lancet", AfterLast("github.com/test/lancet", "/")) assert.Equal("lancet", AfterLast("github.com/test/lancet", "/"))
assert.Equal("/lancet", AfterLast("github.com/test/lancet", "test")) assert.Equal("/lancet", AfterLast("github.com/test/lancet", "test"))
assert.Equal("/lancet", AfterLast("github.com/test/test/lancet", "test")) assert.Equal("/lancet", AfterLast("github.com/test/test/lancet", "test"))
assert.Equal("lancet", AfterLast("lancet", "abcdef"))
assert.NotEqual("/test/lancet", AfterLast("github.com/test/test/lancet", "test"))
} }
func TestIsString(t *testing.T) { func TestIsString(t *testing.T) {
@@ -566,3 +569,15 @@ func TestRemoveWhiteSpace(t *testing.T) {
assert.Equal("helloworld", RemoveWhiteSpace(str, true)) assert.Equal("helloworld", RemoveWhiteSpace(str, true))
assert.Equal("hello world", RemoveWhiteSpace(str, false)) assert.Equal("hello world", RemoveWhiteSpace(str, false))
} }
func TestSubInBetween(t *testing.T) {
assert := internal.NewAssert(t, "TestSubInBetween")
str := "abcde"
assert.Equal("", SubInBetween(str, "", ""))
assert.Equal("ab", SubInBetween(str, "", "c"))
assert.Equal("bc", SubInBetween(str, "a", "d"))
assert.Equal("", SubInBetween(str, "a", ""))
assert.Equal("", SubInBetween(str, "a", "f"))
}