Compare commits

...

13 Commits

Author SHA1 Message Date
oauth
f48c4c8d61 🎉 Release 5.0.3 2026-02-09 13:34:05 +00:00
oauth
79e77dabe4 Merge branch 'origin/main' into 'next-release/main' 2026-02-09 13:34:04 +00:00
Viktor Scharf
564805bf94 fix: make file urls (#2304) 2026-02-09 14:31:58 +01:00
oauth
25e8d591ce 🎉 Release 5.0.3 2026-02-09 13:25:30 +00:00
oauth
fdaa6c0099 Merge branch 'origin/main' into 'next-release/main' 2026-02-09 13:25:29 +00:00
Ralf Haferkamp
c33850f213 Reapply "adapt test for #514 (#2255)" (#2305)
This reverts commit f21207ed96.
2026-02-09 14:23:24 +01:00
oauth
1fd612b220 🎉 Release 5.0.3 2026-02-09 13:11:38 +00:00
oauth
f14ca42964 Merge branch 'origin/main' into 'next-release/main' 2026-02-09 13:11:37 +00:00
VicDeo
ab9c4d8f23 Sanitize web config only once 2026-02-09 14:09:33 +01:00
oauth
b326dae054 🎉 Release 5.0.3 2026-02-09 08:08:32 +00:00
dependabot[bot]
8c725823f7 build(deps): bump github.com/go-chi/chi/v5 from 5.2.4 to 5.2.5
Bumps [github.com/go-chi/chi/v5](https://github.com/go-chi/chi) from 5.2.4 to 5.2.5.
- [Release notes](https://github.com/go-chi/chi/releases)
- [Changelog](https://github.com/go-chi/chi/blob/master/CHANGELOG.md)
- [Commits](https://github.com/go-chi/chi/compare/v5.2.4...v5.2.5)

---
updated-dependencies:
- dependency-name: github.com/go-chi/chi/v5
  dependency-version: 5.2.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-09 09:05:12 +01:00
OpenCloud Devops
673f606abf 🎉 Release 5.0.2 (#2230)
* 🎉 Release 5.0.2

* 🎉 Release 5.0.2

* 🎉 Release 5.0.2

* 🎉 Release 5.0.2

* 🎉 Release 5.0.2

* 🎉 Release 5.0.2

* 🎉 Release 5.0.2

* 🎉 Release 5.0.2

* 🎉 Release 5.0.2

* 🎉 Release 5.0.2

* 🎉 Release 5.0.2

* 🎉 Release 5.0.2

* 🎉 Release 5.0.2

* 🎉 Release 5.0.2
2026-02-05 17:26:50 +01:00
Viktor Scharf
1e432b717e reva-bump-2.42.3 (#2276) 2026-02-05 16:51:58 +01:00
49 changed files with 1842 additions and 760 deletions

View File

@@ -1,5 +1,48 @@
# Changelog
## [5.0.3](https://github.com/opencloud-eu/opencloud/releases/tag/v5.0.3) - 2026-02-09
### ❤️ Thanks to all contributors! ❤️
@ScharfViktor, @VicDeo, @rhafer
### 📚 Documentation
- fix: make file urls [[#2304](https://github.com/opencloud-eu/opencloud/pull/2304)]
### 🐛 Bug Fixes
- Sanitize web config only once [[#2286](https://github.com/opencloud-eu/opencloud/pull/2286)]
### 📦️ Dependencies
- build(deps): bump github.com/go-chi/chi/v5 from 5.2.4 to 5.2.5 [[#2278](https://github.com/opencloud-eu/opencloud/pull/2278)]
## [5.0.2](https://github.com/opencloud-eu/opencloud/releases/tag/v5.0.2) - 2026-02-05
### ❤️ Thanks to all contributors! ❤️
@AlexAndBear, @ScharfViktor, @flimmy, @individual-it, @rhafer, @saw-jan
### 🐛 Bug Fixes
- [full-ci] reva-bump-2.42.3 [[#2276](https://github.com/opencloud-eu/opencloud/pull/2276)]
### ✅ Tests
- adapt test for #514 [[#2255](https://github.com/opencloud-eu/opencloud/pull/2255)]
- api-test: upload-rename-download file with back slash [[#2239](https://github.com/opencloud-eu/opencloud/pull/2239)]
- [full-ci][tests-only] test: add hook failures to the test failures list [[#2041](https://github.com/opencloud-eu/opencloud/pull/2041)]
### 📚 Documentation
- docs(proxy): Clarify PROXY_OIDC_USERINFO_CACHE_TTL value [[#2256](https://github.com/opencloud-eu/opencloud/pull/2256)]
### 📦️ Dependencies
- [full-ci] reva-bump-2.42.2 [[#2270](https://github.com/opencloud-eu/opencloud/pull/2270)]
- build(deps): bump github.com/grpc-ecosystem/grpc-gateway/v2 from 2.27.5 to 2.27.6 [[#2238](https://github.com/opencloud-eu/opencloud/pull/2238)]
## [5.0.1](https://github.com/opencloud-eu/opencloud/releases/tag/v5.0.1) - 2026-01-28
### ❤️ Thanks to all contributors! ❤️

View File

@@ -83,7 +83,7 @@ help:
@echo "Please use 'make <target>' where <target> is one of the following:"
@echo
@echo -e "${GREEN}Testing with test suite natively installed:${RESET}\n"
@echo -e "${PURPLE}\tdocs: https://opencloud.dev/opencloud/development/testing/#testing-with-test-suite-natively-installed${RESET}\n"
@echo -e "${PURPLE}\ttests: https://github.com/opencloud-eu/opencloud/blob/main/tests/README.md#running-test-suite-in-local-environment${RESET}\n"
@echo -e "\tmake test-acceptance-api\t\t${BLUE}run API acceptance tests${RESET}"
@echo -e "\tmake clean-tests\t\t\t${BLUE}delete API tests framework dependencies${RESET}"
@echo
@@ -92,17 +92,11 @@ help:
@echo -e "${RED}You also should have a look at other available Makefiles:${RESET}"
@echo
@echo -e "${GREEN}opencloud:${RESET}\n"
@echo -e "${PURPLE}\tdocs: https://opencloud.dev/opencloud/development/build/${RESET}\n"
@echo -e "${PURPLE}\tdocs: https://github.com/opencloud-eu/opencloud/blob/main/README.md#build-opencloud${RESET}\n"
@echo -e "\tsee ./opencloud/Makefile"
@echo -e "\tor run ${YELLOW}make -C opencloud help${RESET}"
@echo
@echo -e "${GREEN}Documentation:${RESET}\n"
@echo -e "${PURPLE}\tdocs: https://opencloud.dev/opencloud/development/build-docs/${RESET}\n"
@echo -e "\tsee ./docs/Makefile"
@echo -e "\tor run ${YELLOW}make -C docs help${RESET}"
@echo
@echo -e "${GREEN}Testing with test suite in docker:${RESET}\n"
@echo -e "${PURPLE}\tdocs: https://opencloud.dev/opencloud/development/testing/#testing-with-test-suite-in-docker${RESET}\n"
@echo -e "${PURPLE}\ttests: https://github.com/opencloud-eu/opencloud/blob/main/tests/README.md#running-test-suite-in-docker${RESET}\n"
@echo -e "\tsee ./tests/acceptance/docker/Makefile"
@echo -e "\tor run ${YELLOW}make -C tests/acceptance/docker help${RESET}"
@echo

12
go.mod
View File

@@ -20,7 +20,7 @@ require (
github.com/dutchcoders/go-clamd v0.0.0-20170520113014-b970184f4d9e
github.com/gabriel-vasile/mimetype v1.4.12
github.com/ggwhite/go-masker v1.1.0
github.com/go-chi/chi/v5 v5.2.4
github.com/go-chi/chi/v5 v5.2.5
github.com/go-chi/render v1.0.3
github.com/go-jose/go-jose/v3 v3.0.4
github.com/go-ldap/ldap/v3 v3.4.12
@@ -65,7 +65,7 @@ require (
github.com/open-policy-agent/opa v1.12.3
github.com/opencloud-eu/icap-client v0.0.0-20250930132611-28a2afe62d89
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20250724122329-41ba6b191e76
github.com/opencloud-eu/reva/v2 v2.42.2
github.com/opencloud-eu/reva/v2 v2.42.3
github.com/opensearch-project/opensearch-go/v4 v4.6.0
github.com/orcaman/concurrent-map v1.0.0
github.com/pkg/errors v0.9.1
@@ -339,9 +339,9 @@ require (
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd // indirect
github.com/sagikazarmark/locafero v0.11.0 // indirect
github.com/samber/lo v1.51.0 // indirect
github.com/samber/slog-common v0.19.0 // indirect
github.com/samber/slog-zerolog/v2 v2.9.0 // indirect
github.com/samber/lo v1.52.0 // indirect
github.com/samber/slog-common v0.20.0 // indirect
github.com/samber/slog-zerolog/v2 v2.9.1 // indirect
github.com/segmentio/asm v1.2.1 // indirect
github.com/segmentio/kafka-go v0.4.50 // indirect
github.com/segmentio/ksuid v1.0.4 // indirect
@@ -417,3 +417,5 @@ replace github.com/go-micro/plugins/v4/store/nats-js-kv => github.com/opencloud-
// to get the logger injection (https://github.com/pablodz/inotifywaitgo/pull/11)
replace github.com/pablodz/inotifywaitgo v0.0.9 => github.com/opencloud-eu/inotifywaitgo v0.0.0-20251111171128-a390bae3c5e9
replace github.com/opencloud-eu/reva/v2 => github.com/aduffeck/reva/v2 v2.27.3-0.20260209115512-73c029375aec

20
go.sum
View File

@@ -97,6 +97,8 @@ github.com/RoaringBitmap/roaring/v2 v2.4.5 h1:uGrrMreGjvAtTBobc0g5IrW1D5ldxDQYe2
github.com/RoaringBitmap/roaring/v2 v2.4.5/go.mod h1:FiJcsfkGje/nZBZgCu0ZxCPOKD/hVXDS2dXi7/eUFE0=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/aduffeck/reva/v2 v2.27.3-0.20260209115512-73c029375aec h1:0OGMGUDSQeOSungcukaZOC1MfY7N7zJ8OrfnWGxrnBE=
github.com/aduffeck/reva/v2 v2.27.3-0.20260209115512-73c029375aec/go.mod h1:EIhSI2Tv1FCdtvv5AO4ublX4yrM+1KZCcrkSImDVCDg=
github.com/agnivade/levenshtein v1.2.1 h1:EHBY3UOn1gwdy/VbFwgo4cxecRznFk7fKWN1KOX7eoM=
github.com/agnivade/levenshtein v1.2.1/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU=
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
@@ -381,8 +383,8 @@ github.com/go-asn1-ber/asn1-ber v1.4.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkPro
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo=
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/go-chi/chi/v5 v5.2.4 h1:WtFKPHwlywe8Srng8j2BhOD9312j9cGUxG1SP4V2cR4=
github.com/go-chi/chi/v5 v5.2.4/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0=
github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug=
github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0=
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s=
@@ -969,8 +971,6 @@ github.com/opencloud-eu/inotifywaitgo v0.0.0-20251111171128-a390bae3c5e9 h1:dIft
github.com/opencloud-eu/inotifywaitgo v0.0.0-20251111171128-a390bae3c5e9/go.mod h1:JWyDC6H+5oZRdUJUgKuaye+8Ph5hEs6HVzVoPKzWSGI=
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20250724122329-41ba6b191e76 h1:vD/EdfDUrv4omSFjrinT8Mvf+8D7f9g4vgQ2oiDrVUI=
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20250724122329-41ba6b191e76/go.mod h1:pzatilMEHZFT3qV7C/X3MqOa3NlRQuYhlRhZTL+hN6Q=
github.com/opencloud-eu/reva/v2 v2.42.2 h1:2v2RXsD9qX3L4vhu3Pl4QEXgx6jANeKz3FSFhZ3oU5E=
github.com/opencloud-eu/reva/v2 v2.42.2/go.mod h1:LrMYMcSrH9nvTywiE1ry0i2w38yGLFKUPYxWjvKulBo=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
@@ -1103,12 +1103,12 @@ github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb
github.com/sacloud/libsacloud v1.36.2/go.mod h1:P7YAOVmnIn3DKHqCZcUKYUXmSwGBm3yS7IBEjKVSrjg=
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
github.com/samber/lo v1.51.0 h1:kysRYLbHy/MB7kQZf5DSN50JHmMsNEdeY24VzJFu7wI=
github.com/samber/lo v1.51.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
github.com/samber/slog-common v0.19.0 h1:fNcZb8B2uOLooeYwFpAlKjkQTUafdjfqKcwcC89G9YI=
github.com/samber/slog-common v0.19.0/go.mod h1:dTz+YOU76aH007YUU0DffsXNsGFQRQllPQh9XyNoA3M=
github.com/samber/slog-zerolog/v2 v2.9.0 h1:6LkOabJmZdNLaUWkTC3IVVA+dq7b/V0FM6lz6/7+THI=
github.com/samber/slog-zerolog/v2 v2.9.0/go.mod h1:gnQW9VnCfM34v2pRMUIGMsZOVbYLqY/v0Wxu6atSVGc=
github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw=
github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
github.com/samber/slog-common v0.20.0 h1:WaLnm/aCvBJSk5nR5aXZTFBaV0B47A+AEaEOiZDeUnc=
github.com/samber/slog-common v0.20.0/go.mod h1:+Ozat1jgnnE59UAlmNX1IF3IByHsODnnwf9jUcBZ+m8=
github.com/samber/slog-zerolog/v2 v2.9.1 h1:RMOq8XqzfuGx1X0TEIlS9OXbbFmqLY2/wJppghz66YY=
github.com/samber/slog-zerolog/v2 v2.9.1/go.mod h1:DQYYve14WgCRN/XnKeHl4266jXK0DgYkYXkfZ4Fp98k=
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.7.0.20210127161313-bd30bebeac4f/go.mod h1:CJJ5VAbozOl0yEw7nHB9+7BXTJbIn6h7W+f6Gau5IP8=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0=

View File

@@ -153,25 +153,30 @@ func Sanitize(cfg *config.Config) {
cfg.Web.Config.OpenIDConnect.MetadataURL = strings.TrimRight(cfg.Web.Config.OpenIDConnect.Authority, "/") + "/.well-known/openid-configuration"
}
// remove AccountEdit parent if no value is set
if cfg.Web.Config.Options.AccountEditLink.Href == "" {
if cfg.Web.Config.Options.AccountEditLink != nil &&
cfg.Web.Config.Options.AccountEditLink.Href == "" {
cfg.Web.Config.Options.AccountEditLink = nil
}
// remove Editor parent if no value is set
if !cfg.Web.Config.Options.Editor.AutosaveEnabled {
if cfg.Web.Config.Options.Editor != nil &&
!cfg.Web.Config.Options.Editor.AutosaveEnabled {
cfg.Web.Config.Options.Editor = nil
}
// remove FeedbackLink parent if no value is set
if cfg.Web.Config.Options.FeedbackLink.Href == "" &&
if cfg.Web.Config.Options.FeedbackLink != nil &&
cfg.Web.Config.Options.FeedbackLink.Href == "" &&
cfg.Web.Config.Options.FeedbackLink.AriaLabel == "" &&
cfg.Web.Config.Options.FeedbackLink.Description == "" {
cfg.Web.Config.Options.FeedbackLink = nil
}
// remove Upload parent if no value is set
if cfg.Web.Config.Options.Upload.CompanionURL == "" {
if cfg.Web.Config.Options.Upload != nil &&
cfg.Web.Config.Options.Upload.CompanionURL == "" {
cfg.Web.Config.Options.Upload = nil
}
// remove Embed parent if no value is set
if cfg.Web.Config.Options.Embed.Enabled == "" &&
if cfg.Web.Config.Options.Embed != nil &&
cfg.Web.Config.Options.Embed.Enabled == "" &&
cfg.Web.Config.Options.Embed.Target == "" &&
cfg.Web.Config.Options.Embed.MessagesOrigin == "" &&
cfg.Web.Config.Options.Embed.DelegateAuthentication &&

View File

@@ -324,13 +324,6 @@ _ocdav: api compatibility, return correct status code_
- [coreApiWebdavUploadTUS/uploadToShare.feature:376](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature#L376)
- [coreApiWebdavUploadTUS/uploadToShare.feature:377](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature#L377)
#### [Renaming resource to banned name is allowed in spaces webdav](https://github.com/owncloud/ocis/issues/3099)
- [coreApiWebdavMove/moveFile.feature:143](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavMove/moveFile.feature#L143)
- [coreApiWebdavMove/moveFolder.feature:36](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavMove/moveFolder.feature#L36)
- [coreApiWebdavMove/moveFolder.feature:50](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavMove/moveFolder.feature#L50)
- [coreApiWebdavMove/moveFolder.feature:64](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavMove/moveFolder.feature#L64)
#### [Trying to delete other user's trashbin item returns 409 for spaces path instead of 404](https://github.com/owncloud/ocis/issues/9791)
- [coreApiTrashbin/trashbinDelete.feature:92](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiTrashbin/trashbinDelete.feature#L92)
@@ -340,9 +333,9 @@ _ocdav: api compatibility, return correct status code_
- [coreApiWebdavMove/moveFile.feature:100](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavMove/moveFile.feature#L100)
- [coreApiWebdavMove/moveFile.feature:101](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavMove/moveFile.feature#L101)
- [coreApiWebdavMove/moveFile.feature:102](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavMove/moveFile.feature#L102)
- [coreApiWebdavMove/moveFolder.feature:220](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavMove/moveFolder.feature#L220)
- [coreApiWebdavMove/moveFolder.feature:221](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavMove/moveFolder.feature#L221)
- [coreApiWebdavMove/moveFolder.feature:222](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavMove/moveFolder.feature#L222)
- [coreApiWebdavMove/moveFolder.feature:217](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavMove/moveFolder.feature#L217)
- [coreApiWebdavMove/moveFolder.feature:218](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavMove/moveFolder.feature#L218)
- [coreApiWebdavMove/moveFolder.feature:219](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavMove/moveFolder.feature#L219)
- [coreApiWebdavMove/moveShareOnOpencloud.feature:334](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavMove/moveShareOnOpencloud.feature#L334)
- [coreApiWebdavMove/moveShareOnOpencloud.feature:337](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavMove/moveShareOnOpencloud.feature#L337)
- [coreApiWebdavMove/moveShareOnOpencloud.feature:340](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavMove/moveShareOnOpencloud.feature#L340)
@@ -370,24 +363,6 @@ _ocdav: api compatibility, return correct status code_
- [coreApiWebdavPreviews/previews.feature:264](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavPreviews/previews.feature#L264)
- [coreApiWebdavPreviews/previews.feature:265](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavPreviews/previews.feature#L265)
#### [Renaming a file with a slash in the filename makes the file inaccessible](https://github.com/opencloud-eu/web/issues/1893)
- [coreApiWebdavProperties/createFileFolder.feature:29](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavProperties/createFileFolder.feature#L29)
- [coreApiWebdavProperties/createFileFolder.feature:42](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavProperties/createFileFolder.feature#L42)
- [coreApiWebdavProperties/createFileFolder.feature:148](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavProperties/createFileFolder.feature#L148)
- [coreApiWebdavProperties/createFileFolder.feature:160](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavProperties/createFileFolder.feature#L160)
- [coreApiWebdavProperties/createFileFolder.feature:172](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavProperties/createFileFolder.feature#L172)
- [coreApiWebdavMove/moveFile.feature:441](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavMove/moveFile.feature#L441)
- [coreApiWebdavMove/moveFile.feature:452](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavMove/moveFile.feature#L452)
- [coreApiWebdavMove/moveFolder.feature:164](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavMove/moveFolder.feature#L164)
- [coreApiWebdavMove/moveFolder.feature:179](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavMove/moveFolder.feature#L179)
- [coreApiWebdavOperations/downloadFile.feature:307](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavOperations/downloadFile.feature#L307)
- [coreApiWebdavOperations/downloadFile.feature:313](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavOperations/downloadFile.feature#L313)
- [coreApiWebdavOperations/downloadFile.feature:319](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavOperations/downloadFile.feature#L319)
### Won't fix
Not everything needs to be implemented for opencloud.

View File

@@ -324,12 +324,6 @@ _ocdav: api compatibility, return correct status code_
- [coreApiWebdavUploadTUS/uploadToShare.feature:376](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature#L376)
- [coreApiWebdavUploadTUS/uploadToShare.feature:377](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature#L377)
#### [Renaming resource to banned name is allowed in spaces webdav](https://github.com/owncloud/ocis/issues/3099)
- [coreApiWebdavMove/moveFile.feature:143](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavMove/moveFile.feature#L143)
- [coreApiWebdavMove/moveFolder.feature:36](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavMove/moveFolder.feature#L36)
- [coreApiWebdavMove/moveFolder.feature:50](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavMove/moveFolder.feature#L50)
- [coreApiWebdavMove/moveFolder.feature:64](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavMove/moveFolder.feature#L64)
#### [Trying to delete other user's trashbin item returns 409 for spaces path instead of 404](https://github.com/owncloud/ocis/issues/9791)
@@ -340,9 +334,9 @@ _ocdav: api compatibility, return correct status code_
- [coreApiWebdavMove/moveFile.feature:100](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavMove/moveFile.feature#L100)
- [coreApiWebdavMove/moveFile.feature:101](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavMove/moveFile.feature#L101)
- [coreApiWebdavMove/moveFile.feature:102](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavMove/moveFile.feature#L102)
- [coreApiWebdavMove/moveFolder.feature:220](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavMove/moveFolder.feature#L220)
- [coreApiWebdavMove/moveFolder.feature:221](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavMove/moveFolder.feature#L221)
- [coreApiWebdavMove/moveFolder.feature:222](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavMove/moveFolder.feature#L222)
- [coreApiWebdavMove/moveFolder.feature:217](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavMove/moveFolder.feature#L217)
- [coreApiWebdavMove/moveFolder.feature:218](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavMove/moveFolder.feature#L218)
- [coreApiWebdavMove/moveFolder.feature:219](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavMove/moveFolder.feature#L219)
- [coreApiWebdavMove/moveShareOnOpencloud.feature:334](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavMove/moveShareOnOpencloud.feature#L334)
- [coreApiWebdavMove/moveShareOnOpencloud.feature:337](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavMove/moveShareOnOpencloud.feature#L337)
- [coreApiWebdavMove/moveShareOnOpencloud.feature:340](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavMove/moveShareOnOpencloud.feature#L340)
@@ -371,24 +365,6 @@ _ocdav: api compatibility, return correct status code_
- [coreApiWebdavPreviews/previews.feature:265](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavPreviews/previews.feature#L265)
#### [Renaming a file with a slash in the filename makes the file inaccessible](https://github.com/opencloud-eu/web/issues/1893)
- [coreApiWebdavProperties/createFileFolder.feature:29](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavProperties/createFileFolder.feature#L29)
- [coreApiWebdavProperties/createFileFolder.feature:42](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavProperties/createFileFolder.feature#L42)
- [coreApiWebdavProperties/createFileFolder.feature:148](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavProperties/createFileFolder.feature#L148)
- [coreApiWebdavProperties/createFileFolder.feature:160](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavProperties/createFileFolder.feature#L160)
- [coreApiWebdavProperties/createFileFolder.feature:172](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavProperties/createFileFolder.feature#L172)
- [coreApiWebdavMove/moveFile.feature:441](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavMove/moveFile.feature#L441)
- [coreApiWebdavMove/moveFile.feature:452](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavMove/moveFile.feature#L452)
- [coreApiWebdavMove/moveFolder.feature:164](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavMove/moveFolder.feature#L164)
- [coreApiWebdavMove/moveFolder.feature:179](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavMove/moveFolder.feature#L179)
- [coreApiWebdavOperations/downloadFile.feature:307](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavOperations/downloadFile.feature#L307)
- [coreApiWebdavOperations/downloadFile.feature:313](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavOperations/downloadFile.feature#L313)
- [coreApiWebdavOperations/downloadFile.feature:319](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavOperations/downloadFile.feature#L319)
### Won't fix
Not everything needs to be implemented for opencloud.

View File

@@ -134,7 +134,7 @@ Feature: move (rename) file
Scenario Outline: rename a file into an invalid filename
Given using <dav-path-version> DAV path
And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "fileToRename.txt"
When user "Alice" moves file "/fileToRename.txt" to "/a\\a" using the WebDAV API
When user "Alice" moves file "/fileToRename.txt" to "/with\\backslash" using the WebDAV API
Then the HTTP status code should be "400"
Examples:
| dav-path-version |
@@ -438,7 +438,6 @@ Feature: move (rename) file
| old | "strängé .txt" | "testfile.txt" |
| old | "file,comma.txt" | "testfile.txt" |
| old | " start with space.txt" | "testfile.txt" |
| old | "testfile.txt" | "with\backslash" |
| new | "testfile.txt" | "'single'quotes.txt" |
| new | "testfile.txt" | '"double"quotes.txt' |
| new | "testfile.txt" | "strängé .txt" |
@@ -449,7 +448,6 @@ Feature: move (rename) file
| new | "strängé .txt" | "testfile.txt" |
| new | "file,comma.txt" | "testfile.txt" |
| new | " start with space.txt" | "testfile.txt" |
| new | "testfile.txt" | "with\backslash" |
| spaces | "testfile.txt" | "'single'quotes.txt" |
| spaces | "testfile.txt" | '"double"quotes.txt' |
| spaces | "testfile.txt" | "strängé .txt" |
@@ -460,7 +458,6 @@ Feature: move (rename) file
| spaces | "strängé .txt" | "testfile.txt" |
| spaces | "file,comma.txt" | "testfile.txt" |
| spaces | " start with space.txt" | "testfile.txt" |
| spaces | "testfile.txt" | "with\backslash" |
Scenario Outline: try to rename file to name having white space at the end

View File

@@ -161,7 +161,6 @@ Feature: move (rename) folder
| old | "Sample,Folder,With,Comma" | "testFolder" |
| old | " start with space" | "testFolder" |
| old | "renamed.part" | "testFolder" |
| old | "testFolder" | "with\backslash" |
| new | "testFolder" | "'single'quotes" |
| new | "testFolder" | '"double"quotes' |
| new | "testFolder" | "strängé folder" |
@@ -176,7 +175,6 @@ Feature: move (rename) folder
| new | "Sample,Folder,With,Comma" | "testFolder" |
| new | " start with space" | "testFolder" |
| new | "renamed.part" | "testFolder" |
| new | "testFolder" | "with\backslash" |
| spaces | "testFolder" | "'single'quotes" |
| spaces | "testFolder" | '"double"quotes' |
| spaces | "testFolder" | "strängé folder" |
@@ -191,7 +189,6 @@ Feature: move (rename) folder
| spaces | "Sample,Folder,With,Comma" | "testFolder" |
| spaces | " start with space" | "testFolder" |
| spaces | "renamed.part" | "testFolder" |
| spaces | "testFolder" | "with\backslash" |
Scenario Outline: try to rename folder to name having white space at the end

View File

@@ -304,18 +304,15 @@ Feature: download file
| old | "😀 🤖.txt" |
| old | "" |
| old | "C++ file.cpp" |
| old | "with\backslash" |
| old | "file #2.txt" |
| old | "file ?2.pdf" |
| new | "😀 🤖.txt" |
| new | "" |
| new | "C++ file.cpp" |
| new | "with\backslash" |
| new | "file #2.txt" |
| new | "file ?2.pdf" |
| spaces | "😀 🤖.txt" |
| spaces | "" |
| spaces | "C++ file.cpp" |
| spaces | "with\backslash" |
| spaces | "file #2.txt" |
| spaces | "file ?2.pdf" |

View File

@@ -26,7 +26,6 @@ Feature: create files and folder
| old | "Sample,comma" |
| old | "'single'" |
| old | '"double"' |
| old | "with\backslash" |
| new | "upload" |
| new | "strängé folder" |
| new | "C++ folder.cpp" |
@@ -39,7 +38,6 @@ Feature: create files and folder
| new | "'single'" |
| new | '"double"' |
| new | "" |
| new | "with\backslash" |
| spaces | "upload" |
| spaces | "strängé folder" |
| spaces | "C++ folder.cpp" |
@@ -51,7 +49,6 @@ Feature: create files and folder
| spaces | "Sample,comma" |
| spaces | "'single'" |
| spaces | '"double"' |
| spaces | "with\backslash" |
@smokeTest
Scenario Outline: get resourcetype property of a folder
@@ -145,7 +142,6 @@ Feature: create files and folder
| old | "Sample,comma.txt" |
| old | "'single'.txt" |
| old | '"double".txt' |
| old | "with\backslash" |
| new | "upload.txt" |
| new | "strängéfile.txt" |
| new | "C++ file.cpp" |
@@ -157,7 +153,6 @@ Feature: create files and folder
| new | "Sample,comma.txt" |
| new | "'single'.txt" |
| new | '"double".txt' |
| new | "with\backslash" |
| spaces | "upload.txt" |
| spaces | "strängéfile.txt" |
| spaces | "C++ file.cpp" |
@@ -169,7 +164,6 @@ Feature: create files and folder
| spaces | "Sample,comma.txt" |
| spaces | "'single'.txt" |
| spaces | '"double".txt' |
| spaces | "with\backslash" |
@issue-10339 @issue-9568
Scenario Outline: try to create file with '.', '..' and 'empty'
@@ -208,10 +202,32 @@ Feature: create files and folder
| new | | 400 |
| spaces | /. | 400 |
| spaces | /.. | 405 |
| spaces | /../lorem | 404 |
| spaces | /../lorem | 400 |
| spaces | | 400 |
Scenario Outline: try to create folder with backslash
Given using <dav-path-version> DAV path
When user "Alice" creates folder "<folder-name>" using the WebDAV API
Then the HTTP status code should be "400"
Examples:
| dav-path-version | folder-name |
| old | with\backslash |
| new | with\backslash |
| spaces | with\backslash |
Scenario Outline: try to create file with backslash
Given using <dav-path-version> DAV path
When user "Alice" uploads file with content "some text" to "<file-name>" using the WebDAV API
Then the HTTP status code should be "400"
Examples:
| dav-path-version | file-name |
| old | with\backslash |
| new | with\backslash |
| spaces | with\backslash |
Scenario Outline: create a file with dots in the name
Given using <dav-path-version> DAV path
And user "Alice" uploads file with content "some text" to "<file-name>" using the WebDAV API

View File

@@ -79,6 +79,7 @@ func (hr HeaderRouter) Handler(next http.Handler) http.Handler {
if len(hr) == 0 {
// skip if no routes set
next.ServeHTTP(w, r)
return
}
// find first matching header route, and continue

View File

@@ -21,6 +21,7 @@ package auth
import (
"context"
"fmt"
"path/filepath"
"strings"
"time"
@@ -283,8 +284,11 @@ func checkIfNestedResource(ctx context.Context, ref *provider.Reference, parent
return false, statuspkg.NewErrorFromCode(pathResp.Status.Code, "auth interceptor")
}
childPath := pathResp.Path
return strings.HasPrefix(childPath, parentPath), nil
rel, err := filepath.Rel(parentPath, childPath)
if err != nil {
return false, err
}
return !strings.HasPrefix(rel, ".."), nil
}
func extractRefFromListProvidersReq(v *registry.ListStorageProvidersRequest) (*provider.Reference, bool) {

View File

@@ -97,6 +97,10 @@ func (s *svc) handleSpacesMkCol(w http.ResponseWriter, r *http.Request, spaceID
sublog := appctx.GetLogger(ctx).With().Str("path", r.URL.Path).Str("spaceid", spaceID).Str("handler", "mkcol").Logger()
if err := ValidateName(filename(r.URL.Path), s.nameValidators); err != nil {
return http.StatusBadRequest, err
}
parentRef, err := spacelookup.MakeStorageSpaceReference(spaceID, path.Dir(r.URL.Path))
if err != nil {
return http.StatusBadRequest, fmt.Errorf("invalid space id")

View File

@@ -131,6 +131,16 @@ func (s *svc) handleSpacesMove(w http.ResponseWriter, r *http.Request, srcSpaceI
dstSpaceID, dstRelPath := router.ShiftPath(dst)
if dstRelPath != "" && dstRelPath != "." && dstRelPath != "/" {
err := ValidateDestination(filename(dstRelPath), s.nameValidators)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
b, err := errors.Marshal(http.StatusBadRequest, "destination naming rules", "", "")
errors.HandleWebdavError(appctx.GetLogger(ctx), w, b, err)
return
}
}
dstRef, err := spacelookup.MakeStorageSpaceReference(dstSpaceID, dstRelPath)
if err != nil {
w.WriteHeader(http.StatusBadRequest)

104
vendor/github.com/samber/lo/.golangci.yml generated vendored Normal file
View File

@@ -0,0 +1,104 @@
version: "2"
run:
concurrency: 4
# also lint _test.go files
tests: true
timeout: 5m
linters:
enable:
- govet
- staticcheck
- unused
- errcheck
- gocritic
- gocyclo
- revive
- ineffassign
- unconvert
- goconst
# - depguard
- prealloc
# - dupl
- misspell
- bodyclose
- sqlclosecheck
- nilerr
- nestif
- forcetypeassert
- exhaustive
- funlen
# - wsl_v5
- testifylint
- whitespace
- perfsprint
- nolintlint
- godot
- thelper
- tparallel
- paralleltest
- predeclared
# disable noisy/controversial ones which you might enable later
disable:
- lll # line length — handled by gfmt/gofumpt
settings:
dupl:
threshold: 20 # lower => stricter (tokens)
errcheck:
check-type-assertions: true
funlen:
lines: 120
statements: 80
goconst:
min-len: 2
min-occurrences: 3
gocyclo:
min-complexity: 15 # strict; lower => stricter
wsl_v5:
allow-first-in-block: true
allow-whole-block: false
branch-max-lines: 2
testifylint:
disable:
- require-error
- float-compare
exclusions:
generated: lax
paths:
- examples$
rules:
- linters:
- revive
text: "^unused-parameter:"
- linters:
- revive
text: "^package-comments:"
- linters:
- errcheck
text: "Error return value of `.*\\.Body\\.Close` is not checked"
# linters disabled in tests
- linters:
- dupl
- goconst
- funlen
path: "_test\\.go$"
issues:
max-issues-per-linter: 0 # 0 = unlimited (we want ALL issues)
max-same-issues: 100
formatters:
enable:
- gofmt
- gofumpt
settings:
gofumpt:
extra-rules: true
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$

24
vendor/github.com/samber/lo/Makefile generated vendored
View File

@@ -3,20 +3,19 @@ build:
go build -v ./...
test:
go test -race -v ./...
go test -race ./...
watch-test:
reflex -t 50ms -s -- sh -c 'gotest -race -v ./...'
reflex -t 50ms -s -- sh -c 'gotest -race ./...'
bench:
go test -benchmem -count 3 -bench ./...
go test -v -run=^Benchmark -benchmem -count 3 -bench ./...
watch-bench:
reflex -t 50ms -s -- sh -c 'go test -benchmem -count 3 -bench ./...'
reflex -t 50ms -s -- sh -c 'go test -v -run=^Benchmark -benchmem -count 3 -bench ./...'
coverage:
go test -v -coverprofile=cover.out -covermode=atomic ./...
go tool cover -html=cover.out -o cover.html
# tools
tools:
go install github.com/cespare/reflex@latest
go install github.com/rakyll/gotest@latest
@@ -25,18 +24,27 @@ tools:
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
go get -t -u golang.org/x/tools/cmd/cover
go install github.com/sonatype-nexus-community/nancy@latest
go install golang.org/x/perf/cmd/benchstat@latest
go install github.com/cespare/prettybench@latest
go mod tidy
# brew install hougesen/tap/mdsf
lint:
golangci-lint run --timeout 60s --max-same-issues 50 ./...
# mdsf verify --debug --log-level warn docs/
lint-fix:
golangci-lint run --timeout 60s --max-same-issues 50 --fix ./...
# mdsf format --debug --log-level warn docs/
audit: tools
audit:
go list -json -m all | nancy sleuth
outdated: tools
outdated:
go list -u -m -json all | go-mod-outdated -update -direct
weight: tools
weight:
goweight
doc:
cd docs && npm install && npm start

540
vendor/github.com/samber/lo/README.md generated vendored
View File

File diff suppressed because it is too large Load Diff

View File

@@ -5,15 +5,17 @@ import (
"sync"
"time"
"github.com/samber/lo/internal/rand"
"github.com/samber/lo/internal/xrand"
)
// DispatchingStrategy is a function that distributes messages to channels.
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 {
// Play: https://go.dev/play/p/UZGu2wVg3J2
func ChannelDispatcher[T any](stream <-chan T, count, channelBufferCap int, strategy DispatchingStrategy[T]) []<-chan T {
children := createChannels[T](count, channelBufferCap)
roChildren := channelsToReadOnly(children)
@@ -22,7 +24,7 @@ func ChannelDispatcher[T any](stream <-chan T, count int, channelBufferCap int,
// propagate channel closing to children
defer closeChannels(children)
var i uint64 = 0
var i uint64
for {
msg, ok := <-stream
@@ -40,7 +42,7 @@ func ChannelDispatcher[T any](stream <-chan T, count int, channelBufferCap int,
return roChildren
}
func createChannels[T any](count int, channelBufferCap int) []chan T {
func createChannels[T any](count, channelBufferCap int) []chan T {
children := make([]chan T, 0, count)
for i := 0; i < count; i++ {
@@ -72,6 +74,7 @@ func channelIsNotFull[T any](ch <-chan T) bool {
// DispatchingStrategyRoundRobin distributes messages in a rotating sequential manner.
// If the channel capacity is exceeded, the next channel will be selected and so on.
// Play: https://go.dev/play/p/UZGu2wVg3J2
func DispatchingStrategyRoundRobin[T any](msg T, index uint64, channels []<-chan T) int {
for {
i := int(index % uint64(len(channels)))
@@ -86,9 +89,10 @@ func DispatchingStrategyRoundRobin[T any](msg T, index uint64, channels []<-chan
// DispatchingStrategyRandom distributes messages in a random manner.
// If the channel capacity is exceeded, another random channel will be selected and so on.
// Play: https://go.dev/play/p/GEyGn3TdGk4
func DispatchingStrategyRandom[T any](msg T, index uint64, channels []<-chan T) int {
for {
i := rand.IntN(len(channels))
i := xrand.IntN(len(channels))
if channelIsNotFull(channels[i]) {
return i
}
@@ -99,6 +103,7 @@ func DispatchingStrategyRandom[T any](msg T, index uint64, channels []<-chan T)
// DispatchingStrategyWeightedRandom distributes messages in a weighted manner.
// If the channel capacity is exceeded, another random channel will be selected and so on.
// Play: https://go.dev/play/p/v0eMh8NZG2L
func DispatchingStrategyWeightedRandom[T any](weights []int) DispatchingStrategy[T] {
seq := []int{}
@@ -110,7 +115,7 @@ func DispatchingStrategyWeightedRandom[T any](weights []int) DispatchingStrategy
return func(msg T, index uint64, channels []<-chan T) int {
for {
i := seq[rand.IntN(len(seq))]
i := seq[xrand.IntN(len(seq))]
if channelIsNotFull(channels[i]) {
return i
}
@@ -122,6 +127,7 @@ func DispatchingStrategyWeightedRandom[T any](weights []int) DispatchingStrategy
// 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.
// Play: https://go.dev/play/p/OrJCvOmk42f
func DispatchingStrategyFirst[T any](msg T, index uint64, channels []<-chan T) int {
for {
for i := range channels {
@@ -135,25 +141,28 @@ func DispatchingStrategyFirst[T any](msg T, index uint64, channels []<-chan T) i
}
// DispatchingStrategyLeast distributes messages in the emptiest channel.
// Play: https://go.dev/play/p/ypy0jrRcEe7
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])
return MinBy(seq, func(item, mIn int) bool {
return len(channels[item]) < len(channels[mIn])
})
}
// DispatchingStrategyMost distributes messages in the fullest channel.
// If the channel capacity is exceeded, the next channel will be selected and so on.
// Play: https://go.dev/play/p/erHHone7rF9
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])
return MaxBy(seq, func(item, mAx int) bool {
return len(channels[item]) > len(channels[mAx]) && channelIsNotFull(channels[item])
})
}
// SliceToChannel returns a read-only channels of collection elements.
// SliceToChannel returns a read-only channel of collection elements.
// Play: https://go.dev/play/p/lIbSY3QmiEg
func SliceToChannel[T any](bufferSize int, collection []T) <-chan T {
ch := make(chan T, bufferSize)
@@ -168,7 +177,8 @@ func SliceToChannel[T any](bufferSize int, collection []T) <-chan T {
return ch
}
// ChannelToSlice returns a slice built from channels items. Blocks until channel closes.
// ChannelToSlice returns a slice built from channel items. Blocks until channel closes.
// Play: https://go.dev/play/p/lIbSY3QmiEg
func ChannelToSlice[T any](ch <-chan T) []T {
collection := []T{}
@@ -180,6 +190,9 @@ func ChannelToSlice[T any](ch <-chan T) []T {
}
// Generator implements the generator design pattern.
// Play: https://go.dev/play/p/lIbSY3QmiEg
//
// Deprecated: use "iter" package instead (Go >= 1.23).
func Generator[T any](bufferSize int, generator func(yield func(T))) <-chan T {
ch := make(chan T, bufferSize)
@@ -196,7 +209,8 @@ func Generator[T any](bufferSize int, generator func(yield func(T))) <-chan T {
}
// Buffer creates a slice of n elements from a channel. Returns the slice and the slice length.
// @TODO: we should probably provide an helper that reuse the same buffer.
// @TODO: we should probably provide a helper that reuses the same buffer.
// Play: https://go.dev/play/p/gPQ-6xmcKQI
func Buffer[T any](ch <-chan T, size int) (collection []T, length int, readTime time.Duration, ok bool) {
buffer := make([]T, 0, size)
index := 0
@@ -222,7 +236,8 @@ func Batch[T any](ch <-chan T, size int) (collection []T, length int, readTime t
}
// BufferWithContext creates a slice of n elements from a channel, with context. Returns the slice and the slice length.
// @TODO: we should probably provide an helper that reuse the same buffer.
// @TODO: we should probably provide a helper that reuses the same buffer.
// Play: https://go.dev/play/p/oRfOyJWK9YF
func BufferWithContext[T any](ctx context.Context, ch <-chan T, size int) (collection []T, length int, readTime time.Duration, ok bool) {
buffer := make([]T, 0, size)
now := time.Now()
@@ -245,6 +260,7 @@ func BufferWithContext[T any](ctx context.Context, ch <-chan T, size int) (colle
}
// BufferWithTimeout creates a slice of n elements from a channel, with timeout. Returns the slice and the slice length.
// Play: https://go.dev/play/p/sxyEM3koo4n
func BufferWithTimeout[T any](ch <-chan T, size int, timeout time.Duration) (collection []T, length int, readTime time.Duration, ok bool) {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
@@ -259,7 +275,8 @@ func BatchWithTimeout[T any](ch <-chan T, size int, timeout time.Duration) (coll
}
// FanIn collects messages from multiple input channels into a single buffered channel.
// Output messages has no priority. When all upstream channels reach EOF, downstream channel closes.
// Output messages have no priority. When all upstream channels reach EOF, downstream channel closes.
// Play: https://go.dev/play/p/FH8Wq-T04Jb
func FanIn[T any](channelBufferCap int, upstreams ...<-chan T) <-chan T {
out := make(chan T, channelBufferCap)
var wg sync.WaitGroup
@@ -284,7 +301,7 @@ func FanIn[T any](channelBufferCap int, upstreams ...<-chan T) <-chan T {
}
// ChannelMerge collects messages from multiple input channels into a single buffered channel.
// Output messages has no priority. When all upstream channels reach EOF, downstream channel closes.
// Output messages have no priority. When all upstream channels reach EOF, downstream channel closes.
//
// Deprecated: Use [FanIn] instead.
func ChannelMerge[T any](channelBufferCap int, upstreams ...<-chan T) <-chan T {
@@ -292,9 +309,10 @@ func ChannelMerge[T any](channelBufferCap int, upstreams ...<-chan T) <-chan T {
}
// FanOut broadcasts all the upstream messages to multiple downstream channels.
// When upstream channel reach EOF, downstream channels close. If any downstream
// When upstream channel reaches EOF, downstream channels close. If any downstream
// channels is full, broadcasting is paused.
func FanOut[T any](count int, channelsBufferCap int, upstream <-chan T) []<-chan T {
// Play: https://go.dev/play/p/2LHxcjKX23L
func FanOut[T any](count, channelsBufferCap int, upstream <-chan T) []<-chan T {
downstreams := createChannels[T](count, channelsBufferCap)
go func() {

View File

@@ -17,9 +17,10 @@ func (s *synchronize) Do(cb func()) {
}
// Synchronize wraps the underlying callback in a mutex. It receives an optional mutex.
func Synchronize(opt ...sync.Locker) *synchronize {
// Play: https://go.dev/play/p/X3cqROSpQmu
func Synchronize(opt ...sync.Locker) *synchronize { //nolint:revive
if len(opt) > 1 {
panic("unexpected arguments")
panic("lo.Synchronize: unexpected arguments")
} else if len(opt) == 0 {
opt = append(opt, &sync.Mutex{})
}
@@ -30,6 +31,7 @@ func Synchronize(opt ...sync.Locker) *synchronize {
}
// Async executes a function in a goroutine and returns the result in a channel.
// Play: https://go.dev/play/p/uo35gosuTLw
func Async[A any](f func() A) <-chan A {
ch := make(chan A, 1)
go func() {
@@ -39,6 +41,7 @@ func Async[A any](f func() A) <-chan A {
}
// Async0 executes a function in a goroutine and returns a channel set once the function finishes.
// Play: https://go.dev/play/p/tNqf1cClG_o
func Async0(f func()) <-chan struct{} {
ch := make(chan struct{}, 1)
go func() {
@@ -49,11 +52,13 @@ func Async0(f func()) <-chan struct{} {
}
// Async1 is an alias to Async.
// Play: https://go.dev/play/p/uo35gosuTLw
func Async1[A any](f func() A) <-chan A {
return Async(f)
}
// Async2 has the same behavior as Async, but returns the 2 results as a tuple inside the channel.
// Play: https://go.dev/play/p/7W7mKQi0AhA
func Async2[A, B any](f func() (A, B)) <-chan Tuple2[A, B] {
ch := make(chan Tuple2[A, B], 1)
go func() {
@@ -63,6 +68,7 @@ func Async2[A, B any](f func() (A, B)) <-chan Tuple2[A, B] {
}
// Async3 has the same behavior as Async, but returns the 3 results as a tuple inside the channel.
// Play: https://go.dev/play/p/L1d6o6l6q0d
func Async3[A, B, C any](f func() (A, B, C)) <-chan Tuple3[A, B, C] {
ch := make(chan Tuple3[A, B, C], 1)
go func() {
@@ -72,6 +78,7 @@ func Async3[A, B, C any](f func() (A, B, C)) <-chan Tuple3[A, B, C] {
}
// Async4 has the same behavior as Async, but returns the 4 results as a tuple inside the channel.
// Play: https://go.dev/play/p/1X7q6oL0TqF
func Async4[A, B, C, D any](f func() (A, B, C, D)) <-chan Tuple4[A, B, C, D] {
ch := make(chan Tuple4[A, B, C, D], 1)
go func() {
@@ -81,6 +88,7 @@ func Async4[A, B, C, D any](f func() (A, B, C, D)) <-chan Tuple4[A, B, C, D] {
}
// Async5 has the same behavior as Async, but returns the 5 results as a tuple inside the channel.
// Play: https://go.dev/play/p/2W7q4oL1TqG
func Async5[A, B, C, D, E any](f func() (A, B, C, D, E)) <-chan Tuple5[A, B, C, D, E] {
ch := make(chan Tuple5[A, B, C, D, E], 1)
go func() {
@@ -90,6 +98,7 @@ func Async5[A, B, C, D, E any](f func() (A, B, C, D, E)) <-chan Tuple5[A, B, C,
}
// Async6 has the same behavior as Async, but returns the 6 results as a tuple inside the channel.
// Play: https://go.dev/play/p/3X8q5pM2UrH
func Async6[A, B, C, D, E, F any](f func() (A, B, C, D, E, F)) <-chan Tuple6[A, B, C, D, E, F] {
ch := make(chan Tuple6[A, B, C, D, E, F], 1)
go func() {
@@ -99,7 +108,8 @@ func Async6[A, B, C, D, E, F any](f func() (A, B, C, D, E, F)) <-chan Tuple6[A,
}
// WaitFor runs periodically until a condition is validated.
func WaitFor(condition func(i int) bool, timeout time.Duration, heartbeatDelay time.Duration) (totalIterations int, elapsed time.Duration, conditionFound bool) {
// Play: https://go.dev/play/p/t_wTDmubbK3
func WaitFor(condition func(i int) bool, timeout, heartbeatDelay time.Duration) (totalIterations int, elapsed time.Duration, conditionFound bool) {
conditionWithContext := func(_ context.Context, currentIteration int) bool {
return condition(currentIteration)
}
@@ -107,7 +117,8 @@ func WaitFor(condition func(i int) bool, timeout time.Duration, heartbeatDelay t
}
// WaitForWithContext runs periodically until a condition is validated or context is canceled.
func WaitForWithContext(ctx context.Context, condition func(ctx context.Context, currentIteration int) bool, timeout time.Duration, heartbeatDelay time.Duration) (totalIterations int, elapsed time.Duration, conditionFound bool) {
// Play: https://go.dev/play/p/t_wTDmubbK3
func WaitForWithContext(ctx context.Context, condition func(ctx context.Context, currentIteration int) bool, timeout, heartbeatDelay time.Duration) (totalIterations int, elapsed time.Duration, conditionFound bool) {
start := time.Now()
if ctx.Err() != nil {

View File

@@ -1,9 +1,9 @@
package lo
// Ternary is a 1 line if/else statement.
// Ternary is a single line if/else statement.
// Take care to avoid dereferencing potentially nil pointers in your A/B expressions, because they are both evaluated. See TernaryF to avoid this problem.
// Play: https://go.dev/play/p/t-D7WBL44h2
func Ternary[T any](condition bool, ifOutput T, elseOutput T) T {
func Ternary[T any](condition bool, ifOutput, elseOutput T) T {
if condition {
return ifOutput
}
@@ -11,9 +11,9 @@ 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
// TernaryF is a single 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 {
func TernaryF[T any](condition bool, ifFunc, elseFunc func() T) T {
if condition {
return ifFunc()
}
@@ -26,9 +26,9 @@ type ifElse[T any] struct {
done bool
}
// If.
// If is a single line if/else statement.
// Play: https://go.dev/play/p/WSw3ApMxhyW
func If[T any](condition bool, result T) *ifElse[T] {
func If[T any](condition bool, result T) *ifElse[T] { //nolint:revive
if condition {
return &ifElse[T]{result, true}
}
@@ -37,9 +37,9 @@ func If[T any](condition bool, result T) *ifElse[T] {
return &ifElse[T]{t, false}
}
// IfF.
// IfF is a single line if/else statement whose options are functions.
// Play: https://go.dev/play/p/WSw3ApMxhyW
func IfF[T any](condition bool, resultF func() T) *ifElse[T] {
func IfF[T any](condition bool, resultF func() T) *ifElse[T] { //nolint:revive
if condition {
return &ifElse[T]{resultF(), true}
}
@@ -98,7 +98,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] {
func Switch[T comparable, R any](predicate T) *switchCase[T, R] { //nolint:revive
var result R
return &switchCase[T, R]{

View File

@@ -25,7 +25,7 @@ func messageFromMsgAndArgs(msgAndArgs ...any) string {
return fmt.Sprintf("%+v", msgAndArgs[0])
}
if len(msgAndArgs) > 1 {
return fmt.Sprintf(msgAndArgs[0].(string), msgAndArgs[1:]...)
return fmt.Sprintf(msgAndArgs[0].(string), msgAndArgs[1:]...) //nolint:errcheck,forcetypeassert
}
return ""
}
@@ -51,9 +51,8 @@ func must(err any, messageArgs ...any) {
message := messageFromMsgAndArgs(messageArgs...)
if message != "" {
panic(message + ": " + e.Error())
} else {
panic(e.Error())
}
panic(e.Error())
default:
panic("must: invalid err type '" + reflect.TypeOf(err).Name() + "', should either be a bool or an error")
@@ -62,7 +61,7 @@ func must(err any, messageArgs ...any) {
// 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.
// Play: https://go.dev/play/p/TMoWrRp3DyC
// Play: https://go.dev/play/p/fOqtX5HudtN
func Must[T any](val T, err any, messageArgs ...any) T {
must(err, messageArgs...)
return val
@@ -74,7 +73,7 @@ func Must0(err any, messageArgs ...any) {
must(err, messageArgs...)
}
// Must1 is an alias to Must
// Must1 is an alias to Must.
// Play: https://go.dev/play/p/TMoWrRp3DyC
func Must1[T any](val T, err any, messageArgs ...any) T {
return Must(val, err, messageArgs...)
@@ -130,7 +129,7 @@ func Try(callback func() error) (ok bool) {
ok = false
}
return
return ok
}
// Try0 has the same behavior as Try, but callback returns no variable.
@@ -328,7 +327,7 @@ func TryWithErrorValue(callback func() error) (errorValue any, ok bool) {
errorValue = err
}
return
return errorValue, ok
}
// TryCatch has the same behavior as Try, but calls the catch function in case of error.

243
vendor/github.com/samber/lo/find.go generated vendored
View File

@@ -5,11 +5,12 @@ import (
"time"
"github.com/samber/lo/internal/constraints"
"github.com/samber/lo/internal/rand"
"github.com/samber/lo/internal/xrand"
)
// IndexOf returns the index at which the first occurrence of a value is found in an array or return -1
// IndexOf returns the index at which the first occurrence of a value is found in a slice or -1
// if the value cannot be found.
// Play: https://go.dev/play/p/Eo7W0lvKTky
func IndexOf[T comparable](collection []T, element T) int {
for i := range collection {
if collection[i] == element {
@@ -20,8 +21,9 @@ func IndexOf[T comparable](collection []T, element T) int {
return -1
}
// LastIndexOf returns the index at which the last occurrence of a value is found in an array or return -1
// LastIndexOf returns the index at which the last occurrence of a value is found in a slice or -1
// if the value cannot be found.
// Play: https://go.dev/play/p/Eo7W0lvKTky
func LastIndexOf[T comparable](collection []T, element T) int {
length := len(collection)
@@ -34,7 +36,40 @@ func LastIndexOf[T comparable](collection []T, element T) int {
return -1
}
// Find search an element in a slice based on a predicate. It returns element and true if element was found.
// HasPrefix returns true if the collection has the prefix.
// Play: https://go.dev/play/p/SrljzVDpMQM
func HasPrefix[T comparable](collection, prefix []T) bool {
if len(collection) < len(prefix) {
return false
}
for i := range prefix {
if collection[i] != prefix[i] {
return false
}
}
return true
}
// HasSuffix returns true if the collection has the suffix.
// Play: https://go.dev/play/p/bJeLetQNAON
func HasSuffix[T comparable](collection, suffix []T) bool {
if len(collection) < len(suffix) {
return false
}
for i := range suffix {
if collection[len(collection)-len(suffix)+i] != suffix[i] {
return false
}
}
return true
}
// Find searches for an element in a slice based on a predicate. Returns element and true if element was found.
// Play: https://go.dev/play/p/Eo7W0lvKTky
func Find[T any](collection []T, predicate func(item T) bool) (T, bool) {
for i := range collection {
if predicate(collection[i]) {
@@ -46,8 +81,9 @@ func Find[T any](collection []T, predicate func(item T) bool) (T, bool) {
return result, false
}
// FindIndexOf searches an element in a slice based on a predicate and returns the index and true.
// It returns -1 and false if the element is not found.
// FindIndexOf searches for an element in a slice based on a predicate and returns the index and true.
// Returns -1 and false if the element is not found.
// Play: https://go.dev/play/p/XWSEM4Ic_t0
func FindIndexOf[T any](collection []T, predicate func(item T) bool) (T, int, bool) {
for i := range collection {
if predicate(collection[i]) {
@@ -59,8 +95,9 @@ func FindIndexOf[T any](collection []T, predicate func(item T) bool) (T, int, bo
return result, -1, false
}
// FindLastIndexOf searches last element in a slice based on a predicate and returns the index and true.
// It returns -1 and false if the element is not found.
// FindLastIndexOf searches for the last element in a slice based on a predicate and returns the index and true.
// Returns -1 and false if the element is not found.
// Play: https://go.dev/play/p/dPiMRtJ6cUx
func FindLastIndexOf[T any](collection []T, predicate func(item T) bool) (T, int, bool) {
length := len(collection)
@@ -74,7 +111,8 @@ func FindLastIndexOf[T any](collection []T, predicate func(item T) bool) (T, int
return result, -1, false
}
// FindOrElse search an element in a slice based on a predicate. It returns the element if found or a given fallback value otherwise.
// FindOrElse searches for an element in a slice based on a predicate. Returns the element if found or a given fallback value otherwise.
// Play: https://go.dev/play/p/Eo7W0lvKTky
func FindOrElse[T any](collection []T, fallback T, predicate func(item T) bool) T {
for i := range collection {
if predicate(collection[i]) {
@@ -86,9 +124,10 @@ func FindOrElse[T any](collection []T, fallback T, predicate func(item T) bool)
}
// 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 := range object {
if object[k] == value {
// Play: https://go.dev/play/p/Bg0w1VDPYXx
func FindKey[K, V comparable](object map[K]V, value V) (K, bool) {
for k, v := range object {
if v == value {
return k, true
}
}
@@ -96,10 +135,11 @@ func FindKey[K comparable, V comparable](object map[K]V, value V) (K, bool) {
return Empty[K](), false
}
// FindKeyBy returns the key of the first element predicate returns truthy for.
// FindKeyBy returns the key of the first element predicate returns true for.
// Play: https://go.dev/play/p/9IbiPElcyo8
func FindKeyBy[K comparable, V any](object map[K]V, predicate func(key K, value V) bool) (K, bool) {
for k := range object {
if predicate(k, object[k]) {
for k, v := range object {
if predicate(k, v) {
return k, true
}
}
@@ -107,7 +147,7 @@ func FindKeyBy[K comparable, V any](object map[K]V, predicate func(key K, value
return Empty[K](), false
}
// FindUniques returns a slice with all the unique elements of the collection.
// FindUniques returns a slice with all the elements that appear in the collection only once.
// The order of result values is determined by the order they occur in the collection.
func FindUniques[T comparable, Slice ~[]T](collection Slice) Slice {
isDupl := make(map[T]bool, len(collection))
@@ -132,9 +172,9 @@ func FindUniques[T comparable, Slice ~[]T](collection Slice) Slice {
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.
// FindUniquesBy returns a slice with all the elements that appear in the collection only once.
// The order of result values is determined by the order they occur in the slice. It accepts `iteratee` which is
// invoked for each element in the slice to generate the criterion by which uniqueness is computed.
func FindUniquesBy[T any, U comparable, Slice ~[]T](collection Slice, iteratee func(item T) U) Slice {
isDupl := make(map[U]bool, len(collection))
@@ -162,7 +202,7 @@ func FindUniquesBy[T any, U comparable, Slice ~[]T](collection Slice, iteratee f
return result
}
// FindDuplicates returns a slice with the first occurrence of each duplicated elements of the collection.
// FindDuplicates returns a slice with the first occurrence of each duplicated element in the collection.
// The order of result values is determined by the order they occur in the collection.
func FindDuplicates[T comparable, Slice ~[]T](collection Slice) Slice {
isDupl := make(map[T]bool, len(collection))
@@ -188,9 +228,9 @@ func FindDuplicates[T comparable, Slice ~[]T](collection Slice) Slice {
return result
}
// FindDuplicatesBy returns a slice with the first occurrence 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.
// FindDuplicatesBy returns a slice with the first occurrence of each duplicated element in the collection.
// The order of result values is determined by the order they occur in the slice. It accepts `iteratee` which is
// invoked for each element in the slice to generate the criterion by which uniqueness is computed.
func FindDuplicatesBy[T any, U comparable, Slice ~[]T](collection Slice, iteratee func(item T) U) Slice {
isDupl := make(map[U]bool, len(collection))
@@ -221,122 +261,123 @@ func FindDuplicatesBy[T any, U comparable, Slice ~[]T](collection Slice, iterate
// Min search the minimum value of a collection.
// Returns zero value when the collection is empty.
// Play: https://go.dev/play/p/r6e-Z8JozS8
func Min[T constraints.Ordered](collection []T) T {
var min T
var mIn T
if len(collection) == 0 {
return min
return mIn
}
min = collection[0]
mIn = collection[0]
for i := 1; i < len(collection); i++ {
item := collection[i]
if item < min {
min = item
if item < mIn {
mIn = item
}
}
return min
return mIn
}
// MinIndex search the minimum value of a collection and the index of the minimum value.
// Returns (zero value, -1) when the collection is empty.
func MinIndex[T constraints.Ordered](collection []T) (T, int) {
var (
min T
mIn T
index int
)
if len(collection) == 0 {
return min, -1
return mIn, -1
}
min = collection[0]
mIn = collection[0]
for i := 1; i < len(collection); i++ {
item := collection[i]
if item < min {
min = item
if item < mIn {
mIn = item
index = i
}
}
return min, index
return mIn, index
}
// MinBy search the minimum value of a collection using the given comparison function.
// If several values of the collection are equal to the smallest value, returns the first such value.
// Returns zero value when the collection is empty.
func MinBy[T any](collection []T, comparison func(a T, b T) bool) T {
var min T
func MinBy[T any](collection []T, comparison func(a, b T) bool) T {
var mIn T
if len(collection) == 0 {
return min
return mIn
}
min = collection[0]
mIn = collection[0]
for i := 1; i < len(collection); i++ {
item := collection[i]
if comparison(item, min) {
min = item
if comparison(item, mIn) {
mIn = item
}
}
return min
return mIn
}
// MinIndexBy search the minimum value of a collection using the given comparison function and the index of the minimum value.
// If several values of the collection are equal to the smallest value, returns the first such value.
// Returns (zero value, -1) when the collection is empty.
func MinIndexBy[T any](collection []T, comparison func(a T, b T) bool) (T, int) {
func MinIndexBy[T any](collection []T, comparison func(a, b T) bool) (T, int) {
var (
min T
mIn T
index int
)
if len(collection) == 0 {
return min, -1
return mIn, -1
}
min = collection[0]
mIn = collection[0]
for i := 1; i < len(collection); i++ {
item := collection[i]
if comparison(item, min) {
min = item
if comparison(item, mIn) {
mIn = item
index = i
}
}
return min, index
return mIn, index
}
// Earliest search the minimum time.Time of a collection.
// Returns zero value when the collection is empty.
func Earliest(times ...time.Time) time.Time {
var min time.Time
var mIn time.Time
if len(times) == 0 {
return min
return mIn
}
min = times[0]
mIn = times[0]
for i := 1; i < len(times); i++ {
item := times[i]
if item.Before(min) {
min = item
if item.Before(mIn) {
mIn = item
}
}
return min
return mIn
}
// EarliestBy search the minimum time.Time of a collection using the given iteratee function.
@@ -365,122 +406,123 @@ func EarliestBy[T any](collection []T, iteratee func(item T) time.Time) T {
// Max searches the maximum value of a collection.
// Returns zero value when the collection is empty.
// Play: https://go.dev/play/p/r6e-Z8JozS8
func Max[T constraints.Ordered](collection []T) T {
var max T
var mAx T
if len(collection) == 0 {
return max
return mAx
}
max = collection[0]
mAx = collection[0]
for i := 1; i < len(collection); i++ {
item := collection[i]
if item > max {
max = item
if item > mAx {
mAx = item
}
}
return max
return mAx
}
// MaxIndex searches the maximum value of a collection and the index of the maximum value.
// Returns (zero value, -1) when the collection is empty.
func MaxIndex[T constraints.Ordered](collection []T) (T, int) {
var (
max T
mAx T
index int
)
if len(collection) == 0 {
return max, -1
return mAx, -1
}
max = collection[0]
mAx = collection[0]
for i := 1; i < len(collection); i++ {
item := collection[i]
if item > max {
max = item
if item > mAx {
mAx = item
index = i
}
}
return max, index
return mAx, index
}
// MaxBy search the maximum value of a collection using the given comparison function.
// If several values of the collection are equal to the greatest value, returns the first such value.
// Returns zero value when the collection is empty.
func MaxBy[T any](collection []T, comparison func(a T, b T) bool) T {
var max T
func MaxBy[T any](collection []T, comparison func(a, b T) bool) T {
var mAx T
if len(collection) == 0 {
return max
return mAx
}
max = collection[0]
mAx = collection[0]
for i := 1; i < len(collection); i++ {
item := collection[i]
if comparison(item, max) {
max = item
if comparison(item, mAx) {
mAx = item
}
}
return max
return mAx
}
// MaxIndexBy search the maximum value of a collection using the given comparison function and the index of the maximum value.
// If several values of the collection are equal to the greatest value, returns the first such value.
// Returns (zero value, -1) when the collection is empty.
func MaxIndexBy[T any](collection []T, comparison func(a T, b T) bool) (T, int) {
func MaxIndexBy[T any](collection []T, comparison func(a, b T) bool) (T, int) {
var (
max T
mAx T
index int
)
if len(collection) == 0 {
return max, -1
return mAx, -1
}
max = collection[0]
mAx = collection[0]
for i := 1; i < len(collection); i++ {
item := collection[i]
if comparison(item, max) {
max = item
if comparison(item, mAx) {
mAx = item
index = i
}
}
return max, index
return mAx, index
}
// Latest search the maximum time.Time of a collection.
// Returns zero value when the collection is empty.
func Latest(times ...time.Time) time.Time {
var max time.Time
var mAx time.Time
if len(times) == 0 {
return max
return mAx
}
max = times[0]
mAx = times[0]
for i := 1; i < len(times); i++ {
item := times[i]
if item.After(max) {
max = item
if item.After(mAx) {
mAx = item
}
}
return max
return mAx
}
// LatestBy search the maximum time.Time of a collection using the given iteratee function.
@@ -508,6 +550,7 @@ func LatestBy[T any](collection []T, iteratee func(item T) time.Time) T {
}
// First returns the first element of a collection and check for availability of the first element.
// Play: https://go.dev/play/p/ul45Z0y2EFO
func First[T any](collection []T) (T, bool) {
length := len(collection)
@@ -520,12 +563,14 @@ func First[T any](collection []T) (T, bool) {
}
// FirstOrEmpty returns the first element of a collection or zero value if empty.
// Play: https://go.dev/play/p/ul45Z0y2EFO
func FirstOrEmpty[T any](collection []T) T {
i, _ := First(collection)
return i
}
// FirstOr returns the first element of a collection or the fallback value if empty.
// Play: https://go.dev/play/p/ul45Z0y2EFO
func FirstOr[T any](collection []T, fallback T) T {
i, ok := First(collection)
if !ok {
@@ -536,6 +581,7 @@ func FirstOr[T any](collection []T, fallback T) T {
}
// Last returns the last element of a collection or error if empty.
// Play: https://go.dev/play/p/ul45Z0y2EFO
func Last[T any](collection []T) (T, bool) {
length := len(collection)
@@ -548,12 +594,14 @@ func Last[T any](collection []T) (T, bool) {
}
// LastOrEmpty returns the last element of a collection or zero value if empty.
// Play: https://go.dev/play/p/ul45Z0y2EFO
func LastOrEmpty[T any](collection []T) T {
i, _ := Last(collection)
return i
}
// LastOr returns the last element of a collection or the fallback value if empty.
// Play: https://go.dev/play/p/ul45Z0y2EFO
func LastOr[T any](collection []T, fallback T) T {
i, ok := Last(collection)
if !ok {
@@ -565,6 +613,7 @@ func LastOr[T any](collection []T, fallback T) T {
// 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.
// Play: https://go.dev/play/p/sHoh88KWt6B
func Nth[T any, N constraints.Integer](collection []T, nth N) (T, error) {
n := int(nth)
l := len(collection)
@@ -582,6 +631,7 @@ func Nth[T any, N constraints.Integer](collection []T, nth N) (T, error) {
// NthOr returns the element at index `nth` of collection.
// If `nth` is negative, it returns the nth element from the end.
// If `nth` is out of slice bounds, it returns the fallback value instead of an error.
// Play: https://go.dev/play/p/sHoh88KWt6B
func NthOr[T any, N constraints.Integer](collection []T, nth N, fallback T) T {
value, err := Nth(collection, nth)
if err != nil {
@@ -593,6 +643,7 @@ func NthOr[T any, N constraints.Integer](collection []T, nth N, fallback T) T {
// NthOrEmpty returns the element at index `nth` of collection.
// If `nth` is negative, it returns the nth element from the end.
// If `nth` is out of slice bounds, it returns the zero value (empty value) for that type.
// Play: https://go.dev/play/p/sHoh88KWt6B
func NthOrEmpty[T any, N constraints.Integer](collection []T, nth N) T {
value, err := Nth(collection, nth)
if err != nil {
@@ -603,16 +654,18 @@ func NthOrEmpty[T any, N constraints.Integer](collection []T, nth N) T {
}
// randomIntGenerator is a function that should return a random integer in the range [0, n)
// where n is the parameter passed to the randomIntGenerator.
// where n is the argument passed to the randomIntGenerator.
type randomIntGenerator func(n int) int
// Sample returns a random item from collection.
// Play: https://go.dev/play/p/vCcSJbh5s6l
func Sample[T any](collection []T) T {
result := SampleBy(collection, rand.IntN)
result := SampleBy(collection, xrand.IntN)
return result
}
// SampleBy returns a random item from collection, using randomIntGenerator as the random index generator.
// Play: https://go.dev/play/p/HDmKmMgq0XN
func SampleBy[T any](collection []T, randomIntGenerator randomIntGenerator) T {
size := len(collection)
if size == 0 {
@@ -622,16 +675,18 @@ func SampleBy[T any](collection []T, randomIntGenerator randomIntGenerator) T {
}
// Samples returns N random unique items from collection.
// Play: https://go.dev/play/p/vCcSJbh5s6l
func Samples[T any, Slice ~[]T](collection Slice, count int) Slice {
results := SamplesBy(collection, count, rand.IntN)
results := SamplesBy(collection, count, xrand.IntN)
return results
}
// SamplesBy returns N random unique items from collection, using randomIntGenerator as the random index generator.
// Play: https://go.dev/play/p/HDmKmMgq0XN
func SamplesBy[T any, Slice ~[]T](collection Slice, count int, randomIntGenerator randomIntGenerator) Slice {
size := len(collection)
copy := append(Slice{}, collection...)
cOpy := append(Slice{}, collection...)
results := Slice{}
@@ -639,12 +694,12 @@ func SamplesBy[T any, Slice ~[]T](collection Slice, count int, randomIntGenerato
copyLength := size - i
index := randomIntGenerator(size - i)
results = append(results, copy[index])
results = append(results, cOpy[index])
// Removes element.
// It is faster to swap with last element and remove it.
copy[index] = copy[copyLength-1]
copy = copy[:copyLength-1]
cOpy[index] = cOpy[copyLength-1]
cOpy = cOpy[:copyLength-1]
}
return results

View File

@@ -1,6 +1,7 @@
package lo
// Partial returns new function that, when called, has its first argument set to the provided value.
// Play: https://go.dev/play/p/Sy1gAQiQZ3v
func Partial[T1, T2, R any](f func(a T1, b T2) R, arg1 T1) func(T2) R {
return func(t2 T2) R {
return f(arg1, t2)
@@ -8,11 +9,13 @@ func Partial[T1, T2, R any](f func(a T1, b T2) R, arg1 T1) func(T2) R {
}
// Partial1 returns new function that, when called, has its first argument set to the provided value.
// Play: https://go.dev/play/p/D-ASTXCLBzw
func Partial1[T1, T2, R any](f func(T1, T2) R, arg1 T1) func(T2) R {
return Partial(f, arg1)
}
// Partial2 returns new function that, when called, has its first argument set to the provided value.
// Play: https://go.dev/play/p/-xiPjy4JChJ
func Partial2[T1, T2, T3, R any](f func(T1, T2, T3) R, arg1 T1) func(T2, T3) R {
return func(t2 T2, t3 T3) R {
return f(arg1, t2, t3)
@@ -20,6 +23,7 @@ func Partial2[T1, T2, T3, R any](f func(T1, T2, T3) R, arg1 T1) func(T2, T3) R {
}
// Partial3 returns new function that, when called, has its first argument set to the provided value.
// Play: https://go.dev/play/p/zWtSutpI26m
func Partial3[T1, T2, T3, T4, R any](f func(T1, T2, T3, T4) R, arg1 T1) func(T2, T3, T4) R {
return func(t2 T2, t3 T3, t4 T4) R {
return f(arg1, t2, t3, t4)
@@ -27,13 +31,15 @@ func Partial3[T1, T2, T3, T4, R any](f func(T1, T2, T3, T4) R, arg1 T1) func(T2,
}
// Partial4 returns new function that, when called, has its first argument set to the provided value.
// Play: https://go.dev/play/p/kBrnnMTcJm0
func Partial4[T1, T2, T3, T4, T5, R any](f func(T1, T2, T3, T4, T5) R, arg1 T1) func(T2, T3, T4, T5) R {
return func(t2 T2, t3 T3, t4 T4, t5 T5) R {
return f(arg1, t2, t3, t4, t5)
}
}
// Partial5 returns new function that, when called, has its first argument set to the provided value
// Partial5 returns new function that, when called, has its first argument set to the provided value.
// Play: https://go.dev/play/p/7Is7K2y_VC3
func Partial5[T1, T2, T3, T4, T5, T6, R any](f func(T1, T2, T3, T4, T5, T6) R, arg1 T1) func(T2, T3, T4, T5, T6) R {
return func(t2 T2, t3 T3, t4 T4, t5 T5, t6 T6) R {
return f(arg1, t2, t3, t4, t5, t6)

View File

@@ -0,0 +1,4 @@
# Constraints
This package is for Go 1.18 retrocompatiblity purpose.

View File

@@ -6,4 +6,8 @@ import (
"cmp"
)
// Ordered is a constraint that permits any ordered type: any type
// that supports the operators < <= >= >.
// If future releases of Go add new ordered types,
// this constraint will be modified to include them.
type Ordered = cmp.Ordered

View File

@@ -1,17 +0,0 @@
//go:build go1.22
package rand
import "math/rand/v2"
func Shuffle(n int, swap func(i, j int)) {
rand.Shuffle(n, swap)
}
func IntN(n int) int {
return rand.IntN(n)
}
func Int64() int64 {
return rand.Int64()
}

View File

@@ -1,22 +1,28 @@
//go:build !go1.22
package rand
package xrand
import "math/rand"
// Shuffle returns a slice of shuffled values. Uses the Fisher-Yates shuffle algorithm.
func Shuffle(n int, swap func(i, j int)) {
rand.Shuffle(n, swap)
}
// IntN returns, as an int, a pseudo-random number in the half-open interval [0,n)
// from the default Source.
// It panics if n <= 0.
func IntN(n int) int {
// bearer:disable go_gosec_crypto_weak_random
return rand.Intn(n)
}
// Int64 returns a non-negative pseudo-random 63-bit integer as an int64
// from the default Source.
func Int64() int64 {
// bearer:disable go_gosec_crypto_weak_random
n := rand.Int63()
// bearer:disable go_gosec_crypto_weak_random
if rand.Intn(2) == 0 {
return -n

View File

@@ -0,0 +1,23 @@
//go:build go1.22
package xrand
import "math/rand/v2"
// Shuffle returns a slice of shuffled values. Uses the Fisher-Yates shuffle algorithm.
func Shuffle(n int, swap func(i, j int)) {
rand.Shuffle(n, swap)
}
// IntN returns, as an int, a pseudo-random number in the half-open interval [0,n)
// from the default Source.
// It panics if n <= 0.
func IntN(n int) int {
return rand.IntN(n)
}
// Int64 returns a non-negative pseudo-random 63-bit integer as an int64
// from the default Source.
func Int64() int64 {
return rand.Int64()
}

6
vendor/github.com/samber/lo/internal/xtime/README.md generated vendored Normal file
View File

@@ -0,0 +1,6 @@
# xtime
Lightweight mock for time package.
A dedicated package such as [jonboulle/clockwork](https://github.com/jonboulle/clockwork/) would be better, but I would rather limit dependencies for this package. `clockwork` does not support Go 1.18 anymore.

40
vendor/github.com/samber/lo/internal/xtime/fake.go generated vendored Normal file
View File

@@ -0,0 +1,40 @@
//nolint:revive
package xtime
import (
"time"
)
func NewFakeClock() *FakeClock {
return NewFakeClockAt(time.Now())
}
func NewFakeClockAt(t time.Time) *FakeClock {
return &FakeClock{
time: t,
}
}
type FakeClock struct {
_ noCopy
// Not protected by a mutex. If a warning is thrown in your tests,
// just disable parallel tests.
time time.Time
}
func (c *FakeClock) Now() time.Time {
return c.time
}
func (c *FakeClock) Since(t time.Time) time.Duration {
return c.time.Sub(t)
}
func (c *FakeClock) Until(t time.Time) time.Duration {
return t.Sub(c.time)
}
func (c *FakeClock) Sleep(d time.Duration) {
c.time = c.time.Add(d)
}

14
vendor/github.com/samber/lo/internal/xtime/noCopy.go generated vendored Normal file
View File

@@ -0,0 +1,14 @@
package xtime
// noCopy may be added to structs which must not be copied
// after the first use.
//
// See https://golang.org/issues/8005#issuecomment-190753527
// for details.
//
// Note that it must not be embedded, due to the Lock and Unlock methods.
type noCopy struct{}
// Lock is a no-op used by -copylocks checker from `go vet`.
func (*noCopy) Lock() {}
func (*noCopy) Unlock() {}

30
vendor/github.com/samber/lo/internal/xtime/real.go generated vendored Normal file
View File

@@ -0,0 +1,30 @@
//nolint:revive
package xtime
import (
"time"
)
func NewRealClock() *RealClock {
return &RealClock{}
}
type RealClock struct {
_ noCopy
}
func (c *RealClock) Now() time.Time {
return time.Now()
}
func (c *RealClock) Since(t time.Time) time.Duration {
return time.Since(t)
}
func (c *RealClock) Until(t time.Time) time.Duration {
return time.Until(t)
}
func (c *RealClock) Sleep(d time.Duration) {
time.Sleep(d)
}

33
vendor/github.com/samber/lo/internal/xtime/time.go generated vendored Normal file
View File

@@ -0,0 +1,33 @@
//nolint:revive
package xtime
import "time"
var clock Clock = &RealClock{}
func SetClock(c Clock) {
clock = c
}
func Now() time.Time {
return clock.Now()
}
func Since(t time.Time) time.Duration {
return clock.Since(t)
}
func Until(t time.Time) time.Duration {
return clock.Until(t)
}
func Sleep(d time.Duration) {
clock.Sleep(d)
}
type Clock interface {
Now() time.Time
Since(t time.Time) time.Duration
Until(t time.Time) time.Duration
Sleep(d time.Duration)
}

View File

@@ -1,6 +1,7 @@
package lo
// Contains returns true if an element is present in a collection.
// Play: https://go.dev/play/p/W1EvyqY6t9j
func Contains[T comparable](collection []T, element T) bool {
for i := range collection {
if collection[i] == element {
@@ -12,6 +13,7 @@ func Contains[T comparable](collection []T, element T) bool {
}
// ContainsBy returns true if predicate function return true.
// Play: https://go.dev/play/p/W1EvyqY6t9j
func ContainsBy[T any](collection []T, predicate func(item T) bool) bool {
for i := range collection {
if predicate(collection[i]) {
@@ -22,8 +24,9 @@ func ContainsBy[T any](collection []T, predicate func(item T) bool) bool {
return false
}
// Every returns true if all elements of a subset are contained into a collection or if the subset is empty.
func Every[T comparable](collection []T, subset []T) bool {
// Every returns true if all elements of a subset are contained in a collection or if the subset is empty.
// Play: https://go.dev/play/p/W1EvyqY6t9j
func Every[T comparable](collection, subset []T) bool {
for i := range subset {
if !Contains(collection, subset[i]) {
return false
@@ -34,6 +37,7 @@ func Every[T comparable](collection []T, subset []T) bool {
}
// EveryBy returns true if the predicate returns true for all elements in the collection or if the collection is empty.
// Play: https://go.dev/play/p/dn1-vhHsq9x
func EveryBy[T any](collection []T, predicate func(item T) bool) bool {
for i := range collection {
if !predicate(collection[i]) {
@@ -44,9 +48,10 @@ func EveryBy[T any](collection []T, predicate func(item T) bool) bool {
return true
}
// Some returns true if at least 1 element of a subset is contained into a collection.
// Some returns true if at least 1 element of a subset is contained in a collection.
// If the subset is empty Some returns false.
func Some[T comparable](collection []T, subset []T) bool {
// Play: https://go.dev/play/p/Lj4ceFkeT9V
func Some[T comparable](collection, subset []T) bool {
for i := range subset {
if Contains(collection, subset[i]) {
return true
@@ -58,6 +63,7 @@ func Some[T comparable](collection []T, subset []T) bool {
// SomeBy returns true if the predicate returns true for any of the elements in the collection.
// If the collection is empty SomeBy returns false.
// Play: https://go.dev/play/p/DXF-TORBudx
func SomeBy[T any](collection []T, predicate func(item T) bool) bool {
for i := range collection {
if predicate(collection[i]) {
@@ -68,8 +74,9 @@ func SomeBy[T any](collection []T, predicate func(item T) bool) bool {
return false
}
// None returns true if no element of a subset are contained into a collection or if the subset is empty.
func None[T comparable](collection []T, subset []T) bool {
// None returns true if no element of a subset is contained in a collection or if the subset is empty.
// Play: https://go.dev/play/p/fye7JsmxzPV
func None[T comparable](collection, subset []T) bool {
for i := range subset {
if Contains(collection, subset[i]) {
return false
@@ -80,6 +87,7 @@ func None[T comparable](collection []T, subset []T) bool {
}
// NoneBy returns true if the predicate returns true for none of the elements in the collection or if the collection is empty.
// Play: https://go.dev/play/p/O64WZ32H58S
func NoneBy[T any](collection []T, predicate func(item T) bool) bool {
for i := range collection {
if predicate(collection[i]) {
@@ -91,7 +99,8 @@ func NoneBy[T any](collection []T, predicate func(item T) bool) bool {
}
// Intersect returns the intersection between two collections.
func Intersect[T comparable, Slice ~[]T](list1 Slice, list2 Slice) Slice {
// Play: https://go.dev/play/p/uuElL9X9e58
func Intersect[T comparable, Slice ~[]T](list1, list2 Slice) Slice {
result := Slice{}
seen := map[T]struct{}{}
@@ -109,9 +118,10 @@ func Intersect[T comparable, Slice ~[]T](list1 Slice, list2 Slice) Slice {
}
// Difference returns the difference between two collections.
// The first value is the collection of element absent of list2.
// The second value is the collection of element absent of list1.
func Difference[T comparable, Slice ~[]T](list1 Slice, list2 Slice) (Slice, Slice) {
// The first value is the collection of elements absent from list2.
// The second value is the collection of elements absent from list1.
// Play: https://go.dev/play/p/pKE-JgzqRpz
func Difference[T comparable, Slice ~[]T](list1, list2 Slice) (Slice, Slice) {
left := Slice{}
right := Slice{}
@@ -143,6 +153,7 @@ func Difference[T comparable, Slice ~[]T](list1 Slice, list2 Slice) (Slice, Slic
// Union returns all distinct elements from given collections.
// result returns will not change the order of elements relatively.
// Play: https://go.dev/play/p/DI9RVEB_qMK
func Union[T comparable, Slice ~[]T](lists ...Slice) Slice {
var capLen int
@@ -165,7 +176,8 @@ func Union[T comparable, Slice ~[]T](lists ...Slice) Slice {
return result
}
// Without returns slice excluding all given values.
// Without returns a slice excluding all given values.
// Play: https://go.dev/play/p/5j30Ux8TaD0
func Without[T comparable, Slice ~[]T](collection Slice, exclude ...T) Slice {
excludeMap := make(map[T]struct{}, len(exclude))
for i := range exclude {
@@ -182,14 +194,15 @@ func Without[T comparable, Slice ~[]T](collection Slice, exclude ...T) Slice {
}
// WithoutBy filters a slice by excluding elements whose extracted keys match any in the exclude list.
// It returns a new slice containing only the elements whose keys are not in the exclude list.
func WithoutBy[T any, K comparable](collection []T, iteratee func(item T) K, exclude ...K) []T {
// Returns a new slice containing only the elements whose keys are not in the exclude list.
// Play: https://go.dev/play/p/VgWJOF01NbJ
func WithoutBy[T any, K comparable, Slice ~[]T](collection Slice, iteratee func(item T) K, exclude ...K) Slice {
excludeMap := make(map[K]struct{}, len(exclude))
for _, e := range exclude {
excludeMap[e] = struct{}{}
}
result := make([]T, 0, len(collection))
result := make(Slice, 0, len(collection))
for _, item := range collection {
if _, ok := excludeMap[iteratee(item)]; !ok {
result = append(result, item)
@@ -198,14 +211,15 @@ func WithoutBy[T any, K comparable](collection []T, iteratee func(item T) K, exc
return result
}
// WithoutEmpty returns slice excluding zero values.
// WithoutEmpty returns a slice excluding zero values.
//
// Deprecated: Use lo.Compact instead.
func WithoutEmpty[T comparable, Slice ~[]T](collection Slice) Slice {
return Compact(collection)
}
// WithoutNth returns slice excluding nth value.
// WithoutNth returns a slice excluding the nth value.
// Play: https://go.dev/play/p/5g3F9R2H1xL
func WithoutNth[T comparable, Slice ~[]T](collection Slice, nths ...int) Slice {
length := len(collection)
@@ -227,16 +241,18 @@ func WithoutNth[T comparable, Slice ~[]T](collection Slice, nths ...int) Slice {
}
// ElementsMatch returns true if lists contain the same set of elements (including empty set).
// If there are duplicate elements, the number of appearances of each of them in both lists should match.
// If there are duplicate elements, the number of occurrences in each list should match.
// The order of elements is not checked.
func ElementsMatch[T comparable, Slice ~[]T](list1 Slice, list2 Slice) bool {
// Play: https://go.dev/play/p/XWSEM4Ic_t0
func ElementsMatch[T comparable, Slice ~[]T](list1, list2 Slice) bool {
return ElementsMatchBy(list1, list2, func(item T) T { return item })
}
// ElementsMatchBy returns true if lists contain the same set of elements' keys (including empty set).
// If there are duplicate keys, the number of appearances of each of them in both lists should match.
// If there are duplicate keys, the number of occurrences in each list should match.
// The order of elements is not checked.
func ElementsMatchBy[T any, K comparable](list1 []T, list2 []T, iteratee func(item T) K) bool {
// Play: https://go.dev/play/p/XWSEM4Ic_t0
func ElementsMatchBy[T any, K comparable](list1, list2 []T, iteratee func(item T) K) bool {
if len(list1) != len(list2) {
return false
}

148
vendor/github.com/samber/lo/map.go generated vendored
View File

@@ -1,6 +1,6 @@
package lo
// Keys creates an array of the map keys.
// Keys creates a slice of the map keys.
// Play: https://go.dev/play/p/Uu11fHASqrU
func Keys[K comparable, V any](in ...map[K]V) []K {
size := 0
@@ -18,7 +18,7 @@ func Keys[K comparable, V any](in ...map[K]V) []K {
return result
}
// UniqKeys creates an array of unique keys in the map.
// UniqKeys creates a slice of unique keys in the map.
// Play: https://go.dev/play/p/TPKAb6ILdHk
func UniqKeys[K comparable, V any](in ...map[K]V) []K {
size := 0
@@ -49,7 +49,7 @@ func HasKey[K comparable, V any](in map[K]V, key K) bool {
return ok
}
// Values creates an array of the map values.
// Values creates a slice of the map values.
// Play: https://go.dev/play/p/nnRTQkzQfF6
func Values[K comparable, V any](in ...map[K]V) []V {
size := 0
@@ -59,17 +59,17 @@ func Values[K comparable, V any](in ...map[K]V) []V {
result := make([]V, 0, size)
for i := range in {
for k := range in[i] {
result = append(result, in[i][k])
for _, v := range in[i] {
result = append(result, v)
}
}
return result
}
// UniqValues creates an array of unique values in the map.
// UniqValues creates a slice of unique values in the map.
// Play: https://go.dev/play/p/nf6bXMh7rM3
func UniqValues[K comparable, V comparable](in ...map[K]V) []V {
func UniqValues[K, V comparable](in ...map[K]V) []V {
size := 0
for i := range in {
size += len(in[i])
@@ -79,13 +79,12 @@ func UniqValues[K comparable, V comparable](in ...map[K]V) []V {
result := make([]V, 0)
for i := range in {
for k := range in[i] {
val := in[i][k]
if _, exists := seen[val]; exists {
for _, v := range in[i] {
if _, exists := seen[v]; exists {
continue
}
seen[val] = struct{}{}
result = append(result, val)
seen[v] = struct{}{}
result = append(result, v)
}
}
@@ -105,9 +104,9 @@ func ValueOr[K comparable, V any](in map[K]V, key K, fallback V) V {
// Play: https://go.dev/play/p/kdg8GR_QMmf
func PickBy[K comparable, V any, Map ~map[K]V](in Map, predicate func(key K, value V) bool) Map {
r := Map{}
for k := range in {
if predicate(k, in[k]) {
r[k] = in[k]
for k, v := range in {
if predicate(k, v) {
r[k] = v
}
}
return r
@@ -127,11 +126,11 @@ func PickByKeys[K comparable, V any, Map ~map[K]V](in Map, keys []K) Map {
// PickByValues returns same map type filtered by given values.
// Play: https://go.dev/play/p/1zdzSvbfsJc
func PickByValues[K comparable, V comparable, Map ~map[K]V](in Map, values []V) Map {
func PickByValues[K, V comparable, Map ~map[K]V](in Map, values []V) Map {
r := Map{}
for k := range in {
if Contains(values, in[k]) {
r[k] = in[k]
for k, v := range in {
if Contains(values, v) {
r[k] = v
}
}
return r
@@ -141,9 +140,9 @@ func PickByValues[K comparable, V comparable, Map ~map[K]V](in Map, values []V)
// Play: https://go.dev/play/p/EtBsR43bdsd
func OmitBy[K comparable, V any, Map ~map[K]V](in Map, predicate func(key K, value V) bool) Map {
r := Map{}
for k := range in {
if !predicate(k, in[k]) {
r[k] = in[k]
for k, v := range in {
if !predicate(k, v) {
r[k] = v
}
}
return r
@@ -153,8 +152,8 @@ func OmitBy[K comparable, V any, Map ~map[K]V](in Map, predicate func(key K, val
// Play: https://go.dev/play/p/t1QjCrs-ysk
func OmitByKeys[K comparable, V any, Map ~map[K]V](in Map, keys []K) Map {
r := Map{}
for k := range in {
r[k] = in[k]
for k, v := range in {
r[k] = v
}
for i := range keys {
delete(r, keys[i])
@@ -164,39 +163,39 @@ func OmitByKeys[K comparable, V any, Map ~map[K]V](in Map, keys []K) Map {
// OmitByValues returns same map type filtered by given values.
// Play: https://go.dev/play/p/9UYZi-hrs8j
func OmitByValues[K comparable, V comparable, Map ~map[K]V](in Map, values []V) Map {
func OmitByValues[K, V comparable, Map ~map[K]V](in Map, values []V) Map {
r := Map{}
for k := range in {
if !Contains(values, in[k]) {
r[k] = in[k]
for k, v := range in {
if !Contains(values, v) {
r[k] = v
}
}
return r
}
// Entries transforms a map into array of key/value pairs.
// Play:
// Entries transforms a map into a slice of key/value pairs.
// Play: https://go.dev/play/p/_t4Xe34-Nl5
func Entries[K comparable, V any](in map[K]V) []Entry[K, V] {
entries := make([]Entry[K, V], 0, len(in))
for k := range in {
for k, v := range in {
entries = append(entries, Entry[K, V]{
Key: k,
Value: in[k],
Value: v,
})
}
return entries
}
// ToPairs transforms a map into array of key/value pairs.
// ToPairs transforms a map into a slice 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.
// FromEntries transforms a slice 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 := make(map[K]V, len(entries))
@@ -208,7 +207,7 @@ 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.
// FromPairs transforms a slice 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 {
@@ -219,11 +218,11 @@ func FromPairs[K comparable, V any](entries []Entry[K, V]) map[K]V {
// 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 {
func Invert[K, V comparable](in map[K]V) map[V]K {
out := make(map[V]K, len(in))
for k := range in {
out[in[k]] = k
for k, v := range in {
out[v] = k
}
return out
@@ -239,20 +238,20 @@ func Assign[K comparable, V any, Map ~map[K]V](maps ...Map) Map {
out := make(Map, count)
for i := range maps {
for k := range maps[i] {
out[k] = maps[i][k]
for k, v := range maps[i] {
out[k] = v
}
}
return out
}
// ChunkEntries splits a map into an array of elements in groups of a length equal to its size. If the map cannot be split evenly,
// ChunkEntries splits a map into a slice of elements in groups of length equal to its size. If the map cannot be split evenly,
// the final chunk will contain the remaining elements.
// Play: https://go.dev/play/p/X_YQL6mmoD-
func ChunkEntries[K comparable, V any](m map[K]V, size int) []map[K]V {
if size <= 0 {
panic("The chunk size must be greater than 0")
panic("lo.ChunkEntries: size must be greater than 0")
}
count := len(m)
@@ -262,7 +261,7 @@ func ChunkEntries[K comparable, V any](m map[K]V, size int) []map[K]V {
chunksNum := count / size
if count%size != 0 {
chunksNum += 1
chunksNum++
}
result := make([]map[K]V, 0, chunksNum)
@@ -278,31 +277,31 @@ func ChunkEntries[K comparable, V any](m map[K]V, size int) []map[K]V {
return result
}
// MapKeys manipulates a map keys and transforms it to a map of another type.
// MapKeys manipulates 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(value V, key K) R) map[R]V {
result := make(map[R]V, len(in))
for k := range in {
result[iteratee(in[k], k)] = in[k]
for k, v := range in {
result[iteratee(v, k)] = v
}
return result
}
// MapValues manipulates a map values and transforms it to a map of another type.
// MapValues manipulates 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(value V, key K) R) map[K]R {
func MapValues[K comparable, V, R any](in map[K]V, iteratee func(value V, key K) R) map[K]R {
result := make(map[K]R, len(in))
for k := range in {
result[k] = iteratee(in[k], k)
for k, v := range in {
result[k] = iteratee(v, k)
}
return result
}
// MapEntries manipulates a map entries and transforms it to a map of another type.
// MapEntries manipulates map entries and transforms it to a map of another type.
// Play: https://go.dev/play/p/VuvNQzxKimT
func MapEntries[K1 comparable, V1 any, K2 comparable, V2 any](in map[K1]V1, iteratee func(key K1, value V1) (K2, V2)) map[K2]V2 {
result := make(map[K2]V2, len(in))
@@ -315,27 +314,58 @@ func MapEntries[K1 comparable, V1 any, K2 comparable, V2 any](in map[K1]V1, iter
return result
}
// MapToSlice transforms a map into a slice based on specific iteratee
// MapToSlice transforms a map into a slice based on specified iteratee.
// Play: https://go.dev/play/p/ZuiCZpDt6LD
func MapToSlice[K comparable, V any, R any](in map[K]V, iteratee func(key K, value V) R) []R {
func MapToSlice[K comparable, V, R any](in map[K]V, iteratee func(key K, value V) R) []R {
result := make([]R, 0, len(in))
for k := range in {
result = append(result, iteratee(k, in[k]))
for k, v := range in {
result = append(result, iteratee(k, v))
}
return result
}
// FilterMapToSlice transforms a map into a slice based on specific iteratee.
// FilterMapToSlice transforms a map into a slice based on specified iteratee.
// The iteratee returns a value and a boolean. If the boolean is true, the value is added to the result slice.
// If the boolean is false, the value is not added to the result slice.
// The order of the keys in the input map is not specified and the order of the keys in the output slice is not guaranteed.
func FilterMapToSlice[K comparable, V any, R any](in map[K]V, iteratee func(key K, value V) (R, bool)) []R {
// Play: https://go.dev/play/p/jgsD_Kil9pV
func FilterMapToSlice[K comparable, V, R any](in map[K]V, iteratee func(key K, value V) (R, bool)) []R {
result := make([]R, 0, len(in))
for k := range in {
if v, ok := iteratee(k, in[k]); ok {
for k, v := range in {
if v, ok := iteratee(k, v); ok {
result = append(result, v)
}
}
return result
}
// FilterKeys transforms a map into a slice based on predicate returns true for specific elements.
// It is a mix of lo.Filter() and lo.Keys().
// Play: https://go.dev/play/p/OFlKXlPrBAe
func FilterKeys[K comparable, V any](in map[K]V, predicate func(key K, value V) bool) []K {
result := make([]K, 0)
for k, v := range in {
if predicate(k, v) {
result = append(result, k)
}
}
return result
}
// FilterValues transforms a map into a slice based on predicate returns true for specific elements.
// It is a mix of lo.Filter() and lo.Values().
// Play: https://go.dev/play/p/YVD5r_h-LX-
func FilterValues[K comparable, V any](in map[K]V, predicate func(key K, value V) bool) []V {
result := make([]V, 0)
for k, v := range in {
if predicate(k, v) {
result = append(result, v)
}
}

68
vendor/github.com/samber/lo/math.go generated vendored
View File

@@ -4,7 +4,7 @@ import (
"github.com/samber/lo/internal/constraints"
)
// Range creates an array of numbers (positive and/or negative) with given length.
// Range creates a slice 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)
@@ -16,7 +16,7 @@ func Range(elementNum int) []int {
return result
}
// RangeFrom creates an array of numbers from start with specified length.
// RangeFrom creates a slice 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)
@@ -28,8 +28,8 @@ func RangeFrom[T constraints.Integer | constraints.Float](start T, elementNum in
return result
}
// 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.
// RangeWithSteps creates a slice of numbers (positive and/or negative) progressing from start up to, but not including end.
// step set to zero will return an empty slice.
// Play: https://go.dev/play/p/0r6VimXAi9H
func RangeWithSteps[T constraints.Integer | constraints.Float](start, end, step T) []T {
result := []T{}
@@ -56,11 +56,11 @@ 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
} else if value > max {
return max
func Clamp[T constraints.Ordered](value, mIn, mAx T) T {
if value < mIn {
return mIn
} else if value > mAx {
return mAx
}
return value
}
@@ -68,7 +68,7 @@ func Clamp[T constraints.Ordered](value T, min T, max T) T {
// Sum sums the values in a collection. If collection is empty 0 is returned.
// Play: https://go.dev/play/p/upfeJVqs4Bt
func Sum[T constraints.Float | constraints.Integer | constraints.Complex](collection []T) T {
var sum T = 0
var sum T
for i := range collection {
sum += collection[i]
}
@@ -78,14 +78,14 @@ func Sum[T constraints.Float | constraints.Integer | constraints.Complex](collec
// 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 | constraints.Complex](collection []T, iteratee func(item T) R) R {
var sum R = 0
var sum R
for i := range collection {
sum = sum + iteratee(collection[i])
sum += iteratee(collection[i])
}
return sum
}
// Product gets the product of the values in a collection. If collection is empty 0 is returned.
// Product gets the product of the values in a collection. If collection is empty 1 is returned.
// Play: https://go.dev/play/p/2_kjM_smtAH
func Product[T constraints.Float | constraints.Integer | constraints.Complex](collection []T) T {
if collection == nil {
@@ -103,7 +103,7 @@ func Product[T constraints.Float | constraints.Integer | constraints.Complex](co
return product
}
// ProductBy summarizes the values in a collection using the given return value from the iteration function. If collection is empty 0 is returned.
// ProductBy summarizes the values in a collection using the given return value from the iteration function. If collection is empty 1 is returned.
// Play: https://go.dev/play/p/wadzrWr9Aer
func ProductBy[T any, R constraints.Float | constraints.Integer | constraints.Complex](collection []T, iteratee func(item T) R) R {
if collection == nil {
@@ -116,27 +116,57 @@ func ProductBy[T any, R constraints.Float | constraints.Integer | constraints.Co
var product R = 1
for i := range collection {
product = product * iteratee(collection[i])
product *= iteratee(collection[i])
}
return product
}
// Mean calculates the mean of a collection of numbers.
// Play: https://go.dev/play/p/tPURSuteUsP
func Mean[T constraints.Float | constraints.Integer](collection []T) T {
var length = T(len(collection))
length := T(len(collection))
if length == 0 {
return 0
}
var sum = Sum(collection)
sum := Sum(collection)
return sum / length
}
// MeanBy calculates the mean of a collection of numbers using the given return value from the iteration function.
// Play: https://go.dev/play/p/j7TsVwBOZ7P
func MeanBy[T any, R constraints.Float | constraints.Integer](collection []T, iteratee func(item T) R) R {
var length = R(len(collection))
length := R(len(collection))
if length == 0 {
return 0
}
var sum = SumBy(collection, iteratee)
sum := SumBy(collection, iteratee)
return sum / length
}
// Mode returns the mode (most frequent value) of a collection.
// If multiple values have the same highest frequency, then multiple values are returned.
// If the collection is empty, then the zero value of T is returned.
func Mode[T constraints.Integer | constraints.Float](collection []T) []T {
length := T(len(collection))
if length == 0 {
return []T{}
}
mode := make([]T, 0)
maxFreq := 0
frequency := make(map[T]int)
for _, item := range collection {
frequency[item]++
count := frequency[item]
if count > maxFreq {
maxFreq = count
mode = []T{item}
} else if count == maxFreq {
mode = append(mode, item)
}
}
return mode
}

View File

@@ -1,12 +1,13 @@
package mutable
import "github.com/samber/lo/internal/rand"
import "github.com/samber/lo/internal/xrand"
// Filter is a generic function that modifies the input slice in-place to contain only the elements
// that satisfy the provided predicate function. The predicate function takes an element of the slice and its index,
// and should return true for elements that should be kept and false for elements that should be removed.
// The function returns the modified slice, which may be shorter than the original if some elements were removed.
// Note that the order of elements in the original slice is preserved in the output.
// Play: https://go.dev/play/p/0jY3Z0B7O_5
func Filter[T any, Slice ~[]T](collection Slice, predicate func(item T) bool) Slice {
j := 0
for _, item := range collection {
@@ -36,6 +37,7 @@ func FilterI[T any, Slice ~[]T](collection Slice, predicate func(item T, index i
// Map is a generic function that modifies the input slice in-place to contain the result of applying the provided
// function to each element of the slice. The function returns the modified slice, which has the same length as the original.
// Play: https://go.dev/play/p/0jY3Z0B7O_5
func Map[T any, Slice ~[]T](collection Slice, fn func(item T) T) {
for i := range collection {
collection[i] = fn(collection[i])
@@ -50,21 +52,21 @@ func MapI[T any, Slice ~[]T](collection Slice, fn func(item T, index int) T) {
}
}
// Shuffle returns an array of shuffled values. Uses the Fisher-Yates shuffle algorithm.
// Shuffle returns a slice of shuffled values. Uses the Fisher-Yates shuffle algorithm.
// Play: https://go.dev/play/p/2xb3WdLjeSJ
func Shuffle[T any, Slice ~[]T](collection Slice) {
rand.Shuffle(len(collection), func(i, j int) {
xrand.Shuffle(len(collection), func(i, j int) {
collection[i], collection[j] = collection[j], collection[i]
})
}
// Reverse reverses array so that the first element becomes the last, the second element becomes the second to last, and so on.
// Reverse reverses a slice 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/O-M5pmCRgzV
func Reverse[T any, Slice ~[]T](collection Slice) {
length := len(collection)
half := length / 2
for i := 0; i < half; i = i + 1 {
for i := 0; i < half; i++ {
j := length - 1 - i
collection[i], collection[j] = collection[j], collection[i]
}

63
vendor/github.com/samber/lo/retry.go generated vendored
View File

@@ -3,6 +3,8 @@ package lo
import (
"sync"
"time"
"github.com/samber/lo/internal/xtime"
)
type debounce struct {
@@ -26,8 +28,13 @@ func (d *debounce) reset() {
}
d.timer = time.AfterFunc(d.after, func() {
for i := range d.callbacks {
d.callbacks[i]()
// We need to lock the mutex here to avoid race conditions with 2 concurrent calls to reset()
d.mu.Lock()
callbacks := append([]func(){}, d.callbacks...)
d.mu.Unlock()
for i := range callbacks {
callbacks[i]()
}
})
}
@@ -96,13 +103,15 @@ func (d *debounceBy[T]) reset(key T) {
}
item.timer = time.AfterFunc(d.after, func() {
// We need to lock the mutex here to avoid race conditions with 2 concurrent calls to reset()
item.mu.Lock()
count := item.count
item.count = 0
callbacks := append([]func(key T, count int){}, d.callbacks...)
item.mu.Unlock()
for i := range d.callbacks {
d.callbacks[i](key, count)
for i := range callbacks {
callbacks[i](key, count)
}
})
}
@@ -165,20 +174,20 @@ func Attempt(maxIteration int, f func(index int) error) (int, error) {
func AttemptWithDelay(maxIteration int, delay time.Duration, f func(index int, duration time.Duration) error) (int, time.Duration, error) {
var err error
start := time.Now()
start := xtime.Now()
for i := 0; maxIteration <= 0 || i < maxIteration; i++ {
err = f(i, time.Since(start))
err = f(i, xtime.Since(start))
if err == nil {
return i + 1, time.Since(start), nil
return i + 1, xtime.Since(start), nil
}
if maxIteration <= 0 || i+1 < maxIteration {
time.Sleep(delay)
xtime.Sleep(delay)
}
}
return maxIteration, time.Since(start), err
return maxIteration, xtime.Since(start), err
}
// AttemptWhile invokes a function N times until it returns valid output.
@@ -187,6 +196,7 @@ func AttemptWithDelay(maxIteration int, delay time.Duration, f func(index int, d
// immediately if the second return value is false. When the first
// argument is less than `1`, the function runs until a successful response is
// returned.
// Play: https://go.dev/play/p/1VS7HxlYMOG
func AttemptWhile(maxIteration int, f func(int) (error, bool)) (int, error) {
var err error
var shouldContinueInvoke bool
@@ -211,27 +221,28 @@ func AttemptWhile(maxIteration int, f func(int) (error, bool)) (int, error) {
// It will terminate the invoke immediately if the second return value is false.
// When the first argument is less than `1`, the function runs until a successful
// response is returned.
// Play: https://go.dev/play/p/mhufUjJfLEF
func AttemptWhileWithDelay(maxIteration int, delay time.Duration, f func(int, time.Duration) (error, bool)) (int, time.Duration, error) {
var err error
var shouldContinueInvoke bool
start := time.Now()
start := xtime.Now()
for i := 0; maxIteration <= 0 || i < maxIteration; i++ {
err, shouldContinueInvoke = f(i, time.Since(start))
err, shouldContinueInvoke = f(i, xtime.Since(start))
if !shouldContinueInvoke { // if shouldContinueInvoke is false, then return immediately
return i + 1, time.Since(start), err
return i + 1, xtime.Since(start), err
}
if err == nil {
return i + 1, time.Since(start), nil
return i + 1, xtime.Since(start), nil
}
if maxIteration <= 0 || i+1 < maxIteration {
time.Sleep(delay)
xtime.Sleep(delay)
}
}
return maxIteration, time.Since(start), err
return maxIteration, xtime.Since(start), err
}
type transactionStep[T any] struct {
@@ -240,18 +251,20 @@ type transactionStep[T any] struct {
}
// NewTransaction instantiate a new transaction.
// Play: https://go.dev/play/p/Qxrd7MGQGh1
func NewTransaction[T any]() *Transaction[T] {
return &Transaction[T]{
steps: []transactionStep[T]{},
}
}
// Transaction implements a Saga pattern
// Transaction implements a Saga pattern.
type Transaction[T any] struct {
steps []transactionStep[T]
}
// Then adds a step to the chain of callbacks. It returns the same Transaction.
// Then adds a step to the chain of callbacks. Returns the same Transaction.
// Play: https://go.dev/play/p/Qxrd7MGQGh1 https://go.dev/play/p/xrHb2_kMvTY
func (t *Transaction[T]) Then(exec func(T) (T, error), onRollback func(T) T) *Transaction[T] {
t.steps = append(t.steps, transactionStep[T]{
exec: exec,
@@ -262,6 +275,7 @@ func (t *Transaction[T]) Then(exec func(T) (T, error), onRollback func(T) T) *Tr
}
// Process runs the Transaction steps and rollbacks in case of errors.
// Play: https://go.dev/play/p/Qxrd7MGQGh1 https://go.dev/play/p/xrHb2_kMvTY
func (t *Transaction[T]) Process(state T) (T, error) {
var i int
var err error
@@ -287,7 +301,7 @@ func (t *Transaction[T]) Process(state T) (T, error) {
return state, err
}
// @TODO: single mutex per key ?
// @TODO: single mutex per key?
type throttleBy[T comparable] struct {
mu *sync.Mutex
timer *time.Timer
@@ -311,7 +325,6 @@ func (th *throttleBy[T]) throttledFunc(key T) {
for _, f := range th.callbacks {
f(key)
}
}
if th.timer == nil {
th.timer = time.AfterFunc(th.interval, func() {
@@ -333,13 +346,15 @@ func (th *throttleBy[T]) reset() {
}
// NewThrottle creates a throttled instance that invokes given functions only once in every interval.
// This returns 2 functions, First one is throttled function and Second one is a function to reset interval
func NewThrottle(interval time.Duration, f ...func()) (throttle func(), reset func()) {
// This returns 2 functions, First one is throttled function and Second one is a function to reset interval.
// Play: https://go.dev/play/p/qQn3fm8Z7jS
func NewThrottle(interval time.Duration, f ...func()) (throttle, reset func()) {
return NewThrottleWithCount(interval, 1, f...)
}
// NewThrottleWithCount is NewThrottle with count limit, throttled function will be invoked count times in every interval.
func NewThrottleWithCount(interval time.Duration, count int, f ...func()) (throttle func(), reset func()) {
// Play: https://go.dev/play/p/w5nc0MgWtjC
func NewThrottleWithCount(interval time.Duration, count int, f ...func()) (throttle, reset func()) {
callbacks := Map(f, func(item func(), _ int) func(struct{}) {
return func(struct{}) {
item()
@@ -353,12 +368,14 @@ func NewThrottleWithCount(interval time.Duration, count int, f ...func()) (throt
}
// NewThrottleBy creates a throttled instance that invokes given functions only once in every interval.
// This returns 2 functions, First one is throttled function and Second one is a function to reset interval
// This returns 2 functions, First one is throttled function and Second one is a function to reset interval.
// Play: https://go.dev/play/p/0Wv6oX7dHdC
func NewThrottleBy[T comparable](interval time.Duration, f ...func(key T)) (throttle func(key T), reset func()) {
return NewThrottleByWithCount[T](interval, 1, f...)
}
// NewThrottleByWithCount is NewThrottleBy with count limit, throttled function will be invoked count times in every interval.
// Play: https://go.dev/play/p/vQk3ECH7_EW
func NewThrottleByWithCount[T comparable](interval time.Duration, count int, f ...func(key T)) (throttle func(key T), reset func()) {
if count <= 0 {
count = 1

339
vendor/github.com/samber/lo/slice.go generated vendored
View File

@@ -7,7 +7,7 @@ import (
"github.com/samber/lo/mutable"
)
// Filter iterates over elements of collection, returning an array of all elements predicate returns truthy for.
// Filter iterates over elements of collection, returning a slice of all elements predicate returns true for.
// Play: https://go.dev/play/p/Apjg3WeSi7K
func Filter[T any, Slice ~[]T](collection Slice, predicate func(item T, index int) bool) Slice {
result := make(Slice, 0, len(collection))
@@ -23,7 +23,7 @@ func Filter[T any, Slice ~[]T](collection Slice, predicate func(item T, index in
// 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(item T, index int) R) []R {
func Map[T, R any](collection []T, iteratee func(item T, index int) R) []R {
result := make([]R, len(collection))
for i := range collection {
@@ -34,6 +34,7 @@ func Map[T any, R any](collection []T, iteratee func(item T, index int) R) []R {
}
// UniqMap manipulates a slice and transforms it to a slice of another type with unique values.
// Play: https://go.dev/play/p/fygzLBhvUdB
func UniqMap[T any, R comparable](collection []T, iteratee func(item T, index int) R) []R {
result := make([]R, 0, len(collection))
seen := make(map[R]struct{}, len(collection))
@@ -48,13 +49,13 @@ func UniqMap[T any, R comparable](collection []T, iteratee func(item T, index in
return result
}
// FilterMap returns a slice which obtained after both filtering and mapping using the given callback function.
// FilterMap returns a slice obtained after both filtering and mapping using the given callback function.
// 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(item T, index int) (R, bool)) []R {
// Play: https://go.dev/play/p/CgHYNUpOd1I
func FilterMap[T, R any](collection []T, callback func(item T, index int) (R, bool)) []R {
result := make([]R, 0, len(collection))
for i := range collection {
@@ -69,8 +70,8 @@ func FilterMap[T any, R any](collection []T, callback func(item T, index int) (R
// FlatMap manipulates a slice and transforms and flattens it to a slice of another type.
// The transform function can either return a slice or a `nil`, and in the `nil` case
// no value is added to the final slice.
// Play: https://go.dev/play/p/YSoYmQTA8-U
func FlatMap[T any, R any](collection []T, iteratee func(item T, index int) []R) []R {
// Play: https://go.dev/play/p/pFCF5WVB225
func FlatMap[T, R any](collection []T, iteratee func(item T, index int) []R) []R {
result := make([]R, 0, len(collection))
for i := range collection {
@@ -82,8 +83,8 @@ func FlatMap[T any, R any](collection []T, iteratee func(item T, index int) []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(agg R, item T, index int) R, initial R) R {
// Play: https://go.dev/play/p/CgHYNUpOd1I
func Reduce[T, R any](collection []T, accumulator func(agg R, item T, index int) R, initial R) R {
for i := range collection {
initial = accumulator(initial, collection[i], i)
}
@@ -91,9 +92,9 @@ func Reduce[T any, R any](collection []T, accumulator func(agg R, item T, index
return initial
}
// ReduceRight helper is like Reduce except that it iterates over elements of collection from right to left.
// ReduceRight 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(agg R, item T, index int) R, initial R) R {
func ReduceRight[T, R any](collection []T, accumulator func(agg R, item T, index int) R, initial R) R {
for i := len(collection) - 1; i >= 0; i-- {
initial = accumulator(initial, collection[i], i)
}
@@ -112,7 +113,7 @@ func ForEach[T any](collection []T, iteratee func(item T, index int)) {
// ForEachWhile iterates over elements of collection and invokes iteratee for each element
// collection return value decide to continue or break, like do while().
// Play: https://go.dev/play/p/QnLGt35tnow
func ForEachWhile[T any](collection []T, iteratee func(item T, index int) (goon bool)) {
func ForEachWhile[T any](collection []T, iteratee func(item T, index int) bool) {
for i := range collection {
if !iteratee(collection[i], i) {
break
@@ -120,7 +121,7 @@ func ForEachWhile[T any](collection []T, iteratee func(item T, index int) (goon
}
}
// Times invokes the iteratee n times, returning an array of the results of each invocation.
// Times invokes the iteratee n times, returning a slice 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(index int) T) []T {
@@ -133,8 +134,8 @@ func Times[T any](count int, iteratee func(index int) T) []T {
return result
}
// 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.
// Uniq returns a duplicate-free version of a slice, 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 slice.
// Play: https://go.dev/play/p/DTzbeXZ6iEN
func Uniq[T comparable, Slice ~[]T](collection Slice) Slice {
result := make(Slice, 0, len(collection))
@@ -152,9 +153,9 @@ func Uniq[T comparable, Slice ~[]T](collection Slice) Slice {
return result
}
// 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.
// UniqBy returns a duplicate-free version of a slice, 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 slice. It accepts `iteratee` which is
// invoked for each element in the slice to generate the criterion by which uniqueness is computed.
// Play: https://go.dev/play/p/g42Z3QSb53u
func UniqBy[T any, U comparable, Slice ~[]T](collection Slice, iteratee func(item T) U) Slice {
result := make(Slice, 0, len(collection))
@@ -189,6 +190,7 @@ func GroupBy[T any, U comparable, Slice ~[]T](collection Slice, iteratee func(it
}
// GroupByMap returns an object composed of keys generated from the results of running each element of collection through iteratee.
// Play: https://go.dev/play/p/iMeruQ3_W80
func GroupByMap[T any, K comparable, V any](collection []T, iteratee func(item T) (K, V)) map[K][]V {
result := map[K][]V{}
@@ -201,17 +203,17 @@ func GroupByMap[T any, K comparable, V any](collection []T, iteratee func(item T
return result
}
// Chunk returns an array of elements split into groups the length of size. If array can't be split evenly,
// Chunk returns a slice of elements split into groups of length size. If the slice can't be split evenly,
// the final chunk will be the remaining elements.
// Play: https://go.dev/play/p/EeKl0AuTehH
// Play: https://go.dev/play/p/kEMkFbdu85g
func Chunk[T any, Slice ~[]T](collection Slice, size int) []Slice {
if size <= 0 {
panic("Second parameter must be greater than 0")
panic("lo.Chunk: size must be greater than 0")
}
chunksNum := len(collection) / size
if len(collection)%size != 0 {
chunksNum += 1
chunksNum++
}
result := make([]Slice, 0, chunksNum)
@@ -221,13 +223,17 @@ func Chunk[T any, Slice ~[]T](collection Slice, size int) []Slice {
if last > len(collection) {
last = len(collection)
}
result = append(result, collection[i*size:last:last])
// Copy chunk in a new slice, to prevent memory leak and free memory from initial collection.
newSlice := make(Slice, last-i*size)
copy(newSlice, collection[i*size:last])
result = append(result, newSlice)
}
return result
}
// PartitionBy returns an array of elements split into groups. The order of grouped values is
// PartitionBy returns a slice 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
@@ -255,7 +261,7 @@ func PartitionBy[T any, K comparable, Slice ~[]T](collection Slice, iteratee fun
// return Values[K, []T](groups)
}
// Flatten returns an array a single level deep.
// Flatten returns a slice a single level deep.
// Play: https://go.dev/play/p/rbp9ORaMpjw
func Flatten[T any, Slice ~[]T](collection []Slice) Slice {
totalLen := 0
@@ -271,7 +277,7 @@ func Flatten[T any, Slice ~[]T](collection []Slice) Slice {
return result
}
// Interleave round-robin alternating input slices and sequentially appending value at index into result
// Interleave round-robin alternating input slices and sequentially appending value at index into result.
// Play: https://go.dev/play/p/-RJkTLQEDVt
func Interleave[T any, Slice ~[]T](collections ...Slice) Slice {
if len(collections) == 0 {
@@ -309,7 +315,7 @@ func Interleave[T any, Slice ~[]T](collections ...Slice) Slice {
return result
}
// Shuffle returns an array of shuffled values. Uses the Fisher-Yates shuffle algorithm.
// Shuffle returns a slice of shuffled values. Uses the Fisher-Yates shuffle algorithm.
// Play: https://go.dev/play/p/ZTGG7OUCdnp
//
// Deprecated: use mutable.Shuffle() instead.
@@ -318,7 +324,7 @@ func Shuffle[T any, Slice ~[]T](collection Slice) Slice {
return collection
}
// Reverse reverses array so that the first element becomes the last, the second element becomes the second to last, and so on.
// Reverse reverses a slice 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/iv2e9jslfBM
//
// Deprecated: use mutable.Reverse() instead.
@@ -327,7 +333,7 @@ func Reverse[T any, Slice ~[]T](collection Slice) Slice {
return collection
}
// Fill fills elements of array with `initial` value.
// Fill fills elements of a slice with `initial` value.
// Play: https://go.dev/play/p/VwR34GzqEub
func Fill[T Clonable[T], Slice ~[]T](collection Slice, initial T) Slice {
result := make(Slice, 0, len(collection))
@@ -363,8 +369,8 @@ func RepeatBy[T any](count int, predicate func(index int) T) []T {
return result
}
// 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
// KeyBy transforms a slice or a slice of structs to a map based on a pivot callback.
// Play: https://go.dev/play/p/ccUiUL_Lnel
func KeyBy[K comparable, V any](collection []V, iteratee func(item V) K) map[K]V {
result := make(map[K]V, len(collection))
@@ -377,14 +383,24 @@ func KeyBy[K comparable, V any](collection []V, iteratee func(item V) K) map[K]V
}
// 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.
// If any of two pairs 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 slice.
// Play: https://go.dev/play/p/WHa2CfMO3Lr
func Associate[T any, K comparable, V any](collection []T, transform func(item T) (K, V)) map[K]V {
return AssociateI(collection, func(item T, _ int) (K, V) {
return transform(item)
})
}
// AssociateI returns a map containing key-value pairs provided by transform function applied to elements of the given slice.
// If any of two pairs 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 slice.
// Play: https://go.dev/play/p/Ugmz6S22rRO
func AssociateI[T any, K comparable, V any](collection []T, transform func(item T, index int) (K, V)) map[K]V {
result := make(map[K]V, len(collection))
for i := range collection {
k, v := transform(collection[i])
for index, item := range collection {
k, v := transform(item, index)
result[k] = v
}
@@ -392,23 +408,44 @@ func Associate[T any, K comparable, V any](collection []T, transform func(item T
}
// 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.
// If any of two pairs 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 slice.
// Alias of Associate().
// Play: https://go.dev/play/p/WHa2CfMO3Lr
func SliceToMap[T any, K comparable, V any](collection []T, transform func(item T) (K, V)) map[K]V {
return Associate(collection, transform)
}
// SliceToMapI returns a map containing key-value pairs provided by transform function applied to elements of the given slice.
// If any of two pairs 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 slice.
// Alias of AssociateI().
// Play: https://go.dev/play/p/mMBm5GV3_eq
func SliceToMapI[T any, K comparable, V any](collection []T, transform func(item T, index int) (K, V)) map[K]V {
return AssociateI(collection, transform)
}
// FilterSliceToMap 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.
// If any of two pairs 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 slice.
// The third return value of the transform function is a boolean that indicates whether the key-value pair should be included in the map.
// Play: https://go.dev/play/p/2z0rDz2ZSGU
func FilterSliceToMap[T any, K comparable, V any](collection []T, transform func(item T) (K, V, bool)) map[K]V {
return FilterSliceToMapI(collection, func(item T, _ int) (K, V, bool) {
return transform(item)
})
}
// FilterSliceToMapI returns a map containing key-value pairs provided by transform function applied to elements of the given slice.
// If any of two pairs 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 slice.
// The third return value of the transform function is a boolean that indicates whether the key-value pair should be included in the map.
// Play: https://go.dev/play/p/mSz_bUIk9aJ
func FilterSliceToMapI[T any, K comparable, V any](collection []T, transform func(item T, index int) (K, V, bool)) map[K]V {
result := make(map[K]V, len(collection))
for i := range collection {
k, v, ok := transform(collection[i])
for index, item := range collection {
k, v, ok := transform(item, index)
if ok {
result[k] = v
}
@@ -418,6 +455,7 @@ func FilterSliceToMap[T any, K comparable, V any](collection []T, transform func
}
// Keyify returns a map with each unique element of the slice as a key.
// Play: https://go.dev/play/p/RYhhM_csqIG
func Keyify[T comparable, Slice ~[]T](collection Slice) map[T]struct{} {
result := make(map[T]struct{}, len(collection))
@@ -428,9 +466,13 @@ func Keyify[T comparable, Slice ~[]T](collection Slice) map[T]struct{} {
return result
}
// Drop drops n elements from the beginning of a slice or array.
// Drop drops n elements from the beginning of a slice.
// Play: https://go.dev/play/p/JswS7vXRJP2
func Drop[T any, Slice ~[]T](collection Slice, n int) Slice {
if n < 0 {
panic("lo.Drop: n must not be negative")
}
if len(collection) <= n {
return make(Slice, 0)
}
@@ -440,9 +482,13 @@ func Drop[T any, Slice ~[]T](collection Slice, n int) Slice {
return append(result, collection[n:]...)
}
// DropRight drops n elements from the end of a slice or array.
// DropRight drops n elements from the end of a slice.
// Play: https://go.dev/play/p/GG0nXkSJJa3
func DropRight[T any, Slice ~[]T](collection Slice, n int) Slice {
if n < 0 {
panic("lo.DropRight: n must not be negative")
}
if len(collection) <= n {
return Slice{}
}
@@ -451,7 +497,7 @@ func DropRight[T any, Slice ~[]T](collection Slice, n int) Slice {
return append(result, collection[:len(collection)-n]...)
}
// DropWhile drops elements from the beginning of a slice or array while the predicate returns true.
// DropWhile drops elements from the beginning of a slice while the predicate returns true.
// Play: https://go.dev/play/p/7gBPYw2IK16
func DropWhile[T any, Slice ~[]T](collection Slice, predicate func(item T) bool) Slice {
i := 0
@@ -465,7 +511,7 @@ func DropWhile[T any, Slice ~[]T](collection Slice, predicate func(item T) bool)
return append(result, collection[i:]...)
}
// DropRightWhile drops elements from the end of a slice or array while the predicate returns true.
// DropRightWhile drops elements from the end of a slice while the predicate returns true.
// Play: https://go.dev/play/p/3-n71oEC0Hz
func DropRightWhile[T any, Slice ~[]T](collection Slice, predicate func(item T) bool) Slice {
i := len(collection) - 1
@@ -479,13 +525,13 @@ func DropRightWhile[T any, Slice ~[]T](collection Slice, predicate func(item T)
return append(result, collection[:i+1]...)
}
// DropByIndex drops elements from a slice or array by the index.
// DropByIndex drops elements from a slice by the index.
// A negative index will drop elements from the end of the slice.
// Play: https://go.dev/play/p/bPIH4npZRxS
func DropByIndex[T any](collection []T, indexes ...int) []T {
func DropByIndex[T any, Slice ~[]T](collection Slice, indexes ...int) Slice {
initialSize := len(collection)
if initialSize == 0 {
return make([]T, 0)
return make(Slice, 0)
}
for i := range indexes {
@@ -497,7 +543,7 @@ func DropByIndex[T any](collection []T, indexes ...int) []T {
indexes = Uniq(indexes)
sort.Ints(indexes)
result := make([]T, 0, initialSize)
result := make(Slice, 0, initialSize)
result = append(result, collection...)
for i := range indexes {
@@ -511,8 +557,8 @@ func DropByIndex[T any](collection []T, indexes ...int) []T {
return result
}
// 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
// Reject is the opposite of Filter, this method returns the elements of collection that predicate does not return true for.
// Play: https://go.dev/play/p/pFCF5WVB225
func Reject[T any, Slice ~[]T](collection Slice, predicate func(item T, index int) bool) Slice {
result := Slice{}
@@ -525,11 +571,13 @@ func Reject[T any, Slice ~[]T](collection Slice, predicate func(item T, index in
return result
}
// RejectMap is the opposite of FilterMap, this method returns a slice which obtained after both filtering and mapping using the given callback function.
// RejectMap is the opposite of FilterMap, this method returns a slice obtained after both filtering and mapping using the given callback function.
// The callback function should return two values:
// - the result of the mapping operation and
// - whether the result element should be included or not.
func RejectMap[T any, R any](collection []T, callback func(item T, index int) (R, bool)) []R {
//
// Play: https://go.dev/play/p/W9Ug9r0QFkL
func RejectMap[T, R any](collection []T, callback func(item T, index int) (R, bool)) []R {
result := []R{}
for i := range collection {
@@ -542,8 +590,9 @@ func RejectMap[T any, R any](collection []T, callback func(item T, index int) (R
}
// FilterReject mixes Filter and Reject, this method returns two slices, one for the elements of collection that
// predicate returns truthy for and one for the elements that predicate does not return truthy for.
func FilterReject[T any, Slice ~[]T](collection Slice, predicate func(T, int) bool) (kept Slice, rejected Slice) {
// predicate returns true for and one for the elements that predicate does not return true for.
// Play: https://go.dev/play/p/lHSEGSznJjB
func FilterReject[T any, Slice ~[]T](collection Slice, predicate func(T, int) bool) (kept, rejected Slice) {
kept = make(Slice, 0, len(collection))
rejected = make(Slice, 0, len(collection))
@@ -558,9 +607,11 @@ func FilterReject[T any, Slice ~[]T](collection Slice, predicate func(T, int) bo
return kept, rejected
}
// Count counts the number of elements in the collection that compare equal to value.
// Count counts the number of elements in the collection that equal value.
// Play: https://go.dev/play/p/Y3FlK54yveC
func Count[T comparable](collection []T, value T) (count int) {
func Count[T comparable](collection []T, value T) int {
var count int
for i := range collection {
if collection[i] == value {
count++
@@ -572,7 +623,9 @@ 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(item T) bool) (count int) {
func CountBy[T any](collection []T, predicate func(item T) bool) int {
var count int
for i := range collection {
if predicate(collection[i]) {
count++
@@ -594,7 +647,7 @@ func CountValues[T comparable](collection []T) map[T]int {
return result
}
// CountValuesBy counts the number of each element return from mapper function.
// CountValuesBy counts the number of each element returned from mapper function.
// Is equivalent to chaining lo.Map and lo.CountValues.
// Play: https://go.dev/play/p/2U0dG1SnOmS
func CountValuesBy[T any, U comparable](collection []T, mapper func(item T) U) map[U]int {
@@ -632,7 +685,7 @@ func Subset[T any, Slice ~[]T](collection Slice, offset int, length uint) Slice
// 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, Slice ~[]T](collection Slice, start int, end int) Slice {
func Slice[T any, Slice ~[]T](collection Slice, start, end int) Slice {
size := len(collection)
if start >= end {
@@ -658,13 +711,13 @@ func Slice[T any, Slice ~[]T](collection Slice, start int, end int) Slice {
// 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, Slice ~[]T](collection Slice, old T, new T, n int) Slice {
func Replace[T comparable, Slice ~[]T](collection Slice, old, nEw T, n int) Slice {
result := make(Slice, len(collection))
copy(result, collection)
for i := range result {
if result[i] == old && n != 0 {
result[i] = new
result[i] = nEw
n--
}
}
@@ -674,8 +727,8 @@ func Replace[T comparable, Slice ~[]T](collection Slice, old T, new T, n int) Sl
// 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, Slice ~[]T](collection Slice, old T, new T) Slice {
return Replace(collection, old, new, -1)
func ReplaceAll[T comparable, Slice ~[]T](collection Slice, old, nEw T) Slice {
return Replace(collection, old, nEw, -1)
}
// Compact returns a slice of all non-zero elements.
@@ -707,7 +760,6 @@ func IsSorted[T constraints.Ordered](collection []T) bool {
}
// 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(item T) K) bool {
size := len(collection)
@@ -728,18 +780,167 @@ func Splice[T any, Slice ~[]T](collection Slice, i int, elements ...T) Slice {
sizeElements := len(elements)
output := make(Slice, 0, sizeCollection+sizeElements) // preallocate memory for the output slice
if sizeElements == 0 {
switch {
case sizeElements == 0:
return append(output, collection...) // simple copy
} else if i > sizeCollection {
case i > sizeCollection:
// positive overflow
return append(append(output, collection...), elements...)
} else if i < -sizeCollection {
case i < -sizeCollection:
// negative overflow
return append(append(output, elements...), collection...)
} else if i < 0 {
case i < 0:
// backward
i = sizeCollection + i
}
return append(append(append(output, collection[:i]...), elements...), collection[i:]...)
}
// Cut slices collection around the first instance of separator, returning the part of collection
// before and after separator. The found result reports whether separator appears in collection.
// If separator does not appear in s, cut returns collection, empty slice of []T, false.
// Play: https://go.dev/play/p/GiL3qhpIP3f
func Cut[T comparable, Slice ~[]T](collection, separator Slice) (before, after Slice, found bool) {
if len(separator) == 0 {
return make(Slice, 0), collection, true
}
for i := 0; i+len(separator) <= len(collection); i++ {
match := true
for j := 0; j < len(separator); j++ {
if collection[i+j] != separator[j] {
match = false
break
}
}
if match {
return collection[:i], collection[i+len(separator):], true
}
}
return collection, make(Slice, 0), false
}
// CutPrefix returns collection without the provided leading prefix []T
// and reports whether it found the prefix.
// If s doesn't start with prefix, CutPrefix returns collection, false.
// If prefix is the empty []T, CutPrefix returns collection, true.
// Play: https://go.dev/play/p/7Plak4a1ICl
func CutPrefix[T comparable, Slice ~[]T](collection, separator Slice) (after Slice, found bool) {
if len(separator) == 0 {
return collection, true
}
if len(separator) > len(collection) {
return collection, false
}
for i := range separator {
if collection[i] != separator[i] {
return collection, false
}
}
return collection[len(separator):], true
}
// CutSuffix returns collection without the provided ending suffix []T and reports
// whether it found the suffix. If s doesn't end with suffix, CutSuffix returns collection, false.
// If suffix is the empty []T, CutSuffix returns collection, true.
// Play: https://go.dev/play/p/7FKfBFvPTaT
func CutSuffix[T comparable, Slice ~[]T](collection, separator Slice) (before Slice, found bool) {
if len(separator) == 0 {
return collection, true
}
if len(separator) > len(collection) {
return collection, false
}
start := len(collection) - len(separator)
for i := range separator {
if collection[start+i] != separator[i] {
return collection, false
}
}
return collection[:start], true
}
// Trim removes all the leading and trailing cutset from the collection.
// Play: https://go.dev/play/p/1an9mxLdRG5
func Trim[T comparable, Slice ~[]T](collection, cutset Slice) Slice {
set := Keyify(cutset)
i := 0
for ; i < len(collection); i++ {
if _, ok := set[collection[i]]; !ok {
break
}
}
if i >= len(collection) {
return Slice{}
}
j := len(collection) - 1
for ; j >= 0; j-- {
if _, ok := set[collection[j]]; !ok {
break
}
}
result := make(Slice, 0, j+1-i)
return append(result, collection[i:j+1]...)
}
// TrimLeft removes all the leading cutset from the collection.
// Play: https://go.dev/play/p/74aqfAYLmyi
func TrimLeft[T comparable, Slice ~[]T](collection, cutset Slice) Slice {
set := Keyify(cutset)
return DropWhile(collection, func(item T) bool {
_, ok := set[item]
return ok
})
}
// TrimPrefix removes all the leading prefix from the collection.
// Play: https://go.dev/play/p/SHO6X-YegPg
func TrimPrefix[T comparable, Slice ~[]T](collection, prefix Slice) Slice {
if len(prefix) == 0 {
return collection
}
for {
if !HasPrefix(collection, prefix) {
return collection
}
collection = collection[len(prefix):]
}
}
// TrimRight removes all the trailing cutset from the collection.
// Play: https://go.dev/play/p/MRpAfR6sf0g
func TrimRight[T comparable, Slice ~[]T](collection, cutset Slice) Slice {
set := Keyify(cutset)
return DropRightWhile(collection, func(item T) bool {
_, ok := set[item]
return ok
})
}
// TrimSuffix removes all the trailing suffix from the collection.
// Play: https://go.dev/play/p/IjEUrV0iofq
func TrimSuffix[T comparable, Slice ~[]T](collection, suffix Slice) Slice {
if len(suffix) == 0 {
return collection
}
for {
if !HasSuffix(collection, suffix) {
return collection
}
collection = collection[:len(collection)-len(suffix)]
}
}

View File

@@ -7,13 +7,14 @@ import (
"unicode"
"unicode/utf8"
"github.com/samber/lo/internal/rand"
"github.com/samber/lo/internal/xrand"
"golang.org/x/text/cases"
"golang.org/x/text/language"
)
var (
//nolint:revive
LowerCaseLettersCharset = []rune("abcdefghijklmnopqrstuvwxyz")
UpperCaseLettersCharset = []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
LettersCharset = append(LowerCaseLettersCharset, UpperCaseLettersCharset...)
@@ -33,38 +34,49 @@ var (
// Play: https://go.dev/play/p/rRseOQVVum4
func RandomString(size int, charset []rune) string {
if size <= 0 {
panic("lo.RandomString: Size parameter must be greater than 0")
panic("lo.RandomString: size must be greater than 0")
}
if len(charset) <= 0 {
panic("lo.RandomString: Charset parameter must not be empty")
if len(charset) == 0 {
panic("lo.RandomString: charset must not be empty")
}
// see https://stackoverflow.com/questions/22892120/how-to-generate-a-random-string-of-a-fixed-length-in-go
sb := strings.Builder{}
sb.Grow(size)
if len(charset) == 1 {
// Edge case, because if the charset is a single character,
// it will panic below (divide by zero).
// -> https://github.com/samber/lo/issues/679
for i := 0; i < size; i++ {
sb.WriteRune(charset[0])
}
return sb.String()
}
// Calculate the number of bits required to represent the charset,
// e.g., for 62 characters, it would need 6 bits (since 62 -> 64 = 2^6)
letterIdBits := int(math.Log2(float64(nearestPowerOfTwo(len(charset)))))
letterIDBits := int(math.Log2(float64(nearestPowerOfTwo(len(charset)))))
// Determine the corresponding bitmask,
// e.g., for 62 characters, the bitmask would be 111111.
var letterIdMask int64 = 1<<letterIdBits - 1
// Available count, since rand.Int64() returns a non-negative number, the first bit is fixed, so there are 63 random bits
var letterIDMask int64 = 1<<letterIDBits - 1
// Available count, since xrand.Int64() returns a non-negative number, the first bit is fixed, so there are 63 random bits
// e.g., for 62 characters, this value is 10 (63 / 6).
letterIdMax := 63 / letterIdBits
letterIDMax := 63 / letterIDBits
// Generate the random string in a loop.
for i, cache, remain := size-1, rand.Int64(), letterIdMax; i >= 0; {
for i, cache, remain := size-1, xrand.Int64(), letterIDMax; i >= 0; {
// Regenerate the random number if all available bits have been used
if remain == 0 {
cache, remain = rand.Int64(), letterIdMax
cache, remain = xrand.Int64(), letterIDMax
}
// Select a character from the charset
if idx := int(cache & letterIdMask); idx < len(charset) {
if idx := int(cache & letterIDMask); idx < len(charset) {
sb.WriteRune(charset[idx])
i--
}
// Shift the bits to the right to prepare for the next character selection,
// e.g., for 62 characters, shift by 6 bits.
cache >>= letterIdBits
cache >>= letterIDBits
// Decrease the remaining number of uses for the current random number.
remain--
}
@@ -72,8 +84,8 @@ func RandomString(size int, charset []rune) string {
}
// nearestPowerOfTwo returns the nearest power of two.
func nearestPowerOfTwo(cap int) int {
n := cap - 1
func nearestPowerOfTwo(capacity int) int {
n := capacity - 1
n |= n >> 1
n |= n >> 2
n |= n >> 4
@@ -112,12 +124,12 @@ func Substring[T ~string](str T, offset int, length uint) T {
return T(strings.ReplaceAll(string(rs[offset:offset+int(length)]), "\x00", ""))
}
// 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.
// ChunkString returns a slice of strings split into groups of length size. If the string can't be split evenly,
// the final chunk will be the remaining characters.
// 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")
panic("lo.ChunkString: size must be greater than 0")
}
if len(str) == 0 {
@@ -128,7 +140,7 @@ func ChunkString[T ~string](str T, size int) []T {
return []T{str}
}
var chunks = make([]T, 0, ((len(str)-1)/size)+1)
chunks := make([]T, 0, ((len(str)-1)/size)+1)
currentLen := 0
currentStart := 0
for i := range str {
@@ -150,6 +162,7 @@ func RuneLength(str string) int {
}
// PascalCase converts string to pascal case.
// Play: https://go.dev/play/p/Dy_V_6DUYhe
func PascalCase(str string) string {
items := Words(str)
for i := range items {
@@ -159,6 +172,7 @@ func PascalCase(str string) string {
}
// CamelCase converts string to camel case.
// Play: https://go.dev/play/p/Go6aKwUiq59
func CamelCase(str string) string {
items := Words(str)
for i, item := range items {
@@ -172,6 +186,7 @@ func CamelCase(str string) string {
}
// KebabCase converts string to kebab case.
// Play: https://go.dev/play/p/96gT_WZnTVP
func KebabCase(str string) string {
items := Words(str)
for i := range items {
@@ -181,6 +196,7 @@ func KebabCase(str string) string {
}
// SnakeCase converts string to snake case.
// Play: https://go.dev/play/p/ziB0V89IeVH
func SnakeCase(str string) string {
items := Words(str)
for i := range items {
@@ -189,7 +205,8 @@ func SnakeCase(str string) string {
return strings.Join(items, "_")
}
// Words splits string into an array of its words.
// Words splits string into a slice of its words.
// Play: https://go.dev/play/p/-f3VIQqiaVw
func Words(str string) []string {
str = splitWordReg.ReplaceAllString(str, `$1$3$5$7 $2$4$6$8$9`)
// example: Int8Value => Int 8Value => Int 8 Value
@@ -206,6 +223,7 @@ func Words(str string) []string {
}
// Capitalize converts the first character of string to upper case and the remaining to lower case.
// Play: https://go.dev/play/p/uLTZZQXqnsa
func Capitalize(str string) string {
return cases.Title(language.English).String(str)
}
@@ -213,6 +231,7 @@ func Capitalize(str string) string {
// Ellipsis trims and truncates a string to a specified length **in bytes** and appends an ellipsis
// if truncated. If the string contains non-ASCII characters (which may occupy multiple bytes in UTF-8),
// truncating by byte length may split a character in the middle, potentially resulting in garbled output.
// Play: https://go.dev/play/p/qE93rgqe1TW
func Ellipsis(str string, length int) string {
str = strings.TrimSpace(str)

16
vendor/github.com/samber/lo/time.go generated vendored
View File

@@ -1,13 +1,17 @@
package lo
import "time"
import (
"time"
)
// Duration returns the time taken to execute a function.
// Play: https://go.dev/play/p/HQfbBbAXaFP
func Duration(cb func()) time.Duration {
return Duration0(cb)
}
// Duration0 returns the time taken to execute a function.
// Play: https://go.dev/play/p/HQfbBbAXaFP
func Duration0(cb func()) time.Duration {
start := time.Now()
cb()
@@ -15,6 +19,7 @@ func Duration0(cb func()) time.Duration {
}
// Duration1 returns the time taken to execute a function.
// Play: https://go.dev/play/p/HQfbBbAXaFP
func Duration1[A any](cb func() A) (A, time.Duration) {
start := time.Now()
a := cb()
@@ -22,6 +27,7 @@ func Duration1[A any](cb func() A) (A, time.Duration) {
}
// Duration2 returns the time taken to execute a function.
// Play: https://go.dev/play/p/HQfbBbAXaFP
func Duration2[A, B any](cb func() (A, B)) (A, B, time.Duration) {
start := time.Now()
a, b := cb()
@@ -29,6 +35,7 @@ func Duration2[A, B any](cb func() (A, B)) (A, B, time.Duration) {
}
// Duration3 returns the time taken to execute a function.
// Play: https://go.dev/play/p/xr863iwkAxQ
func Duration3[A, B, C any](cb func() (A, B, C)) (A, B, C, time.Duration) {
start := time.Now()
a, b, c := cb()
@@ -36,6 +43,7 @@ func Duration3[A, B, C any](cb func() (A, B, C)) (A, B, C, time.Duration) {
}
// Duration4 returns the time taken to execute a function.
// Play: https://go.dev/play/p/xr863iwkAxQ
func Duration4[A, B, C, D any](cb func() (A, B, C, D)) (A, B, C, D, time.Duration) {
start := time.Now()
a, b, c, d := cb()
@@ -43,6 +51,7 @@ func Duration4[A, B, C, D any](cb func() (A, B, C, D)) (A, B, C, D, time.Duratio
}
// Duration5 returns the time taken to execute a function.
// Play: https://go.dev/play/p/xr863iwkAxQ
func Duration5[A, B, C, D, E any](cb func() (A, B, C, D, E)) (A, B, C, D, E, time.Duration) {
start := time.Now()
a, b, c, d, e := cb()
@@ -50,6 +59,7 @@ func Duration5[A, B, C, D, E any](cb func() (A, B, C, D, E)) (A, B, C, D, E, tim
}
// Duration6 returns the time taken to execute a function.
// Play: https://go.dev/play/p/mR4bTQKO-Tf
func Duration6[A, B, C, D, E, F any](cb func() (A, B, C, D, E, F)) (A, B, C, D, E, F, time.Duration) {
start := time.Now()
a, b, c, d, e, f := cb()
@@ -57,6 +67,7 @@ func Duration6[A, B, C, D, E, F any](cb func() (A, B, C, D, E, F)) (A, B, C, D,
}
// Duration7 returns the time taken to execute a function.
// Play: https://go.dev/play/p/jgIAcBWWInS
func Duration7[A, B, C, D, E, F, G any](cb func() (A, B, C, D, E, F, G)) (A, B, C, D, E, F, G, time.Duration) {
start := time.Now()
a, b, c, d, e, f, g := cb()
@@ -64,6 +75,7 @@ func Duration7[A, B, C, D, E, F, G any](cb func() (A, B, C, D, E, F, G)) (A, B,
}
// Duration8 returns the time taken to execute a function.
// Play: https://go.dev/play/p/T8kxpG1c5Na
func Duration8[A, B, C, D, E, F, G, H any](cb func() (A, B, C, D, E, F, G, H)) (A, B, C, D, E, F, G, H, time.Duration) {
start := time.Now()
a, b, c, d, e, f, g, h := cb()
@@ -71,6 +83,7 @@ func Duration8[A, B, C, D, E, F, G, H any](cb func() (A, B, C, D, E, F, G, H)) (
}
// Duration9 returns the time taken to execute a function.
// Play: https://go.dev/play/p/bg9ix2VrZ0j
func Duration9[A, B, C, D, E, F, G, H, I any](cb func() (A, B, C, D, E, F, G, H, I)) (A, B, C, D, E, F, G, H, I, time.Duration) {
start := time.Now()
a, b, c, d, e, f, g, h, i := cb()
@@ -78,6 +91,7 @@ func Duration9[A, B, C, D, E, F, G, H, I any](cb func() (A, B, C, D, E, F, G, H,
}
// Duration10 returns the time taken to execute a function.
// Play: https://go.dev/play/p/Y3n7oJXqJbk
func Duration10[A, B, C, D, E, F, G, H, I, J any](cb func() (A, B, C, D, E, F, G, H, I, J)) (A, B, C, D, E, F, G, H, I, J, time.Duration) {
start := time.Now()
a, b, c, d, e, f, g, h, i, j := cb()

244
vendor/github.com/samber/lo/tuples.go generated vendored
View File

@@ -48,57 +48,57 @@ func T9[A, B, C, D, E, F, G, H, I any](a A, b B, c C, d D, e E, f F, g G, h 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.
// Unpack2 returns values contained in a tuple.
// Play: https://go.dev/play/p/xVP_k0kJ96W
func Unpack2[A, B any](tuple Tuple2[A, B]) (A, B) {
return tuple.A, tuple.B
}
// Unpack3 returns values contained in tuple.
// Unpack3 returns values contained in a tuple.
// Play: https://go.dev/play/p/xVP_k0kJ96W
func Unpack3[A, B, C any](tuple Tuple3[A, B, C]) (A, B, C) {
return tuple.A, tuple.B, tuple.C
}
// Unpack4 returns values contained in tuple.
// Unpack4 returns values contained in a tuple.
// Play: https://go.dev/play/p/xVP_k0kJ96W
func Unpack4[A, B, C, 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.
// Unpack5 returns values contained in a tuple.
// Play: https://go.dev/play/p/xVP_k0kJ96W
func Unpack5[A, B, C, D, 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.
// Unpack6 returns values contained in a tuple.
// Play: https://go.dev/play/p/xVP_k0kJ96W
func Unpack6[A, B, C, D, E, 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.
// Unpack7 returns values contained in a tuple.
// Play: https://go.dev/play/p/xVP_k0kJ96W
func Unpack7[A, B, C, D, E, F, 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.
// Unpack8 returns values contained in a tuple.
// Play: https://go.dev/play/p/xVP_k0kJ96W
func Unpack8[A, B, C, D, E, F, G, 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.
// Unpack9 returns values contained in a tuple.
// Play: https://go.dev/play/p/xVP_k0kJ96W
func Unpack9[A, B, C, D, E, F, G, H, 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
}
// 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.
// of the given slices, the second of which contains the second elements of the given slices, and so on.
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/jujaA6GaJTp
func Zip2[A, B any](a []A, b []B) []Tuple2[A, B] {
size := Max([]int{len(a), len(b)})
@@ -119,8 +119,8 @@ func Zip2[A, 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.
// of the given slices, the second of which contains the second elements of the given slices, and so on.
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/jujaA6GaJTp
func Zip3[A, B, C any](a []A, b []B, c []C) []Tuple3[A, B, C] {
size := Max([]int{len(a), len(b), len(c)})
@@ -143,8 +143,8 @@ func Zip3[A, B, 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.
// of the given slices, the second of which contains the second elements of the given slices, and so on.
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/jujaA6GaJTp
func Zip4[A, B, C, D any](a []A, b []B, c []C, d []D) []Tuple4[A, B, C, D] {
size := Max([]int{len(a), len(b), len(c), len(d)})
@@ -169,8 +169,8 @@ func Zip4[A, B, C, D any](a []A, b []B, c []C, d []D) []Tuple4[A, B, C, D] {
}
// 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.
// of the given slices, the second of which contains the second elements of the given slices, and so on.
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/jujaA6GaJTp
func Zip5[A, B, C, D, E any](a []A, b []B, c []C, d []D, e []E) []Tuple5[A, B, C, D, E] {
size := Max([]int{len(a), len(b), len(c), len(d), len(e)})
@@ -197,8 +197,8 @@ func Zip5[A, B, C, D, E any](a []A, b []B, c []C, d []D, e []E) []Tuple5[A, B, C
}
// 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.
// of the given slices, the second of which contains the second elements of the given slices, and so on.
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/jujaA6GaJTp
func Zip6[A, B, C, D, E, F any](a []A, b []B, c []C, d []D, e []E, f []F) []Tuple6[A, B, C, D, E, F] {
size := Max([]int{len(a), len(b), len(c), len(d), len(e), len(f)})
@@ -227,8 +227,8 @@ func Zip6[A, B, C, D, E, F any](a []A, b []B, c []C, d []D, e []E, f []F) []Tupl
}
// 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.
// of the given slices, the second of which contains the second elements of the given slices, and so on.
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/jujaA6GaJTp
func Zip7[A, B, C, D, E, F, 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{len(a), len(b), len(c), len(d), len(e), len(f), len(g)})
@@ -259,8 +259,8 @@ func Zip7[A, B, C, D, E, F, G any](a []A, b []B, c []C, d []D, e []E, f []F, g [
}
// 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.
// of the given slices, the second of which contains the second elements of the given slices, and so on.
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/jujaA6GaJTp
func Zip8[A, B, C, D, E, F, G, 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{len(a), len(b), len(c), len(d), len(e), len(f), len(g), len(h)})
@@ -293,8 +293,8 @@ func Zip8[A, B, C, D, E, F, G, H any](a []A, b []B, c []C, d []D, e []E, f []F,
}
// 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.
// of the given slices, the second of which contains the second elements of the given slices, and so on.
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/jujaA6GaJTp
func Zip9[A, B, C, D, E, F, G, H, 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{len(a), len(b), len(c), len(d), len(e), len(f), len(g), len(h), len(i)})
@@ -329,9 +329,10 @@ func Zip9[A, B, C, D, E, F, G, H, I any](a []A, b []B, c []C, d []D, e []E, f []
}
// ZipBy2 creates a slice of transformed 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.
func ZipBy2[A any, B any, Out any](a []A, b []B, iteratee func(a A, b B) Out) []Out {
// of the given slices, the second of which contains the second elements of the given slices, and so on.
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/wlHur6yO8rR
func ZipBy2[A, B, Out any](a []A, b []B, iteratee func(a A, b B) Out) []Out {
size := Max([]int{len(a), len(b)})
result := make([]Out, 0, size)
@@ -347,9 +348,10 @@ func ZipBy2[A any, B any, Out any](a []A, b []B, iteratee func(a A, b B) Out) []
}
// ZipBy3 creates a slice of transformed 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.
func ZipBy3[A any, B any, C any, Out any](a []A, b []B, c []C, iteratee func(a A, b B, c C) Out) []Out {
// of the given slices, the second of which contains the second elements of the given slices, and so on.
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/j9maveOnSQX
func ZipBy3[A, B, C, Out any](a []A, b []B, c []C, iteratee func(a A, b B, c C) Out) []Out {
size := Max([]int{len(a), len(b), len(c)})
result := make([]Out, 0, size)
@@ -366,9 +368,10 @@ func ZipBy3[A any, B any, C any, Out any](a []A, b []B, c []C, iteratee func(a A
}
// ZipBy4 creates a slice of transformed 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.
func ZipBy4[A any, B any, C any, D any, Out any](a []A, b []B, c []C, d []D, iteratee func(a A, b B, c C, d D) Out) []Out {
// of the given slices, the second of which contains the second elements of the given slices, and so on.
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/Y1eF2Ke0Ayz
func ZipBy4[A, B, C, D, Out any](a []A, b []B, c []C, d []D, iteratee func(a A, b B, c C, d D) Out) []Out {
size := Max([]int{len(a), len(b), len(c), len(d)})
result := make([]Out, 0, size)
@@ -386,9 +389,10 @@ func ZipBy4[A any, B any, C any, D any, Out any](a []A, b []B, c []C, d []D, ite
}
// ZipBy5 creates a slice of transformed 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.
func ZipBy5[A any, B any, C any, D any, E any, Out any](a []A, b []B, c []C, d []D, e []E, iteratee func(a A, b B, c C, d D, e E) Out) []Out {
// of the given slices, the second of which contains the second elements of the given slices, and so on.
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/SLynyalh5Oa
func ZipBy5[A, B, C, D, E, Out any](a []A, b []B, c []C, d []D, e []E, iteratee func(a A, b B, c C, d D, e E) Out) []Out {
size := Max([]int{len(a), len(b), len(c), len(d), len(e)})
result := make([]Out, 0, size)
@@ -407,9 +411,10 @@ func ZipBy5[A any, B any, C any, D any, E any, Out any](a []A, b []B, c []C, d [
}
// ZipBy6 creates a slice of transformed 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.
func ZipBy6[A any, B any, C any, D any, E any, F any, Out any](a []A, b []B, c []C, d []D, e []E, f []F, iteratee func(a A, b B, c C, d D, e E, f F) Out) []Out {
// of the given slices, the second of which contains the second elements of the given slices, and so on.
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/IK6KVgw9e-S
func ZipBy6[A, B, C, D, E, F, Out any](a []A, b []B, c []C, d []D, e []E, f []F, iteratee func(a A, b B, c C, d D, e E, f F) Out) []Out {
size := Max([]int{len(a), len(b), len(c), len(d), len(e), len(f)})
result := make([]Out, 0, size)
@@ -429,10 +434,11 @@ func ZipBy6[A any, B any, C any, D any, E any, F any, Out any](a []A, b []B, c [
}
// ZipBy7 creates a slice of transformed 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.
func ZipBy7[A any, B any, C any, D any, E any, F any, G any, Out any](a []A, b []B, c []C, d []D, e []E, f []F, g []G, iteratee func(a A, b B, c C, d D, e E, f F, g G) Out) []Out {
size := Max([]int{len(a), len(b), len(c), len(d), len(e), len(f)})
// of the given slices, the second of which contains the second elements of the given slices, and so on.
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/4uW6a2vXh8w
func ZipBy7[A, B, C, D, E, F, G, Out any](a []A, b []B, c []C, d []D, e []E, f []F, g []G, iteratee func(a A, b B, c C, d D, e E, f F, g G) Out) []Out {
size := Max([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g)})
result := make([]Out, 0, size)
@@ -452,10 +458,11 @@ func ZipBy7[A any, B any, C any, D any, E any, F any, G any, Out any](a []A, b [
}
// ZipBy8 creates a slice of transformed 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.
func ZipBy8[A any, B any, C any, D any, E any, F any, G any, H any, Out any](a []A, b []B, c []C, d []D, e []E, f []F, g []G, h []H, iteratee func(a A, b B, c C, d D, e E, f F, g G, h H) Out) []Out {
size := Max([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g)})
// of the given slices, the second of which contains the second elements of the given slices, and so on.
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/tk8xW7XzY4v
func ZipBy8[A, B, C, D, E, F, G, H, Out any](a []A, b []B, c []C, d []D, e []E, f []F, g []G, h []H, iteratee func(a A, b B, c C, d D, e E, f F, g G, h H) Out) []Out {
size := Max([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g), len(h)})
result := make([]Out, 0, size)
@@ -476,9 +483,10 @@ func ZipBy8[A any, B any, C any, D any, E any, F any, G any, H any, Out any](a [
}
// ZipBy9 creates a slice of transformed 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.
func ZipBy9[A any, B any, C any, D any, E any, F any, G any, H any, I any, Out any](a []A, b []B, c []C, d []D, e []E, f []F, g []G, h []H, i []I, iteratee func(a A, b B, c C, d D, e E, f F, g G, h H, i I) Out) []Out {
// of the given slices, the second of which contains the second elements of the given slices, and so on.
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/VGqjDmQ9YqX
func ZipBy9[A, B, C, D, E, F, G, H, I, Out any](a []A, b []B, c []C, d []D, e []E, f []F, g []G, h []H, i []I, iteratee func(a A, b B, c C, d D, e E, f F, g G, h H, i I) Out) []Out {
size := Max([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g), len(h), len(i)})
result := make([]Out, 0, size)
@@ -500,7 +508,7 @@ func ZipBy9[A any, B any, C any, D any, E any, F any, G any, H any, I any, Out a
return result
}
// Unzip2 accepts an array of grouped elements and creates an array regrouping the elements
// Unzip2 accepts a slice of grouped elements and creates a slice regrouping the elements
// to their pre-zip configuration.
// Play: https://go.dev/play/p/ciHugugvaAW
func Unzip2[A, B any](tuples []Tuple2[A, B]) ([]A, []B) {
@@ -516,7 +524,7 @@ func Unzip2[A, B any](tuples []Tuple2[A, B]) ([]A, []B) {
return r1, r2
}
// Unzip3 accepts an array of grouped elements and creates an array regrouping the elements
// Unzip3 accepts a slice of grouped elements and creates a slice regrouping the elements
// to their pre-zip configuration.
// Play: https://go.dev/play/p/ciHugugvaAW
func Unzip3[A, B, C any](tuples []Tuple3[A, B, C]) ([]A, []B, []C) {
@@ -534,7 +542,7 @@ func Unzip3[A, B, C any](tuples []Tuple3[A, B, C]) ([]A, []B, []C) {
return r1, r2, r3
}
// Unzip4 accepts an array of grouped elements and creates an array regrouping the elements
// Unzip4 accepts a slice of grouped elements and creates a slice regrouping the elements
// to their pre-zip configuration.
// Play: https://go.dev/play/p/ciHugugvaAW
func Unzip4[A, B, C, D any](tuples []Tuple4[A, B, C, D]) ([]A, []B, []C, []D) {
@@ -554,7 +562,7 @@ func Unzip4[A, B, C, D any](tuples []Tuple4[A, B, C, D]) ([]A, []B, []C, []D) {
return r1, r2, r3, r4
}
// Unzip5 accepts an array of grouped elements and creates an array regrouping the elements
// Unzip5 accepts a slice of grouped elements and creates a slice regrouping the elements
// to their pre-zip configuration.
// Play: https://go.dev/play/p/ciHugugvaAW
func Unzip5[A, B, C, D, E any](tuples []Tuple5[A, B, C, D, E]) ([]A, []B, []C, []D, []E) {
@@ -576,7 +584,7 @@ func Unzip5[A, B, C, D, E any](tuples []Tuple5[A, B, C, D, E]) ([]A, []B, []C, [
return r1, r2, r3, r4, r5
}
// Unzip6 accepts an array of grouped elements and creates an array regrouping the elements
// Unzip6 accepts a slice of grouped elements and creates a slice regrouping the elements
// to their pre-zip configuration.
// Play: https://go.dev/play/p/ciHugugvaAW
func Unzip6[A, B, C, D, E, F any](tuples []Tuple6[A, B, C, D, E, F]) ([]A, []B, []C, []D, []E, []F) {
@@ -600,7 +608,7 @@ func Unzip6[A, B, C, D, E, F any](tuples []Tuple6[A, B, C, D, E, F]) ([]A, []B,
return r1, r2, r3, r4, r5, r6
}
// Unzip7 accepts an array of grouped elements and creates an array regrouping the elements
// Unzip7 accepts a slice of grouped elements and creates a slice regrouping the elements
// to their pre-zip configuration.
// Play: https://go.dev/play/p/ciHugugvaAW
func Unzip7[A, B, C, D, E, F, G any](tuples []Tuple7[A, B, C, D, E, F, G]) ([]A, []B, []C, []D, []E, []F, []G) {
@@ -626,7 +634,7 @@ func Unzip7[A, B, C, D, E, F, G any](tuples []Tuple7[A, B, C, D, E, F, G]) ([]A,
return r1, r2, r3, r4, r5, r6, r7
}
// Unzip8 accepts an array of grouped elements and creates an array regrouping the elements
// Unzip8 accepts a slice of grouped elements and creates a slice regrouping the elements
// to their pre-zip configuration.
// Play: https://go.dev/play/p/ciHugugvaAW
func Unzip8[A, B, C, D, E, F, G, H any](tuples []Tuple8[A, B, C, D, E, F, G, H]) ([]A, []B, []C, []D, []E, []F, []G, []H) {
@@ -654,7 +662,7 @@ func Unzip8[A, B, C, D, E, F, G, H any](tuples []Tuple8[A, B, C, D, E, F, G, H])
return r1, r2, r3, r4, r5, r6, r7, r8
}
// Unzip9 accepts an array of grouped elements and creates an array regrouping the elements
// Unzip9 accepts a slice of grouped elements and creates a slice regrouping the elements
// to their pre-zip configuration.
// Play: https://go.dev/play/p/ciHugugvaAW
func Unzip9[A, B, C, D, E, F, G, H, I any](tuples []Tuple9[A, B, C, D, E, F, G, H, I]) ([]A, []B, []C, []D, []E, []F, []G, []H, []I) {
@@ -684,9 +692,10 @@ func Unzip9[A, B, C, D, E, F, G, H, I any](tuples []Tuple9[A, B, C, D, E, F, G,
return r1, r2, r3, r4, r5, r6, r7, r8, r9
}
// UnzipBy2 iterates over a collection and creates an array regrouping the elements
// UnzipBy2 iterates over a collection and creates a slice regrouping the elements
// to their pre-zip configuration.
func UnzipBy2[In any, A any, B any](items []In, iteratee func(In) (a A, b B)) ([]A, []B) {
// Play: https://go.dev/play/p/tN8yqaRZz0r
func UnzipBy2[In, A, B any](items []In, iteratee func(In) (a A, b B)) ([]A, []B) {
size := len(items)
r1 := make([]A, 0, size)
r2 := make([]B, 0, size)
@@ -700,9 +709,10 @@ func UnzipBy2[In any, A any, B any](items []In, iteratee func(In) (a A, b B)) ([
return r1, r2
}
// UnzipBy3 iterates over a collection and creates an array regrouping the elements
// UnzipBy3 iterates over a collection and creates a slice regrouping the elements
// to their pre-zip configuration.
func UnzipBy3[In any, A any, B any, C any](items []In, iteratee func(In) (a A, b B, c C)) ([]A, []B, []C) {
// Play: https://go.dev/play/p/36ITO2DlQq1
func UnzipBy3[In, A, B, C any](items []In, iteratee func(In) (a A, b B, c C)) ([]A, []B, []C) {
size := len(items)
r1 := make([]A, 0, size)
r2 := make([]B, 0, size)
@@ -718,9 +728,10 @@ func UnzipBy3[In any, A any, B any, C any](items []In, iteratee func(In) (a A, b
return r1, r2, r3
}
// UnzipBy4 iterates over a collection and creates an array regrouping the elements
// UnzipBy4 iterates over a collection and creates a slice regrouping the elements
// to their pre-zip configuration.
func UnzipBy4[In any, A any, B any, C any, D any](items []In, iteratee func(In) (a A, b B, c C, d D)) ([]A, []B, []C, []D) {
// Play: https://go.dev/play/p/zJ6qY1dD1rL
func UnzipBy4[In, A, B, C, D any](items []In, iteratee func(In) (a A, b B, c C, d D)) ([]A, []B, []C, []D) {
size := len(items)
r1 := make([]A, 0, size)
r2 := make([]B, 0, size)
@@ -738,9 +749,10 @@ func UnzipBy4[In any, A any, B any, C any, D any](items []In, iteratee func(In)
return r1, r2, r3, r4
}
// UnzipBy5 iterates over a collection and creates an array regrouping the elements
// UnzipBy5 iterates over a collection and creates a slice regrouping the elements
// to their pre-zip configuration.
func UnzipBy5[In any, A any, B any, C any, D any, E any](items []In, iteratee func(In) (a A, b B, c C, d D, e E)) ([]A, []B, []C, []D, []E) {
// Play: https://go.dev/play/p/3f7jKkV9xZt
func UnzipBy5[In, A, B, C, D, E any](items []In, iteratee func(In) (a A, b B, c C, d D, e E)) ([]A, []B, []C, []D, []E) {
size := len(items)
r1 := make([]A, 0, size)
r2 := make([]B, 0, size)
@@ -760,9 +772,10 @@ func UnzipBy5[In any, A any, B any, C any, D any, E any](items []In, iteratee fu
return r1, r2, r3, r4, r5
}
// UnzipBy6 iterates over a collection and creates an array regrouping the elements
// UnzipBy6 iterates over a collection and creates a slice regrouping the elements
// to their pre-zip configuration.
func UnzipBy6[In any, A any, B any, C any, D any, E any, F any](items []In, iteratee func(In) (a A, b B, c C, d D, e E, f F)) ([]A, []B, []C, []D, []E, []F) {
// Play: https://go.dev/play/p/8Y1b7tKu2pL
func UnzipBy6[In, A, B, C, D, E, F any](items []In, iteratee func(In) (a A, b B, c C, d D, e E, f F)) ([]A, []B, []C, []D, []E, []F) {
size := len(items)
r1 := make([]A, 0, size)
r2 := make([]B, 0, size)
@@ -784,9 +797,10 @@ func UnzipBy6[In any, A any, B any, C any, D any, E any, F any](items []In, iter
return r1, r2, r3, r4, r5, r6
}
// UnzipBy7 iterates over a collection and creates an array regrouping the elements
// UnzipBy7 iterates over a collection and creates a slice regrouping the elements
// to their pre-zip configuration.
func UnzipBy7[In any, A any, B any, C any, D any, E any, F any, G any](items []In, iteratee func(In) (a A, b B, c C, d D, e E, f F, g G)) ([]A, []B, []C, []D, []E, []F, []G) {
// Play: https://go.dev/play/p/7j1kLmVn3pM
func UnzipBy7[In, A, B, C, D, E, F, G any](items []In, iteratee func(In) (a A, b B, c C, d D, e E, f F, g G)) ([]A, []B, []C, []D, []E, []F, []G) {
size := len(items)
r1 := make([]A, 0, size)
r2 := make([]B, 0, size)
@@ -810,9 +824,10 @@ func UnzipBy7[In any, A any, B any, C any, D any, E any, F any, G any](items []I
return r1, r2, r3, r4, r5, r6, r7
}
// UnzipBy8 iterates over a collection and creates an array regrouping the elements
// UnzipBy8 iterates over a collection and creates a slice regrouping the elements
// to their pre-zip configuration.
func UnzipBy8[In any, A any, B any, C any, D any, E any, F any, G any, H any](items []In, iteratee func(In) (a A, b B, c C, d D, e E, f F, g G, h H)) ([]A, []B, []C, []D, []E, []F, []G, []H) {
// Play: https://go.dev/play/p/1n2k3L4m5N6
func UnzipBy8[In, A, B, C, D, E, F, G, H any](items []In, iteratee func(In) (a A, b B, c C, d D, e E, f F, g G, h H)) ([]A, []B, []C, []D, []E, []F, []G, []H) {
size := len(items)
r1 := make([]A, 0, size)
r2 := make([]B, 0, size)
@@ -838,9 +853,10 @@ func UnzipBy8[In any, A any, B any, C any, D any, E any, F any, G any, H any](it
return r1, r2, r3, r4, r5, r6, r7, r8
}
// UnzipBy9 iterates over a collection and creates an array regrouping the elements
// UnzipBy9 iterates over a collection and creates a slice regrouping the elements
// to their pre-zip configuration.
func UnzipBy9[In any, A any, B any, C any, D any, E any, F any, G any, H any, I any](items []In, iteratee func(In) (a A, b B, c C, d D, e E, f F, g G, h H, i I)) ([]A, []B, []C, []D, []E, []F, []G, []H, []I) {
// Play: https://go.dev/play/p/7o8p9q0r1s2
func UnzipBy9[In, A, B, C, D, E, F, G, H, I any](items []In, iteratee func(In) (a A, b B, c C, d D, e E, f F, g G, h H, i I)) ([]A, []B, []C, []D, []E, []F, []G, []H, []I) {
size := len(items)
r1 := make([]A, 0, size)
r2 := make([]B, 0, size)
@@ -868,66 +884,75 @@ func UnzipBy9[In any, A any, B any, C any, D any, E any, F any, G any, H any, I
return r1, r2, r3, r4, r5, r6, r7, r8, r9
}
// CrossJoin2 combines every items from one list with every items from others.
// CrossJoin2 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments.
// It returns an empty list if a list is empty.
// Returns an empty list if a list is empty.
// Play: https://go.dev/play/p/3VFppyL9FDU
func CrossJoin2[A, B any](listA []A, listB []B) []Tuple2[A, B] {
return CrossJoinBy2(listA, listB, T2[A, B])
}
// CrossJoin3 combines every items from one list with every items from others.
// CrossJoin3 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments.
// It returns an empty list if a list is empty.
// Returns an empty list if a list is empty.
// Play: https://go.dev/play/p/2WGeHyJj4fK
func CrossJoin3[A, B, C any](listA []A, listB []B, listC []C) []Tuple3[A, B, C] {
return CrossJoinBy3(listA, listB, listC, T3[A, B, C])
}
// CrossJoin4 combines every items from one list with every items from others.
// CrossJoin4 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments.
// It returns an empty list if a list is empty.
// Returns an empty list if a list is empty.
// Play: https://go.dev/play/p/6XhKjLmMnNp
func CrossJoin4[A, B, C, D any](listA []A, listB []B, listC []C, listD []D) []Tuple4[A, B, C, D] {
return CrossJoinBy4(listA, listB, listC, listD, T4[A, B, C, D])
}
// CrossJoin5 combines every items from one list with every items from others.
// CrossJoin5 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments.
// It returns an empty list if a list is empty.
// Returns an empty list if a list is empty.
// Play: https://go.dev/play/p/7oPqRsTuVwX
func CrossJoin5[A, B, C, D, E any](listA []A, listB []B, listC []C, listD []D, listE []E) []Tuple5[A, B, C, D, E] {
return CrossJoinBy5(listA, listB, listC, listD, listE, T5[A, B, C, D, E])
}
// CrossJoin6 combines every items from one list with every items from others.
// CrossJoin6 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments.
// It returns an empty list if a list is empty.
// Returns an empty list if a list is empty.
// Play: https://go.dev/play/p/8yZ1aB2cD3e
func CrossJoin6[A, B, C, D, E, F any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F) []Tuple6[A, B, C, D, E, F] {
return CrossJoinBy6(listA, listB, listC, listD, listE, listF, T6[A, B, C, D, E, F])
}
// CrossJoin7 combines every items from one list with every items from others.
// CrossJoin7 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments.
// It returns an empty list if a list is empty.
// Returns an empty list if a list is empty.
// Play: https://go.dev/play/p/9f4g5h6i7j8
func CrossJoin7[A, B, C, D, E, F, G any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, listG []G) []Tuple7[A, B, C, D, E, F, G] {
return CrossJoinBy7(listA, listB, listC, listD, listE, listF, listG, T7[A, B, C, D, E, F, G])
}
// CrossJoin8 combines every items from one list with every items from others.
// CrossJoin8 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments.
// It returns an empty list if a list is empty.
// Returns an empty list if a list is empty.
// Play: https://go.dev/play/p/0k1l2m3n4o5
func CrossJoin8[A, B, C, D, E, F, G, H any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, listG []G, listH []H) []Tuple8[A, B, C, D, E, F, G, H] {
return CrossJoinBy8(listA, listB, listC, listD, listE, listF, listG, listH, T8[A, B, C, D, E, F, G, H])
}
// CrossJoin9 combines every items from one list with every items from others.
// CrossJoin9 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments.
// It returns an empty list if a list is empty.
// Returns an empty list if a list is empty.
// Play: https://go.dev/play/p/6p7q8r9s0t1
func CrossJoin9[A, B, C, D, E, F, G, H, I any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, listG []G, listH []H, listI []I) []Tuple9[A, B, C, D, E, F, G, H, I] {
return CrossJoinBy9(listA, listB, listC, listD, listE, listF, listG, listH, listI, T9[A, B, C, D, E, F, G, H, I])
}
// CrossJoinBy2 combines every items from one list with every items from others.
// CrossJoinBy2 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments. The project function
// is used to create the output values.
// It returns an empty list if a list is empty.
// Returns an empty list if a list is empty.
// Play: https://go.dev/play/p/8Y7btpvuA-C
func CrossJoinBy2[A, B, Out any](listA []A, listB []B, project func(a A, b B) Out) []Out {
size := len(listA) * len(listB)
if size == 0 {
@@ -945,10 +970,11 @@ func CrossJoinBy2[A, B, Out any](listA []A, listB []B, project func(a A, b B) Ou
return result
}
// CrossJoinBy3 combines every items from one list with every items from others.
// CrossJoinBy3 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments. The project function
// is used to create the output values.
// It returns an empty list if a list is empty.
// Returns an empty list if a list is empty.
// Play: https://go.dev/play/p/3z4y5x6w7v8
func CrossJoinBy3[A, B, C, Out any](listA []A, listB []B, listC []C, project func(a A, b B, c C) Out) []Out {
size := len(listA) * len(listB) * len(listC)
if size == 0 {
@@ -968,10 +994,11 @@ func CrossJoinBy3[A, B, C, Out any](listA []A, listB []B, listC []C, project fun
return result
}
// CrossJoinBy4 combines every items from one list with every items from others.
// CrossJoinBy4 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments. The project function
// is used to create the output values.
// It returns an empty list if a list is empty.
// Returns an empty list if a list is empty.
// Play: https://go.dev/play/p/8b9c0d1e2f3
func CrossJoinBy4[A, B, C, D, Out any](listA []A, listB []B, listC []C, listD []D, project func(a A, b B, c C, d D) Out) []Out {
size := len(listA) * len(listB) * len(listC) * len(listD)
if size == 0 {
@@ -993,10 +1020,11 @@ func CrossJoinBy4[A, B, C, D, Out any](listA []A, listB []B, listC []C, listD []
return result
}
// CrossJoinBy5 combines every items from one list with every items from others.
// CrossJoinBy5 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments. The project function
// is used to create the output values.
// It returns an empty list if a list is empty.
// Returns an empty list if a list is empty.
// Play: https://go.dev/play/p/4g5h6i7j8k9
func CrossJoinBy5[A, B, C, D, E, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, project func(a A, b B, c C, d D, e E) Out) []Out {
size := len(listA) * len(listB) * len(listC) * len(listD) * len(listE)
if size == 0 {
@@ -1020,10 +1048,11 @@ func CrossJoinBy5[A, B, C, D, E, Out any](listA []A, listB []B, listC []C, listD
return result
}
// CrossJoinBy6 combines every items from one list with every items from others.
// CrossJoinBy6 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments. The project function
// is used to create the output values.
// It returns an empty list if a list is empty.
// Returns an empty list if a list is empty.
// Play: https://go.dev/play/p/1l2m3n4o5p6
func CrossJoinBy6[A, B, C, D, E, F, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, project func(a A, b B, c C, d D, e E, f F) Out) []Out {
size := len(listA) * len(listB) * len(listC) * len(listD) * len(listE) * len(listF)
if size == 0 {
@@ -1049,10 +1078,11 @@ func CrossJoinBy6[A, B, C, D, E, F, Out any](listA []A, listB []B, listC []C, li
return result
}
// CrossJoinBy7 combines every items from one list with every items from others.
// CrossJoinBy7 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments. The project function
// is used to create the output values.
// It returns an empty list if a list is empty.
// Returns an empty list if a list is empty.
// Play: https://go.dev/play/p/7q8r9s0t1u2
func CrossJoinBy7[A, B, C, D, E, F, G, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, listG []G, project func(a A, b B, c C, d D, e E, f F, g G) Out) []Out {
size := len(listA) * len(listB) * len(listC) * len(listD) * len(listE) * len(listF) * len(listG)
if size == 0 {
@@ -1080,10 +1110,11 @@ func CrossJoinBy7[A, B, C, D, E, F, G, Out any](listA []A, listB []B, listC []C,
return result
}
// CrossJoinBy8 combines every items from one list with every items from others.
// CrossJoinBy8 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments. The project function
// is used to create the output values.
// It returns an empty list if a list is empty.
// Returns an empty list if a list is empty.
// Play: https://go.dev/play/p/3v4w5x6y7z8
func CrossJoinBy8[A, B, C, D, E, F, G, H, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, listG []G, listH []H, project func(a A, b B, c C, d D, e E, f F, g G, h H) Out) []Out {
size := len(listA) * len(listB) * len(listC) * len(listD) * len(listE) * len(listF) * len(listG) * len(listH)
if size == 0 {
@@ -1113,10 +1144,11 @@ func CrossJoinBy8[A, B, C, D, E, F, G, H, Out any](listA []A, listB []B, listC [
return result
}
// CrossJoinBy9 combines every items from one list with every items from others.
// CrossJoinBy9 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments. The project function
// is used to create the output values.
// It returns an empty list if a list is empty.
// Returns an empty list if a list is empty.
// Play: https://go.dev/play/p/9a0b1c2d3e4
func CrossJoinBy9[A, B, C, D, E, F, G, H, I, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, listG []G, listH []H, listI []I, project func(a A, b B, c C, d D, e E, f F, g G, h H, i I) Out) []Out {
size := len(listA) * len(listB) * len(listC) * len(listD) * len(listE) * len(listF) * len(listG) * len(listH) * len(listI)
if size == 0 {

View File

@@ -3,28 +3,41 @@ package lo
import "reflect"
// IsNil checks if a value is nil or if it's a reference type with a nil underlying value.
// Play: https://go.dev/play/p/P2sD0PMXw4F
func IsNil(x any) bool {
defer func() { recover() }() // nolint:errcheck
return x == nil || reflect.ValueOf(x).IsNil()
if x == nil {
return true
}
v := reflect.ValueOf(x)
switch v.Kind() { //nolint:exhaustive
case reflect.Chan, reflect.Func, reflect.Map, reflect.Pointer, reflect.UnsafePointer, reflect.Interface, reflect.Slice:
return v.IsNil()
default:
return false
}
}
// IsNotNil checks if a value is not nil or if it's not a reference type with a nil underlying value.
// Play: https://go.dev/play/p/P2sD0PMXw4F
func IsNotNil(x any) bool {
return !IsNil(x)
}
// ToPtr returns a pointer copy of value.
// Play: https://go.dev/play/p/P2sD0PMXw4F
func ToPtr[T any](x T) *T {
return &x
}
// Nil returns a nil pointer of type.
// Play: https://go.dev/play/p/P2sD0PMXw4F
func Nil[T any]() *T {
return nil
}
// EmptyableToPtr returns a pointer copy of value if it's nonzero.
// Otherwise, returns nil pointer.
// Play: https://go.dev/play/p/P2sD0PMXw4F
func EmptyableToPtr[T any](x T) *T {
// 🤮
isZero := reflect.ValueOf(&x).Elem().IsZero()
@@ -36,6 +49,7 @@ func EmptyableToPtr[T any](x T) *T {
}
// FromPtr returns the pointer value or empty.
// Play: https://go.dev/play/p/mhD9CwO3X0m
func FromPtr[T any](x *T) T {
if x == nil {
return Empty[T]()
@@ -45,6 +59,7 @@ func FromPtr[T any](x *T) T {
}
// FromPtrOr returns the pointer value or the fallback value.
// Play: https://go.dev/play/p/mhD9CwO3X0m
func FromPtrOr[T any](x *T, fallback T) T {
if x == nil {
return fallback
@@ -53,7 +68,8 @@ func FromPtrOr[T any](x *T, fallback T) T {
return *x
}
// ToSlicePtr returns a slice of pointer copy of value.
// ToSlicePtr returns a slice of pointers to each value.
// Play: https://go.dev/play/p/P2sD0PMXw4F
func ToSlicePtr[T any](collection []T) []*T {
result := make([]*T, len(collection))
@@ -65,6 +81,7 @@ func ToSlicePtr[T any](collection []T) []*T {
// FromSlicePtr returns a slice with the pointer values.
// Returns a zero value in case of a nil pointer element.
// Play: https://go.dev/play/p/lbunFvzlUDX
func FromSlicePtr[T any](collection []*T) []T {
return Map(collection, func(x *T, _ int) T {
if x == nil {
@@ -85,7 +102,8 @@ func FromSlicePtrOr[T any](collection []*T, fallback T) []T {
})
}
// ToAnySlice returns a slice with all elements mapped to `any` type
// ToAnySlice returns a slice with all elements mapped to `any` type.
// Play: https://go.dev/play/p/P2sD0PMXw4F
func ToAnySlice[T any](collection []T) []any {
result := make([]any, len(collection))
for i := range collection {
@@ -94,61 +112,65 @@ func ToAnySlice[T any](collection []T) []any {
return result
}
// FromAnySlice returns an `any` slice with all elements mapped to a type.
// FromAnySlice returns a 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))
// Play: https://go.dev/play/p/P2sD0PMXw4F
func FromAnySlice[T any](in []any) ([]T, bool) {
out := make([]T, len(in))
for i := range in {
result[i] = in[i].(T)
t, ok := in[i].(T)
if !ok {
return []T{}, false
}
out[i] = t
}
return result, true
return out, true
}
// Empty returns the zero value (https://go.dev/ref/spec#The_zero_value).
// Play: https://go.dev/play/p/P2sD0PMXw4F
func Empty[T any]() T {
var zero T
return zero
}
// IsEmpty returns true if argument is a zero value.
// Play: https://go.dev/play/p/P2sD0PMXw4F
func IsEmpty[T comparable](v T) bool {
var zero T
return zero == v
}
// IsNotEmpty returns true if argument is not a zero value.
// Play: https://go.dev/play/p/P2sD0PMXw4F
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](values ...T) (result T, ok bool) {
// Play: https://go.dev/play/p/Gyo9otyvFHH
func Coalesce[T comparable](values ...T) (T, bool) {
var zero T
for i := range values {
if values[i] != result {
result = values[i]
ok = true
return
if values[i] != zero {
return values[i], true
}
}
return
return zero, false
}
// CoalesceOrEmpty returns the first non-empty arguments. Arguments must be comparable.
// Play: https://go.dev/play/p/Gyo9otyvFHH
func CoalesceOrEmpty[T comparable](v ...T) T {
result, _ := Coalesce(v...)
return result
}
// CoalesceSlice returns the first non-zero slice.
// Play: https://go.dev/play/p/Gyo9otyvFHH
func CoalesceSlice[T any](v ...[]T) ([]T, bool) {
for i := range v {
if v[i] != nil && len(v[i]) > 0 {
@@ -159,6 +181,7 @@ func CoalesceSlice[T any](v ...[]T) ([]T, bool) {
}
// CoalesceSliceOrEmpty returns the first non-zero slice.
// Play: https://go.dev/play/p/Gyo9otyvFHH
func CoalesceSliceOrEmpty[T any](v ...[]T) []T {
for i := range v {
if v[i] != nil && len(v[i]) > 0 {
@@ -169,6 +192,7 @@ func CoalesceSliceOrEmpty[T any](v ...[]T) []T {
}
// CoalesceMap returns the first non-zero map.
// Play: https://go.dev/play/p/Gyo9otyvFHH
func CoalesceMap[K comparable, V any](v ...map[K]V) (map[K]V, bool) {
for i := range v {
if v[i] != nil && len(v[i]) > 0 {
@@ -179,6 +203,7 @@ func CoalesceMap[K comparable, V any](v ...map[K]V) (map[K]V, bool) {
}
// CoalesceMapOrEmpty returns the first non-zero map.
// Play: https://go.dev/play/p/Gyo9otyvFHH
func CoalesceMapOrEmpty[K comparable, V any](v ...map[K]V) map[K]V {
for i := range v {
if v[i] != nil && len(v[i]) > 0 {

24
vendor/github.com/samber/lo/types.go generated vendored
View File

@@ -12,7 +12,8 @@ type Tuple2[A, B any] struct {
B B
}
// Unpack returns values contained in tuple.
// Unpack returns values contained in a tuple.
// Play: https://go.dev/play/p/yrtn7QJTmL_E
func (t Tuple2[A, B]) Unpack() (A, B) {
return t.A, t.B
}
@@ -24,7 +25,8 @@ type Tuple3[A, B, C any] struct {
C C
}
// Unpack returns values contained in tuple.
// Unpack returns values contained in a tuple.
// Play: https://go.dev/play/p/yrtn7QJTmL_E
func (t Tuple3[A, B, C]) Unpack() (A, B, C) {
return t.A, t.B, t.C
}
@@ -37,7 +39,8 @@ type Tuple4[A, B, C, D any] struct {
D D
}
// Unpack returns values contained in tuple.
// Unpack returns values contained in a tuple.
// Play: https://go.dev/play/p/yrtn7QJTmL_E
func (t Tuple4[A, B, C, D]) Unpack() (A, B, C, D) {
return t.A, t.B, t.C, t.D
}
@@ -51,7 +54,8 @@ type Tuple5[A, B, C, D, E any] struct {
E E
}
// Unpack returns values contained in tuple.
// Unpack returns values contained in a tuple.
// Play: https://go.dev/play/p/7J4KrtgtK3M
func (t Tuple5[A, B, C, D, E]) Unpack() (A, B, C, D, E) {
return t.A, t.B, t.C, t.D, t.E
}
@@ -66,7 +70,8 @@ type Tuple6[A, B, C, D, E, F any] struct {
F F
}
// Unpack returns values contained in tuple.
// Unpack returns values contained in a tuple.
// Play: https://go.dev/play/p/7J4KrtgtK3M
func (t Tuple6[A, B, C, D, E, F]) Unpack() (A, B, C, D, E, F) {
return t.A, t.B, t.C, t.D, t.E, t.F
}
@@ -82,7 +87,8 @@ type Tuple7[A, B, C, D, E, F, G any] struct {
G G
}
// Unpack returns values contained in tuple.
// Unpack returns values contained in a tuple.
// Play: https://go.dev/play/p/Ow9Zgf_zeiA
func (t Tuple7[A, B, C, D, E, F, G]) Unpack() (A, B, C, D, E, F, G) {
return t.A, t.B, t.C, t.D, t.E, t.F, t.G
}
@@ -99,7 +105,8 @@ type Tuple8[A, B, C, D, E, F, G, H any] struct {
H H
}
// Unpack returns values contained in tuple.
// Unpack returns values contained in a tuple.
// Play: https://go.dev/play/p/Ow9Zgf_zeiA
func (t Tuple8[A, B, C, D, E, F, G, H]) Unpack() (A, B, C, D, E, F, G, H) {
return t.A, t.B, t.C, t.D, t.E, t.F, t.G, t.H
}
@@ -117,7 +124,8 @@ type Tuple9[A, B, C, D, E, F, G, H, I any] struct {
I I
}
// Unpack returns values contained in tuple.
// Unpack returns values contained in a tuple.
// Play: https://go.dev/play/p/Ow9Zgf_zeiA
func (t Tuple9[A, B, C, D, E, F, G, H, I]) Unpack() (A, B, C, D, E, F, G, H, I) {
return t.A, t.B, t.C, t.D, t.E, t.F, t.G, t.H, t.I
}

View File

@@ -20,12 +20,21 @@ This project gathers common functions for my [slog](https://pkg.go.dev/log/slog)
<hr>
<sup><b>Sponsored by:</b></sup>
<br>
<a href="https://quickwit.io?utm_campaign=github_sponsorship&utm_medium=referral&utm_content=samber-slog-common&utm_source=github">
<a href="https://cast.ai/samuel">
<div>
<img src="https://github.com/samber/oops/assets/2951285/49aaaa2b-b8c6-4f21-909f-c12577bb6a2e" width="240" alt="Quickwit">
<img src="https://github.com/user-attachments/assets/502f8fa8-e7e8-4754-a51f-036d0443e694" width="200" alt="Cast AI">
</div>
<div>
Cloud-native search engine for observability - An OSS alternative to Splunk, Elasticsearch, Loki, and Tempo.
Cut Kubernetes & AI costs, boost application stability
</div>
</a>
<br>
<a href="https://www.dash0.com?utm_campaign=148395251-samber%20github%20sponsorship&utm_source=github&utm_medium=sponsorship&utm_content=samber">
<div>
<img src="https://github.com/user-attachments/assets/b1f2e876-0954-4dc3-824d-935d29ba8f3f" width="200" alt="Dash0">
</div>
<div>
100% OpenTelemetry-native observability platform<br>Simple to use, built on open standards, and designed for full cost control
</div>
</a>
<hr>

View File

@@ -38,6 +38,8 @@ func ReplaceAttrs(fn ReplaceAttrFn, groups []string, attrs ...slog.Attr) []slog.
attrs[i].Value = slog.GroupValue(ReplaceAttrs(fn, append(groups, attr.Key), value.Group()...)...)
} else if fn != nil {
attrs[i] = fn(groups, attr)
} else {
attrs[i].Value = value
}
}
@@ -215,7 +217,11 @@ func FormatErrorKey(values map[string]any, errorKeys ...string) map[string]any {
return values
}
func FormatError(err error) map[string]any {
func FormatError(err error) any {
if e, ok := err.(slog.LogValuer); ok {
return e.LogValue()
}
return map[string]any{
"kind": reflect.TypeOf(err).String(),
"error": err.Error(),
@@ -298,21 +304,28 @@ func FindAttribute(attrs []slog.Attr, groups []string, key string) (slog.Attr, b
}
func RemoveEmptyAttrs(attrs []slog.Attr) []slog.Attr {
return lo.FilterMap(attrs, func(attr slog.Attr, _ int) (slog.Attr, bool) {
if attr.Key == "" {
return attr, false
}
return lo.FlatMap(attrs, func(attr slog.Attr, _ int) []slog.Attr {
if attr.Key != "" {
if attr.Value.Kind() == slog.KindGroup {
values := RemoveEmptyAttrs(attr.Value.Group())
if len(values) == 0 {
return nil
}
if attr.Value.Kind() == slog.KindGroup {
values := RemoveEmptyAttrs(attr.Value.Group())
if len(values) == 0 {
return attr, false
attr.Value = slog.GroupValue(values...)
}
attr.Value = slog.GroupValue(values...)
return attr, true
if attr.Value.Equal(slog.Value{}) {
return nil
}
return []slog.Attr{attr}
}
return attr, !attr.Value.Equal(slog.Value{})
if attr.Value.Kind() != slog.KindGroup {
return nil
}
return RemoveEmptyAttrs(attr.Value.Group())
})
}

View File

@@ -16,6 +16,15 @@ A [Zerolog](https://github.com/rs/zerolog) Handler for [slog](https://pkg.go.dev
<hr>
<sup><b>Sponsored by:</b></sup>
<br>
<a href="https://cast.ai/samuel">
<div>
<img src="https://github.com/user-attachments/assets/502f8fa8-e7e8-4754-a51f-036d0443e694" width="200" alt="Cast AI">
</div>
<div>
Cut Kubernetes & AI costs, boost application stability
</div>
</a>
<br>
<a href="https://www.dash0.com?utm_campaign=148395251-samber%20github%20sponsorship&utm_source=github&utm_medium=sponsorship&utm_content=samber">
<div>
<img src="https://github.com/user-attachments/assets/b1f2e876-0954-4dc3-824d-935d29ba8f3f" width="200" alt="Dash0">

14
vendor/modules.txt vendored
View File

@@ -475,7 +475,7 @@ github.com/go-acme/lego/v4/challenge
# github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667
## explicit; go 1.13
github.com/go-asn1-ber/asn1-ber
# github.com/go-chi/chi/v5 v5.2.4
# github.com/go-chi/chi/v5 v5.2.5
## explicit; go 1.22
github.com/go-chi/chi/v5
github.com/go-chi/chi/v5/middleware
@@ -1376,7 +1376,7 @@ github.com/opencloud-eu/icap-client
# github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20250724122329-41ba6b191e76
## explicit; go 1.18
github.com/opencloud-eu/libre-graph-api-go
# github.com/opencloud-eu/reva/v2 v2.42.2
# github.com/opencloud-eu/reva/v2 v2.42.3 => github.com/aduffeck/reva/v2 v2.27.3-0.20260209115512-73c029375aec
## explicit; go 1.24.1
github.com/opencloud-eu/reva/v2/cmd/revad/internal/grace
github.com/opencloud-eu/reva/v2/cmd/revad/runtime
@@ -1923,16 +1923,17 @@ github.com/rwcarlsen/goexif/tiff
# github.com/sagikazarmark/locafero v0.11.0
## explicit; go 1.23.0
github.com/sagikazarmark/locafero
# github.com/samber/lo v1.51.0
# github.com/samber/lo v1.52.0
## explicit; go 1.18
github.com/samber/lo
github.com/samber/lo/internal/constraints
github.com/samber/lo/internal/rand
github.com/samber/lo/internal/xrand
github.com/samber/lo/internal/xtime
github.com/samber/lo/mutable
# github.com/samber/slog-common v0.19.0
# github.com/samber/slog-common v0.20.0
## explicit; go 1.21
github.com/samber/slog-common
# github.com/samber/slog-zerolog/v2 v2.9.0
# github.com/samber/slog-zerolog/v2 v2.9.1
## explicit; go 1.21
github.com/samber/slog-zerolog/v2
# github.com/segmentio/asm v1.2.1
@@ -2754,3 +2755,4 @@ stash.kopano.io/kgol/rndm
# github.com/unrolled/secure => github.com/DeepDiver1975/secure v0.0.0-20240611112133-abc838fb797c
# go-micro.dev/v4 => github.com/butonic/go-micro/v4 v4.11.1-0.20241115112658-b5d4de5ed9b3
# github.com/go-micro/plugins/v4/store/nats-js-kv => github.com/opencloud-eu/go-micro-plugins/v4/store/nats-js-kv v0.0.0-20250512152754-23325793059a
# github.com/opencloud-eu/reva/v2 => github.com/aduffeck/reva/v2 v2.27.3-0.20260209115512-73c029375aec