mirror of
https://github.com/jesseduffield/lazydocker.git
synced 2026-07-21 23:01:03 +00:00
allow changing window size
This commit is contained in:
parent
df1a0b7c07
commit
badc43b225
46 changed files with 3452 additions and 656 deletions
9
go.mod
9
go.mod
|
|
@ -16,13 +16,15 @@ require (
|
|||
github.com/jesseduffield/asciigraph v0.0.0-20190605104717-6d88e39309ee
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20220417002912-bce22fd599f6
|
||||
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10
|
||||
github.com/jesseduffield/lazycore v0.0.0-20221009164442-17c8b878c316
|
||||
github.com/jesseduffield/yaml v0.0.0-20190702115811-b900b7e08b56
|
||||
github.com/mattn/go-runewidth v0.0.13
|
||||
github.com/mcuadros/go-lookup v0.0.0-20171110082742-5650f26be767
|
||||
github.com/mgutz/str v1.2.0
|
||||
github.com/samber/lo v1.20.0
|
||||
github.com/samber/lo v1.31.0
|
||||
github.com/sirupsen/logrus v1.4.2
|
||||
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/stretchr/testify v1.8.0
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1
|
||||
)
|
||||
|
||||
|
|
@ -39,7 +41,6 @@ require (
|
|||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.4 // indirect
|
||||
github.com/mattn/go-isatty v0.0.11 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
|
||||
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c // indirect
|
||||
github.com/onsi/ginkgo v1.8.0 // indirect
|
||||
|
|
@ -57,6 +58,6 @@ require (
|
|||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect
|
||||
gopkg.in/yaml.v2 v2.2.2 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gotest.tools/v3 v3.2.0 // indirect
|
||||
)
|
||||
|
|
|
|||
17
go.sum
17
go.sum
|
|
@ -54,6 +54,8 @@ github.com/jesseduffield/gocui v0.3.1-0.20220417002912-bce22fd599f6 h1:Fmay0Lz21
|
|||
github.com/jesseduffield/gocui v0.3.1-0.20220417002912-bce22fd599f6/go.mod h1:znJuCDnF2Ph40YZSlBwdX/4GEofnIoWLGdT4mK5zRAU=
|
||||
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10 h1:jmpr7KpX2+2GRiE91zTgfq49QvgiqB0nbmlwZ8UnOx0=
|
||||
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10/go.mod h1:aA97kHeNA+sj2Hbki0pvLslmE4CbDyhBeSSTUUnOuVo=
|
||||
github.com/jesseduffield/lazycore v0.0.0-20221009164442-17c8b878c316 h1:UzTg0utG+cLS94Qjb/ZFDzfMeZDFXErSbNgJOyj70EA=
|
||||
github.com/jesseduffield/lazycore v0.0.0-20221009164442-17c8b878c316/go.mod h1:qxN4mHOAyeIDLP7IK7defgPClM/z1Kze8VVQiaEjzsQ=
|
||||
github.com/jesseduffield/yaml v0.0.0-20190702115811-b900b7e08b56 h1:33wSxJWU/f2TAozHYtJ8zqBxEnEVYM+22moLoiAkxvg=
|
||||
github.com/jesseduffield/yaml v0.0.0-20190702115811-b900b7e08b56/go.mod h1:FZJBwOhE+RXz8EVZfY+xnbCw2cVOwxlK3/aIi581z/s=
|
||||
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
||||
|
|
@ -99,8 +101,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
|
|||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/samber/lo v1.20.0 h1:20FtphdORvp4yxklurzZv2HX+g+0urEMQziODC5bV70=
|
||||
github.com/samber/lo v1.20.0/go.mod h1:2I7tgIv8Q1SG2xEIkRq0F2i2zgxVpnyPOP0d3Gj2r+A=
|
||||
github.com/samber/lo v1.31.0 h1:Sfa+/064Tdo4SvlohQUQzBhgSer9v/coGvKQI/XLWAM=
|
||||
github.com/samber/lo v1.31.0/go.mod h1:HLeWcJRRyLKp3+/XBJvOrerCQn9mhdKMHyd7IRlgeQ8=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
|
|
@ -108,11 +110,14 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn
|
|||
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad h1:fiWzISvDn0Csy5H0iwgAuJGQTUpVfEMJJd4nRFXogbc=
|
||||
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/thoas/go-funk v0.9.1 h1:O549iLZqPpTUQ10ykd26sZhzD+rmR5pWhuElrhbC20M=
|
||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8=
|
||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
|
||||
|
|
@ -177,8 +182,8 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
|
||||
gotest.tools/v3 v3.2.0 h1:I0DwBVMGAx26dttAj1BtJLAkVGncrkkUXfJLC4Flt/I=
|
||||
gotest.tools/v3 v3.2.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A=
|
||||
|
|
|
|||
|
|
@ -114,6 +114,16 @@ type GuiConfig struct {
|
|||
// By default, containers are now sorted by status. This setting allows users to
|
||||
// use legacy behaviour instead.
|
||||
LegacySortContainers bool `yaml:"legacySortContainers,omitempty"`
|
||||
|
||||
// If 0.333, then the side panels will be 1/3 of the screen's width
|
||||
SidePanelWidth float64 `yaml:"sidePanelWidth"`
|
||||
|
||||
// Determines whether we show the bottom line (the one containing keybinding
|
||||
// info and the status of the app).
|
||||
ShowBottomLine bool `yaml:"showBottomLine"`
|
||||
|
||||
// When true, ensures that the focused side panel always takes up the full vertical space
|
||||
ExpandFocusedSidePanel bool `yaml:"expandFocusedSidePanel"`
|
||||
}
|
||||
|
||||
// CommandTemplatesConfig determines what commands actually get called when we
|
||||
|
|
@ -320,10 +330,13 @@ func GetDefaultConfig() UserConfig {
|
|||
InactiveBorderColor: []string{"default"},
|
||||
OptionsTextColor: []string{"blue"},
|
||||
},
|
||||
ShowAllContainers: false,
|
||||
ReturnImmediately: false,
|
||||
WrapMainPanel: true,
|
||||
LegacySortContainers: false,
|
||||
ShowAllContainers: false,
|
||||
ReturnImmediately: false,
|
||||
WrapMainPanel: true,
|
||||
LegacySortContainers: false,
|
||||
SidePanelWidth: 0.3333,
|
||||
ShowBottomLine: true,
|
||||
ExpandFocusedSidePanel: false,
|
||||
},
|
||||
ConfirmOnQuit: false,
|
||||
Logs: LogsConfig{
|
||||
|
|
|
|||
237
pkg/gui/arrangement.go
Normal file
237
pkg/gui/arrangement.go
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
package gui
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazycore/pkg/boxlayout"
|
||||
"github.com/jesseduffield/lazydocker/pkg/utils"
|
||||
"github.com/mattn/go-runewidth"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
// In this file we use the boxlayout package, along with knowledge about the app's state,
|
||||
// to arrange the windows (i.e. panels) on the screen.
|
||||
|
||||
const INFO_SECTION_PADDING = " "
|
||||
|
||||
func (gui *Gui) getWindowDimensions(informationStr string, appStatus string) map[string]boxlayout.Dimensions {
|
||||
minimumHeight := 9
|
||||
minimumWidth := 10
|
||||
width, height := gui.g.Size()
|
||||
if width < minimumWidth || height < minimumHeight {
|
||||
return boxlayout.ArrangeWindows(&boxlayout.Box{Window: "limit"}, 0, 0, width, height)
|
||||
}
|
||||
|
||||
sideSectionWeight, mainSectionWeight := gui.getMidSectionWeights()
|
||||
|
||||
sidePanelsDirection := boxlayout.COLUMN
|
||||
portraitMode := width <= 84 && height > 45
|
||||
if portraitMode {
|
||||
sidePanelsDirection = boxlayout.ROW
|
||||
}
|
||||
|
||||
showInfoSection := gui.Config.UserConfig.Gui.ShowBottomLine
|
||||
infoSectionSize := 0
|
||||
if showInfoSection {
|
||||
infoSectionSize = 1
|
||||
}
|
||||
|
||||
root := &boxlayout.Box{
|
||||
Direction: boxlayout.ROW,
|
||||
Children: []*boxlayout.Box{
|
||||
{
|
||||
Direction: sidePanelsDirection,
|
||||
Weight: 1,
|
||||
Children: []*boxlayout.Box{
|
||||
{
|
||||
Direction: boxlayout.ROW,
|
||||
Weight: sideSectionWeight,
|
||||
ConditionalChildren: gui.sidePanelChildren,
|
||||
},
|
||||
{
|
||||
Window: "main",
|
||||
Weight: mainSectionWeight,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Direction: boxlayout.COLUMN,
|
||||
Size: infoSectionSize,
|
||||
Children: gui.infoSectionChildren(informationStr, appStatus),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return boxlayout.ArrangeWindows(root, 0, 0, width, height)
|
||||
}
|
||||
|
||||
func MergeMaps[K comparable, V any](maps ...map[K]V) map[K]V {
|
||||
result := map[K]V{}
|
||||
for _, currMap := range maps {
|
||||
for key, value := range currMap {
|
||||
result[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (gui *Gui) getMidSectionWeights() (int, int) {
|
||||
currentWindow := gui.currentWindow()
|
||||
|
||||
// we originally specified this as a ratio i.e. .20 would correspond to a weight of 1 against 4
|
||||
sidePanelWidthRatio := gui.Config.UserConfig.Gui.SidePanelWidth
|
||||
// we could make this better by creating ratios like 2:3 rather than always 1:something
|
||||
mainSectionWeight := int(1/sidePanelWidthRatio) - 1
|
||||
sideSectionWeight := 1
|
||||
|
||||
if currentWindow == "main" && gui.State.ScreenMode == SCREEN_FULL {
|
||||
mainSectionWeight = 1
|
||||
sideSectionWeight = 0
|
||||
} else {
|
||||
if gui.State.ScreenMode == SCREEN_HALF {
|
||||
mainSectionWeight = 1
|
||||
} else if gui.State.ScreenMode == SCREEN_FULL {
|
||||
mainSectionWeight = 0
|
||||
}
|
||||
}
|
||||
|
||||
return sideSectionWeight, mainSectionWeight
|
||||
}
|
||||
|
||||
func (gui *Gui) infoSectionChildren(informationStr string, appStatus string) []*boxlayout.Box {
|
||||
result := []*boxlayout.Box{}
|
||||
|
||||
if len(appStatus) > 0 {
|
||||
result = append(result,
|
||||
&boxlayout.Box{
|
||||
Window: "appStatus",
|
||||
Size: runewidth.StringWidth(appStatus) + runewidth.StringWidth(INFO_SECTION_PADDING),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
result = append(result,
|
||||
[]*boxlayout.Box{
|
||||
{
|
||||
Window: "options",
|
||||
Weight: 1,
|
||||
},
|
||||
{
|
||||
Window: "information",
|
||||
// unlike appStatus, informationStr has various colors so we need to decolorise before taking the length
|
||||
Size: runewidth.StringWidth(INFO_SECTION_PADDING) + runewidth.StringWidth(utils.Decolorise(informationStr)),
|
||||
},
|
||||
}...,
|
||||
)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (gui *Gui) sideViewNames() []string {
|
||||
if gui.DockerCommand.InDockerComposeProject {
|
||||
return []string{"project", "services", "containers", "images", "volumes"}
|
||||
} else {
|
||||
return []string{"project", "containers", "images", "volumes"}
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) sidePanelChildren(width int, height int) []*boxlayout.Box {
|
||||
currentWindow := gui.currentSideWindowName()
|
||||
sideWindowNames := gui.sideViewNames()
|
||||
|
||||
if gui.State.ScreenMode == SCREEN_FULL || gui.State.ScreenMode == SCREEN_HALF {
|
||||
fullHeightBox := func(window string) *boxlayout.Box {
|
||||
if window == currentWindow {
|
||||
return &boxlayout.Box{
|
||||
Window: window,
|
||||
Weight: 1,
|
||||
}
|
||||
} else {
|
||||
return &boxlayout.Box{
|
||||
Window: window,
|
||||
Size: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return lo.Map(sideWindowNames, func(window string, _ int) *boxlayout.Box {
|
||||
return fullHeightBox(window)
|
||||
})
|
||||
|
||||
} else if height >= 28 {
|
||||
accordionMode := gui.Config.UserConfig.Gui.ExpandFocusedSidePanel
|
||||
accordionBox := func(defaultBox *boxlayout.Box) *boxlayout.Box {
|
||||
if accordionMode && defaultBox.Window == currentWindow {
|
||||
return &boxlayout.Box{
|
||||
Window: defaultBox.Window,
|
||||
Weight: 2,
|
||||
}
|
||||
}
|
||||
|
||||
return defaultBox
|
||||
}
|
||||
|
||||
return append([]*boxlayout.Box{
|
||||
{
|
||||
Window: sideWindowNames[0],
|
||||
Size: 3,
|
||||
},
|
||||
}, lo.Map(sideWindowNames[1:], func(window string, _ int) *boxlayout.Box {
|
||||
return accordionBox(&boxlayout.Box{Window: window, Weight: 1})
|
||||
})...)
|
||||
} else {
|
||||
squashedHeight := 1
|
||||
if height >= 21 {
|
||||
squashedHeight = 3
|
||||
}
|
||||
|
||||
squashedSidePanelBox := func(window string) *boxlayout.Box {
|
||||
if window == currentWindow {
|
||||
return &boxlayout.Box{
|
||||
Window: window,
|
||||
Weight: 1,
|
||||
}
|
||||
} else {
|
||||
return &boxlayout.Box{
|
||||
Window: window,
|
||||
Size: squashedHeight,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return lo.Map(sideWindowNames, func(window string, _ int) *boxlayout.Box {
|
||||
return squashedSidePanelBox(window)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: reintroduce
|
||||
// func (gui *Gui) currentSideWindowName() string {
|
||||
// // there is always one and only one cyclable context in the context stack. We'll look from top to bottom
|
||||
// gui.State.ContextManager.RLock()
|
||||
// defer gui.State.ContextManager.RUnlock()
|
||||
|
||||
// for idx := range gui.State.ContextManager.ContextStack {
|
||||
// reversedIdx := len(gui.State.ContextManager.ContextStack) - 1 - idx
|
||||
// context := gui.State.ContextManager.ContextStack[reversedIdx]
|
||||
|
||||
// if context.GetKind() == types.SIDE_CONTEXT {
|
||||
// return context.GetWindowName()
|
||||
// }
|
||||
// }
|
||||
|
||||
// return "files" // default
|
||||
// }
|
||||
|
||||
func (gui *Gui) currentNonPopupWindowName() string {
|
||||
return gui.peekPreviousView()
|
||||
}
|
||||
|
||||
// TODO: do this better.
|
||||
func (gui *Gui) currentSideWindowName() string {
|
||||
windowName := gui.currentWindow()
|
||||
if !lo.Contains(gui.sideViewNames(), windowName) {
|
||||
return gui.peekPreviousView()
|
||||
}
|
||||
|
||||
return windowName
|
||||
}
|
||||
|
|
@ -73,6 +73,9 @@ type Gui struct {
|
|||
T *tasks.TaskManager
|
||||
ErrorChan chan error
|
||||
CyclableViews []string
|
||||
Views Views
|
||||
|
||||
ViewsSetup bool
|
||||
}
|
||||
|
||||
type servicePanelState struct {
|
||||
|
|
@ -131,8 +134,22 @@ type guiState struct {
|
|||
// We increment it each time we switch to a new subprocess
|
||||
// Every time we go to a subprocess we need to close a few goroutines so this index is used for that purpose
|
||||
SessionIndex int
|
||||
|
||||
ScreenMode WindowMaximisation
|
||||
}
|
||||
|
||||
// screen sizing determines how much space your selected window takes up (window
|
||||
// as in panel, not your terminal's window). Sometimes you want a bit more space
|
||||
// to see the contents of a panel, and this keeps track of how much maximisation
|
||||
// you've set
|
||||
type WindowMaximisation int
|
||||
|
||||
const (
|
||||
SCREEN_NORMAL WindowMaximisation = iota
|
||||
SCREEN_HALF
|
||||
SCREEN_FULL
|
||||
)
|
||||
|
||||
// NewGui builds a new gui handler
|
||||
func NewGui(log *logrus.Entry, dockerCommand *commands.DockerCommand, oSCommand *commands.OSCommand, tr *i18n.TranslationSet, config *config.AppConfig, errorChan chan error) (*Gui, error) {
|
||||
initialState := guiState{
|
||||
|
|
|
|||
|
|
@ -506,6 +506,18 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
|||
Modifier: gocui.ModNone,
|
||||
Handler: gui.scrollRightMain,
|
||||
},
|
||||
{
|
||||
ViewName: "",
|
||||
Key: '+',
|
||||
Handler: wrappedHandler(gui.nextScreenMode),
|
||||
Description: gui.Tr.LcNextScreenMode,
|
||||
},
|
||||
{
|
||||
ViewName: "",
|
||||
Key: '_',
|
||||
Handler: wrappedHandler(gui.prevScreenMode),
|
||||
Description: gui.Tr.LcPrevScreenMode,
|
||||
},
|
||||
}
|
||||
|
||||
// TODO: add more views here
|
||||
|
|
@ -573,3 +585,9 @@ func (gui *Gui) keybindings(g *gocui.Gui) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func wrappedHandler(f func() error) func(*gocui.Gui, *gocui.View) error {
|
||||
return func(g *gocui.Gui, v *gocui.View) error {
|
||||
return f()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
package gui
|
||||
|
||||
import (
|
||||
"github.com/fatih/color"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazydocker/pkg/utils"
|
||||
)
|
||||
|
||||
const UNKNOWN_VIEW_ERROR_MSG = "unknown view"
|
||||
|
||||
// getFocusLayout returns a manager function for when view gain and lose focus
|
||||
func (gui *Gui) getFocusLayout() func(g *gocui.Gui) error {
|
||||
var previousView *gocui.View
|
||||
|
|
@ -59,204 +59,61 @@ func (gui *Gui) onFocus(v *gocui.View) {
|
|||
|
||||
// layout is called for every screen re-render e.g. when the screen is resized
|
||||
func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
if !gui.ViewsSetup {
|
||||
if err := gui.createAllViews(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gui.ViewsSetup = true
|
||||
}
|
||||
|
||||
g.Highlight = true
|
||||
width, height := g.Size()
|
||||
|
||||
information := gui.Config.Version
|
||||
if gui.g.Mouse {
|
||||
donate := color.New(color.FgMagenta, color.Underline).Sprint(gui.Tr.Donate)
|
||||
information = donate + " " + information
|
||||
}
|
||||
|
||||
minimumHeight := 9
|
||||
minimumWidth := 10
|
||||
if height < minimumHeight || width < minimumWidth {
|
||||
v, err := g.SetView("limit", 0, 0, width-1, height-1, 0)
|
||||
if err != nil {
|
||||
if err.Error() != "unknown view" {
|
||||
return err
|
||||
}
|
||||
v.Title = gui.Tr.NotEnoughSpace
|
||||
v.Wrap = true
|
||||
_, _ = g.SetViewOnTop("limit")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
currView := gui.g.CurrentView()
|
||||
currentCyclebleView := gui.peekPreviousView()
|
||||
if currView != nil {
|
||||
viewName := currView.Name()
|
||||
usePreviouseView := true
|
||||
for _, view := range gui.CyclableViews {
|
||||
if view == viewName {
|
||||
currentCyclebleView = viewName
|
||||
usePreviouseView = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if usePreviouseView {
|
||||
currentCyclebleView = gui.peekPreviousView()
|
||||
}
|
||||
}
|
||||
|
||||
usableSpace := height - 4
|
||||
|
||||
tallPanels := 3
|
||||
var vHeights map[string]int
|
||||
if gui.DockerCommand.InDockerComposeProject {
|
||||
tallPanels++
|
||||
vHeights = map[string]int{
|
||||
"project": 3,
|
||||
"services": usableSpace/tallPanels + usableSpace%tallPanels,
|
||||
"containers": usableSpace / tallPanels,
|
||||
"images": usableSpace / tallPanels,
|
||||
"volumes": usableSpace / tallPanels,
|
||||
"options": 1,
|
||||
}
|
||||
} else {
|
||||
vHeights = map[string]int{
|
||||
"project": 3,
|
||||
"containers": usableSpace/tallPanels + usableSpace%tallPanels,
|
||||
"images": usableSpace / tallPanels,
|
||||
"volumes": usableSpace / tallPanels,
|
||||
"options": 1,
|
||||
}
|
||||
}
|
||||
|
||||
if height < 28 {
|
||||
defaultHeight := 3
|
||||
if height < 21 {
|
||||
defaultHeight = 1
|
||||
}
|
||||
vHeights = map[string]int{
|
||||
"project": defaultHeight,
|
||||
"containers": defaultHeight,
|
||||
"images": defaultHeight,
|
||||
"volumes": defaultHeight,
|
||||
"options": defaultHeight,
|
||||
}
|
||||
if gui.DockerCommand.InDockerComposeProject {
|
||||
vHeights["services"] = defaultHeight
|
||||
}
|
||||
vHeights[currentCyclebleView] = height - defaultHeight*tallPanels - 1
|
||||
}
|
||||
|
||||
optionsVersionBoundary := width - max(len(utils.Decolorise(information)), 1)
|
||||
leftSideWidth := width / 3
|
||||
|
||||
appStatus := gui.statusManager.getStatusString()
|
||||
appStatusOptionsBoundary := 0
|
||||
if appStatus != "" {
|
||||
appStatusOptionsBoundary = len(appStatus) + 2
|
||||
}
|
||||
|
||||
_, _ = g.SetViewOnBottom("limit")
|
||||
_ = g.DeleteView("limit")
|
||||
viewDimensions := gui.getWindowDimensions(gui.getInformationContent(), appStatus)
|
||||
// we assume that the view has already been created.
|
||||
setViewFromDimensions := func(viewName string, windowName string) (*gocui.View, error) {
|
||||
dimensionsObj, ok := viewDimensions[windowName]
|
||||
|
||||
v, err := g.SetView("main", leftSideWidth+1, 0, width-1, height-2, gocui.LEFT)
|
||||
if err != nil {
|
||||
if err.Error() != "unknown view" {
|
||||
return err
|
||||
}
|
||||
v.Wrap = gui.Config.UserConfig.Gui.WrapMainPanel
|
||||
v.FgColor = gocui.ColorDefault
|
||||
|
||||
// when you run a docker container with the -it flags (interactive mode) it adds carriage returns for some reason. This is not docker's fault, it's an os-level default.
|
||||
v.IgnoreCarriageReturns = true
|
||||
}
|
||||
|
||||
if v, err := g.SetView("project", 0, 0, leftSideWidth, vHeights["project"]-1, gocui.BOTTOM|gocui.RIGHT); err != nil {
|
||||
if err.Error() != "unknown view" {
|
||||
return err
|
||||
}
|
||||
v.Title = gui.Tr.ProjectTitle
|
||||
v.FgColor = gocui.ColorDefault
|
||||
}
|
||||
|
||||
var servicesView *gocui.View
|
||||
aboveContainersView := "project"
|
||||
if gui.DockerCommand.InDockerComposeProject {
|
||||
aboveContainersView = "services"
|
||||
servicesView, err = g.SetViewBeneath("services", "project", vHeights["services"])
|
||||
view, err := g.View(viewName)
|
||||
if err != nil {
|
||||
if err.Error() != "unknown view" {
|
||||
return err
|
||||
}
|
||||
servicesView.Highlight = true
|
||||
servicesView.Title = gui.Tr.ServicesTitle
|
||||
servicesView.FgColor = gocui.ColorDefault
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !ok {
|
||||
// view not specified in dimensions object: so create the view and hide it
|
||||
// making the view take up the whole space in the background in case it needs
|
||||
// to render content as soon as it appears, because lazyloaded content (via a pty task)
|
||||
// cares about the size of the view.
|
||||
_, err := g.SetView(viewName, 0, 0, width, height, 0)
|
||||
view.Visible = false
|
||||
return view, err
|
||||
}
|
||||
|
||||
frameOffset := 1
|
||||
if view.Frame {
|
||||
frameOffset = 0
|
||||
}
|
||||
_, err = g.SetView(
|
||||
viewName,
|
||||
dimensionsObj.X0-frameOffset,
|
||||
dimensionsObj.Y0-frameOffset,
|
||||
dimensionsObj.X1+frameOffset,
|
||||
dimensionsObj.Y1+frameOffset,
|
||||
0,
|
||||
)
|
||||
view.Visible = true
|
||||
|
||||
return view, err
|
||||
}
|
||||
|
||||
containersView, err := g.SetViewBeneath("containers", aboveContainersView, vHeights["containers"])
|
||||
if err != nil {
|
||||
if err.Error() != "unknown view" {
|
||||
for _, viewName := range []string{"project", "services", "containers", "images", "volumes", "options", "information", "appStatus", "main", "limit"} {
|
||||
_, err := setViewFromDimensions(viewName, viewName)
|
||||
if err != nil && err.Error() != UNKNOWN_VIEW_ERROR_MSG {
|
||||
return err
|
||||
}
|
||||
containersView.Highlight = true
|
||||
if gui.Config.UserConfig.Gui.ShowAllContainers || !gui.DockerCommand.InDockerComposeProject {
|
||||
containersView.Title = gui.Tr.ContainersTitle
|
||||
} else {
|
||||
containersView.Title = gui.Tr.StandaloneContainersTitle
|
||||
}
|
||||
containersView.FgColor = gocui.ColorDefault
|
||||
}
|
||||
|
||||
imagesView, err := g.SetViewBeneath("images", "containers", vHeights["images"])
|
||||
if err != nil {
|
||||
if err.Error() != "unknown view" {
|
||||
return err
|
||||
}
|
||||
imagesView.Highlight = true
|
||||
imagesView.Title = gui.Tr.ImagesTitle
|
||||
imagesView.FgColor = gocui.ColorDefault
|
||||
}
|
||||
|
||||
volumesView, err := g.SetViewBeneath("volumes", "images", vHeights["volumes"])
|
||||
if err != nil {
|
||||
if err.Error() != "unknown view" {
|
||||
return err
|
||||
}
|
||||
volumesView.Highlight = true
|
||||
volumesView.Title = gui.Tr.VolumesTitle
|
||||
volumesView.FgColor = gocui.ColorDefault
|
||||
}
|
||||
|
||||
if v, err := g.SetView("options", appStatusOptionsBoundary-1, height-2, optionsVersionBoundary-1, height, 0); err != nil {
|
||||
if err.Error() != "unknown view" {
|
||||
return err
|
||||
}
|
||||
v.Frame = false
|
||||
if v.FgColor, err = gui.GetOptionsPanelTextColor(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if appStatusView, err := g.SetView("appStatus", -1, height-2, width, height, 0); err != nil {
|
||||
if err.Error() != "unknown view" {
|
||||
return err
|
||||
}
|
||||
appStatusView.BgColor = gocui.ColorDefault
|
||||
appStatusView.FgColor = gocui.ColorCyan
|
||||
appStatusView.Frame = false
|
||||
if _, err := g.SetViewOnBottom("appStatus"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if v, err := g.SetView("information", optionsVersionBoundary-1, height-2, width, height, 0); err != nil {
|
||||
if err.Error() != "unknown view" {
|
||||
return err
|
||||
}
|
||||
v.BgColor = gocui.ColorDefault
|
||||
v.FgColor = gocui.ColorGreen
|
||||
v.Frame = false
|
||||
if err := gui.renderString(g, "information", information); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gui.waitForIntro.Done()
|
||||
}
|
||||
|
||||
if gui.g.CurrentView() == nil {
|
||||
|
|
@ -303,3 +160,9 @@ func (gui *Gui) focusPointInView(view *gocui.View) {
|
|||
gui.focusY(state.selectedLine, state.lineCount, view)
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) prepareView(viewName string) (*gocui.View, error) {
|
||||
// arbitrarily giving the view enough size so that we don't get an error, but
|
||||
// it's expected that the view will be given the correct size before being shown
|
||||
return gui.g.SetView(viewName, 0, 0, 10, 10, 0)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ import (
|
|||
)
|
||||
|
||||
// GetOptionsPanelTextColor gets the color of the options panel text
|
||||
func (gui *Gui) GetOptionsPanelTextColor() (gocui.Attribute, error) {
|
||||
return GetGocuiStyle(gui.Config.UserConfig.Gui.Theme.OptionsTextColor), nil
|
||||
func (gui *Gui) GetOptionsPanelTextColor() gocui.Attribute {
|
||||
return GetGocuiStyle(gui.Config.UserConfig.Gui.Theme.OptionsTextColor)
|
||||
}
|
||||
|
||||
// SetColorScheme sets the color scheme for the app based on the user config
|
||||
|
|
|
|||
|
|
@ -291,6 +291,10 @@ func (gui *Gui) trimmedContent(v *gocui.View) string {
|
|||
|
||||
func (gui *Gui) currentViewName() string {
|
||||
currentView := gui.g.CurrentView()
|
||||
// this can happen when the app is first starting up
|
||||
if currentView == nil {
|
||||
return gui.initiallyFocusedViewName()
|
||||
}
|
||||
return currentView.Name()
|
||||
}
|
||||
|
||||
|
|
@ -383,3 +387,51 @@ func (gui *Gui) handleClick(v *gocui.View, itemCount int, selectedLine *int, han
|
|||
|
||||
return handleSelect(gui.g, v)
|
||||
}
|
||||
|
||||
func (gui *Gui) nextScreenMode() error {
|
||||
if gui.currentViewName() == "main" {
|
||||
gui.State.ScreenMode = prevIntInCycle([]WindowMaximisation{SCREEN_NORMAL, SCREEN_HALF, SCREEN_FULL}, gui.State.ScreenMode)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
gui.State.ScreenMode = nextIntInCycle([]WindowMaximisation{SCREEN_NORMAL, SCREEN_HALF, SCREEN_FULL}, gui.State.ScreenMode)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) prevScreenMode() error {
|
||||
if gui.currentViewName() == "main" {
|
||||
gui.State.ScreenMode = nextIntInCycle([]WindowMaximisation{SCREEN_NORMAL, SCREEN_HALF, SCREEN_FULL}, gui.State.ScreenMode)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
gui.State.ScreenMode = prevIntInCycle([]WindowMaximisation{SCREEN_NORMAL, SCREEN_HALF, SCREEN_FULL}, gui.State.ScreenMode)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func nextIntInCycle(sl []WindowMaximisation, current WindowMaximisation) WindowMaximisation {
|
||||
for i, val := range sl {
|
||||
if val == current {
|
||||
if i == len(sl)-1 {
|
||||
return sl[0]
|
||||
}
|
||||
return sl[i+1]
|
||||
}
|
||||
}
|
||||
return sl[0]
|
||||
}
|
||||
|
||||
func prevIntInCycle(sl []WindowMaximisation, current WindowMaximisation) WindowMaximisation {
|
||||
for i, val := range sl {
|
||||
if val == current {
|
||||
if i > 0 {
|
||||
return sl[i-1]
|
||||
}
|
||||
return sl[len(sl)-1]
|
||||
}
|
||||
}
|
||||
return sl[len(sl)-1]
|
||||
}
|
||||
|
|
|
|||
128
pkg/gui/views.go
Normal file
128
pkg/gui/views.go
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
package gui
|
||||
|
||||
import (
|
||||
"github.com/fatih/color"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
type Views struct {
|
||||
Project *gocui.View
|
||||
Services *gocui.View
|
||||
Containers *gocui.View
|
||||
Images *gocui.View
|
||||
Volumes *gocui.View
|
||||
|
||||
Main *gocui.View
|
||||
|
||||
Options *gocui.View
|
||||
Confirmation *gocui.View
|
||||
Menu *gocui.View
|
||||
Information *gocui.View
|
||||
AppStatus *gocui.View
|
||||
Limit *gocui.View
|
||||
}
|
||||
|
||||
type viewNameMapping struct {
|
||||
viewPtr **gocui.View
|
||||
name string
|
||||
}
|
||||
|
||||
func (gui *Gui) orderedViews() []*gocui.View {
|
||||
return lo.Map(gui.orderedViewNameMappings(), func(v viewNameMapping, _ int) *gocui.View {
|
||||
return *v.viewPtr
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) orderedViewNameMappings() []viewNameMapping {
|
||||
return []viewNameMapping{
|
||||
// first layer. Ordering within this layer does not matter because there are
|
||||
// no overlapping views
|
||||
{viewPtr: &gui.Views.Project, name: "project"},
|
||||
{viewPtr: &gui.Views.Services, name: "services"},
|
||||
{viewPtr: &gui.Views.Containers, name: "containers"},
|
||||
{viewPtr: &gui.Views.Images, name: "images"},
|
||||
{viewPtr: &gui.Views.Volumes, name: "volumes"},
|
||||
|
||||
{viewPtr: &gui.Views.Main, name: "main"},
|
||||
|
||||
// bottom line
|
||||
{viewPtr: &gui.Views.Options, name: "options"},
|
||||
{viewPtr: &gui.Views.AppStatus, name: "appStatus"},
|
||||
{viewPtr: &gui.Views.Information, name: "information"},
|
||||
|
||||
// popups.
|
||||
{viewPtr: &gui.Views.Menu, name: "menu"},
|
||||
{viewPtr: &gui.Views.Confirmation, name: "confirmation"},
|
||||
|
||||
// this guy will cover everything else when it appears
|
||||
{viewPtr: &gui.Views.Limit, name: "limit"},
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) createAllViews() error {
|
||||
var err error
|
||||
for _, mapping := range gui.orderedViewNameMappings() {
|
||||
*mapping.viewPtr, err = gui.prepareView(mapping.name)
|
||||
if err != nil && err.Error() != UNKNOWN_VIEW_ERROR_MSG {
|
||||
return err
|
||||
}
|
||||
(*mapping.viewPtr).FgColor = gocui.ColorDefault
|
||||
}
|
||||
|
||||
gui.Views.Main.Wrap = gui.Config.UserConfig.Gui.WrapMainPanel
|
||||
// when you run a docker container with the -it flags (interactive mode) it adds carriage returns for some reason. This is not docker's fault, it's an os-level default.
|
||||
gui.Views.Main.IgnoreCarriageReturns = true
|
||||
|
||||
gui.Views.Project.Title = gui.Tr.ProjectTitle
|
||||
|
||||
gui.Views.Services.Highlight = true
|
||||
gui.Views.Services.Title = gui.Tr.ServicesTitle
|
||||
|
||||
gui.Views.Containers.Highlight = true
|
||||
if gui.Config.UserConfig.Gui.ShowAllContainers || !gui.DockerCommand.InDockerComposeProject {
|
||||
gui.Views.Containers.Title = gui.Tr.ContainersTitle
|
||||
} else {
|
||||
gui.Views.Containers.Title = gui.Tr.StandaloneContainersTitle
|
||||
}
|
||||
|
||||
gui.Views.Images.Highlight = true
|
||||
gui.Views.Images.Title = gui.Tr.ImagesTitle
|
||||
|
||||
gui.Views.Volumes.Highlight = true
|
||||
gui.Views.Volumes.Title = gui.Tr.VolumesTitle
|
||||
|
||||
gui.Views.Options.Frame = false
|
||||
gui.Views.Options.FgColor = gui.GetOptionsPanelTextColor()
|
||||
|
||||
gui.Views.AppStatus.FgColor = gocui.ColorCyan
|
||||
gui.Views.AppStatus.Frame = false
|
||||
|
||||
gui.Views.Information.Frame = false
|
||||
gui.Views.Information.FgColor = gocui.ColorGreen
|
||||
|
||||
if err := gui.renderString(gui.g, "information", gui.getInformationContent()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gui.Views.Confirmation.Visible = false
|
||||
gui.Views.Menu.Visible = false
|
||||
|
||||
gui.Views.Limit.Visible = false
|
||||
gui.Views.Limit.Title = gui.Tr.NotEnoughSpace
|
||||
gui.Views.Limit.Wrap = true
|
||||
|
||||
gui.waitForIntro.Done()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) getInformationContent() string {
|
||||
informationStr := gui.Config.Version
|
||||
if !gui.g.Mouse {
|
||||
return informationStr
|
||||
}
|
||||
|
||||
donate := color.New(color.FgMagenta, color.Underline).Sprint(gui.Tr.Donate)
|
||||
return donate + " " + informationStr
|
||||
}
|
||||
7
pkg/gui/window.go
Normal file
7
pkg/gui/window.go
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
package gui
|
||||
|
||||
func (gui *Gui) currentWindow() string {
|
||||
// at the moment, we only have one view per window in lazydocker, so we
|
||||
// are using the view name as the window name
|
||||
return gui.currentViewName()
|
||||
}
|
||||
|
|
@ -105,6 +105,9 @@ type TranslationSet struct {
|
|||
|
||||
No string
|
||||
Yes string
|
||||
|
||||
LcNextScreenMode string
|
||||
LcPrevScreenMode string
|
||||
}
|
||||
|
||||
func englishSet() TranslationSet {
|
||||
|
|
@ -217,5 +220,8 @@ func englishSet() TranslationSet {
|
|||
|
||||
No: "no",
|
||||
Yes: "yes",
|
||||
|
||||
LcNextScreenMode: "next screen mode (normal/half/fullscreen)",
|
||||
LcPrevScreenMode: "prev screen mode",
|
||||
}
|
||||
}
|
||||
|
|
|
|||
5
scripts/bump_gocui.sh
Executable file
5
scripts/bump_gocui.sh
Executable file
|
|
@ -0,0 +1,5 @@
|
|||
# Go's proxy servers are not very up-to-date so that's why we use `GOPROXY=direct`
|
||||
# We specify the `awesome` branch to avoid the default behaviour of looking for a semver tag.
|
||||
GOPROXY=direct go get -u github.com/jesseduffield/gocui@awesome && go mod vendor && go mod tidy
|
||||
|
||||
# Note to self if you ever want to fork a repo be sure to use this same approach: it's important to use the branch name (e.g. master)
|
||||
5
scripts/bump_lazycore.sh
Executable file
5
scripts/bump_lazycore.sh
Executable file
|
|
@ -0,0 +1,5 @@
|
|||
# Go's proxy servers are not very up-to-date so that's why we use `GOPROXY=direct`
|
||||
# We specify the `awesome` branch to avoid the default behaviour of looking for a semver tag.
|
||||
GOPROXY=direct go get -u github.com/jesseduffield/lazycore@master && go mod vendor && go mod tidy
|
||||
|
||||
# Note to self if you ever want to fork a repo be sure to use this same approach: it's important to use the branch name (e.g. master)
|
||||
21
vendor/github.com/jesseduffield/lazycore/LICENSE
generated
vendored
Normal file
21
vendor/github.com/jesseduffield/lazycore/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2022 Jesse Duffield
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
211
vendor/github.com/jesseduffield/lazycore/pkg/boxlayout/boxlayout.go
generated
vendored
Normal file
211
vendor/github.com/jesseduffield/lazycore/pkg/boxlayout/boxlayout.go
generated
vendored
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
package boxlayout
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazycore/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
type Dimensions struct {
|
||||
X0 int
|
||||
X1 int
|
||||
Y0 int
|
||||
Y1 int
|
||||
}
|
||||
|
||||
type Direction int
|
||||
|
||||
const (
|
||||
ROW Direction = iota
|
||||
COLUMN
|
||||
)
|
||||
|
||||
// to give a high-level explanation of what's going on here. We layout our windows by arranging a bunch of boxes in the available space.
|
||||
// If a box has children, it needs to specify how it wants to arrange those children: ROW or COLUMN.
|
||||
// If a box represents a window, you can put the window name in the Window field.
|
||||
// When determining how to divvy-up the available height (for row children) or width (for column children), we first
|
||||
// give the boxes with a static `size` the space that they want. Then we apportion
|
||||
// the remaining space based on the weights of the dynamic boxes (you can't define
|
||||
// both size and weight at the same time: you gotta pick one). If there are two
|
||||
// boxes, one with weight 1 and the other with weight 2, the first one gets 33%
|
||||
// of the available space and the second one gets the remaining 66%
|
||||
|
||||
type Box struct {
|
||||
// Direction decides how the children boxes are laid out. ROW means the children will each form a row i.e. that they will be stacked on top of eachother.
|
||||
Direction Direction
|
||||
|
||||
// function which takes the width and height assigned to the box and decides which orientation it will have
|
||||
ConditionalDirection func(width int, height int) Direction
|
||||
|
||||
Children []*Box
|
||||
|
||||
// function which takes the width and height assigned to the box and decides the layout of the children.
|
||||
ConditionalChildren func(width int, height int) []*Box
|
||||
|
||||
// Window refers to the name of the window this box represents, if there is one
|
||||
Window string
|
||||
|
||||
// static Size. If parent box's direction is ROW this refers to height, otherwise width
|
||||
Size int
|
||||
|
||||
// dynamic size. Once all statically sized children have been considered, Weight decides how much of the remaining space will be taken up by the box
|
||||
// TODO: consider making there be one int and a type enum so we can't have size and Weight simultaneously defined
|
||||
Weight int
|
||||
}
|
||||
|
||||
func ArrangeWindows(root *Box, x0, y0, width, height int) map[string]Dimensions {
|
||||
children := root.getChildren(width, height)
|
||||
if len(children) == 0 {
|
||||
// leaf node
|
||||
if root.Window != "" {
|
||||
dimensionsForWindow := Dimensions{X0: x0, Y0: y0, X1: x0 + width - 1, Y1: y0 + height - 1}
|
||||
return map[string]Dimensions{root.Window: dimensionsForWindow}
|
||||
}
|
||||
return map[string]Dimensions{}
|
||||
}
|
||||
|
||||
direction := root.getDirection(width, height)
|
||||
|
||||
var availableSize int
|
||||
if direction == COLUMN {
|
||||
availableSize = width
|
||||
} else {
|
||||
availableSize = height
|
||||
}
|
||||
|
||||
sizes := calcSizes(children, availableSize)
|
||||
|
||||
result := map[string]Dimensions{}
|
||||
offset := 0
|
||||
for i, child := range children {
|
||||
boxSize := sizes[i]
|
||||
|
||||
var resultForChild map[string]Dimensions
|
||||
if direction == COLUMN {
|
||||
resultForChild = ArrangeWindows(child, x0+offset, y0, boxSize, height)
|
||||
} else {
|
||||
resultForChild = ArrangeWindows(child, x0, y0+offset, width, boxSize)
|
||||
}
|
||||
|
||||
result = mergeDimensionMaps(result, resultForChild)
|
||||
offset += boxSize
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func calcSizes(boxes []*Box, availableSpace int) []int {
|
||||
normalizedWeights := normalizeWeights(lo.Map(boxes, func(box *Box, _ int) int { return box.Weight }))
|
||||
|
||||
totalWeight := 0
|
||||
reservedSpace := 0
|
||||
for i, box := range boxes {
|
||||
if box.isStatic() {
|
||||
reservedSpace += box.Size
|
||||
} else {
|
||||
totalWeight += normalizedWeights[i]
|
||||
}
|
||||
}
|
||||
|
||||
dynamicSpace := utils.Max(0, availableSpace-reservedSpace)
|
||||
|
||||
unitSize := 0
|
||||
extraSpace := 0
|
||||
if totalWeight > 0 {
|
||||
unitSize = dynamicSpace / totalWeight
|
||||
extraSpace = dynamicSpace % totalWeight
|
||||
}
|
||||
|
||||
result := make([]int, len(boxes))
|
||||
for i, box := range boxes {
|
||||
if box.isStatic() {
|
||||
// assuming that only one static child can have a size greater than the
|
||||
// available space. In that case we just crop the size to what's available
|
||||
result[i] = utils.Min(availableSpace, box.Size)
|
||||
} else {
|
||||
result[i] = unitSize * normalizedWeights[i]
|
||||
}
|
||||
}
|
||||
|
||||
// distribute the remainder across dynamic boxes.
|
||||
for extraSpace > 0 {
|
||||
for i, weight := range normalizedWeights {
|
||||
if weight > 0 {
|
||||
result[i]++
|
||||
extraSpace--
|
||||
normalizedWeights[i]--
|
||||
|
||||
if extraSpace == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// removes common multiple from weights e.g. if we get 2, 4, 4 we return 1, 2, 2.
|
||||
func normalizeWeights(weights []int) []int {
|
||||
if len(weights) == 0 {
|
||||
return []int{}
|
||||
}
|
||||
|
||||
// to spare us some computation we'll exit early if any of our weights is 1
|
||||
if lo.SomeBy(weights, func(weight int) bool { return weight == 1 }) {
|
||||
return weights
|
||||
}
|
||||
|
||||
// map weights to factorSlices and find the lowest common factor
|
||||
positiveWeights := lo.Filter(weights, func(weight int, _ int) bool { return weight > 0 })
|
||||
factorSlices := lo.Map(positiveWeights, func(weight int, _ int) []int { return calcFactors(weight) })
|
||||
commonFactors := factorSlices[0]
|
||||
for _, factors := range factorSlices {
|
||||
commonFactors = lo.Intersect(commonFactors, factors)
|
||||
}
|
||||
|
||||
if len(commonFactors) == 0 {
|
||||
return weights
|
||||
}
|
||||
|
||||
newWeights := lo.Map(weights, func(weight int, _ int) int { return weight / commonFactors[0] })
|
||||
|
||||
return normalizeWeights(newWeights)
|
||||
}
|
||||
|
||||
func calcFactors(n int) []int {
|
||||
factors := []int{}
|
||||
for i := 2; i <= n; i++ {
|
||||
if n%i == 0 {
|
||||
factors = append(factors, i)
|
||||
}
|
||||
}
|
||||
return factors
|
||||
}
|
||||
|
||||
func (b *Box) isStatic() bool {
|
||||
return b.Size > 0
|
||||
}
|
||||
|
||||
func (b *Box) getDirection(width int, height int) Direction {
|
||||
if b.ConditionalDirection != nil {
|
||||
return b.ConditionalDirection(width, height)
|
||||
}
|
||||
return b.Direction
|
||||
}
|
||||
|
||||
func (b *Box) getChildren(width int, height int) []*Box {
|
||||
if b.ConditionalChildren != nil {
|
||||
return b.ConditionalChildren(width, height)
|
||||
}
|
||||
return b.Children
|
||||
}
|
||||
|
||||
func mergeDimensionMaps(a map[string]Dimensions, b map[string]Dimensions) map[string]Dimensions {
|
||||
result := map[string]Dimensions{}
|
||||
for _, dimensionMap := range []map[string]Dimensions{a, b} {
|
||||
for k, v := range dimensionMap {
|
||||
result[k] = v
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
16
vendor/github.com/jesseduffield/lazycore/pkg/utils/utils.go
generated
vendored
Normal file
16
vendor/github.com/jesseduffield/lazycore/pkg/utils/utils.go
generated
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
package utils
|
||||
|
||||
// Min returns the minimum of two integers
|
||||
func Min(x, y int) int {
|
||||
if x < y {
|
||||
return x
|
||||
}
|
||||
return y
|
||||
}
|
||||
|
||||
func Max(x, y int) int {
|
||||
if x > y {
|
||||
return x
|
||||
}
|
||||
return y
|
||||
}
|
||||
124
vendor/github.com/samber/lo/CHANGELOG.md
generated
vendored
124
vendor/github.com/samber/lo/CHANGELOG.md
generated
vendored
|
|
@ -2,6 +2,128 @@
|
|||
|
||||
@samber: I sometimes forget to update this file. Ping me on [Twitter](https://twitter.com/samuelberthe) or open an issue in case of error. We need to keep a clear changelog for easier lib upgrade.
|
||||
|
||||
## 1.31.0 (2022-10-06)
|
||||
|
||||
Adding:
|
||||
|
||||
- lo.SliceToChannel
|
||||
- lo.Generator
|
||||
- lo.Batch
|
||||
- lo.BatchWithTimeout
|
||||
|
||||
## 1.30.1 (2022-10-06)
|
||||
|
||||
Fix:
|
||||
|
||||
- lo.Try1: remove generic type
|
||||
- lo.Validate: format error properly
|
||||
|
||||
## 1.30.0 (2022-10-04)
|
||||
|
||||
Adding:
|
||||
|
||||
- lo.TernaryF
|
||||
- lo.Validate
|
||||
|
||||
## 1.29.0 (2022-10-02)
|
||||
|
||||
Adding:
|
||||
|
||||
- lo.ErrorAs
|
||||
- lo.TryOr
|
||||
- lo.TryOrX
|
||||
|
||||
## 1.28.0 (2022-09-05)
|
||||
|
||||
Adding:
|
||||
|
||||
- lo.ChannelDispatcher with 6 dispatching strategies:
|
||||
- lo.DispatchingStrategyRoundRobin
|
||||
- lo.DispatchingStrategyRandom
|
||||
- lo.DispatchingStrategyWeightedRandom
|
||||
- lo.DispatchingStrategyFirst
|
||||
- lo.DispatchingStrategyLeast
|
||||
- lo.DispatchingStrategyMost
|
||||
|
||||
## 1.27.1 (2022-08-15)
|
||||
|
||||
Bugfix:
|
||||
|
||||
- Removed comparable constraint for lo.FindKeyBy
|
||||
|
||||
## 1.27.0 (2022-07-29)
|
||||
|
||||
Breaking:
|
||||
|
||||
- Change of MapToSlice prototype: `MapToSlice[K comparable, V any, R any](in map[K]V, iteratee func(V, K) R) []R` -> `MapToSlice[K comparable, V any, R any](in map[K]V, iteratee func(K, V) R) []R`
|
||||
|
||||
Added:
|
||||
|
||||
- lo.ChunkString
|
||||
- lo.SliceToMap (alias to lo.Associate)
|
||||
|
||||
## 1.26.0 (2022-07-24)
|
||||
|
||||
Adding:
|
||||
|
||||
- lo.Associate
|
||||
- lo.ReduceRight
|
||||
- lo.FromPtrOr
|
||||
- lo.MapToSlice
|
||||
- lo.IsSorted
|
||||
- lo.IsSortedByKey
|
||||
|
||||
## 1.25.0 (2022-07-04)
|
||||
|
||||
Adding:
|
||||
|
||||
- lo.FindUniques
|
||||
- lo.FindUniquesBy
|
||||
- lo.FindDuplicates
|
||||
- lo.FindDuplicatesBy
|
||||
- lo.IsNotEmpty
|
||||
|
||||
## 1.24.0 (2022-07-04)
|
||||
|
||||
Adding:
|
||||
|
||||
- lo.Without
|
||||
- lo.WithoutEmpty
|
||||
|
||||
## 1.23.0 (2022-07-04)
|
||||
|
||||
Adding:
|
||||
|
||||
- lo.FindKey
|
||||
- lo.FindKeyBy
|
||||
|
||||
## 1.22.0 (2022-07-04)
|
||||
|
||||
Adding:
|
||||
|
||||
- lo.Slice
|
||||
- lo.FromPtr
|
||||
- lo.IsEmpty
|
||||
- lo.Compact
|
||||
- lo.ToPairs: alias to lo.Entries
|
||||
- lo.FromPairs: alias to lo.FromEntries
|
||||
- lo.Partial
|
||||
|
||||
Change:
|
||||
|
||||
- lo.Must + lo.MustX: add context to panic message
|
||||
|
||||
Fix:
|
||||
|
||||
- lo.Nth: out of bound exception (#137)
|
||||
|
||||
## 1.21.0 (2022-05-10)
|
||||
|
||||
Adding:
|
||||
|
||||
- lo.ToAnySlice
|
||||
- lo.FromAnySlice
|
||||
|
||||
## 1.20.0 (2022-05-02)
|
||||
|
||||
Adding:
|
||||
|
|
@ -55,7 +177,7 @@ Improvement:
|
|||
|
||||
Adding:
|
||||
|
||||
- lo.Colaesce
|
||||
- lo.Coalesce
|
||||
|
||||
## 1.13.0 (2022-04-14)
|
||||
|
||||
|
|
|
|||
11
vendor/github.com/samber/lo/Makefile
generated
vendored
11
vendor/github.com/samber/lo/Makefile
generated
vendored
|
|
@ -1,10 +1,5 @@
|
|||
|
||||
BIN=go
|
||||
# BIN=go1.18beta1
|
||||
|
||||
go1.18beta1:
|
||||
go install golang.org/dl/go1.18beta1@latest
|
||||
go1.18beta1 download
|
||||
|
||||
build:
|
||||
${BIN} build -v ./...
|
||||
|
|
@ -20,7 +15,7 @@ watch-bench:
|
|||
reflex -t 50ms -s -- sh -c 'go test -benchmem -count 3 -bench ./...'
|
||||
|
||||
coverage:
|
||||
${BIN} test -v -coverprofile cover.out .
|
||||
${BIN} test -v -coverprofile=cover.out -covermode=atomic .
|
||||
${BIN} tool cover -html=cover.out -o cover.html
|
||||
|
||||
# tools
|
||||
|
|
@ -31,7 +26,7 @@ tools:
|
|||
${BIN} install github.com/jondot/goweight@latest
|
||||
${BIN} install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
|
||||
${BIN} get -t -u golang.org/x/tools/cmd/cover
|
||||
${BIN} get -t -u github.com/sonatype-nexus-community/nancy@latest
|
||||
${BIN} install github.com/sonatype-nexus-community/nancy@latest
|
||||
go mod tidy
|
||||
|
||||
lint:
|
||||
|
|
@ -40,11 +35,9 @@ lint-fix:
|
|||
golangci-lint run --timeout 60s --max-same-issues 50 --fix ./...
|
||||
|
||||
audit: tools
|
||||
${BIN} mod tidy
|
||||
${BIN} list -json -m all | nancy sleuth
|
||||
|
||||
outdated: tools
|
||||
${BIN} mod tidy
|
||||
${BIN} list -u -m -json all | go-mod-outdated -update -direct
|
||||
|
||||
weight: tools
|
||||
|
|
|
|||
1196
vendor/github.com/samber/lo/README.md
generated
vendored
1196
vendor/github.com/samber/lo/README.md
generated
vendored
File diff suppressed because it is too large
Load diff
228
vendor/github.com/samber/lo/channel.go
generated
vendored
Normal file
228
vendor/github.com/samber/lo/channel.go
generated
vendored
Normal file
|
|
@ -0,0 +1,228 @@
|
|||
package lo
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
type DispatchingStrategy[T any] func(msg T, index uint64, channels []<-chan T) int
|
||||
|
||||
// ChannelDispatcher distributes messages from input channels into N child channels.
|
||||
// Close events are propagated to children.
|
||||
// Underlying channels can have a fixed buffer capacity or be unbuffered when cap is 0.
|
||||
func ChannelDispatcher[T any](stream <-chan T, count int, channelBufferCap int, strategy DispatchingStrategy[T]) []<-chan T {
|
||||
children := createChannels[T](count, channelBufferCap)
|
||||
|
||||
roChildren := channelsToReadOnly(children)
|
||||
|
||||
go func() {
|
||||
// propagate channel closing to children
|
||||
defer closeChannels(children)
|
||||
|
||||
var i uint64 = 0
|
||||
|
||||
for {
|
||||
msg, ok := <-stream
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
destination := strategy(msg, i, roChildren) % count
|
||||
children[destination] <- msg
|
||||
|
||||
i++
|
||||
}
|
||||
}()
|
||||
|
||||
return roChildren
|
||||
}
|
||||
|
||||
func createChannels[T any](count int, channelBufferCap int) []chan T {
|
||||
children := make([]chan T, 0, count)
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
children = append(children, make(chan T, channelBufferCap))
|
||||
}
|
||||
|
||||
return children
|
||||
}
|
||||
|
||||
func channelsToReadOnly[T any](children []chan T) []<-chan T {
|
||||
roChildren := make([]<-chan T, 0, len(children))
|
||||
|
||||
for i := range children {
|
||||
roChildren = append(roChildren, children[i])
|
||||
}
|
||||
|
||||
return roChildren
|
||||
}
|
||||
|
||||
func closeChannels[T any](children []chan T) {
|
||||
for i := 0; i < len(children); i++ {
|
||||
close(children[i])
|
||||
}
|
||||
}
|
||||
|
||||
func channelIsNotFull[T any](ch <-chan T) bool {
|
||||
return cap(ch) == 0 || len(ch) < cap(ch)
|
||||
}
|
||||
|
||||
// DispatchingStrategyRoundRobin distributes messages in a rotating sequential manner.
|
||||
// If the channel capacity is exceeded, the next channel will be selected and so on.
|
||||
func DispatchingStrategyRoundRobin[T any](msg T, index uint64, channels []<-chan T) int {
|
||||
for {
|
||||
i := int(index % uint64(len(channels)))
|
||||
if channelIsNotFull(channels[i]) {
|
||||
return i
|
||||
}
|
||||
|
||||
index++
|
||||
time.Sleep(10 * time.Microsecond) // prevent CPU from burning 🔥
|
||||
}
|
||||
}
|
||||
|
||||
// DispatchingStrategyRandom distributes messages in a random manner.
|
||||
// If the channel capacity is exceeded, another random channel will be selected and so on.
|
||||
func DispatchingStrategyRandom[T any](msg T, index uint64, channels []<-chan T) int {
|
||||
for {
|
||||
i := rand.Intn(len(channels))
|
||||
if channelIsNotFull(channels[i]) {
|
||||
return i
|
||||
}
|
||||
|
||||
time.Sleep(10 * time.Microsecond) // prevent CPU from burning 🔥
|
||||
}
|
||||
}
|
||||
|
||||
// DispatchingStrategyRandom distributes messages in a weighted manner.
|
||||
// If the channel capacity is exceeded, another random channel will be selected and so on.
|
||||
func DispatchingStrategyWeightedRandom[T any](weights []int) DispatchingStrategy[T] {
|
||||
seq := []int{}
|
||||
|
||||
for i := 0; i < len(weights); i++ {
|
||||
for j := 0; j < weights[i]; j++ {
|
||||
seq = append(seq, i)
|
||||
}
|
||||
}
|
||||
|
||||
return func(msg T, index uint64, channels []<-chan T) int {
|
||||
for {
|
||||
i := seq[rand.Intn(len(seq))]
|
||||
if channelIsNotFull(channels[i]) {
|
||||
return i
|
||||
}
|
||||
|
||||
time.Sleep(10 * time.Microsecond) // prevent CPU from burning 🔥
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DispatchingStrategyFirst distributes messages in the first non-full channel.
|
||||
// If the capacity of the first channel is exceeded, the second channel will be selected and so on.
|
||||
func DispatchingStrategyFirst[T any](msg T, index uint64, channels []<-chan T) int {
|
||||
for {
|
||||
for i := range channels {
|
||||
if channelIsNotFull(channels[i]) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
||||
time.Sleep(10 * time.Microsecond) // prevent CPU from burning 🔥
|
||||
}
|
||||
}
|
||||
|
||||
// DispatchingStrategyLeast distributes messages in the emptiest channel.
|
||||
func DispatchingStrategyLeast[T any](msg T, index uint64, channels []<-chan T) int {
|
||||
seq := Range(len(channels))
|
||||
|
||||
return MinBy(seq, func(item int, min int) bool {
|
||||
return len(channels[item]) < len(channels[min])
|
||||
})
|
||||
}
|
||||
|
||||
// DispatchingStrategyMost distributes messages in the fulliest channel.
|
||||
// If the channel capacity is exceeded, the next channel will be selected and so on.
|
||||
func DispatchingStrategyMost[T any](msg T, index uint64, channels []<-chan T) int {
|
||||
seq := Range(len(channels))
|
||||
|
||||
return MaxBy(seq, func(item int, max int) bool {
|
||||
return len(channels[item]) > len(channels[max]) && channelIsNotFull(channels[item])
|
||||
})
|
||||
}
|
||||
|
||||
// SliceToChannel returns a read-only channels of collection elements.
|
||||
func SliceToChannel[T any](bufferSize int, collection []T) <-chan T {
|
||||
ch := make(chan T, bufferSize)
|
||||
|
||||
go func() {
|
||||
for _, item := range collection {
|
||||
ch <- item
|
||||
}
|
||||
|
||||
close(ch)
|
||||
}()
|
||||
|
||||
return ch
|
||||
}
|
||||
|
||||
// Generator implements the generator design pattern.
|
||||
func Generator[T any](bufferSize int, generator func(yield func(T))) <-chan T {
|
||||
ch := make(chan T, bufferSize)
|
||||
|
||||
go func() {
|
||||
// WARNING: infinite loop
|
||||
generator(func(t T) {
|
||||
ch <- t
|
||||
})
|
||||
|
||||
close(ch)
|
||||
}()
|
||||
|
||||
return ch
|
||||
}
|
||||
|
||||
// Batch creates a slice of n elements from a channel. Returns the slice and the slice length.
|
||||
// @TODO: we should probaby provide an helper that reuse the same buffer.
|
||||
func Batch[T any](ch <-chan T, size int) (collection []T, length int, readTime time.Duration, ok bool) {
|
||||
buffer := make([]T, 0, size)
|
||||
index := 0
|
||||
now := time.Now()
|
||||
|
||||
for ; index < size; index++ {
|
||||
item, ok := <-ch
|
||||
if !ok {
|
||||
return buffer, index, time.Since(now), false
|
||||
}
|
||||
|
||||
buffer = append(buffer, item)
|
||||
}
|
||||
|
||||
return buffer, index, time.Since(now), true
|
||||
}
|
||||
|
||||
// BatchWithTimeout creates a slice of n elements from a channel, with timeout. Returns the slice and the slice length.
|
||||
// @TODO: we should probaby provide an helper that reuse the same buffer.
|
||||
func BatchWithTimeout[T any](ch <-chan T, size int, timeout time.Duration) (collection []T, length int, readTime time.Duration, ok bool) {
|
||||
expire := time.NewTimer(timeout)
|
||||
defer expire.Stop()
|
||||
|
||||
buffer := make([]T, 0, size)
|
||||
index := 0
|
||||
now := time.Now()
|
||||
|
||||
for ; index < size; index++ {
|
||||
select {
|
||||
case item, ok := <-ch:
|
||||
if !ok {
|
||||
return buffer, index, time.Since(now), false
|
||||
}
|
||||
|
||||
buffer = append(buffer, item)
|
||||
|
||||
case <-expire.C:
|
||||
return buffer, index, time.Since(now), true
|
||||
}
|
||||
}
|
||||
|
||||
return buffer, index, time.Since(now), true
|
||||
}
|
||||
22
vendor/github.com/samber/lo/condition.go
generated
vendored
22
vendor/github.com/samber/lo/condition.go
generated
vendored
|
|
@ -1,6 +1,7 @@
|
|||
package lo
|
||||
|
||||
// Ternary is a 1 line if/else statement.
|
||||
// Play: https://go.dev/play/p/t-D7WBL44h2
|
||||
func Ternary[T any](condition bool, ifOutput T, elseOutput T) T {
|
||||
if condition {
|
||||
return ifOutput
|
||||
|
|
@ -9,12 +10,23 @@ func Ternary[T any](condition bool, ifOutput T, elseOutput T) T {
|
|||
return elseOutput
|
||||
}
|
||||
|
||||
// TernaryF is a 1 line if/else statement whose options are functions
|
||||
// Play: https://go.dev/play/p/AO4VW20JoqM
|
||||
func TernaryF[T any](condition bool, ifFunc func() T, elseFunc func() T) T {
|
||||
if condition {
|
||||
return ifFunc()
|
||||
}
|
||||
|
||||
return elseFunc()
|
||||
}
|
||||
|
||||
type ifElse[T any] struct {
|
||||
result T
|
||||
done bool
|
||||
}
|
||||
|
||||
// If.
|
||||
// Play: https://go.dev/play/p/WSw3ApMxhyW
|
||||
func If[T any](condition bool, result T) *ifElse[T] {
|
||||
if condition {
|
||||
return &ifElse[T]{result, true}
|
||||
|
|
@ -25,6 +37,7 @@ func If[T any](condition bool, result T) *ifElse[T] {
|
|||
}
|
||||
|
||||
// IfF.
|
||||
// Play: https://go.dev/play/p/WSw3ApMxhyW
|
||||
func IfF[T any](condition bool, resultF func() T) *ifElse[T] {
|
||||
if condition {
|
||||
return &ifElse[T]{resultF(), true}
|
||||
|
|
@ -35,6 +48,7 @@ func IfF[T any](condition bool, resultF func() T) *ifElse[T] {
|
|||
}
|
||||
|
||||
// ElseIf.
|
||||
// Play: https://go.dev/play/p/WSw3ApMxhyW
|
||||
func (i *ifElse[T]) ElseIf(condition bool, result T) *ifElse[T] {
|
||||
if !i.done && condition {
|
||||
i.result = result
|
||||
|
|
@ -45,6 +59,7 @@ func (i *ifElse[T]) ElseIf(condition bool, result T) *ifElse[T] {
|
|||
}
|
||||
|
||||
// ElseIfF.
|
||||
// Play: https://go.dev/play/p/WSw3ApMxhyW
|
||||
func (i *ifElse[T]) ElseIfF(condition bool, resultF func() T) *ifElse[T] {
|
||||
if !i.done && condition {
|
||||
i.result = resultF()
|
||||
|
|
@ -55,6 +70,7 @@ func (i *ifElse[T]) ElseIfF(condition bool, resultF func() T) *ifElse[T] {
|
|||
}
|
||||
|
||||
// Else.
|
||||
// Play: https://go.dev/play/p/WSw3ApMxhyW
|
||||
func (i *ifElse[T]) Else(result T) T {
|
||||
if i.done {
|
||||
return i.result
|
||||
|
|
@ -64,6 +80,7 @@ func (i *ifElse[T]) Else(result T) T {
|
|||
}
|
||||
|
||||
// ElseF.
|
||||
// Play: https://go.dev/play/p/WSw3ApMxhyW
|
||||
func (i *ifElse[T]) ElseF(resultF func() T) T {
|
||||
if i.done {
|
||||
return i.result
|
||||
|
|
@ -79,6 +96,7 @@ type switchCase[T comparable, R any] struct {
|
|||
}
|
||||
|
||||
// Switch is a pure functional switch/case/default statement.
|
||||
// Play: https://go.dev/play/p/TGbKUMAeRUd
|
||||
func Switch[T comparable, R any](predicate T) *switchCase[T, R] {
|
||||
var result R
|
||||
|
||||
|
|
@ -90,6 +108,7 @@ func Switch[T comparable, R any](predicate T) *switchCase[T, R] {
|
|||
}
|
||||
|
||||
// Case.
|
||||
// Play: https://go.dev/play/p/TGbKUMAeRUd
|
||||
func (s *switchCase[T, R]) Case(val T, result R) *switchCase[T, R] {
|
||||
if !s.done && s.predicate == val {
|
||||
s.result = result
|
||||
|
|
@ -100,6 +119,7 @@ func (s *switchCase[T, R]) Case(val T, result R) *switchCase[T, R] {
|
|||
}
|
||||
|
||||
// CaseF.
|
||||
// Play: https://go.dev/play/p/TGbKUMAeRUd
|
||||
func (s *switchCase[T, R]) CaseF(val T, cb func() R) *switchCase[T, R] {
|
||||
if !s.done && s.predicate == val {
|
||||
s.result = cb()
|
||||
|
|
@ -110,6 +130,7 @@ func (s *switchCase[T, R]) CaseF(val T, cb func() R) *switchCase[T, R] {
|
|||
}
|
||||
|
||||
// Default.
|
||||
// Play: https://go.dev/play/p/TGbKUMAeRUd
|
||||
func (s *switchCase[T, R]) Default(result R) R {
|
||||
if !s.done {
|
||||
s.result = result
|
||||
|
|
@ -119,6 +140,7 @@ func (s *switchCase[T, R]) Default(result R) R {
|
|||
}
|
||||
|
||||
// DefaultF.
|
||||
// Play: https://go.dev/play/p/TGbKUMAeRUd
|
||||
func (s *switchCase[T, R]) DefaultF(cb func() R) R {
|
||||
if !s.done {
|
||||
s.result = cb()
|
||||
|
|
|
|||
4
vendor/github.com/samber/lo/docker-compose.yml
generated
vendored
4
vendor/github.com/samber/lo/docker-compose.yml
generated
vendored
|
|
@ -2,8 +2,8 @@ version: '3'
|
|||
|
||||
services:
|
||||
dev:
|
||||
build: .
|
||||
image: golang:1.18-bullseye
|
||||
volumes:
|
||||
- ./:/go/src/github.com/samber/lo
|
||||
working_dir: /go/src/github.com/samber/lo
|
||||
command: bash -c 'make tools ; make watch-test'
|
||||
command: make watch-test
|
||||
|
|
|
|||
236
vendor/github.com/samber/lo/errors.go
generated
vendored
236
vendor/github.com/samber/lo/errors.go
generated
vendored
|
|
@ -1,62 +1,115 @@
|
|||
package lo
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// Validate is a helper that creates an error when a condition is not met.
|
||||
// Play: https://go.dev/play/p/vPyh51XpCBt
|
||||
func Validate(ok bool, format string, args ...any) error {
|
||||
if !ok {
|
||||
return fmt.Errorf(fmt.Sprintf(format, args...))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func messageFromMsgAndArgs(msgAndArgs ...interface{}) string {
|
||||
if len(msgAndArgs) == 1 {
|
||||
if msgAsStr, ok := msgAndArgs[0].(string); ok {
|
||||
return msgAsStr
|
||||
}
|
||||
return fmt.Sprintf("%+v", msgAndArgs[0])
|
||||
}
|
||||
if len(msgAndArgs) > 1 {
|
||||
return fmt.Sprintf(msgAndArgs[0].(string), msgAndArgs[1:]...)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// must panics if err is error or false.
|
||||
func must(err any) {
|
||||
b, isBool := err.(bool)
|
||||
if isBool && !b {
|
||||
panic("not ok")
|
||||
func must(err any, messageArgs ...interface{}) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
e, isError := err.(error)
|
||||
if isError {
|
||||
panic(e)
|
||||
switch e := err.(type) {
|
||||
case bool:
|
||||
if !e {
|
||||
message := messageFromMsgAndArgs(messageArgs...)
|
||||
if message == "" {
|
||||
message = "not ok"
|
||||
}
|
||||
|
||||
panic(message)
|
||||
}
|
||||
|
||||
case error:
|
||||
message := messageFromMsgAndArgs(messageArgs...)
|
||||
if message != "" {
|
||||
panic(message + ": " + e.Error())
|
||||
} else {
|
||||
panic(e.Error())
|
||||
}
|
||||
|
||||
default:
|
||||
panic("must: invalid err type '" + reflect.TypeOf(err).Name() + "', should either be a bool or an error")
|
||||
}
|
||||
}
|
||||
|
||||
// Must is a helper that wraps a call to a function returning a value and an error
|
||||
// and panics if err is error or false.
|
||||
func Must[T any](val T, err any) T {
|
||||
must(err)
|
||||
// Play: https://go.dev/play/p/TMoWrRp3DyC
|
||||
func Must[T any](val T, err any, messageArgs ...interface{}) T {
|
||||
must(err, messageArgs...)
|
||||
return val
|
||||
}
|
||||
|
||||
// Must0 has the same behavior than Must, but callback returns no variable.
|
||||
func Must0(err any) {
|
||||
must(err)
|
||||
// Play: https://go.dev/play/p/TMoWrRp3DyC
|
||||
func Must0(err any, messageArgs ...interface{}) {
|
||||
must(err, messageArgs...)
|
||||
}
|
||||
|
||||
// Must1 is an alias to Must
|
||||
func Must1[T any](val T, err any) T {
|
||||
return Must(val, err)
|
||||
// Play: https://go.dev/play/p/TMoWrRp3DyC
|
||||
func Must1[T any](val T, err any, messageArgs ...interface{}) T {
|
||||
return Must(val, err, messageArgs...)
|
||||
}
|
||||
|
||||
// Must2 has the same behavior than Must, but callback returns 2 variables.
|
||||
func Must2[T1 any, T2 any](val1 T1, val2 T2, err any) (T1, T2) {
|
||||
must(err)
|
||||
// Play: https://go.dev/play/p/TMoWrRp3DyC
|
||||
func Must2[T1 any, T2 any](val1 T1, val2 T2, err any, messageArgs ...interface{}) (T1, T2) {
|
||||
must(err, messageArgs...)
|
||||
return val1, val2
|
||||
}
|
||||
|
||||
// Must3 has the same behavior than Must, but callback returns 3 variables.
|
||||
func Must3[T1 any, T2 any, T3 any](val1 T1, val2 T2, val3 T3, err any) (T1, T2, T3) {
|
||||
must(err)
|
||||
// Play: https://go.dev/play/p/TMoWrRp3DyC
|
||||
func Must3[T1 any, T2 any, T3 any](val1 T1, val2 T2, val3 T3, err any, messageArgs ...interface{}) (T1, T2, T3) {
|
||||
must(err, messageArgs...)
|
||||
return val1, val2, val3
|
||||
}
|
||||
|
||||
// Must4 has the same behavior than Must, but callback returns 4 variables.
|
||||
func Must4[T1 any, T2 any, T3 any, T4 any](val1 T1, val2 T2, val3 T3, val4 T4, err any) (T1, T2, T3, T4) {
|
||||
must(err)
|
||||
// Play: https://go.dev/play/p/TMoWrRp3DyC
|
||||
func Must4[T1 any, T2 any, T3 any, T4 any](val1 T1, val2 T2, val3 T3, val4 T4, err any, messageArgs ...interface{}) (T1, T2, T3, T4) {
|
||||
must(err, messageArgs...)
|
||||
return val1, val2, val3, val4
|
||||
}
|
||||
|
||||
// Must5 has the same behavior than Must, but callback returns 5 variables.
|
||||
func Must5[T1 any, T2 any, T3 any, T4 any, T5 any](val1 T1, val2 T2, val3 T3, val4 T4, val5 T5, err any) (T1, T2, T3, T4, T5) {
|
||||
must(err)
|
||||
// Play: https://go.dev/play/p/TMoWrRp3DyC
|
||||
func Must5[T1 any, T2 any, T3 any, T4 any, T5 any](val1 T1, val2 T2, val3 T3, val4 T4, val5 T5, err any, messageArgs ...interface{}) (T1, T2, T3, T4, T5) {
|
||||
must(err, messageArgs...)
|
||||
return val1, val2, val3, val4, val5
|
||||
}
|
||||
|
||||
// Must6 has the same behavior than Must, but callback returns 6 variables.
|
||||
func Must6[T1 any, T2 any, T3 any, T4 any, T5 any, T6 any](val1 T1, val2 T2, val3 T3, val4 T4, val5 T5, val6 T6, err any) (T1, T2, T3, T4, T5, T6) {
|
||||
must(err)
|
||||
// Play: https://go.dev/play/p/TMoWrRp3DyC
|
||||
func Must6[T1 any, T2 any, T3 any, T4 any, T5 any, T6 any](val1 T1, val2 T2, val3 T3, val4 T4, val5 T5, val6 T6, err any, messageArgs ...interface{}) (T1, T2, T3, T4, T5, T6) {
|
||||
must(err, messageArgs...)
|
||||
return val1, val2, val3, val4, val5, val6
|
||||
}
|
||||
|
||||
|
|
@ -79,6 +132,7 @@ func Try(callback func() error) (ok bool) {
|
|||
}
|
||||
|
||||
// Try0 has the same behavior than Try, but callback returns no variable.
|
||||
// Play: https://go.dev/play/p/mTyyWUvn9u4
|
||||
func Try0(callback func()) bool {
|
||||
return Try(func() error {
|
||||
callback()
|
||||
|
|
@ -87,11 +141,13 @@ func Try0(callback func()) bool {
|
|||
}
|
||||
|
||||
// Try1 is an alias to Try.
|
||||
func Try1[T any](callback func() error) bool {
|
||||
// Play: https://go.dev/play/p/mTyyWUvn9u4
|
||||
func Try1(callback func() error) bool {
|
||||
return Try(callback)
|
||||
}
|
||||
|
||||
// Try2 has the same behavior than Try, but callback returns 2 variables.
|
||||
// Play: https://go.dev/play/p/mTyyWUvn9u4
|
||||
func Try2[T any](callback func() (T, error)) bool {
|
||||
return Try(func() error {
|
||||
_, err := callback()
|
||||
|
|
@ -100,6 +156,7 @@ func Try2[T any](callback func() (T, error)) bool {
|
|||
}
|
||||
|
||||
// Try3 has the same behavior than Try, but callback returns 3 variables.
|
||||
// Play: https://go.dev/play/p/mTyyWUvn9u4
|
||||
func Try3[T, R any](callback func() (T, R, error)) bool {
|
||||
return Try(func() error {
|
||||
_, _, err := callback()
|
||||
|
|
@ -108,6 +165,7 @@ func Try3[T, R any](callback func() (T, R, error)) bool {
|
|||
}
|
||||
|
||||
// Try4 has the same behavior than Try, but callback returns 4 variables.
|
||||
// Play: https://go.dev/play/p/mTyyWUvn9u4
|
||||
func Try4[T, R, S any](callback func() (T, R, S, error)) bool {
|
||||
return Try(func() error {
|
||||
_, _, _, err := callback()
|
||||
|
|
@ -116,6 +174,7 @@ func Try4[T, R, S any](callback func() (T, R, S, error)) bool {
|
|||
}
|
||||
|
||||
// Try5 has the same behavior than Try, but callback returns 5 variables.
|
||||
// Play: https://go.dev/play/p/mTyyWUvn9u4
|
||||
func Try5[T, R, S, Q any](callback func() (T, R, S, Q, error)) bool {
|
||||
return Try(func() error {
|
||||
_, _, _, _, err := callback()
|
||||
|
|
@ -124,6 +183,7 @@ func Try5[T, R, S, Q any](callback func() (T, R, S, Q, error)) bool {
|
|||
}
|
||||
|
||||
// Try6 has the same behavior than Try, but callback returns 6 variables.
|
||||
// Play: https://go.dev/play/p/mTyyWUvn9u4
|
||||
func Try6[T, R, S, Q, U any](callback func() (T, R, S, Q, U, error)) bool {
|
||||
return Try(func() error {
|
||||
_, _, _, _, _, err := callback()
|
||||
|
|
@ -131,7 +191,125 @@ func Try6[T, R, S, Q, U any](callback func() (T, R, S, Q, U, error)) bool {
|
|||
})
|
||||
}
|
||||
|
||||
// TryOr has the same behavior than Must, but returns a default value in case of error.
|
||||
// Play: https://go.dev/play/p/B4F7Wg2Zh9X
|
||||
func TryOr[A any](callback func() (A, error), fallbackA A) (A, bool) {
|
||||
return TryOr1(callback, fallbackA)
|
||||
}
|
||||
|
||||
// TryOr1 has the same behavior than Must, but returns a default value in case of error.
|
||||
// Play: https://go.dev/play/p/B4F7Wg2Zh9X
|
||||
func TryOr1[A any](callback func() (A, error), fallbackA A) (A, bool) {
|
||||
ok := false
|
||||
|
||||
Try0(func() {
|
||||
a, err := callback()
|
||||
if err == nil {
|
||||
fallbackA = a
|
||||
ok = true
|
||||
}
|
||||
})
|
||||
|
||||
return fallbackA, ok
|
||||
}
|
||||
|
||||
// TryOr2 has the same behavior than Must, but returns a default value in case of error.
|
||||
// Play: https://go.dev/play/p/B4F7Wg2Zh9X
|
||||
func TryOr2[A any, B any](callback func() (A, B, error), fallbackA A, fallbackB B) (A, B, bool) {
|
||||
ok := false
|
||||
|
||||
Try0(func() {
|
||||
a, b, err := callback()
|
||||
if err == nil {
|
||||
fallbackA = a
|
||||
fallbackB = b
|
||||
ok = true
|
||||
}
|
||||
})
|
||||
|
||||
return fallbackA, fallbackB, ok
|
||||
}
|
||||
|
||||
// TryOr3 has the same behavior than Must, but returns a default value in case of error.
|
||||
// Play: https://go.dev/play/p/B4F7Wg2Zh9X
|
||||
func TryOr3[A any, B any, C any](callback func() (A, B, C, error), fallbackA A, fallbackB B, fallbackC C) (A, B, C, bool) {
|
||||
ok := false
|
||||
|
||||
Try0(func() {
|
||||
a, b, c, err := callback()
|
||||
if err == nil {
|
||||
fallbackA = a
|
||||
fallbackB = b
|
||||
fallbackC = c
|
||||
ok = true
|
||||
}
|
||||
})
|
||||
|
||||
return fallbackA, fallbackB, fallbackC, ok
|
||||
}
|
||||
|
||||
// TryOr4 has the same behavior than Must, but returns a default value in case of error.
|
||||
// Play: https://go.dev/play/p/B4F7Wg2Zh9X
|
||||
func TryOr4[A any, B any, C any, D any](callback func() (A, B, C, D, error), fallbackA A, fallbackB B, fallbackC C, fallbackD D) (A, B, C, D, bool) {
|
||||
ok := false
|
||||
|
||||
Try0(func() {
|
||||
a, b, c, d, err := callback()
|
||||
if err == nil {
|
||||
fallbackA = a
|
||||
fallbackB = b
|
||||
fallbackC = c
|
||||
fallbackD = d
|
||||
ok = true
|
||||
}
|
||||
})
|
||||
|
||||
return fallbackA, fallbackB, fallbackC, fallbackD, ok
|
||||
}
|
||||
|
||||
// TryOr5 has the same behavior than Must, but returns a default value in case of error.
|
||||
// Play: https://go.dev/play/p/B4F7Wg2Zh9X
|
||||
func TryOr5[A any, B any, C any, D any, E any](callback func() (A, B, C, D, E, error), fallbackA A, fallbackB B, fallbackC C, fallbackD D, fallbackE E) (A, B, C, D, E, bool) {
|
||||
ok := false
|
||||
|
||||
Try0(func() {
|
||||
a, b, c, d, e, err := callback()
|
||||
if err == nil {
|
||||
fallbackA = a
|
||||
fallbackB = b
|
||||
fallbackC = c
|
||||
fallbackD = d
|
||||
fallbackE = e
|
||||
ok = true
|
||||
}
|
||||
})
|
||||
|
||||
return fallbackA, fallbackB, fallbackC, fallbackD, fallbackE, ok
|
||||
}
|
||||
|
||||
// TryOr6 has the same behavior than Must, but returns a default value in case of error.
|
||||
// Play: https://go.dev/play/p/B4F7Wg2Zh9X
|
||||
func TryOr6[A any, B any, C any, D any, E any, F any](callback func() (A, B, C, D, E, F, error), fallbackA A, fallbackB B, fallbackC C, fallbackD D, fallbackE E, fallbackF F) (A, B, C, D, E, F, bool) {
|
||||
ok := false
|
||||
|
||||
Try0(func() {
|
||||
a, b, c, d, e, f, err := callback()
|
||||
if err == nil {
|
||||
fallbackA = a
|
||||
fallbackB = b
|
||||
fallbackC = c
|
||||
fallbackD = d
|
||||
fallbackE = e
|
||||
fallbackF = f
|
||||
ok = true
|
||||
}
|
||||
})
|
||||
|
||||
return fallbackA, fallbackB, fallbackC, fallbackD, fallbackE, fallbackF, ok
|
||||
}
|
||||
|
||||
// TryWithErrorValue has the same behavior than Try, but also returns value passed to panic.
|
||||
// Play: https://go.dev/play/p/Kc7afQIT2Fs
|
||||
func TryWithErrorValue(callback func() error) (errorValue any, ok bool) {
|
||||
ok = true
|
||||
|
||||
|
|
@ -152,6 +330,7 @@ func TryWithErrorValue(callback func() error) (errorValue any, ok bool) {
|
|||
}
|
||||
|
||||
// TryCatch has the same behavior than Try, but calls the catch function in case of error.
|
||||
// Play: https://go.dev/play/p/PnOON-EqBiU
|
||||
func TryCatch(callback func() error, catch func()) {
|
||||
if !Try(callback) {
|
||||
catch()
|
||||
|
|
@ -159,8 +338,17 @@ func TryCatch(callback func() error, catch func()) {
|
|||
}
|
||||
|
||||
// TryCatchWithErrorValue has the same behavior than TryWithErrorValue, but calls the catch function in case of error.
|
||||
// Play: https://go.dev/play/p/8Pc9gwX_GZO
|
||||
func TryCatchWithErrorValue(callback func() error, catch func(any)) {
|
||||
if err, ok := TryWithErrorValue(callback); !ok {
|
||||
catch(err)
|
||||
}
|
||||
}
|
||||
|
||||
// ErrorsAs is a shortcut for errors.As(err, &&T).
|
||||
// Play: https://go.dev/play/p/8wk5rH8UfrE
|
||||
func ErrorsAs[T error](err error) (T, bool) {
|
||||
var t T
|
||||
ok := errors.As(err, &t)
|
||||
return t, ok
|
||||
}
|
||||
|
|
|
|||
158
vendor/github.com/samber/lo/find.go
generated
vendored
158
vendor/github.com/samber/lo/find.go
generated
vendored
|
|
@ -2,7 +2,6 @@ package lo
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"math/rand"
|
||||
|
||||
"golang.org/x/exp/constraints"
|
||||
|
|
@ -87,6 +86,140 @@ func FindOrElse[T any](collection []T, fallback T, predicate func(T) bool) T {
|
|||
return fallback
|
||||
}
|
||||
|
||||
// FindKey returns the key of the first value matching.
|
||||
func FindKey[K comparable, V comparable](object map[K]V, value V) (K, bool) {
|
||||
for k, v := range object {
|
||||
if v == value {
|
||||
return k, true
|
||||
}
|
||||
}
|
||||
|
||||
return Empty[K](), false
|
||||
}
|
||||
|
||||
// FindKeyBy returns the key of the first element predicate returns truthy for.
|
||||
func FindKeyBy[K comparable, V any](object map[K]V, predicate func(K, V) bool) (K, bool) {
|
||||
for k, v := range object {
|
||||
if predicate(k, v) {
|
||||
return k, true
|
||||
}
|
||||
}
|
||||
|
||||
return Empty[K](), false
|
||||
}
|
||||
|
||||
// FindUniques returns a slice with all the unique elements of the collection.
|
||||
// The order of result values is determined by the order they occur in the collection.
|
||||
func FindUniques[T comparable](collection []T) []T {
|
||||
isDupl := make(map[T]bool, len(collection))
|
||||
|
||||
for _, item := range collection {
|
||||
duplicated, ok := isDupl[item]
|
||||
if !ok {
|
||||
isDupl[item] = false
|
||||
} else if !duplicated {
|
||||
isDupl[item] = true
|
||||
}
|
||||
}
|
||||
|
||||
result := make([]T, 0, len(collection)-len(isDupl))
|
||||
|
||||
for _, item := range collection {
|
||||
if duplicated := isDupl[item]; !duplicated {
|
||||
result = append(result, item)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// FindUniquesBy returns a slice with all the unique elements of the collection.
|
||||
// The order of result values is determined by the order they occur in the array. It accepts `iteratee` which is
|
||||
// invoked for each element in array to generate the criterion by which uniqueness is computed.
|
||||
func FindUniquesBy[T any, U comparable](collection []T, iteratee func(T) U) []T {
|
||||
isDupl := make(map[U]bool, len(collection))
|
||||
|
||||
for _, item := range collection {
|
||||
key := iteratee(item)
|
||||
|
||||
duplicated, ok := isDupl[key]
|
||||
if !ok {
|
||||
isDupl[key] = false
|
||||
} else if !duplicated {
|
||||
isDupl[key] = true
|
||||
}
|
||||
}
|
||||
|
||||
result := make([]T, 0, len(collection)-len(isDupl))
|
||||
|
||||
for _, item := range collection {
|
||||
key := iteratee(item)
|
||||
|
||||
if duplicated := isDupl[key]; !duplicated {
|
||||
result = append(result, item)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// FindDuplicates returns a slice with the first occurence of each duplicated elements of the collection.
|
||||
// The order of result values is determined by the order they occur in the collection.
|
||||
func FindDuplicates[T comparable](collection []T) []T {
|
||||
isDupl := make(map[T]bool, len(collection))
|
||||
|
||||
for _, item := range collection {
|
||||
duplicated, ok := isDupl[item]
|
||||
if !ok {
|
||||
isDupl[item] = false
|
||||
} else if !duplicated {
|
||||
isDupl[item] = true
|
||||
}
|
||||
}
|
||||
|
||||
result := make([]T, 0, len(collection)-len(isDupl))
|
||||
|
||||
for _, item := range collection {
|
||||
if duplicated := isDupl[item]; duplicated {
|
||||
result = append(result, item)
|
||||
isDupl[item] = false
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// FindDuplicatesBy returns a slice with the first occurence of each duplicated elements of the collection.
|
||||
// The order of result values is determined by the order they occur in the array. It accepts `iteratee` which is
|
||||
// invoked for each element in array to generate the criterion by which uniqueness is computed.
|
||||
func FindDuplicatesBy[T any, U comparable](collection []T, iteratee func(T) U) []T {
|
||||
isDupl := make(map[U]bool, len(collection))
|
||||
|
||||
for _, item := range collection {
|
||||
key := iteratee(item)
|
||||
|
||||
duplicated, ok := isDupl[key]
|
||||
if !ok {
|
||||
isDupl[key] = false
|
||||
} else if !duplicated {
|
||||
isDupl[key] = true
|
||||
}
|
||||
}
|
||||
|
||||
result := make([]T, 0, len(collection)-len(isDupl))
|
||||
|
||||
for _, item := range collection {
|
||||
key := iteratee(item)
|
||||
|
||||
if duplicated := isDupl[key]; duplicated {
|
||||
result = append(result, item)
|
||||
isDupl[key] = false
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Min search the minimum value of a collection.
|
||||
func Min[T constraints.Ordered](collection []T) T {
|
||||
var min T
|
||||
|
|
@ -187,19 +320,18 @@ func Last[T any](collection []T) (T, error) {
|
|||
|
||||
// Nth returns the element at index `nth` of collection. If `nth` is negative, the nth element
|
||||
// from the end is returned. An error is returned when nth is out of slice bounds.
|
||||
func Nth[T any](collection []T, nth int) (T, error) {
|
||||
if int(math.Abs(float64(nth))) >= len(collection) {
|
||||
func Nth[T any, N constraints.Integer](collection []T, nth N) (T, error) {
|
||||
n := int(nth)
|
||||
l := len(collection)
|
||||
if n >= l || -n > l {
|
||||
var t T
|
||||
return t, fmt.Errorf("nth: %d out of slice bounds", nth)
|
||||
return t, fmt.Errorf("nth: %d out of slice bounds", n)
|
||||
}
|
||||
|
||||
length := len(collection)
|
||||
|
||||
if nth >= 0 {
|
||||
return collection[nth], nil
|
||||
if n >= 0 {
|
||||
return collection[n], nil
|
||||
}
|
||||
|
||||
return collection[length+nth], nil
|
||||
return collection[l+n], nil
|
||||
}
|
||||
|
||||
// Sample returns a random item from collection.
|
||||
|
|
@ -216,11 +348,7 @@ func Sample[T any](collection []T) T {
|
|||
func Samples[T any](collection []T, count int) []T {
|
||||
size := len(collection)
|
||||
|
||||
// put values into a map, for faster deletion
|
||||
cOpy := make([]T, 0, size)
|
||||
for _, v := range collection {
|
||||
cOpy = append(cOpy, v)
|
||||
}
|
||||
cOpy := append([]T{}, collection...)
|
||||
|
||||
results := []T{}
|
||||
|
||||
|
|
|
|||
8
vendor/github.com/samber/lo/func.go
generated
vendored
Normal file
8
vendor/github.com/samber/lo/func.go
generated
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
package lo
|
||||
|
||||
// Partial returns new function that, when called, has its first argument set to the provided value.
|
||||
func Partial[T1, T2, R any](f func(T1, T2) R, arg1 T1) func(T2) R {
|
||||
return func(t2 T2) R {
|
||||
return f(arg1, t2)
|
||||
}
|
||||
}
|
||||
25
vendor/github.com/samber/lo/intersect.go
generated
vendored
25
vendor/github.com/samber/lo/intersect.go
generated
vendored
|
|
@ -175,3 +175,28 @@ func Union[T comparable](list1 []T, list2 []T) []T {
|
|||
|
||||
return result
|
||||
}
|
||||
|
||||
// Without returns slice excluding all given values.
|
||||
func Without[T comparable](collection []T, exclude ...T) []T {
|
||||
result := make([]T, 0, len(collection))
|
||||
for _, e := range collection {
|
||||
if !Contains(exclude, e) {
|
||||
result = append(result, e)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// WithoutEmpty returns slice excluding empty values.
|
||||
func WithoutEmpty[T comparable](collection []T) []T {
|
||||
var empty T
|
||||
|
||||
result := make([]T, 0, len(collection))
|
||||
for _, e := range collection {
|
||||
if e != empty {
|
||||
result = append(result, e)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
|
|
|||
42
vendor/github.com/samber/lo/map.go
generated
vendored
42
vendor/github.com/samber/lo/map.go
generated
vendored
|
|
@ -1,6 +1,7 @@
|
|||
package lo
|
||||
|
||||
// Keys creates an array of the map keys.
|
||||
// Play: https://go.dev/play/p/Uu11fHASqrU
|
||||
func Keys[K comparable, V any](in map[K]V) []K {
|
||||
result := make([]K, 0, len(in))
|
||||
|
||||
|
|
@ -12,6 +13,7 @@ func Keys[K comparable, V any](in map[K]V) []K {
|
|||
}
|
||||
|
||||
// Values creates an array of the map values.
|
||||
// Play: https://go.dev/play/p/nnRTQkzQfF6
|
||||
func Values[K comparable, V any](in map[K]V) []V {
|
||||
result := make([]V, 0, len(in))
|
||||
|
||||
|
|
@ -23,6 +25,7 @@ func Values[K comparable, V any](in map[K]V) []V {
|
|||
}
|
||||
|
||||
// PickBy returns same map type filtered by given predicate.
|
||||
// Play: https://go.dev/play/p/kdg8GR_QMmf
|
||||
func PickBy[K comparable, V any](in map[K]V, predicate func(K, V) bool) map[K]V {
|
||||
r := map[K]V{}
|
||||
for k, v := range in {
|
||||
|
|
@ -34,6 +37,7 @@ func PickBy[K comparable, V any](in map[K]V, predicate func(K, V) bool) map[K]V
|
|||
}
|
||||
|
||||
// PickByKeys returns same map type filtered by given keys.
|
||||
// Play: https://go.dev/play/p/R1imbuci9qU
|
||||
func PickByKeys[K comparable, V any](in map[K]V, keys []K) map[K]V {
|
||||
r := map[K]V{}
|
||||
for k, v := range in {
|
||||
|
|
@ -45,6 +49,7 @@ func PickByKeys[K comparable, V any](in map[K]V, keys []K) map[K]V {
|
|||
}
|
||||
|
||||
// PickByValues returns same map type filtered by given values.
|
||||
// Play: https://go.dev/play/p/1zdzSvbfsJc
|
||||
func PickByValues[K comparable, V comparable](in map[K]V, values []V) map[K]V {
|
||||
r := map[K]V{}
|
||||
for k, v := range in {
|
||||
|
|
@ -55,7 +60,8 @@ func PickByValues[K comparable, V comparable](in map[K]V, values []V) map[K]V {
|
|||
return r
|
||||
}
|
||||
|
||||
// PickBy returns same map type filtered by given predicate.
|
||||
// OmitBy returns same map type filtered by given predicate.
|
||||
// Play: https://go.dev/play/p/EtBsR43bdsd
|
||||
func OmitBy[K comparable, V any](in map[K]V, predicate func(K, V) bool) map[K]V {
|
||||
r := map[K]V{}
|
||||
for k, v := range in {
|
||||
|
|
@ -67,6 +73,7 @@ func OmitBy[K comparable, V any](in map[K]V, predicate func(K, V) bool) map[K]V
|
|||
}
|
||||
|
||||
// OmitByKeys returns same map type filtered by given keys.
|
||||
// Play: https://go.dev/play/p/t1QjCrs-ysk
|
||||
func OmitByKeys[K comparable, V any](in map[K]V, keys []K) map[K]V {
|
||||
r := map[K]V{}
|
||||
for k, v := range in {
|
||||
|
|
@ -78,6 +85,7 @@ func OmitByKeys[K comparable, V any](in map[K]V, keys []K) map[K]V {
|
|||
}
|
||||
|
||||
// OmitByValues returns same map type filtered by given values.
|
||||
// Play: https://go.dev/play/p/9UYZi-hrs8j
|
||||
func OmitByValues[K comparable, V comparable](in map[K]V, values []V) map[K]V {
|
||||
r := map[K]V{}
|
||||
for k, v := range in {
|
||||
|
|
@ -89,6 +97,7 @@ func OmitByValues[K comparable, V comparable](in map[K]V, values []V) map[K]V {
|
|||
}
|
||||
|
||||
// Entries transforms a map into array of key/value pairs.
|
||||
// Play:
|
||||
func Entries[K comparable, V any](in map[K]V) []Entry[K, V] {
|
||||
entries := make([]Entry[K, V], 0, len(in))
|
||||
|
||||
|
|
@ -102,7 +111,15 @@ func Entries[K comparable, V any](in map[K]V) []Entry[K, V] {
|
|||
return entries
|
||||
}
|
||||
|
||||
// ToPairs transforms a map into array of key/value pairs.
|
||||
// Alias of Entries().
|
||||
// Play: https://go.dev/play/p/3Dhgx46gawJ
|
||||
func ToPairs[K comparable, V any](in map[K]V) []Entry[K, V] {
|
||||
return Entries(in)
|
||||
}
|
||||
|
||||
// FromEntries transforms an array of key/value pairs into a map.
|
||||
// Play: https://go.dev/play/p/oIr5KHFGCEN
|
||||
func FromEntries[K comparable, V any](entries []Entry[K, V]) map[K]V {
|
||||
out := map[K]V{}
|
||||
|
||||
|
|
@ -113,9 +130,17 @@ func FromEntries[K comparable, V any](entries []Entry[K, V]) map[K]V {
|
|||
return out
|
||||
}
|
||||
|
||||
// FromPairs transforms an array of key/value pairs into a map.
|
||||
// Alias of FromEntries().
|
||||
// Play: https://go.dev/play/p/oIr5KHFGCEN
|
||||
func FromPairs[K comparable, V any](entries []Entry[K, V]) map[K]V {
|
||||
return FromEntries(entries)
|
||||
}
|
||||
|
||||
// Invert creates a map composed of the inverted keys and values. If map
|
||||
// contains duplicate values, subsequent values overwrite property assignments
|
||||
// of previous values.
|
||||
// Play: https://go.dev/play/p/rFQ4rak6iA1
|
||||
func Invert[K comparable, V comparable](in map[K]V) map[V]K {
|
||||
out := map[V]K{}
|
||||
|
||||
|
|
@ -127,6 +152,7 @@ func Invert[K comparable, V comparable](in map[K]V) map[V]K {
|
|||
}
|
||||
|
||||
// Assign merges multiple maps from left to right.
|
||||
// Play: https://go.dev/play/p/VhwfJOyxf5o
|
||||
func Assign[K comparable, V any](maps ...map[K]V) map[K]V {
|
||||
out := map[K]V{}
|
||||
|
||||
|
|
@ -140,6 +166,7 @@ func Assign[K comparable, V any](maps ...map[K]V) map[K]V {
|
|||
}
|
||||
|
||||
// MapKeys manipulates a map keys and transforms it to a map of another type.
|
||||
// Play: https://go.dev/play/p/9_4WPIqOetJ
|
||||
func MapKeys[K comparable, V any, R comparable](in map[K]V, iteratee func(V, K) R) map[R]V {
|
||||
result := map[R]V{}
|
||||
|
||||
|
|
@ -151,6 +178,7 @@ func MapKeys[K comparable, V any, R comparable](in map[K]V, iteratee func(V, K)
|
|||
}
|
||||
|
||||
// MapValues manipulates a map values and transforms it to a map of another type.
|
||||
// Play: https://go.dev/play/p/T_8xAfvcf0W
|
||||
func MapValues[K comparable, V any, R any](in map[K]V, iteratee func(V, K) R) map[K]R {
|
||||
result := map[K]R{}
|
||||
|
||||
|
|
@ -160,3 +188,15 @@ func MapValues[K comparable, V any, R any](in map[K]V, iteratee func(V, K) R) ma
|
|||
|
||||
return result
|
||||
}
|
||||
|
||||
// MapToSlice transforms a map into a slice based on specific iteratee
|
||||
// Play: https://go.dev/play/p/ZuiCZpDt6LD
|
||||
func MapToSlice[K comparable, V any, R any](in map[K]V, iteratee func(K, V) R) []R {
|
||||
result := make([]R, 0, len(in))
|
||||
|
||||
for k, v := range in {
|
||||
result = append(result, iteratee(k, v))
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
|
|
|||
7
vendor/github.com/samber/lo/math.go
generated
vendored
7
vendor/github.com/samber/lo/math.go
generated
vendored
|
|
@ -3,6 +3,7 @@ package lo
|
|||
import "golang.org/x/exp/constraints"
|
||||
|
||||
// Range creates an array of numbers (positive and/or negative) with given length.
|
||||
// Play: https://go.dev/play/p/0r6VimXAi9H
|
||||
func Range(elementNum int) []int {
|
||||
length := If(elementNum < 0, -elementNum).Else(elementNum)
|
||||
result := make([]int, length)
|
||||
|
|
@ -14,6 +15,7 @@ func Range(elementNum int) []int {
|
|||
}
|
||||
|
||||
// RangeFrom creates an array of numbers from start with specified length.
|
||||
// Play: https://go.dev/play/p/0r6VimXAi9H
|
||||
func RangeFrom[T constraints.Integer | constraints.Float](start T, elementNum int) []T {
|
||||
length := If(elementNum < 0, -elementNum).Else(elementNum)
|
||||
result := make([]T, length)
|
||||
|
|
@ -26,6 +28,7 @@ func RangeFrom[T constraints.Integer | constraints.Float](start T, elementNum in
|
|||
|
||||
// RangeWithSteps creates an array of numbers (positive and/or negative) progressing from start up to, but not including end.
|
||||
// step set to zero will return empty array.
|
||||
// Play: https://go.dev/play/p/0r6VimXAi9H
|
||||
func RangeWithSteps[T constraints.Integer | constraints.Float](start, end, step T) []T {
|
||||
result := []T{}
|
||||
if start == end || step == 0 {
|
||||
|
|
@ -50,6 +53,7 @@ func RangeWithSteps[T constraints.Integer | constraints.Float](start, end, step
|
|||
}
|
||||
|
||||
// Clamp clamps number within the inclusive lower and upper bounds.
|
||||
// Play: https://go.dev/play/p/RU4lJNC2hlI
|
||||
func Clamp[T constraints.Ordered](value T, min T, max T) T {
|
||||
if value < min {
|
||||
return min
|
||||
|
|
@ -59,7 +63,8 @@ func Clamp[T constraints.Ordered](value T, min T, max T) T {
|
|||
return value
|
||||
}
|
||||
|
||||
// Summarizes the values in a collection.
|
||||
// SumBy summarizes the values in a collection using the given return value from the iteration function. If collection is empty 0 is returned.
|
||||
// Play: https://go.dev/play/p/Dz_a_7jN_ca
|
||||
func SumBy[T any, R constraints.Float | constraints.Integer](collection []T, iteratee func(T) R) R {
|
||||
var sum R = 0
|
||||
for _, item := range collection {
|
||||
|
|
|
|||
32
vendor/github.com/samber/lo/pointers.go
generated
vendored
32
vendor/github.com/samber/lo/pointers.go
generated
vendored
|
|
@ -1,32 +0,0 @@
|
|||
package lo
|
||||
|
||||
// ToPtr returns a pointer copy of value.
|
||||
func ToPtr[T any](x T) *T {
|
||||
return &x
|
||||
}
|
||||
|
||||
// ToSlicePtr returns a slice of pointer copy of value.
|
||||
func ToSlicePtr[T any](collection []T) []*T {
|
||||
return Map(collection, func(x T, _ int) *T {
|
||||
return &x
|
||||
})
|
||||
}
|
||||
|
||||
// Empty returns an empty value.
|
||||
func Empty[T any]() T {
|
||||
var t T
|
||||
return t
|
||||
}
|
||||
|
||||
// Coalesce returns the first non-empty arguments. Arguments must be comparable.
|
||||
func Coalesce[T comparable](v ...T) (result T, ok bool) {
|
||||
for _, e := range v {
|
||||
if e != result {
|
||||
result = e
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
9
vendor/github.com/samber/lo/retry.go
generated
vendored
9
vendor/github.com/samber/lo/retry.go
generated
vendored
|
|
@ -46,6 +46,7 @@ func (d *debounce) cancel() {
|
|||
}
|
||||
|
||||
// NewDebounce creates a debounced instance that delays invoking functions given until after wait milliseconds have elapsed.
|
||||
// Play: https://go.dev/play/p/mz32VMK2nqe
|
||||
func NewDebounce(duration time.Duration, f ...func()) (func(), func()) {
|
||||
d := &debounce{
|
||||
after: duration,
|
||||
|
|
@ -60,7 +61,8 @@ func NewDebounce(duration time.Duration, f ...func()) (func(), func()) {
|
|||
}, d.cancel
|
||||
}
|
||||
|
||||
// Attempt invokes a function N times until it returns valid output. Returning either the caught error or nil. When first argument is less than `1`, the function runs until a sucessfull response is returned.
|
||||
// Attempt invokes a function N times until it returns valid output. Returning either the caught error or nil. When first argument is less than `1`, the function runs until a successful response is returned.
|
||||
// Play: https://go.dev/play/p/3ggJZ2ZKcMj
|
||||
func Attempt(maxIteration int, f func(int) error) (int, error) {
|
||||
var err error
|
||||
|
||||
|
|
@ -76,9 +78,10 @@ func Attempt(maxIteration int, f func(int) error) (int, error) {
|
|||
}
|
||||
|
||||
// AttemptWithDelay invokes a function N times until it returns valid output,
|
||||
// with a pause betwwen each call. Returning either the caught error or nil.
|
||||
// When first argument is less than `1`, the function runs until a sucessfull
|
||||
// with a pause between each call. Returning either the caught error or nil.
|
||||
// When first argument is less than `1`, the function runs until a successful
|
||||
// response is returned.
|
||||
// Play: https://go.dev/play/p/tVs6CygC7m1
|
||||
func AttemptWithDelay(maxIteration int, delay time.Duration, f func(int, time.Duration) error) (int, time.Duration, error) {
|
||||
var err error
|
||||
|
||||
|
|
|
|||
229
vendor/github.com/samber/lo/slice.go
generated
vendored
229
vendor/github.com/samber/lo/slice.go
generated
vendored
|
|
@ -2,9 +2,12 @@ package lo
|
|||
|
||||
import (
|
||||
"math/rand"
|
||||
|
||||
"golang.org/x/exp/constraints"
|
||||
)
|
||||
|
||||
// Filter iterates over elements of collection, returning an array of all elements predicate returns truthy for.
|
||||
// Play: https://go.dev/play/p/Apjg3WeSi7K
|
||||
func Filter[V any](collection []V, predicate func(V, int) bool) []V {
|
||||
result := []V{}
|
||||
|
||||
|
|
@ -18,6 +21,7 @@ func Filter[V any](collection []V, predicate func(V, int) bool) []V {
|
|||
}
|
||||
|
||||
// Map manipulates a slice and transforms it to a slice of another type.
|
||||
// Play: https://go.dev/play/p/OkPcYAhBo0D
|
||||
func Map[T any, R any](collection []T, iteratee func(T, int) R) []R {
|
||||
result := make([]R, len(collection))
|
||||
|
||||
|
|
@ -32,6 +36,8 @@ func Map[T any, R any](collection []T, iteratee func(T, int) R) []R {
|
|||
// The callback function should return two values:
|
||||
// - the result of the mapping operation and
|
||||
// - whether the result element should be included or not.
|
||||
//
|
||||
// Play: https://go.dev/play/p/-AuYXfy7opz
|
||||
func FilterMap[T any, R any](collection []T, callback func(T, int) (R, bool)) []R {
|
||||
result := []R{}
|
||||
|
||||
|
|
@ -45,6 +51,7 @@ func FilterMap[T any, R any](collection []T, callback func(T, int) (R, bool)) []
|
|||
}
|
||||
|
||||
// FlatMap manipulates a slice and transforms and flattens it to a slice of another type.
|
||||
// Play: https://go.dev/play/p/YSoYmQTA8-U
|
||||
func FlatMap[T any, R any](collection []T, iteratee func(T, int) []R) []R {
|
||||
result := []R{}
|
||||
|
||||
|
|
@ -57,6 +64,7 @@ func FlatMap[T any, R any](collection []T, iteratee func(T, int) []R) []R {
|
|||
|
||||
// Reduce reduces collection to a value which is the accumulated result of running each element in collection
|
||||
// through accumulator, where each successive invocation is supplied the return value of the previous.
|
||||
// Play: https://go.dev/play/p/R4UHXZNaaUG
|
||||
func Reduce[T any, R any](collection []T, accumulator func(R, T, int) R, initial R) R {
|
||||
for i, item := range collection {
|
||||
initial = accumulator(initial, item, i)
|
||||
|
|
@ -65,7 +73,18 @@ func Reduce[T any, R any](collection []T, accumulator func(R, T, int) R, initial
|
|||
return initial
|
||||
}
|
||||
|
||||
// ReduceRight helper is like Reduce except that it iterates over elements of collection from right to left.
|
||||
// Play: https://go.dev/play/p/Fq3W70l7wXF
|
||||
func ReduceRight[T any, R any](collection []T, accumulator func(R, T, int) R, initial R) R {
|
||||
for i := len(collection) - 1; i >= 0; i-- {
|
||||
initial = accumulator(initial, collection[i], i)
|
||||
}
|
||||
|
||||
return initial
|
||||
}
|
||||
|
||||
// ForEach iterates over elements of collection and invokes iteratee for each element.
|
||||
// Play: https://go.dev/play/p/oofyiUPRf8t
|
||||
func ForEach[T any](collection []T, iteratee func(T, int)) {
|
||||
for i, item := range collection {
|
||||
iteratee(item, i)
|
||||
|
|
@ -74,6 +93,7 @@ func ForEach[T any](collection []T, iteratee func(T, int)) {
|
|||
|
||||
// Times invokes the iteratee n times, returning an array of the results of each invocation.
|
||||
// The iteratee is invoked with index as argument.
|
||||
// Play: https://go.dev/play/p/vgQj3Glr6lT
|
||||
func Times[T any](count int, iteratee func(int) T) []T {
|
||||
result := make([]T, count)
|
||||
|
||||
|
|
@ -86,6 +106,7 @@ func Times[T any](count int, iteratee func(int) T) []T {
|
|||
|
||||
// Uniq returns a duplicate-free version of an array, in which only the first occurrence of each element is kept.
|
||||
// The order of result values is determined by the order they occur in the array.
|
||||
// Play: https://go.dev/play/p/DTzbeXZ6iEN
|
||||
func Uniq[T comparable](collection []T) []T {
|
||||
result := make([]T, 0, len(collection))
|
||||
seen := make(map[T]struct{}, len(collection))
|
||||
|
|
@ -105,6 +126,7 @@ func Uniq[T comparable](collection []T) []T {
|
|||
// UniqBy returns a duplicate-free version of an array, in which only the first occurrence of each element is kept.
|
||||
// The order of result values is determined by the order they occur in the array. It accepts `iteratee` which is
|
||||
// invoked for each element in array to generate the criterion by which uniqueness is computed.
|
||||
// Play: https://go.dev/play/p/g42Z3QSb53u
|
||||
func UniqBy[T any, U comparable](collection []T, iteratee func(T) U) []T {
|
||||
result := make([]T, 0, len(collection))
|
||||
seen := make(map[U]struct{}, len(collection))
|
||||
|
|
@ -124,6 +146,7 @@ func UniqBy[T any, U comparable](collection []T, iteratee func(T) U) []T {
|
|||
}
|
||||
|
||||
// GroupBy returns an object composed of keys generated from the results of running each element of collection through iteratee.
|
||||
// Play: https://go.dev/play/p/XnQBd_v6brd
|
||||
func GroupBy[T any, U comparable](collection []T, iteratee func(T) U) map[U][]T {
|
||||
result := map[U][]T{}
|
||||
|
||||
|
|
@ -138,22 +161,25 @@ func GroupBy[T any, U comparable](collection []T, iteratee func(T) U) map[U][]T
|
|||
|
||||
// Chunk returns an array of elements split into groups the length of size. If array can't be split evenly,
|
||||
// the final chunk will be the remaining elements.
|
||||
// Play: https://go.dev/play/p/EeKl0AuTehH
|
||||
func Chunk[T any](collection []T, size int) [][]T {
|
||||
if size <= 0 {
|
||||
panic("Second parameter must be greater than 0")
|
||||
}
|
||||
|
||||
result := make([][]T, 0, len(collection)/2+1)
|
||||
length := len(collection)
|
||||
chunksNum := len(collection) / size
|
||||
if len(collection)%size != 0 {
|
||||
chunksNum += 1
|
||||
}
|
||||
|
||||
for i := 0; i < length; i++ {
|
||||
chunk := i / size
|
||||
result := make([][]T, 0, chunksNum)
|
||||
|
||||
if i%size == 0 {
|
||||
result = append(result, make([]T, 0, size))
|
||||
for i := 0; i < chunksNum; i++ {
|
||||
last := (i + 1) * size
|
||||
if last > len(collection) {
|
||||
last = len(collection)
|
||||
}
|
||||
|
||||
result[chunk] = append(result[chunk], collection[i])
|
||||
result = append(result, collection[i*size:last])
|
||||
}
|
||||
|
||||
return result
|
||||
|
|
@ -162,6 +188,7 @@ func Chunk[T any](collection []T, size int) [][]T {
|
|||
// PartitionBy returns an array of elements split into groups. The order of grouped values is
|
||||
// determined by the order they occur in collection. The grouping is generated from the results
|
||||
// of running each element of collection through iteratee.
|
||||
// Play: https://go.dev/play/p/NfQ_nGjkgXW
|
||||
func PartitionBy[T any, K comparable](collection []T, iteratee func(x T) K) [][]T {
|
||||
result := [][]T{}
|
||||
seen := map[K]int{}
|
||||
|
|
@ -187,17 +214,23 @@ func PartitionBy[T any, K comparable](collection []T, iteratee func(x T) K) [][]
|
|||
}
|
||||
|
||||
// Flatten returns an array a single level deep.
|
||||
// Play: https://go.dev/play/p/rbp9ORaMpjw
|
||||
func Flatten[T any](collection [][]T) []T {
|
||||
result := []T{}
|
||||
totalLen := 0
|
||||
for i := range collection {
|
||||
totalLen += len(collection[i])
|
||||
}
|
||||
|
||||
for _, item := range collection {
|
||||
result = append(result, item...)
|
||||
result := make([]T, 0, totalLen)
|
||||
for i := range collection {
|
||||
result = append(result, collection[i]...)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Shuffle returns an array of shuffled values. Uses the Fisher-Yates shuffle algorithm.
|
||||
// Play: https://go.dev/play/p/Qp73bnTDnc7
|
||||
func Shuffle[T any](collection []T) []T {
|
||||
rand.Shuffle(len(collection), func(i, j int) {
|
||||
collection[i], collection[j] = collection[j], collection[i]
|
||||
|
|
@ -207,6 +240,7 @@ func Shuffle[T any](collection []T) []T {
|
|||
}
|
||||
|
||||
// Reverse reverses array so that the first element becomes the last, the second element becomes the second to last, and so on.
|
||||
// Play: https://go.dev/play/p/fhUMLvZ7vS6
|
||||
func Reverse[T any](collection []T) []T {
|
||||
length := len(collection)
|
||||
half := length / 2
|
||||
|
|
@ -220,6 +254,7 @@ func Reverse[T any](collection []T) []T {
|
|||
}
|
||||
|
||||
// Fill fills elements of array with `initial` value.
|
||||
// Play: https://go.dev/play/p/VwR34GzqEub
|
||||
func Fill[T Clonable[T]](collection []T, initial T) []T {
|
||||
result := make([]T, 0, len(collection))
|
||||
|
||||
|
|
@ -231,6 +266,7 @@ func Fill[T Clonable[T]](collection []T, initial T) []T {
|
|||
}
|
||||
|
||||
// Repeat builds a slice with N copies of initial value.
|
||||
// Play: https://go.dev/play/p/g3uHXbmc3b6
|
||||
func Repeat[T Clonable[T]](count int, initial T) []T {
|
||||
result := make([]T, 0, count)
|
||||
|
||||
|
|
@ -242,6 +278,7 @@ func Repeat[T Clonable[T]](count int, initial T) []T {
|
|||
}
|
||||
|
||||
// RepeatBy builds a slice with values returned by N calls of callback.
|
||||
// Play: https://go.dev/play/p/ozZLCtX_hNU
|
||||
func RepeatBy[T any](count int, predicate func(int) T) []T {
|
||||
result := make([]T, 0, count)
|
||||
|
||||
|
|
@ -253,6 +290,7 @@ func RepeatBy[T any](count int, predicate func(int) T) []T {
|
|||
}
|
||||
|
||||
// KeyBy transforms a slice or an array of structs to a map based on a pivot callback.
|
||||
// Play: https://go.dev/play/p/mdaClUAT-zZ
|
||||
func KeyBy[K comparable, V any](collection []V, iteratee func(V) K) map[K]V {
|
||||
result := make(map[K]V, len(collection))
|
||||
|
||||
|
|
@ -264,21 +302,55 @@ func KeyBy[K comparable, V any](collection []V, iteratee func(V) K) map[K]V {
|
|||
return result
|
||||
}
|
||||
|
||||
// Drop drops n elements from the beginning of a slice or array.
|
||||
func Drop[T any](collection []T, n int) []T {
|
||||
if len(collection) <= n {
|
||||
return make([]T, 0)
|
||||
}
|
||||
// Associate returns a map containing key-value pairs provided by transform function applied to elements of the given slice.
|
||||
// If any of two pairs would have the same key the last one gets added to the map.
|
||||
// The order of keys in returned map is not specified and is not guaranteed to be the same from the original array.
|
||||
// Play: https://go.dev/play/p/WHa2CfMO3Lr
|
||||
func Associate[T any, K comparable, V any](collection []T, transform func(T) (K, V)) map[K]V {
|
||||
result := make(map[K]V)
|
||||
|
||||
result := make([]T, len(collection)-n)
|
||||
for i := n; i < len(collection); i++ {
|
||||
result[i-n] = collection[i]
|
||||
for _, t := range collection {
|
||||
k, v := transform(t)
|
||||
result[k] = v
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// SliceToMap returns a map containing key-value pairs provided by transform function applied to elements of the given slice.
|
||||
// If any of two pairs would have the same key the last one gets added to the map.
|
||||
// The order of keys in returned map is not specified and is not guaranteed to be the same from the original array.
|
||||
// Alias of Associate().
|
||||
// Play: https://go.dev/play/p/WHa2CfMO3Lr
|
||||
func SliceToMap[T any, K comparable, V any](collection []T, transform func(T) (K, V)) map[K]V {
|
||||
return Associate(collection, transform)
|
||||
}
|
||||
|
||||
// Drop drops n elements from the beginning of a slice or array.
|
||||
// Play: https://go.dev/play/p/JswS7vXRJP2
|
||||
func Drop[T any](collection []T, n int) []T {
|
||||
if len(collection) <= n {
|
||||
return make([]T, 0)
|
||||
}
|
||||
|
||||
result := make([]T, 0, len(collection)-n)
|
||||
|
||||
return append(result, collection[n:]...)
|
||||
}
|
||||
|
||||
// DropRight drops n elements from the end of a slice or array.
|
||||
// Play: https://go.dev/play/p/GG0nXkSJJa3
|
||||
func DropRight[T any](collection []T, n int) []T {
|
||||
if len(collection) <= n {
|
||||
return []T{}
|
||||
}
|
||||
|
||||
result := make([]T, 0, len(collection)-n)
|
||||
return append(result, collection[:len(collection)-n]...)
|
||||
}
|
||||
|
||||
// DropWhile drops elements from the beginning of a slice or array while the predicate returns true.
|
||||
// Play: https://go.dev/play/p/7gBPYw2IK16
|
||||
func DropWhile[T any](collection []T, predicate func(T) bool) []T {
|
||||
i := 0
|
||||
for ; i < len(collection); i++ {
|
||||
|
|
@ -287,30 +359,12 @@ func DropWhile[T any](collection []T, predicate func(T) bool) []T {
|
|||
}
|
||||
}
|
||||
|
||||
result := make([]T, len(collection)-i)
|
||||
|
||||
for j := 0; i < len(collection); i, j = i+1, j+1 {
|
||||
result[j] = collection[i]
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// DropRight drops n elements from the end of a slice or array.
|
||||
func DropRight[T any](collection []T, n int) []T {
|
||||
if len(collection) <= n {
|
||||
return make([]T, 0)
|
||||
}
|
||||
|
||||
result := make([]T, len(collection)-n)
|
||||
for i := len(collection) - 1 - n; i >= 0; i-- {
|
||||
result[i] = collection[i]
|
||||
}
|
||||
|
||||
return result
|
||||
result := make([]T, 0, len(collection)-i)
|
||||
return append(result, collection[i:]...)
|
||||
}
|
||||
|
||||
// DropRightWhile drops elements from the end of a slice or array while the predicate returns true.
|
||||
// Play: https://go.dev/play/p/3-n71oEC0Hz
|
||||
func DropRightWhile[T any](collection []T, predicate func(T) bool) []T {
|
||||
i := len(collection) - 1
|
||||
for ; i >= 0; i-- {
|
||||
|
|
@ -319,16 +373,12 @@ func DropRightWhile[T any](collection []T, predicate func(T) bool) []T {
|
|||
}
|
||||
}
|
||||
|
||||
result := make([]T, i+1)
|
||||
|
||||
for ; i >= 0; i-- {
|
||||
result[i] = collection[i]
|
||||
}
|
||||
|
||||
return result
|
||||
result := make([]T, 0, i+1)
|
||||
return append(result, collection[:i+1]...)
|
||||
}
|
||||
|
||||
// Reject is the opposite of Filter, this method returns the elements of collection that predicate does not return truthy for.
|
||||
// Play: https://go.dev/play/p/YkLMODy1WEL
|
||||
func Reject[V any](collection []V, predicate func(V, int) bool) []V {
|
||||
result := []V{}
|
||||
|
||||
|
|
@ -342,6 +392,7 @@ func Reject[V any](collection []V, predicate func(V, int) bool) []V {
|
|||
}
|
||||
|
||||
// Count counts the number of elements in the collection that compare equal to value.
|
||||
// Play: https://go.dev/play/p/Y3FlK54yveC
|
||||
func Count[T comparable](collection []T, value T) (count int) {
|
||||
for _, item := range collection {
|
||||
if item == value {
|
||||
|
|
@ -353,6 +404,7 @@ func Count[T comparable](collection []T, value T) (count int) {
|
|||
}
|
||||
|
||||
// CountBy counts the number of elements in the collection for which predicate is true.
|
||||
// Play: https://go.dev/play/p/ByQbNYQQi4X
|
||||
func CountBy[T any](collection []T, predicate func(T) bool) (count int) {
|
||||
for _, item := range collection {
|
||||
if predicate(item) {
|
||||
|
|
@ -363,7 +415,8 @@ func CountBy[T any](collection []T, predicate func(T) bool) (count int) {
|
|||
return count
|
||||
}
|
||||
|
||||
// Subset return part of a slice.
|
||||
// Subset returns a copy of a slice from `offset` up to `length` elements. Like `slice[start:start+length]`, but does not panic on overflow.
|
||||
// Play: https://go.dev/play/p/tOQu1GhFcog
|
||||
func Subset[T any](collection []T, offset int, length uint) []T {
|
||||
size := len(collection)
|
||||
|
||||
|
|
@ -385,17 +438,36 @@ func Subset[T any](collection []T, offset int, length uint) []T {
|
|||
return collection[offset : offset+int(length)]
|
||||
}
|
||||
|
||||
// Replace returns a copy of the slice with the first n non-overlapping instances of old replaced by new.
|
||||
func Replace[T comparable](collection []T, old T, new T, n int) []T {
|
||||
// Slice returns a copy of a slice from `start` up to, but not including `end`. Like `slice[start:end]`, but does not panic on overflow.
|
||||
// Play: https://go.dev/play/p/8XWYhfMMA1h
|
||||
func Slice[T any](collection []T, start int, end int) []T {
|
||||
size := len(collection)
|
||||
result := make([]T, 0, size)
|
||||
|
||||
for _, item := range collection {
|
||||
if item == old && n != 0 {
|
||||
result = append(result, new)
|
||||
if start >= end {
|
||||
return []T{}
|
||||
}
|
||||
|
||||
if start > size {
|
||||
start = size
|
||||
}
|
||||
|
||||
if end > size {
|
||||
end = size
|
||||
}
|
||||
|
||||
return collection[start:end]
|
||||
}
|
||||
|
||||
// Replace returns a copy of the slice with the first n non-overlapping instances of old replaced by new.
|
||||
// Play: https://go.dev/play/p/XfPzmf9gql6
|
||||
func Replace[T comparable](collection []T, old T, new T, n int) []T {
|
||||
result := make([]T, len(collection))
|
||||
copy(result, collection)
|
||||
|
||||
for i := range result {
|
||||
if result[i] == old && n != 0 {
|
||||
result[i] = new
|
||||
n--
|
||||
} else {
|
||||
result = append(result, item)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -403,6 +475,49 @@ func Replace[T comparable](collection []T, old T, new T, n int) []T {
|
|||
}
|
||||
|
||||
// ReplaceAll returns a copy of the slice with all non-overlapping instances of old replaced by new.
|
||||
// Play: https://go.dev/play/p/a9xZFUHfYcV
|
||||
func ReplaceAll[T comparable](collection []T, old T, new T) []T {
|
||||
return Replace[T](collection, old, new, -1)
|
||||
return Replace(collection, old, new, -1)
|
||||
}
|
||||
|
||||
// Compact returns a slice of all non-zero elements.
|
||||
// Play: https://go.dev/play/p/tXiy-iK6PAc
|
||||
func Compact[T comparable](collection []T) []T {
|
||||
var zero T
|
||||
|
||||
result := []T{}
|
||||
|
||||
for _, item := range collection {
|
||||
if item != zero {
|
||||
result = append(result, item)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// IsSorted checks if a slice is sorted.
|
||||
// Play: https://go.dev/play/p/mc3qR-t4mcx
|
||||
func IsSorted[T constraints.Ordered](collection []T) bool {
|
||||
for i := 1; i < len(collection); i++ {
|
||||
if collection[i-1] > collection[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// IsSortedByKey checks if a slice is sorted by iteratee.
|
||||
// Play: https://go.dev/play/p/wiG6XyBBu49
|
||||
func IsSortedByKey[T any, K constraints.Ordered](collection []T, iteratee func(T) K) bool {
|
||||
size := len(collection)
|
||||
|
||||
for i := 0; i < size-1; i++ {
|
||||
if iteratee(collection[i]) > iteratee(collection[i+1]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
37
vendor/github.com/samber/lo/string.go
generated
vendored
37
vendor/github.com/samber/lo/string.go
generated
vendored
|
|
@ -1,8 +1,11 @@
|
|||
package lo
|
||||
|
||||
import "unicode/utf8"
|
||||
import (
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// Substring return part of a string.
|
||||
// Play: https://go.dev/play/p/TQlxQi82Lu1
|
||||
func Substring[T ~string](str T, offset int, length uint) T {
|
||||
size := len(str)
|
||||
|
||||
|
|
@ -24,7 +27,39 @@ func Substring[T ~string](str T, offset int, length uint) T {
|
|||
return str[offset : offset+int(length)]
|
||||
}
|
||||
|
||||
// ChunkString returns an array of strings split into groups the length of size. If array can't be split evenly,
|
||||
// the final chunk will be the remaining elements.
|
||||
// Play: https://go.dev/play/p/__FLTuJVz54
|
||||
func ChunkString[T ~string](str T, size int) []T {
|
||||
if size <= 0 {
|
||||
panic("lo.ChunkString: Size parameter must be greater than 0")
|
||||
}
|
||||
|
||||
if len(str) == 0 {
|
||||
return []T{""}
|
||||
}
|
||||
|
||||
if size >= len(str) {
|
||||
return []T{str}
|
||||
}
|
||||
|
||||
var chunks []T = make([]T, 0, ((len(str)-1)/size)+1)
|
||||
currentLen := 0
|
||||
currentStart := 0
|
||||
for i := range str {
|
||||
if currentLen == size {
|
||||
chunks = append(chunks, str[currentStart:i])
|
||||
currentLen = 0
|
||||
currentStart = i
|
||||
}
|
||||
currentLen++
|
||||
}
|
||||
chunks = append(chunks, str[currentStart:])
|
||||
return chunks
|
||||
}
|
||||
|
||||
// RuneLength is an alias to utf8.RuneCountInString which returns the number of runes in string.
|
||||
// Play: https://go.dev/play/p/tuhgW_lWY8l
|
||||
func RuneLength(str string) int {
|
||||
return utf8.RuneCountInString(str)
|
||||
}
|
||||
|
|
|
|||
32
vendor/github.com/samber/lo/test.go
generated
vendored
Normal file
32
vendor/github.com/samber/lo/test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
package lo
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// https://github.com/stretchr/testify/issues/1101
|
||||
func testWithTimeout(t *testing.T, timeout time.Duration) {
|
||||
t.Helper()
|
||||
|
||||
testFinished := make(chan struct{})
|
||||
t.Cleanup(func() { close(testFinished) })
|
||||
|
||||
go func() {
|
||||
select {
|
||||
case <-testFinished:
|
||||
case <-time.After(timeout):
|
||||
t.Errorf("test timed out after %s", timeout)
|
||||
os.Exit(1)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
type foo struct {
|
||||
bar string
|
||||
}
|
||||
|
||||
func (f foo) Clone() foo {
|
||||
return foo{f.bar}
|
||||
}
|
||||
136
vendor/github.com/samber/lo/tuples.go
generated
vendored
136
vendor/github.com/samber/lo/tuples.go
generated
vendored
|
|
@ -1,81 +1,97 @@
|
|||
package lo
|
||||
|
||||
// T2 creates a tuple from a list of values.
|
||||
// Play: https://go.dev/play/p/IllL3ZO4BQm
|
||||
func T2[A any, B any](a A, b B) Tuple2[A, B] {
|
||||
return Tuple2[A, B]{A: a, B: b}
|
||||
}
|
||||
|
||||
// T3 creates a tuple from a list of values.
|
||||
// Play: https://go.dev/play/p/IllL3ZO4BQm
|
||||
func T3[A any, B any, C any](a A, b B, c C) Tuple3[A, B, C] {
|
||||
return Tuple3[A, B, C]{A: a, B: b, C: c}
|
||||
}
|
||||
|
||||
// T4 creates a tuple from a list of values.
|
||||
// Play: https://go.dev/play/p/IllL3ZO4BQm
|
||||
func T4[A any, B any, C any, D any](a A, b B, c C, d D) Tuple4[A, B, C, D] {
|
||||
return Tuple4[A, B, C, D]{A: a, B: b, C: c, D: d}
|
||||
}
|
||||
|
||||
// T5 creates a tuple from a list of values.
|
||||
// Play: https://go.dev/play/p/IllL3ZO4BQm
|
||||
func T5[A any, B any, C any, D any, E any](a A, b B, c C, d D, e E) Tuple5[A, B, C, D, E] {
|
||||
return Tuple5[A, B, C, D, E]{A: a, B: b, C: c, D: d, E: e}
|
||||
}
|
||||
|
||||
// T6 creates a tuple from a list of values.
|
||||
// Play: https://go.dev/play/p/IllL3ZO4BQm
|
||||
func T6[A any, B any, C any, D any, E any, F any](a A, b B, c C, d D, e E, f F) Tuple6[A, B, C, D, E, F] {
|
||||
return Tuple6[A, B, C, D, E, F]{A: a, B: b, C: c, D: d, E: e, F: f}
|
||||
}
|
||||
|
||||
// T7 creates a tuple from a list of values.
|
||||
// Play: https://go.dev/play/p/IllL3ZO4BQm
|
||||
func T7[A any, B any, C any, D any, E any, F any, G any](a A, b B, c C, d D, e E, f F, g G) Tuple7[A, B, C, D, E, F, G] {
|
||||
return Tuple7[A, B, C, D, E, F, G]{A: a, B: b, C: c, D: d, E: e, F: f, G: g}
|
||||
}
|
||||
|
||||
// T8 creates a tuple from a list of values.
|
||||
// Play: https://go.dev/play/p/IllL3ZO4BQm
|
||||
func T8[A any, B any, C any, D any, E any, F any, G any, H any](a A, b B, c C, d D, e E, f F, g G, h H) Tuple8[A, B, C, D, E, F, G, H] {
|
||||
return Tuple8[A, B, C, D, E, F, G, H]{A: a, B: b, C: c, D: d, E: e, F: f, G: g, H: h}
|
||||
}
|
||||
|
||||
// T8 creates a tuple from a list of values.
|
||||
// Play: https://go.dev/play/p/IllL3ZO4BQm
|
||||
func T9[A any, B any, C any, D any, E any, F any, G any, H any, I any](a A, b B, c C, d D, e E, f F, g G, h H, i I) Tuple9[A, B, C, D, E, F, G, H, I] {
|
||||
return Tuple9[A, B, C, D, E, F, G, H, I]{A: a, B: b, C: c, D: d, E: e, F: f, G: g, H: h, I: i}
|
||||
}
|
||||
|
||||
// Unpack2 returns values contained in tuple.
|
||||
// Play: https://go.dev/play/p/xVP_k0kJ96W
|
||||
func Unpack2[A any, B any](tuple Tuple2[A, B]) (A, B) {
|
||||
return tuple.A, tuple.B
|
||||
}
|
||||
|
||||
// Unpack3 returns values contained in tuple.
|
||||
// Play: https://go.dev/play/p/xVP_k0kJ96W
|
||||
func Unpack3[A any, B any, C any](tuple Tuple3[A, B, C]) (A, B, C) {
|
||||
return tuple.A, tuple.B, tuple.C
|
||||
}
|
||||
|
||||
// Unpack4 returns values contained in tuple.
|
||||
// Play: https://go.dev/play/p/xVP_k0kJ96W
|
||||
func Unpack4[A any, B any, C any, D any](tuple Tuple4[A, B, C, D]) (A, B, C, D) {
|
||||
return tuple.A, tuple.B, tuple.C, tuple.D
|
||||
}
|
||||
|
||||
// Unpack5 returns values contained in tuple.
|
||||
// Play: https://go.dev/play/p/xVP_k0kJ96W
|
||||
func Unpack5[A any, B any, C any, D any, E any](tuple Tuple5[A, B, C, D, E]) (A, B, C, D, E) {
|
||||
return tuple.A, tuple.B, tuple.C, tuple.D, tuple.E
|
||||
}
|
||||
|
||||
// Unpack6 returns values contained in tuple.
|
||||
// Play: https://go.dev/play/p/xVP_k0kJ96W
|
||||
func Unpack6[A any, B any, C any, D any, E any, F any](tuple Tuple6[A, B, C, D, E, F]) (A, B, C, D, E, F) {
|
||||
return tuple.A, tuple.B, tuple.C, tuple.D, tuple.E, tuple.F
|
||||
}
|
||||
|
||||
// Unpack7 returns values contained in tuple.
|
||||
// Play: https://go.dev/play/p/xVP_k0kJ96W
|
||||
func Unpack7[A any, B any, C any, D any, E any, F any, G any](tuple Tuple7[A, B, C, D, E, F, G]) (A, B, C, D, E, F, G) {
|
||||
return tuple.A, tuple.B, tuple.C, tuple.D, tuple.E, tuple.F, tuple.G
|
||||
}
|
||||
|
||||
// Unpack8 returns values contained in tuple.
|
||||
// Play: https://go.dev/play/p/xVP_k0kJ96W
|
||||
func Unpack8[A any, B any, C any, D any, E any, F any, G any, H any](tuple Tuple8[A, B, C, D, E, F, G, H]) (A, B, C, D, E, F, G, H) {
|
||||
return tuple.A, tuple.B, tuple.C, tuple.D, tuple.E, tuple.F, tuple.G, tuple.H
|
||||
}
|
||||
|
||||
// Unpack9 returns values contained in tuple.
|
||||
// Play: https://go.dev/play/p/xVP_k0kJ96W
|
||||
func Unpack9[A any, B any, C any, D any, E any, F any, G any, H any, I any](tuple Tuple9[A, B, C, D, E, F, G, H, I]) (A, B, C, D, E, F, G, H, I) {
|
||||
return tuple.A, tuple.B, tuple.C, tuple.D, tuple.E, tuple.F, tuple.G, tuple.H, tuple.I
|
||||
}
|
||||
|
|
@ -83,14 +99,15 @@ func Unpack9[A any, B any, C any, D any, E any, F any, G any, H any, I any](tupl
|
|||
// Zip2 creates a slice of grouped elements, the first of which contains the first elements
|
||||
// of the given arrays, the second of which contains the second elements of the given arrays, and so on.
|
||||
// When collections have different size, the Tuple attributes are filled with zero value.
|
||||
// Play: https://go.dev/play/p/jujaA6GaJTp
|
||||
func Zip2[A any, B any](a []A, b []B) []Tuple2[A, B] {
|
||||
size := Max[int]([]int{len(a), len(b)})
|
||||
size := Max([]int{len(a), len(b)})
|
||||
|
||||
result := make([]Tuple2[A, B], 0, size)
|
||||
|
||||
for index := 0; index < size; index++ {
|
||||
_a, _ := Nth[A](a, index)
|
||||
_b, _ := Nth[B](b, index)
|
||||
_a, _ := Nth(a, index)
|
||||
_b, _ := Nth(b, index)
|
||||
|
||||
result = append(result, Tuple2[A, B]{
|
||||
A: _a,
|
||||
|
|
@ -104,15 +121,16 @@ func Zip2[A any, B any](a []A, b []B) []Tuple2[A, B] {
|
|||
// Zip3 creates a slice of grouped elements, the first of which contains the first elements
|
||||
// of the given arrays, the second of which contains the second elements of the given arrays, and so on.
|
||||
// When collections have different size, the Tuple attributes are filled with zero value.
|
||||
// Play: https://go.dev/play/p/jujaA6GaJTp
|
||||
func Zip3[A any, B any, C any](a []A, b []B, c []C) []Tuple3[A, B, C] {
|
||||
size := Max[int]([]int{len(a), len(b), len(c)})
|
||||
size := Max([]int{len(a), len(b), len(c)})
|
||||
|
||||
result := make([]Tuple3[A, B, C], 0, size)
|
||||
|
||||
for index := 0; index < size; index++ {
|
||||
_a, _ := Nth[A](a, index)
|
||||
_b, _ := Nth[B](b, index)
|
||||
_c, _ := Nth[C](c, index)
|
||||
_a, _ := Nth(a, index)
|
||||
_b, _ := Nth(b, index)
|
||||
_c, _ := Nth(c, index)
|
||||
|
||||
result = append(result, Tuple3[A, B, C]{
|
||||
A: _a,
|
||||
|
|
@ -127,16 +145,17 @@ func Zip3[A any, B any, C any](a []A, b []B, c []C) []Tuple3[A, B, C] {
|
|||
// Zip4 creates a slice of grouped elements, the first of which contains the first elements
|
||||
// of the given arrays, the second of which contains the second elements of the given arrays, and so on.
|
||||
// When collections have different size, the Tuple attributes are filled with zero value.
|
||||
// Play: https://go.dev/play/p/jujaA6GaJTp
|
||||
func Zip4[A any, B any, C any, D any](a []A, b []B, c []C, d []D) []Tuple4[A, B, C, D] {
|
||||
size := Max[int]([]int{len(a), len(b), len(c), len(d)})
|
||||
size := Max([]int{len(a), len(b), len(c), len(d)})
|
||||
|
||||
result := make([]Tuple4[A, B, C, D], 0, size)
|
||||
|
||||
for index := 0; index < size; index++ {
|
||||
_a, _ := Nth[A](a, index)
|
||||
_b, _ := Nth[B](b, index)
|
||||
_c, _ := Nth[C](c, index)
|
||||
_d, _ := Nth[D](d, index)
|
||||
_a, _ := Nth(a, index)
|
||||
_b, _ := Nth(b, index)
|
||||
_c, _ := Nth(c, index)
|
||||
_d, _ := Nth(d, index)
|
||||
|
||||
result = append(result, Tuple4[A, B, C, D]{
|
||||
A: _a,
|
||||
|
|
@ -152,17 +171,18 @@ func Zip4[A any, B any, C any, D any](a []A, b []B, c []C, d []D) []Tuple4[A, B,
|
|||
// Zip5 creates a slice of grouped elements, the first of which contains the first elements
|
||||
// of the given arrays, the second of which contains the second elements of the given arrays, and so on.
|
||||
// When collections have different size, the Tuple attributes are filled with zero value.
|
||||
// Play: https://go.dev/play/p/jujaA6GaJTp
|
||||
func Zip5[A any, B any, C any, D any, E any](a []A, b []B, c []C, d []D, e []E) []Tuple5[A, B, C, D, E] {
|
||||
size := Max[int]([]int{len(a), len(b), len(c), len(d), len(e)})
|
||||
size := Max([]int{len(a), len(b), len(c), len(d), len(e)})
|
||||
|
||||
result := make([]Tuple5[A, B, C, D, E], 0, size)
|
||||
|
||||
for index := 0; index < size; index++ {
|
||||
_a, _ := Nth[A](a, index)
|
||||
_b, _ := Nth[B](b, index)
|
||||
_c, _ := Nth[C](c, index)
|
||||
_d, _ := Nth[D](d, index)
|
||||
_e, _ := Nth[E](e, index)
|
||||
_a, _ := Nth(a, index)
|
||||
_b, _ := Nth(b, index)
|
||||
_c, _ := Nth(c, index)
|
||||
_d, _ := Nth(d, index)
|
||||
_e, _ := Nth(e, index)
|
||||
|
||||
result = append(result, Tuple5[A, B, C, D, E]{
|
||||
A: _a,
|
||||
|
|
@ -179,18 +199,19 @@ func Zip5[A any, B any, C any, D any, E any](a []A, b []B, c []C, d []D, e []E)
|
|||
// Zip6 creates a slice of grouped elements, the first of which contains the first elements
|
||||
// of the given arrays, the second of which contains the second elements of the given arrays, and so on.
|
||||
// When collections have different size, the Tuple attributes are filled with zero value.
|
||||
// Play: https://go.dev/play/p/jujaA6GaJTp
|
||||
func Zip6[A any, B any, C any, D any, E any, F any](a []A, b []B, c []C, d []D, e []E, f []F) []Tuple6[A, B, C, D, E, F] {
|
||||
size := Max[int]([]int{len(a), len(b), len(c), len(d), len(e), len(f)})
|
||||
size := Max([]int{len(a), len(b), len(c), len(d), len(e), len(f)})
|
||||
|
||||
result := make([]Tuple6[A, B, C, D, E, F], 0, size)
|
||||
|
||||
for index := 0; index < size; index++ {
|
||||
_a, _ := Nth[A](a, index)
|
||||
_b, _ := Nth[B](b, index)
|
||||
_c, _ := Nth[C](c, index)
|
||||
_d, _ := Nth[D](d, index)
|
||||
_e, _ := Nth[E](e, index)
|
||||
_f, _ := Nth[F](f, index)
|
||||
_a, _ := Nth(a, index)
|
||||
_b, _ := Nth(b, index)
|
||||
_c, _ := Nth(c, index)
|
||||
_d, _ := Nth(d, index)
|
||||
_e, _ := Nth(e, index)
|
||||
_f, _ := Nth(f, index)
|
||||
|
||||
result = append(result, Tuple6[A, B, C, D, E, F]{
|
||||
A: _a,
|
||||
|
|
@ -208,19 +229,20 @@ func Zip6[A any, B any, C any, D any, E any, F any](a []A, b []B, c []C, d []D,
|
|||
// Zip7 creates a slice of grouped elements, the first of which contains the first elements
|
||||
// of the given arrays, the second of which contains the second elements of the given arrays, and so on.
|
||||
// When collections have different size, the Tuple attributes are filled with zero value.
|
||||
// Play: https://go.dev/play/p/jujaA6GaJTp
|
||||
func Zip7[A any, B any, C any, D any, E any, F any, G any](a []A, b []B, c []C, d []D, e []E, f []F, g []G) []Tuple7[A, B, C, D, E, F, G] {
|
||||
size := Max[int]([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g)})
|
||||
size := Max([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g)})
|
||||
|
||||
result := make([]Tuple7[A, B, C, D, E, F, G], 0, size)
|
||||
|
||||
for index := 0; index < size; index++ {
|
||||
_a, _ := Nth[A](a, index)
|
||||
_b, _ := Nth[B](b, index)
|
||||
_c, _ := Nth[C](c, index)
|
||||
_d, _ := Nth[D](d, index)
|
||||
_e, _ := Nth[E](e, index)
|
||||
_f, _ := Nth[F](f, index)
|
||||
_g, _ := Nth[G](g, index)
|
||||
_a, _ := Nth(a, index)
|
||||
_b, _ := Nth(b, index)
|
||||
_c, _ := Nth(c, index)
|
||||
_d, _ := Nth(d, index)
|
||||
_e, _ := Nth(e, index)
|
||||
_f, _ := Nth(f, index)
|
||||
_g, _ := Nth(g, index)
|
||||
|
||||
result = append(result, Tuple7[A, B, C, D, E, F, G]{
|
||||
A: _a,
|
||||
|
|
@ -239,20 +261,21 @@ func Zip7[A any, B any, C any, D any, E any, F any, G any](a []A, b []B, c []C,
|
|||
// Zip8 creates a slice of grouped elements, the first of which contains the first elements
|
||||
// of the given arrays, the second of which contains the second elements of the given arrays, and so on.
|
||||
// When collections have different size, the Tuple attributes are filled with zero value.
|
||||
// Play: https://go.dev/play/p/jujaA6GaJTp
|
||||
func Zip8[A any, B any, C any, D any, E any, F any, G any, H any](a []A, b []B, c []C, d []D, e []E, f []F, g []G, h []H) []Tuple8[A, B, C, D, E, F, G, H] {
|
||||
size := Max[int]([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g), len(h)})
|
||||
size := Max([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g), len(h)})
|
||||
|
||||
result := make([]Tuple8[A, B, C, D, E, F, G, H], 0, size)
|
||||
|
||||
for index := 0; index < size; index++ {
|
||||
_a, _ := Nth[A](a, index)
|
||||
_b, _ := Nth[B](b, index)
|
||||
_c, _ := Nth[C](c, index)
|
||||
_d, _ := Nth[D](d, index)
|
||||
_e, _ := Nth[E](e, index)
|
||||
_f, _ := Nth[F](f, index)
|
||||
_g, _ := Nth[G](g, index)
|
||||
_h, _ := Nth[H](h, index)
|
||||
_a, _ := Nth(a, index)
|
||||
_b, _ := Nth(b, index)
|
||||
_c, _ := Nth(c, index)
|
||||
_d, _ := Nth(d, index)
|
||||
_e, _ := Nth(e, index)
|
||||
_f, _ := Nth(f, index)
|
||||
_g, _ := Nth(g, index)
|
||||
_h, _ := Nth(h, index)
|
||||
|
||||
result = append(result, Tuple8[A, B, C, D, E, F, G, H]{
|
||||
A: _a,
|
||||
|
|
@ -272,21 +295,22 @@ func Zip8[A any, B any, C any, D any, E any, F any, G any, H any](a []A, b []B,
|
|||
// Zip9 creates a slice of grouped elements, the first of which contains the first elements
|
||||
// of the given arrays, the second of which contains the second elements of the given arrays, and so on.
|
||||
// When collections have different size, the Tuple attributes are filled with zero value.
|
||||
// Play: https://go.dev/play/p/jujaA6GaJTp
|
||||
func Zip9[A any, B any, C any, D any, E any, F any, G any, H any, I any](a []A, b []B, c []C, d []D, e []E, f []F, g []G, h []H, i []I) []Tuple9[A, B, C, D, E, F, G, H, I] {
|
||||
size := Max[int]([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g), len(h), len(i)})
|
||||
size := Max([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g), len(h), len(i)})
|
||||
|
||||
result := make([]Tuple9[A, B, C, D, E, F, G, H, I], 0, size)
|
||||
|
||||
for index := 0; index < size; index++ {
|
||||
_a, _ := Nth[A](a, index)
|
||||
_b, _ := Nth[B](b, index)
|
||||
_c, _ := Nth[C](c, index)
|
||||
_d, _ := Nth[D](d, index)
|
||||
_e, _ := Nth[E](e, index)
|
||||
_f, _ := Nth[F](f, index)
|
||||
_g, _ := Nth[G](g, index)
|
||||
_h, _ := Nth[H](h, index)
|
||||
_i, _ := Nth[I](i, index)
|
||||
_a, _ := Nth(a, index)
|
||||
_b, _ := Nth(b, index)
|
||||
_c, _ := Nth(c, index)
|
||||
_d, _ := Nth(d, index)
|
||||
_e, _ := Nth(e, index)
|
||||
_f, _ := Nth(f, index)
|
||||
_g, _ := Nth(g, index)
|
||||
_h, _ := Nth(h, index)
|
||||
_i, _ := Nth(i, index)
|
||||
|
||||
result = append(result, Tuple9[A, B, C, D, E, F, G, H, I]{
|
||||
A: _a,
|
||||
|
|
@ -306,6 +330,7 @@ func Zip9[A any, B any, C any, D any, E any, F any, G any, H any, I any](a []A,
|
|||
|
||||
// Unzip2 accepts an array of grouped elements and creates an array regrouping the elements
|
||||
// to their pre-zip configuration.
|
||||
// Play: https://go.dev/play/p/ciHugugvaAW
|
||||
func Unzip2[A any, B any](tuples []Tuple2[A, B]) ([]A, []B) {
|
||||
size := len(tuples)
|
||||
r1 := make([]A, 0, size)
|
||||
|
|
@ -321,6 +346,7 @@ func Unzip2[A any, B any](tuples []Tuple2[A, B]) ([]A, []B) {
|
|||
|
||||
// Unzip3 accepts an array of grouped elements and creates an array regrouping the elements
|
||||
// to their pre-zip configuration.
|
||||
// Play: https://go.dev/play/p/ciHugugvaAW
|
||||
func Unzip3[A any, B any, C any](tuples []Tuple3[A, B, C]) ([]A, []B, []C) {
|
||||
size := len(tuples)
|
||||
r1 := make([]A, 0, size)
|
||||
|
|
@ -338,6 +364,7 @@ func Unzip3[A any, B any, C any](tuples []Tuple3[A, B, C]) ([]A, []B, []C) {
|
|||
|
||||
// Unzip4 accepts an array of grouped elements and creates an array regrouping the elements
|
||||
// to their pre-zip configuration.
|
||||
// Play: https://go.dev/play/p/ciHugugvaAW
|
||||
func Unzip4[A any, B any, C any, D any](tuples []Tuple4[A, B, C, D]) ([]A, []B, []C, []D) {
|
||||
size := len(tuples)
|
||||
r1 := make([]A, 0, size)
|
||||
|
|
@ -357,6 +384,7 @@ func Unzip4[A any, B any, C any, D any](tuples []Tuple4[A, B, C, D]) ([]A, []B,
|
|||
|
||||
// Unzip5 accepts an array of grouped elements and creates an array regrouping the elements
|
||||
// to their pre-zip configuration.
|
||||
// Play: https://go.dev/play/p/ciHugugvaAW
|
||||
func Unzip5[A any, B any, C any, D any, E any](tuples []Tuple5[A, B, C, D, E]) ([]A, []B, []C, []D, []E) {
|
||||
size := len(tuples)
|
||||
r1 := make([]A, 0, size)
|
||||
|
|
@ -378,6 +406,7 @@ func Unzip5[A any, B any, C any, D any, E any](tuples []Tuple5[A, B, C, D, E]) (
|
|||
|
||||
// Unzip6 accepts an array of grouped elements and creates an array regrouping the elements
|
||||
// to their pre-zip configuration.
|
||||
// Play: https://go.dev/play/p/ciHugugvaAW
|
||||
func Unzip6[A any, B any, C any, D any, E any, F any](tuples []Tuple6[A, B, C, D, E, F]) ([]A, []B, []C, []D, []E, []F) {
|
||||
size := len(tuples)
|
||||
r1 := make([]A, 0, size)
|
||||
|
|
@ -401,6 +430,7 @@ func Unzip6[A any, B any, C any, D any, E any, F any](tuples []Tuple6[A, B, C, D
|
|||
|
||||
// Unzip7 accepts an array of grouped elements and creates an array regrouping the elements
|
||||
// to their pre-zip configuration.
|
||||
// Play: https://go.dev/play/p/ciHugugvaAW
|
||||
func Unzip7[A any, B any, C any, D any, E any, F any, G any](tuples []Tuple7[A, B, C, D, E, F, G]) ([]A, []B, []C, []D, []E, []F, []G) {
|
||||
size := len(tuples)
|
||||
r1 := make([]A, 0, size)
|
||||
|
|
@ -426,6 +456,7 @@ func Unzip7[A any, B any, C any, D any, E any, F any, G any](tuples []Tuple7[A,
|
|||
|
||||
// Unzip8 accepts an array of grouped elements and creates an array regrouping the elements
|
||||
// to their pre-zip configuration.
|
||||
// Play: https://go.dev/play/p/ciHugugvaAW
|
||||
func Unzip8[A any, B any, C any, D any, E any, F any, G any, H any](tuples []Tuple8[A, B, C, D, E, F, G, H]) ([]A, []B, []C, []D, []E, []F, []G, []H) {
|
||||
size := len(tuples)
|
||||
r1 := make([]A, 0, size)
|
||||
|
|
@ -453,6 +484,7 @@ func Unzip8[A any, B any, C any, D any, E any, F any, G any, H any](tuples []Tup
|
|||
|
||||
// Unzip9 accepts an array of grouped elements and creates an array regrouping the elements
|
||||
// to their pre-zip configuration.
|
||||
// Play: https://go.dev/play/p/ciHugugvaAW
|
||||
func Unzip9[A any, B any, C any, D any, E any, F any, G any, H any, I any](tuples []Tuple9[A, B, C, D, E, F, G, H, I]) ([]A, []B, []C, []D, []E, []F, []G, []H, []I) {
|
||||
size := len(tuples)
|
||||
r1 := make([]A, 0, size)
|
||||
|
|
|
|||
88
vendor/github.com/samber/lo/type_manipulation.go
generated
vendored
Normal file
88
vendor/github.com/samber/lo/type_manipulation.go
generated
vendored
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
package lo
|
||||
|
||||
// ToPtr returns a pointer copy of value.
|
||||
func ToPtr[T any](x T) *T {
|
||||
return &x
|
||||
}
|
||||
|
||||
// FromPtr returns the pointer value or empty.
|
||||
func FromPtr[T any](x *T) T {
|
||||
if x == nil {
|
||||
return Empty[T]()
|
||||
}
|
||||
|
||||
return *x
|
||||
}
|
||||
|
||||
// FromPtrOr returns the pointer value or the fallback value.
|
||||
func FromPtrOr[T any](x *T, fallback T) T {
|
||||
if x == nil {
|
||||
return fallback
|
||||
}
|
||||
|
||||
return *x
|
||||
}
|
||||
|
||||
// ToSlicePtr returns a slice of pointer copy of value.
|
||||
func ToSlicePtr[T any](collection []T) []*T {
|
||||
return Map(collection, func(x T, _ int) *T {
|
||||
return &x
|
||||
})
|
||||
}
|
||||
|
||||
// ToAnySlice returns a slice with all elements mapped to `any` type
|
||||
func ToAnySlice[T any](collection []T) []any {
|
||||
result := make([]any, len(collection))
|
||||
for i, item := range collection {
|
||||
result[i] = item
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// FromAnySlice returns an `any` slice with all elements mapped to a type.
|
||||
// Returns false in case of type conversion failure.
|
||||
func FromAnySlice[T any](in []any) (out []T, ok bool) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
out = []T{}
|
||||
ok = false
|
||||
}
|
||||
}()
|
||||
|
||||
result := make([]T, len(in))
|
||||
for i, item := range in {
|
||||
result[i] = item.(T)
|
||||
}
|
||||
return result, true
|
||||
}
|
||||
|
||||
// Empty returns an empty value.
|
||||
func Empty[T any]() T {
|
||||
var zero T
|
||||
return zero
|
||||
}
|
||||
|
||||
// IsEmpty returns true if argument is a zero value.
|
||||
func IsEmpty[T comparable](v T) bool {
|
||||
var zero T
|
||||
return zero == v
|
||||
}
|
||||
|
||||
// IsNotEmpty returns true if argument is not a zero value.
|
||||
func IsNotEmpty[T comparable](v T) bool {
|
||||
var zero T
|
||||
return zero != v
|
||||
}
|
||||
|
||||
// Coalesce returns the first non-empty arguments. Arguments must be comparable.
|
||||
func Coalesce[T comparable](v ...T) (result T, ok bool) {
|
||||
for _, e := range v {
|
||||
if e != result {
|
||||
result = e
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
76
vendor/github.com/stretchr/testify/assert/assertion_compare.go
generated
vendored
76
vendor/github.com/stretchr/testify/assert/assertion_compare.go
generated
vendored
|
|
@ -1,8 +1,10 @@
|
|||
package assert
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
type CompareType int
|
||||
|
|
@ -30,6 +32,9 @@ var (
|
|||
float64Type = reflect.TypeOf(float64(1))
|
||||
|
||||
stringType = reflect.TypeOf("")
|
||||
|
||||
timeType = reflect.TypeOf(time.Time{})
|
||||
bytesType = reflect.TypeOf([]byte{})
|
||||
)
|
||||
|
||||
func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
|
||||
|
|
@ -299,6 +304,47 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
|
|||
return compareLess, true
|
||||
}
|
||||
}
|
||||
// Check for known struct types we can check for compare results.
|
||||
case reflect.Struct:
|
||||
{
|
||||
// All structs enter here. We're not interested in most types.
|
||||
if !canConvert(obj1Value, timeType) {
|
||||
break
|
||||
}
|
||||
|
||||
// time.Time can compared!
|
||||
timeObj1, ok := obj1.(time.Time)
|
||||
if !ok {
|
||||
timeObj1 = obj1Value.Convert(timeType).Interface().(time.Time)
|
||||
}
|
||||
|
||||
timeObj2, ok := obj2.(time.Time)
|
||||
if !ok {
|
||||
timeObj2 = obj2Value.Convert(timeType).Interface().(time.Time)
|
||||
}
|
||||
|
||||
return compare(timeObj1.UnixNano(), timeObj2.UnixNano(), reflect.Int64)
|
||||
}
|
||||
case reflect.Slice:
|
||||
{
|
||||
// We only care about the []byte type.
|
||||
if !canConvert(obj1Value, bytesType) {
|
||||
break
|
||||
}
|
||||
|
||||
// []byte can be compared!
|
||||
bytesObj1, ok := obj1.([]byte)
|
||||
if !ok {
|
||||
bytesObj1 = obj1Value.Convert(bytesType).Interface().([]byte)
|
||||
|
||||
}
|
||||
bytesObj2, ok := obj2.([]byte)
|
||||
if !ok {
|
||||
bytesObj2 = obj2Value.Convert(bytesType).Interface().([]byte)
|
||||
}
|
||||
|
||||
return CompareType(bytes.Compare(bytesObj1, bytesObj2)), true
|
||||
}
|
||||
}
|
||||
|
||||
return compareEqual, false
|
||||
|
|
@ -310,7 +356,10 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
|
|||
// assert.Greater(t, float64(2), float64(1))
|
||||
// assert.Greater(t, "b", "a")
|
||||
func Greater(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
|
||||
return compareTwoValues(t, e1, e2, []CompareType{compareGreater}, "\"%v\" is not greater than \"%v\"", msgAndArgs)
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return compareTwoValues(t, e1, e2, []CompareType{compareGreater}, "\"%v\" is not greater than \"%v\"", msgAndArgs...)
|
||||
}
|
||||
|
||||
// GreaterOrEqual asserts that the first element is greater than or equal to the second
|
||||
|
|
@ -320,7 +369,10 @@ func Greater(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface
|
|||
// assert.GreaterOrEqual(t, "b", "a")
|
||||
// assert.GreaterOrEqual(t, "b", "b")
|
||||
func GreaterOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
|
||||
return compareTwoValues(t, e1, e2, []CompareType{compareGreater, compareEqual}, "\"%v\" is not greater than or equal to \"%v\"", msgAndArgs)
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return compareTwoValues(t, e1, e2, []CompareType{compareGreater, compareEqual}, "\"%v\" is not greater than or equal to \"%v\"", msgAndArgs...)
|
||||
}
|
||||
|
||||
// Less asserts that the first element is less than the second
|
||||
|
|
@ -329,7 +381,10 @@ func GreaterOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...in
|
|||
// assert.Less(t, float64(1), float64(2))
|
||||
// assert.Less(t, "a", "b")
|
||||
func Less(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
|
||||
return compareTwoValues(t, e1, e2, []CompareType{compareLess}, "\"%v\" is not less than \"%v\"", msgAndArgs)
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return compareTwoValues(t, e1, e2, []CompareType{compareLess}, "\"%v\" is not less than \"%v\"", msgAndArgs...)
|
||||
}
|
||||
|
||||
// LessOrEqual asserts that the first element is less than or equal to the second
|
||||
|
|
@ -339,7 +394,10 @@ func Less(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{})
|
|||
// assert.LessOrEqual(t, "a", "b")
|
||||
// assert.LessOrEqual(t, "b", "b")
|
||||
func LessOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
|
||||
return compareTwoValues(t, e1, e2, []CompareType{compareLess, compareEqual}, "\"%v\" is not less than or equal to \"%v\"", msgAndArgs)
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return compareTwoValues(t, e1, e2, []CompareType{compareLess, compareEqual}, "\"%v\" is not less than or equal to \"%v\"", msgAndArgs...)
|
||||
}
|
||||
|
||||
// Positive asserts that the specified element is positive
|
||||
|
|
@ -347,8 +405,11 @@ func LessOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...inter
|
|||
// assert.Positive(t, 1)
|
||||
// assert.Positive(t, 1.23)
|
||||
func Positive(t TestingT, e interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
zero := reflect.Zero(reflect.TypeOf(e))
|
||||
return compareTwoValues(t, e, zero.Interface(), []CompareType{compareGreater}, "\"%v\" is not positive", msgAndArgs)
|
||||
return compareTwoValues(t, e, zero.Interface(), []CompareType{compareGreater}, "\"%v\" is not positive", msgAndArgs...)
|
||||
}
|
||||
|
||||
// Negative asserts that the specified element is negative
|
||||
|
|
@ -356,8 +417,11 @@ func Positive(t TestingT, e interface{}, msgAndArgs ...interface{}) bool {
|
|||
// assert.Negative(t, -1)
|
||||
// assert.Negative(t, -1.23)
|
||||
func Negative(t TestingT, e interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
zero := reflect.Zero(reflect.TypeOf(e))
|
||||
return compareTwoValues(t, e, zero.Interface(), []CompareType{compareLess}, "\"%v\" is not negative", msgAndArgs)
|
||||
return compareTwoValues(t, e, zero.Interface(), []CompareType{compareLess}, "\"%v\" is not negative", msgAndArgs...)
|
||||
}
|
||||
|
||||
func compareTwoValues(t TestingT, e1 interface{}, e2 interface{}, allowedComparesResults []CompareType, failMessage string, msgAndArgs ...interface{}) bool {
|
||||
|
|
|
|||
16
vendor/github.com/stretchr/testify/assert/assertion_compare_can_convert.go
generated
vendored
Normal file
16
vendor/github.com/stretchr/testify/assert/assertion_compare_can_convert.go
generated
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
//go:build go1.17
|
||||
// +build go1.17
|
||||
|
||||
// TODO: once support for Go 1.16 is dropped, this file can be
|
||||
// merged/removed with assertion_compare_go1.17_test.go and
|
||||
// assertion_compare_legacy.go
|
||||
|
||||
package assert
|
||||
|
||||
import "reflect"
|
||||
|
||||
// Wrapper around reflect.Value.CanConvert, for compatibility
|
||||
// reasons.
|
||||
func canConvert(value reflect.Value, to reflect.Type) bool {
|
||||
return value.CanConvert(to)
|
||||
}
|
||||
16
vendor/github.com/stretchr/testify/assert/assertion_compare_legacy.go
generated
vendored
Normal file
16
vendor/github.com/stretchr/testify/assert/assertion_compare_legacy.go
generated
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
//go:build !go1.17
|
||||
// +build !go1.17
|
||||
|
||||
// TODO: once support for Go 1.16 is dropped, this file can be
|
||||
// merged/removed with assertion_compare_go1.17_test.go and
|
||||
// assertion_compare_can_convert.go
|
||||
|
||||
package assert
|
||||
|
||||
import "reflect"
|
||||
|
||||
// Older versions of Go does not have the reflect.Value.CanConvert
|
||||
// method.
|
||||
func canConvert(value reflect.Value, to reflect.Type) bool {
|
||||
return false
|
||||
}
|
||||
22
vendor/github.com/stretchr/testify/assert/assertion_format.go
generated
vendored
22
vendor/github.com/stretchr/testify/assert/assertion_format.go
generated
vendored
|
|
@ -123,6 +123,18 @@ func ErrorAsf(t TestingT, err error, target interface{}, msg string, args ...int
|
|||
return ErrorAs(t, err, target, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// ErrorContainsf asserts that a function returned an error (i.e. not `nil`)
|
||||
// and that the error contains the specified substring.
|
||||
//
|
||||
// actualObj, err := SomeFunction()
|
||||
// assert.ErrorContainsf(t, err, expectedErrorSubString, "error message %s", "formatted")
|
||||
func ErrorContainsf(t TestingT, theError error, contains string, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return ErrorContains(t, theError, contains, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// ErrorIsf asserts that at least one of the errors in err's chain matches target.
|
||||
// This is a wrapper for errors.Is.
|
||||
func ErrorIsf(t TestingT, err error, target error, msg string, args ...interface{}) bool {
|
||||
|
|
@ -724,6 +736,16 @@ func WithinDurationf(t TestingT, expected time.Time, actual time.Time, delta tim
|
|||
return WithinDuration(t, expected, actual, delta, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// WithinRangef asserts that a time is within a time range (inclusive).
|
||||
//
|
||||
// assert.WithinRangef(t, time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second), "error message %s", "formatted")
|
||||
func WithinRangef(t TestingT, actual time.Time, start time.Time, end time.Time, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return WithinRange(t, actual, start, end, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// YAMLEqf asserts that two YAML strings are equivalent.
|
||||
func YAMLEqf(t TestingT, expected string, actual string, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
|
|
|
|||
44
vendor/github.com/stretchr/testify/assert/assertion_forward.go
generated
vendored
44
vendor/github.com/stretchr/testify/assert/assertion_forward.go
generated
vendored
|
|
@ -222,6 +222,30 @@ func (a *Assertions) ErrorAsf(err error, target interface{}, msg string, args ..
|
|||
return ErrorAsf(a.t, err, target, msg, args...)
|
||||
}
|
||||
|
||||
// ErrorContains asserts that a function returned an error (i.e. not `nil`)
|
||||
// and that the error contains the specified substring.
|
||||
//
|
||||
// actualObj, err := SomeFunction()
|
||||
// a.ErrorContains(err, expectedErrorSubString)
|
||||
func (a *Assertions) ErrorContains(theError error, contains string, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return ErrorContains(a.t, theError, contains, msgAndArgs...)
|
||||
}
|
||||
|
||||
// ErrorContainsf asserts that a function returned an error (i.e. not `nil`)
|
||||
// and that the error contains the specified substring.
|
||||
//
|
||||
// actualObj, err := SomeFunction()
|
||||
// a.ErrorContainsf(err, expectedErrorSubString, "error message %s", "formatted")
|
||||
func (a *Assertions) ErrorContainsf(theError error, contains string, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return ErrorContainsf(a.t, theError, contains, msg, args...)
|
||||
}
|
||||
|
||||
// ErrorIs asserts that at least one of the errors in err's chain matches target.
|
||||
// This is a wrapper for errors.Is.
|
||||
func (a *Assertions) ErrorIs(err error, target error, msgAndArgs ...interface{}) bool {
|
||||
|
|
@ -1437,6 +1461,26 @@ func (a *Assertions) WithinDurationf(expected time.Time, actual time.Time, delta
|
|||
return WithinDurationf(a.t, expected, actual, delta, msg, args...)
|
||||
}
|
||||
|
||||
// WithinRange asserts that a time is within a time range (inclusive).
|
||||
//
|
||||
// a.WithinRange(time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second))
|
||||
func (a *Assertions) WithinRange(actual time.Time, start time.Time, end time.Time, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return WithinRange(a.t, actual, start, end, msgAndArgs...)
|
||||
}
|
||||
|
||||
// WithinRangef asserts that a time is within a time range (inclusive).
|
||||
//
|
||||
// a.WithinRangef(time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second), "error message %s", "formatted")
|
||||
func (a *Assertions) WithinRangef(actual time.Time, start time.Time, end time.Time, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return WithinRangef(a.t, actual, start, end, msg, args...)
|
||||
}
|
||||
|
||||
// YAMLEq asserts that two YAML strings are equivalent.
|
||||
func (a *Assertions) YAMLEq(expected string, actual string, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
|
|
|
|||
8
vendor/github.com/stretchr/testify/assert/assertion_order.go
generated
vendored
8
vendor/github.com/stretchr/testify/assert/assertion_order.go
generated
vendored
|
|
@ -50,7 +50,7 @@ func isOrdered(t TestingT, object interface{}, allowedComparesResults []CompareT
|
|||
// assert.IsIncreasing(t, []float{1, 2})
|
||||
// assert.IsIncreasing(t, []string{"a", "b"})
|
||||
func IsIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
|
||||
return isOrdered(t, object, []CompareType{compareLess}, "\"%v\" is not less than \"%v\"", msgAndArgs)
|
||||
return isOrdered(t, object, []CompareType{compareLess}, "\"%v\" is not less than \"%v\"", msgAndArgs...)
|
||||
}
|
||||
|
||||
// IsNonIncreasing asserts that the collection is not increasing
|
||||
|
|
@ -59,7 +59,7 @@ func IsIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) boo
|
|||
// assert.IsNonIncreasing(t, []float{2, 1})
|
||||
// assert.IsNonIncreasing(t, []string{"b", "a"})
|
||||
func IsNonIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
|
||||
return isOrdered(t, object, []CompareType{compareEqual, compareGreater}, "\"%v\" is not greater than or equal to \"%v\"", msgAndArgs)
|
||||
return isOrdered(t, object, []CompareType{compareEqual, compareGreater}, "\"%v\" is not greater than or equal to \"%v\"", msgAndArgs...)
|
||||
}
|
||||
|
||||
// IsDecreasing asserts that the collection is decreasing
|
||||
|
|
@ -68,7 +68,7 @@ func IsNonIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{})
|
|||
// assert.IsDecreasing(t, []float{2, 1})
|
||||
// assert.IsDecreasing(t, []string{"b", "a"})
|
||||
func IsDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
|
||||
return isOrdered(t, object, []CompareType{compareGreater}, "\"%v\" is not greater than \"%v\"", msgAndArgs)
|
||||
return isOrdered(t, object, []CompareType{compareGreater}, "\"%v\" is not greater than \"%v\"", msgAndArgs...)
|
||||
}
|
||||
|
||||
// IsNonDecreasing asserts that the collection is not decreasing
|
||||
|
|
@ -77,5 +77,5 @@ func IsDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) boo
|
|||
// assert.IsNonDecreasing(t, []float{1, 2})
|
||||
// assert.IsNonDecreasing(t, []string{"a", "b"})
|
||||
func IsNonDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
|
||||
return isOrdered(t, object, []CompareType{compareLess, compareEqual}, "\"%v\" is not less than or equal to \"%v\"", msgAndArgs)
|
||||
return isOrdered(t, object, []CompareType{compareLess, compareEqual}, "\"%v\" is not less than or equal to \"%v\"", msgAndArgs...)
|
||||
}
|
||||
|
|
|
|||
190
vendor/github.com/stretchr/testify/assert/assertions.go
generated
vendored
190
vendor/github.com/stretchr/testify/assert/assertions.go
generated
vendored
|
|
@ -8,6 +8,7 @@ import (
|
|||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"runtime"
|
||||
|
|
@ -144,7 +145,8 @@ func CallerInfo() []string {
|
|||
if len(parts) > 1 {
|
||||
dir := parts[len(parts)-2]
|
||||
if (dir != "assert" && dir != "mock" && dir != "require") || file == "mock_test.go" {
|
||||
callers = append(callers, fmt.Sprintf("%s:%d", file, line))
|
||||
path, _ := filepath.Abs(file)
|
||||
callers = append(callers, fmt.Sprintf("%s:%d", path, line))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -563,16 +565,17 @@ func isEmpty(object interface{}) bool {
|
|||
|
||||
switch objValue.Kind() {
|
||||
// collection types are empty when they have no element
|
||||
case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice:
|
||||
case reflect.Chan, reflect.Map, reflect.Slice:
|
||||
return objValue.Len() == 0
|
||||
// pointers are empty if nil or if the value they point to is empty
|
||||
// pointers are empty if nil or if the value they point to is empty
|
||||
case reflect.Ptr:
|
||||
if objValue.IsNil() {
|
||||
return true
|
||||
}
|
||||
deref := objValue.Elem().Interface()
|
||||
return isEmpty(deref)
|
||||
// for all other types, compare against the zero value
|
||||
// for all other types, compare against the zero value
|
||||
// array types are empty when they match their zero-initialized state
|
||||
default:
|
||||
zero := reflect.Zero(objValue.Type())
|
||||
return reflect.DeepEqual(object, zero.Interface())
|
||||
|
|
@ -718,10 +721,14 @@ func NotEqualValues(t TestingT, expected, actual interface{}, msgAndArgs ...inte
|
|||
// return (false, false) if impossible.
|
||||
// return (true, false) if element was not found.
|
||||
// return (true, true) if element was found.
|
||||
func includeElement(list interface{}, element interface{}) (ok, found bool) {
|
||||
func containsElement(list interface{}, element interface{}) (ok, found bool) {
|
||||
|
||||
listValue := reflect.ValueOf(list)
|
||||
listKind := reflect.TypeOf(list).Kind()
|
||||
listType := reflect.TypeOf(list)
|
||||
if listType == nil {
|
||||
return false, false
|
||||
}
|
||||
listKind := listType.Kind()
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
ok = false
|
||||
|
|
@ -764,7 +771,7 @@ func Contains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) bo
|
|||
h.Helper()
|
||||
}
|
||||
|
||||
ok, found := includeElement(s, contains)
|
||||
ok, found := containsElement(s, contains)
|
||||
if !ok {
|
||||
return Fail(t, fmt.Sprintf("%#v could not be applied builtin len()", s), msgAndArgs...)
|
||||
}
|
||||
|
|
@ -787,7 +794,7 @@ func NotContains(t TestingT, s, contains interface{}, msgAndArgs ...interface{})
|
|||
h.Helper()
|
||||
}
|
||||
|
||||
ok, found := includeElement(s, contains)
|
||||
ok, found := containsElement(s, contains)
|
||||
if !ok {
|
||||
return Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", s), msgAndArgs...)
|
||||
}
|
||||
|
|
@ -811,7 +818,6 @@ func Subset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok
|
|||
return true // we consider nil to be equal to the nil set
|
||||
}
|
||||
|
||||
subsetValue := reflect.ValueOf(subset)
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
ok = false
|
||||
|
|
@ -821,17 +827,35 @@ func Subset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok
|
|||
listKind := reflect.TypeOf(list).Kind()
|
||||
subsetKind := reflect.TypeOf(subset).Kind()
|
||||
|
||||
if listKind != reflect.Array && listKind != reflect.Slice {
|
||||
if listKind != reflect.Array && listKind != reflect.Slice && listKind != reflect.Map {
|
||||
return Fail(t, fmt.Sprintf("%q has an unsupported type %s", list, listKind), msgAndArgs...)
|
||||
}
|
||||
|
||||
if subsetKind != reflect.Array && subsetKind != reflect.Slice {
|
||||
if subsetKind != reflect.Array && subsetKind != reflect.Slice && listKind != reflect.Map {
|
||||
return Fail(t, fmt.Sprintf("%q has an unsupported type %s", subset, subsetKind), msgAndArgs...)
|
||||
}
|
||||
|
||||
subsetValue := reflect.ValueOf(subset)
|
||||
if subsetKind == reflect.Map && listKind == reflect.Map {
|
||||
listValue := reflect.ValueOf(list)
|
||||
subsetKeys := subsetValue.MapKeys()
|
||||
|
||||
for i := 0; i < len(subsetKeys); i++ {
|
||||
subsetKey := subsetKeys[i]
|
||||
subsetElement := subsetValue.MapIndex(subsetKey).Interface()
|
||||
listElement := listValue.MapIndex(subsetKey).Interface()
|
||||
|
||||
if !ObjectsAreEqual(subsetElement, listElement) {
|
||||
return Fail(t, fmt.Sprintf("\"%s\" does not contain \"%s\"", list, subsetElement), msgAndArgs...)
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
for i := 0; i < subsetValue.Len(); i++ {
|
||||
element := subsetValue.Index(i).Interface()
|
||||
ok, found := includeElement(list, element)
|
||||
ok, found := containsElement(list, element)
|
||||
if !ok {
|
||||
return Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", list), msgAndArgs...)
|
||||
}
|
||||
|
|
@ -852,10 +876,9 @@ func NotSubset(t TestingT, list, subset interface{}, msgAndArgs ...interface{})
|
|||
h.Helper()
|
||||
}
|
||||
if subset == nil {
|
||||
return Fail(t, fmt.Sprintf("nil is the empty set which is a subset of every set"), msgAndArgs...)
|
||||
return Fail(t, "nil is the empty set which is a subset of every set", msgAndArgs...)
|
||||
}
|
||||
|
||||
subsetValue := reflect.ValueOf(subset)
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
ok = false
|
||||
|
|
@ -865,17 +888,35 @@ func NotSubset(t TestingT, list, subset interface{}, msgAndArgs ...interface{})
|
|||
listKind := reflect.TypeOf(list).Kind()
|
||||
subsetKind := reflect.TypeOf(subset).Kind()
|
||||
|
||||
if listKind != reflect.Array && listKind != reflect.Slice {
|
||||
if listKind != reflect.Array && listKind != reflect.Slice && listKind != reflect.Map {
|
||||
return Fail(t, fmt.Sprintf("%q has an unsupported type %s", list, listKind), msgAndArgs...)
|
||||
}
|
||||
|
||||
if subsetKind != reflect.Array && subsetKind != reflect.Slice {
|
||||
if subsetKind != reflect.Array && subsetKind != reflect.Slice && listKind != reflect.Map {
|
||||
return Fail(t, fmt.Sprintf("%q has an unsupported type %s", subset, subsetKind), msgAndArgs...)
|
||||
}
|
||||
|
||||
subsetValue := reflect.ValueOf(subset)
|
||||
if subsetKind == reflect.Map && listKind == reflect.Map {
|
||||
listValue := reflect.ValueOf(list)
|
||||
subsetKeys := subsetValue.MapKeys()
|
||||
|
||||
for i := 0; i < len(subsetKeys); i++ {
|
||||
subsetKey := subsetKeys[i]
|
||||
subsetElement := subsetValue.MapIndex(subsetKey).Interface()
|
||||
listElement := listValue.MapIndex(subsetKey).Interface()
|
||||
|
||||
if !ObjectsAreEqual(subsetElement, listElement) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return Fail(t, fmt.Sprintf("%q is a subset of %q", subset, list), msgAndArgs...)
|
||||
}
|
||||
|
||||
for i := 0; i < subsetValue.Len(); i++ {
|
||||
element := subsetValue.Index(i).Interface()
|
||||
ok, found := includeElement(list, element)
|
||||
ok, found := containsElement(list, element)
|
||||
if !ok {
|
||||
return Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", list), msgAndArgs...)
|
||||
}
|
||||
|
|
@ -1000,27 +1041,21 @@ func Condition(t TestingT, comp Comparison, msgAndArgs ...interface{}) bool {
|
|||
type PanicTestFunc func()
|
||||
|
||||
// didPanic returns true if the function passed to it panics. Otherwise, it returns false.
|
||||
func didPanic(f PanicTestFunc) (bool, interface{}, string) {
|
||||
|
||||
didPanic := false
|
||||
var message interface{}
|
||||
var stack string
|
||||
func() {
|
||||
|
||||
defer func() {
|
||||
if message = recover(); message != nil {
|
||||
didPanic = true
|
||||
stack = string(debug.Stack())
|
||||
}
|
||||
}()
|
||||
|
||||
// call the target function
|
||||
f()
|
||||
func didPanic(f PanicTestFunc) (didPanic bool, message interface{}, stack string) {
|
||||
didPanic = true
|
||||
|
||||
defer func() {
|
||||
message = recover()
|
||||
if didPanic {
|
||||
stack = string(debug.Stack())
|
||||
}
|
||||
}()
|
||||
|
||||
return didPanic, message, stack
|
||||
// call the target function
|
||||
f()
|
||||
didPanic = false
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Panics asserts that the code inside the specified PanicTestFunc panics.
|
||||
|
|
@ -1111,6 +1146,27 @@ func WithinDuration(t TestingT, expected, actual time.Time, delta time.Duration,
|
|||
return true
|
||||
}
|
||||
|
||||
// WithinRange asserts that a time is within a time range (inclusive).
|
||||
//
|
||||
// assert.WithinRange(t, time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second))
|
||||
func WithinRange(t TestingT, actual, start, end time.Time, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
|
||||
if end.Before(start) {
|
||||
return Fail(t, "Start should be before end", msgAndArgs...)
|
||||
}
|
||||
|
||||
if actual.Before(start) {
|
||||
return Fail(t, fmt.Sprintf("Time %v expected to be in time range %v to %v, but is before the range", actual, start, end), msgAndArgs...)
|
||||
} else if actual.After(end) {
|
||||
return Fail(t, fmt.Sprintf("Time %v expected to be in time range %v to %v, but is after the range", actual, start, end), msgAndArgs...)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func toFloat(x interface{}) (float64, bool) {
|
||||
var xf float64
|
||||
xok := true
|
||||
|
|
@ -1161,11 +1217,15 @@ func InDelta(t TestingT, expected, actual interface{}, delta float64, msgAndArgs
|
|||
bf, bok := toFloat(actual)
|
||||
|
||||
if !aok || !bok {
|
||||
return Fail(t, fmt.Sprintf("Parameters must be numerical"), msgAndArgs...)
|
||||
return Fail(t, "Parameters must be numerical", msgAndArgs...)
|
||||
}
|
||||
|
||||
if math.IsNaN(af) && math.IsNaN(bf) {
|
||||
return true
|
||||
}
|
||||
|
||||
if math.IsNaN(af) {
|
||||
return Fail(t, fmt.Sprintf("Expected must not be NaN"), msgAndArgs...)
|
||||
return Fail(t, "Expected must not be NaN", msgAndArgs...)
|
||||
}
|
||||
|
||||
if math.IsNaN(bf) {
|
||||
|
|
@ -1188,7 +1248,7 @@ func InDeltaSlice(t TestingT, expected, actual interface{}, delta float64, msgAn
|
|||
if expected == nil || actual == nil ||
|
||||
reflect.TypeOf(actual).Kind() != reflect.Slice ||
|
||||
reflect.TypeOf(expected).Kind() != reflect.Slice {
|
||||
return Fail(t, fmt.Sprintf("Parameters must be slice"), msgAndArgs...)
|
||||
return Fail(t, "Parameters must be slice", msgAndArgs...)
|
||||
}
|
||||
|
||||
actualSlice := reflect.ValueOf(actual)
|
||||
|
|
@ -1250,8 +1310,12 @@ func InDeltaMapValues(t TestingT, expected, actual interface{}, delta float64, m
|
|||
|
||||
func calcRelativeError(expected, actual interface{}) (float64, error) {
|
||||
af, aok := toFloat(expected)
|
||||
if !aok {
|
||||
return 0, fmt.Errorf("expected value %q cannot be converted to float", expected)
|
||||
bf, bok := toFloat(actual)
|
||||
if !aok || !bok {
|
||||
return 0, fmt.Errorf("Parameters must be numerical")
|
||||
}
|
||||
if math.IsNaN(af) && math.IsNaN(bf) {
|
||||
return 0, nil
|
||||
}
|
||||
if math.IsNaN(af) {
|
||||
return 0, errors.New("expected value must not be NaN")
|
||||
|
|
@ -1259,10 +1323,6 @@ func calcRelativeError(expected, actual interface{}) (float64, error) {
|
|||
if af == 0 {
|
||||
return 0, fmt.Errorf("expected value must have a value other than zero to calculate the relative error")
|
||||
}
|
||||
bf, bok := toFloat(actual)
|
||||
if !bok {
|
||||
return 0, fmt.Errorf("actual value %q cannot be converted to float", actual)
|
||||
}
|
||||
if math.IsNaN(bf) {
|
||||
return 0, errors.New("actual value must not be NaN")
|
||||
}
|
||||
|
|
@ -1298,7 +1358,7 @@ func InEpsilonSlice(t TestingT, expected, actual interface{}, epsilon float64, m
|
|||
if expected == nil || actual == nil ||
|
||||
reflect.TypeOf(actual).Kind() != reflect.Slice ||
|
||||
reflect.TypeOf(expected).Kind() != reflect.Slice {
|
||||
return Fail(t, fmt.Sprintf("Parameters must be slice"), msgAndArgs...)
|
||||
return Fail(t, "Parameters must be slice", msgAndArgs...)
|
||||
}
|
||||
|
||||
actualSlice := reflect.ValueOf(actual)
|
||||
|
|
@ -1375,6 +1435,27 @@ func EqualError(t TestingT, theError error, errString string, msgAndArgs ...inte
|
|||
return true
|
||||
}
|
||||
|
||||
// ErrorContains asserts that a function returned an error (i.e. not `nil`)
|
||||
// and that the error contains the specified substring.
|
||||
//
|
||||
// actualObj, err := SomeFunction()
|
||||
// assert.ErrorContains(t, err, expectedErrorSubString)
|
||||
func ErrorContains(t TestingT, theError error, contains string, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
if !Error(t, theError, msgAndArgs...) {
|
||||
return false
|
||||
}
|
||||
|
||||
actual := theError.Error()
|
||||
if !strings.Contains(actual, contains) {
|
||||
return Fail(t, fmt.Sprintf("Error %#v does not contain %#v", actual, contains), msgAndArgs...)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// matchRegexp return true if a specified regexp matches a string.
|
||||
func matchRegexp(rx interface{}, str interface{}) bool {
|
||||
|
||||
|
|
@ -1588,12 +1669,17 @@ func diff(expected interface{}, actual interface{}) string {
|
|||
}
|
||||
|
||||
var e, a string
|
||||
if et != reflect.TypeOf("") {
|
||||
e = spewConfig.Sdump(expected)
|
||||
a = spewConfig.Sdump(actual)
|
||||
} else {
|
||||
|
||||
switch et {
|
||||
case reflect.TypeOf(""):
|
||||
e = reflect.ValueOf(expected).String()
|
||||
a = reflect.ValueOf(actual).String()
|
||||
case reflect.TypeOf(time.Time{}):
|
||||
e = spewConfigStringerEnabled.Sdump(expected)
|
||||
a = spewConfigStringerEnabled.Sdump(actual)
|
||||
default:
|
||||
e = spewConfig.Sdump(expected)
|
||||
a = spewConfig.Sdump(actual)
|
||||
}
|
||||
|
||||
diff, _ := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{
|
||||
|
|
@ -1625,6 +1711,14 @@ var spewConfig = spew.ConfigState{
|
|||
MaxDepth: 10,
|
||||
}
|
||||
|
||||
var spewConfigStringerEnabled = spew.ConfigState{
|
||||
Indent: " ",
|
||||
DisablePointerAddresses: true,
|
||||
DisableCapacities: true,
|
||||
SortKeys: true,
|
||||
MaxDepth: 10,
|
||||
}
|
||||
|
||||
type tHelper interface {
|
||||
Helper()
|
||||
}
|
||||
|
|
|
|||
78
vendor/gopkg.in/yaml.v3/decode.go
generated
vendored
78
vendor/gopkg.in/yaml.v3/decode.go
generated
vendored
|
|
@ -100,7 +100,10 @@ func (p *parser) peek() yaml_event_type_t {
|
|||
if p.event.typ != yaml_NO_EVENT {
|
||||
return p.event.typ
|
||||
}
|
||||
if !yaml_parser_parse(&p.parser, &p.event) {
|
||||
// It's curious choice from the underlying API to generally return a
|
||||
// positive result on success, but on this case return true in an error
|
||||
// scenario. This was the source of bugs in the past (issue #666).
|
||||
if !yaml_parser_parse(&p.parser, &p.event) || p.parser.error != yaml_NO_ERROR {
|
||||
p.fail()
|
||||
}
|
||||
return p.event.typ
|
||||
|
|
@ -320,6 +323,8 @@ type decoder struct {
|
|||
decodeCount int
|
||||
aliasCount int
|
||||
aliasDepth int
|
||||
|
||||
mergedFields map[interface{}]bool
|
||||
}
|
||||
|
||||
var (
|
||||
|
|
@ -808,6 +813,11 @@ func (d *decoder) mapping(n *Node, out reflect.Value) (good bool) {
|
|||
}
|
||||
}
|
||||
|
||||
mergedFields := d.mergedFields
|
||||
d.mergedFields = nil
|
||||
|
||||
var mergeNode *Node
|
||||
|
||||
mapIsNew := false
|
||||
if out.IsNil() {
|
||||
out.Set(reflect.MakeMap(outt))
|
||||
|
|
@ -815,11 +825,18 @@ func (d *decoder) mapping(n *Node, out reflect.Value) (good bool) {
|
|||
}
|
||||
for i := 0; i < l; i += 2 {
|
||||
if isMerge(n.Content[i]) {
|
||||
d.merge(n.Content[i+1], out)
|
||||
mergeNode = n.Content[i+1]
|
||||
continue
|
||||
}
|
||||
k := reflect.New(kt).Elem()
|
||||
if d.unmarshal(n.Content[i], k) {
|
||||
if mergedFields != nil {
|
||||
ki := k.Interface()
|
||||
if mergedFields[ki] {
|
||||
continue
|
||||
}
|
||||
mergedFields[ki] = true
|
||||
}
|
||||
kkind := k.Kind()
|
||||
if kkind == reflect.Interface {
|
||||
kkind = k.Elem().Kind()
|
||||
|
|
@ -833,6 +850,12 @@ func (d *decoder) mapping(n *Node, out reflect.Value) (good bool) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
d.mergedFields = mergedFields
|
||||
if mergeNode != nil {
|
||||
d.merge(n, mergeNode, out)
|
||||
}
|
||||
|
||||
d.stringMapType = stringMapType
|
||||
d.generalMapType = generalMapType
|
||||
return true
|
||||
|
|
@ -844,7 +867,8 @@ func isStringMap(n *Node) bool {
|
|||
}
|
||||
l := len(n.Content)
|
||||
for i := 0; i < l; i += 2 {
|
||||
if n.Content[i].ShortTag() != strTag {
|
||||
shortTag := n.Content[i].ShortTag()
|
||||
if shortTag != strTag && shortTag != mergeTag {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
@ -861,7 +885,6 @@ func (d *decoder) mappingStruct(n *Node, out reflect.Value) (good bool) {
|
|||
var elemType reflect.Type
|
||||
if sinfo.InlineMap != -1 {
|
||||
inlineMap = out.Field(sinfo.InlineMap)
|
||||
inlineMap.Set(reflect.New(inlineMap.Type()).Elem())
|
||||
elemType = inlineMap.Type().Elem()
|
||||
}
|
||||
|
||||
|
|
@ -870,6 +893,9 @@ func (d *decoder) mappingStruct(n *Node, out reflect.Value) (good bool) {
|
|||
d.prepare(n, field)
|
||||
}
|
||||
|
||||
mergedFields := d.mergedFields
|
||||
d.mergedFields = nil
|
||||
var mergeNode *Node
|
||||
var doneFields []bool
|
||||
if d.uniqueKeys {
|
||||
doneFields = make([]bool, len(sinfo.FieldsList))
|
||||
|
|
@ -879,13 +905,20 @@ func (d *decoder) mappingStruct(n *Node, out reflect.Value) (good bool) {
|
|||
for i := 0; i < l; i += 2 {
|
||||
ni := n.Content[i]
|
||||
if isMerge(ni) {
|
||||
d.merge(n.Content[i+1], out)
|
||||
mergeNode = n.Content[i+1]
|
||||
continue
|
||||
}
|
||||
if !d.unmarshal(ni, name) {
|
||||
continue
|
||||
}
|
||||
if info, ok := sinfo.FieldsMap[name.String()]; ok {
|
||||
sname := name.String()
|
||||
if mergedFields != nil {
|
||||
if mergedFields[sname] {
|
||||
continue
|
||||
}
|
||||
mergedFields[sname] = true
|
||||
}
|
||||
if info, ok := sinfo.FieldsMap[sname]; ok {
|
||||
if d.uniqueKeys {
|
||||
if doneFields[info.Id] {
|
||||
d.terrors = append(d.terrors, fmt.Sprintf("line %d: field %s already set in type %s", ni.Line, name.String(), out.Type()))
|
||||
|
|
@ -911,6 +944,11 @@ func (d *decoder) mappingStruct(n *Node, out reflect.Value) (good bool) {
|
|||
d.terrors = append(d.terrors, fmt.Sprintf("line %d: field %s not found in type %s", ni.Line, name.String(), out.Type()))
|
||||
}
|
||||
}
|
||||
|
||||
d.mergedFields = mergedFields
|
||||
if mergeNode != nil {
|
||||
d.merge(n, mergeNode, out)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
@ -918,19 +956,29 @@ func failWantMap() {
|
|||
failf("map merge requires map or sequence of maps as the value")
|
||||
}
|
||||
|
||||
func (d *decoder) merge(n *Node, out reflect.Value) {
|
||||
switch n.Kind {
|
||||
func (d *decoder) merge(parent *Node, merge *Node, out reflect.Value) {
|
||||
mergedFields := d.mergedFields
|
||||
if mergedFields == nil {
|
||||
d.mergedFields = make(map[interface{}]bool)
|
||||
for i := 0; i < len(parent.Content); i += 2 {
|
||||
k := reflect.New(ifaceType).Elem()
|
||||
if d.unmarshal(parent.Content[i], k) {
|
||||
d.mergedFields[k.Interface()] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch merge.Kind {
|
||||
case MappingNode:
|
||||
d.unmarshal(n, out)
|
||||
d.unmarshal(merge, out)
|
||||
case AliasNode:
|
||||
if n.Alias != nil && n.Alias.Kind != MappingNode {
|
||||
if merge.Alias != nil && merge.Alias.Kind != MappingNode {
|
||||
failWantMap()
|
||||
}
|
||||
d.unmarshal(n, out)
|
||||
d.unmarshal(merge, out)
|
||||
case SequenceNode:
|
||||
// Step backwards as earlier nodes take precedence.
|
||||
for i := len(n.Content) - 1; i >= 0; i-- {
|
||||
ni := n.Content[i]
|
||||
for i := 0; i < len(merge.Content); i++ {
|
||||
ni := merge.Content[i]
|
||||
if ni.Kind == AliasNode {
|
||||
if ni.Alias != nil && ni.Alias.Kind != MappingNode {
|
||||
failWantMap()
|
||||
|
|
@ -943,6 +991,8 @@ func (d *decoder) merge(n *Node, out reflect.Value) {
|
|||
default:
|
||||
failWantMap()
|
||||
}
|
||||
|
||||
d.mergedFields = mergedFields
|
||||
}
|
||||
|
||||
func isMerge(n *Node) bool {
|
||||
|
|
|
|||
11
vendor/gopkg.in/yaml.v3/parserc.go
generated
vendored
11
vendor/gopkg.in/yaml.v3/parserc.go
generated
vendored
|
|
@ -687,6 +687,9 @@ func yaml_parser_parse_node(parser *yaml_parser_t, event *yaml_event_t, block, i
|
|||
func yaml_parser_parse_block_sequence_entry(parser *yaml_parser_t, event *yaml_event_t, first bool) bool {
|
||||
if first {
|
||||
token := peek_token(parser)
|
||||
if token == nil {
|
||||
return false
|
||||
}
|
||||
parser.marks = append(parser.marks, token.start_mark)
|
||||
skip_token(parser)
|
||||
}
|
||||
|
|
@ -786,7 +789,7 @@ func yaml_parser_split_stem_comment(parser *yaml_parser_t, stem_len int) {
|
|||
}
|
||||
|
||||
token := peek_token(parser)
|
||||
if token.typ != yaml_BLOCK_SEQUENCE_START_TOKEN && token.typ != yaml_BLOCK_MAPPING_START_TOKEN {
|
||||
if token == nil || token.typ != yaml_BLOCK_SEQUENCE_START_TOKEN && token.typ != yaml_BLOCK_MAPPING_START_TOKEN {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -813,6 +816,9 @@ func yaml_parser_split_stem_comment(parser *yaml_parser_t, stem_len int) {
|
|||
func yaml_parser_parse_block_mapping_key(parser *yaml_parser_t, event *yaml_event_t, first bool) bool {
|
||||
if first {
|
||||
token := peek_token(parser)
|
||||
if token == nil {
|
||||
return false
|
||||
}
|
||||
parser.marks = append(parser.marks, token.start_mark)
|
||||
skip_token(parser)
|
||||
}
|
||||
|
|
@ -922,6 +928,9 @@ func yaml_parser_parse_block_mapping_value(parser *yaml_parser_t, event *yaml_ev
|
|||
func yaml_parser_parse_flow_sequence_entry(parser *yaml_parser_t, event *yaml_event_t, first bool) bool {
|
||||
if first {
|
||||
token := peek_token(parser)
|
||||
if token == nil {
|
||||
return false
|
||||
}
|
||||
parser.marks = append(parser.marks, token.start_mark)
|
||||
skip_token(parser)
|
||||
}
|
||||
|
|
|
|||
10
vendor/modules.txt
vendored
10
vendor/modules.txt
vendored
|
|
@ -121,6 +121,10 @@ github.com/jesseduffield/gocui
|
|||
# github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10
|
||||
## explicit; go 1.18
|
||||
github.com/jesseduffield/kill
|
||||
# github.com/jesseduffield/lazycore v0.0.0-20221009164442-17c8b878c316
|
||||
## explicit; go 1.18
|
||||
github.com/jesseduffield/lazycore/pkg/boxlayout
|
||||
github.com/jesseduffield/lazycore/pkg/utils
|
||||
# github.com/jesseduffield/yaml v0.0.0-20190702115811-b900b7e08b56
|
||||
## explicit
|
||||
github.com/jesseduffield/yaml
|
||||
|
|
@ -169,7 +173,7 @@ github.com/pmezard/go-difflib/difflib
|
|||
# github.com/rivo/uniseg v0.2.0
|
||||
## explicit; go 1.12
|
||||
github.com/rivo/uniseg
|
||||
# github.com/samber/lo v1.20.0
|
||||
# github.com/samber/lo v1.31.0
|
||||
## explicit; go 1.18
|
||||
github.com/samber/lo
|
||||
# github.com/sirupsen/logrus v1.4.2
|
||||
|
|
@ -178,7 +182,7 @@ github.com/sirupsen/logrus
|
|||
# github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad
|
||||
## explicit
|
||||
github.com/spkg/bom
|
||||
# github.com/stretchr/testify v1.7.0
|
||||
# github.com/stretchr/testify v1.8.0
|
||||
## explicit; go 1.13
|
||||
github.com/stretchr/testify/assert
|
||||
# github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778
|
||||
|
|
@ -213,7 +217,7 @@ golang.org/x/xerrors
|
|||
golang.org/x/xerrors/internal
|
||||
# gopkg.in/yaml.v2 v2.2.2
|
||||
## explicit
|
||||
# gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
||||
# gopkg.in/yaml.v3 v3.0.1
|
||||
## explicit
|
||||
gopkg.in/yaml.v3
|
||||
# gotest.tools/v3 v3.2.0
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue