mirror of
https://github.com/phishingclub/phishingclub.git
synced 2026-10-03 22:16:49 +02:00
Bumped deps Go, surf, x/crypto, compress, x/mod, swapped enetx utls/uquic forks for upstream utls/quic-go and updated surf method calls
Signed-off-by: RonniSkansing <rskansing@gmail.com>
This commit is contained in:
845 files changed
+12486
-157341
No files matched your search
@@ -67,7 +67,7 @@ jobs:
|
||||
-v "$(pwd)":/app \
|
||||
-v /tmp/go-build-cache:/root/.cache/go-build \
|
||||
-w /app/backend \
|
||||
golang@sha256:fec9672136e7ccfa4f6cf59177987de3d8ee9b14953b6ea5485dbd08bf7b4aed `# golang:1.25.13-alpine linux/amd64` \
|
||||
golang@sha256:f86f1a6701e3dcc445fec097a42f78b758f15950ccf032c2d3e54e2754d32fdb `# golang:1.27.1-alpine linux/amd64` \
|
||||
sh -c "apk add --no-cache gcc musl-dev && go build -trimpath \
|
||||
-ldflags='-X github.com/phishingclub/phishingclub/version.hash=ph${{ steps.get_version.outputs.HASH }} -X github.com/phishingclub/phishingclub/version.version=${{ steps.get_version.outputs.VERSION }} -linkmode=external -extldflags=-static' \
|
||||
-tags production -o ../build/amd64/phishingclub main.go"
|
||||
@@ -78,7 +78,7 @@ jobs:
|
||||
-v "$(pwd)":/app \
|
||||
-v /tmp/go-build-cache:/root/.cache/go-build \
|
||||
-w /app/backend \
|
||||
golang@sha256:0ae9afcc66ea58a852d29541782ad911f6151e073355e8eb0f10f91f6d97869f `# golang:1.25.13-alpine linux/arm64` \
|
||||
golang@sha256:df4c4a0eeb85873e0122c6e2eb1b436f3131576f572505c1ea61954b00fa6460 `# golang:1.27.1-alpine linux/arm64` \
|
||||
sh -c "apk add --no-cache gcc musl-dev && go build -trimpath \
|
||||
-ldflags='-X github.com/phishingclub/phishingclub/version.hash=ph${{ steps.get_version.outputs.HASH }} -X github.com/phishingclub/phishingclub/version.version=${{ steps.get_version.outputs.VERSION }} -linkmode=external -extldflags=-static' \
|
||||
-tags production -o ../build/arm64/phishingclub main.go"
|
||||
|
||||
@@ -82,7 +82,7 @@ jobs:
|
||||
-v "$(pwd)":/app \
|
||||
-v /tmp/go-build-cache:/root/.cache/go-build \
|
||||
-w /app/backend \
|
||||
golang@sha256:fec9672136e7ccfa4f6cf59177987de3d8ee9b14953b6ea5485dbd08bf7b4aed `# golang:1.25.13-alpine linux/amd64` \
|
||||
golang@sha256:f86f1a6701e3dcc445fec097a42f78b758f15950ccf032c2d3e54e2754d32fdb `# golang:1.27.1-alpine linux/amd64` \
|
||||
sh -c "apk add --no-cache gcc musl-dev && go build -trimpath \
|
||||
-ldflags='-X github.com/phishingclub/phishingclub/version.hash=ph${{ steps.get_version.outputs.HASH }} -X github.com/phishingclub/phishingclub/version.version=${{ steps.get_version.outputs.VERSION }} -linkmode=external -extldflags=-static' \
|
||||
-tags production -o ../build/amd64/phishingclub main.go"
|
||||
@@ -95,7 +95,7 @@ jobs:
|
||||
-v "$(pwd)":/app \
|
||||
-v /tmp/go-build-cache:/root/.cache/go-build \
|
||||
-w /app/backend \
|
||||
golang@sha256:0ae9afcc66ea58a852d29541782ad911f6151e073355e8eb0f10f91f6d97869f `# golang:1.25.13-alpine linux/arm64` \
|
||||
golang@sha256:df4c4a0eeb85873e0122c6e2eb1b436f3131576f572505c1ea61954b00fa6460 `# golang:1.27.1-alpine linux/arm64` \
|
||||
sh -c "apk add --no-cache gcc musl-dev && go build -trimpath \
|
||||
-ldflags='-X github.com/phishingclub/phishingclub/version.hash=ph${{ steps.get_version.outputs.HASH }} -X github.com/phishingclub/phishingclub/version.version=${{ steps.get_version.outputs.VERSION }} -linkmode=external -extldflags=-static' \
|
||||
-tags production -o ../build/arm64/phishingclub main.go"
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
# development docker file
|
||||
FROM golang:1.25.13@sha256:cbff9d1a9041b316010f2da6b701b6c0d597718cb90928c85eb597334a0d23d4
|
||||
FROM golang:1.27.1@sha256:f44f6e88636cfb311f9ebace870ded69d943f227bb3cb27d32ffd84ea18c43ea
|
||||
|
||||
EXPOSE 8000 8001
|
||||
|
||||
|
||||
+21
-25
@@ -1,6 +1,6 @@
|
||||
module github.com/phishingclub/phishingclub
|
||||
|
||||
go 1.25.13
|
||||
go 1.27
|
||||
|
||||
require (
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2
|
||||
@@ -14,7 +14,8 @@ require (
|
||||
github.com/charmbracelet/lipgloss v1.1.0
|
||||
github.com/coreos/go-oidc/v3 v3.11.0
|
||||
github.com/dop251/goja v0.0.0-20260226184354-913bd86fb70c
|
||||
github.com/enetx/surf v1.0.141
|
||||
github.com/enetx/g v1.1.0
|
||||
github.com/enetx/surf v1.0.206
|
||||
github.com/exaring/ja4plus v0.0.2
|
||||
github.com/fatih/color v1.15.0
|
||||
github.com/gin-contrib/zap v1.1.4
|
||||
@@ -23,18 +24,18 @@ require (
|
||||
github.com/go-rod/rod v0.116.2
|
||||
github.com/google/uuid v1.3.1
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/klauspost/compress v1.18.1
|
||||
github.com/klauspost/compress v1.18.7
|
||||
github.com/oapi-codegen/nullable v1.1.0
|
||||
github.com/pquerna/otp v1.4.0
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/wneessen/go-mail v0.7.2
|
||||
github.com/yeqown/go-qrcode/v2 v2.2.4
|
||||
go.uber.org/zap v1.27.0
|
||||
golang.org/x/crypto v0.53.0
|
||||
golang.org/x/mod v0.37.0
|
||||
golang.org/x/net v0.56.0
|
||||
golang.org/x/crypto v0.56.0
|
||||
golang.org/x/mod v0.40.0
|
||||
golang.org/x/net v0.58.0
|
||||
golang.org/x/oauth2 v0.27.0
|
||||
golang.org/x/sync v0.21.0
|
||||
golang.org/x/sync v0.22.0
|
||||
golang.org/x/time v0.14.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gorm.io/driver/sqlite v1.6.0
|
||||
@@ -56,26 +57,20 @@ require (
|
||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dlclark/regexp2 v1.11.4 // indirect
|
||||
github.com/enetx/g v1.0.194 // indirect
|
||||
github.com/enetx/http v1.0.19 // indirect
|
||||
github.com/enetx/http2 v1.0.20 // indirect
|
||||
github.com/enetx/iter v0.0.0-20250912135656-f1583323588f // indirect
|
||||
github.com/enetx/uquic v0.0.0-20250922085439-3a2249d297c9 // indirect
|
||||
github.com/enetx/utls v0.0.0-20251024090823-efbd194d7328 // indirect
|
||||
github.com/enetx/http v1.0.29 // indirect
|
||||
github.com/enetx/http2 v1.0.26 // indirect
|
||||
github.com/enetx/http3 v1.0.9 // indirect
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.5 // indirect
|
||||
github.com/gaukas/clienthellod v0.4.2 // indirect
|
||||
github.com/gaukas/godicttls v0.0.4 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.4 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.22.0 // indirect
|
||||
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||
github.com/goccy/go-json v0.10.3 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
|
||||
github.com/google/gopacket v1.1.19 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
@@ -97,16 +92,17 @@ require (
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||
github.com/muesli/termenv v0.16.0 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.27.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/quic-go/qpack v0.5.1 // indirect
|
||||
github.com/refraction-networking/utls v1.8.2 // indirect
|
||||
github.com/quic-go/qpack v0.6.0 // indirect
|
||||
github.com/quic-go/quic-go v0.61.0 // indirect
|
||||
github.com/refraction-networking/utls v1.8.3-0.20260623165621-880e27d8b0e5 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
github.com/wzshiming/socks5 v0.6.0 // indirect
|
||||
github.com/wzshiming/socks5 v0.7.0 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
github.com/yeqown/reedsolomon v1.0.0 // indirect
|
||||
github.com/ysmood/fetchup v0.2.3 // indirect
|
||||
@@ -119,8 +115,8 @@ require (
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/arch v0.9.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
|
||||
golang.org/x/sys v0.46.0 // indirect
|
||||
golang.org/x/text v0.39.0 // indirect
|
||||
golang.org/x/tools v0.47.0 // indirect
|
||||
golang.org/x/sys v0.47.0 // indirect
|
||||
golang.org/x/text v0.41.0 // indirect
|
||||
golang.org/x/tools v0.49.0 // indirect
|
||||
google.golang.org/protobuf v1.36.7 // indirect
|
||||
)
|
||||
+40
-98
@@ -51,20 +51,16 @@ github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yA
|
||||
github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dop251/goja v0.0.0-20260226184354-913bd86fb70c h1:hIlkLbQ+tYoUqlG42LnxwGcohL5jaGqD8mGeJWavm8A=
|
||||
github.com/dop251/goja v0.0.0-20260226184354-913bd86fb70c/go.mod h1:MxLav0peU43GgvwVgNbLAj1s/bSGboKkhuULvq/7hx4=
|
||||
github.com/enetx/g v1.0.194 h1:lI/eicj+Qdcdt1xBUhaHv3M/ujN4v+WXYZDZYD1Dxuo=
|
||||
github.com/enetx/g v1.0.194/go.mod h1:B3YULbT/hAx9+p2Q8GHrsTmjjM19iz1Rcdz3Y9+kSg4=
|
||||
github.com/enetx/http v1.0.19 h1:4W97CyqKrPiR16wEm6UOesqNrt8l4RsVMjZHz6+I84E=
|
||||
github.com/enetx/http v1.0.19/go.mod h1:1f4mytfF/SfjATEJnynpwGS6aa1ALjb8DtmYgFVblY0=
|
||||
github.com/enetx/http2 v1.0.20 h1:181A9wyzQOxvj+LqIJ/oGGx6Vl0Ry1wTLOfX+zTnaS0=
|
||||
github.com/enetx/http2 v1.0.20/go.mod h1:t54ex5HIS8V1+2j6cvEOv6umlrHsbUPFKQ54nYB58Nk=
|
||||
github.com/enetx/iter v0.0.0-20250912135656-f1583323588f h1:GUW+4AWfECIEJ9oAxgEAVGCpaozMCjRiUYnuR6Q0bCQ=
|
||||
github.com/enetx/iter v0.0.0-20250912135656-f1583323588f/go.mod h1:oMZN8hGLUpi7QBlMEUqailocNy0NFAO/7Lu+Nwh9HMM=
|
||||
github.com/enetx/surf v1.0.141 h1:IEFRySO/2AjXtE3J0jPQmRWBqwTM3KTonrf9iCJ3Y+A=
|
||||
github.com/enetx/surf v1.0.141/go.mod h1:taI3kiTjMQPh7phIK0VN3Nz+sOJf5Gkam1tJ5UDy1Q0=
|
||||
github.com/enetx/uquic v0.0.0-20250922085439-3a2249d297c9 h1:jAuNOIRE/Lk2vpSRgbzSDup34J3Ay5cGRtwKoeZUDGg=
|
||||
github.com/enetx/uquic v0.0.0-20250922085439-3a2249d297c9/go.mod h1:Yt1e0NLtImehclHlurlZ6Pji7PqHuNZpjAgjwuATsQw=
|
||||
github.com/enetx/utls v0.0.0-20251024090823-efbd194d7328 h1:C1RHsl1CKSDiXumSHpJzk2jSIhvPn2x2MFGeg2ZnQIQ=
|
||||
github.com/enetx/utls v0.0.0-20251024090823-efbd194d7328/go.mod h1:oeYX2NTbsqs75GBc4Vv56KlItghZAveDkb4dGUwRtkM=
|
||||
github.com/enetx/g v1.1.0 h1:Hd8cFCeLOiLQIoqhhe1CaRp9/lgi1mFRC91H/+NRxis=
|
||||
github.com/enetx/g v1.1.0/go.mod h1:nVdbiRFOVdOb/TDVPB3gMaMd8161Wv6dQZYdqvg9IHE=
|
||||
github.com/enetx/http v1.0.29 h1:B+NVXEN7vTAVYI+kZbx06Jqh/WwhHfUT8nG6OiRpQ78=
|
||||
github.com/enetx/http v1.0.29/go.mod h1:1f4mytfF/SfjATEJnynpwGS6aa1ALjb8DtmYgFVblY0=
|
||||
github.com/enetx/http2 v1.0.26 h1:wy3lYGVwnIUY4Q+gyPPQCJ1a+BMXD1B7Unpyc/Csrxc=
|
||||
github.com/enetx/http2 v1.0.26/go.mod h1:t54ex5HIS8V1+2j6cvEOv6umlrHsbUPFKQ54nYB58Nk=
|
||||
github.com/enetx/http3 v1.0.9 h1:TN/QdJNu463zgyJc84XqHevjaOkXGc6Suq+u32v1cvU=
|
||||
github.com/enetx/http3 v1.0.9/go.mod h1:z6JWrv7d27bY8av5zSLfIXSCCNbdGphM371JNLKE07g=
|
||||
github.com/enetx/surf v1.0.206 h1:PO5oOTxuzejBH78VbFYzr0zviV2RVa5Ord65Sl3OWW8=
|
||||
github.com/enetx/surf v1.0.206/go.mod h1:E2aV9TcL9zF/Zm6eXUgzF93VFozfALap2QQhEeBRc2A=
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
||||
github.com/exaring/ja4plus v0.0.2 h1:lfLUicnWFuIlAVHPaq9t0PfSC++AOt1vt+PXg3+Hz5w=
|
||||
@@ -73,28 +69,16 @@ github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
|
||||
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
|
||||
github.com/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4=
|
||||
github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4=
|
||||
github.com/gaukas/clienthellod v0.4.2 h1:LPJ+LSeqt99pqeCV4C0cllk+pyWmERisP7w6qWr7eqE=
|
||||
github.com/gaukas/clienthellod v0.4.2/go.mod h1:M57+dsu0ZScvmdnNxaxsDPM46WhSEdPYAOdNgfL7IKA=
|
||||
github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=
|
||||
github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-contrib/zap v1.1.4 h1:xvxTybg6XBdNtcQLH3Tf0lFr4vhDkwzgLLrIGlNTqIo=
|
||||
github.com/gin-contrib/zap v1.1.4/go.mod h1:7lgEpe91kLbeJkwBTPgtVBy4zMa6oSBEcvj662diqKQ=
|
||||
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
||||
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
github.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs=
|
||||
github.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo=
|
||||
github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M=
|
||||
github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk=
|
||||
github.com/gkampitakis/go-snaps v0.5.15 h1:amyJrvM1D33cPHwVrjo9jQxX8g/7E2wYdZ+01KS3zGE=
|
||||
github.com/gkampitakis/go-snaps v0.5.15/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc=
|
||||
github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk=
|
||||
github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
||||
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
|
||||
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
@@ -107,19 +91,13 @@ github.com/go-rod/rod v0.116.2 h1:A5t2Ky2A+5eD/ZJQr1EfsQSe5rms5Xof/qj296e+ZqA=
|
||||
github.com/go-rod/rod v0.116.2/go.mod h1:H+CMO9SCNc2TJ2WfrG+pKhITz57uGNYU43qYHh438Mg=
|
||||
github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
|
||||
github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
|
||||
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
|
||||
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
|
||||
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
|
||||
@@ -130,12 +108,10 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE=
|
||||
github.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co=
|
||||
github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0=
|
||||
github.com/klauspost/compress v1.18.7 h1:aUyZsS4kH3QTKurYhAOwAHxllVPnOthb3vPfnF1Ehjw=
|
||||
github.com/klauspost/compress v1.18.7/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
||||
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
|
||||
@@ -153,8 +129,6 @@ github.com/libdns/libdns v0.2.1 h1:Wu59T7wSHRgtA0cfxC+n1c/e+O3upJGWytknkmFEDis=
|
||||
github.com/libdns/libdns v0.2.1/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo=
|
||||
github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
@@ -166,8 +140,6 @@ github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6T
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/mfridman/tparse v0.18.0 h1:wh6dzOKaIwkUGyKgOntDW4liXSo37qg5AXbIhkMV3vE=
|
||||
github.com/mfridman/tparse v0.18.0/go.mod h1:gEvqZTuCgEhPbYk/2lS3Kcxg1GmTxxU7kTC8DvP0i/A=
|
||||
github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30=
|
||||
github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE=
|
||||
github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo=
|
||||
@@ -185,20 +157,20 @@ github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc
|
||||
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
|
||||
github.com/oapi-codegen/nullable v1.1.0 h1:eAh8JVc5430VtYVnq00Hrbpag9PFRGWLjxR1/3KntMs=
|
||||
github.com/oapi-codegen/nullable v1.1.0/go.mod h1:KUZ3vUzkmEKY90ksAmit2+5juDIhIZhfDl+0PwOQlFY=
|
||||
github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns=
|
||||
github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
|
||||
github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
|
||||
github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg=
|
||||
github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
||||
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
||||
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
||||
github.com/refraction-networking/utls v1.8.2 h1:j4Q1gJj0xngdeH+Ox/qND11aEfhpgoEvV+S9iJ2IdQo=
|
||||
github.com/refraction-networking/utls v1.8.2/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
|
||||
github.com/quic-go/go-ossfuzz-seeds v0.1.0 h1:APacT+iIaNF6fd8AGEiN3bT/Jtkd2jz4v4TzM7MFjy0=
|
||||
github.com/quic-go/go-ossfuzz-seeds v0.1.0/go.mod h1:3IOHRbJIc+L6YKMwfDtJAM9Vj9k0YY4muhuyUYk5tbk=
|
||||
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
||||
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
|
||||
github.com/quic-go/quic-go v0.61.0 h1:ui88A53s8MSVYLC56en0KQ17HARk+9986Dn0SBfKNvA=
|
||||
github.com/quic-go/quic-go v0.61.0/go.mod h1:9So2anK4Tp22URSQq00k+Vo2PNkle96ycDPDHL4s9vs=
|
||||
github.com/refraction-networking/utls v1.8.3-0.20260623165621-880e27d8b0e5 h1:I/IOtgzmM5XMCLflEYU1UEl7HMBF9k256yXkS4z6dtM=
|
||||
github.com/refraction-networking/utls v1.8.3-0.20260623165621-880e27d8b0e5/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
@@ -215,24 +187,17 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
||||
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
||||
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/wneessen/go-mail v0.7.2 h1:xxPnhZ6IZLSgxShebmZ6DPKh1b6OJcoHfzy7UjOkzS8=
|
||||
github.com/wneessen/go-mail v0.7.2/go.mod h1:+TkW6QP3EVkgTEqHtVmnAE/1MRhmzb8Y9/W3pweuS+k=
|
||||
github.com/wzshiming/socks5 v0.6.0 h1:p5RFNs21Byv+Tnc7chYRFtz0SzK8TcqKV7xFkXSeZvw=
|
||||
github.com/wzshiming/socks5 v0.6.0/go.mod h1:BvCAqlzocQN5xwLjBZDBbvWlrx8sCYSSbHEOf2wZgT0=
|
||||
github.com/wzshiming/socks5 v0.7.0 h1:euJ+U48WrvVngi+opC8vAnpZ5sK12y1C2hPvb1f48Rg=
|
||||
github.com/wzshiming/socks5 v0.7.0/go.mod h1:BvCAqlzocQN5xwLjBZDBbvWlrx8sCYSSbHEOf2wZgT0=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||
@@ -270,46 +235,31 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/arch v0.9.0 h1:ub9TgUInamJ8mrZIGlBG6/4TqWeMszd4N8lNorbrr6k=
|
||||
golang.org/x/arch v0.9.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
|
||||
golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc=
|
||||
golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
|
||||
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
|
||||
golang.org/x/crypto v0.56.0 h1:GUh5Ii4J5jtcseSMiRqr1jXCNHoxjeV9Fmekc2oLy6Y=
|
||||
golang.org/x/crypto v0.56.0/go.mod h1:OMW5y6CY9l38uPLmxU6l6pwcXp1obtLo3e6gT7gQR2I=
|
||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY=
|
||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
|
||||
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
|
||||
golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
|
||||
golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs=
|
||||
golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
|
||||
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
|
||||
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
|
||||
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
|
||||
golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To=
|
||||
golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU=
|
||||
golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=
|
||||
golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
|
||||
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
|
||||
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -319,10 +269,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
|
||||
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
|
||||
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
||||
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
@@ -331,27 +279,21 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
||||
golang.org/x/text v0.39.0 h1:UbZz4pLOvn600D6Oh6GGEI6VAmndrEBLv8/6BEXzyus=
|
||||
golang.org/x/text v0.39.0/go.mod h1:3UwRclnC2g0TU9x8PZiyfOajCd1zaUNHF9cvqcQZ+ZM=
|
||||
golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=
|
||||
golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M=
|
||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
|
||||
golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
|
||||
golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q=
|
||||
golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA=
|
||||
golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI=
|
||||
golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A=
|
||||
google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/enetx/g"
|
||||
"github.com/enetx/surf"
|
||||
"github.com/phishingclub/phishingclub/service"
|
||||
)
|
||||
@@ -103,7 +104,7 @@ func (m *ProxyHandler) createSurfClient(userAgent string, proxyConfig *service.P
|
||||
m.logger.Debugw("applying chrome browser impersonation")
|
||||
case profile.isFirefox:
|
||||
// firefox impersonation
|
||||
builder = impersonate.FireFox()
|
||||
builder = impersonate.Firefox()
|
||||
m.logger.Debugw("applying firefox browser impersonation")
|
||||
case profile.isSafari:
|
||||
// safari uses webkit - default to chrome for now as surf doesn't have safari profile
|
||||
@@ -141,17 +142,20 @@ func (m *ProxyHandler) createSurfClient(userAgent string, proxyConfig *service.P
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
builder = builder.Proxy(proxyURL.String())
|
||||
builder = builder.Proxy(g.String(proxyURL.String()))
|
||||
m.logger.Debugw("configured surf client with proxy",
|
||||
"proxy", proxyURL.String(),
|
||||
)
|
||||
}
|
||||
|
||||
// build the client
|
||||
client := builder.Build()
|
||||
result := builder.Build()
|
||||
if result.IsErr() {
|
||||
return nil, result.Err()
|
||||
}
|
||||
|
||||
// convert surf client to standard http.Client for compatibility
|
||||
return client.Std(), nil
|
||||
return result.Ok().Std(), nil
|
||||
}
|
||||
|
||||
// createHTTPClientWithImpersonation creates surf http client with optional impersonation
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
_fuzz/
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
run:
|
||||
deadline: 2m
|
||||
|
||||
linters:
|
||||
disable-all: true
|
||||
enable:
|
||||
- misspell
|
||||
- govet
|
||||
- staticcheck
|
||||
- errcheck
|
||||
- unparam
|
||||
- ineffassign
|
||||
- nakedret
|
||||
- gocyclo
|
||||
- dupl
|
||||
- goimports
|
||||
- revive
|
||||
- gosec
|
||||
- gosimple
|
||||
- typecheck
|
||||
- unused
|
||||
|
||||
linters-settings:
|
||||
gofmt:
|
||||
simplify: true
|
||||
dupl:
|
||||
threshold: 600
|
||||
-268
@@ -1,268 +0,0 @@
|
||||
# Changelog
|
||||
|
||||
## 3.4.0 (2025-06-27)
|
||||
|
||||
### Added
|
||||
|
||||
- #268: Added property to Constraints to include prereleases for Check and Validate
|
||||
|
||||
### Changed
|
||||
|
||||
- #263: Updated Go testing for 1.24, 1.23, and 1.22
|
||||
- #269: Updated the error message handling for message case and wrapping errors
|
||||
- #266: Restore the ability to have leading 0's when parsing with NewVersion.
|
||||
Opt-out of this by setting CoerceNewVersion to false.
|
||||
|
||||
### Fixed
|
||||
|
||||
- #257: Fixed the CodeQL link (thanks @dmitris)
|
||||
- #262: Restored detailed errors when failed to parse with NewVersion. Opt-out
|
||||
of this by setting DetailedNewVersionErrors to false for faster performance.
|
||||
- #267: Handle pre-releases for an "and" group if one constraint includes them
|
||||
|
||||
## 3.3.1 (2024-11-19)
|
||||
|
||||
### Fixed
|
||||
|
||||
- #253: Fix for allowing some version that were invalid
|
||||
|
||||
## 3.3.0 (2024-08-27)
|
||||
|
||||
### Added
|
||||
|
||||
- #238: Add LessThanEqual and GreaterThanEqual functions (thanks @grosser)
|
||||
- #213: nil version equality checking (thanks @KnutZuidema)
|
||||
|
||||
### Changed
|
||||
|
||||
- #241: Simplify StrictNewVersion parsing (thanks @grosser)
|
||||
- Testing support up through Go 1.23
|
||||
- Minimum version set to 1.21 as this is what's tested now
|
||||
- Fuzz testing now supports caching
|
||||
|
||||
## 3.2.1 (2023-04-10)
|
||||
|
||||
### Changed
|
||||
|
||||
- #198: Improved testing around pre-release names
|
||||
- #200: Improved code scanning with addition of CodeQL
|
||||
- #201: Testing now includes Go 1.20. Go 1.17 has been dropped
|
||||
- #202: Migrated Fuzz testing to Go built-in Fuzzing. CI runs daily
|
||||
- #203: Docs updated for security details
|
||||
|
||||
### Fixed
|
||||
|
||||
- #199: Fixed issue with range transformations
|
||||
|
||||
## 3.2.0 (2022-11-28)
|
||||
|
||||
### Added
|
||||
|
||||
- #190: Added text marshaling and unmarshaling
|
||||
- #167: Added JSON marshalling for constraints (thanks @SimonTheLeg)
|
||||
- #173: Implement encoding.TextMarshaler and encoding.TextUnmarshaler on Version (thanks @MarkRosemaker)
|
||||
- #179: Added New() version constructor (thanks @kazhuravlev)
|
||||
|
||||
### Changed
|
||||
|
||||
- #182/#183: Updated CI testing setup
|
||||
|
||||
### Fixed
|
||||
|
||||
- #186: Fixing issue where validation of constraint section gave false positives
|
||||
- #176: Fix constraints check with *-0 (thanks @mtt0)
|
||||
- #181: Fixed Caret operator (^) gives unexpected results when the minor version in constraint is 0 (thanks @arshchimni)
|
||||
- #161: Fixed godoc (thanks @afirth)
|
||||
|
||||
## 3.1.1 (2020-11-23)
|
||||
|
||||
### Fixed
|
||||
|
||||
- #158: Fixed issue with generated regex operation order that could cause problem
|
||||
|
||||
## 3.1.0 (2020-04-15)
|
||||
|
||||
### Added
|
||||
|
||||
- #131: Add support for serializing/deserializing SQL (thanks @ryancurrah)
|
||||
|
||||
### Changed
|
||||
|
||||
- #148: More accurate validation messages on constraints
|
||||
|
||||
## 3.0.3 (2019-12-13)
|
||||
|
||||
### Fixed
|
||||
|
||||
- #141: Fixed issue with <= comparison
|
||||
|
||||
## 3.0.2 (2019-11-14)
|
||||
|
||||
### Fixed
|
||||
|
||||
- #134: Fixed broken constraint checking with ^0.0 (thanks @krmichelos)
|
||||
|
||||
## 3.0.1 (2019-09-13)
|
||||
|
||||
### Fixed
|
||||
|
||||
- #125: Fixes issue with module path for v3
|
||||
|
||||
## 3.0.0 (2019-09-12)
|
||||
|
||||
This is a major release of the semver package which includes API changes. The Go
|
||||
API is compatible with ^1. The Go API was not changed because many people are using
|
||||
`go get` without Go modules for their applications and API breaking changes cause
|
||||
errors which we have or would need to support.
|
||||
|
||||
The changes in this release are the handling based on the data passed into the
|
||||
functions. These are described in the added and changed sections below.
|
||||
|
||||
### Added
|
||||
|
||||
- StrictNewVersion function. This is similar to NewVersion but will return an
|
||||
error if the version passed in is not a strict semantic version. For example,
|
||||
1.2.3 would pass but v1.2.3 or 1.2 would fail because they are not strictly
|
||||
speaking semantic versions. This function is faster, performs fewer operations,
|
||||
and uses fewer allocations than NewVersion.
|
||||
- Fuzzing has been performed on NewVersion, StrictNewVersion, and NewConstraint.
|
||||
The Makefile contains the operations used. For more information on you can start
|
||||
on Wikipedia at https://en.wikipedia.org/wiki/Fuzzing
|
||||
- Now using Go modules
|
||||
|
||||
### Changed
|
||||
|
||||
- NewVersion has proper prerelease and metadata validation with error messages
|
||||
to signal an issue with either of them
|
||||
- ^ now operates using a similar set of rules to npm/js and Rust/Cargo. If the
|
||||
version is >=1 the ^ ranges works the same as v1. For major versions of 0 the
|
||||
rules have changed. The minor version is treated as the stable version unless
|
||||
a patch is specified and then it is equivalent to =. One difference from npm/js
|
||||
is that prereleases there are only to a specific version (e.g. 1.2.3).
|
||||
Prereleases here look over multiple versions and follow semantic version
|
||||
ordering rules. This pattern now follows along with the expected and requested
|
||||
handling of this packaged by numerous users.
|
||||
|
||||
## 1.5.0 (2019-09-11)
|
||||
|
||||
### Added
|
||||
|
||||
- #103: Add basic fuzzing for `NewVersion()` (thanks @jesse-c)
|
||||
|
||||
### Changed
|
||||
|
||||
- #82: Clarify wildcard meaning in range constraints and update tests for it (thanks @greysteil)
|
||||
- #83: Clarify caret operator range for pre-1.0.0 dependencies (thanks @greysteil)
|
||||
- #72: Adding docs comment pointing to vert for a cli
|
||||
- #71: Update the docs on pre-release comparator handling
|
||||
- #89: Test with new go versions (thanks @thedevsaddam)
|
||||
- #87: Added $ to ValidPrerelease for better validation (thanks @jeremycarroll)
|
||||
|
||||
### Fixed
|
||||
|
||||
- #78: Fix unchecked error in example code (thanks @ravron)
|
||||
- #70: Fix the handling of pre-releases and the 0.0.0 release edge case
|
||||
- #97: Fixed copyright file for proper display on GitHub
|
||||
- #107: Fix handling prerelease when sorting alphanum and num
|
||||
- #109: Fixed where Validate sometimes returns wrong message on error
|
||||
|
||||
## 1.4.2 (2018-04-10)
|
||||
|
||||
### Changed
|
||||
|
||||
- #72: Updated the docs to point to vert for a console appliaction
|
||||
- #71: Update the docs on pre-release comparator handling
|
||||
|
||||
### Fixed
|
||||
|
||||
- #70: Fix the handling of pre-releases and the 0.0.0 release edge case
|
||||
|
||||
## 1.4.1 (2018-04-02)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed #64: Fix pre-release precedence issue (thanks @uudashr)
|
||||
|
||||
## 1.4.0 (2017-10-04)
|
||||
|
||||
### Changed
|
||||
|
||||
- #61: Update NewVersion to parse ints with a 64bit int size (thanks @zknill)
|
||||
|
||||
## 1.3.1 (2017-07-10)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed #57: number comparisons in prerelease sometimes inaccurate
|
||||
|
||||
## 1.3.0 (2017-05-02)
|
||||
|
||||
### Added
|
||||
|
||||
- #45: Added json (un)marshaling support (thanks @mh-cbon)
|
||||
- Stability marker. See https://masterminds.github.io/stability/
|
||||
|
||||
### Fixed
|
||||
|
||||
- #51: Fix handling of single digit tilde constraint (thanks @dgodd)
|
||||
|
||||
### Changed
|
||||
|
||||
- #55: The godoc icon moved from png to svg
|
||||
|
||||
## 1.2.3 (2017-04-03)
|
||||
|
||||
### Fixed
|
||||
|
||||
- #46: Fixed 0.x.x and 0.0.x in constraints being treated as *
|
||||
|
||||
## Release 1.2.2 (2016-12-13)
|
||||
|
||||
### Fixed
|
||||
|
||||
- #34: Fixed issue where hyphen range was not working with pre-release parsing.
|
||||
|
||||
## Release 1.2.1 (2016-11-28)
|
||||
|
||||
### Fixed
|
||||
|
||||
- #24: Fixed edge case issue where constraint "> 0" does not handle "0.0.1-alpha"
|
||||
properly.
|
||||
|
||||
## Release 1.2.0 (2016-11-04)
|
||||
|
||||
### Added
|
||||
|
||||
- #20: Added MustParse function for versions (thanks @adamreese)
|
||||
- #15: Added increment methods on versions (thanks @mh-cbon)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Issue #21: Per the SemVer spec (section 9) a pre-release is unstable and
|
||||
might not satisfy the intended compatibility. The change here ignores pre-releases
|
||||
on constraint checks (e.g., ~ or ^) when a pre-release is not part of the
|
||||
constraint. For example, `^1.2.3` will ignore pre-releases while
|
||||
`^1.2.3-alpha` will include them.
|
||||
|
||||
## Release 1.1.1 (2016-06-30)
|
||||
|
||||
### Changed
|
||||
|
||||
- Issue #9: Speed up version comparison performance (thanks @sdboyer)
|
||||
- Issue #8: Added benchmarks (thanks @sdboyer)
|
||||
- Updated Go Report Card URL to new location
|
||||
- Updated Readme to add code snippet formatting (thanks @mh-cbon)
|
||||
- Updating tagging to v[SemVer] structure for compatibility with other tools.
|
||||
|
||||
## Release 1.1.0 (2016-03-11)
|
||||
|
||||
- Issue #2: Implemented validation to provide reasons a versions failed a
|
||||
constraint.
|
||||
|
||||
## Release 1.0.1 (2015-12-31)
|
||||
|
||||
- Fixed #1: * constraint failing on valid versions.
|
||||
|
||||
## Release 1.0.0 (2015-10-20)
|
||||
|
||||
- Initial release
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
Copyright (C) 2014-2019, Matt Butcher and Matt Farina
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
-31
@@ -1,31 +0,0 @@
|
||||
GOPATH=$(shell go env GOPATH)
|
||||
GOLANGCI_LINT=$(GOPATH)/bin/golangci-lint
|
||||
|
||||
.PHONY: lint
|
||||
lint: $(GOLANGCI_LINT)
|
||||
@echo "==> Linting codebase"
|
||||
@$(GOLANGCI_LINT) run
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
@echo "==> Running tests"
|
||||
GO111MODULE=on go test -v
|
||||
|
||||
.PHONY: test-cover
|
||||
test-cover:
|
||||
@echo "==> Running Tests with coverage"
|
||||
GO111MODULE=on go test -cover .
|
||||
|
||||
.PHONY: fuzz
|
||||
fuzz:
|
||||
@echo "==> Running Fuzz Tests"
|
||||
go env GOCACHE
|
||||
go test -fuzz=FuzzNewVersion -fuzztime=15s .
|
||||
go test -fuzz=FuzzStrictNewVersion -fuzztime=15s .
|
||||
go test -fuzz=FuzzNewConstraint -fuzztime=15s .
|
||||
|
||||
$(GOLANGCI_LINT):
|
||||
# Install golangci-lint. The configuration for it is in the .golangci.yml
|
||||
# file in the root of the repository
|
||||
echo ${GOPATH}
|
||||
curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(GOPATH)/bin v1.56.2
|
||||
-274
@@ -1,274 +0,0 @@
|
||||
# SemVer
|
||||
|
||||
The `semver` package provides the ability to work with [Semantic Versions](http://semver.org) in Go. Specifically it provides the ability to:
|
||||
|
||||
* Parse semantic versions
|
||||
* Sort semantic versions
|
||||
* Check if a semantic version fits within a set of constraints
|
||||
* Optionally work with a `v` prefix
|
||||
|
||||
[](https://masterminds.github.io/stability/active.html)
|
||||
[](https://github.com/Masterminds/semver/actions)
|
||||
[](https://pkg.go.dev/github.com/Masterminds/semver/v3)
|
||||
[](https://goreportcard.com/report/github.com/Masterminds/semver)
|
||||
|
||||
## Package Versions
|
||||
|
||||
Note, import `github.com/Masterminds/semver/v3` to use the latest version.
|
||||
|
||||
There are three major versions fo the `semver` package.
|
||||
|
||||
* 3.x.x is the stable and active version. This version is focused on constraint
|
||||
compatibility for range handling in other tools from other languages. It has
|
||||
a similar API to the v1 releases. The development of this version is on the master
|
||||
branch. The documentation for this version is below.
|
||||
* 2.x was developed primarily for [dep](https://github.com/golang/dep). There are
|
||||
no tagged releases and the development was performed by [@sdboyer](https://github.com/sdboyer).
|
||||
There are API breaking changes from v1. This version lives on the [2.x branch](https://github.com/Masterminds/semver/tree/2.x).
|
||||
* 1.x.x is the original release. It is no longer maintained. You should use the
|
||||
v3 release instead. You can read the documentation for the 1.x.x release
|
||||
[here](https://github.com/Masterminds/semver/blob/release-1/README.md).
|
||||
|
||||
## Parsing Semantic Versions
|
||||
|
||||
There are two functions that can parse semantic versions. The `StrictNewVersion`
|
||||
function only parses valid version 2 semantic versions as outlined in the
|
||||
specification. The `NewVersion` function attempts to coerce a version into a
|
||||
semantic version and parse it. For example, if there is a leading v or a version
|
||||
listed without all 3 parts (e.g. `v1.2`) it will attempt to coerce it into a valid
|
||||
semantic version (e.g., 1.2.0). In both cases a `Version` object is returned
|
||||
that can be sorted, compared, and used in constraints.
|
||||
|
||||
When parsing a version an error is returned if there is an issue parsing the
|
||||
version. For example,
|
||||
|
||||
v, err := semver.NewVersion("1.2.3-beta.1+build345")
|
||||
|
||||
The version object has methods to get the parts of the version, compare it to
|
||||
other versions, convert the version back into a string, and get the original
|
||||
string. Getting the original string is useful if the semantic version was coerced
|
||||
into a valid form.
|
||||
|
||||
There are package level variables that affect how `NewVersion` handles parsing.
|
||||
|
||||
- `CoerceNewVersion` is `true` by default. When set to `true` it coerces non-compliant
|
||||
versions into SemVer. For example, allowing a leading 0 in a major, minor, or patch
|
||||
part. This enables the use of CalVer in versions even when not compliant with SemVer.
|
||||
When set to `false` less coercion work is done.
|
||||
- `DetailedNewVersionErrors` provides more detailed errors. It only has an affect when
|
||||
`CoerceNewVersion` is set to `false`. When `DetailedNewVersionErrors` is set to `true`
|
||||
it can provide some more insight into why a version is invalid. Setting
|
||||
`DetailedNewVersionErrors` to `false` is faster on performance but provides less
|
||||
detailed error messages if a version fails to parse.
|
||||
|
||||
## Sorting Semantic Versions
|
||||
|
||||
A set of versions can be sorted using the `sort` package from the standard library.
|
||||
For example,
|
||||
|
||||
```go
|
||||
raw := []string{"1.2.3", "1.0", "1.3", "2", "0.4.2",}
|
||||
vs := make([]*semver.Version, len(raw))
|
||||
for i, r := range raw {
|
||||
v, err := semver.NewVersion(r)
|
||||
if err != nil {
|
||||
t.Errorf("Error parsing version: %s", err)
|
||||
}
|
||||
|
||||
vs[i] = v
|
||||
}
|
||||
|
||||
sort.Sort(semver.Collection(vs))
|
||||
```
|
||||
|
||||
## Checking Version Constraints
|
||||
|
||||
There are two methods for comparing versions. One uses comparison methods on
|
||||
`Version` instances and the other uses `Constraints`. There are some important
|
||||
differences to notes between these two methods of comparison.
|
||||
|
||||
1. When two versions are compared using functions such as `Compare`, `LessThan`,
|
||||
and others it will follow the specification and always include pre-releases
|
||||
within the comparison. It will provide an answer that is valid with the
|
||||
comparison section of the spec at https://semver.org/#spec-item-11
|
||||
2. When constraint checking is used for checks or validation it will follow a
|
||||
different set of rules that are common for ranges with tools like npm/js
|
||||
and Rust/Cargo. This includes considering pre-releases to be invalid if the
|
||||
ranges does not include one. If you want to have it include pre-releases a
|
||||
simple solution is to include `-0` in your range.
|
||||
3. Constraint ranges can have some complex rules including the shorthand use of
|
||||
~ and ^. For more details on those see the options below.
|
||||
|
||||
There are differences between the two methods or checking versions because the
|
||||
comparison methods on `Version` follow the specification while comparison ranges
|
||||
are not part of the specification. Different packages and tools have taken it
|
||||
upon themselves to come up with range rules. This has resulted in differences.
|
||||
For example, npm/js and Cargo/Rust follow similar patterns while PHP has a
|
||||
different pattern for ^. The comparison features in this package follow the
|
||||
npm/js and Cargo/Rust lead because applications using it have followed similar
|
||||
patters with their versions.
|
||||
|
||||
Checking a version against version constraints is one of the most featureful
|
||||
parts of the package.
|
||||
|
||||
```go
|
||||
c, err := semver.NewConstraint(">= 1.2.3")
|
||||
if err != nil {
|
||||
// Handle constraint not being parsable.
|
||||
}
|
||||
|
||||
v, err := semver.NewVersion("1.3")
|
||||
if err != nil {
|
||||
// Handle version not being parsable.
|
||||
}
|
||||
// Check if the version meets the constraints. The variable a will be true.
|
||||
a := c.Check(v)
|
||||
```
|
||||
|
||||
### Basic Comparisons
|
||||
|
||||
There are two elements to the comparisons. First, a comparison string is a list
|
||||
of space or comma separated AND comparisons. These are then separated by || (OR)
|
||||
comparisons. For example, `">= 1.2 < 3.0.0 || >= 4.2.3"` is looking for a
|
||||
comparison that's greater than or equal to 1.2 and less than 3.0.0 or is
|
||||
greater than or equal to 4.2.3.
|
||||
|
||||
The basic comparisons are:
|
||||
|
||||
* `=`: equal (aliased to no operator)
|
||||
* `!=`: not equal
|
||||
* `>`: greater than
|
||||
* `<`: less than
|
||||
* `>=`: greater than or equal to
|
||||
* `<=`: less than or equal to
|
||||
|
||||
### Working With Prerelease Versions
|
||||
|
||||
Pre-releases, for those not familiar with them, are used for software releases
|
||||
prior to stable or generally available releases. Examples of pre-releases include
|
||||
development, alpha, beta, and release candidate releases. A pre-release may be
|
||||
a version such as `1.2.3-beta.1` while the stable release would be `1.2.3`. In the
|
||||
order of precedence, pre-releases come before their associated releases. In this
|
||||
example `1.2.3-beta.1 < 1.2.3`.
|
||||
|
||||
According to the Semantic Version specification, pre-releases may not be
|
||||
API compliant with their release counterpart. It says,
|
||||
|
||||
> A pre-release version indicates that the version is unstable and might not satisfy the intended compatibility requirements as denoted by its associated normal version.
|
||||
|
||||
SemVer's comparisons using constraints without a pre-release comparator will skip
|
||||
pre-release versions. For example, `>=1.2.3` will skip pre-releases when looking
|
||||
at a list of releases while `>=1.2.3-0` will evaluate and find pre-releases.
|
||||
|
||||
The reason for the `0` as a pre-release version in the example comparison is
|
||||
because pre-releases can only contain ASCII alphanumerics and hyphens (along with
|
||||
`.` separators), per the spec. Sorting happens in ASCII sort order, again per the
|
||||
spec. The lowest character is a `0` in ASCII sort order
|
||||
(see an [ASCII Table](http://www.asciitable.com/))
|
||||
|
||||
Understanding ASCII sort ordering is important because A-Z comes before a-z. That
|
||||
means `>=1.2.3-BETA` will return `1.2.3-alpha`. What you might expect from case
|
||||
sensitivity doesn't apply here. This is due to ASCII sort ordering which is what
|
||||
the spec specifies.
|
||||
|
||||
The `Constraints` instance returned from `semver.NewConstraint()` has a property
|
||||
`IncludePrerelease` that, when set to true, will return prerelease versions when calls
|
||||
to `Check()` and `Validate()` are made.
|
||||
|
||||
### Hyphen Range Comparisons
|
||||
|
||||
There are multiple methods to handle ranges and the first is hyphens ranges.
|
||||
These look like:
|
||||
|
||||
* `1.2 - 1.4.5` which is equivalent to `>= 1.2 <= 1.4.5`
|
||||
* `2.3.4 - 4.5` which is equivalent to `>= 2.3.4 <= 4.5`
|
||||
|
||||
Note that `1.2-1.4.5` without whitespace is parsed completely differently; it's
|
||||
parsed as a single constraint `1.2.0` with _prerelease_ `1.4.5`.
|
||||
|
||||
### Wildcards In Comparisons
|
||||
|
||||
The `x`, `X`, and `*` characters can be used as a wildcard character. This works
|
||||
for all comparison operators. When used on the `=` operator it falls
|
||||
back to the patch level comparison (see tilde below). For example,
|
||||
|
||||
* `1.2.x` is equivalent to `>= 1.2.0, < 1.3.0`
|
||||
* `>= 1.2.x` is equivalent to `>= 1.2.0`
|
||||
* `<= 2.x` is equivalent to `< 3`
|
||||
* `*` is equivalent to `>= 0.0.0`
|
||||
|
||||
### Tilde Range Comparisons (Patch)
|
||||
|
||||
The tilde (`~`) comparison operator is for patch level ranges when a minor
|
||||
version is specified and major level changes when the minor number is missing.
|
||||
For example,
|
||||
|
||||
* `~1.2.3` is equivalent to `>= 1.2.3, < 1.3.0`
|
||||
* `~1` is equivalent to `>= 1, < 2`
|
||||
* `~2.3` is equivalent to `>= 2.3, < 2.4`
|
||||
* `~1.2.x` is equivalent to `>= 1.2.0, < 1.3.0`
|
||||
* `~1.x` is equivalent to `>= 1, < 2`
|
||||
|
||||
### Caret Range Comparisons (Major)
|
||||
|
||||
The caret (`^`) comparison operator is for major level changes once a stable
|
||||
(1.0.0) release has occurred. Prior to a 1.0.0 release the minor versions acts
|
||||
as the API stability level. This is useful when comparisons of API versions as a
|
||||
major change is API breaking. For example,
|
||||
|
||||
* `^1.2.3` is equivalent to `>= 1.2.3, < 2.0.0`
|
||||
* `^1.2.x` is equivalent to `>= 1.2.0, < 2.0.0`
|
||||
* `^2.3` is equivalent to `>= 2.3, < 3`
|
||||
* `^2.x` is equivalent to `>= 2.0.0, < 3`
|
||||
* `^0.2.3` is equivalent to `>=0.2.3 <0.3.0`
|
||||
* `^0.2` is equivalent to `>=0.2.0 <0.3.0`
|
||||
* `^0.0.3` is equivalent to `>=0.0.3 <0.0.4`
|
||||
* `^0.0` is equivalent to `>=0.0.0 <0.1.0`
|
||||
* `^0` is equivalent to `>=0.0.0 <1.0.0`
|
||||
|
||||
## Validation
|
||||
|
||||
In addition to testing a version against a constraint, a version can be validated
|
||||
against a constraint. When validation fails a slice of errors containing why a
|
||||
version didn't meet the constraint is returned. For example,
|
||||
|
||||
```go
|
||||
c, err := semver.NewConstraint("<= 1.2.3, >= 1.4")
|
||||
if err != nil {
|
||||
// Handle constraint not being parseable.
|
||||
}
|
||||
|
||||
v, err := semver.NewVersion("1.3")
|
||||
if err != nil {
|
||||
// Handle version not being parseable.
|
||||
}
|
||||
|
||||
// Validate a version against a constraint.
|
||||
a, msgs := c.Validate(v)
|
||||
// a is false
|
||||
for _, m := range msgs {
|
||||
fmt.Println(m)
|
||||
|
||||
// Loops over the errors which would read
|
||||
// "1.3 is greater than 1.2.3"
|
||||
// "1.3 is less than 1.4"
|
||||
}
|
||||
```
|
||||
|
||||
## Contribute
|
||||
|
||||
If you find an issue or want to contribute please file an [issue](https://github.com/Masterminds/semver/issues)
|
||||
or [create a pull request](https://github.com/Masterminds/semver/pulls).
|
||||
|
||||
## Security
|
||||
|
||||
Security is an important consideration for this project. The project currently
|
||||
uses the following tools to help discover security issues:
|
||||
|
||||
* [CodeQL](https://codeql.github.com)
|
||||
* [gosec](https://github.com/securego/gosec)
|
||||
* Daily Fuzz testing
|
||||
|
||||
If you believe you have found a security vulnerability you can privately disclose
|
||||
it through the [GitHub security page](https://github.com/Masterminds/semver/security).
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
The following versions of semver are currently supported:
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 3.x | :white_check_mark: |
|
||||
| 2.x | :x: |
|
||||
| 1.x | :x: |
|
||||
|
||||
Fixes are only released for the latest minor version in the form of a patch release.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
You can privately disclose a vulnerability through GitHubs
|
||||
[private vulnerability reporting](https://github.com/Masterminds/semver/security/advisories)
|
||||
mechanism.
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
package semver
|
||||
|
||||
// Collection is a collection of Version instances and implements the sort
|
||||
// interface. See the sort package for more details.
|
||||
// https://golang.org/pkg/sort/
|
||||
type Collection []*Version
|
||||
|
||||
// Len returns the length of a collection. The number of Version instances
|
||||
// on the slice.
|
||||
func (c Collection) Len() int {
|
||||
return len(c)
|
||||
}
|
||||
|
||||
// Less is needed for the sort interface to compare two Version objects on the
|
||||
// slice. If checks if one is less than the other.
|
||||
func (c Collection) Less(i, j int) bool {
|
||||
return c[i].LessThan(c[j])
|
||||
}
|
||||
|
||||
// Swap is needed for the sort interface to replace the Version objects
|
||||
// at two different positions in the slice.
|
||||
func (c Collection) Swap(i, j int) {
|
||||
c[i], c[j] = c[j], c[i]
|
||||
}
|
||||
-601
@@ -1,601 +0,0 @@
|
||||
package semver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Constraints is one or more constraint that a semantic version can be
|
||||
// checked against.
|
||||
type Constraints struct {
|
||||
constraints [][]*constraint
|
||||
containsPre []bool
|
||||
|
||||
// IncludePrerelease specifies if pre-releases should be included in
|
||||
// the results. Note, if a constraint range has a prerelease than
|
||||
// prereleases will be included for that AND group even if this is
|
||||
// set to false.
|
||||
IncludePrerelease bool
|
||||
}
|
||||
|
||||
// NewConstraint returns a Constraints instance that a Version instance can
|
||||
// be checked against. If there is a parse error it will be returned.
|
||||
func NewConstraint(c string) (*Constraints, error) {
|
||||
|
||||
// Rewrite - ranges into a comparison operation.
|
||||
c = rewriteRange(c)
|
||||
|
||||
ors := strings.Split(c, "||")
|
||||
lenors := len(ors)
|
||||
or := make([][]*constraint, lenors)
|
||||
hasPre := make([]bool, lenors)
|
||||
for k, v := range ors {
|
||||
// Validate the segment
|
||||
if !validConstraintRegex.MatchString(v) {
|
||||
return nil, fmt.Errorf("improper constraint: %s", v)
|
||||
}
|
||||
|
||||
cs := findConstraintRegex.FindAllString(v, -1)
|
||||
if cs == nil {
|
||||
cs = append(cs, v)
|
||||
}
|
||||
result := make([]*constraint, len(cs))
|
||||
for i, s := range cs {
|
||||
pc, err := parseConstraint(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If one of the constraints has a prerelease record this.
|
||||
// This information is used when checking all in an "and"
|
||||
// group to ensure they all check for prereleases.
|
||||
if pc.con.pre != "" {
|
||||
hasPre[k] = true
|
||||
}
|
||||
|
||||
result[i] = pc
|
||||
}
|
||||
or[k] = result
|
||||
}
|
||||
|
||||
o := &Constraints{
|
||||
constraints: or,
|
||||
containsPre: hasPre,
|
||||
}
|
||||
return o, nil
|
||||
}
|
||||
|
||||
// Check tests if a version satisfies the constraints.
|
||||
func (cs Constraints) Check(v *Version) bool {
|
||||
// TODO(mattfarina): For v4 of this library consolidate the Check and Validate
|
||||
// functions as the underlying functions make that possible now.
|
||||
// loop over the ORs and check the inner ANDs
|
||||
for i, o := range cs.constraints {
|
||||
joy := true
|
||||
for _, c := range o {
|
||||
if check, _ := c.check(v, (cs.IncludePrerelease || cs.containsPre[i])); !check {
|
||||
joy = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if joy {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Validate checks if a version satisfies a constraint. If not a slice of
|
||||
// reasons for the failure are returned in addition to a bool.
|
||||
func (cs Constraints) Validate(v *Version) (bool, []error) {
|
||||
// loop over the ORs and check the inner ANDs
|
||||
var e []error
|
||||
|
||||
// Capture the prerelease message only once. When it happens the first time
|
||||
// this var is marked
|
||||
var prerelesase bool
|
||||
for i, o := range cs.constraints {
|
||||
joy := true
|
||||
for _, c := range o {
|
||||
// Before running the check handle the case there the version is
|
||||
// a prerelease and the check is not searching for prereleases.
|
||||
if !(cs.IncludePrerelease || cs.containsPre[i]) && v.pre != "" {
|
||||
if !prerelesase {
|
||||
em := fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
|
||||
e = append(e, em)
|
||||
prerelesase = true
|
||||
}
|
||||
joy = false
|
||||
|
||||
} else {
|
||||
|
||||
if _, err := c.check(v, (cs.IncludePrerelease || cs.containsPre[i])); err != nil {
|
||||
e = append(e, err)
|
||||
joy = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if joy {
|
||||
return true, []error{}
|
||||
}
|
||||
}
|
||||
|
||||
return false, e
|
||||
}
|
||||
|
||||
func (cs Constraints) String() string {
|
||||
buf := make([]string, len(cs.constraints))
|
||||
var tmp bytes.Buffer
|
||||
|
||||
for k, v := range cs.constraints {
|
||||
tmp.Reset()
|
||||
vlen := len(v)
|
||||
for kk, c := range v {
|
||||
tmp.WriteString(c.string())
|
||||
|
||||
// Space separate the AND conditions
|
||||
if vlen > 1 && kk < vlen-1 {
|
||||
tmp.WriteString(" ")
|
||||
}
|
||||
}
|
||||
buf[k] = tmp.String()
|
||||
}
|
||||
|
||||
return strings.Join(buf, " || ")
|
||||
}
|
||||
|
||||
// UnmarshalText implements the encoding.TextUnmarshaler interface.
|
||||
func (cs *Constraints) UnmarshalText(text []byte) error {
|
||||
temp, err := NewConstraint(string(text))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*cs = *temp
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalText implements the encoding.TextMarshaler interface.
|
||||
func (cs Constraints) MarshalText() ([]byte, error) {
|
||||
return []byte(cs.String()), nil
|
||||
}
|
||||
|
||||
var constraintOps map[string]cfunc
|
||||
var constraintRegex *regexp.Regexp
|
||||
var constraintRangeRegex *regexp.Regexp
|
||||
|
||||
// Used to find individual constraints within a multi-constraint string
|
||||
var findConstraintRegex *regexp.Regexp
|
||||
|
||||
// Used to validate an segment of ANDs is valid
|
||||
var validConstraintRegex *regexp.Regexp
|
||||
|
||||
const cvRegex string = `v?([0-9|x|X|\*]+)(\.[0-9|x|X|\*]+)?(\.[0-9|x|X|\*]+)?` +
|
||||
`(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` +
|
||||
`(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?`
|
||||
|
||||
func init() {
|
||||
constraintOps = map[string]cfunc{
|
||||
"": constraintTildeOrEqual,
|
||||
"=": constraintTildeOrEqual,
|
||||
"!=": constraintNotEqual,
|
||||
">": constraintGreaterThan,
|
||||
"<": constraintLessThan,
|
||||
">=": constraintGreaterThanEqual,
|
||||
"=>": constraintGreaterThanEqual,
|
||||
"<=": constraintLessThanEqual,
|
||||
"=<": constraintLessThanEqual,
|
||||
"~": constraintTilde,
|
||||
"~>": constraintTilde,
|
||||
"^": constraintCaret,
|
||||
}
|
||||
|
||||
ops := `=||!=|>|<|>=|=>|<=|=<|~|~>|\^`
|
||||
|
||||
constraintRegex = regexp.MustCompile(fmt.Sprintf(
|
||||
`^\s*(%s)\s*(%s)\s*$`,
|
||||
ops,
|
||||
cvRegex))
|
||||
|
||||
constraintRangeRegex = regexp.MustCompile(fmt.Sprintf(
|
||||
`\s*(%s)\s+-\s+(%s)\s*`,
|
||||
cvRegex, cvRegex))
|
||||
|
||||
findConstraintRegex = regexp.MustCompile(fmt.Sprintf(
|
||||
`(%s)\s*(%s)`,
|
||||
ops,
|
||||
cvRegex))
|
||||
|
||||
// The first time a constraint shows up will look slightly different from
|
||||
// future times it shows up due to a leading space or comma in a given
|
||||
// string.
|
||||
validConstraintRegex = regexp.MustCompile(fmt.Sprintf(
|
||||
`^(\s*(%s)\s*(%s)\s*)((?:\s+|,\s*)(%s)\s*(%s)\s*)*$`,
|
||||
ops,
|
||||
cvRegex,
|
||||
ops,
|
||||
cvRegex))
|
||||
}
|
||||
|
||||
// An individual constraint
|
||||
type constraint struct {
|
||||
// The version used in the constraint check. For example, if a constraint
|
||||
// is '<= 2.0.0' the con a version instance representing 2.0.0.
|
||||
con *Version
|
||||
|
||||
// The original parsed version (e.g., 4.x from != 4.x)
|
||||
orig string
|
||||
|
||||
// The original operator for the constraint
|
||||
origfunc string
|
||||
|
||||
// When an x is used as part of the version (e.g., 1.x)
|
||||
minorDirty bool
|
||||
dirty bool
|
||||
patchDirty bool
|
||||
}
|
||||
|
||||
// Check if a version meets the constraint
|
||||
func (c *constraint) check(v *Version, includePre bool) (bool, error) {
|
||||
return constraintOps[c.origfunc](v, c, includePre)
|
||||
}
|
||||
|
||||
// String prints an individual constraint into a string
|
||||
func (c *constraint) string() string {
|
||||
return c.origfunc + c.orig
|
||||
}
|
||||
|
||||
type cfunc func(v *Version, c *constraint, includePre bool) (bool, error)
|
||||
|
||||
func parseConstraint(c string) (*constraint, error) {
|
||||
if len(c) > 0 {
|
||||
m := constraintRegex.FindStringSubmatch(c)
|
||||
if m == nil {
|
||||
return nil, fmt.Errorf("improper constraint: %s", c)
|
||||
}
|
||||
|
||||
cs := &constraint{
|
||||
orig: m[2],
|
||||
origfunc: m[1],
|
||||
}
|
||||
|
||||
ver := m[2]
|
||||
minorDirty := false
|
||||
patchDirty := false
|
||||
dirty := false
|
||||
if isX(m[3]) || m[3] == "" {
|
||||
ver = fmt.Sprintf("0.0.0%s", m[6])
|
||||
dirty = true
|
||||
} else if isX(strings.TrimPrefix(m[4], ".")) || m[4] == "" {
|
||||
minorDirty = true
|
||||
dirty = true
|
||||
ver = fmt.Sprintf("%s.0.0%s", m[3], m[6])
|
||||
} else if isX(strings.TrimPrefix(m[5], ".")) || m[5] == "" {
|
||||
dirty = true
|
||||
patchDirty = true
|
||||
ver = fmt.Sprintf("%s%s.0%s", m[3], m[4], m[6])
|
||||
}
|
||||
|
||||
con, err := NewVersion(ver)
|
||||
if err != nil {
|
||||
|
||||
// The constraintRegex should catch any regex parsing errors. So,
|
||||
// we should never get here.
|
||||
return nil, errors.New("constraint parser error")
|
||||
}
|
||||
|
||||
cs.con = con
|
||||
cs.minorDirty = minorDirty
|
||||
cs.patchDirty = patchDirty
|
||||
cs.dirty = dirty
|
||||
|
||||
return cs, nil
|
||||
}
|
||||
|
||||
// The rest is the special case where an empty string was passed in which
|
||||
// is equivalent to * or >=0.0.0
|
||||
con, err := StrictNewVersion("0.0.0")
|
||||
if err != nil {
|
||||
|
||||
// The constraintRegex should catch any regex parsing errors. So,
|
||||
// we should never get here.
|
||||
return nil, errors.New("constraint parser error")
|
||||
}
|
||||
|
||||
cs := &constraint{
|
||||
con: con,
|
||||
orig: c,
|
||||
origfunc: "",
|
||||
minorDirty: false,
|
||||
patchDirty: false,
|
||||
dirty: true,
|
||||
}
|
||||
return cs, nil
|
||||
}
|
||||
|
||||
// Constraint functions
|
||||
func constraintNotEqual(v *Version, c *constraint, includePre bool) (bool, error) {
|
||||
// The existence of prereleases is checked at the group level and passed in.
|
||||
// Exit early if the version has a prerelease but those are to be ignored.
|
||||
if v.Prerelease() != "" && !includePre {
|
||||
return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
|
||||
}
|
||||
|
||||
if c.dirty {
|
||||
if c.con.Major() != v.Major() {
|
||||
return true, nil
|
||||
}
|
||||
if c.con.Minor() != v.Minor() && !c.minorDirty {
|
||||
return true, nil
|
||||
} else if c.minorDirty {
|
||||
return false, fmt.Errorf("%s is equal to %s", v, c.orig)
|
||||
} else if c.con.Patch() != v.Patch() && !c.patchDirty {
|
||||
return true, nil
|
||||
} else if c.patchDirty {
|
||||
// Need to handle prereleases if present
|
||||
if v.Prerelease() != "" || c.con.Prerelease() != "" {
|
||||
eq := comparePrerelease(v.Prerelease(), c.con.Prerelease()) != 0
|
||||
if eq {
|
||||
return true, nil
|
||||
}
|
||||
return false, fmt.Errorf("%s is equal to %s", v, c.orig)
|
||||
}
|
||||
return false, fmt.Errorf("%s is equal to %s", v, c.orig)
|
||||
}
|
||||
}
|
||||
|
||||
eq := v.Equal(c.con)
|
||||
if eq {
|
||||
return false, fmt.Errorf("%s is equal to %s", v, c.orig)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func constraintGreaterThan(v *Version, c *constraint, includePre bool) (bool, error) {
|
||||
|
||||
// The existence of prereleases is checked at the group level and passed in.
|
||||
// Exit early if the version has a prerelease but those are to be ignored.
|
||||
if v.Prerelease() != "" && !includePre {
|
||||
return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
|
||||
}
|
||||
|
||||
var eq bool
|
||||
|
||||
if !c.dirty {
|
||||
eq = v.Compare(c.con) == 1
|
||||
if eq {
|
||||
return true, nil
|
||||
}
|
||||
return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig)
|
||||
}
|
||||
|
||||
if v.Major() > c.con.Major() {
|
||||
return true, nil
|
||||
} else if v.Major() < c.con.Major() {
|
||||
return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig)
|
||||
} else if c.minorDirty {
|
||||
// This is a range case such as >11. When the version is something like
|
||||
// 11.1.0 is it not > 11. For that we would need 12 or higher
|
||||
return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig)
|
||||
} else if c.patchDirty {
|
||||
// This is for ranges such as >11.1. A version of 11.1.1 is not greater
|
||||
// which one of 11.2.1 is greater
|
||||
eq = v.Minor() > c.con.Minor()
|
||||
if eq {
|
||||
return true, nil
|
||||
}
|
||||
return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig)
|
||||
}
|
||||
|
||||
// If we have gotten here we are not comparing pre-preleases and can use the
|
||||
// Compare function to accomplish that.
|
||||
eq = v.Compare(c.con) == 1
|
||||
if eq {
|
||||
return true, nil
|
||||
}
|
||||
return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig)
|
||||
}
|
||||
|
||||
func constraintLessThan(v *Version, c *constraint, includePre bool) (bool, error) {
|
||||
// The existence of prereleases is checked at the group level and passed in.
|
||||
// Exit early if the version has a prerelease but those are to be ignored.
|
||||
if v.Prerelease() != "" && !includePre {
|
||||
return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
|
||||
}
|
||||
|
||||
eq := v.Compare(c.con) < 0
|
||||
if eq {
|
||||
return true, nil
|
||||
}
|
||||
return false, fmt.Errorf("%s is greater than or equal to %s", v, c.orig)
|
||||
}
|
||||
|
||||
func constraintGreaterThanEqual(v *Version, c *constraint, includePre bool) (bool, error) {
|
||||
|
||||
// The existence of prereleases is checked at the group level and passed in.
|
||||
// Exit early if the version has a prerelease but those are to be ignored.
|
||||
if v.Prerelease() != "" && !includePre {
|
||||
return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
|
||||
}
|
||||
|
||||
eq := v.Compare(c.con) >= 0
|
||||
if eq {
|
||||
return true, nil
|
||||
}
|
||||
return false, fmt.Errorf("%s is less than %s", v, c.orig)
|
||||
}
|
||||
|
||||
func constraintLessThanEqual(v *Version, c *constraint, includePre bool) (bool, error) {
|
||||
// The existence of prereleases is checked at the group level and passed in.
|
||||
// Exit early if the version has a prerelease but those are to be ignored.
|
||||
if v.Prerelease() != "" && !includePre {
|
||||
return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
|
||||
}
|
||||
|
||||
var eq bool
|
||||
|
||||
if !c.dirty {
|
||||
eq = v.Compare(c.con) <= 0
|
||||
if eq {
|
||||
return true, nil
|
||||
}
|
||||
return false, fmt.Errorf("%s is greater than %s", v, c.orig)
|
||||
}
|
||||
|
||||
if v.Major() > c.con.Major() {
|
||||
return false, fmt.Errorf("%s is greater than %s", v, c.orig)
|
||||
} else if v.Major() == c.con.Major() && v.Minor() > c.con.Minor() && !c.minorDirty {
|
||||
return false, fmt.Errorf("%s is greater than %s", v, c.orig)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// ~*, ~>* --> >= 0.0.0 (any)
|
||||
// ~2, ~2.x, ~2.x.x, ~>2, ~>2.x ~>2.x.x --> >=2.0.0, <3.0.0
|
||||
// ~2.0, ~2.0.x, ~>2.0, ~>2.0.x --> >=2.0.0, <2.1.0
|
||||
// ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0, <1.3.0
|
||||
// ~1.2.3, ~>1.2.3 --> >=1.2.3, <1.3.0
|
||||
// ~1.2.0, ~>1.2.0 --> >=1.2.0, <1.3.0
|
||||
func constraintTilde(v *Version, c *constraint, includePre bool) (bool, error) {
|
||||
// The existence of prereleases is checked at the group level and passed in.
|
||||
// Exit early if the version has a prerelease but those are to be ignored.
|
||||
if v.Prerelease() != "" && !includePre {
|
||||
return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
|
||||
}
|
||||
|
||||
if v.LessThan(c.con) {
|
||||
return false, fmt.Errorf("%s is less than %s", v, c.orig)
|
||||
}
|
||||
|
||||
// ~0.0.0 is a special case where all constraints are accepted. It's
|
||||
// equivalent to >= 0.0.0.
|
||||
if c.con.Major() == 0 && c.con.Minor() == 0 && c.con.Patch() == 0 &&
|
||||
!c.minorDirty && !c.patchDirty {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if v.Major() != c.con.Major() {
|
||||
return false, fmt.Errorf("%s does not have same major version as %s", v, c.orig)
|
||||
}
|
||||
|
||||
if v.Minor() != c.con.Minor() && !c.minorDirty {
|
||||
return false, fmt.Errorf("%s does not have same major and minor version as %s", v, c.orig)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// When there is a .x (dirty) status it automatically opts in to ~. Otherwise
|
||||
// it's a straight =
|
||||
func constraintTildeOrEqual(v *Version, c *constraint, includePre bool) (bool, error) {
|
||||
// The existence of prereleases is checked at the group level and passed in.
|
||||
// Exit early if the version has a prerelease but those are to be ignored.
|
||||
if v.Prerelease() != "" && !includePre {
|
||||
return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
|
||||
}
|
||||
|
||||
if c.dirty {
|
||||
return constraintTilde(v, c, includePre)
|
||||
}
|
||||
|
||||
eq := v.Equal(c.con)
|
||||
if eq {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, fmt.Errorf("%s is not equal to %s", v, c.orig)
|
||||
}
|
||||
|
||||
// ^* --> (any)
|
||||
// ^1.2.3 --> >=1.2.3 <2.0.0
|
||||
// ^1.2 --> >=1.2.0 <2.0.0
|
||||
// ^1 --> >=1.0.0 <2.0.0
|
||||
// ^0.2.3 --> >=0.2.3 <0.3.0
|
||||
// ^0.2 --> >=0.2.0 <0.3.0
|
||||
// ^0.0.3 --> >=0.0.3 <0.0.4
|
||||
// ^0.0 --> >=0.0.0 <0.1.0
|
||||
// ^0 --> >=0.0.0 <1.0.0
|
||||
func constraintCaret(v *Version, c *constraint, includePre bool) (bool, error) {
|
||||
// The existence of prereleases is checked at the group level and passed in.
|
||||
// Exit early if the version has a prerelease but those are to be ignored.
|
||||
if v.Prerelease() != "" && !includePre {
|
||||
return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
|
||||
}
|
||||
|
||||
// This less than handles prereleases
|
||||
if v.LessThan(c.con) {
|
||||
return false, fmt.Errorf("%s is less than %s", v, c.orig)
|
||||
}
|
||||
|
||||
var eq bool
|
||||
|
||||
// ^ when the major > 0 is >=x.y.z < x+1
|
||||
if c.con.Major() > 0 || c.minorDirty {
|
||||
|
||||
// ^ has to be within a major range for > 0. Everything less than was
|
||||
// filtered out with the LessThan call above. This filters out those
|
||||
// that greater but not within the same major range.
|
||||
eq = v.Major() == c.con.Major()
|
||||
if eq {
|
||||
return true, nil
|
||||
}
|
||||
return false, fmt.Errorf("%s does not have same major version as %s", v, c.orig)
|
||||
}
|
||||
|
||||
// ^ when the major is 0 and minor > 0 is >=0.y.z < 0.y+1
|
||||
if c.con.Major() == 0 && v.Major() > 0 {
|
||||
return false, fmt.Errorf("%s does not have same major version as %s", v, c.orig)
|
||||
}
|
||||
// If the con Minor is > 0 it is not dirty
|
||||
if c.con.Minor() > 0 || c.patchDirty {
|
||||
eq = v.Minor() == c.con.Minor()
|
||||
if eq {
|
||||
return true, nil
|
||||
}
|
||||
return false, fmt.Errorf("%s does not have same minor version as %s. Expected minor versions to match when constraint major version is 0", v, c.orig)
|
||||
}
|
||||
// ^ when the minor is 0 and minor > 0 is =0.0.z
|
||||
if c.con.Minor() == 0 && v.Minor() > 0 {
|
||||
return false, fmt.Errorf("%s does not have same minor version as %s", v, c.orig)
|
||||
}
|
||||
|
||||
// At this point the major is 0 and the minor is 0 and not dirty. The patch
|
||||
// is not dirty so we need to check if they are equal. If they are not equal
|
||||
eq = c.con.Patch() == v.Patch()
|
||||
if eq {
|
||||
return true, nil
|
||||
}
|
||||
return false, fmt.Errorf("%s does not equal %s. Expect version and constraint to equal when major and minor versions are 0", v, c.orig)
|
||||
}
|
||||
|
||||
func isX(x string) bool {
|
||||
switch x {
|
||||
case "x", "*", "X":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func rewriteRange(i string) string {
|
||||
m := constraintRangeRegex.FindAllStringSubmatch(i, -1)
|
||||
if m == nil {
|
||||
return i
|
||||
}
|
||||
o := i
|
||||
for _, v := range m {
|
||||
t := fmt.Sprintf(">= %s, <= %s ", v[1], v[11])
|
||||
o = strings.Replace(o, v[0], t, 1)
|
||||
}
|
||||
|
||||
return o
|
||||
}
|
||||
-184
@@ -1,184 +0,0 @@
|
||||
/*
|
||||
Package semver provides the ability to work with Semantic Versions (http://semver.org) in Go.
|
||||
|
||||
Specifically it provides the ability to:
|
||||
|
||||
- Parse semantic versions
|
||||
- Sort semantic versions
|
||||
- Check if a semantic version fits within a set of constraints
|
||||
- Optionally work with a `v` prefix
|
||||
|
||||
# Parsing Semantic Versions
|
||||
|
||||
There are two functions that can parse semantic versions. The `StrictNewVersion`
|
||||
function only parses valid version 2 semantic versions as outlined in the
|
||||
specification. The `NewVersion` function attempts to coerce a version into a
|
||||
semantic version and parse it. For example, if there is a leading v or a version
|
||||
listed without all 3 parts (e.g. 1.2) it will attempt to coerce it into a valid
|
||||
semantic version (e.g., 1.2.0). In both cases a `Version` object is returned
|
||||
that can be sorted, compared, and used in constraints.
|
||||
|
||||
When parsing a version an optional error can be returned if there is an issue
|
||||
parsing the version. For example,
|
||||
|
||||
v, err := semver.NewVersion("1.2.3-beta.1+b345")
|
||||
|
||||
The version object has methods to get the parts of the version, compare it to
|
||||
other versions, convert the version back into a string, and get the original
|
||||
string. For more details please see the documentation
|
||||
at https://godoc.org/github.com/Masterminds/semver.
|
||||
|
||||
# Sorting Semantic Versions
|
||||
|
||||
A set of versions can be sorted using the `sort` package from the standard library.
|
||||
For example,
|
||||
|
||||
raw := []string{"1.2.3", "1.0", "1.3", "2", "0.4.2",}
|
||||
vs := make([]*semver.Version, len(raw))
|
||||
for i, r := range raw {
|
||||
v, err := semver.NewVersion(r)
|
||||
if err != nil {
|
||||
t.Errorf("Error parsing version: %s", err)
|
||||
}
|
||||
|
||||
vs[i] = v
|
||||
}
|
||||
|
||||
sort.Sort(semver.Collection(vs))
|
||||
|
||||
# Checking Version Constraints and Comparing Versions
|
||||
|
||||
There are two methods for comparing versions. One uses comparison methods on
|
||||
`Version` instances and the other is using Constraints. There are some important
|
||||
differences to notes between these two methods of comparison.
|
||||
|
||||
1. When two versions are compared using functions such as `Compare`, `LessThan`,
|
||||
and others it will follow the specification and always include prereleases
|
||||
within the comparison. It will provide an answer valid with the comparison
|
||||
spec section at https://semver.org/#spec-item-11
|
||||
2. When constraint checking is used for checks or validation it will follow a
|
||||
different set of rules that are common for ranges with tools like npm/js
|
||||
and Rust/Cargo. This includes considering prereleases to be invalid if the
|
||||
ranges does not include on. If you want to have it include pre-releases a
|
||||
simple solution is to include `-0` in your range.
|
||||
3. Constraint ranges can have some complex rules including the shorthard use of
|
||||
~ and ^. For more details on those see the options below.
|
||||
|
||||
There are differences between the two methods or checking versions because the
|
||||
comparison methods on `Version` follow the specification while comparison ranges
|
||||
are not part of the specification. Different packages and tools have taken it
|
||||
upon themselves to come up with range rules. This has resulted in differences.
|
||||
For example, npm/js and Cargo/Rust follow similar patterns which PHP has a
|
||||
different pattern for ^. The comparison features in this package follow the
|
||||
npm/js and Cargo/Rust lead because applications using it have followed similar
|
||||
patters with their versions.
|
||||
|
||||
Checking a version against version constraints is one of the most featureful
|
||||
parts of the package.
|
||||
|
||||
c, err := semver.NewConstraint(">= 1.2.3")
|
||||
if err != nil {
|
||||
// Handle constraint not being parsable.
|
||||
}
|
||||
|
||||
v, err := semver.NewVersion("1.3")
|
||||
if err != nil {
|
||||
// Handle version not being parsable.
|
||||
}
|
||||
// Check if the version meets the constraints. The a variable will be true.
|
||||
a := c.Check(v)
|
||||
|
||||
# Basic Comparisons
|
||||
|
||||
There are two elements to the comparisons. First, a comparison string is a list
|
||||
of comma or space separated AND comparisons. These are then separated by || (OR)
|
||||
comparisons. For example, `">= 1.2 < 3.0.0 || >= 4.2.3"` is looking for a
|
||||
comparison that's greater than or equal to 1.2 and less than 3.0.0 or is
|
||||
greater than or equal to 4.2.3. This can also be written as
|
||||
`">= 1.2, < 3.0.0 || >= 4.2.3"`
|
||||
|
||||
The basic comparisons are:
|
||||
|
||||
- `=`: equal (aliased to no operator)
|
||||
- `!=`: not equal
|
||||
- `>`: greater than
|
||||
- `<`: less than
|
||||
- `>=`: greater than or equal to
|
||||
- `<=`: less than or equal to
|
||||
|
||||
# Hyphen Range Comparisons
|
||||
|
||||
There are multiple methods to handle ranges and the first is hyphens ranges.
|
||||
These look like:
|
||||
|
||||
- `1.2 - 1.4.5` which is equivalent to `>= 1.2, <= 1.4.5`
|
||||
- `2.3.4 - 4.5` which is equivalent to `>= 2.3.4 <= 4.5`
|
||||
|
||||
# Wildcards In Comparisons
|
||||
|
||||
The `x`, `X`, and `*` characters can be used as a wildcard character. This works
|
||||
for all comparison operators. When used on the `=` operator it falls
|
||||
back to the tilde operation. For example,
|
||||
|
||||
- `1.2.x` is equivalent to `>= 1.2.0 < 1.3.0`
|
||||
- `>= 1.2.x` is equivalent to `>= 1.2.0`
|
||||
- `<= 2.x` is equivalent to `<= 3`
|
||||
- `*` is equivalent to `>= 0.0.0`
|
||||
|
||||
Tilde Range Comparisons (Patch)
|
||||
|
||||
The tilde (`~`) comparison operator is for patch level ranges when a minor
|
||||
version is specified and major level changes when the minor number is missing.
|
||||
For example,
|
||||
|
||||
- `~1.2.3` is equivalent to `>= 1.2.3 < 1.3.0`
|
||||
- `~1` is equivalent to `>= 1, < 2`
|
||||
- `~2.3` is equivalent to `>= 2.3 < 2.4`
|
||||
- `~1.2.x` is equivalent to `>= 1.2.0 < 1.3.0`
|
||||
- `~1.x` is equivalent to `>= 1 < 2`
|
||||
|
||||
Caret Range Comparisons (Major)
|
||||
|
||||
The caret (`^`) comparison operator is for major level changes once a stable
|
||||
(1.0.0) release has occurred. Prior to a 1.0.0 release the minor versions acts
|
||||
as the API stability level. This is useful when comparisons of API versions as a
|
||||
major change is API breaking. For example,
|
||||
|
||||
- `^1.2.3` is equivalent to `>= 1.2.3, < 2.0.0`
|
||||
- `^1.2.x` is equivalent to `>= 1.2.0, < 2.0.0`
|
||||
- `^2.3` is equivalent to `>= 2.3, < 3`
|
||||
- `^2.x` is equivalent to `>= 2.0.0, < 3`
|
||||
- `^0.2.3` is equivalent to `>=0.2.3 <0.3.0`
|
||||
- `^0.2` is equivalent to `>=0.2.0 <0.3.0`
|
||||
- `^0.0.3` is equivalent to `>=0.0.3 <0.0.4`
|
||||
- `^0.0` is equivalent to `>=0.0.0 <0.1.0`
|
||||
- `^0` is equivalent to `>=0.0.0 <1.0.0`
|
||||
|
||||
# Validation
|
||||
|
||||
In addition to testing a version against a constraint, a version can be validated
|
||||
against a constraint. When validation fails a slice of errors containing why a
|
||||
version didn't meet the constraint is returned. For example,
|
||||
|
||||
c, err := semver.NewConstraint("<= 1.2.3, >= 1.4")
|
||||
if err != nil {
|
||||
// Handle constraint not being parseable.
|
||||
}
|
||||
|
||||
v, _ := semver.NewVersion("1.3")
|
||||
if err != nil {
|
||||
// Handle version not being parseable.
|
||||
}
|
||||
|
||||
// Validate a version against a constraint.
|
||||
a, msgs := c.Validate(v)
|
||||
// a is false
|
||||
for _, m := range msgs {
|
||||
fmt.Println(m)
|
||||
|
||||
// Loops over the errors which would read
|
||||
// "1.3 is greater than 1.2.3"
|
||||
// "1.3 is less than 1.4"
|
||||
}
|
||||
*/
|
||||
package semver
|
||||
-788
@@ -1,788 +0,0 @@
|
||||
package semver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// The compiled version of the regex created at init() is cached here so it
|
||||
// only needs to be created once.
|
||||
var versionRegex *regexp.Regexp
|
||||
var looseVersionRegex *regexp.Regexp
|
||||
|
||||
// CoerceNewVersion sets if leading 0's are allowd in the version part. Leading 0's are
|
||||
// not allowed in a valid semantic version. When set to true, NewVersion will coerce
|
||||
// leading 0's into a valid version.
|
||||
var CoerceNewVersion = true
|
||||
|
||||
// DetailedNewVersionErrors specifies if detailed errors are returned from the NewVersion
|
||||
// function. This is used when CoerceNewVersion is set to false. If set to false
|
||||
// ErrInvalidSemVer is returned for an invalid version. This does not apply to
|
||||
// StrictNewVersion. Setting this function to false returns errors more quickly.
|
||||
var DetailedNewVersionErrors = true
|
||||
|
||||
var (
|
||||
// ErrInvalidSemVer is returned a version is found to be invalid when
|
||||
// being parsed.
|
||||
ErrInvalidSemVer = errors.New("invalid semantic version")
|
||||
|
||||
// ErrEmptyString is returned when an empty string is passed in for parsing.
|
||||
ErrEmptyString = errors.New("version string empty")
|
||||
|
||||
// ErrInvalidCharacters is returned when invalid characters are found as
|
||||
// part of a version
|
||||
ErrInvalidCharacters = errors.New("invalid characters in version")
|
||||
|
||||
// ErrSegmentStartsZero is returned when a version segment starts with 0.
|
||||
// This is invalid in SemVer.
|
||||
ErrSegmentStartsZero = errors.New("version segment starts with 0")
|
||||
|
||||
// ErrInvalidMetadata is returned when the metadata is an invalid format
|
||||
ErrInvalidMetadata = errors.New("invalid metadata string")
|
||||
|
||||
// ErrInvalidPrerelease is returned when the pre-release is an invalid format
|
||||
ErrInvalidPrerelease = errors.New("invalid prerelease string")
|
||||
)
|
||||
|
||||
// semVerRegex is the regular expression used to parse a semantic version.
|
||||
// This is not the official regex from the semver spec. It has been modified to allow for loose handling
|
||||
// where versions like 2.1 are detected.
|
||||
const semVerRegex string = `v?(0|[1-9]\d*)(?:\.(0|[1-9]\d*))?(?:\.(0|[1-9]\d*))?` +
|
||||
`(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?` +
|
||||
`(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?`
|
||||
|
||||
// looseSemVerRegex is a regular expression that lets invalid semver expressions through
|
||||
// with enough detail that certain errors can be checked for.
|
||||
const looseSemVerRegex string = `v?([0-9]+)(\.[0-9]+)?(\.[0-9]+)?` +
|
||||
`(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` +
|
||||
`(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?`
|
||||
|
||||
// Version represents a single semantic version.
|
||||
type Version struct {
|
||||
major, minor, patch uint64
|
||||
pre string
|
||||
metadata string
|
||||
original string
|
||||
}
|
||||
|
||||
func init() {
|
||||
versionRegex = regexp.MustCompile("^" + semVerRegex + "$")
|
||||
looseVersionRegex = regexp.MustCompile("^" + looseSemVerRegex + "$")
|
||||
}
|
||||
|
||||
const (
|
||||
num string = "0123456789"
|
||||
allowed string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-" + num
|
||||
)
|
||||
|
||||
// StrictNewVersion parses a given version and returns an instance of Version or
|
||||
// an error if unable to parse the version. Only parses valid semantic versions.
|
||||
// Performs checking that can find errors within the version.
|
||||
// If you want to coerce a version such as 1 or 1.2 and parse it as the 1.x
|
||||
// releases of semver did, use the NewVersion() function.
|
||||
func StrictNewVersion(v string) (*Version, error) {
|
||||
// Parsing here does not use RegEx in order to increase performance and reduce
|
||||
// allocations.
|
||||
|
||||
if len(v) == 0 {
|
||||
return nil, ErrEmptyString
|
||||
}
|
||||
|
||||
// Split the parts into [0]major, [1]minor, and [2]patch,prerelease,build
|
||||
parts := strings.SplitN(v, ".", 3)
|
||||
if len(parts) != 3 {
|
||||
return nil, ErrInvalidSemVer
|
||||
}
|
||||
|
||||
sv := &Version{
|
||||
original: v,
|
||||
}
|
||||
|
||||
// Extract build metadata
|
||||
if strings.Contains(parts[2], "+") {
|
||||
extra := strings.SplitN(parts[2], "+", 2)
|
||||
sv.metadata = extra[1]
|
||||
parts[2] = extra[0]
|
||||
if err := validateMetadata(sv.metadata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Extract build prerelease
|
||||
if strings.Contains(parts[2], "-") {
|
||||
extra := strings.SplitN(parts[2], "-", 2)
|
||||
sv.pre = extra[1]
|
||||
parts[2] = extra[0]
|
||||
if err := validatePrerelease(sv.pre); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Validate the number segments are valid. This includes only having positive
|
||||
// numbers and no leading 0's.
|
||||
for _, p := range parts {
|
||||
if !containsOnly(p, num) {
|
||||
return nil, ErrInvalidCharacters
|
||||
}
|
||||
|
||||
if len(p) > 1 && p[0] == '0' {
|
||||
return nil, ErrSegmentStartsZero
|
||||
}
|
||||
}
|
||||
|
||||
// Extract major, minor, and patch
|
||||
var err error
|
||||
sv.major, err = strconv.ParseUint(parts[0], 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sv.minor, err = strconv.ParseUint(parts[1], 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sv.patch, err = strconv.ParseUint(parts[2], 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return sv, nil
|
||||
}
|
||||
|
||||
// NewVersion parses a given version and returns an instance of Version or
|
||||
// an error if unable to parse the version. If the version is SemVer-ish it
|
||||
// attempts to convert it to SemVer. If you want to validate it was a strict
|
||||
// semantic version at parse time see StrictNewVersion().
|
||||
func NewVersion(v string) (*Version, error) {
|
||||
if CoerceNewVersion {
|
||||
return coerceNewVersion(v)
|
||||
}
|
||||
m := versionRegex.FindStringSubmatch(v)
|
||||
if m == nil {
|
||||
|
||||
// Disabling detailed errors is first so that it is in the fast path.
|
||||
if !DetailedNewVersionErrors {
|
||||
return nil, ErrInvalidSemVer
|
||||
}
|
||||
|
||||
// Check for specific errors with the semver string and return a more detailed
|
||||
// error.
|
||||
m = looseVersionRegex.FindStringSubmatch(v)
|
||||
if m == nil {
|
||||
return nil, ErrInvalidSemVer
|
||||
}
|
||||
err := validateVersion(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, ErrInvalidSemVer
|
||||
}
|
||||
|
||||
sv := &Version{
|
||||
metadata: m[5],
|
||||
pre: m[4],
|
||||
original: v,
|
||||
}
|
||||
|
||||
var err error
|
||||
sv.major, err = strconv.ParseUint(m[1], 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing version segment: %w", err)
|
||||
}
|
||||
|
||||
if m[2] != "" {
|
||||
sv.minor, err = strconv.ParseUint(m[2], 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing version segment: %w", err)
|
||||
}
|
||||
} else {
|
||||
sv.minor = 0
|
||||
}
|
||||
|
||||
if m[3] != "" {
|
||||
sv.patch, err = strconv.ParseUint(m[3], 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing version segment: %w", err)
|
||||
}
|
||||
} else {
|
||||
sv.patch = 0
|
||||
}
|
||||
|
||||
// Perform some basic due diligence on the extra parts to ensure they are
|
||||
// valid.
|
||||
|
||||
if sv.pre != "" {
|
||||
if err = validatePrerelease(sv.pre); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if sv.metadata != "" {
|
||||
if err = validateMetadata(sv.metadata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return sv, nil
|
||||
}
|
||||
|
||||
func coerceNewVersion(v string) (*Version, error) {
|
||||
m := looseVersionRegex.FindStringSubmatch(v)
|
||||
if m == nil {
|
||||
return nil, ErrInvalidSemVer
|
||||
}
|
||||
|
||||
sv := &Version{
|
||||
metadata: m[8],
|
||||
pre: m[5],
|
||||
original: v,
|
||||
}
|
||||
|
||||
var err error
|
||||
sv.major, err = strconv.ParseUint(m[1], 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing version segment: %w", err)
|
||||
}
|
||||
|
||||
if m[2] != "" {
|
||||
sv.minor, err = strconv.ParseUint(strings.TrimPrefix(m[2], "."), 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing version segment: %w", err)
|
||||
}
|
||||
} else {
|
||||
sv.minor = 0
|
||||
}
|
||||
|
||||
if m[3] != "" {
|
||||
sv.patch, err = strconv.ParseUint(strings.TrimPrefix(m[3], "."), 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing version segment: %w", err)
|
||||
}
|
||||
} else {
|
||||
sv.patch = 0
|
||||
}
|
||||
|
||||
// Perform some basic due diligence on the extra parts to ensure they are
|
||||
// valid.
|
||||
|
||||
if sv.pre != "" {
|
||||
if err = validatePrerelease(sv.pre); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if sv.metadata != "" {
|
||||
if err = validateMetadata(sv.metadata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return sv, nil
|
||||
}
|
||||
|
||||
// New creates a new instance of Version with each of the parts passed in as
|
||||
// arguments instead of parsing a version string.
|
||||
func New(major, minor, patch uint64, pre, metadata string) *Version {
|
||||
v := Version{
|
||||
major: major,
|
||||
minor: minor,
|
||||
patch: patch,
|
||||
pre: pre,
|
||||
metadata: metadata,
|
||||
original: "",
|
||||
}
|
||||
|
||||
v.original = v.String()
|
||||
|
||||
return &v
|
||||
}
|
||||
|
||||
// MustParse parses a given version and panics on error.
|
||||
func MustParse(v string) *Version {
|
||||
sv, err := NewVersion(v)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return sv
|
||||
}
|
||||
|
||||
// String converts a Version object to a string.
|
||||
// Note, if the original version contained a leading v this version will not.
|
||||
// See the Original() method to retrieve the original value. Semantic Versions
|
||||
// don't contain a leading v per the spec. Instead it's optional on
|
||||
// implementation.
|
||||
func (v Version) String() string {
|
||||
var buf bytes.Buffer
|
||||
|
||||
fmt.Fprintf(&buf, "%d.%d.%d", v.major, v.minor, v.patch)
|
||||
if v.pre != "" {
|
||||
fmt.Fprintf(&buf, "-%s", v.pre)
|
||||
}
|
||||
if v.metadata != "" {
|
||||
fmt.Fprintf(&buf, "+%s", v.metadata)
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// Original returns the original value passed in to be parsed.
|
||||
func (v *Version) Original() string {
|
||||
return v.original
|
||||
}
|
||||
|
||||
// Major returns the major version.
|
||||
func (v Version) Major() uint64 {
|
||||
return v.major
|
||||
}
|
||||
|
||||
// Minor returns the minor version.
|
||||
func (v Version) Minor() uint64 {
|
||||
return v.minor
|
||||
}
|
||||
|
||||
// Patch returns the patch version.
|
||||
func (v Version) Patch() uint64 {
|
||||
return v.patch
|
||||
}
|
||||
|
||||
// Prerelease returns the pre-release version.
|
||||
func (v Version) Prerelease() string {
|
||||
return v.pre
|
||||
}
|
||||
|
||||
// Metadata returns the metadata on the version.
|
||||
func (v Version) Metadata() string {
|
||||
return v.metadata
|
||||
}
|
||||
|
||||
// originalVPrefix returns the original 'v' prefix if any.
|
||||
func (v Version) originalVPrefix() string {
|
||||
// Note, only lowercase v is supported as a prefix by the parser.
|
||||
if v.original != "" && v.original[:1] == "v" {
|
||||
return v.original[:1]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// IncPatch produces the next patch version.
|
||||
// If the current version does not have prerelease/metadata information,
|
||||
// it unsets metadata and prerelease values, increments patch number.
|
||||
// If the current version has any of prerelease or metadata information,
|
||||
// it unsets both values and keeps current patch value
|
||||
func (v Version) IncPatch() Version {
|
||||
vNext := v
|
||||
// according to http://semver.org/#spec-item-9
|
||||
// Pre-release versions have a lower precedence than the associated normal version.
|
||||
// according to http://semver.org/#spec-item-10
|
||||
// Build metadata SHOULD be ignored when determining version precedence.
|
||||
if v.pre != "" {
|
||||
vNext.metadata = ""
|
||||
vNext.pre = ""
|
||||
} else {
|
||||
vNext.metadata = ""
|
||||
vNext.pre = ""
|
||||
vNext.patch = v.patch + 1
|
||||
}
|
||||
vNext.original = v.originalVPrefix() + "" + vNext.String()
|
||||
return vNext
|
||||
}
|
||||
|
||||
// IncMinor produces the next minor version.
|
||||
// Sets patch to 0.
|
||||
// Increments minor number.
|
||||
// Unsets metadata.
|
||||
// Unsets prerelease status.
|
||||
func (v Version) IncMinor() Version {
|
||||
vNext := v
|
||||
vNext.metadata = ""
|
||||
vNext.pre = ""
|
||||
vNext.patch = 0
|
||||
vNext.minor = v.minor + 1
|
||||
vNext.original = v.originalVPrefix() + "" + vNext.String()
|
||||
return vNext
|
||||
}
|
||||
|
||||
// IncMajor produces the next major version.
|
||||
// Sets patch to 0.
|
||||
// Sets minor to 0.
|
||||
// Increments major number.
|
||||
// Unsets metadata.
|
||||
// Unsets prerelease status.
|
||||
func (v Version) IncMajor() Version {
|
||||
vNext := v
|
||||
vNext.metadata = ""
|
||||
vNext.pre = ""
|
||||
vNext.patch = 0
|
||||
vNext.minor = 0
|
||||
vNext.major = v.major + 1
|
||||
vNext.original = v.originalVPrefix() + "" + vNext.String()
|
||||
return vNext
|
||||
}
|
||||
|
||||
// SetPrerelease defines the prerelease value.
|
||||
// Value must not include the required 'hyphen' prefix.
|
||||
func (v Version) SetPrerelease(prerelease string) (Version, error) {
|
||||
vNext := v
|
||||
if len(prerelease) > 0 {
|
||||
if err := validatePrerelease(prerelease); err != nil {
|
||||
return vNext, err
|
||||
}
|
||||
}
|
||||
vNext.pre = prerelease
|
||||
vNext.original = v.originalVPrefix() + "" + vNext.String()
|
||||
return vNext, nil
|
||||
}
|
||||
|
||||
// SetMetadata defines metadata value.
|
||||
// Value must not include the required 'plus' prefix.
|
||||
func (v Version) SetMetadata(metadata string) (Version, error) {
|
||||
vNext := v
|
||||
if len(metadata) > 0 {
|
||||
if err := validateMetadata(metadata); err != nil {
|
||||
return vNext, err
|
||||
}
|
||||
}
|
||||
vNext.metadata = metadata
|
||||
vNext.original = v.originalVPrefix() + "" + vNext.String()
|
||||
return vNext, nil
|
||||
}
|
||||
|
||||
// LessThan tests if one version is less than another one.
|
||||
func (v *Version) LessThan(o *Version) bool {
|
||||
return v.Compare(o) < 0
|
||||
}
|
||||
|
||||
// LessThanEqual tests if one version is less or equal than another one.
|
||||
func (v *Version) LessThanEqual(o *Version) bool {
|
||||
return v.Compare(o) <= 0
|
||||
}
|
||||
|
||||
// GreaterThan tests if one version is greater than another one.
|
||||
func (v *Version) GreaterThan(o *Version) bool {
|
||||
return v.Compare(o) > 0
|
||||
}
|
||||
|
||||
// GreaterThanEqual tests if one version is greater or equal than another one.
|
||||
func (v *Version) GreaterThanEqual(o *Version) bool {
|
||||
return v.Compare(o) >= 0
|
||||
}
|
||||
|
||||
// Equal tests if two versions are equal to each other.
|
||||
// Note, versions can be equal with different metadata since metadata
|
||||
// is not considered part of the comparable version.
|
||||
func (v *Version) Equal(o *Version) bool {
|
||||
if v == o {
|
||||
return true
|
||||
}
|
||||
if v == nil || o == nil {
|
||||
return false
|
||||
}
|
||||
return v.Compare(o) == 0
|
||||
}
|
||||
|
||||
// Compare compares this version to another one. It returns -1, 0, or 1 if
|
||||
// the version smaller, equal, or larger than the other version.
|
||||
//
|
||||
// Versions are compared by X.Y.Z. Build metadata is ignored. Prerelease is
|
||||
// lower than the version without a prerelease. Compare always takes into account
|
||||
// prereleases. If you want to work with ranges using typical range syntaxes that
|
||||
// skip prereleases if the range is not looking for them use constraints.
|
||||
func (v *Version) Compare(o *Version) int {
|
||||
// Compare the major, minor, and patch version for differences. If a
|
||||
// difference is found return the comparison.
|
||||
if d := compareSegment(v.Major(), o.Major()); d != 0 {
|
||||
return d
|
||||
}
|
||||
if d := compareSegment(v.Minor(), o.Minor()); d != 0 {
|
||||
return d
|
||||
}
|
||||
if d := compareSegment(v.Patch(), o.Patch()); d != 0 {
|
||||
return d
|
||||
}
|
||||
|
||||
// At this point the major, minor, and patch versions are the same.
|
||||
ps := v.pre
|
||||
po := o.Prerelease()
|
||||
|
||||
if ps == "" && po == "" {
|
||||
return 0
|
||||
}
|
||||
if ps == "" {
|
||||
return 1
|
||||
}
|
||||
if po == "" {
|
||||
return -1
|
||||
}
|
||||
|
||||
return comparePrerelease(ps, po)
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements JSON.Unmarshaler interface.
|
||||
func (v *Version) UnmarshalJSON(b []byte) error {
|
||||
var s string
|
||||
if err := json.Unmarshal(b, &s); err != nil {
|
||||
return err
|
||||
}
|
||||
temp, err := NewVersion(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.major = temp.major
|
||||
v.minor = temp.minor
|
||||
v.patch = temp.patch
|
||||
v.pre = temp.pre
|
||||
v.metadata = temp.metadata
|
||||
v.original = temp.original
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON implements JSON.Marshaler interface.
|
||||
func (v Version) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(v.String())
|
||||
}
|
||||
|
||||
// UnmarshalText implements the encoding.TextUnmarshaler interface.
|
||||
func (v *Version) UnmarshalText(text []byte) error {
|
||||
temp, err := NewVersion(string(text))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*v = *temp
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalText implements the encoding.TextMarshaler interface.
|
||||
func (v Version) MarshalText() ([]byte, error) {
|
||||
return []byte(v.String()), nil
|
||||
}
|
||||
|
||||
// Scan implements the SQL.Scanner interface.
|
||||
func (v *Version) Scan(value interface{}) error {
|
||||
var s string
|
||||
s, _ = value.(string)
|
||||
temp, err := NewVersion(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.major = temp.major
|
||||
v.minor = temp.minor
|
||||
v.patch = temp.patch
|
||||
v.pre = temp.pre
|
||||
v.metadata = temp.metadata
|
||||
v.original = temp.original
|
||||
return nil
|
||||
}
|
||||
|
||||
// Value implements the Driver.Valuer interface.
|
||||
func (v Version) Value() (driver.Value, error) {
|
||||
return v.String(), nil
|
||||
}
|
||||
|
||||
func compareSegment(v, o uint64) int {
|
||||
if v < o {
|
||||
return -1
|
||||
}
|
||||
if v > o {
|
||||
return 1
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func comparePrerelease(v, o string) int {
|
||||
// split the prelease versions by their part. The separator, per the spec,
|
||||
// is a .
|
||||
sparts := strings.Split(v, ".")
|
||||
oparts := strings.Split(o, ".")
|
||||
|
||||
// Find the longer length of the parts to know how many loop iterations to
|
||||
// go through.
|
||||
slen := len(sparts)
|
||||
olen := len(oparts)
|
||||
|
||||
l := slen
|
||||
if olen > slen {
|
||||
l = olen
|
||||
}
|
||||
|
||||
// Iterate over each part of the prereleases to compare the differences.
|
||||
for i := 0; i < l; i++ {
|
||||
// Since the lentgh of the parts can be different we need to create
|
||||
// a placeholder. This is to avoid out of bounds issues.
|
||||
stemp := ""
|
||||
if i < slen {
|
||||
stemp = sparts[i]
|
||||
}
|
||||
|
||||
otemp := ""
|
||||
if i < olen {
|
||||
otemp = oparts[i]
|
||||
}
|
||||
|
||||
d := comparePrePart(stemp, otemp)
|
||||
if d != 0 {
|
||||
return d
|
||||
}
|
||||
}
|
||||
|
||||
// Reaching here means two versions are of equal value but have different
|
||||
// metadata (the part following a +). They are not identical in string form
|
||||
// but the version comparison finds them to be equal.
|
||||
return 0
|
||||
}
|
||||
|
||||
func comparePrePart(s, o string) int {
|
||||
// Fastpath if they are equal
|
||||
if s == o {
|
||||
return 0
|
||||
}
|
||||
|
||||
// When s or o are empty we can use the other in an attempt to determine
|
||||
// the response.
|
||||
if s == "" {
|
||||
if o != "" {
|
||||
return -1
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
if o == "" {
|
||||
if s != "" {
|
||||
return 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// When comparing strings "99" is greater than "103". To handle
|
||||
// cases like this we need to detect numbers and compare them. According
|
||||
// to the semver spec, numbers are always positive. If there is a - at the
|
||||
// start like -99 this is to be evaluated as an alphanum. numbers always
|
||||
// have precedence over alphanum. Parsing as Uints because negative numbers
|
||||
// are ignored.
|
||||
|
||||
oi, n1 := strconv.ParseUint(o, 10, 64)
|
||||
si, n2 := strconv.ParseUint(s, 10, 64)
|
||||
|
||||
// The case where both are strings compare the strings
|
||||
if n1 != nil && n2 != nil {
|
||||
if s > o {
|
||||
return 1
|
||||
}
|
||||
return -1
|
||||
} else if n1 != nil {
|
||||
// o is a string and s is a number
|
||||
return -1
|
||||
} else if n2 != nil {
|
||||
// s is a string and o is a number
|
||||
return 1
|
||||
}
|
||||
// Both are numbers
|
||||
if si > oi {
|
||||
return 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// Like strings.ContainsAny but does an only instead of any.
|
||||
func containsOnly(s string, comp string) bool {
|
||||
return strings.IndexFunc(s, func(r rune) bool {
|
||||
return !strings.ContainsRune(comp, r)
|
||||
}) == -1
|
||||
}
|
||||
|
||||
// From the spec, "Identifiers MUST comprise only
|
||||
// ASCII alphanumerics and hyphen [0-9A-Za-z-]. Identifiers MUST NOT be empty.
|
||||
// Numeric identifiers MUST NOT include leading zeroes.". These segments can
|
||||
// be dot separated.
|
||||
func validatePrerelease(p string) error {
|
||||
eparts := strings.Split(p, ".")
|
||||
for _, p := range eparts {
|
||||
if p == "" {
|
||||
return ErrInvalidPrerelease
|
||||
} else if containsOnly(p, num) {
|
||||
if len(p) > 1 && p[0] == '0' {
|
||||
return ErrSegmentStartsZero
|
||||
}
|
||||
} else if !containsOnly(p, allowed) {
|
||||
return ErrInvalidPrerelease
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// From the spec, "Build metadata MAY be denoted by
|
||||
// appending a plus sign and a series of dot separated identifiers immediately
|
||||
// following the patch or pre-release version. Identifiers MUST comprise only
|
||||
// ASCII alphanumerics and hyphen [0-9A-Za-z-]. Identifiers MUST NOT be empty."
|
||||
func validateMetadata(m string) error {
|
||||
eparts := strings.Split(m, ".")
|
||||
for _, p := range eparts {
|
||||
if p == "" {
|
||||
return ErrInvalidMetadata
|
||||
} else if !containsOnly(p, allowed) {
|
||||
return ErrInvalidMetadata
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateVersion checks for common validation issues but may not catch all errors
|
||||
func validateVersion(m []string) error {
|
||||
var err error
|
||||
var v string
|
||||
if m[1] != "" {
|
||||
if len(m[1]) > 1 && m[1][0] == '0' {
|
||||
return ErrSegmentStartsZero
|
||||
}
|
||||
_, err = strconv.ParseUint(m[1], 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing version segment: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if m[2] != "" {
|
||||
v = strings.TrimPrefix(m[2], ".")
|
||||
if len(v) > 1 && v[0] == '0' {
|
||||
return ErrSegmentStartsZero
|
||||
}
|
||||
_, err = strconv.ParseUint(v, 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing version segment: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if m[3] != "" {
|
||||
v = strings.TrimPrefix(m[3], ".")
|
||||
if len(v) > 1 && v[0] == '0' {
|
||||
return ErrSegmentStartsZero
|
||||
}
|
||||
_, err = strconv.ParseUint(v, 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing version segment: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if m[5] != "" {
|
||||
if err = validatePrerelease(m[5]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if m[8] != "" {
|
||||
if err = validateMetadata(m[8]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
-1
@@ -1,5 +1,4 @@
|
||||
profile.cov
|
||||
string_crypt.go
|
||||
pool_ch.go_
|
||||
archive/
|
||||
tests/string_crypt_test.go
|
||||
+869
-180
File diff suppressed because it is too large.
Load diff
+332
-100
@@ -2,9 +2,11 @@ package g
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql/driver"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
"unsafe"
|
||||
@@ -15,33 +17,30 @@ import (
|
||||
"golang.org/x/text/unicode/norm"
|
||||
)
|
||||
|
||||
// Bytes is a wrapper around the []byte type.
|
||||
type Bytes []byte
|
||||
|
||||
var (
|
||||
lower = cases.Lower(language.Und)
|
||||
upper = cases.Upper(language.Und)
|
||||
title = cases.Title(language.Und)
|
||||
)
|
||||
|
||||
// NewBytes creates a new Bytes value.
|
||||
func NewBytes(size ...Int) Bytes {
|
||||
var (
|
||||
length Int
|
||||
capacity Int
|
||||
)
|
||||
|
||||
switch {
|
||||
case len(size) > 1:
|
||||
length, capacity = size[0], size[1]
|
||||
case len(size) == 1:
|
||||
length, capacity = size[0], size[0]
|
||||
}
|
||||
|
||||
return make([]byte, length, capacity)
|
||||
}
|
||||
// NewBytes creates a Bytes from the provided string or byte slice, mirroring
|
||||
// NewString. For an empty pre-sized buffer use make(Bytes, n) or
|
||||
// make(Bytes, n, cap) directly.
|
||||
func NewBytes[T ~string | ~[]byte](b T) Bytes { return Bytes(b) }
|
||||
|
||||
// Transform applies a transformation function to the Bytes and returns the result.
|
||||
func (bs Bytes) Transform(fn func(Bytes) Bytes) Bytes { return fn(bs) }
|
||||
func (bs Bytes) Transform[U any](fn func(Bytes) U) U { return fn(bs) }
|
||||
|
||||
// Reverse reverses bytes for ASCII or invalid UTF-8 for valid UTF-8 it reverses by runes.
|
||||
// Min returns the minimum of Bytes.
|
||||
func (bs Bytes) Min(b ...Bytes) Bytes { return cmp.MinBy(Bytes.Cmp, append(b, bs)...) }
|
||||
|
||||
// Max returns the maximum of Bytes.
|
||||
func (bs Bytes) Max(b ...Bytes) Bytes { return cmp.MaxBy(Bytes.Cmp, append(b, bs)...) }
|
||||
|
||||
// Reverse reverses bytes for ASCII or invalid UTF-8; for valid UTF-8 it reverses by runes.
|
||||
func (bs Bytes) Reverse() Bytes {
|
||||
n := len(bs)
|
||||
out := make(Bytes, n)
|
||||
@@ -134,12 +133,14 @@ func intFromBytes(bs Bytes, order binary.ByteOrder) Int {
|
||||
}
|
||||
|
||||
// IntBE interprets the Bytes as a signed 64-bit integer in BigEndian order.
|
||||
// If the Bytes length is less than 8, it is padded with leading zeros.
|
||||
// If the Bytes length is less than 8, the value is sign-extended to 64 bits
|
||||
// (the most-significant byte's high bit determines the sign).
|
||||
// If the Bytes length is greater than 8, only the last 8 bytes are used.
|
||||
func (bs Bytes) IntBE() Int { return intFromBytes(bs, binary.BigEndian) }
|
||||
|
||||
// IntLE interprets the Bytes as a signed 64-bit integer in LittleEndian order.
|
||||
// If the Bytes length is less than 8, it is padded with trailing zeros.
|
||||
// If the Bytes length is less than 8, the value is sign-extended to 64 bits
|
||||
// (the most-significant byte's high bit determines the sign).
|
||||
// If the Bytes length is greater than 8, only the first 8 bytes are used.
|
||||
func (bs Bytes) IntLE() Int { return intFromBytes(bs, binary.LittleEndian) }
|
||||
|
||||
@@ -173,31 +174,102 @@ func (bs Bytes) StripPrefix(cutset Bytes) Bytes { return bytes.TrimPrefix(bs, cu
|
||||
// StripSuffix trims the specified Bytes suffix from the Bytes.
|
||||
func (bs Bytes) StripSuffix(cutset Bytes) Bytes { return bytes.TrimSuffix(bs, cutset) }
|
||||
|
||||
// Split splits the Bytes by the specified separator and returns the iterator.
|
||||
func (bs Bytes) Split(sep ...Bytes) SeqSlice[Bytes] {
|
||||
return transformSeq(
|
||||
bytes.SplitSeq(bs, Slice[Bytes](sep).Get(0).UnwrapOrDefault()),
|
||||
func(b []byte) Bytes { return Bytes(b) },
|
||||
)
|
||||
// StartsWith checks if the Bytes starts with the specified prefix.
|
||||
func (bs Bytes) StartsWith(prefix Bytes) bool { return bytes.HasPrefix(bs, prefix) }
|
||||
|
||||
// StartsWithAny checks if the Bytes starts with any of the provided prefixes.
|
||||
// The method accepts a variable number of arguments, allowing for checking against multiple
|
||||
// prefixes at once. It iterates over the provided prefixes and uses the HasPrefix function from
|
||||
// the bytes package to check if the Bytes starts with each prefix.
|
||||
// The function returns true if the Bytes starts with any of the prefixes, and false otherwise.
|
||||
func (bs Bytes) StartsWithAny(prefixes ...Bytes) bool {
|
||||
for _, prefix := range prefixes {
|
||||
if bytes.HasPrefix(bs, prefix) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// SplitAfter splits the Bytes after each instance of the specified separator and returns the iterator.
|
||||
func (bs Bytes) SplitAfter(sep Bytes) SeqSlice[Bytes] {
|
||||
return transformSeq(bytes.SplitAfterSeq(bs, sep), func(b []byte) Bytes { return Bytes(b) })
|
||||
// EndsWith checks if the Bytes ends with the specified suffix.
|
||||
func (bs Bytes) EndsWith(suffix Bytes) bool { return bytes.HasSuffix(bs, suffix) }
|
||||
|
||||
// EndsWithAny checks if the Bytes ends with any of the provided suffixes.
|
||||
// The method accepts a variable number of arguments, allowing for checking against multiple
|
||||
// suffixes at once. It iterates over the provided suffixes and uses the HasSuffix function from
|
||||
// the bytes package to check if the Bytes ends with each suffix.
|
||||
// The function returns true if the Bytes ends with any of the suffixes, and false otherwise.
|
||||
func (bs Bytes) EndsWithAny(suffixes ...Bytes) bool {
|
||||
for _, suffix := range suffixes {
|
||||
if bytes.HasSuffix(bs, suffix) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Fields splits the Bytes into a slice of substrings, removing any whitespace, and returns the iterator.
|
||||
func (bs Bytes) Fields() SeqSlice[Bytes] {
|
||||
return transformSeq(bytes.FieldsSeq(bs), func(b []byte) Bytes { return Bytes(b) })
|
||||
// Split splits the Bytes by the specified separator. If sep is empty, the
|
||||
// Bytes are split after each UTF-8 rune. See [String.Lines] for why the return
|
||||
// type is a plain slice.
|
||||
func (bs Bytes) Split(sep Bytes) []Bytes {
|
||||
return castBytesSlices(bytes.Split(bs, sep))
|
||||
}
|
||||
|
||||
// FieldsBy splits the Bytes into a slice of substrings using a custom function to determine the field boundaries,
|
||||
// and returns the iterator.
|
||||
func (bs Bytes) FieldsBy(fn func(r rune) bool) SeqSlice[Bytes] {
|
||||
return transformSeq(bytes.FieldsFuncSeq(bs, fn), func(b []byte) Bytes { return Bytes(b) })
|
||||
// SplitAfter splits the Bytes after each instance of the specified separator.
|
||||
// See [String.Lines] for why the return type is a plain slice.
|
||||
func (bs Bytes) SplitAfter(sep Bytes) []Bytes {
|
||||
return castBytesSlices(bytes.SplitAfter(bs, sep))
|
||||
}
|
||||
|
||||
// SplitN splits the Bytes into subslices using the provided separator and
|
||||
// returns a plain []Bytes of the results (convert with Slice[Bytes] for
|
||||
// chaining). The n parameter controls the number of subslices to return:
|
||||
// - If n is negative, there is no limit on the number of subslices returned.
|
||||
// - If n is zero, an empty slice is returned.
|
||||
// - If n is positive, at most n subslices are returned.
|
||||
func (bs Bytes) SplitN(sep Bytes, n Int) []Bytes {
|
||||
parts := bytes.SplitN(bs, sep, n.Std())
|
||||
|
||||
result := make([]Bytes, len(parts))
|
||||
for i, p := range parts {
|
||||
result[i] = Bytes(p)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Lines splits the Bytes by lines, with trailing whitespace trimmed per line.
|
||||
// See [String.Lines] for why the return type is a plain slice.
|
||||
func (bs Bytes) Lines() []Bytes {
|
||||
var result []Bytes
|
||||
|
||||
for line := range bytes.Lines(bs) {
|
||||
result = append(result, Bytes(line).TrimEnd())
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Fields splits the Bytes around whitespace. See [String.Lines] for why the
|
||||
// return type is a plain slice.
|
||||
func (bs Bytes) Fields() []Bytes {
|
||||
return castBytesSlices(bytes.Fields(bs))
|
||||
}
|
||||
|
||||
// FieldsBy splits the Bytes using a custom function to determine the field
|
||||
// boundaries. See [String.Lines] for why the return type is a plain slice.
|
||||
func (bs Bytes) FieldsBy(fn func(r rune) bool) []Bytes {
|
||||
return castBytesSlices(bytes.FieldsFunc(bs, fn))
|
||||
}
|
||||
|
||||
// Append appends the given Bytes to the current Bytes.
|
||||
//
|
||||
// Warning: like the builtin append, this may reuse and mutate the receiver's
|
||||
// backing array when it has spare capacity, so the returned Bytes can alias bs.
|
||||
// This is asymmetric with Prepend (which always copies) and with the immutable
|
||||
// String.Append. Clone the receiver first if it must remain unchanged.
|
||||
func (bs Bytes) Append(obs Bytes) Bytes { return append(bs, obs...) }
|
||||
|
||||
// Prepend prepends the given Bytes to the current Bytes.
|
||||
@@ -260,8 +332,8 @@ func (bs Bytes) ContainsRune(r rune) bool { return bytes.ContainsRune(bs, r) }
|
||||
// Count counts the number of occurrences of the specified Bytes in the Bytes.
|
||||
func (bs Bytes) Count(obs Bytes) Int { return Int(bytes.Count(bs, obs)) }
|
||||
|
||||
// Empty checks if the Bytes is empty.
|
||||
func (bs Bytes) Empty() bool { return len(bs) == 0 }
|
||||
// IsEmpty checks if the Bytes is empty.
|
||||
func (bs Bytes) IsEmpty() bool { return len(bs) == 0 }
|
||||
|
||||
// Eq checks if the Bytes is equal to another Bytes.
|
||||
func (bs Bytes) Eq(obs Bytes) bool { return bs.Cmp(obs).IsEq() }
|
||||
@@ -272,15 +344,36 @@ func (bs Bytes) EqFold(obs Bytes) bool { return bytes.EqualFold(bs, obs) }
|
||||
// Gt checks if the Bytes is greater than another Bytes.
|
||||
func (bs Bytes) Gt(obs Bytes) bool { return bs.Cmp(obs).IsGt() }
|
||||
|
||||
// Gte checks if the Bytes is greater than or equal to another Bytes.
|
||||
func (bs Bytes) Gte(obs Bytes) bool { return !bs.Cmp(obs).IsLt() }
|
||||
|
||||
// String returns the Bytes as an String.
|
||||
func (bs Bytes) String() String { return String(bs) }
|
||||
|
||||
// StringUnsafe converts the Bytes into a String without copying memory.
|
||||
// Warning: the resulting String shares the same underlying memory as the original Bytes.
|
||||
// If the Bytes is modified later, the String will reflect those changes and may cause undefined behavior.
|
||||
func (bs Bytes) StringUnsafe() String { return String(*(*string)(unsafe.Pointer(&bs))) }
|
||||
func (bs Bytes) StringUnsafe() String { return String(unsafe.String(unsafe.SliceData(bs), len(bs))) }
|
||||
|
||||
// Index returns the index of the first instance of obs in bs, or -1 if bs is not present in obs.
|
||||
// TryInt parses the Bytes as an integer, mirroring String.TryInt.
|
||||
func (bs Bytes) TryInt() Result[Int] { return bs.StringUnsafe().TryInt() }
|
||||
|
||||
// TryUint parses the Bytes as an unsigned integer, mirroring String.TryUint.
|
||||
func (bs Bytes) TryUint() Result[uint] { return bs.StringUnsafe().TryUint() }
|
||||
|
||||
// TryFloat parses the Bytes as a float, mirroring String.TryFloat.
|
||||
func (bs Bytes) TryFloat() Result[Float] { return bs.StringUnsafe().TryFloat() }
|
||||
|
||||
// TryBool parses the Bytes as a bool, mirroring String.TryBool.
|
||||
func (bs Bytes) TryBool() Result[bool] { return bs.StringUnsafe().TryBool() }
|
||||
|
||||
// TryComplex parses the Bytes as a complex number, mirroring String.TryComplex.
|
||||
func (bs Bytes) TryComplex() Result[complex128] { return bs.StringUnsafe().TryComplex() }
|
||||
|
||||
// TryBigInt parses the Bytes as a *big.Int, mirroring String.TryBigInt.
|
||||
func (bs Bytes) TryBigInt() Result[*big.Int] { return bs.StringUnsafe().TryBigInt() }
|
||||
|
||||
// Index returns the index of the first instance of obs in bs, or -1 if obs is not present in bs.
|
||||
func (bs Bytes) Index(obs Bytes) Int { return Int(bytes.Index(bs, obs)) }
|
||||
|
||||
// LastIndex returns the index of the last instance of obs in bs, or -1 if obs is not present in bs.
|
||||
@@ -307,99 +400,197 @@ func (bs Bytes) LenRunes() Int { return Int(utf8.RuneCount(bs)) }
|
||||
// Lt checks if the Bytes is less than another Bytes.
|
||||
func (bs Bytes) Lt(obs Bytes) bool { return bs.Cmp(obs).IsLt() }
|
||||
|
||||
// Lte checks if the Bytes is less than or equal to another Bytes.
|
||||
func (bs Bytes) Lte(obs Bytes) bool { return !bs.Cmp(obs).IsGt() }
|
||||
|
||||
// Map applies a function to each rune in the Bytes and returns the modified Bytes.
|
||||
func (bs Bytes) Map(fn func(rune) rune) Bytes { return bytes.Map(fn, bs) }
|
||||
|
||||
// NormalizeNFC returns a new Bytes with its Unicode characters normalized using the NFC form.
|
||||
func (bs Bytes) NormalizeNFC() Bytes { return norm.NFC.Bytes(bs) }
|
||||
|
||||
// Ne checks if the Bytes is not equal to another Bytes.
|
||||
func (bs Bytes) Ne(obs Bytes) bool { return !bs.Eq(obs) }
|
||||
|
||||
// NotEmpty checks if the Bytes is not empty.
|
||||
func (bs Bytes) NotEmpty() bool { return !bs.Empty() }
|
||||
|
||||
// Reader returns a *bytes.Reader initialized with the content of Bytes.
|
||||
func (bs Bytes) Reader() *bytes.Reader { return bytes.NewReader(bs) }
|
||||
|
||||
// Repeat returns a new Bytes consisting of the current Bytes repeated 'count' times.
|
||||
func (bs Bytes) Repeat(count Int) Bytes { return bytes.Repeat(bs, count.Std()) }
|
||||
|
||||
// Reset resets the length of the Bytes slice to zero, preserving its capacity.
|
||||
func (bs *Bytes) Reset() { *bs = (*bs)[:0] }
|
||||
|
||||
// Runes returns the Bytes as a slice of runes.
|
||||
func (bs Bytes) Runes() []rune { return bytes.Runes(bs) }
|
||||
|
||||
// Title converts the Bytes to title case.
|
||||
func (bs Bytes) Title() Bytes { return title.Bytes(bs) }
|
||||
|
||||
// Lower converts the Bytes to lowercase.
|
||||
func (bs Bytes) Lower() Bytes {
|
||||
for _, b := range bs {
|
||||
if b >= utf8.RuneSelf {
|
||||
return lower.Bytes(bs)
|
||||
}
|
||||
}
|
||||
// NormalizeNFC returns a new Bytes with its Unicode characters normalized using the NFC form.
|
||||
func (bs Bytes) NormalizeNFC() Bytes { return norm.NFC.Bytes(bs) }
|
||||
|
||||
needs := false
|
||||
// Reset resets the length of the Bytes slice to zero, preserving its capacity.
|
||||
func (bs *Bytes) Reset() { *bs = (*bs)[:0] }
|
||||
|
||||
for _, b := range bs {
|
||||
if 'A' <= b && b <= 'Z' {
|
||||
needs = true
|
||||
break
|
||||
}
|
||||
}
|
||||
// Runes returns the Bytes as a plain slice of runes.
|
||||
func (bs Bytes) Runes() []rune { return bytes.Runes(bs) }
|
||||
|
||||
if !needs {
|
||||
return bs
|
||||
}
|
||||
|
||||
out := make(Bytes, len(bs))
|
||||
// Chars splits the Bytes into individual UTF-8 characters, equivalent to
|
||||
// bs.Split(Bytes("")) and mirroring String.Chars.
|
||||
func (bs Bytes) Chars() []Bytes { return bs.Split(Bytes("")) }
|
||||
|
||||
func convertCase(bs Bytes, from byte, diff int8, ucFn func([]byte) []byte) Bytes {
|
||||
for i, b := range bs {
|
||||
if 'A' <= b && b <= 'Z' {
|
||||
out[i] = b + ('a' - 'A')
|
||||
} else {
|
||||
out[i] = b
|
||||
if b >= utf8.RuneSelf {
|
||||
return ucFn(bs)
|
||||
}
|
||||
|
||||
if from <= b && b <= from+25 {
|
||||
for _, c := range bs[i+1:] {
|
||||
if c >= utf8.RuneSelf {
|
||||
return ucFn(bs)
|
||||
}
|
||||
}
|
||||
|
||||
out := make(Bytes, len(bs))
|
||||
copy(out, bs[:i])
|
||||
out[i] = byte(int8(b) + diff)
|
||||
|
||||
for j, c := range bs[i+1:] {
|
||||
if from <= c && c <= from+25 {
|
||||
out[i+1+j] = byte(int8(c) + diff)
|
||||
} else {
|
||||
out[i+1+j] = c
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
return bs
|
||||
}
|
||||
|
||||
// Lower converts the Bytes to lowercase.
|
||||
func (bs Bytes) Lower() Bytes { return convertCase(bs, 'A', 'a'-'A', lower.Bytes) }
|
||||
|
||||
// Upper converts the Bytes to uppercase.
|
||||
func (bs Bytes) Upper() Bytes {
|
||||
for _, b := range bs {
|
||||
if b >= utf8.RuneSelf {
|
||||
return upper.Bytes(bs)
|
||||
}
|
||||
}
|
||||
func (bs Bytes) Upper() Bytes { return convertCase(bs, 'a', 'A'-'a', upper.Bytes) }
|
||||
|
||||
needs := false
|
||||
|
||||
for _, b := range bs {
|
||||
if 'a' <= b && b <= 'z' {
|
||||
needs = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !needs {
|
||||
return bs
|
||||
}
|
||||
|
||||
out := make(Bytes, len(bs))
|
||||
// IsLower reports whether bs contains at least one letter and no uppercase letters.
|
||||
func (bs Bytes) IsLower() bool {
|
||||
letter := false
|
||||
|
||||
for i, b := range bs {
|
||||
if b >= utf8.RuneSelf {
|
||||
rest := bs[i:]
|
||||
for len(rest) > 0 {
|
||||
r, size := utf8.DecodeRune(rest)
|
||||
rest = rest[size:]
|
||||
if r == utf8.RuneError && size == 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
if unicode.IsLetter(r) {
|
||||
letter = true
|
||||
if unicode.IsUpper(r) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return letter
|
||||
}
|
||||
|
||||
if 'A' <= b && b <= 'Z' {
|
||||
return false
|
||||
}
|
||||
|
||||
if 'a' <= b && b <= 'z' {
|
||||
out[i] = b - ('a' - 'A')
|
||||
} else {
|
||||
out[i] = b
|
||||
letter = true
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
return letter
|
||||
}
|
||||
|
||||
// IsUpper reports whether bs contains at least one letter and no lowercase letters.
|
||||
func (bs Bytes) IsUpper() bool {
|
||||
letter := false
|
||||
|
||||
for i, b := range bs {
|
||||
if b >= utf8.RuneSelf {
|
||||
rest := bs[i:]
|
||||
for len(rest) > 0 {
|
||||
r, size := utf8.DecodeRune(rest)
|
||||
rest = rest[size:]
|
||||
if r == utf8.RuneError && size == 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
if unicode.IsLetter(r) {
|
||||
letter = true
|
||||
if unicode.IsLower(r) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return letter
|
||||
}
|
||||
|
||||
if 'a' <= b && b <= 'z' {
|
||||
return false
|
||||
}
|
||||
|
||||
if 'A' <= b && b <= 'Z' {
|
||||
letter = true
|
||||
}
|
||||
}
|
||||
|
||||
return letter
|
||||
}
|
||||
|
||||
// IsTitle reports whether bs is in title case: the first letter of each word
|
||||
// is uppercase (or titlecase), the remaining letters are lowercase.
|
||||
// Non-letter characters act as word separators. Returns false if bs has no letters.
|
||||
func (bs Bytes) IsTitle() bool {
|
||||
letter := false
|
||||
prevLetter := false
|
||||
|
||||
for i, b := range bs {
|
||||
if b >= utf8.RuneSelf {
|
||||
rest := bs[i:]
|
||||
for len(rest) > 0 {
|
||||
r, size := utf8.DecodeRune(rest)
|
||||
rest = rest[size:]
|
||||
|
||||
if r == utf8.RuneError && size == 1 {
|
||||
prevLetter = false
|
||||
continue
|
||||
}
|
||||
|
||||
if unicode.IsLetter(r) {
|
||||
letter = true
|
||||
if prevLetter && !unicode.IsLower(r) {
|
||||
return false
|
||||
}
|
||||
if !prevLetter && !unicode.IsUpper(r) && !unicode.IsTitle(r) {
|
||||
return false
|
||||
}
|
||||
prevLetter = true
|
||||
} else {
|
||||
prevLetter = false
|
||||
}
|
||||
}
|
||||
|
||||
return letter
|
||||
}
|
||||
|
||||
if ('a' <= b && b <= 'z') || ('A' <= b && b <= 'Z') {
|
||||
letter = true
|
||||
if prevLetter == (b <= 'Z') {
|
||||
return false
|
||||
}
|
||||
prevLetter = true
|
||||
} else {
|
||||
prevLetter = false
|
||||
}
|
||||
}
|
||||
|
||||
return letter
|
||||
}
|
||||
|
||||
// Print writes the content of the Bytes to the standard output (console)
|
||||
@@ -409,3 +600,44 @@ func (bs Bytes) Print() Bytes { fmt.Print(bs); return bs }
|
||||
// Println writes the content of the Bytes to the standard output (console) with a newline
|
||||
// and returns the Bytes unchanged.
|
||||
func (bs Bytes) Println() Bytes { fmt.Println(bs); return bs }
|
||||
|
||||
// Scan implements the database/sql.Scanner interface for g.Bytes.
|
||||
//
|
||||
// Behavior:
|
||||
// - If src is nil, the Bytes slice is set to nil (SQL NULL).
|
||||
// - If src is a []byte, a copy is stored (database/sql may reuse the driver's
|
||||
// buffer on the next row, so the bytes must not be retained by reference).
|
||||
// - Otherwise, an error is returned.
|
||||
//
|
||||
// Supported SQL types (common):
|
||||
// - BLOB / BYTEA → []byte
|
||||
//
|
||||
// Notes:
|
||||
// - This allows g.Bytes to be used directly with database/sql and compatible drivers.
|
||||
func (bs *Bytes) Scan(src any) error {
|
||||
if src == nil {
|
||||
*bs = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
if b, ok := src.([]byte); ok {
|
||||
*bs = append(Bytes(nil), b...)
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("g.Bytes.Scan: cannot scan %T into g.Bytes", src)
|
||||
}
|
||||
|
||||
// Value implements the database/sql/driver.Valuer interface for g.Bytes.
|
||||
//
|
||||
// Behavior:
|
||||
// - Returns the underlying byte slice, ready for database insertion.
|
||||
// - Always returns a value compatible with SQL BLOB / BYTEA types.
|
||||
func (bs Bytes) Value() (driver.Value, error) { return []byte(bs), nil }
|
||||
|
||||
// castBytesSlices reinterprets a [][]byte as []Bytes without copying: Bytes is
|
||||
// defined as `type Bytes []byte`, so the two slice types share one memory
|
||||
// layout.
|
||||
func castBytesSlices(bss [][]byte) []Bytes {
|
||||
return unsafe.Slice((*Bytes)(unsafe.SliceData(bss)), len(bss))
|
||||
}
|
||||
+59
-17
@@ -1,11 +1,11 @@
|
||||
package g
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"encoding/hex"
|
||||
"hash"
|
||||
)
|
||||
|
||||
@@ -15,25 +15,67 @@ type bhash struct{ bytes Bytes }
|
||||
// Hash returns a bhash struct wrapping the given Bytes.
|
||||
func (bs Bytes) Hash() bhash { return bhash{bs} }
|
||||
|
||||
// MD5 computes the MD5 hash of the wrapped Bytes and returns the hash as an Bytes.
|
||||
func (bh bhash) MD5() Bytes { return bytesHasher(md5.New(), bh.bytes) }
|
||||
// MD5 computes the MD5 hash of the wrapped Bytes and returns the hash as hex-encoded Bytes.
|
||||
//
|
||||
// Warning: MD5 is cryptographically broken and must not be used as a security
|
||||
// primitive (e.g. for passwords, signatures, or integrity against an adversary).
|
||||
// Use it only for checksums or non-security obfuscation. Prefer SHA256/SHA512.
|
||||
func (bh bhash) MD5() Bytes { return bh.MD5Raw().Encode().Hex() }
|
||||
|
||||
// SHA1 computes the SHA1 hash of the wrapped Bytes and returns the hash as an Bytes.
|
||||
func (bh bhash) SHA1() Bytes { return bytesHasher(sha1.New(), bh.bytes) }
|
||||
// SHA1 computes the SHA1 hash of the wrapped Bytes and returns the hash as hex-encoded Bytes.
|
||||
//
|
||||
// Warning: SHA1 is cryptographically broken and must not be used as a security
|
||||
// primitive (e.g. for signatures or integrity against an adversary). Use it only
|
||||
// for checksums or non-security obfuscation. Prefer SHA256/SHA512.
|
||||
func (bh bhash) SHA1() Bytes { return bh.SHA1Raw().Encode().Hex() }
|
||||
|
||||
// SHA256 computes the SHA256 hash of the wrapped Bytes and returns the hash as an Bytes.
|
||||
func (bh bhash) SHA256() Bytes { return bytesHasher(sha256.New(), bh.bytes) }
|
||||
// SHA256 computes the SHA256 hash of the wrapped Bytes and returns the hash as hex-encoded Bytes.
|
||||
func (bh bhash) SHA256() Bytes { return bh.SHA256Raw().Encode().Hex() }
|
||||
|
||||
// SHA512 computes the SHA512 hash of the wrapped Bytes and returns the hash as an Bytes.
|
||||
func (bh bhash) SHA512() Bytes { return bytesHasher(sha512.New(), bh.bytes) }
|
||||
// SHA512 computes the SHA512 hash of the wrapped Bytes and returns the hash as hex-encoded Bytes.
|
||||
func (bh bhash) SHA512() Bytes { return bh.SHA512Raw().Encode().Hex() }
|
||||
|
||||
// bytesHasher a helper function that computes the hash of the given Bytes using the specified
|
||||
// hash.Hash algorithm and returns the hash as an Bytes.
|
||||
func bytesHasher(h hash.Hash, bs Bytes) Bytes {
|
||||
_, _ = h.Write(bs)
|
||||
sum := h.Sum(nil)
|
||||
out := make(Bytes, hex.EncodedLen(len(sum)))
|
||||
hex.Encode(out, sum)
|
||||
// HMACSHA256 computes the HMAC-SHA256 of the wrapped Bytes using the provided key
|
||||
// and returns the result as hex-encoded Bytes.
|
||||
func (bh bhash) HMACSHA256(key Bytes) Bytes { return bh.HMACSHA256Raw(key).Encode().Hex() }
|
||||
|
||||
return out
|
||||
// HMACSHA512 computes the HMAC-SHA512 of the wrapped Bytes using the provided key
|
||||
// and returns the result as hex-encoded Bytes.
|
||||
func (bh bhash) HMACSHA512(key Bytes) Bytes { return bh.HMACSHA512Raw(key).Encode().Hex() }
|
||||
|
||||
// MD5Raw computes the MD5 hash of the wrapped Bytes and returns the raw digest.
|
||||
//
|
||||
// Warning: MD5 is cryptographically broken and must not be used as a security
|
||||
// primitive. Use it only for checksums or non-security obfuscation.
|
||||
func (bh bhash) MD5Raw() Bytes { return rawHasher(md5.New(), bh.bytes) }
|
||||
|
||||
// SHA1Raw computes the SHA1 hash of the wrapped Bytes and returns the raw digest.
|
||||
//
|
||||
// Warning: SHA1 is cryptographically broken and must not be used as a security
|
||||
// primitive. Use it only for checksums or non-security obfuscation.
|
||||
func (bh bhash) SHA1Raw() Bytes { return rawHasher(sha1.New(), bh.bytes) }
|
||||
|
||||
// SHA256Raw computes the SHA256 hash of the wrapped Bytes and returns the raw digest.
|
||||
func (bh bhash) SHA256Raw() Bytes { return rawHasher(sha256.New(), bh.bytes) }
|
||||
|
||||
// SHA512Raw computes the SHA512 hash of the wrapped Bytes and returns the raw digest.
|
||||
func (bh bhash) SHA512Raw() Bytes { return rawHasher(sha512.New(), bh.bytes) }
|
||||
|
||||
// HMACSHA256Raw computes the HMAC-SHA256 of the wrapped Bytes using the provided key
|
||||
// and returns the raw digest.
|
||||
func (bh bhash) HMACSHA256Raw(key Bytes) Bytes {
|
||||
return rawHasher(hmac.New(sha256.New, key), bh.bytes)
|
||||
}
|
||||
|
||||
// HMACSHA512Raw computes the HMAC-SHA512 of the wrapped Bytes using the provided key
|
||||
// and returns the raw digest.
|
||||
func (bh bhash) HMACSHA512Raw(key Bytes) Bytes {
|
||||
return rawHasher(hmac.New(sha512.New, key), bh.bytes)
|
||||
}
|
||||
|
||||
// rawHasher computes the hash of the given Bytes using the specified hash.Hash
|
||||
// algorithm and returns the raw digest.
|
||||
func rawHasher(h hash.Hash, bs Bytes) Bytes {
|
||||
_, _ = h.Write(bs)
|
||||
return h.Sum(nil)
|
||||
}
|
||||
-125
@@ -1,125 +0,0 @@
|
||||
package g
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
"github.com/enetx/g/f"
|
||||
)
|
||||
|
||||
// regexps struct wraps a Bytes and provides regex-related methods.
|
||||
type regexpb struct{ bytes Bytes }
|
||||
|
||||
// Regexp wraps a Bytes into an re struct to provide regex-related methods.
|
||||
func (bs Bytes) Regexp() regexpb { return regexpb{bs} }
|
||||
|
||||
// Find searches the Bytes for the first occurrence of the regular expression pattern
|
||||
// and returns an Option[Bytes] containing the matched substring.
|
||||
// If no match is found, the Option[Bytes] will be None.
|
||||
func (r regexpb) Find(pattern *regexp.Regexp) Option[Bytes] {
|
||||
result := Bytes(pattern.Find(r.bytes))
|
||||
if result.Empty() {
|
||||
return None[Bytes]()
|
||||
}
|
||||
|
||||
return Some(result)
|
||||
}
|
||||
|
||||
// Match checks if the Bytes contains a match for the specified regular expression pattern.
|
||||
func (r regexpb) Match(pattern *regexp.Regexp) bool { return f.Match[Bytes](pattern)(r.bytes) }
|
||||
|
||||
// MatchAny checks if the Bytes contains a match for any of the specified regular
|
||||
// expression patterns.
|
||||
func (r regexpb) MatchAny(patterns ...*regexp.Regexp) bool {
|
||||
return Slice[*regexp.Regexp](patterns).
|
||||
Iter().
|
||||
Any(func(pattern *regexp.Regexp) bool { return r.Match(pattern) })
|
||||
}
|
||||
|
||||
// MatchAll checks if the Bytes contains a match for all of the specified regular expression patterns.
|
||||
func (r regexpb) MatchAll(patterns ...*regexp.Regexp) bool {
|
||||
return Slice[*regexp.Regexp](patterns).
|
||||
Iter().
|
||||
All(func(pattern *regexp.Regexp) bool { return r.Match(pattern) })
|
||||
}
|
||||
|
||||
// Index searches for the first occurrence of the regular expression pattern in the Bytes.
|
||||
// If a match is found, it returns an Option containing an Slice with the start and end indices of the match.
|
||||
// If no match is found, it returns None.
|
||||
func (r regexpb) Index(pattern *regexp.Regexp) Option[Slice[Int]] {
|
||||
result := TransformSlice(pattern.FindIndex(r.bytes), NewInt)
|
||||
if result.Empty() {
|
||||
return None[Slice[Int]]()
|
||||
}
|
||||
|
||||
return Some(result)
|
||||
}
|
||||
|
||||
// FindAll searches the Bytes for all occurrences of the regular expression pattern
|
||||
// and returns an Option[Slice[Bytes]] containing a slice of matched substrings.
|
||||
// If no matches are found, the Option[Slice[Bytes]] will be None.
|
||||
func (r regexpb) FindAll(pattern *regexp.Regexp) Option[Slice[Bytes]] {
|
||||
return r.FindAllN(pattern, -1)
|
||||
}
|
||||
|
||||
// FindAllN searches the Bytes for up to n occurrences of the regular expression pattern
|
||||
// and returns an Option[Slice[Bytes]] containing a slice of matched substrings.
|
||||
// If no matches are found, the Option[Slice[Bytes]] will be None.
|
||||
// If n is negative, all occurrences will be returned.
|
||||
func (r regexpb) FindAllN(pattern *regexp.Regexp, n Int) Option[Slice[Bytes]] {
|
||||
result := TransformSlice(pattern.FindAll(r.bytes, n.Std()), func(bs []byte) Bytes { return Bytes(bs) })
|
||||
if result.Empty() {
|
||||
return None[Slice[Bytes]]()
|
||||
}
|
||||
|
||||
return Some(result)
|
||||
}
|
||||
|
||||
// FindSubmatch searches the Bytes for the first occurrence of the regular expression pattern
|
||||
// and returns an Option[Slice[Bytes]] containing the matched substrings and submatches.
|
||||
// The Option[Slice[Bytes]] will contain an Slice[Bytes] for each match,
|
||||
// where each Slice[Bytes] will contain the full match at index 0, followed by any captured submatches.
|
||||
// If no match is found, the Option[Slice[Bytes]] will be None.
|
||||
func (r regexpb) FindSubmatch(pattern *regexp.Regexp) Option[Slice[Bytes]] {
|
||||
result := TransformSlice(pattern.FindSubmatch(r.bytes), func(bs []byte) Bytes { return Bytes(bs) })
|
||||
if result.Empty() {
|
||||
return None[Slice[Bytes]]()
|
||||
}
|
||||
|
||||
return Some(result)
|
||||
}
|
||||
|
||||
// FindAllSubmatch searches the Bytes for all occurrences of the regular expression pattern
|
||||
// and returns an Option[Slice[Slice[Bytes]]] containing the matched substrings and submatches.
|
||||
// The Option[Slice[Slice[Bytes]]] will contain an Slice[Bytes] for each match,
|
||||
// where each Slice[Bytes] will contain the full match at index 0, followed by any captured submatches.
|
||||
// If no match is found, the Option[Slice[Slice[Bytes]]] will be None.
|
||||
// This method is equivalent to calling SubmatchAllRegexpN with n = -1, which means it finds all occurrences.
|
||||
func (r regexpb) FindAllSubmatch(pattern *regexp.Regexp) Option[Slice[Slice[Bytes]]] {
|
||||
return r.FindAllSubmatchN(pattern, -1)
|
||||
}
|
||||
|
||||
// FindAllSubmatchN searches the Bytes for occurrences of the regular expression pattern
|
||||
// and returns an Option[Slice[Slice[Bytes]]] containing the matched substrings and submatches.
|
||||
// The Option[Slice[Slice[Bytes]]] will contain an Slice[Bytes] for each match,
|
||||
// where each Slice[Bytes] will contain the full match at index 0, followed by any captured submatches.
|
||||
// If no match is found, the Option[Slice[Slice[Bytes]]] will be None.
|
||||
// The 'n' parameter specifies the maximum number of matches to find. If n is negative, it finds all occurrences.
|
||||
func (r regexpb) FindAllSubmatchN(pattern *regexp.Regexp, n Int) Option[Slice[Slice[Bytes]]] {
|
||||
var result Slice[Slice[Bytes]]
|
||||
|
||||
for _, v := range pattern.FindAllSubmatch(r.bytes, n.Std()) {
|
||||
result = append(result, TransformSlice(v, func(bs []byte) Bytes { return Bytes(bs) }))
|
||||
}
|
||||
|
||||
if result.Empty() {
|
||||
return None[Slice[Slice[Bytes]]]()
|
||||
}
|
||||
|
||||
return Some(result)
|
||||
}
|
||||
|
||||
// Replace replaces all occurrences of the regular expression matches in the Bytes
|
||||
// with the provided newB and returns the resulting Bytes after the replacement.
|
||||
func (r regexpb) Replace(pattern *regexp.Regexp, newB Bytes) Bytes {
|
||||
return pattern.ReplaceAll(r.bytes, newB)
|
||||
}
|
||||
-81
@@ -1,81 +0,0 @@
|
||||
package cell
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Cell is a thread-safe wrapper around a value T.
|
||||
// It provides safe concurrent access through a read-write mutex.
|
||||
type Cell[T any] struct {
|
||||
mu sync.RWMutex
|
||||
val T
|
||||
}
|
||||
|
||||
// New creates a new Cell with the provided value.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// c := cell.New(42)
|
||||
// config := cell.New(Config{Port: 8080, Debug: false})
|
||||
func New[T any](val T) *Cell[T] {
|
||||
return &Cell[T]{val: val}
|
||||
}
|
||||
|
||||
// Get returns the current value stored in the Cell.
|
||||
func (c *Cell[T]) Get() T {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
|
||||
return c.val
|
||||
}
|
||||
|
||||
// Set replaces the current value with the given value.
|
||||
func (c *Cell[T]) Set(value T) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
c.val = value
|
||||
}
|
||||
|
||||
// Replace atomically replaces the current value with the new one
|
||||
// and returns the previous value.
|
||||
func (c *Cell[T]) Replace(new T) T {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
old := c.val
|
||||
c.val = new
|
||||
|
||||
return old
|
||||
}
|
||||
|
||||
// Swap swaps the values of two cells.
|
||||
func (c *Cell[T]) Swap(other *Cell[T]) {
|
||||
if c == other {
|
||||
return
|
||||
}
|
||||
|
||||
first, second := c, other
|
||||
if uintptr(unsafe.Pointer(c)) > uintptr(unsafe.Pointer(other)) {
|
||||
first, second = other, c
|
||||
}
|
||||
|
||||
first.mu.Lock()
|
||||
defer first.mu.Unlock()
|
||||
|
||||
second.mu.Lock()
|
||||
defer second.mu.Unlock()
|
||||
|
||||
c.val, other.val = other.val, c.val
|
||||
}
|
||||
|
||||
// Update atomically updates the value using the provided function.
|
||||
// The function receives the current value and should return the new value.
|
||||
// This operation is atomic and thread-safe.
|
||||
func (c *Cell[T]) Update(fn func(T) T) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
c.val = fn(c.val)
|
||||
}
|
||||
-68
@@ -1,68 +0,0 @@
|
||||
package cell
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
. "github.com/enetx/g"
|
||||
)
|
||||
|
||||
// LazyCell is a thread-safe, lazy-initialization wrapper around a computation.
|
||||
// The computation function is executed at most once, on the first call to Force().
|
||||
// Subsequent calls return the cached result.
|
||||
// Internally uses Cell for thread-safe operations.
|
||||
type LazyCell[T any] struct {
|
||||
cell *Cell[Option[T]]
|
||||
fn func() T
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
// NewLazy creates a new LazyCell wrapper around the given computation function.
|
||||
//
|
||||
// The function will not be executed until the first call to Force().
|
||||
// The function should be idempotent and side-effect free for predictable behavior.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// expensive := cell.NewLazy(func() int {
|
||||
// time.Sleep(1 * time.Second)
|
||||
// return 42
|
||||
// })
|
||||
//
|
||||
// // Function not called yet
|
||||
// result := expensive.Force() // Function called here
|
||||
// result2 := expensive.Force() // Cached result returned
|
||||
func NewLazy[T any](fn func() T) *LazyCell[T] {
|
||||
return &LazyCell[T]{
|
||||
cell: New(None[T]()),
|
||||
fn: fn,
|
||||
}
|
||||
}
|
||||
|
||||
// Force executes the computation function (if not already executed) and returns the result.
|
||||
//
|
||||
// The function is guaranteed to be called at most once, even in concurrent scenarios.
|
||||
// All subsequent calls return the same cached value.
|
||||
//
|
||||
// This method is thread-safe and can be called from multiple goroutines concurrently.
|
||||
func (l *LazyCell[T]) Force() T {
|
||||
l.once.Do(func() {
|
||||
result := l.fn()
|
||||
l.cell.Set(Some(result))
|
||||
})
|
||||
|
||||
return l.cell.Get().Some()
|
||||
}
|
||||
|
||||
// Get returns Some(value) if the lazy value has been computed, None otherwise.
|
||||
// This method never triggers the computation - it only returns already computed results.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// if val := lazy.Get(); val.IsSome() {
|
||||
// fmt.Println("Already computed:", val.Some())
|
||||
// } else {
|
||||
// fmt.Println("Not computed yet")
|
||||
// }
|
||||
func (l *LazyCell[T]) Get() Option[T] {
|
||||
return l.cell.Get()
|
||||
}
|
||||
-130
@@ -1,130 +0,0 @@
|
||||
package cell
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
. "github.com/enetx/g"
|
||||
)
|
||||
|
||||
// OnceCell is a thread-safe cell which can be set exactly once.
|
||||
// After being set, it provides immutable access to the stored value.
|
||||
// This is equivalent to Rust's OnceCell.
|
||||
type OnceCell[T any] struct {
|
||||
cell *Cell[Option[T]]
|
||||
once sync.Once
|
||||
set bool
|
||||
}
|
||||
|
||||
// NewOnce creates a new empty OnceCell.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// cell := cell.NewOnce[int]()
|
||||
// result := cell.Set(42)
|
||||
// if result.IsOk() {
|
||||
// println("Value set successfully")
|
||||
// }
|
||||
// value := cell.Get()
|
||||
// if value.IsSome() {
|
||||
// println("Value:", value.Some())
|
||||
// }
|
||||
func NewOnce[T any]() *OnceCell[T] {
|
||||
return &OnceCell[T]{
|
||||
cell: New(None[T]()),
|
||||
set: false,
|
||||
}
|
||||
}
|
||||
|
||||
// Set attempts to store a value in the cell.
|
||||
// Returns Ok(()) if the value was stored, Err if the cell was already set.
|
||||
// This operation is thread-safe and will succeed for exactly one caller.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// cell := cell.NewOnce[string]()
|
||||
// result := cell.Set("hello") // Returns Ok(())
|
||||
// result2 := cell.Set("world") // Returns Err("value already set")
|
||||
func (o *OnceCell[T]) Set(value T) Result[Unit] {
|
||||
success := false
|
||||
|
||||
o.once.Do(func() {
|
||||
o.cell.Set(Some(value))
|
||||
o.set = true
|
||||
success = true
|
||||
})
|
||||
|
||||
if success {
|
||||
return Ok(Unit{})
|
||||
}
|
||||
|
||||
return Err[Unit](errors.New("value already set"))
|
||||
}
|
||||
|
||||
// Get returns Some(value) if the cell has been set, None otherwise.
|
||||
// This method never blocks and is very fast after the cell has been set.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// cell := cell.NewOnce[int]()
|
||||
// val := cell.Get()
|
||||
// if val.IsNone() {
|
||||
// println("Cell is empty")
|
||||
// }
|
||||
// cell.Set(42)
|
||||
// val = cell.Get()
|
||||
// println("Value:", val.Some()) // Prints: Value: 42
|
||||
func (o *OnceCell[T]) Get() Option[T] {
|
||||
return o.cell.Get()
|
||||
}
|
||||
|
||||
// GetOrInit returns the value if the cell has been set, or sets and returns
|
||||
// the result of calling the init function. The init function is guaranteed
|
||||
// to be called at most once.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// cell := cell.NewOnce[string]()
|
||||
// value := cell.GetOrInit(func() string {
|
||||
// return "initialized"
|
||||
// })
|
||||
// println(value) // Prints: initialized
|
||||
//
|
||||
// value2 := cell.GetOrInit(func() string {
|
||||
// return "this won't be called"
|
||||
// })
|
||||
// println(value2) // Prints: initialized
|
||||
func (o *OnceCell[T]) GetOrInit(init func() T) T {
|
||||
if current := o.cell.Get(); current.IsSome() {
|
||||
return current.Some()
|
||||
}
|
||||
|
||||
o.once.Do(func() {
|
||||
if !o.set {
|
||||
value := init()
|
||||
o.cell.Set(Some(value))
|
||||
o.set = true
|
||||
}
|
||||
})
|
||||
|
||||
return o.cell.Get().Some()
|
||||
}
|
||||
|
||||
// Take removes and returns the value from the cell, if it has been set.
|
||||
// After calling this method, the cell becomes empty.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// cell := cell.NewOnce[int]()
|
||||
// cell.Set(42)
|
||||
// value := cell.Take()
|
||||
// println(value.Some()) // 42
|
||||
// println(cell.Get().IsNone()) // true
|
||||
func (o *OnceCell[T]) Take() Option[T] {
|
||||
current := o.cell.Get()
|
||||
if current.IsSome() {
|
||||
o.cell.Set(None[T]())
|
||||
}
|
||||
|
||||
return current
|
||||
}
|
||||
+2
@@ -1,3 +1,5 @@
|
||||
// Package cmp provides three-way comparison primitives (Ordering, Cmp, Reverse)
|
||||
// used by the sorting, heap and min/max APIs of the g library.
|
||||
package cmp
|
||||
|
||||
import "cmp"
|
||||
|
||||
+2
-1
@@ -11,7 +11,8 @@ const (
|
||||
Greater // Greater represents an ordered value where a compared value is greater than another.
|
||||
)
|
||||
|
||||
// Then returns the receiver if it's equal to Equal, otherwise returns the receiver.
|
||||
// Then returns other if the receiver is Equal, otherwise returns the receiver.
|
||||
// It is useful for chaining comparisons, where the first non-Equal result wins.
|
||||
func (o Ordering) Then(other Ordering) Ordering {
|
||||
if o.IsEq() {
|
||||
return other
|
||||
|
||||
+44
-3
@@ -1,10 +1,51 @@
|
||||
// Package constraints defines generic type constraints (Signed, Unsigned, Integer, Float, ...)
|
||||
// shared across the g library.
|
||||
package constraints
|
||||
|
||||
// Signed is a constraint that permits any signed integer type.
|
||||
// If future releases of Go add new predeclared signed integer types,
|
||||
// this constraint will be modified to include them.
|
||||
type Signed interface {
|
||||
~int | ~int8 | ~int16 | ~int32 | ~int64
|
||||
}
|
||||
|
||||
// Unsigned is a constraint that permits any unsigned integer type.
|
||||
// If future releases of Go add new predeclared unsigned integer types,
|
||||
// this constraint will be modified to include them.
|
||||
type Unsigned interface {
|
||||
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
|
||||
}
|
||||
|
||||
// Integer is a constraint that permits any integer type.
|
||||
// If future releases of Go add new predeclared integer types,
|
||||
// this constraint will be modified to include them.
|
||||
type Integer interface {
|
||||
Signed | Unsigned
|
||||
}
|
||||
|
||||
// Float is a constraint that permits any floating-point type.
|
||||
// If future releases of Go add new predeclared floating-point types,
|
||||
// this constraint will be modified to include them.
|
||||
type Float interface {
|
||||
~float32 | ~float64
|
||||
}
|
||||
|
||||
type Integer interface {
|
||||
~int | ~int8 | ~int16 | ~int32 | ~int64 |
|
||||
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
|
||||
// Complex is a constraint that permits any complex numeric type.
|
||||
// If future releases of Go add new predeclared complex numeric types,
|
||||
// this constraint will be modified to include them.
|
||||
type Complex interface {
|
||||
~complex64 | ~complex128
|
||||
}
|
||||
|
||||
// Number is a constraint that permits any integer or floating-point type.
|
||||
type Number interface {
|
||||
Integer | Float
|
||||
}
|
||||
|
||||
// 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 interface {
|
||||
Integer | Float | ~string
|
||||
}
|
||||
+208
-88
@@ -2,11 +2,21 @@ package g
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"slices"
|
||||
|
||||
"github.com/enetx/g/cmp"
|
||||
"github.com/enetx/g/f"
|
||||
)
|
||||
|
||||
// Deque is a double-ended queue implemented with a growable ring buffer.
|
||||
// It provides efficient insertion and removal of elements at both ends.
|
||||
type Deque[T any] struct {
|
||||
data Slice[T]
|
||||
front Int
|
||||
len Int
|
||||
}
|
||||
|
||||
// NewDeque creates a new Deque of the given generic type T with the specified capacity.
|
||||
// The capacity parameter specifies the initial capacity of the underlying slice.
|
||||
// If no capacity is provided, an empty Deque with a capacity of 0 is returned.
|
||||
@@ -17,7 +27,7 @@ import (
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - Deque[T]: A new Deque of the specified generic type T with the given capacity
|
||||
// - *Deque[T]: A new Deque of the specified generic type T with the given capacity
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
@@ -39,13 +49,15 @@ func NewDeque[T any](capacity ...Int) *Deque[T] {
|
||||
|
||||
// DequeOf creates a new Deque containing the provided elements.
|
||||
func DequeOf[T any](elements ...T) *Deque[T] {
|
||||
dq := NewDeque[T](Int(len(elements)))
|
||||
n := Int(len(elements))
|
||||
data := make(Slice[T], n)
|
||||
copy(data, elements)
|
||||
|
||||
for _, elem := range elements {
|
||||
dq.PushBack(elem)
|
||||
return &Deque[T]{
|
||||
data: data,
|
||||
front: 0,
|
||||
len: n,
|
||||
}
|
||||
|
||||
return dq
|
||||
}
|
||||
|
||||
// Len returns the number of elements in the Deque.
|
||||
@@ -77,15 +89,30 @@ func (dq *Deque[T]) grow() {
|
||||
}
|
||||
|
||||
newData := make(Slice[T], newCap)
|
||||
|
||||
for i := Int(0); i < dq.len; i++ {
|
||||
newData[i] = dq.data[dq.realIndex(i)]
|
||||
}
|
||||
|
||||
dq.copyToContiguous(newData)
|
||||
dq.data = newData
|
||||
dq.front = 0
|
||||
}
|
||||
|
||||
// copyToContiguous copies the deque's logical elements into dst starting at index 0.
|
||||
// dst must have at least dq.len capacity.
|
||||
func (dq *Deque[T]) copyToContiguous(dst Slice[T]) {
|
||||
if dq.len == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
cap := Int(len(dq.data))
|
||||
if dq.front+dq.len <= cap {
|
||||
// Contiguous region
|
||||
copy(dst, dq.data[dq.front:dq.front+dq.len])
|
||||
} else {
|
||||
// Wrap-around: two copy operations
|
||||
firstPart := cap - dq.front
|
||||
copy(dst, dq.data[dq.front:])
|
||||
copy(dst[firstPart:], dq.data[:dq.len-firstPart])
|
||||
}
|
||||
}
|
||||
|
||||
// PushFront adds an element to the front of the Deque.
|
||||
func (dq *Deque[T]) PushFront(value T) {
|
||||
if dq.len == Int(len(dq.data)) {
|
||||
@@ -108,6 +135,15 @@ func (dq *Deque[T]) PushBack(value T) {
|
||||
dq.len++
|
||||
}
|
||||
|
||||
// Extend appends the given values to the back of the Deque, in order.
|
||||
// It accepts a spread slice too: dq.Extend(sl...).
|
||||
func (dq *Deque[T]) Extend(values ...T) {
|
||||
dq.Reserve(Int(len(values)))
|
||||
for _, v := range values {
|
||||
dq.PushBack(v)
|
||||
}
|
||||
}
|
||||
|
||||
// PopFront removes and returns the first element of the Deque.
|
||||
// Returns None if the Deque is empty.
|
||||
func (dq *Deque[T]) PopFront() Option[T] {
|
||||
@@ -140,7 +176,7 @@ func (dq *Deque[T]) PopBack() Option[T] {
|
||||
return Some(value)
|
||||
}
|
||||
|
||||
// Front returns a reference to the first element.
|
||||
// Front returns the first element of the Deque.
|
||||
// Returns None if the Deque is empty.
|
||||
func (dq *Deque[T]) Front() Option[T] {
|
||||
if dq.IsEmpty() {
|
||||
@@ -150,7 +186,7 @@ func (dq *Deque[T]) Front() Option[T] {
|
||||
return Some(dq.data[dq.front])
|
||||
}
|
||||
|
||||
// Back returns a reference to the last element.
|
||||
// Back returns the last element of the Deque.
|
||||
// Returns None if the Deque is empty.
|
||||
func (dq *Deque[T]) Back() Option[T] {
|
||||
if dq.IsEmpty() {
|
||||
@@ -175,26 +211,27 @@ func (dq *Deque[T]) Get(index Int) Option[T] {
|
||||
return Some(dq.data[realIdx])
|
||||
}
|
||||
|
||||
// Set sets the element at the specified index.
|
||||
// Set sets the element at the specified index, returning the old value as an Option.
|
||||
// Index 0 represents the front of the Deque.
|
||||
// Returns true if the index is valid, false otherwise.
|
||||
func (dq *Deque[T]) Set(index Int, value T) bool {
|
||||
// Returns None if the index is out of bounds.
|
||||
func (dq *Deque[T]) Set(index Int, value T) Option[T] {
|
||||
if index < 0 || index >= dq.len {
|
||||
return false
|
||||
return None[T]()
|
||||
}
|
||||
|
||||
realIdx := dq.realIndex(index)
|
||||
old := dq.data[realIdx]
|
||||
dq.data[realIdx] = value
|
||||
|
||||
return true
|
||||
return Some(old)
|
||||
}
|
||||
|
||||
// Insert inserts an element at the specified index.
|
||||
// Index 0 represents the front of the Deque.
|
||||
// Panics if the index is out of bounds.
|
||||
// Panics if the index is out of bounds (index < 0 or index > Len()).
|
||||
func (dq *Deque[T]) Insert(index Int, value T) {
|
||||
if index < 0 || index > dq.len {
|
||||
panic(fmt.Sprintf("index out of bounds: %d", index))
|
||||
panic(fmt.Sprintf("runtime error: deque index out of range [%d] with length %d", index, dq.len))
|
||||
}
|
||||
|
||||
if index == 0 {
|
||||
@@ -276,12 +313,7 @@ func (dq *Deque[T]) Remove(index Int) Option[T] {
|
||||
|
||||
// Clear removes all elements from the Deque.
|
||||
func (dq *Deque[T]) Clear() {
|
||||
var zero T
|
||||
|
||||
for i := Int(0); i < dq.len; i++ {
|
||||
dq.data[dq.realIndex(i)] = zero
|
||||
}
|
||||
|
||||
clear(dq.data)
|
||||
dq.front = 0
|
||||
dq.len = 0
|
||||
}
|
||||
@@ -289,8 +321,12 @@ func (dq *Deque[T]) Clear() {
|
||||
// Swap swaps the elements at indices i and j.
|
||||
// Panics if either index is out of bounds.
|
||||
func (dq *Deque[T]) Swap(i, j Int) {
|
||||
if i < 0 || i >= dq.len || j < 0 || j >= dq.len {
|
||||
panic("index out of bounds")
|
||||
if i < 0 || i >= dq.len {
|
||||
panic(fmt.Sprintf("runtime error: deque index out of range [%d] with length %d", i, dq.len))
|
||||
}
|
||||
|
||||
if j < 0 || j >= dq.len {
|
||||
panic(fmt.Sprintf("runtime error: deque index out of range [%d] with length %d", j, dq.len))
|
||||
}
|
||||
|
||||
realI := dq.realIndex(i)
|
||||
@@ -310,13 +346,16 @@ func (dq *Deque[T]) RotateLeft(mid Int) {
|
||||
if mid == 0 {
|
||||
return
|
||||
}
|
||||
if dq.len == Int(len(dq.data)) {
|
||||
dq.front = dq.realIndex(mid)
|
||||
return
|
||||
}
|
||||
|
||||
contiguous := dq.MakeContiguous()
|
||||
|
||||
temp := make(Slice[T], mid)
|
||||
copy(temp, contiguous[:mid])
|
||||
copy(contiguous, contiguous[mid:])
|
||||
copy(contiguous[dq.len-mid:], temp)
|
||||
reverseDequeSlice(contiguous[:mid])
|
||||
reverseDequeSlice(contiguous[mid:])
|
||||
reverseDequeSlice(contiguous)
|
||||
}
|
||||
|
||||
// RotateRight rotates the Deque in-place such that the first len - k elements
|
||||
@@ -346,10 +385,7 @@ func (dq *Deque[T]) MakeContiguous() Slice[T] {
|
||||
}
|
||||
|
||||
newData := make(Slice[T], len(dq.data))
|
||||
for i := Int(0); i < dq.len; i++ {
|
||||
newData[i] = dq.data[dq.realIndex(i)]
|
||||
}
|
||||
|
||||
dq.copyToContiguous(newData)
|
||||
dq.data = newData
|
||||
dq.front = 0
|
||||
|
||||
@@ -358,23 +394,44 @@ func (dq *Deque[T]) MakeContiguous() Slice[T] {
|
||||
|
||||
// Clone creates a deep copy of the Deque.
|
||||
func (dq *Deque[T]) Clone() *Deque[T] {
|
||||
newDeque := NewDeque[T](dq.Capacity())
|
||||
newData := make(Slice[T], len(dq.data))
|
||||
dq.copyToContiguous(newData)
|
||||
|
||||
for i := Int(0); i < dq.len; i++ {
|
||||
newDeque.PushBack(dq.data[dq.realIndex(i)])
|
||||
return &Deque[T]{
|
||||
data: newData,
|
||||
front: 0,
|
||||
len: dq.len,
|
||||
}
|
||||
|
||||
return newDeque
|
||||
}
|
||||
|
||||
// Iter returns an iterator for the Deque, allowing for sequential iteration
|
||||
// over its elements from front to back.
|
||||
func (dq *Deque[T]) Iter() SeqDeque[T] {
|
||||
func (dq *Deque[T]) Iter() Seq[T] {
|
||||
return func(yield func(T) bool) {
|
||||
for i := Int(0); i < dq.len; i++ {
|
||||
value := dq.data[dq.realIndex(i)]
|
||||
if !yield(value) {
|
||||
return
|
||||
cap := Int(len(dq.data))
|
||||
if cap == 0 || dq.len == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if dq.front+dq.len <= cap {
|
||||
// Contiguous: iterate directly
|
||||
for _, v := range dq.data[dq.front : dq.front+dq.len] {
|
||||
if !yield(v) {
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Wrap-around: first part from front to end, then from start
|
||||
for _, v := range dq.data[dq.front:] {
|
||||
if !yield(v) {
|
||||
return
|
||||
}
|
||||
}
|
||||
wrapLen := dq.len - (cap - dq.front)
|
||||
for _, v := range dq.data[:wrapLen] {
|
||||
if !yield(v) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -382,7 +439,7 @@ func (dq *Deque[T]) Iter() SeqDeque[T] {
|
||||
|
||||
// IterReverse returns an iterator for the Deque that allows for sequential iteration
|
||||
// over its elements in reverse order (from back to front).
|
||||
func (dq *Deque[T]) IterReverse() SeqDeque[T] {
|
||||
func (dq *Deque[T]) IterReverse() Seq[T] {
|
||||
return func(yield func(T) bool) {
|
||||
for i := dq.len - 1; i >= 0; i-- {
|
||||
value := dq.data[dq.realIndex(i)]
|
||||
@@ -411,10 +468,7 @@ func (dq *Deque[T]) Reserve(additional Int) {
|
||||
}
|
||||
|
||||
newData := make(Slice[T], newCap)
|
||||
for i := Int(0); i < dq.len; i++ {
|
||||
newData[i] = dq.data[dq.realIndex(i)]
|
||||
}
|
||||
|
||||
dq.copyToContiguous(newData)
|
||||
dq.data = newData
|
||||
dq.front = 0
|
||||
}
|
||||
@@ -433,27 +487,43 @@ func (dq *Deque[T]) ShrinkToFit() {
|
||||
}
|
||||
|
||||
newData := make(Slice[T], dq.len)
|
||||
for i := Int(0); i < dq.len; i++ {
|
||||
newData[i] = dq.data[dq.realIndex(i)]
|
||||
}
|
||||
|
||||
dq.copyToContiguous(newData)
|
||||
dq.data = newData
|
||||
dq.front = 0
|
||||
}
|
||||
|
||||
// Contains checks if the Deque contains the specified value.
|
||||
func (dq *Deque[T]) Contains(value T) bool {
|
||||
var zero T
|
||||
if dq.len == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if f.IsComparable(zero) {
|
||||
for i := Int(0); i < dq.len; i++ {
|
||||
if f.Eq[any](dq.data[dq.realIndex(i)])(value) {
|
||||
return true
|
||||
cap := Int(len(dq.data))
|
||||
|
||||
if f.IsComparable[T]() && reflect.TypeFor[T]().Kind() != reflect.Interface {
|
||||
target := any(value)
|
||||
if dq.front+dq.len <= cap {
|
||||
for _, v := range dq.data[dq.front : dq.front+dq.len] {
|
||||
if any(v) == target {
|
||||
return true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, v := range dq.data[dq.front:] {
|
||||
if any(v) == target {
|
||||
return true
|
||||
}
|
||||
}
|
||||
wrapLen := dq.len - (cap - dq.front)
|
||||
for _, v := range dq.data[:wrapLen] {
|
||||
if any(v) == target {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for i := Int(0); i < dq.len; i++ {
|
||||
if f.Eqd(value)(dq.data[dq.realIndex(i)]) {
|
||||
if reflect.DeepEqual(dq.data[dq.realIndex(i)], value) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -462,20 +532,47 @@ func (dq *Deque[T]) Contains(value T) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// ContainsAny checks if the Deque contains any element from the provided values.
|
||||
func (dq *Deque[T]) ContainsAny(values ...T) bool {
|
||||
if dq.len == 0 || len(values) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
return slices.ContainsFunc(values, dq.Contains)
|
||||
}
|
||||
|
||||
// ContainsAll checks if the Deque contains all of the provided values.
|
||||
func (dq *Deque[T]) ContainsAll(values ...T) bool {
|
||||
if len(values) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
if dq.len == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, v := range values {
|
||||
if !dq.Contains(v) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Index returns the index of the first occurrence of the specified value,
|
||||
// or -1 if not found.
|
||||
func (dq *Deque[T]) Index(value T) Int {
|
||||
var zero T
|
||||
|
||||
if f.IsComparable(zero) {
|
||||
if f.IsComparable[T]() && reflect.TypeFor[T]().Kind() != reflect.Interface {
|
||||
target := any(value)
|
||||
for i := Int(0); i < dq.len; i++ {
|
||||
if f.Eq[any](dq.data[dq.realIndex(i)])(value) {
|
||||
if any(dq.data[dq.realIndex(i)]) == target {
|
||||
return i
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for i := Int(0); i < dq.len; i++ {
|
||||
if f.Eqd(value)(dq.data[dq.realIndex(i)]) {
|
||||
if reflect.DeepEqual(dq.data[dq.realIndex(i)], value) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
@@ -485,14 +582,13 @@ func (dq *Deque[T]) Index(value T) Int {
|
||||
}
|
||||
|
||||
// BinarySearch searches for a value in a sorted Deque using binary search.
|
||||
// Returns the index where the value is found, or where it should be inserted.
|
||||
// Returns the index where the value is found, or where it should be inserted,
|
||||
// and a boolean reporting whether the value was found.
|
||||
func (dq *Deque[T]) BinarySearch(value T, fn func(T, T) cmp.Ordering) (Int, bool) {
|
||||
contiguous := dq.MakeContiguous()
|
||||
|
||||
left, right := Int(0), dq.len
|
||||
for left < right {
|
||||
mid := (left + right) / 2
|
||||
result := fn(contiguous[mid], value)
|
||||
result := fn(dq.data[dq.realIndex(mid)], value)
|
||||
|
||||
switch result {
|
||||
case cmp.Less:
|
||||
@@ -507,32 +603,48 @@ func (dq *Deque[T]) BinarySearch(value T, fn func(T, T) cmp.Ordering) (Int, bool
|
||||
return left, false
|
||||
}
|
||||
|
||||
// ToSlice converts the Deque to a Slice, maintaining element order.
|
||||
func (dq *Deque[T]) ToSlice() Slice[T] {
|
||||
result := make(Slice[T], dq.len)
|
||||
|
||||
for i := Int(0); i < dq.len; i++ {
|
||||
result[i] = dq.data[dq.realIndex(i)]
|
||||
func reverseDequeSlice[T any](values Slice[T]) {
|
||||
for left, right := 0, len(values)-1; left < right; left, right = left+1, right-1 {
|
||||
values[left], values[right] = values[right], values[left]
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Transform applies a transformation function to the Deque and returns the result.
|
||||
func (dq *Deque[T]) Transform[U any](fn func(*Deque[T]) U) U { return fn(dq) }
|
||||
|
||||
// String returns a string representation of the Deque.
|
||||
func (dq Deque[T]) String() string {
|
||||
func (dq *Deque[T]) String() string {
|
||||
if dq.IsEmpty() {
|
||||
return "Deque[]"
|
||||
}
|
||||
|
||||
var b Builder
|
||||
b.Grow(dq.len * 8)
|
||||
b.WriteString("Deque[")
|
||||
|
||||
for i := Int(0); i < dq.len; i++ {
|
||||
if i > 0 {
|
||||
cap := Int(len(dq.data))
|
||||
first := true
|
||||
|
||||
writeElem := func(v T) {
|
||||
if !first {
|
||||
b.WriteString(", ")
|
||||
}
|
||||
first = false
|
||||
fmt.Fprint(&b, v)
|
||||
}
|
||||
|
||||
b.WriteString(Format("{}", dq.data[dq.realIndex(i)]))
|
||||
if dq.front+dq.len <= cap {
|
||||
for _, v := range dq.data[dq.front : dq.front+dq.len] {
|
||||
writeElem(v)
|
||||
}
|
||||
} else {
|
||||
for _, v := range dq.data[dq.front:] {
|
||||
writeElem(v)
|
||||
}
|
||||
wrapLen := dq.len - (cap - dq.front)
|
||||
for _, v := range dq.data[:wrapLen] {
|
||||
writeElem(v)
|
||||
}
|
||||
}
|
||||
|
||||
b.WriteString("]")
|
||||
@@ -542,16 +654,21 @@ func (dq Deque[T]) String() string {
|
||||
|
||||
// Eq checks if two Deques are equal.
|
||||
func (dq *Deque[T]) Eq(other *Deque[T]) bool {
|
||||
if dq == other {
|
||||
return true
|
||||
}
|
||||
|
||||
if dq == nil || other == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if dq.len != other.len {
|
||||
return false
|
||||
}
|
||||
|
||||
var zero T
|
||||
if f.IsComparable(zero) {
|
||||
if f.IsComparable[T]() && reflect.TypeFor[T]().Kind() != reflect.Interface {
|
||||
for i := Int(0); i < dq.len; i++ {
|
||||
a := dq.data[dq.realIndex(i)]
|
||||
b := other.data[other.realIndex(i)]
|
||||
if !f.Eq[any](a)(b) {
|
||||
if any(dq.data[dq.realIndex(i)]) != any(other.data[other.realIndex(i)]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -559,7 +676,7 @@ func (dq *Deque[T]) Eq(other *Deque[T]) bool {
|
||||
for i := Int(0); i < dq.len; i++ {
|
||||
a := dq.data[dq.realIndex(i)]
|
||||
b := other.data[other.realIndex(i)]
|
||||
if !f.Eqd(a)(b) {
|
||||
if !reflect.DeepEqual(a, b) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -568,6 +685,9 @@ func (dq *Deque[T]) Eq(other *Deque[T]) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Ne checks if two Deques are not equal.
|
||||
func (dq *Deque[T]) Ne(other *Deque[T]) bool { return !dq.Eq(other) }
|
||||
|
||||
// Retain keeps only the elements specified by the predicate.
|
||||
func (dq *Deque[T]) Retain(predicate func(T) bool) {
|
||||
writePos := Int(0)
|
||||
|
||||
-967
@@ -1,967 +0,0 @@
|
||||
package g
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"runtime"
|
||||
|
||||
"github.com/enetx/g/cmp"
|
||||
"github.com/enetx/g/f"
|
||||
"github.com/enetx/iter"
|
||||
)
|
||||
|
||||
// Pull converts the "push-style" iterator sequence seq
|
||||
// into a "pull-style" iterator accessed by the two functions
|
||||
// next and stop.
|
||||
//
|
||||
// Next returns the next value in the sequence
|
||||
// and a boolean indicating whether the value is valid.
|
||||
// When the sequence is over, next returns the zero V and false.
|
||||
// It is valid to call next after reaching the end of the sequence
|
||||
// or after calling stop. These calls will continue
|
||||
// to return the zero V and false.
|
||||
//
|
||||
// Stop ends the iteration. It must be called when the caller is
|
||||
// no longer interested in next values and next has not yet
|
||||
// signaled that the sequence is over (with a false boolean return).
|
||||
// It is valid to call stop multiple times and when next has
|
||||
// already returned false.
|
||||
//
|
||||
// It is an error to call next or stop from multiple goroutines
|
||||
// simultaneously.
|
||||
func (seq SeqDeque[V]) Pull() (func() (V, bool), func()) { return iter.Pull(iter.Seq[V](seq)) }
|
||||
|
||||
// Parallel converts a sequential deque iterator into a parallel iterator with the specified number of workers.
|
||||
// If no worker count is provided, it defaults to the number of CPU cores.
|
||||
// The parallel iterator processes elements concurrently using a worker pool.
|
||||
func (seq SeqDeque[V]) Parallel(workers ...Int) SeqDequePar[V] {
|
||||
numCPU := Int(runtime.NumCPU())
|
||||
count := Slice[Int](workers).Get(0).UnwrapOr(numCPU)
|
||||
|
||||
if count.Lte(0) {
|
||||
count = numCPU
|
||||
}
|
||||
|
||||
return SeqDequePar[V]{
|
||||
seq: seq,
|
||||
workers: count,
|
||||
process: func(v V) (V, bool) { return v, true },
|
||||
}
|
||||
}
|
||||
|
||||
// All checks whether all elements in the iterator satisfy the provided condition.
|
||||
// This function is useful when you want to determine if all elements in an iterator
|
||||
// meet a specific criteria.
|
||||
//
|
||||
// Parameters:
|
||||
// - fn func(V) bool: A function that returns a boolean indicating whether the element satisfies
|
||||
// the condition.
|
||||
//
|
||||
// Returns:
|
||||
// - bool: True if all elements in the iterator satisfy the condition, false otherwise.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// deque := g.DequeOf(1, 2, 3, 4, 5, 6, 7, -1, -2)
|
||||
// isPositive := func(num int) bool { return num > 0 }
|
||||
// allPositive := deque.Iter().All(isPositive)
|
||||
//
|
||||
// The resulting allPositive will be true if all elements returned by the iterator are positive.
|
||||
func (seq SeqDeque[V]) All(fn func(v V) bool) bool { return iter.All(iter.Seq[V](seq), fn) }
|
||||
|
||||
// Any checks whether any element in the iterator satisfies the provided condition.
|
||||
// This function is useful when you want to determine if at least one element in an iterator
|
||||
// meets a specific criteria.
|
||||
//
|
||||
// Parameters:
|
||||
// - fn func(V) bool: A function that returns a boolean indicating whether the element satisfies
|
||||
// the condition.
|
||||
//
|
||||
// Returns:
|
||||
// - bool: True if at least one element in the iterator satisfies the condition, false otherwise.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// deque := g.DequeOf(1, 3, 5, 7, 9)
|
||||
// isEven := func(num int) bool { return num%2 == 0 }
|
||||
// anyEven := deque.Iter().Any(isEven)
|
||||
//
|
||||
// The resulting anyEven will be true if at least one element returned by the iterator is even.
|
||||
func (seq SeqDeque[V]) Any(fn func(V) bool) bool { return iter.Any(iter.Seq[V](seq), fn) }
|
||||
|
||||
// Chain concatenates the current iterator with other iterators, returning a new iterator.
|
||||
//
|
||||
// The function creates a new iterator that combines the elements of the current iterator
|
||||
// with elements from the provided iterators in the order they are given.
|
||||
//
|
||||
// Params:
|
||||
//
|
||||
// - seqs ([]SeqDeque[V]): Other iterators to be concatenated with the current iterator.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - SeqDeque[V]: A new iterator containing elements from the current iterator and the provided iterators.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// iter1 := g.DequeOf(1, 2, 3).Iter()
|
||||
// iter2 := g.DequeOf(4, 5, 6).Iter()
|
||||
// iter1.Chain(iter2).Collect().Print()
|
||||
//
|
||||
// Output: Deque[1, 2, 3, 4, 5, 6]
|
||||
//
|
||||
// The resulting iterator will contain elements from both iterators in the specified order.
|
||||
func (seq SeqDeque[V]) Chain(seqs ...SeqDeque[V]) SeqDeque[V] {
|
||||
iterSeqs := make([]iter.Seq[V], len(seqs))
|
||||
for i, s := range seqs {
|
||||
iterSeqs[i] = iter.Seq[V](s)
|
||||
}
|
||||
|
||||
return SeqDeque[V](iter.Chain(iter.Seq[V](seq), iterSeqs...))
|
||||
}
|
||||
|
||||
// Chunks returns an iterator that yields chunks of elements of the specified size.
|
||||
//
|
||||
// The function creates a new iterator that yields chunks of elements from the original iterator,
|
||||
// with each chunk containing elements of the specified size.
|
||||
//
|
||||
// Params:
|
||||
//
|
||||
// - n (Int): The size of each chunk.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - SeqSlices[V]: An iterator yielding chunks of elements of the specified size.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// deque := g.DequeOf(1, 2, 3, 4, 5, 6)
|
||||
// chunks := deque.Iter().Chunks(2).Collect()
|
||||
//
|
||||
// Output: [Slice[1, 2] Slice[3, 4] Slice[5, 6]]
|
||||
//
|
||||
// The resulting iterator will yield chunks of elements, each containing the specified number of elements.
|
||||
func (seq SeqDeque[V]) Chunks(n Int) SeqSlices[V] {
|
||||
return SeqSlices[V](iter.Chunks(iter.Seq[V](seq), int(n)))
|
||||
}
|
||||
|
||||
// Collect gathers all elements from the iterator into a Deque.
|
||||
func (seq SeqDeque[V]) Collect() *Deque[V] {
|
||||
result := NewDeque[V]()
|
||||
seq(func(v V) bool {
|
||||
result.PushBack(v)
|
||||
return true
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
// Count consumes the iterator, counting the number of iterations and returning it.
|
||||
func (seq SeqDeque[V]) Count() Int { return Int(iter.Count(iter.Seq[V](seq))) }
|
||||
|
||||
// Counter returns a map where each key is a unique element
|
||||
// from the deque and each value is the count of how many times that element appears.
|
||||
//
|
||||
// The function counts the occurrences of each element in the deque
|
||||
// and returns a map representing the unique elements and their respective counts.
|
||||
// This method uses iter.Counter from the iter package.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - SeqMapOrd[V, Int]: with keys representing the unique elements in the deque
|
||||
// and values representing the counts of those elements.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// deque := g.DequeOf(1, 2, 3, 1, 2, 1)
|
||||
// counts := deque.Iter().Counter()
|
||||
// // The counts map will contain:
|
||||
// // 1 -> 3 (since 1 appears three times)
|
||||
// // 2 -> 2 (since 2 appears two times)
|
||||
// // 3 -> 1 (since 3 appears once)
|
||||
func (seq SeqDeque[V]) Counter() SeqMapOrd[any, Int] {
|
||||
return func(yield func(any, Int) bool) {
|
||||
for k, v := range iter.Counter(iter.Seq[V](seq)) {
|
||||
if !yield(k, Int(v)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GroupBy groups consecutive elements of the sequence based on a custom equality function.
|
||||
//
|
||||
// The provided function `fn` takes two consecutive elements `a` and `b` and returns `true`
|
||||
// if they belong to the same group, or `false` if a new group should start.
|
||||
// The function returns a `SeqSlices[V]`, where each `[]V` represents a group of consecutive
|
||||
// elements that satisfy the provided equality condition.
|
||||
//
|
||||
// Notes:
|
||||
// - Each group is returned as a copy of the elements, since `SeqDeque` does not guarantee
|
||||
// that elements share the same backing array.
|
||||
//
|
||||
// Parameters:
|
||||
// - fn (func(a, b V) bool): Function that determines whether two consecutive elements belong to the same group.
|
||||
//
|
||||
// Returns:
|
||||
// - SeqSlices[V]: An iterator yielding slices, each containing one group.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// deque := g.DequeOf(1, 1, 2, 3, 2, 3, 4)
|
||||
// groups := deque.Iter().GroupBy(func(a, b int) bool { return a <= b }).Collect()
|
||||
// // Output: [Slice[1, 1, 2, 3] Slice[2, 3, 4]]
|
||||
//
|
||||
// The resulting iterator will yield groups of consecutive elements according to the provided function.
|
||||
func (seq SeqDeque[V]) GroupBy(fn func(a, b V) bool) SeqSlices[V] {
|
||||
return SeqSlices[V](iter.GroupByAdjacent(iter.Seq[V](seq), fn))
|
||||
}
|
||||
|
||||
// Combinations generates all combinations of length 'n' from the sequence.
|
||||
func (seq SeqDeque[V]) Combinations(size Int) SeqSlices[V] {
|
||||
return SeqSlices[V](iter.Combinations(iter.Seq[V](seq), int(size)))
|
||||
}
|
||||
|
||||
// Cycle returns an iterator that endlessly repeats the elements of the current sequence.
|
||||
func (seq SeqDeque[V]) Cycle() SeqDeque[V] {
|
||||
return SeqDeque[V](iter.Cycle(iter.Seq[V](seq)))
|
||||
}
|
||||
|
||||
// Enumerate adds an index to each element in the iterator.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - SeqMapOrd[Int, V] An iterator with each element of type Pair[Int, V], where the first
|
||||
// element of the pair is the index and the second element is the original element from the
|
||||
// iterator.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// ps := g.DequeOf("bbb", "ddd", "xxx", "aaa", "ccc").
|
||||
// Iter().
|
||||
// Enumerate().
|
||||
// Collect()
|
||||
//
|
||||
// ps.Print()
|
||||
//
|
||||
// Output: MapOrd{0:bbb, 1:ddd, 2:xxx, 3:aaa, 4:ccc}
|
||||
func (seq SeqDeque[V]) Enumerate() SeqMapOrd[Int, V] {
|
||||
return func(yield func(Int, V) bool) {
|
||||
iterEnum := iter.Enumerate(iter.Seq[V](seq), 0)
|
||||
iterEnum(func(i int, v V) bool {
|
||||
return yield(Int(i), v)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Dedup creates a new iterator that removes consecutive duplicate elements from the original iterator,
|
||||
// leaving only one occurrence of each unique element. If the iterator is sorted, all elements will be unique.
|
||||
//
|
||||
// Parameters:
|
||||
// - None
|
||||
//
|
||||
// Returns:
|
||||
// - SeqDeque[V]: A new iterator with consecutive duplicates removed.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// deque := g.DequeOf(1, 2, 2, 3, 4, 4, 4, 5)
|
||||
// iter := deque.Iter().Dedup()
|
||||
// result := iter.Collect()
|
||||
// result.Print()
|
||||
//
|
||||
// Output: Deque[1, 2, 3, 4, 5]
|
||||
//
|
||||
// The resulting iterator will contain only unique elements, removing consecutive duplicates.
|
||||
func (seq SeqDeque[V]) Dedup() SeqDeque[V] {
|
||||
return SeqDeque[V](iter.DedupBy(iter.Seq[V](seq), func(a, b V) bool {
|
||||
if f.IsComparable(a) {
|
||||
return f.Eq[any](a)(b)
|
||||
}
|
||||
return f.Eqd(a)(b)
|
||||
}))
|
||||
}
|
||||
|
||||
// Filter returns a new iterator containing only the elements that satisfy the provided function.
|
||||
//
|
||||
// The function applies the provided function to each element of the iterator.
|
||||
// If the function returns true for an element, that element is included in the resulting iterator.
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// - fn (func(V) bool): The function to be applied to each element of the iterator
|
||||
// to determine if it should be included in the result.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - SeqDeque[V]: A new iterator containing the elements that satisfy the given condition.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// deque := g.DequeOf(1, 2, 3, 4, 5)
|
||||
// even := deque.Iter().
|
||||
// Filter(
|
||||
// func(val int) bool {
|
||||
// return val%2 == 0
|
||||
// }).
|
||||
// Collect()
|
||||
// even.Print()
|
||||
//
|
||||
// Output: Deque[2, 4].
|
||||
//
|
||||
// The resulting iterator will contain only the elements that satisfy the provided function.
|
||||
func (seq SeqDeque[V]) Filter(fn func(V) bool) SeqDeque[V] {
|
||||
return SeqDeque[V](iter.Filter(iter.Seq[V](seq), fn))
|
||||
}
|
||||
|
||||
// Exclude returns a new iterator excluding elements that satisfy the provided function.
|
||||
//
|
||||
// The function applies the provided function to each element of the iterator.
|
||||
// If the function returns true for an element, that element is excluded from the resulting iterator.
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// - fn (func(V) bool): The function to be applied to each element of the iterator
|
||||
// to determine if it should be excluded from the result.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - SeqDeque[V]: A new iterator containing the elements that do not satisfy the given condition.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// deque := g.DequeOf(1, 2, 3, 4, 5)
|
||||
// notEven := deque.Iter().
|
||||
// Exclude(
|
||||
// func(val int) bool {
|
||||
// return val%2 == 0
|
||||
// }).
|
||||
// Collect()
|
||||
// notEven.Print()
|
||||
//
|
||||
// Output: Deque[1, 3, 5]
|
||||
//
|
||||
// The resulting iterator will contain only the elements that do not satisfy the provided function.
|
||||
func (seq SeqDeque[V]) Exclude(fn func(V) bool) SeqDeque[V] {
|
||||
return SeqDeque[V](iter.Exclude(iter.Seq[V](seq), fn))
|
||||
}
|
||||
|
||||
// Fold accumulates values in the iterator using a function.
|
||||
//
|
||||
// The function iterates through the elements of the iterator, accumulating values
|
||||
// using the provided function and an initial value.
|
||||
//
|
||||
// Params:
|
||||
//
|
||||
// - init (V): The initial value for accumulation.
|
||||
// - fn (func(V, V) V): The function that accumulates values; it takes two arguments
|
||||
// of type V and returns a value of type V.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - T: The accumulated value after applying the function to all elements.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// deque := g.DequeOf(1, 2, 3, 4, 5)
|
||||
// sum := deque.Iter().
|
||||
// Fold(0,
|
||||
// func(acc, val int) int {
|
||||
// return acc + val
|
||||
// })
|
||||
// fmt.Println(sum)
|
||||
//
|
||||
// Output: 15.
|
||||
//
|
||||
// The resulting value will be the accumulation of elements based on the provided function.
|
||||
func (seq SeqDeque[V]) Fold(init V, fn func(acc, val V) V) V {
|
||||
return iter.Fold(iter.Seq[V](seq), init, fn)
|
||||
}
|
||||
|
||||
// Reduce aggregates elements of the sequence using the provided function.
|
||||
// The first element of the sequence is used as the initial accumulator value.
|
||||
// If the sequence is empty, it returns None[V].
|
||||
//
|
||||
// Params:
|
||||
// - fn (func(V, V) V): Function that combines two values into one.
|
||||
//
|
||||
// Returns:
|
||||
// - Option[V]: The accumulated value wrapped in Some, or None if the sequence is empty.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// deque := g.DequeOf(1, 2, 3, 4, 5)
|
||||
// product := deque.Iter().Reduce(func(a, b int) int { return a * b })
|
||||
// if product.IsSome() {
|
||||
// fmt.Println(product.Some()) // 120
|
||||
// } else {
|
||||
// fmt.Println("empty")
|
||||
// }
|
||||
func (seq SeqDeque[V]) Reduce(fn func(a, b V) V) Option[V] {
|
||||
return OptionOf(iter.Reduce(iter.Seq[V](seq), fn))
|
||||
}
|
||||
|
||||
// ForEach iterates through all elements and applies the given function to each.
|
||||
//
|
||||
// The function applies the provided function to each element of the iterator.
|
||||
//
|
||||
// Params:
|
||||
//
|
||||
// - fn (func(V)): The function to apply to each element.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// iter := g.DequeOf(1, 2, 3, 4, 5).Iter()
|
||||
// iter.ForEach(func(val V) {
|
||||
// fmt.Println(val) // Replace this with the function logic you need.
|
||||
// })
|
||||
//
|
||||
// The provided function will be applied to each element in the iterator.
|
||||
func (seq SeqDeque[V]) ForEach(fn func(v V)) { iter.ForEach(iter.Seq[V](seq), fn) }
|
||||
|
||||
// Flatten flattens an iterator containing slices into a single iterator.
|
||||
//
|
||||
// The function creates a new iterator that flattens a sequence of iterators,
|
||||
// returning a single iterator containing elements from each iterator in sequence.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - SeqDeque[V]: A single iterator containing elements from the sequence of iterators.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// nestedDeque := g.DequeOf(
|
||||
// 1,
|
||||
// g.SliceOf(2, 3),
|
||||
// "abc",
|
||||
// g.SliceOf("def", "ghi"),
|
||||
// g.SliceOf(4.5, 6.7),
|
||||
// )
|
||||
//
|
||||
// nestedDeque.Iter().Flatten().Collect().Print()
|
||||
//
|
||||
// Output: Deque[1, 2, 3, abc, def, ghi, 4.5, 6.7]
|
||||
//
|
||||
// The resulting iterator will contain elements from each iterator in sequence.
|
||||
func (seq SeqDeque[V]) Flatten() SeqDeque[V] {
|
||||
return func(yield func(V) bool) {
|
||||
var flatten func(item any) bool
|
||||
flatten = func(item any) bool {
|
||||
rv := reflect.ValueOf(item)
|
||||
switch rv.Kind() {
|
||||
case reflect.Slice, reflect.Array:
|
||||
for i := range rv.Len() {
|
||||
if !flatten(rv.Index(i).Interface()) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
default:
|
||||
if v, ok := item.(V); ok {
|
||||
if !yield(v) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
seq(func(item V) bool {
|
||||
return flatten(item)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Inspect creates a new iterator that wraps around the current iterator
|
||||
// and allows inspecting each element as it passes through.
|
||||
func (seq SeqDeque[V]) Inspect(fn func(v V)) SeqDeque[V] {
|
||||
return SeqDeque[V](iter.Inspect(iter.Seq[V](seq), fn))
|
||||
}
|
||||
|
||||
// Intersperse inserts the provided separator between elements of the iterator.
|
||||
//
|
||||
// The function creates a new iterator that inserts the given separator between each
|
||||
// consecutive pair of elements in the original iterator.
|
||||
//
|
||||
// Params:
|
||||
//
|
||||
// - sep (V): The separator to intersperse between elements.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - SeqDeque[V]: An iterator containing elements with the separator interspersed.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// g.DequeOf("Hello", "World", "!").
|
||||
// Iter().
|
||||
// Intersperse(" ").
|
||||
// Collect().
|
||||
// Print()
|
||||
//
|
||||
// Output: "Hello World !".
|
||||
//
|
||||
// The resulting iterator will contain elements with the separator interspersed.
|
||||
func (seq SeqDeque[V]) Intersperse(sep V) SeqDeque[V] {
|
||||
return SeqDeque[V](iter.Intersperse(iter.Seq[V](seq), sep))
|
||||
}
|
||||
|
||||
// Map transforms each element in the iterator using the given function.
|
||||
//
|
||||
// The function creates a new iterator by applying the provided function to each element
|
||||
// of the original iterator.
|
||||
//
|
||||
// Params:
|
||||
//
|
||||
// - fn (func(V) V): The function used to transform elements.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - SeqDeque[V]: A iterator containing elements transformed by the provided function.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// deque := g.DequeOf(1, 2, 3)
|
||||
// doubled := deque.
|
||||
// Iter().
|
||||
// Map(
|
||||
// func(val int) int {
|
||||
// return val * 2
|
||||
// }).
|
||||
// Collect()
|
||||
// doubled.Print()
|
||||
//
|
||||
// Output: Deque[2, 4, 6].
|
||||
//
|
||||
// The resulting iterator will contain elements transformed by the provided function.
|
||||
func (seq SeqDeque[V]) Map(transform func(V) V) SeqDeque[V] {
|
||||
return SeqDeque[V](iter.Map(iter.Seq[V](seq), transform))
|
||||
}
|
||||
|
||||
// Partition divides the elements of the iterator into two separate deques based on a given predicate function.
|
||||
//
|
||||
// The function takes a predicate function 'fn', which should return true or false for each element in the iterator.
|
||||
// Elements for which 'fn' returns true are collected into the left deque, while those for which 'fn' returns false
|
||||
// are collected into the right deque.
|
||||
//
|
||||
// Params:
|
||||
//
|
||||
// - fn (func(V) bool): The predicate function used to determine the placement of elements.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - (Deque[V], Deque[V]): Two deques representing elements that satisfy and don't satisfy the predicate, respectively.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// evens, odds := g.DequeOf(1, 2, 3, 4, 5).
|
||||
// Iter().
|
||||
// Partition(
|
||||
// func(v int) bool {
|
||||
// return v%2 == 0
|
||||
// })
|
||||
//
|
||||
// fmt.Println("Even numbers:", evens) // Output: Even numbers: Deque[2, 4]
|
||||
// fmt.Println("Odd numbers:", odds) // Output: Odd numbers: Deque[1, 3, 5]
|
||||
//
|
||||
// The resulting two deques will contain elements separated based on whether they satisfy the predicate or not.
|
||||
func (seq SeqDeque[V]) Partition(fn func(v V) bool) (*Deque[V], *Deque[V]) {
|
||||
left := NewDeque[V]()
|
||||
right := NewDeque[V]()
|
||||
|
||||
seq(func(v V) bool {
|
||||
if fn(v) {
|
||||
left.PushBack(v)
|
||||
} else {
|
||||
right.PushBack(v)
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return left, right
|
||||
}
|
||||
|
||||
// Permutations generates iterators of all permutations of elements.
|
||||
//
|
||||
// The function uses a recursive approach to generate all the permutations of the elements.
|
||||
// If the iterator is empty or contains a single element, it returns the iterator itself
|
||||
// wrapped in a single-element iterator.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - SeqSlices[V]: An iterator of iterators containing all possible permutations of the
|
||||
// elements in the iterator.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// deque := g.DequeOf(1, 2, 3)
|
||||
// perms := deque.Iter().Permutations().Collect()
|
||||
// for _, perm := range perms {
|
||||
// fmt.Println(perm)
|
||||
// }
|
||||
//
|
||||
// Output:
|
||||
// Slice[1, 2, 3]
|
||||
// Slice[2, 1, 3]
|
||||
// Slice[3, 1, 2]
|
||||
// Slice[1, 3, 2]
|
||||
// Slice[2, 3, 1]
|
||||
// Slice[3, 2, 1]
|
||||
//
|
||||
// The resulting iterator will contain iterators representing all possible permutations
|
||||
// of the elements in the original iterator.
|
||||
func (seq SeqDeque[V]) Permutations() SeqSlices[V] {
|
||||
return SeqSlices[V](iter.Permutations(iter.Seq[V](seq)))
|
||||
}
|
||||
|
||||
// Range iterates through elements until the given function returns false.
|
||||
//
|
||||
// The function iterates through the elements of the iterator and applies the provided function
|
||||
// to each element. It stops iteration when the function returns false for an element.
|
||||
//
|
||||
// Params:
|
||||
//
|
||||
// - fn (func(V) bool): The function that evaluates elements for continuation of iteration.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// iter := g.DequeOf(1, 2, 3, 4, 5).Iter()
|
||||
// iter.Range(func(val int) bool {
|
||||
// fmt.Println(val) // Replace this with the function logic you need.
|
||||
// return val < 5 // Replace this with the condition for continuing iteration.
|
||||
// })
|
||||
//
|
||||
// The iteration will stop when the provided function returns false for an element.
|
||||
func (seq SeqDeque[V]) Range(fn func(v V) bool) { iter.Range(iter.Seq[V](seq), fn) }
|
||||
|
||||
// Skip returns a new iterator skipping the first n elements.
|
||||
//
|
||||
// The function creates a new iterator that skips the first n elements of the current iterator
|
||||
// and returns an iterator starting from the (n+1)th element.
|
||||
//
|
||||
// Params:
|
||||
//
|
||||
// - n (uint): The number of elements to skip from the beginning of the iterator.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - SeqDeque[V]: An iterator that starts after skipping the first n elements.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// iter := g.DequeOf(1, 2, 3, 4, 5, 6).Iter()
|
||||
// iter.Skip(3).Collect().Print()
|
||||
//
|
||||
// Output: Deque[4, 5, 6]
|
||||
//
|
||||
// The resulting iterator will start after skipping the specified number of elements.
|
||||
func (seq SeqDeque[V]) Skip(n uint) SeqDeque[V] {
|
||||
return SeqDeque[V](iter.Skip(iter.Seq[V](seq), int(n)))
|
||||
}
|
||||
|
||||
// StepBy creates a new iterator that iterates over every N-th element of the original iterator.
|
||||
// This function is useful when you want to skip a specific number of elements between each iteration.
|
||||
//
|
||||
// Parameters:
|
||||
// - n uint: The step size, indicating how many elements to skip between each iteration.
|
||||
//
|
||||
// Returns:
|
||||
// - SeqDeque[V]: A new iterator that produces elements from the original iterator with a step size of N.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// deque := g.DequeOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
|
||||
// iter := deque.Iter().StepBy(3)
|
||||
// result := iter.Collect()
|
||||
// result.Print()
|
||||
//
|
||||
// Output: Deque[1, 4, 7, 10]
|
||||
//
|
||||
// The resulting iterator will produce elements from the original iterator with a step size of N.
|
||||
func (seq SeqDeque[V]) StepBy(n uint) SeqDeque[V] {
|
||||
return SeqDeque[V](iter.StepBy(iter.Seq[V](seq), int(n)))
|
||||
}
|
||||
|
||||
// SortBy applies a custom sorting function to the elements in the iterator
|
||||
// and returns a new iterator containing the sorted elements.
|
||||
//
|
||||
// The sorting function 'fn' should take two arguments, 'a' and 'b' of type V,
|
||||
// and return true if 'a' should be ordered before 'b', and false otherwise.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// g.DequeOf("a", "c", "b").
|
||||
// Iter().
|
||||
// SortBy(func(a, b string) cmp.Ordering { return b.Cmp(a) }).
|
||||
// Collect().
|
||||
// Print()
|
||||
//
|
||||
// Output: Deque[c, b, a]
|
||||
//
|
||||
// The returned iterator is of type SeqDeque[V], which implements the iterator
|
||||
// interface for further iteration over the sorted elements.
|
||||
func (seq SeqDeque[V]) SortBy(fn func(a, b V) cmp.Ordering) SeqDeque[V] {
|
||||
return SeqDeque[V](iter.SortBy(iter.Seq[V](seq), func(a, b V) bool { return fn(a, b) == cmp.Less }))
|
||||
}
|
||||
|
||||
// Take returns a new iterator with the first n elements.
|
||||
// The function creates a new iterator containing the first n elements from the original iterator.
|
||||
func (seq SeqDeque[V]) Take(n uint) SeqDeque[V] {
|
||||
return SeqDeque[V](iter.Take(iter.Seq[V](seq), int(n)))
|
||||
}
|
||||
|
||||
// First returns the first element from the sequence.
|
||||
func (seq SeqDeque[V]) First() Option[V] {
|
||||
return OptionOf(iter.First(iter.Seq[V](seq)))
|
||||
}
|
||||
|
||||
// Last returns the last element from the sequence.
|
||||
func (seq SeqDeque[V]) Last() Option[V] {
|
||||
return OptionOf(iter.Last(iter.Seq[V](seq)))
|
||||
}
|
||||
|
||||
// Nth returns the nth element (0-indexed) in the sequence.
|
||||
func (seq SeqDeque[V]) Nth(n Int) Option[V] {
|
||||
return OptionOf(iter.Nth(iter.Seq[V](seq), int(n)))
|
||||
}
|
||||
|
||||
// ToChan converts the iterator into a channel, optionally with context(s).
|
||||
//
|
||||
// The function converts the elements of the iterator into a channel for streaming purposes.
|
||||
// Optionally, it accepts context(s) to handle cancellation or timeout scenarios.
|
||||
//
|
||||
// Params:
|
||||
//
|
||||
// - ctxs (context.Context): Optional context(s) to control the channel behavior (e.g., cancellation).
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - chan V: A channel containing the elements from the iterator.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// iter := g.DequeOf(1, 2, 3).Iter()
|
||||
// ctx, cancel := context.WithCancel(context.Background())
|
||||
// defer cancel() // Ensure cancellation to avoid goroutine leaks.
|
||||
// ch := iter.ToChan(ctx)
|
||||
// for val := range ch {
|
||||
// fmt.Println(val)
|
||||
// }
|
||||
//
|
||||
// The resulting channel allows streaming elements from the iterator with optional context handling.
|
||||
func (seq SeqDeque[V]) ToChan(ctxs ...context.Context) chan V {
|
||||
ctx := context.Background()
|
||||
if len(ctxs) > 0 {
|
||||
ctx = ctxs[0]
|
||||
}
|
||||
|
||||
return iter.ToChan(iter.Seq[V](seq), ctx)
|
||||
}
|
||||
|
||||
// Unique returns an iterator with only unique elements.
|
||||
//
|
||||
// The function returns an iterator containing only the unique elements from the original iterator.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - SeqDeque[V]: An iterator containing unique elements from the original iterator.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// deque := g.DequeOf(1, 2, 3, 2, 4, 5, 3)
|
||||
// unique := deque.Iter().Unique().Collect()
|
||||
// unique.Print()
|
||||
//
|
||||
// Output: Deque[1, 2, 3, 4, 5].
|
||||
//
|
||||
// The resulting iterator will contain only unique elements from the original iterator.
|
||||
func (seq SeqDeque[V]) Unique() SeqDeque[V] {
|
||||
return SeqDeque[V](iter.Unique(iter.Seq[V](seq)))
|
||||
}
|
||||
|
||||
// Zip combines elements from the current sequence and another sequence into pairs,
|
||||
// creating an ordered map with identical keys and values of type V.
|
||||
func (seq SeqDeque[V]) Zip(two SeqDeque[V]) SeqMapOrd[any, any] {
|
||||
return func(yield func(any, any) bool) {
|
||||
zipSeq := iter.Zip(iter.Seq[V](seq), iter.Seq[V](two))
|
||||
zipSeq(func(a, b V) bool {
|
||||
return yield(a, b)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Find searches for an element in the iterator that satisfies the provided function.
|
||||
//
|
||||
// The function iterates through the elements of the iterator and returns the first element
|
||||
// for which the provided function returns true.
|
||||
//
|
||||
// Params:
|
||||
//
|
||||
// - fn (func(V) bool): The function used to test elements for a condition.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - Option[V]: An Option containing the first element that satisfies the condition; None if not found.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// iter := g.DequeOf(1, 2, 3, 4, 5).Iter()
|
||||
//
|
||||
// found := iter.Find(
|
||||
// func(i int) bool {
|
||||
// return i == 2
|
||||
// })
|
||||
//
|
||||
// if found.IsSome() {
|
||||
// fmt.Println("Found:", found.Some())
|
||||
// } else {
|
||||
// fmt.Println("Not found.")
|
||||
// }
|
||||
//
|
||||
// The resulting Option may contain the first element that satisfies the condition, or None if not found.
|
||||
func (seq SeqDeque[V]) Find(fn func(v V) bool) Option[V] {
|
||||
return OptionOf(iter.Find(iter.Seq[V](seq), fn))
|
||||
}
|
||||
|
||||
// Windows returns an iterator that yields sliding windows of elements of the specified size.
|
||||
//
|
||||
// The function creates a new iterator that yields windows of elements from the original iterator,
|
||||
// where each window is a slice containing elements of the specified size and moves one element at a time.
|
||||
//
|
||||
// Params:
|
||||
//
|
||||
// - n (int): The size of each window.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - SeqSlices[V]: An iterator yielding sliding windows of elements of the specified size.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// deque := g.DequeOf(1, 2, 3, 4, 5, 6)
|
||||
// windows := deque.Iter().Windows(3).Collect()
|
||||
//
|
||||
// Output: [Slice[1, 2, 3] Slice[2, 3, 4] Slice[3, 4, 5] Slice[4, 5, 6]]
|
||||
//
|
||||
// The resulting iterator will yield sliding windows of elements, each containing the specified number of elements.
|
||||
func (seq SeqDeque[V]) Windows(n Int) SeqSlices[V] {
|
||||
return SeqSlices[V](iter.Windows(iter.Seq[V](seq), int(n)))
|
||||
}
|
||||
|
||||
// Context allows the iteration to be controlled with a context.Context.
|
||||
func (seq SeqDeque[V]) Context(ctx context.Context) SeqDeque[V] {
|
||||
return SeqDeque[V](iter.Context(iter.Seq[V](seq), ctx))
|
||||
}
|
||||
|
||||
// MaxBy returns the maximum element in the sequence using the provided comparison function.
|
||||
func (seq SeqDeque[V]) MaxBy(fn func(V, V) cmp.Ordering) Option[V] {
|
||||
return OptionOf(iter.MaxBy(iter.Seq[V](seq), func(a, b V) bool { return fn(a, b) == cmp.Less }))
|
||||
}
|
||||
|
||||
// MinBy returns the minimum element in the sequence using the provided comparison function.
|
||||
func (seq SeqDeque[V]) MinBy(fn func(V, V) cmp.Ordering) Option[V] {
|
||||
return OptionOf(iter.MinBy(iter.Seq[V](seq), func(a, b V) bool { return fn(a, b) == cmp.Less }))
|
||||
}
|
||||
|
||||
// FlatMap applies a function to each element and flattens the results into a single sequence.
|
||||
//
|
||||
// The function transforms each element into a new SeqDeque and then flattens all resulting
|
||||
// sequences into a single sequence.
|
||||
//
|
||||
// Params:
|
||||
//
|
||||
// - fn (func(V) SeqDeque[V]): The function that transforms each element into a SeqDeque.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - SeqDeque[V]: A flattened sequence containing all elements from the transformed sequences.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// deque := g.DequeOf(1, 2, 3)
|
||||
// result := deque.Iter().FlatMap(func(n int) g.SeqDeque[int] {
|
||||
// return g.DequeOf(n, n*10).Iter()
|
||||
// }).Collect()
|
||||
// result.Print() // Deque[1, 10, 2, 20, 3, 30]
|
||||
func (seq SeqDeque[V]) FlatMap(fn func(V) SeqDeque[V]) SeqDeque[V] {
|
||||
mapped := iter.MapTo(iter.Seq[V](seq), func(v V) iter.Seq[V] {
|
||||
return iter.Seq[V](fn(v))
|
||||
})
|
||||
return SeqDeque[V](iter.FlattenSeq(mapped))
|
||||
}
|
||||
|
||||
// FilterMap applies a function to each element and filters out None results.
|
||||
//
|
||||
// The function transforms and filters elements in a single pass. Elements where the function
|
||||
// returns None are filtered out, and elements where it returns Some are unwrapped
|
||||
// and included in the result.
|
||||
//
|
||||
// Params:
|
||||
//
|
||||
// - fn (func(V) Option[V]): The function that transforms and filters elements.
|
||||
// Returns Some(value) to include the transformed element, or None to filter it out.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - SeqDeque[V]: A sequence containing only the successfully transformed elements.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// deque := g.DequeOf(1, 2, 3, 4, 5)
|
||||
// result := deque.Iter().FilterMap(func(n int) g.Option[int] {
|
||||
// if n%2 == 0 {
|
||||
// return g.Some(n * 10)
|
||||
// }
|
||||
// return g.None[int]()
|
||||
// }).Collect()
|
||||
// result.Print() // Deque[20, 40]
|
||||
func (seq SeqDeque[V]) FilterMap(fn func(V) Option[V]) SeqDeque[V] {
|
||||
return SeqDeque[V](iter.FilterMap(iter.Seq[V](seq), func(v V) (V, bool) {
|
||||
return fn(v).Option()
|
||||
}))
|
||||
}
|
||||
|
||||
// Scan applies a function to each element and produces a sequence of successive accumulated results.
|
||||
//
|
||||
// The function takes an initial value and applies the provided function to each element along
|
||||
// with the accumulated value, producing a new sequence where each element is the result of
|
||||
// the accumulation. The initial value is included as the first element.
|
||||
//
|
||||
// Params:
|
||||
//
|
||||
// - init (V): The initial value for the accumulation.
|
||||
// - fn (func(acc, val V) V): The function that combines the accumulator with each element.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - SeqDeque[V]: A sequence containing the initial value and all accumulated results.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// deque := g.DequeOf(1, 2, 3, 4, 5)
|
||||
// result := deque.Iter().Scan(0, func(acc, val int) int {
|
||||
// return acc + val
|
||||
// }).Collect()
|
||||
// result.Print() // Deque[0, 1, 3, 6, 10, 15]
|
||||
func (seq SeqDeque[V]) Scan(init V, fn func(acc, val V) V) SeqDeque[V] {
|
||||
return func(yield func(V) bool) {
|
||||
if !yield(init) {
|
||||
return
|
||||
}
|
||||
iter.Scan(iter.Seq[V](seq), init, fn)(yield)
|
||||
}
|
||||
}
|
||||
|
||||
// Next extracts the next element from the iterator and advances it.
|
||||
//
|
||||
// This method consumes the next element from the iterator and returns it wrapped in an Option.
|
||||
// The iterator itself is modified to point to the remaining elements.
|
||||
//
|
||||
// Returns:
|
||||
// - Option[V]: Some(value) if an element exists, None if the iterator is exhausted.
|
||||
func (seq *SeqDeque[V]) Next() Option[V] {
|
||||
if value, remaining, ok := iter.Next(iter.Seq[V](*seq)); ok {
|
||||
*seq = SeqDeque[V](remaining)
|
||||
return Some(value)
|
||||
}
|
||||
|
||||
return None[V]()
|
||||
}
|
||||
-624
@@ -1,624 +0,0 @@
|
||||
package g
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/enetx/g/cmp"
|
||||
)
|
||||
|
||||
// All returns true only if fn returns true for every element.
|
||||
// It stops early on the first false.
|
||||
func (p SeqDequePar[V]) All(fn func(V) bool) bool {
|
||||
var ok atomic.Bool
|
||||
ok.Store(true)
|
||||
|
||||
p.Range(func(v V) bool {
|
||||
if !fn(v) {
|
||||
ok.Store(false)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return ok.Load()
|
||||
}
|
||||
|
||||
// Any returns true if fn returns true for any element.
|
||||
// It stops early on the first true.
|
||||
func (p SeqDequePar[V]) Any(fn func(V) bool) bool {
|
||||
var ok atomic.Bool
|
||||
|
||||
p.Range(func(v V) bool {
|
||||
if fn(v) {
|
||||
ok.Store(true)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return ok.Load()
|
||||
}
|
||||
|
||||
// Chain concatenates this SeqDequePar with others, preserving full parallelism.
|
||||
// Each sequence runs with its own worker pool in parallel.
|
||||
func (p SeqDequePar[V]) Chain(others ...SeqDequePar[V]) SeqDequePar[V] {
|
||||
return SeqDequePar[V]{
|
||||
seq: func(yield func(V) bool) {
|
||||
done := make(chan struct{})
|
||||
result := make(chan V, int(p.workers)*4)
|
||||
|
||||
var (
|
||||
wg sync.WaitGroup
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
runSequence := func(seq SeqDequePar[V]) {
|
||||
defer wg.Done()
|
||||
seq.Range(func(v V) bool {
|
||||
select {
|
||||
case <-done:
|
||||
return false
|
||||
case result <- v:
|
||||
return true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer close(result)
|
||||
|
||||
wg.Add(1)
|
||||
go runSequence(p)
|
||||
|
||||
for _, o := range others {
|
||||
wg.Add(1)
|
||||
go runSequence(o)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
case v, ok := <-result:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if !yield(v) {
|
||||
once.Do(func() { close(done) })
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
workers: p.workers,
|
||||
process: func(v V) (V, bool) { return v, true },
|
||||
}
|
||||
}
|
||||
|
||||
// Collect gathers all processed elements into a Deque.
|
||||
func (p SeqDequePar[V]) Collect() *Deque[V] {
|
||||
ch := make(chan V)
|
||||
|
||||
go func() {
|
||||
defer close(ch)
|
||||
p.Range(func(v V) bool {
|
||||
ch <- v
|
||||
return true
|
||||
})
|
||||
}()
|
||||
|
||||
result := NewDeque[V](0)
|
||||
for v := range ch {
|
||||
result.PushBack(v)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Count returns the total number of elements processed.
|
||||
func (p SeqDequePar[V]) Count() Int {
|
||||
var count atomic.Int64
|
||||
p.Range(func(V) bool {
|
||||
count.Add(1)
|
||||
return true
|
||||
})
|
||||
|
||||
return Int(count.Load())
|
||||
}
|
||||
|
||||
// Exclude removes elements for which fn returns true, in parallel.
|
||||
func (p SeqDequePar[V]) Exclude(fn func(V) bool) SeqDequePar[V] {
|
||||
return p.Filter(func(v V) bool { return !fn(v) })
|
||||
}
|
||||
|
||||
// Filter retains only elements where fn returns true.
|
||||
func (p SeqDequePar[V]) Filter(fn func(V) bool) SeqDequePar[V] {
|
||||
prev := p.process
|
||||
|
||||
return SeqDequePar[V]{
|
||||
seq: p.seq,
|
||||
workers: p.workers,
|
||||
process: func(v V) (V, bool) {
|
||||
if mid, ok := prev(v); ok && fn(mid) {
|
||||
return mid, true
|
||||
}
|
||||
var zero V
|
||||
return zero, false
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Find returns the first element satisfying fn, or None if no such element exists.
|
||||
func (p SeqDequePar[V]) Find(fn func(V) bool) Option[V] {
|
||||
ch := make(chan V)
|
||||
|
||||
go func() {
|
||||
defer close(ch)
|
||||
p.Range(func(v V) bool {
|
||||
if fn(v) {
|
||||
ch <- v
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
}()
|
||||
|
||||
if v, ok := <-ch; ok {
|
||||
return Some(v)
|
||||
}
|
||||
|
||||
return None[V]()
|
||||
}
|
||||
|
||||
// Fold reduces all elements into a single value, using fn to accumulate results.
|
||||
func (p SeqDequePar[V]) Fold(init V, fn func(acc, v V) V) V {
|
||||
ch := make(chan V)
|
||||
|
||||
go func() {
|
||||
defer close(ch)
|
||||
p.Range(func(v V) bool {
|
||||
ch <- v
|
||||
return true
|
||||
})
|
||||
}()
|
||||
|
||||
acc := init
|
||||
for v := range ch {
|
||||
acc = fn(acc, v)
|
||||
}
|
||||
|
||||
return acc
|
||||
}
|
||||
|
||||
// Flatten unpacks nested slices or arrays in the source, returning a flat parallel sequence.
|
||||
func (p SeqDequePar[V]) Flatten() SeqDequePar[V] {
|
||||
seq := func(yield func(V) bool) {
|
||||
resultsChan := make(chan V, 100)
|
||||
doneChan := make(chan struct{})
|
||||
var once sync.Once
|
||||
|
||||
go func() {
|
||||
defer close(resultsChan)
|
||||
|
||||
p.Range(func(v V) bool {
|
||||
select {
|
||||
case <-doneChan:
|
||||
return false
|
||||
default:
|
||||
}
|
||||
|
||||
flattenedItems := flattenToSlice(v)
|
||||
for _, item := range flattenedItems {
|
||||
if flatItem, ok := item.(V); ok {
|
||||
select {
|
||||
case resultsChan <- flatItem:
|
||||
case <-doneChan:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case v, ok := <-resultsChan:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if !yield(v) {
|
||||
once.Do(func() { close(doneChan) })
|
||||
return
|
||||
}
|
||||
case <-doneChan:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return SeqDequePar[V]{
|
||||
seq: seq,
|
||||
workers: p.workers,
|
||||
process: func(v V) (V, bool) { return v, true },
|
||||
}
|
||||
}
|
||||
|
||||
// FlatMap applies fn to each element in parallel, flattening the resulting sequences.
|
||||
func (p SeqDequePar[V]) FlatMap(fn func(V) SeqDeque[V]) SeqDequePar[V] {
|
||||
return SeqDequePar[V]{
|
||||
seq: func(yield func(V) bool) {
|
||||
done := make(chan struct{})
|
||||
result := make(chan V, 100)
|
||||
|
||||
var (
|
||||
wg sync.WaitGroup
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
go func() {
|
||||
defer close(result)
|
||||
|
||||
p.Range(func(v V) bool {
|
||||
select {
|
||||
case <-done:
|
||||
return false
|
||||
default:
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
go func(val V) {
|
||||
defer wg.Done()
|
||||
fn(val)(func(item V) bool {
|
||||
select {
|
||||
case <-done:
|
||||
return false
|
||||
case result <- item:
|
||||
return true
|
||||
}
|
||||
})
|
||||
}(v)
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
wg.Wait()
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
case v, ok := <-result:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if !yield(v) {
|
||||
once.Do(func() { close(done) })
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
workers: p.workers,
|
||||
process: func(v V) (V, bool) { return v, true },
|
||||
}
|
||||
}
|
||||
|
||||
// FilterMap applies fn to each element in parallel, keeping only Some values.
|
||||
func (p SeqDequePar[V]) FilterMap(fn func(V) Option[V]) SeqDequePar[V] {
|
||||
prev := p.process
|
||||
|
||||
return SeqDequePar[V]{
|
||||
seq: p.seq,
|
||||
workers: p.workers,
|
||||
process: func(v V) (V, bool) {
|
||||
if mid, ok := prev(v); ok {
|
||||
if opt := fn(mid); opt.IsSome() {
|
||||
return opt.Some(), true
|
||||
}
|
||||
}
|
||||
var zero V
|
||||
return zero, false
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// StepBy yields every nth element.
|
||||
func (p SeqDequePar[V]) StepBy(n uint) SeqDequePar[V] {
|
||||
if n == 0 {
|
||||
n = 1
|
||||
}
|
||||
|
||||
prev := p.process
|
||||
counter := &atomic.Uint64{}
|
||||
|
||||
return SeqDequePar[V]{
|
||||
seq: p.seq,
|
||||
workers: p.workers,
|
||||
process: func(v V) (V, bool) {
|
||||
if mid, ok := prev(v); ok {
|
||||
count := counter.Add(1)
|
||||
if (count-1)%uint64(n) == 0 {
|
||||
return mid, true
|
||||
}
|
||||
}
|
||||
var zero V
|
||||
return zero, false
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// MaxBy returns the maximum element according to the comparison function.
|
||||
func (p SeqDequePar[V]) MaxBy(fn func(V, V) cmp.Ordering) Option[V] {
|
||||
ch := make(chan V)
|
||||
|
||||
go func() {
|
||||
defer close(ch)
|
||||
p.Range(func(v V) bool {
|
||||
ch <- v
|
||||
return true
|
||||
})
|
||||
}()
|
||||
|
||||
var max V
|
||||
hasMax := false
|
||||
|
||||
for v := range ch {
|
||||
if !hasMax {
|
||||
max = v
|
||||
hasMax = true
|
||||
} else if fn(v, max).IsGt() {
|
||||
max = v
|
||||
}
|
||||
}
|
||||
|
||||
if hasMax {
|
||||
return Some(max)
|
||||
}
|
||||
return None[V]()
|
||||
}
|
||||
|
||||
// MinBy returns the minimum element according to the comparison function.
|
||||
func (p SeqDequePar[V]) MinBy(fn func(V, V) cmp.Ordering) Option[V] {
|
||||
ch := make(chan V)
|
||||
|
||||
go func() {
|
||||
defer close(ch)
|
||||
p.Range(func(v V) bool {
|
||||
ch <- v
|
||||
return true
|
||||
})
|
||||
}()
|
||||
|
||||
var min V
|
||||
hasMin := false
|
||||
|
||||
for v := range ch {
|
||||
if !hasMin {
|
||||
min = v
|
||||
hasMin = true
|
||||
} else if fn(v, min).IsLt() {
|
||||
min = v
|
||||
}
|
||||
}
|
||||
|
||||
if hasMin {
|
||||
return Some(min)
|
||||
}
|
||||
return None[V]()
|
||||
}
|
||||
|
||||
// Reduce aggregates elements of the parallel sequence using the provided function.
|
||||
// The first received element is used as the initial accumulator.
|
||||
// If the sequence is empty, returns None[V].
|
||||
func (p SeqDequePar[V]) Reduce(fn func(a, b V) V) Option[V] {
|
||||
ch := make(chan V)
|
||||
|
||||
go func() {
|
||||
defer close(ch)
|
||||
p.Range(func(v V) bool {
|
||||
ch <- v
|
||||
return true
|
||||
})
|
||||
}()
|
||||
|
||||
var (
|
||||
acc V
|
||||
first = true
|
||||
)
|
||||
|
||||
for v := range ch {
|
||||
if first {
|
||||
acc = v
|
||||
first = false
|
||||
continue
|
||||
}
|
||||
|
||||
acc = fn(acc, v)
|
||||
}
|
||||
|
||||
if first {
|
||||
return None[V]()
|
||||
}
|
||||
|
||||
return Some(acc)
|
||||
}
|
||||
|
||||
// ForEach applies fn to each element without early exit.
|
||||
func (p SeqDequePar[V]) ForEach(fn func(V)) {
|
||||
p.Range(func(v V) bool {
|
||||
fn(v)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// Inspect invokes fn on each element without altering the resulting sequence.
|
||||
func (p SeqDequePar[V]) Inspect(fn func(V)) SeqDequePar[V] {
|
||||
prev := p.process
|
||||
|
||||
return SeqDequePar[V]{
|
||||
seq: p.seq,
|
||||
workers: p.workers,
|
||||
process: func(x V) (V, bool) {
|
||||
if mid, ok := prev(x); ok {
|
||||
fn(mid)
|
||||
return mid, true
|
||||
}
|
||||
var zero V
|
||||
return zero, false
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Map applies fn to each element.
|
||||
func (p SeqDequePar[V]) Map(fn func(V) V) SeqDequePar[V] {
|
||||
prev := p.process
|
||||
|
||||
return SeqDequePar[V]{
|
||||
seq: p.seq,
|
||||
workers: p.workers,
|
||||
process: func(v V) (V, bool) {
|
||||
if mid, ok := prev(v); ok {
|
||||
return fn(mid), true
|
||||
}
|
||||
var zero V
|
||||
return zero, false
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (p SeqDequePar[V]) Partition(fn func(V) bool) (*Deque[V], *Deque[V]) {
|
||||
type item struct {
|
||||
value V
|
||||
isLeft bool
|
||||
}
|
||||
|
||||
ch := make(chan item)
|
||||
|
||||
go func() {
|
||||
defer close(ch)
|
||||
p.Range(func(v V) bool {
|
||||
ch <- item{
|
||||
value: v,
|
||||
isLeft: fn(v),
|
||||
}
|
||||
return true
|
||||
})
|
||||
}()
|
||||
|
||||
left, right := NewDeque[V](0), NewDeque[V](0)
|
||||
for it := range ch {
|
||||
if it.isLeft {
|
||||
left.PushBack(it.value)
|
||||
} else {
|
||||
right.PushBack(it.value)
|
||||
}
|
||||
}
|
||||
|
||||
return left, right
|
||||
}
|
||||
|
||||
// Range applies fn to each processed element in parallel, stopping on false.
|
||||
func (p SeqDequePar[V]) Range(fn func(V) bool) {
|
||||
in := make(chan V)
|
||||
done := make(chan struct{})
|
||||
|
||||
var (
|
||||
wg sync.WaitGroup
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
go func() {
|
||||
defer close(in)
|
||||
p.seq(func(v V) bool {
|
||||
select {
|
||||
case <-done:
|
||||
return false
|
||||
case in <- v:
|
||||
return true
|
||||
}
|
||||
})
|
||||
}()
|
||||
|
||||
wg.Add(int(p.workers))
|
||||
for range p.workers {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for v := range in {
|
||||
if mid, ok := p.process(v); ok {
|
||||
if !fn(mid) {
|
||||
once.Do(func() { close(done) })
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func (p SeqDequePar[V]) Skip(n uint) SeqDequePar[V] {
|
||||
prev := p.process
|
||||
|
||||
return SeqDequePar[V]{
|
||||
seq: func(yield func(V) bool) {
|
||||
var cnt uint64
|
||||
p.seq(func(v V) bool {
|
||||
if atomic.AddUint64(&cnt, 1) > uint64(n) {
|
||||
return yield(v)
|
||||
}
|
||||
return true
|
||||
})
|
||||
},
|
||||
workers: p.workers,
|
||||
process: prev,
|
||||
}
|
||||
}
|
||||
|
||||
func (p SeqDequePar[V]) Take(n uint) SeqDequePar[V] {
|
||||
prev := p.process
|
||||
|
||||
return SeqDequePar[V]{
|
||||
seq: func(yield func(V) bool) {
|
||||
var cnt uint64
|
||||
p.seq(func(v V) bool {
|
||||
if atomic.AddUint64(&cnt, 1) <= uint64(n) {
|
||||
return yield(v)
|
||||
}
|
||||
return false
|
||||
})
|
||||
},
|
||||
workers: p.workers,
|
||||
process: prev,
|
||||
}
|
||||
}
|
||||
|
||||
// Unique removes duplicate elements, preserving the first occurrence.
|
||||
func (p SeqDequePar[V]) Unique() SeqDequePar[V] {
|
||||
prev := p.process
|
||||
seen := NewMapSafe[any, struct{}]()
|
||||
|
||||
return SeqDequePar[V]{
|
||||
seq: p.seq,
|
||||
workers: p.workers,
|
||||
process: func(v V) (V, bool) {
|
||||
if mid, ok := prev(v); ok {
|
||||
if loaded := seen.Entry(mid).OrSet(struct{}{}); loaded.IsSome() {
|
||||
var zero V
|
||||
return zero, false
|
||||
}
|
||||
|
||||
return mid, true
|
||||
}
|
||||
|
||||
var zero V
|
||||
return zero, false
|
||||
},
|
||||
}
|
||||
}
|
||||
-514
@@ -1,514 +0,0 @@
|
||||
package g
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// NewDir returns a new Dir instance with the given path.
|
||||
func NewDir(path String) *Dir { return &Dir{path: path} }
|
||||
|
||||
// Chown changes the ownership of the directory to the specified UID and GID.
|
||||
// It uses os.Chown to modify ownership and returns a Result[*Dir] indicating success or failure.
|
||||
func (d *Dir) Chown(uid, gid int) Result[*Dir] {
|
||||
err := os.Chown(d.path.Std(), uid, gid)
|
||||
if err != nil {
|
||||
return Err[*Dir](err)
|
||||
}
|
||||
|
||||
return Ok(d)
|
||||
}
|
||||
|
||||
// Stat retrieves information about the directory represented by the Dir instance.
|
||||
// It returns a Result[fs.FileInfo] containing details about the directory's metadata.
|
||||
func (d *Dir) Stat() Result[fs.FileInfo] {
|
||||
if d.Path().IsErr() {
|
||||
return Err[fs.FileInfo](d.Path().err)
|
||||
}
|
||||
|
||||
return ResultOf(os.Stat(d.Path().v.Std()))
|
||||
}
|
||||
|
||||
// Lstat retrieves information about the symbolic link represented by the Dir instance.
|
||||
// It returns a Result[fs.FileInfo] containing details about the symbolic link's metadata.
|
||||
// Unlike Stat, Lstat does not follow the link and provides information about the link itself.
|
||||
func (d *Dir) Lstat() Result[fs.FileInfo] { return ResultOf(os.Lstat(d.Path().v.Std())) }
|
||||
|
||||
// IsLink checks if the directory is a symbolic link.
|
||||
func (d *Dir) IsLink() bool {
|
||||
stat := d.Lstat()
|
||||
return stat.IsOk() && stat.v.Mode()&os.ModeSymlink != 0
|
||||
}
|
||||
|
||||
// CreateTemp creates a new temporary directory in the specified directory with the
|
||||
// specified name pattern and returns a Result, which contains a pointer to the Dir
|
||||
// or an error if the operation fails.
|
||||
// If no directory is specified, the default directory for temporary directories is used.
|
||||
// If no name pattern is specified, the default pattern "*" is used.
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// - args ...String: A variadic parameter specifying the directory and/or name
|
||||
// pattern for the temporary directory.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - *Dir: A pointer to the Dir representing the temporary directory.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// d := g.NewDir("")
|
||||
// tmpdir := d.CreateTemp() // Creates a temporary directory with default settings
|
||||
// tmpdirWithDir := d.CreateTemp("mydir") // Creates a temporary directory in "mydir" directory
|
||||
// tmpdirWithPattern := d.CreateTemp("", "tmp") // Creates a temporary directory with "tmp" pattern
|
||||
func (*Dir) CreateTemp(args ...String) Result[*Dir] {
|
||||
dir := ""
|
||||
pattern := "*"
|
||||
|
||||
if len(args) != 0 {
|
||||
if len(args) > 1 {
|
||||
pattern = args[1].Std()
|
||||
}
|
||||
|
||||
dir = args[0].Std()
|
||||
}
|
||||
|
||||
tmpDir, err := os.MkdirTemp(dir, pattern)
|
||||
if err != nil {
|
||||
return Err[*Dir](err)
|
||||
}
|
||||
|
||||
return Ok(NewDir(String(tmpDir)))
|
||||
}
|
||||
|
||||
// Temp returns the default directory to use for temporary files.
|
||||
//
|
||||
// On Unix systems, it returns $TMPDIR if non-empty, else /tmp.
|
||||
// On Windows, it uses GetTempPath, returning the first non-empty
|
||||
// value from %TMP%, %TEMP%, %USERPROFILE%, or the Windows directory.
|
||||
// On Plan 9, it returns /tmp.
|
||||
//
|
||||
// The directory is neither guaranteed to exist nor have accessible
|
||||
// permissions.
|
||||
func (*Dir) Temp() *Dir { return NewDir(String(os.TempDir())) }
|
||||
|
||||
// Remove attempts to delete the directory and its contents.
|
||||
// It returns a Result, which contains either the *Dir or an error.
|
||||
// If the directory does not exist, Remove returns a successful Result with *Dir set.
|
||||
// Any error that occurs during removal will be of type *PathError.
|
||||
func (d *Dir) Remove() Result[*Dir] {
|
||||
if err := os.RemoveAll(d.String().Std()); err != nil {
|
||||
return Err[*Dir](err)
|
||||
}
|
||||
|
||||
return Ok(d)
|
||||
}
|
||||
|
||||
// Copy copies the contents of the current directory to the destination directory.
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// - dest (String): The destination directory where the contents of the current directory should be copied.
|
||||
//
|
||||
// - followLinks (optional): A boolean indicating whether to follow symbolic links during the walk.
|
||||
// If true, symbolic links are followed; otherwise, they are skipped.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - Result[*Dir]: A Result type containing either a pointer to a new Dir instance representing the destination directory or an error.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// sourceDir := g.NewDir("path/to/source")
|
||||
// destinationDirResult := sourceDir.Copy("path/to/destination")
|
||||
// if destinationDirResult.IsErr() {
|
||||
// // Handle error
|
||||
// }
|
||||
// destinationDir := destinationDirResult.Ok()
|
||||
func (d *Dir) Copy(dest String, followLinks ...bool) Result[*Dir] {
|
||||
files := NewSlice[*File]()
|
||||
|
||||
for r := range d.Walk() {
|
||||
if r.IsErr() {
|
||||
return Err[*Dir](r.err)
|
||||
}
|
||||
files.Push(r.v)
|
||||
}
|
||||
|
||||
root := d.Path()
|
||||
if root.IsErr() {
|
||||
return Err[*Dir](root.err)
|
||||
}
|
||||
|
||||
follow := Slice[bool](followLinks).Get(0).UnwrapOr(true)
|
||||
|
||||
for f := range files.Iter() {
|
||||
path := f.Path()
|
||||
if path.IsErr() {
|
||||
return Err[*Dir](path.err)
|
||||
}
|
||||
|
||||
relpath, err := filepath.Rel(root.v.Std(), path.v.Std())
|
||||
if err != nil {
|
||||
return Err[*Dir](err)
|
||||
}
|
||||
|
||||
destpath := NewDir(dest).Join(String(relpath))
|
||||
if destpath.IsErr() {
|
||||
return Err[*Dir](destpath.err)
|
||||
}
|
||||
|
||||
stat := f.Stat()
|
||||
if stat.IsErr() {
|
||||
return Err[*Dir](stat.err)
|
||||
}
|
||||
|
||||
if stat.v.IsDir() {
|
||||
if !follow && f.IsLink() {
|
||||
continue
|
||||
}
|
||||
|
||||
if r := NewDir(destpath.v).CreateAll(stat.v.Mode()); r.IsErr() {
|
||||
return r
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if r := f.Copy(destpath.v, stat.v.Mode()); r.IsErr() {
|
||||
return Err[*Dir](r.err)
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(NewDir(dest))
|
||||
}
|
||||
|
||||
// Create creates a new directory with the specified mode (optional).
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// - mode (os.FileMode, optional): The file mode for the new directory.
|
||||
// If not provided, it defaults to DirDefault (0755).
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - *Dir: A pointer to the Dir instance on which the method was called.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// dir := g.NewDir("path/to/directory")
|
||||
// createdDir := dir.Create(0755) // Optional mode argument
|
||||
func (d *Dir) Create(mode ...os.FileMode) Result[*Dir] {
|
||||
dmode := Slice[os.FileMode](mode).Get(0).UnwrapOr(DirDefault)
|
||||
if err := os.Mkdir(d.path.Std(), dmode); err != nil {
|
||||
return Err[*Dir](err)
|
||||
}
|
||||
|
||||
return Ok(d)
|
||||
}
|
||||
|
||||
// Join joins the current directory path with the given path elements, returning the joined path.
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// - elem (...String): One or more String values representing path elements to
|
||||
// be joined with the current directory path.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - String: The resulting joined path as an String.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// dir := g.NewDir("path/to/directory")
|
||||
// joinedPath := dir.Join("subdir", "file.txt")
|
||||
func (d *Dir) Join(elem ...String) Result[String] {
|
||||
path := d.Path()
|
||||
if path.IsErr() {
|
||||
return Err[String](path.err)
|
||||
}
|
||||
|
||||
se := SliceOf(elem...)
|
||||
se.Insert(0, path.v)
|
||||
|
||||
return Ok(String(filepath.Join(se.ToStringSlice()...)))
|
||||
}
|
||||
|
||||
// SetPath sets the path of the current directory.
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// - path (String): The new path to be set for the current directory.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - *Dir: A pointer to the updated Dir instance with the new path.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// dir := g.NewDir("path/to/directory")
|
||||
// dir.SetPath("new/path/to/directory")
|
||||
func (d *Dir) SetPath(path String) *Dir {
|
||||
d.path = path
|
||||
return d
|
||||
}
|
||||
|
||||
// CreateAll creates all directories along the given path, with the specified mode (optional).
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// - mode ...os.FileMode (optional): The file mode to be used when creating the directories.
|
||||
// If not provided, it defaults to the value of DirDefault constant (0755).
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - *Dir: A pointer to the Dir instance representing the created directories.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// dir := g.NewDir("path/to/directory")
|
||||
// dir.CreateAll()
|
||||
// dir.CreateAll(0755)
|
||||
func (d *Dir) CreateAll(mode ...os.FileMode) Result[*Dir] {
|
||||
if d.Exist() {
|
||||
return Ok(d)
|
||||
}
|
||||
|
||||
path := d.Path()
|
||||
if path.IsErr() {
|
||||
return Err[*Dir](path.err)
|
||||
}
|
||||
|
||||
dmode := Slice[os.FileMode](mode).Get(0).UnwrapOr(DirDefault)
|
||||
|
||||
err := os.MkdirAll(path.v.Std(), dmode)
|
||||
if err != nil {
|
||||
return Err[*Dir](err)
|
||||
}
|
||||
|
||||
return Ok(d)
|
||||
}
|
||||
|
||||
// Rename renames the current directory to the new path.
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// - newpath String: The new path for the directory.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - *Dir: A pointer to the Dir instance representing the renamed directory.
|
||||
// If an error occurs, the original Dir instance is returned with the error stored in d.err,
|
||||
// which can be checked using the Error() method.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// dir := g.NewDir("path/to/directory")
|
||||
// dir.Rename("path/to/new_directory")
|
||||
func (d *Dir) Rename(newpath String) Result[*Dir] {
|
||||
ps := String(os.PathSeparator)
|
||||
|
||||
np := newpath.StripSuffix(ps).Split(ps).Collect()
|
||||
_ = np.Pop()
|
||||
|
||||
if rd := NewDir(np.Join(ps)).CreateAll(); rd.IsErr() {
|
||||
return rd
|
||||
}
|
||||
|
||||
if err := os.Rename(d.path.Std(), newpath.Std()); err != nil {
|
||||
return Err[*Dir](err)
|
||||
}
|
||||
|
||||
return Ok(NewDir(newpath))
|
||||
}
|
||||
|
||||
// Move function simply calls [Dir.Rename]
|
||||
func (d *Dir) Move(newpath String) Result[*Dir] { return d.Rename(newpath) }
|
||||
|
||||
// Path returns the absolute path of the current directory.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - String: The absolute path of the current directory as an String.
|
||||
// If an error occurs while converting the path to an absolute path,
|
||||
// the error is stored in d.err, which can be checked using the Error() method.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// dir := g.NewDir("path/to/directory")
|
||||
// absPath := dir.Path()
|
||||
func (d *Dir) Path() Result[String] {
|
||||
path, err := filepath.Abs(d.path.Std())
|
||||
if err != nil {
|
||||
return Err[String](err)
|
||||
}
|
||||
|
||||
return Ok(String(path))
|
||||
}
|
||||
|
||||
// Exist checks if the current directory exists.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - bool: true if the current directory exists, false otherwise.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// dir := g.NewDir("path/to/directory")
|
||||
// exists := dir.Exist()
|
||||
func (d *Dir) Exist() bool {
|
||||
path := d.Path()
|
||||
if path.IsErr() {
|
||||
return false
|
||||
}
|
||||
|
||||
_, err := os.Stat(path.v.Std())
|
||||
|
||||
return !os.IsNotExist(err)
|
||||
}
|
||||
|
||||
// Read iterates over the content of the current directory and yields File instances for each entry.
|
||||
// This method uses a lazy evaluation strategy where each file is processed one at a time as it is needed.
|
||||
//
|
||||
// Returns:
|
||||
// - SeqResult[*File]: A sequence of Result[*File] instances representing each file and directory
|
||||
// in the current directory. It returns an error if reading the directory fails.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// dir := g.NewDir("path/to/directory")
|
||||
// files := dir.Read()
|
||||
// for file := range files {
|
||||
// fmt.Println(file.Ok().Name())
|
||||
// }
|
||||
func (d *Dir) Read() SeqResult[*File] {
|
||||
return func(yield func(Result[*File]) bool) {
|
||||
entries, err := os.ReadDir(d.path.Std())
|
||||
if err != nil {
|
||||
yield(Err[*File](err))
|
||||
return
|
||||
}
|
||||
|
||||
dpath := d.Path()
|
||||
if dpath.IsErr() {
|
||||
yield(Err[*File](dpath.err))
|
||||
return
|
||||
}
|
||||
|
||||
base := dpath.v
|
||||
|
||||
for _, entry := range entries {
|
||||
full := NewDir(base).Join(String(entry.Name()))
|
||||
if full.IsErr() {
|
||||
yield(Err[*File](full.err))
|
||||
return
|
||||
}
|
||||
|
||||
if !yield(Ok(NewFile(full.v))) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Glob iterates over files in the current directory matching a specified pattern and yields File instances for each match.
|
||||
// This method utilizes a lazy evaluation strategy, processing files as they are needed.
|
||||
//
|
||||
// Returns:
|
||||
// - SeqResult[*File]: A sequence of Result[*File] instances representing the files that match the
|
||||
// provided pattern in the current directory. It returns an error if the glob operation fails.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// dir := g.NewDir("path/to/directory/*.txt")
|
||||
// files := dir.Glob()
|
||||
// for file := range files {
|
||||
// fmt.Println(file.Ok().Name())
|
||||
// }
|
||||
func (d *Dir) Glob() SeqResult[*File] {
|
||||
return (func(yield func(Result[*File]) bool) {
|
||||
matches, err := filepath.Glob(d.path.Std())
|
||||
if err != nil {
|
||||
yield(Err[*File](err))
|
||||
return
|
||||
}
|
||||
|
||||
for _, match := range matches {
|
||||
file := NewFile(String(match)).Path()
|
||||
if file.IsErr() {
|
||||
yield(Err[*File](file.err))
|
||||
return
|
||||
}
|
||||
|
||||
if !yield(Ok(NewFile(file.v))) {
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Walk returns a lazy sequence of all files and directories under the current Dir.
|
||||
// You can customize inclusion/exclusion using SeqResult methods (Exclude, Filter, etc.).
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// NewDir("path/to/dir").
|
||||
// Walk().
|
||||
// Exclude((*File).IsLink).
|
||||
// ForEach(func(r Result[*File]) {
|
||||
// if r.IsOk() {
|
||||
// fmt.Println(r.Ok().Path().Ok().Std())
|
||||
// }
|
||||
// })
|
||||
func (d *Dir) Walk() SeqResult[*File] {
|
||||
return func(yield func(Result[*File]) bool) {
|
||||
stack := SliceOf(d)
|
||||
|
||||
for stack.NotEmpty() {
|
||||
current := stack.Pop()
|
||||
if current.IsNone() {
|
||||
break
|
||||
}
|
||||
|
||||
current.v.Read().Range(func(r Result[*File]) bool {
|
||||
if r.IsErr() {
|
||||
return yield(r)
|
||||
}
|
||||
|
||||
file := r.v
|
||||
if !yield(Ok(file)) {
|
||||
return false
|
||||
}
|
||||
|
||||
stat := file.Stat()
|
||||
if stat.IsErr() {
|
||||
return yield(Err[*File](stat.err))
|
||||
}
|
||||
|
||||
if stat.v.IsDir() {
|
||||
path := file.Path()
|
||||
if path.IsErr() {
|
||||
return yield(Err[*File](path.err))
|
||||
}
|
||||
|
||||
stack.Push(NewDir(path.v))
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// String returns the String representation of the current directory's path.
|
||||
func (d *Dir) String() String { return d.path }
|
||||
|
||||
// Print writes the content of the Dir to the standard output (console)
|
||||
// and returns the Dir unchanged.
|
||||
func (d *Dir) Print() *Dir { fmt.Print(d); return d }
|
||||
|
||||
// Println writes the content of the Dir to the standard output (console) with a newline
|
||||
// and returns the Dir unchanged.
|
||||
func (d *Dir) Println() *Dir { fmt.Println(d); return d }
|
||||
+25
-11
@@ -1,17 +1,31 @@
|
||||
package g
|
||||
|
||||
import "fmt"
|
||||
import "errors"
|
||||
|
||||
// ErrFileNotExist represents an error for when a file does not exist.
|
||||
type ErrFileNotExist struct{ Msg string }
|
||||
var (
|
||||
// ErrInvalidBinaryLength is returned by binary decoding when the input length is not a multiple of 8.
|
||||
ErrInvalidBinaryLength = errors.New("binary string length must be multiple of 8")
|
||||
// ErrInvalidBinaryDigit is returned by binary decoding when the input contains characters other than '0' and '1'.
|
||||
ErrInvalidBinaryDigit = errors.New("binary string must contain only '0' and '1'")
|
||||
|
||||
// Error returns the error message for ErrFileNotExist.
|
||||
func (e *ErrFileNotExist) Error() string { return fmt.Sprintf("no such file: %s", e.Msg) }
|
||||
// ErrParseInt is returned when a String cannot be parsed as an integer.
|
||||
ErrParseInt = errors.New("invalid integer")
|
||||
// ErrParseBigInt is returned when a String cannot be parsed as a big integer.
|
||||
ErrParseBigInt = errors.New("invalid big integer")
|
||||
// ErrParseFloat is returned when a String cannot be parsed as a float.
|
||||
ErrParseFloat = errors.New("invalid float")
|
||||
// ErrParseBool is returned when a String cannot be parsed as a bool.
|
||||
ErrParseBool = errors.New("invalid bool")
|
||||
// ErrParseUint is returned when a String cannot be parsed as an unsigned integer.
|
||||
ErrParseUint = errors.New("invalid unsigned integer")
|
||||
// ErrParseComplex is returned when a String cannot be parsed as a complex number.
|
||||
ErrParseComplex = errors.New("invalid complex number")
|
||||
)
|
||||
|
||||
// ErrFileClosed represents an error for when a file is already closed.
|
||||
type ErrFileClosed struct{ Msg string }
|
||||
|
||||
// Error returns the error message for ErrFileClosed.
|
||||
func (e *ErrFileClosed) Error() string {
|
||||
return fmt.Sprintf("%s: file is already closed and unlocked", e.Msg)
|
||||
type wrappedError struct {
|
||||
msg string
|
||||
errs []error
|
||||
}
|
||||
|
||||
func (e *wrappedError) Error() string { return e.msg }
|
||||
func (e *wrappedError) Unwrap() []error { return e.errs }
|
||||
+68
-7
@@ -1,3 +1,5 @@
|
||||
// Package f provides predicate helpers and combinators (f.Eq, f.Gt, f.Contains, ...)
|
||||
// for use with iterator methods such as Filter and Exclude.
|
||||
package f
|
||||
|
||||
import (
|
||||
@@ -9,8 +11,24 @@ import (
|
||||
"github.com/enetx/g/constraints"
|
||||
)
|
||||
|
||||
// IsComparable reports whether the value v is comparable.
|
||||
func IsComparable[T any](t T) bool { return reflect.ValueOf(t).Comparable() }
|
||||
// Id returns its argument unchanged. It is the identity function, useful
|
||||
// wherever a transform is required but the value should pass through as-is;
|
||||
// it pairs with CounterBy for occurrence counting:
|
||||
//
|
||||
// words.Iter().CounterBy(f.Id)
|
||||
func Id[T any](t T) T { return t }
|
||||
|
||||
// IsComparable reports whether the type T is comparable at the type level.
|
||||
// It uses reflect.TypeFor[T]() without requiring a value, making it suitable
|
||||
// for use in generic code where the type is known at compile time.
|
||||
// The result is determined solely by the type and does not depend on any runtime value.
|
||||
func IsComparable[T any]() bool { return reflect.TypeFor[T]().Comparable() }
|
||||
|
||||
// IsComparableValue reports whether the concrete value v is comparable.
|
||||
// Unlike IsComparable, which checks at the type level, IsComparableValue
|
||||
// inspects the actual runtime value, making it suitable for filtering
|
||||
// or checking dynamic values of type 'any'.
|
||||
func IsComparableValue(v any) bool { return reflect.ValueOf(v).Comparable() }
|
||||
|
||||
// IsZero is a generic function designed to check if a value is considered zero.
|
||||
func IsZero[T cmp.Ordered](v T) bool { return v == *new(T) }
|
||||
@@ -24,35 +42,43 @@ func IsOdd[T constraints.Integer](i T) bool { return i%2 != 0 }
|
||||
// Match returns a function that checks whether a string or []byte matches a given regular expression.
|
||||
func Match[T ~string | ~[]byte](t *regexp.Regexp) func(T) bool {
|
||||
return func(s T) bool {
|
||||
return t.MatchString(string(s))
|
||||
if reflect.TypeFor[T]().Kind() == reflect.String {
|
||||
return t.MatchString(reflect.ValueOf(s).String())
|
||||
}
|
||||
|
||||
return t.Match(reflect.ValueOf(s).Bytes())
|
||||
}
|
||||
}
|
||||
|
||||
// Contains returns a function that checks whether a string or []byte contains a given substring.
|
||||
func Contains[T ~string | ~[]byte](t T) func(T) bool {
|
||||
target := string(t)
|
||||
return func(s T) bool {
|
||||
return strings.Contains(string(s), string(t))
|
||||
return strings.Contains(string(s), target)
|
||||
}
|
||||
}
|
||||
|
||||
// ContainsAnyChars returns a function that checks whether a string contains any of the characters from a given set.
|
||||
func ContainsAnyChars[T ~string | ~[]byte](t T) func(T) bool {
|
||||
chars := string(t)
|
||||
return func(s T) bool {
|
||||
return strings.ContainsAny(string(s), string(t))
|
||||
return strings.ContainsAny(string(s), chars)
|
||||
}
|
||||
}
|
||||
|
||||
// StartsWith returns a function that checks whether a string starts with a given prefix.
|
||||
func StartsWith[T ~string | ~[]byte](t T) func(T) bool {
|
||||
prefix := string(t)
|
||||
return func(s T) bool {
|
||||
return strings.HasPrefix(string(s), string(t))
|
||||
return strings.HasPrefix(string(s), prefix)
|
||||
}
|
||||
}
|
||||
|
||||
// EndsWith returns a function that checks whether a string ends with a given suffix.
|
||||
func EndsWith[T ~string | ~[]byte](t T) func(T) bool {
|
||||
suffix := string(t)
|
||||
return func(s T) bool {
|
||||
return strings.HasSuffix(string(s), string(t))
|
||||
return strings.HasSuffix(string(s), suffix)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,3 +137,38 @@ func Lte[T cmp.Ordered](t T) func(T) bool {
|
||||
return s <= t
|
||||
}
|
||||
}
|
||||
|
||||
// Not returns a predicate that negates the result of the provided predicate.
|
||||
func Not[T any](fn func(T) bool) func(T) bool {
|
||||
return func(s T) bool {
|
||||
return !fn(s)
|
||||
}
|
||||
}
|
||||
|
||||
// And returns a predicate that evaluates to true only when all of the provided predicates do.
|
||||
// It short-circuits on the first predicate that returns false. With no predicates it returns true.
|
||||
func And[T any](fns ...func(T) bool) func(T) bool {
|
||||
return func(s T) bool {
|
||||
for _, fn := range fns {
|
||||
if !fn(s) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Or returns a predicate that evaluates to true when any of the provided predicates does.
|
||||
// It short-circuits on the first predicate that returns true. With no predicates it returns false.
|
||||
func Or[T any](fns ...func(T) bool) func(T) bool {
|
||||
return func(s T) bool {
|
||||
for _, fn := range fns {
|
||||
if fn(s) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
-743
@@ -1,743 +0,0 @@
|
||||
package g
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/enetx/g/internal/filelock"
|
||||
)
|
||||
|
||||
// NewFile returns a new File instance with the given name.
|
||||
func NewFile[T ~string](name T) *File { return &File{name: String(name)} }
|
||||
|
||||
// Lines returns a new iterator instance that can be used to read the file
|
||||
// line by line.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// // Open a new file with the specified name "text.txt"
|
||||
// g.NewFile("text.txt").
|
||||
// Lines(). // Read the file line by line
|
||||
// Skip(3). // Skip the first 3 lines
|
||||
// Exclude(f.Zero). // Exclude lines that are empty or contain only whitespaces
|
||||
// Dedup(). // Remove consecutive duplicate lines
|
||||
// Map(g.String.Upper). // Convert each line to uppercase
|
||||
// ForEach( // For each line, print it
|
||||
// func(func(s Result[String]) {
|
||||
// s.Ok().Print()
|
||||
// })
|
||||
//
|
||||
// // Output:
|
||||
// // UPPERCASED_LINE4
|
||||
// // UPPERCASED_LINE5
|
||||
// // UPPERCASED_LINE6
|
||||
func (f *File) Lines() SeqResult[String] {
|
||||
return func(yield func(Result[String]) bool) {
|
||||
if f.file == nil {
|
||||
if r := f.Open(); r.IsErr() {
|
||||
yield(Err[String](r.err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
defer f.Close()
|
||||
|
||||
scanner := bufio.NewScanner(f.file)
|
||||
scanner.Split(bufio.ScanLines)
|
||||
|
||||
for scanner.Scan() {
|
||||
if !yield(Ok(String(scanner.Text()))) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
yield(Err[String](err))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// LinesRaw returns a new iterator instance that reads the file line by line,
|
||||
// yielding each line as a Bytes slice (raw []byte).
|
||||
//
|
||||
// This version avoids intermediate string allocations by working directly with byte slices.
|
||||
// The returned Bytes are copies of the scanner buffer and are safe to retain.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - SeqResult[Bytes]: An iterator over raw byte lines from the file.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// g.NewFile("text.txt").
|
||||
// LinesRaw(). // Read raw byte lines
|
||||
// Filter(func(b g.Bytes) bool {
|
||||
// return len(b) > 0
|
||||
// }).
|
||||
// ForEach(func(line g.Result[g.Bytes]) {
|
||||
// line.Ok().Print()
|
||||
// })
|
||||
//
|
||||
// Output:
|
||||
// LINE_1
|
||||
// LINE_2
|
||||
// ...
|
||||
//
|
||||
// Note: Each line is copied before yielding to avoid scanner buffer reuse issues.
|
||||
func (f *File) LinesRaw() SeqResult[Bytes] {
|
||||
return func(yield func(Result[Bytes]) bool) {
|
||||
if f.file == nil {
|
||||
if r := f.Open(); r.IsErr() {
|
||||
yield(Err[Bytes](r.err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
defer f.Close()
|
||||
|
||||
scanner := bufio.NewScanner(f.file)
|
||||
scanner.Split(bufio.ScanLines)
|
||||
|
||||
for scanner.Scan() {
|
||||
line := make(Bytes, len(scanner.Bytes()))
|
||||
copy(line, scanner.Bytes())
|
||||
if !yield(Ok(line)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
yield(Err[Bytes](err))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Chunks returns a new iterator instance that can be used to read the file
|
||||
// in fixed-size chunks of the specified size in bytes.
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// - size (int): The size of each chunk in bytes.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// // Open a new file with the specified name "text.txt"
|
||||
// g.NewFile("text.txt").
|
||||
// Chunks(100). // Read the file in chunks of 100 bytes
|
||||
// Map(g.String.Upper). // Convert each chunk to uppercase
|
||||
// ForEach( // For each line, print it
|
||||
// func(func(s Result[String]) {
|
||||
// s.Ok().Print()
|
||||
// })
|
||||
//
|
||||
// // Output:
|
||||
// // UPPERCASED_CHUNK1
|
||||
// // UPPERCASED_CHUNK2
|
||||
// // UPPERCASED_CHUNK3
|
||||
func (f *File) Chunks(size Int) SeqResult[String] {
|
||||
return func(yield func(Result[String]) bool) {
|
||||
if size.Lte(0) {
|
||||
yield(Err[String](errors.New("chunk size must be > 0")))
|
||||
return
|
||||
}
|
||||
|
||||
if f.file == nil {
|
||||
if r := f.Open(); r.IsErr() {
|
||||
yield(Err[String](r.err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
defer f.Close()
|
||||
|
||||
buffer := make([]byte, size)
|
||||
|
||||
for {
|
||||
n, err := f.file.Read(buffer)
|
||||
if err != nil && err != io.EOF {
|
||||
yield(Err[String](err))
|
||||
return
|
||||
}
|
||||
|
||||
if n == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
if !yield(Ok(String(buffer[:n]))) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ChunksRaw returns a new iterator instance that reads the file in fixed-size
|
||||
// chunks of bytes, yielding each chunk as a Bytes slice.
|
||||
//
|
||||
// This method avoids intermediate string allocations and operates directly on byte slices.
|
||||
// Each chunk is copied from the underlying buffer to make it safe for downstream use.
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// - size (Int): The size of each chunk in bytes. Must be > 0.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - SeqResult[Bytes]: An iterator over raw byte chunks from the file.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// g.NewFile("text.txt").
|
||||
// ChunksRaw(128). // Read raw 128-byte chunks
|
||||
// ForEach(func(chunk g.Result[g.Bytes]) {
|
||||
// chunk.Ok().Print()
|
||||
// })
|
||||
//
|
||||
// Output:
|
||||
// RAW_CHUNK_1
|
||||
// RAW_CHUNK_2
|
||||
// ...
|
||||
//
|
||||
// Note: Each chunk is copied from the buffer to ensure memory safety.
|
||||
func (f *File) ChunksRaw(size Int) SeqResult[Bytes] {
|
||||
return func(yield func(Result[Bytes]) bool) {
|
||||
if size.Lte(0) {
|
||||
yield(Err[Bytes](errors.New("chunk size must be > 0")))
|
||||
return
|
||||
}
|
||||
|
||||
if f.file == nil {
|
||||
if r := f.Open(); r.IsErr() {
|
||||
yield(Err[Bytes](r.err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
defer f.Close()
|
||||
|
||||
buf := make([]byte, size)
|
||||
|
||||
for {
|
||||
n, err := f.file.Read(buf)
|
||||
if err != nil && err != io.EOF {
|
||||
yield(Err[Bytes](err))
|
||||
return
|
||||
}
|
||||
|
||||
if n == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
chunk := make(Bytes, n)
|
||||
copy(chunk, buf[:n])
|
||||
|
||||
if !yield(Ok(chunk)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Append appends the given content to the file, with the specified mode (optional).
|
||||
// If no FileMode is provided, the default FileMode (0644) is used.
|
||||
// Don't forget to close the file!
|
||||
func (f *File) Append(content String, mode ...os.FileMode) Result[*File] {
|
||||
if f.file == nil {
|
||||
if r := f.createAll(); r.IsErr() {
|
||||
return r
|
||||
}
|
||||
|
||||
fmode := Slice[os.FileMode](mode).Get(0).UnwrapOr(FileDefault)
|
||||
|
||||
if r := f.OpenFile(os.O_APPEND|os.O_CREATE|os.O_WRONLY, fmode); r.IsErr() {
|
||||
return r
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := f.file.WriteString(content.Std()); err != nil {
|
||||
return Err[*File](err)
|
||||
}
|
||||
|
||||
return Ok(f)
|
||||
}
|
||||
|
||||
// Chmod changes the mode of the file.
|
||||
func (f *File) Chmod(mode os.FileMode) Result[*File] {
|
||||
var err error
|
||||
if f.file != nil {
|
||||
err = f.file.Chmod(mode)
|
||||
} else {
|
||||
err = os.Chmod(f.name.Std(), mode)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return Err[*File](err)
|
||||
}
|
||||
|
||||
return Ok(f)
|
||||
}
|
||||
|
||||
// Chown changes the owner of the file.
|
||||
func (f *File) Chown(uid, gid int) Result[*File] {
|
||||
var err error
|
||||
if f.file != nil {
|
||||
err = f.file.Chown(uid, gid)
|
||||
} else {
|
||||
err = os.Chown(f.name.Std(), uid, gid)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return Err[*File](err)
|
||||
}
|
||||
|
||||
return Ok(f)
|
||||
}
|
||||
|
||||
// Seek sets the file offset for the next Read or Write operation. The offset
|
||||
// is specified by the 'offset' parameter, and the 'whence' parameter determines
|
||||
// the reference point for the offset.
|
||||
//
|
||||
// The 'offset' parameter specifies the new offset in bytes relative to the
|
||||
// reference point determined by 'whence'. If 'whence' is set to io.SeekStart,
|
||||
// io.SeekCurrent, or io.SeekEnd, the offset is relative to the start of the file,
|
||||
// the current offset, or the end of the file, respectively.
|
||||
//
|
||||
// If the file is not open, this method will attempt to open it. If the open
|
||||
// operation fails, an error is returned.
|
||||
//
|
||||
// If the Seek operation fails, the file is closed, and an error is returned.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// file := g.NewFile("example.txt")
|
||||
// result := file.Seek(100, io.SeekStart)
|
||||
// if result.Err() != nil {
|
||||
// log.Fatal(result.Err())
|
||||
// }
|
||||
//
|
||||
// Parameters:
|
||||
// - offset: The new offset in bytes.
|
||||
// - whence: The reference point for the offset (io.SeekStart, io.SeekCurrent, or io.SeekEnd).
|
||||
//
|
||||
// Don't forget to close the file!
|
||||
func (f *File) Seek(offset int64, whence int) Result[*File] {
|
||||
if f.file == nil {
|
||||
if r := f.Open(); r.IsErr() {
|
||||
return r
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := f.file.Seek(offset, whence); err != nil {
|
||||
f.Close()
|
||||
return Err[*File](err)
|
||||
}
|
||||
|
||||
return Ok(f)
|
||||
}
|
||||
|
||||
// Close closes the File and unlocks its underlying file, if it is not already closed.
|
||||
func (f *File) Close() error {
|
||||
if f.file == nil {
|
||||
return &ErrFileClosed{f.name.Std()}
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
if f.guard {
|
||||
err = filelock.Unlock(f.file)
|
||||
}
|
||||
|
||||
if closeErr := f.file.Close(); closeErr != nil {
|
||||
err = closeErr
|
||||
}
|
||||
|
||||
f.file = nil
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Copy copies the file to the specified destination, with the specified mode (optional).
|
||||
// If no mode is provided, the default FileMode (0644) is used.
|
||||
func (f *File) Copy(dest String, mode ...os.FileMode) Result[*File] {
|
||||
if r := f.Open(); r.IsErr() {
|
||||
return r
|
||||
}
|
||||
|
||||
defer f.Close()
|
||||
|
||||
nf := NewFile(dest)
|
||||
if f.guard {
|
||||
nf.guard = true
|
||||
}
|
||||
|
||||
return nf.WriteFromReader(f.file, mode...)
|
||||
}
|
||||
|
||||
// Create is similar to os.Create, but it returns a write-locked file.
|
||||
// Don't forget to close the file!
|
||||
func (f *File) Create() Result[*File] {
|
||||
return f.OpenFile(os.O_RDWR|os.O_CREATE|os.O_TRUNC, FileCreate)
|
||||
}
|
||||
|
||||
// Dir returns the directory the file is in as an Dir instance.
|
||||
func (f *File) Dir() Result[*Dir] {
|
||||
dirPath := f.dirPath()
|
||||
if dirPath.IsErr() {
|
||||
return Err[*Dir](dirPath.err)
|
||||
}
|
||||
|
||||
return Ok(NewDir(dirPath.v))
|
||||
}
|
||||
|
||||
// Exist checks if the file exists.
|
||||
func (f *File) Exist() bool {
|
||||
if f.dirPath().IsOk() {
|
||||
filePath := f.filePath()
|
||||
if filePath.IsOk() {
|
||||
_, err := os.Stat(filePath.v.Std())
|
||||
return !os.IsNotExist(err)
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Ext returns the file extension.
|
||||
func (f *File) Ext() String { return String(filepath.Ext(f.name.Std())) }
|
||||
|
||||
// Guard sets a lock on the file to protect it from concurrent access.
|
||||
// It returns the File instance with the guard enabled.
|
||||
func (f *File) Guard() *File {
|
||||
f.guard = true
|
||||
return f
|
||||
}
|
||||
|
||||
// MimeType returns the MIME type of the file as Result[String].
|
||||
func (f *File) MimeType() Result[String] {
|
||||
if r := f.Open(); r.IsErr() {
|
||||
return Err[String](r.err)
|
||||
}
|
||||
|
||||
defer f.Close()
|
||||
|
||||
const bufferSize = 512
|
||||
|
||||
buff := make([]byte, bufferSize)
|
||||
|
||||
bytesRead, err := f.file.ReadAt(buff, 0)
|
||||
if err != nil && err != io.EOF {
|
||||
return Err[String](err)
|
||||
}
|
||||
|
||||
buff = buff[:bytesRead]
|
||||
|
||||
return Ok(String(http.DetectContentType(buff)))
|
||||
}
|
||||
|
||||
// Move function simply calls [File.Rename]
|
||||
func (f *File) Move(newpath String) Result[*File] { return f.Rename(newpath) }
|
||||
|
||||
// Name returns the name of the file.
|
||||
func (f *File) Name() String {
|
||||
if f.file != nil {
|
||||
return String(filepath.Base(f.file.Name()))
|
||||
}
|
||||
|
||||
return String(filepath.Base(f.name.Std()))
|
||||
}
|
||||
|
||||
// Open is like os.Open, but returns a read-locked file.
|
||||
// Don't forget to close the file!
|
||||
func (f *File) Open() Result[*File] { return f.OpenFile(os.O_RDONLY, 0) }
|
||||
|
||||
// OpenFile is like os.OpenFile, but returns a locked file.
|
||||
// If flag includes os.O_WRONLY or os.O_RDWR, the file is write-locked
|
||||
// otherwise, it is read-locked.
|
||||
// Don't forget to close the file!
|
||||
func (f *File) OpenFile(flag int, perm fs.FileMode) Result[*File] {
|
||||
file, err := os.OpenFile(f.name.Std(), flag&^os.O_TRUNC, perm)
|
||||
if err != nil {
|
||||
return Err[*File](err)
|
||||
}
|
||||
|
||||
if f.guard {
|
||||
switch flag & (os.O_RDONLY | os.O_WRONLY | os.O_RDWR) {
|
||||
case os.O_WRONLY, os.O_RDWR:
|
||||
err = filelock.Lock(file)
|
||||
default:
|
||||
err = filelock.RLock(file)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
file.Close()
|
||||
return Err[*File](err)
|
||||
}
|
||||
}
|
||||
|
||||
if flag&os.O_TRUNC == os.O_TRUNC {
|
||||
if err := file.Truncate(0); err != nil {
|
||||
if fi, statErr := file.Stat(); statErr != nil || fi.Mode().IsRegular() {
|
||||
if f.guard {
|
||||
filelock.Unlock(file)
|
||||
}
|
||||
|
||||
file.Close()
|
||||
|
||||
return Err[*File](err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
f.file = file
|
||||
|
||||
return Ok(f)
|
||||
}
|
||||
|
||||
// Path returns the absolute path of the file.
|
||||
func (f *File) Path() Result[String] { return f.filePath() }
|
||||
|
||||
// Print writes the content of the File to the standard output (console)
|
||||
// and returns the File unchanged.
|
||||
func (f *File) Print() *File { fmt.Print(f); return f }
|
||||
|
||||
// Println writes the content of the File to the standard output (console) with a newline
|
||||
// and returns the File unchanged.
|
||||
func (f *File) Println() *File { fmt.Println(f); return f }
|
||||
|
||||
// Read opens the named file with a read-lock and returns its contents.
|
||||
func (f *File) Read() Result[String] {
|
||||
if r := f.Open(); r.IsErr() {
|
||||
return Err[String](r.err)
|
||||
}
|
||||
|
||||
defer f.Close()
|
||||
|
||||
content, err := io.ReadAll(f.file)
|
||||
if err != nil {
|
||||
return Err[String](err)
|
||||
}
|
||||
|
||||
return Ok(String(content))
|
||||
}
|
||||
|
||||
// Remove removes the file.
|
||||
func (f *File) Remove() Result[*File] {
|
||||
if err := os.Remove(f.name.Std()); err != nil {
|
||||
return Err[*File](err)
|
||||
}
|
||||
|
||||
return Ok(f)
|
||||
}
|
||||
|
||||
// Rename renames the file to the specified new path.
|
||||
func (f *File) Rename(newpath String) Result[*File] {
|
||||
if !f.Exist() {
|
||||
return Err[*File](&ErrFileNotExist{f.name.Std()})
|
||||
}
|
||||
|
||||
nf := NewFile(newpath)
|
||||
if f.guard {
|
||||
nf.guard = true
|
||||
}
|
||||
|
||||
if r := nf.createAll(); r.IsErr() {
|
||||
return r
|
||||
}
|
||||
|
||||
if err := os.Rename(f.name.Std(), newpath.Std()); err != nil {
|
||||
return Err[*File](err)
|
||||
}
|
||||
|
||||
return Ok(nf)
|
||||
}
|
||||
|
||||
// Split splits the file path into its directory and file components.
|
||||
func (f *File) Split() (*Dir, *File) {
|
||||
path := f.Path()
|
||||
if path.IsErr() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
dir, file := filepath.Split(path.v.Std())
|
||||
|
||||
return NewDir(String(dir)), NewFile(String(file))
|
||||
}
|
||||
|
||||
// Stat returns the fs.FileInfo of the file.
|
||||
// It calls the file's Stat method if the file is open, or os.Stat otherwise.
|
||||
func (f *File) Stat() Result[fs.FileInfo] {
|
||||
if f.file != nil {
|
||||
return ResultOf(f.file.Stat())
|
||||
}
|
||||
|
||||
return ResultOf(os.Stat(f.name.Std()))
|
||||
}
|
||||
|
||||
// Lstat retrieves information about the symbolic link represented by the *File instance.
|
||||
// It returns a Result[fs.FileInfo] containing details about the symbolic link's metadata.
|
||||
// Unlike Stat, Lstat does not follow the link and provides information about the link itself.
|
||||
func (f *File) Lstat() Result[fs.FileInfo] {
|
||||
return ResultOf(os.Lstat(f.name.Std()))
|
||||
}
|
||||
|
||||
// IsDir checks if the file is a directory.
|
||||
func (f *File) IsDir() bool {
|
||||
stat := f.Stat()
|
||||
return stat.IsOk() && stat.v.IsDir()
|
||||
}
|
||||
|
||||
// IsLink checks if the file is a symbolic link.
|
||||
func (f *File) IsLink() bool {
|
||||
stat := f.Lstat()
|
||||
return stat.IsOk() && stat.v.Mode()&os.ModeSymlink != 0
|
||||
}
|
||||
|
||||
// Std returns the underlying *os.File instance.
|
||||
// Don't forget to close the file with g.File().Close()!
|
||||
func (f *File) Std() *os.File { return f.file }
|
||||
|
||||
// CreateTemp creates a new temporary file in the specified directory with the
|
||||
// specified name pattern and returns a Result, which contains a pointer to the File
|
||||
// or an error if the operation fails.
|
||||
// If no directory is specified, the default directory for temporary files is used.
|
||||
// If no name pattern is specified, the default pattern "*" is used.
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// - args ...String: A variadic parameter specifying the directory and/or name
|
||||
// pattern for the temporary file.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - *File: A pointer to the File representing the temporary file.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// f := g.NewFile("")
|
||||
// tmpfile := f.CreateTemp() // Creates a temporary file with default settings
|
||||
// tmpfileWithDir := f.CreateTemp("mydir") // Creates a temporary file in "mydir" directory
|
||||
// tmpfileWithPattern := f.CreateTemp("", "tmp") // Creates a temporary file with "tmp" pattern
|
||||
func (f *File) CreateTemp(args ...String) Result[*File] {
|
||||
dir := ""
|
||||
pattern := "*"
|
||||
|
||||
if len(args) != 0 {
|
||||
if len(args) > 1 {
|
||||
pattern = args[1].Std()
|
||||
}
|
||||
|
||||
dir = args[0].Std()
|
||||
}
|
||||
|
||||
tmpfile, err := os.CreateTemp(dir, pattern)
|
||||
if err != nil {
|
||||
return Err[*File](err)
|
||||
}
|
||||
|
||||
ntmpfile := NewFile(String(tmpfile.Name()))
|
||||
ntmpfile.file = tmpfile
|
||||
if f.guard {
|
||||
ntmpfile.guard = true
|
||||
}
|
||||
|
||||
defer ntmpfile.Close()
|
||||
|
||||
return Ok(ntmpfile)
|
||||
}
|
||||
|
||||
// Write opens the named file (creating it with the given permissions if needed),
|
||||
// then write-locks it and overwrites it with the given content.
|
||||
func (f *File) Write(content String, mode ...os.FileMode) Result[*File] {
|
||||
return f.WriteFromReader(content.Reader(), mode...)
|
||||
}
|
||||
|
||||
// WriteFromReader takes an io.Reader (scr) as input and writes the data from the reader into the file.
|
||||
// If no FileMode is provided, the default FileMode (0644) is used.
|
||||
func (f *File) WriteFromReader(scr io.Reader, mode ...os.FileMode) Result[*File] {
|
||||
if f.file == nil {
|
||||
if r := f.createAll(); r.IsErr() {
|
||||
return r
|
||||
}
|
||||
}
|
||||
|
||||
filePath := f.filePath()
|
||||
if filePath.IsErr() {
|
||||
return Err[*File](filePath.err)
|
||||
}
|
||||
|
||||
fmode := Slice[os.FileMode](mode).Get(0).UnwrapOr(FileDefault)
|
||||
|
||||
if r := f.OpenFile(os.O_WRONLY|os.O_CREATE|os.O_TRUNC, fmode); r.IsErr() {
|
||||
return Err[*File](r.err)
|
||||
}
|
||||
|
||||
defer f.Close()
|
||||
|
||||
_, err := io.Copy(f.file, scr)
|
||||
if err != nil {
|
||||
return Err[*File](err)
|
||||
}
|
||||
|
||||
err = f.file.Sync()
|
||||
if err != nil {
|
||||
return Err[*File](err)
|
||||
}
|
||||
|
||||
return Ok(f)
|
||||
}
|
||||
|
||||
// dirPath returns the absolute path of the directory containing the file.
|
||||
func (f *File) dirPath() Result[String] {
|
||||
var (
|
||||
path string
|
||||
err error
|
||||
)
|
||||
|
||||
if f.IsDir() {
|
||||
path, err = filepath.Abs(f.name.Std())
|
||||
} else {
|
||||
path, err = filepath.Abs(filepath.Dir(f.name.Std()))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return Err[String](err)
|
||||
}
|
||||
|
||||
return Ok(String(path))
|
||||
}
|
||||
|
||||
// filePath returns the full file path, including the directory and file name.
|
||||
func (f *File) filePath() Result[String] {
|
||||
dirPath := f.dirPath()
|
||||
if dirPath.IsErr() {
|
||||
return Err[String](dirPath.err)
|
||||
}
|
||||
|
||||
if f.IsDir() {
|
||||
return dirPath
|
||||
}
|
||||
|
||||
return Ok(String(filepath.Join(dirPath.v.Std(), filepath.Base(f.name.Std()))))
|
||||
}
|
||||
|
||||
func (f *File) createAll() Result[*File] {
|
||||
dirPath := f.dirPath()
|
||||
if dirPath.IsErr() {
|
||||
return Err[*File](dirPath.err)
|
||||
}
|
||||
|
||||
if !f.Exist() {
|
||||
if err := os.MkdirAll(dirPath.v.Std(), DirDefault); err != nil {
|
||||
return Err[*File](err)
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(f)
|
||||
}
|
||||
-140
@@ -1,140 +0,0 @@
|
||||
package g
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
type (
|
||||
// fencode represents a wrapper for file encoding.
|
||||
fencode struct{ f *File }
|
||||
|
||||
// fdecode represents a wrapper for file decoding.
|
||||
fdecode struct{ f *File }
|
||||
)
|
||||
|
||||
// Encode returns an fencode struct wrapping the given file for encoding.
|
||||
func (f *File) Encode() fencode { return fencode{f} }
|
||||
|
||||
// Decode returns an fdecode struct wrapping the given file for decoding.
|
||||
func (f *File) Decode() fdecode { return fdecode{f} }
|
||||
|
||||
// Gob encodes the provided data using the encoding/gob package and writes it to the file.
|
||||
// It returns a Result[*File] indicating the success or failure of the encoding operation.
|
||||
//
|
||||
// If the encoding operation is successful, the created file is closed automatically.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// data := g.SliceOf(1, 2, 3, 4)
|
||||
// result := g.NewFile("somefile.gob").Encode().Gob(data)
|
||||
//
|
||||
// Parameters:
|
||||
// - data: The data to be encoded and written to the file.
|
||||
//
|
||||
// Returns:
|
||||
// - Result[*File]: A Result containing a *File if the operation is successful; otherwise, an error Result.
|
||||
func (fe fencode) Gob(data any) Result[*File] {
|
||||
r := fe.f.Create()
|
||||
if r.IsErr() {
|
||||
return r
|
||||
}
|
||||
|
||||
defer r.v.Close()
|
||||
|
||||
if err := gob.NewEncoder(r.v.Std()).Encode(data); err != nil {
|
||||
return Err[*File](err)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// Gob decodes data from the file using the encoding/gob package and populates the provided data structure.
|
||||
// It returns a Result[*File] indicating the success or failure of the decoding operation.
|
||||
//
|
||||
// If the decoding operation is successful, the file is closed automatically.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// var data g.Slice[int]
|
||||
// result := g.NewFile("somefile.gob").Decode().Gob(&data)
|
||||
//
|
||||
// Parameters:
|
||||
// - data: A pointer to the data structure where the decoded data will be stored.
|
||||
//
|
||||
// Returns:
|
||||
// - Result[*File]: A Result containing a *File if the operation is successful; otherwise, an error Result.
|
||||
func (fd fdecode) Gob(data any) Result[*File] {
|
||||
r := fd.f.Open()
|
||||
if r.IsErr() {
|
||||
return r
|
||||
}
|
||||
|
||||
defer r.v.Close()
|
||||
|
||||
if err := gob.NewDecoder(r.v.Std()).Decode(data); err != nil {
|
||||
return Err[*File](err)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// JSON encodes the provided data using the encoding/json package and writes it to the file.
|
||||
// It returns a Result[*File] indicating the success or failure of the encoding operation.
|
||||
//
|
||||
// If the encoding operation is successful, the created file is closed automatically.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// data := g.SliceOf(1, 2, 3, 4)
|
||||
// result := g.NewFile("somefile.json").Encode().JSON(data)
|
||||
//
|
||||
// Parameters:
|
||||
// - data: The data to be encoded and written to the file.
|
||||
//
|
||||
// Returns:
|
||||
// - Result[*File]: A Result containing a *File if the operation is successful; otherwise, an error Result.
|
||||
func (fe fencode) JSON(data any) Result[*File] {
|
||||
r := fe.f.Create()
|
||||
if r.IsErr() {
|
||||
return r
|
||||
}
|
||||
|
||||
defer r.v.Close()
|
||||
|
||||
if err := json.NewEncoder(r.v.Std()).Encode(data); err != nil {
|
||||
return Err[*File](err)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// JSON decodes data from the file using the encoding/json package and populates the provided data structure.
|
||||
// It returns a Result[*File] indicating the success or failure of the decoding operation.
|
||||
//
|
||||
// If the decoding operation is successful, the file is closed automatically.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// var data g.Slice[int]
|
||||
// result := g.NewFile("somefile.json").Decode().JSON(&data)
|
||||
//
|
||||
// Parameters:
|
||||
// - data: A pointer to the data structure where the decoded data will be stored.
|
||||
//
|
||||
// Returns:
|
||||
// - Result[*File]: A Result containing a *File if the operation is successful; otherwise, an error Result.
|
||||
func (fd fdecode) JSON(data any) Result[*File] {
|
||||
r := fd.f.Open()
|
||||
if r.IsErr() {
|
||||
return r
|
||||
}
|
||||
|
||||
defer r.v.Close()
|
||||
|
||||
if err := json.NewDecoder(r.v.Std()).Decode(data); err != nil {
|
||||
return Err[*File](err)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
+89
-8
@@ -1,6 +1,7 @@
|
||||
package g
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math"
|
||||
@@ -11,11 +12,14 @@ import (
|
||||
"github.com/enetx/g/constraints"
|
||||
)
|
||||
|
||||
// Float is a wrapper around the float64 type.
|
||||
type Float float64
|
||||
|
||||
// NewFloat creates a new Float with the provided value.
|
||||
func NewFloat[T constraints.Float | constraints.Integer](float T) Float { return Float(float) }
|
||||
|
||||
// Transform applies a transformation function to the Float and returns the result.
|
||||
func (f Float) Transform(fn func(Float) Float) Float { return fn(f) }
|
||||
func (f Float) Transform[U any](fn func(Float) U) U { return fn(f) }
|
||||
|
||||
// BytesBE returns the IEEE-754 representation of the Float as Bytes in BigEndian order.
|
||||
// The Float is converted to its 64-bit IEEE-754 binary representation.
|
||||
@@ -37,10 +41,10 @@ func (f Float) BytesLE() Bytes {
|
||||
return Bytes(buf[:])
|
||||
}
|
||||
|
||||
// Min returns the minimum of two Floats.
|
||||
// Min returns the minimum of Floats.
|
||||
func (f Float) Min(b ...Float) Float { return cmp.Min(append(b, f)...) }
|
||||
|
||||
// Max returns the maximum of two Floats.
|
||||
// Max returns the maximum of Floats.
|
||||
func (f Float) Max(b ...Float) Float { return cmp.Max(append(b, f)...) }
|
||||
|
||||
// Sqrt returns the square root of the Float.
|
||||
@@ -85,6 +89,9 @@ func (f Float) Mod(b Float) Float { return Float(math.Mod(f.Std(), b.Std())) }
|
||||
// Abs returns the absolute value of the Float.
|
||||
func (f Float) Abs() Float { return Float(math.Abs(f.Std())) }
|
||||
|
||||
// Neg returns the Float with its sign inverted.
|
||||
func (f Float) Neg() Float { return -f }
|
||||
|
||||
// Add adds two Floats and returns the result.
|
||||
func (f Float) Add(b Float) Float { return f + b }
|
||||
|
||||
@@ -92,6 +99,13 @@ func (f Float) Add(b Float) Float { return f + b }
|
||||
func (f Float) BigFloat() *big.Float { return big.NewFloat(f.Std()) }
|
||||
|
||||
// Cmp compares two Floats and returns an cmp.Ordering.
|
||||
//
|
||||
// NaN handling is NOT IEEE 754. Comparison routes through cmp.Compare, which
|
||||
// imposes a total order: a NaN is treated as less than every non-NaN value, and
|
||||
// two NaNs compare as equal. Consequently Eq, Ne, Lt, Gt, Lte, and Gte all
|
||||
// inherit this non-IEEE behavior — for example NaN.Eq(NaN) reports true and a
|
||||
// NaN sorts as the smallest value. Use math.IsNaN(f.Std()) when strict IEEE 754
|
||||
// semantics (where every NaN comparison is false) are required.
|
||||
func (f Float) Cmp(b Float) cmp.Ordering { return cmp.Cmp(f, b) }
|
||||
|
||||
// Div divides two Floats and returns the result.
|
||||
@@ -109,11 +123,17 @@ func (f Float) Std() float64 { return float64(f) }
|
||||
// Gt checks if the Float is greater than the specified Float.
|
||||
func (f Float) Gt(b Float) bool { return f.Cmp(b).IsGt() }
|
||||
|
||||
// Gte checks if the Float is greater than or equal to the specified Float.
|
||||
func (f Float) Gte(b Float) bool { return !f.Lt(b) }
|
||||
|
||||
// Lte checks if the Float is less than or equal to the specified Float.
|
||||
func (f Float) Lte(b Float) bool { return !f.Gt(b) }
|
||||
|
||||
// Int returns the Float as an Int.
|
||||
func (f Float) Int() Int { return Int(f) }
|
||||
|
||||
// String returns the Float as an String.
|
||||
func (f Float) String() String { return String(strconv.FormatFloat(f.Std(), 'g', -1, 64)) }
|
||||
func (f Float) String() String { return String(strconv.FormatFloat(f.Std(), 'f', -1, 64)) }
|
||||
|
||||
// Lt checks if the Float is less than the specified Float.
|
||||
func (f Float) Lt(b Float) bool { return f.Cmp(b).IsLt() }
|
||||
@@ -135,6 +155,8 @@ func (f Float) Round() Int { return Int(math.Round(f.Std())) }
|
||||
//
|
||||
// Returns:
|
||||
// - Float: A new Float value rounded to the specified number of decimal places.
|
||||
// If scaling by 10^precision overflows to a non-finite value (±Inf/NaN),
|
||||
// the Float is returned unchanged.
|
||||
func (f Float) RoundDecimal(precision Int) Float {
|
||||
if precision < 0 {
|
||||
return f
|
||||
@@ -146,7 +168,12 @@ func (f Float) RoundDecimal(precision Int) Float {
|
||||
|
||||
pow := math.Pow(10, float64(precision))
|
||||
|
||||
return Float(math.Round(f.Std()*pow) / pow)
|
||||
scaled := f.Std() * pow
|
||||
if math.IsInf(scaled, 0) || math.IsNaN(scaled) {
|
||||
return f
|
||||
}
|
||||
|
||||
return Float(math.Round(scaled) / pow)
|
||||
}
|
||||
|
||||
// TruncDecimal truncates the Float value to the specified number of decimal places.
|
||||
@@ -157,6 +184,8 @@ func (f Float) RoundDecimal(precision Int) Float {
|
||||
//
|
||||
// Returns:
|
||||
// - Float: A new Float value truncated to the specified number of decimal places.
|
||||
// If scaling by 10^precision overflows to a non-finite value (±Inf/NaN),
|
||||
// the Float is returned unchanged.
|
||||
func (f Float) TruncDecimal(precision Int) Float {
|
||||
if precision < 0 {
|
||||
return f
|
||||
@@ -168,7 +197,12 @@ func (f Float) TruncDecimal(precision Int) Float {
|
||||
|
||||
pow := math.Pow(10, float64(precision))
|
||||
|
||||
return Float(math.Trunc(f.Std()*pow) / pow)
|
||||
scaled := f.Std() * pow
|
||||
if math.IsInf(scaled, 0) || math.IsNaN(scaled) {
|
||||
return f
|
||||
}
|
||||
|
||||
return Float(math.Trunc(scaled) / pow)
|
||||
}
|
||||
|
||||
// CeilDecimal rounds the Float value up (towards +Inf) to the specified number of decimal places.
|
||||
@@ -179,6 +213,8 @@ func (f Float) TruncDecimal(precision Int) Float {
|
||||
//
|
||||
// Returns:
|
||||
// - Float: A new Float value rounded up to the specified number of decimal places.
|
||||
// If scaling by 10^precision overflows to a non-finite value (±Inf/NaN),
|
||||
// the Float is returned unchanged.
|
||||
func (f Float) CeilDecimal(precision Int) Float {
|
||||
if precision < 0 {
|
||||
return f
|
||||
@@ -190,7 +226,12 @@ func (f Float) CeilDecimal(precision Int) Float {
|
||||
|
||||
pow := math.Pow(10, float64(precision))
|
||||
|
||||
return Float(math.Ceil(f.Std()*pow) / pow)
|
||||
scaled := f.Std() * pow
|
||||
if math.IsInf(scaled, 0) || math.IsNaN(scaled) {
|
||||
return f
|
||||
}
|
||||
|
||||
return Float(math.Ceil(scaled) / pow)
|
||||
}
|
||||
|
||||
// FloorDecimal rounds the Float value down (towards -Inf) to the specified number of decimal places.
|
||||
@@ -201,6 +242,8 @@ func (f Float) CeilDecimal(precision Int) Float {
|
||||
//
|
||||
// Returns:
|
||||
// - Float: A new Float value rounded down to the specified number of decimal places.
|
||||
// If scaling by 10^precision overflows to a non-finite value (±Inf/NaN),
|
||||
// the Float is returned unchanged.
|
||||
func (f Float) FloorDecimal(precision Int) Float {
|
||||
if precision < 0 {
|
||||
return f
|
||||
@@ -212,7 +255,12 @@ func (f Float) FloorDecimal(precision Int) Float {
|
||||
|
||||
pow := math.Pow(10, float64(precision))
|
||||
|
||||
return Float(math.Floor(f.Std()*pow) / pow)
|
||||
scaled := f.Std() * pow
|
||||
if math.IsInf(scaled, 0) || math.IsNaN(scaled) {
|
||||
return f
|
||||
}
|
||||
|
||||
return Float(math.Floor(scaled) / pow)
|
||||
}
|
||||
|
||||
// Sub subtracts two Floats and returns the result.
|
||||
@@ -231,3 +279,36 @@ func (f Float) Print() Float { fmt.Print(f); return f }
|
||||
// Println writes the value of the Float to the standard output (console) with a newline
|
||||
// and returns the Float unchanged.
|
||||
func (f Float) Println() Float { fmt.Println(f); return f }
|
||||
|
||||
// Scan implements the database/sql.Scanner interface for g.Float.
|
||||
//
|
||||
// Behavior:
|
||||
// - If src is nil, the value is set to 0 (SQL NULL).
|
||||
// - If src is a float64 (common SQL REAL/DOUBLE type), it is assigned.
|
||||
// - Otherwise, an error is returned.
|
||||
//
|
||||
// Supported SQL types (common):
|
||||
// - REAL / DOUBLE → float64
|
||||
//
|
||||
// Notes:
|
||||
// - This allows g.Float to be used directly with database/sql and compatible drivers.
|
||||
func (f *Float) Scan(src any) error {
|
||||
if src == nil {
|
||||
*f = 0
|
||||
return nil
|
||||
}
|
||||
|
||||
if f64, ok := src.(float64); ok {
|
||||
*f = Float(f64)
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("g.Float.Scan: cannot scan %T into g.Float", src)
|
||||
}
|
||||
|
||||
// Value implements the database/sql/driver.Valuer interface for g.Float.
|
||||
//
|
||||
// Behavior:
|
||||
// - Returns the underlying float64 value, ready for database insertion.
|
||||
// - Always returns a value compatible with SQL REAL / DOUBLE types.
|
||||
func (f Float) Value() (driver.Value, error) { return float64(f), nil }
|
||||
+24
-14
@@ -1,22 +1,32 @@
|
||||
package g
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
import "os"
|
||||
|
||||
const (
|
||||
ASCII_LETTERS String = ASCII_LOWERCASE + ASCII_UPPERCASE
|
||||
ASCII_LOWERCASE String = "abcdefghijklmnopqrstuvwxyz"
|
||||
ASCII_UPPERCASE String = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
DIGITS String = "0123456789"
|
||||
HEXDIGITS String = "0123456789abcdefABCDEF"
|
||||
OCTDIGITS String = "01234567"
|
||||
PUNCTUATION String = `!"#$%&'()*+,-./:;<=>?@[\]^{|}~` + "`"
|
||||
// ASCIILetters is the set of all ASCII letters (lowercase + uppercase).
|
||||
ASCIILetters String = ASCIILowercase + ASCIIUppercase
|
||||
// ASCIILowercase is the set of lowercase ASCII letters.
|
||||
ASCIILowercase String = "abcdefghijklmnopqrstuvwxyz"
|
||||
// ASCIIUppercase is the set of uppercase ASCII letters.
|
||||
ASCIIUppercase String = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
// Digits is the set of decimal digit characters.
|
||||
Digits String = "0123456789"
|
||||
// HexDigits is the set of hexadecimal digit characters (both cases).
|
||||
HexDigits String = "0123456789abcdefABCDEF"
|
||||
// OctDigits is the set of octal digit characters.
|
||||
OctDigits String = "01234567"
|
||||
// Punctuation is the set of ASCII punctuation characters.
|
||||
Punctuation String = `!"#$%&'()*+,-./:;<=>?@[\]^{|}~` + "`"
|
||||
|
||||
// FileDefault is the default permission mode (0o644) used when writing files.
|
||||
FileDefault os.FileMode = 0o644
|
||||
FileCreate os.FileMode = 0o666
|
||||
DirDefault os.FileMode = 0o755
|
||||
FullAccess os.FileMode = 0o777
|
||||
// FileCreate is the permission mode (0o666) used when creating files.
|
||||
FileCreate os.FileMode = 0o666
|
||||
// DirDefault is the default permission mode (0o755) used when creating directories.
|
||||
DirDefault os.FileMode = 0o755
|
||||
// FullAccess is the permission mode (0o777) granting read, write and execute to everyone.
|
||||
FullAccess os.FileMode = 0o777
|
||||
|
||||
PathSeperator = String(os.PathSeparator)
|
||||
// PathSeparator is the OS-specific path separator as a String.
|
||||
PathSeparator = String(os.PathSeparator)
|
||||
)
|
||||
+162
-20
@@ -2,16 +2,32 @@ package g
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/enetx/g/cmp"
|
||||
"github.com/enetx/g/f"
|
||||
)
|
||||
|
||||
// Heap is a generic binary heap data structure that maintains elements in heap order.
|
||||
// It can be configured as either a min-heap or max-heap based on the comparison function.
|
||||
type Heap[T any] struct {
|
||||
data Slice[T]
|
||||
cmp func(T, T) cmp.Ordering
|
||||
}
|
||||
|
||||
// NewHeap creates a new heap with the given comparison function.
|
||||
// The comparison function should return:
|
||||
// - cmp.Less if the first argument should have higher priority
|
||||
// - cmp.Greater if the second argument should have higher priority
|
||||
// - cmp.Equal if they have equal priority
|
||||
//
|
||||
// NewHeap panics if compareFn is nil, since a nil
|
||||
// comparison function would otherwise nil-deref on the first Push.
|
||||
func NewHeap[T any](compareFn func(T, T) cmp.Ordering) *Heap[T] {
|
||||
if compareFn == nil {
|
||||
panic("g.NewHeap: compareFn cannot be nil")
|
||||
}
|
||||
|
||||
return &Heap[T]{
|
||||
data: make(Slice[T], 0),
|
||||
cmp: compareFn,
|
||||
@@ -19,7 +35,7 @@ func NewHeap[T any](compareFn func(T, T) cmp.Ordering) *Heap[T] {
|
||||
}
|
||||
|
||||
// Transform applies a transformation function to the Heap and returns the result.
|
||||
func (h *Heap[T]) Transform(fn func(*Heap[T]) *Heap[T]) *Heap[T] { return fn(h) }
|
||||
func (h *Heap[T]) Transform[U any](fn func(*Heap[T]) U) U { return fn(h) }
|
||||
|
||||
// Iter returns a non-consuming iterator that yields elements in sorted order.
|
||||
//
|
||||
@@ -33,7 +49,7 @@ func (h *Heap[T]) Transform(fn func(*Heap[T]) *Heap[T]) *Heap[T] { return fn(h)
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - SeqSlice[T]: An iterator that yields elements in sorted order
|
||||
// - Seq[T]: An iterator that yields elements in sorted order
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
@@ -48,14 +64,15 @@ func (h *Heap[T]) Transform(fn func(*Heap[T]) *Heap[T]) *Heap[T] { return fn(h)
|
||||
// fmt.Printf("Heap still has %d elements\n", heap.Len()) // Output: 5
|
||||
//
|
||||
// // Can be used with other iterator methods
|
||||
// firstThree := heap.Iter().Take(3).Collect() // [1, 5, 8]
|
||||
// // (the Heap materializer requires a comparison function)
|
||||
// firstThree := heap.Iter().Take(3).Collect().Heap(cmp.Cmp) // [1, 5, 8]
|
||||
// evenNumbers := heap.Iter().Filter(func(x int) bool {
|
||||
// return x%2 == 0
|
||||
// }).Collect() // [8, 10]
|
||||
func (h *Heap[T]) Iter() SeqHeap[T] {
|
||||
// }).Collect().Heap(cmp.Cmp) // [8, 10]
|
||||
func (h *Heap[T]) Iter() Seq[T] {
|
||||
return func(yield func(T) bool) {
|
||||
clone := h.Clone()
|
||||
for !clone.Empty() {
|
||||
for !clone.IsEmpty() {
|
||||
if !yield(clone.Pop().Some()) {
|
||||
return
|
||||
}
|
||||
@@ -78,7 +95,7 @@ func (h *Heap[T]) Iter() SeqHeap[T] {
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - SeqSlice[T]: An iterator that yields elements in sorted order while consuming the heap
|
||||
// - Seq[T]: An iterator that yields elements in sorted order while consuming the heap
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
@@ -86,7 +103,7 @@ func (h *Heap[T]) Iter() SeqHeap[T] {
|
||||
// heap.Push(10, 5, 15, 1, 8)
|
||||
//
|
||||
// // Consume the heap while iterating
|
||||
// result := heap.IntoIter().Collect() // [1, 5, 8, 10, 15]
|
||||
// result := heap.IntoIter().Collect().Heap(cmp.Cmp) // [1, 5, 8, 10, 15]
|
||||
//
|
||||
// fmt.Printf("Heap now has %d elements\n", heap.Len()) // Output: 0
|
||||
//
|
||||
@@ -98,9 +115,9 @@ func (h *Heap[T]) Iter() SeqHeap[T] {
|
||||
// fmt.Printf("%d ", x) // Output: 15 20
|
||||
// })
|
||||
// fmt.Printf("Remaining: %d elements\n", heap2.Len()) // Output: 2
|
||||
func (h *Heap[T]) IntoIter() SeqHeap[T] {
|
||||
func (h *Heap[T]) IntoIter() Seq[T] {
|
||||
return func(yield func(T) bool) {
|
||||
for !h.Empty() {
|
||||
for !h.IsEmpty() {
|
||||
if !yield(h.Pop().Some()) {
|
||||
return
|
||||
}
|
||||
@@ -110,9 +127,27 @@ func (h *Heap[T]) IntoIter() SeqHeap[T] {
|
||||
|
||||
// Push adds one or more items to the heap.
|
||||
func (h *Heap[T]) Push(items ...T) {
|
||||
for _, item := range items {
|
||||
h.data = append(h.data, item)
|
||||
if len(items) == 1 {
|
||||
h.data = append(h.data, items[0])
|
||||
h.heapifyUp(len(h.data) - 1)
|
||||
return
|
||||
}
|
||||
|
||||
if len(items) > 1 {
|
||||
start := len(h.data)
|
||||
h.data = append(h.data, items...)
|
||||
|
||||
// Rebuilding is linear and wins for large batches. For a small batch on
|
||||
// an established heap, sift only the appended elements to avoid scanning
|
||||
// the entire existing heap.
|
||||
if start == 0 || len(items) > start/2 {
|
||||
h.heapify()
|
||||
return
|
||||
}
|
||||
|
||||
for i := start; i < len(h.data); i++ {
|
||||
h.heapifyUp(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,6 +161,8 @@ func (h *Heap[T]) Pop() Option[T] {
|
||||
top := h.data[0]
|
||||
last := len(h.data) - 1
|
||||
h.data[0] = h.data[last]
|
||||
var zero T
|
||||
h.data[last] = zero
|
||||
h.data = h.data[:last]
|
||||
|
||||
if len(h.data) > 0 {
|
||||
@@ -145,28 +182,83 @@ func (h *Heap[T]) Peek() Option[T] {
|
||||
return Some(h.data[0])
|
||||
}
|
||||
|
||||
// Contains reports whether the heap contains the given value.
|
||||
//
|
||||
// Equality is determined the same way as Slice.Contains: a direct == fast path
|
||||
// for comparable element types, falling back to reflect.DeepEqual for
|
||||
// interface-typed or otherwise uncomparable values.
|
||||
func (h *Heap[T]) Contains(value T) bool { return h.data.Contains(value) }
|
||||
|
||||
// Remove removes and returns the element at index i in the heap's backing
|
||||
// storage. Indices follow the internal heap layout (index 0 is the root);
|
||||
// use Slice to observe element positions.
|
||||
//
|
||||
// Returns None if i is out of range. After removal the heap property is
|
||||
// restored in O(log n).
|
||||
func (h *Heap[T]) Remove(i Int) Option[T] {
|
||||
n := len(h.data) - 1
|
||||
if i < 0 || int(i) > n {
|
||||
return None[T]()
|
||||
}
|
||||
|
||||
idx := int(i)
|
||||
removed := h.data[idx]
|
||||
|
||||
if idx != n {
|
||||
h.data[idx] = h.data[n]
|
||||
}
|
||||
|
||||
var zero T
|
||||
h.data[n] = zero
|
||||
h.data = h.data[:n]
|
||||
|
||||
if idx < len(h.data) {
|
||||
h.heapifyDown(idx)
|
||||
h.heapifyUp(idx)
|
||||
}
|
||||
|
||||
return Some(removed)
|
||||
}
|
||||
|
||||
// Fix re-establishes the heap ordering after the element at index i has changed
|
||||
// its value. It is equivalent to, but less expensive than, removing the element
|
||||
// at index i and pushing the new value.
|
||||
//
|
||||
// Indices follow the internal heap layout (index 0 is the root). Fix is a no-op
|
||||
// if i is out of range. The cost is O(log n).
|
||||
func (h *Heap[T]) Fix(i Int) {
|
||||
if i < 0 || int(i) >= len(h.data) {
|
||||
return
|
||||
}
|
||||
|
||||
idx := int(i)
|
||||
h.heapifyDown(idx)
|
||||
h.heapifyUp(idx)
|
||||
}
|
||||
|
||||
// Len returns the number of elements in the heap.
|
||||
func (h *Heap[T]) Len() Int {
|
||||
return h.data.Len()
|
||||
}
|
||||
|
||||
// Empty returns true if the heap contains no elements.
|
||||
func (h *Heap[T]) Empty() bool {
|
||||
// IsEmpty returns true if the heap contains no elements.
|
||||
func (h *Heap[T]) IsEmpty() bool {
|
||||
return len(h.data) == 0
|
||||
}
|
||||
|
||||
// ToSlice returns a slice containing all elements in the heap.
|
||||
// Slice returns a slice containing all elements in the heap.
|
||||
// The order is not guaranteed to be sorted.
|
||||
func (h *Heap[T]) ToSlice() Slice[T] {
|
||||
func (h *Heap[T]) Slice() Slice[T] {
|
||||
result := make(Slice[T], len(h.data))
|
||||
copy(result, h.data)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Clear removes all elements from the heap.
|
||||
// Clear removes all elements from the heap and releases the backing array,
|
||||
// allowing the previously held elements to be garbage collected.
|
||||
func (h *Heap[T]) Clear() {
|
||||
h.data = h.data[:0]
|
||||
h.data = nil
|
||||
}
|
||||
|
||||
// Clone creates a deep copy of the heap.
|
||||
@@ -177,6 +269,47 @@ func (h *Heap[T]) Clone() *Heap[T] {
|
||||
}
|
||||
}
|
||||
|
||||
// Eq checks if two Heaps are equal.
|
||||
//
|
||||
// Heaps are considered equal if they yield the same elements in the same
|
||||
// iteration order (the sorted order produced by Iter), regardless of the
|
||||
// internal layout of their backing storage. The comparison functions
|
||||
// themselves are not compared; each heap is drained using its own ordering.
|
||||
func (h *Heap[T]) Eq(other *Heap[T]) bool {
|
||||
if h == other {
|
||||
return true
|
||||
}
|
||||
|
||||
if h == nil || other == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if h.Len() != other.Len() {
|
||||
return false
|
||||
}
|
||||
|
||||
a, b := h.Clone(), other.Clone()
|
||||
|
||||
if f.IsComparable[T]() && reflect.TypeFor[T]().Kind() != reflect.Interface {
|
||||
for !a.IsEmpty() {
|
||||
if any(a.Pop().Some()) != any(b.Pop().Some()) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for !a.IsEmpty() {
|
||||
if !reflect.DeepEqual(a.Pop().Some(), b.Pop().Some()) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Ne checks if two Heaps are not equal.
|
||||
func (h *Heap[T]) Ne(other *Heap[T]) bool { return !h.Eq(other) }
|
||||
|
||||
// heapify transforms the entire data slice into a valid heap.
|
||||
func (h *Heap[T]) heapify() {
|
||||
for i := len(h.data)/2 - 1; i >= 0; i-- {
|
||||
@@ -223,12 +356,13 @@ func (h *Heap[T]) heapifyDown(idx int) {
|
||||
}
|
||||
|
||||
// String returns a string representation of the heap.
|
||||
func (h Heap[T]) String() string {
|
||||
func (h *Heap[T]) String() string {
|
||||
if len(h.data) == 0 {
|
||||
return "Heap[]"
|
||||
}
|
||||
|
||||
var b Builder
|
||||
b.Grow(Int(len(h.data)) * 8)
|
||||
b.WriteString("Heap[")
|
||||
|
||||
for i, v := range h.data {
|
||||
@@ -236,7 +370,7 @@ func (h Heap[T]) String() string {
|
||||
b.WriteString(", ")
|
||||
}
|
||||
|
||||
b.WriteString(Format("{}", v))
|
||||
fmt.Fprint(&b, v)
|
||||
}
|
||||
|
||||
b.WriteString("]")
|
||||
@@ -251,3 +385,11 @@ func (h *Heap[T]) Print() *Heap[T] { fmt.Print(h); return h }
|
||||
// Println writes the elements of the Heap to the standard output (console) with a newline
|
||||
// and returns the Heap unchanged.
|
||||
func (h *Heap[T]) Println() *Heap[T] { fmt.Println(h); return h }
|
||||
|
||||
// HeapOf creates a new Heap with the given comparison function containing the provided elements.
|
||||
func HeapOf[T any](compareFn func(T, T) cmp.Ordering, values ...T) *Heap[T] {
|
||||
h := NewHeap(compareFn)
|
||||
h.Push(values...)
|
||||
|
||||
return h
|
||||
}
|
||||
-961
@@ -1,961 +0,0 @@
|
||||
package g
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"runtime"
|
||||
|
||||
"github.com/enetx/g/cmp"
|
||||
"github.com/enetx/g/f"
|
||||
"github.com/enetx/iter"
|
||||
)
|
||||
|
||||
// Pull converts the "push-style" iterator sequence seq
|
||||
// into a "pull-style" iterator accessed by the two functions
|
||||
// next and stop.
|
||||
//
|
||||
// Next returns the next value in the sequence
|
||||
// and a boolean indicating whether the value is valid.
|
||||
// When the sequence is over, next returns the zero V and false.
|
||||
// It is valid to call next after reaching the end of the sequence
|
||||
// or after calling stop. These calls will continue
|
||||
// to return the zero V and false.
|
||||
//
|
||||
// Stop ends the iteration. It must be called when the caller is
|
||||
// no longer interested in next values and next has not yet
|
||||
// signaled that the sequence is over (with a false boolean return).
|
||||
// It is valid to call stop multiple times and when next has
|
||||
// already returned false.
|
||||
//
|
||||
// It is an error to call next or stop from multiple goroutines
|
||||
// simultaneously.
|
||||
func (seq SeqHeap[V]) Pull() (func() (V, bool), func()) { return iter.Pull(iter.Seq[V](seq)) }
|
||||
|
||||
// Parallel converts a sequential heap iterator into a parallel iterator with the specified number of workers.
|
||||
// If no worker count is provided, it defaults to the number of CPU cores.
|
||||
// The parallel iterator processes elements concurrently using a worker pool.
|
||||
func (seq SeqHeap[V]) Parallel(workers ...Int) SeqHeapPar[V] {
|
||||
numCPU := Int(runtime.NumCPU())
|
||||
count := Slice[Int](workers).Get(0).UnwrapOr(numCPU)
|
||||
|
||||
if count.Lte(0) {
|
||||
count = numCPU
|
||||
}
|
||||
|
||||
return SeqHeapPar[V]{
|
||||
seq: seq,
|
||||
workers: count,
|
||||
process: func(v V) (V, bool) { return v, true },
|
||||
}
|
||||
}
|
||||
|
||||
// All checks whether all elements in the iterator satisfy the provided condition.
|
||||
// This function is useful when you want to determine if all elements in an iterator
|
||||
// meet a specific criteria.
|
||||
//
|
||||
// Parameters:
|
||||
// - fn func(V) bool: A function that returns a boolean indicating whether the element satisfies
|
||||
// the condition.
|
||||
//
|
||||
// Returns:
|
||||
// - bool: True if all elements in the iterator satisfy the condition, false otherwise.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// heap := g.NewHeap(cmp.Cmp[int])
|
||||
// heap.Push(1, 2, 3, 4, 5, 6, 7, -1, -2)
|
||||
// isPositive := func(num int) bool { return num > 0 }
|
||||
// allPositive := heap.Iter().All(isPositive)
|
||||
//
|
||||
// The resulting allPositive will be true if all elements returned by the iterator are positive.
|
||||
func (seq SeqHeap[V]) All(fn func(v V) bool) bool { return iter.All(iter.Seq[V](seq), fn) }
|
||||
|
||||
// Any checks whether any element in the iterator satisfies the provided condition.
|
||||
// This function is useful when you want to determine if at least one element in an iterator
|
||||
// meets a specific criteria.
|
||||
//
|
||||
// Parameters:
|
||||
// - fn func(V) bool: A function that returns a boolean indicating whether the element satisfies
|
||||
// the condition.
|
||||
//
|
||||
// Returns:
|
||||
// - bool: True if at least one element in the iterator satisfies the condition, false otherwise.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// heap := g.NewHeap(cmp.Cmp[int])
|
||||
// heap.Push(1, 3, 5, 7, 9)
|
||||
// isEven := func(num int) bool { return num%2 == 0 }
|
||||
// anyEven := heap.Iter().Any(isEven)
|
||||
//
|
||||
// The resulting anyEven will be true if at least one element returned by the iterator is even.
|
||||
func (seq SeqHeap[V]) Any(fn func(V) bool) bool { return iter.Any(iter.Seq[V](seq), fn) }
|
||||
|
||||
// Chain concatenates the current iterator with other iterators, returning a new iterator.
|
||||
//
|
||||
// The function creates a new iterator that combines the elements of the current iterator
|
||||
// with elements from the provided iterators in the order they are given.
|
||||
//
|
||||
// Params:
|
||||
//
|
||||
// - seqs ([]SeqHeap[V]): Other iterators to be concatenated with the current iterator.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - SeqHeap[V]: A new iterator containing elements from the current iterator and the provided iterators.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// heap1 := g.NewHeap(cmp.Cmp[int])
|
||||
// heap1.Push(1, 2, 3)
|
||||
// heap2 := g.NewHeap(cmp.Cmp[int])
|
||||
// heap2.Push(4, 5, 6)
|
||||
// heap1.Iter().Chain(heap2.Iter()).Collect() // Creates new heap with all elements
|
||||
//
|
||||
// The resulting iterator will contain elements from both iterators in the specified order.
|
||||
func (seq SeqHeap[V]) Chain(seqs ...SeqHeap[V]) SeqHeap[V] {
|
||||
iterSeqs := make([]iter.Seq[V], len(seqs))
|
||||
for i, s := range seqs {
|
||||
iterSeqs[i] = iter.Seq[V](s)
|
||||
}
|
||||
|
||||
return SeqHeap[V](iter.Chain(iter.Seq[V](seq), iterSeqs...))
|
||||
}
|
||||
|
||||
// Chunks returns an iterator that yields chunks of elements of the specified size.
|
||||
//
|
||||
// The function creates a new iterator that yields chunks of elements from the original iterator,
|
||||
// with each chunk containing elements of the specified size.
|
||||
//
|
||||
// Params:
|
||||
//
|
||||
// - n (Int): The size of each chunk.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - SeqSlices[V]: An iterator yielding chunks of elements of the specified size.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// heap := g.NewHeap(cmp.Cmp[int])
|
||||
// heap.Push(1, 2, 3, 4, 5, 6)
|
||||
// chunks := heap.Iter().Chunks(2).Collect()
|
||||
//
|
||||
// Output: [Slice[1, 2] Slice[3, 4] Slice[5, 6]]
|
||||
//
|
||||
// The resulting iterator will yield chunks of elements, each containing the specified number of elements.
|
||||
func (seq SeqHeap[V]) Chunks(n Int) SeqSlices[V] {
|
||||
return SeqSlices[V](iter.Chunks(iter.Seq[V](seq), int(n)))
|
||||
}
|
||||
|
||||
// Collect gathers all elements from the iterator into a new Heap with a custom comparison function.
|
||||
func (seq SeqHeap[V]) Collect(compareFn func(V, V) cmp.Ordering) *Heap[V] {
|
||||
result := NewHeap(compareFn)
|
||||
seq(func(v V) bool {
|
||||
result.Push(v)
|
||||
return true
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Count consumes the iterator, counting the number of iterations and returning it.
|
||||
func (seq SeqHeap[V]) Count() Int { return Int(iter.Count(iter.Seq[V](seq))) }
|
||||
|
||||
// Counter returns a map where each key is a unique element
|
||||
// from the heap and each value is the count of how many times that element appears.
|
||||
//
|
||||
// The function counts the occurrences of each element in the heap
|
||||
// and returns a map representing the unique elements and their respective counts.
|
||||
// This method uses iter.Counter from the iter package.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - SeqMapOrd[V, Int]: with keys representing the unique elements in the heap
|
||||
// and values representing the counts of those elements.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// heap := g.NewHeap(cmp.Cmp[int])
|
||||
// heap.Push(1, 2, 3, 1, 2, 1)
|
||||
// counts := heap.Iter().Counter()
|
||||
// // The counts map will contain:
|
||||
// // 1 -> 3 (since 1 appears three times)
|
||||
// // 2 -> 2 (since 2 appears two times)
|
||||
// // 3 -> 1 (since 3 appears once)
|
||||
func (seq SeqHeap[V]) Counter() SeqMapOrd[any, Int] {
|
||||
return func(yield func(any, Int) bool) {
|
||||
for k, v := range iter.Counter(iter.Seq[V](seq)) {
|
||||
if !yield(k, Int(v)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GroupBy groups consecutive elements of the sequence based on a custom equality function.
|
||||
//
|
||||
// The provided function `fn` takes two consecutive elements `a` and `b` and returns `true`
|
||||
// if they belong to the same group, or `false` if a new group should start.
|
||||
// The function returns a `SeqSlices[V]`, where each `[]V` represents a group of consecutive
|
||||
// elements that satisfy the provided equality condition.
|
||||
//
|
||||
// Notes:
|
||||
// - Each group is returned as a copy of the elements, since `SeqHeap` does not guarantee
|
||||
// that elements share the same backing array.
|
||||
//
|
||||
// Parameters:
|
||||
// - fn (func(a, b V) bool): Function that determines whether two consecutive elements belong to the same group.
|
||||
//
|
||||
// Returns:
|
||||
// - SeqSlices[V]: An iterator yielding slices, each containing one group.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// heap := g.NewHeap(cmp.Cmp[int])
|
||||
// heap.Push(1, 1, 2, 3, 2, 3, 4)
|
||||
// groups := heap.Iter().GroupBy(func(a, b int) bool { return a <= b }).Collect()
|
||||
// // Output: [Slice[1, 1, 2, 3] Slice[2, 3, 4]]
|
||||
//
|
||||
// The resulting iterator will yield groups of consecutive elements according to the provided function.
|
||||
func (seq SeqHeap[V]) GroupBy(fn func(a, b V) bool) SeqSlices[V] {
|
||||
return SeqSlices[V](iter.GroupByAdjacent(iter.Seq[V](seq), fn))
|
||||
}
|
||||
|
||||
// Combinations generates all combinations of length 'n' from the sequence.
|
||||
func (seq SeqHeap[V]) Combinations(size Int) SeqSlices[V] {
|
||||
return SeqSlices[V](iter.Combinations(iter.Seq[V](seq), int(size)))
|
||||
}
|
||||
|
||||
// Cycle returns an iterator that endlessly repeats the elements of the current sequence.
|
||||
func (seq SeqHeap[V]) Cycle() SeqHeap[V] {
|
||||
return SeqHeap[V](iter.Cycle(iter.Seq[V](seq)))
|
||||
}
|
||||
|
||||
// Enumerate adds an index to each element in the iterator.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - SeqMapOrd[Int, V] An iterator with each element of type Pair[Int, V], where the first
|
||||
// element of the pair is the index and the second element is the original element from the
|
||||
// iterator.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// heap := g.NewHeap(cmp.Cmp[g.String])
|
||||
// heap.Push("bbb", "ddd", "xxx", "aaa", "ccc")
|
||||
// ps := heap.Iter().
|
||||
// Enumerate().
|
||||
// Collect()
|
||||
//
|
||||
// ps.Print()
|
||||
//
|
||||
// Output: MapOrd{0:aaa, 1:bbb, 2:ccc, 3:ddd, 4:xxx}
|
||||
func (seq SeqHeap[V]) Enumerate() SeqMapOrd[Int, V] {
|
||||
return func(yield func(Int, V) bool) {
|
||||
iterEnum := iter.Enumerate(iter.Seq[V](seq), 0)
|
||||
iterEnum(func(i int, v V) bool {
|
||||
return yield(Int(i), v)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Dedup creates a new iterator that removes consecutive duplicate elements from the original iterator,
|
||||
// leaving only one occurrence of each unique element. If the iterator is sorted, all elements will be unique.
|
||||
//
|
||||
// Parameters:
|
||||
// - None
|
||||
//
|
||||
// Returns:
|
||||
// - SeqHeap[V]: A new iterator with consecutive duplicates removed.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// heap := g.NewHeap(cmp.Cmp[int])
|
||||
// heap.Push(1, 2, 2, 3, 4, 4, 4, 5)
|
||||
// iter := heap.Iter().Dedup()
|
||||
// result := iter.CollectWith(cmp.Cmp[int])
|
||||
// result.Iter().ForEach(func(v int) { fmt.Print(v, " ") })
|
||||
//
|
||||
// Output: 1 2 3 4 5
|
||||
//
|
||||
// The resulting iterator will contain only unique elements, removing consecutive duplicates.
|
||||
func (seq SeqHeap[V]) Dedup() SeqHeap[V] {
|
||||
return SeqHeap[V](iter.DedupBy(iter.Seq[V](seq), func(a, b V) bool {
|
||||
if f.IsComparable(a) {
|
||||
return f.Eq[any](a)(b)
|
||||
}
|
||||
return f.Eqd(a)(b)
|
||||
}))
|
||||
}
|
||||
|
||||
// Filter returns a new iterator containing only the elements that satisfy the provided function.
|
||||
//
|
||||
// The function applies the provided function to each element of the iterator.
|
||||
// If the function returns true for an element, that element is included in the resulting iterator.
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// - fn (func(V) bool): The function to be applied to each element of the iterator
|
||||
// to determine if it should be included in the result.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - SeqHeap[V]: A new iterator containing the elements that satisfy the given condition.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// heap := g.NewHeap(cmp.Cmp[int])
|
||||
// heap.Push(1, 2, 3, 4, 5)
|
||||
// even := heap.Iter().
|
||||
// Filter(
|
||||
// func(val int) bool {
|
||||
// return val%2 == 0
|
||||
// }).
|
||||
// CollectWith(cmp.Cmp[int])
|
||||
//
|
||||
// The resulting iterator will contain only the elements that satisfy the provided function.
|
||||
func (seq SeqHeap[V]) Filter(fn func(V) bool) SeqHeap[V] {
|
||||
return SeqHeap[V](iter.Filter(iter.Seq[V](seq), fn))
|
||||
}
|
||||
|
||||
// Exclude returns a new iterator excluding elements that satisfy the provided function.
|
||||
//
|
||||
// The function applies the provided function to each element of the iterator.
|
||||
// If the function returns true for an element, that element is excluded from the resulting iterator.
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// - fn (func(V) bool): The function to be applied to each element of the iterator
|
||||
// to determine if it should be excluded from the result.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - SeqHeap[V]: A new iterator containing the elements that do not satisfy the given condition.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// heap := g.NewHeap(cmp.Cmp[int])
|
||||
// heap.Push(1, 2, 3, 4, 5)
|
||||
// notEven := heap.Iter().
|
||||
// Exclude(
|
||||
// func(val int) bool {
|
||||
// return val%2 == 0
|
||||
// }).
|
||||
// CollectWith(cmp.Cmp[int])
|
||||
//
|
||||
// The resulting iterator will contain only the elements that do not satisfy the provided function.
|
||||
func (seq SeqHeap[V]) Exclude(fn func(V) bool) SeqHeap[V] {
|
||||
return SeqHeap[V](iter.Exclude(iter.Seq[V](seq), fn))
|
||||
}
|
||||
|
||||
// Fold accumulates values in the iterator using a function.
|
||||
//
|
||||
// The function iterates through the elements of the iterator, accumulating values
|
||||
// using the provided function and an initial value.
|
||||
//
|
||||
// Params:
|
||||
//
|
||||
// - init (V): The initial value for accumulation.
|
||||
// - fn (func(V, V) V): The function that accumulates values; it takes two arguments
|
||||
// of type V and returns a value of type V.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - T: The accumulated value after applying the function to all elements.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// heap := g.NewHeap(cmp.Cmp[int])
|
||||
// heap.Push(1, 2, 3, 4, 5)
|
||||
// sum := heap.Iter().
|
||||
// Fold(0,
|
||||
// func(acc, val int) int {
|
||||
// return acc + val
|
||||
// })
|
||||
// fmt.Println(sum)
|
||||
//
|
||||
// Output: 15.
|
||||
//
|
||||
// The resulting value will be the accumulation of elements based on the provided function.
|
||||
func (seq SeqHeap[V]) Fold(init V, fn func(acc, val V) V) V {
|
||||
return iter.Fold(iter.Seq[V](seq), init, fn)
|
||||
}
|
||||
|
||||
// Reduce aggregates elements of the sequence using the provided function.
|
||||
// The first element of the sequence is used as the initial accumulator value.
|
||||
// If the sequence is empty, it returns None[V].
|
||||
//
|
||||
// Params:
|
||||
// - fn (func(V, V) V): Function that combines two values into one.
|
||||
//
|
||||
// Returns:
|
||||
// - Option[V]: The accumulated value wrapped in Some, or None if the sequence is empty.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// heap := g.NewHeap(cmp.Cmp[int])
|
||||
// heap.Push(1, 2, 3, 4, 5)
|
||||
// product := heap.Iter().Reduce(func(a, b int) int { return a * b })
|
||||
// if product.IsSome() {
|
||||
// fmt.Println(product.Some()) // 120
|
||||
// } else {
|
||||
// fmt.Println("empty")
|
||||
// }
|
||||
func (seq SeqHeap[V]) Reduce(fn func(a, b V) V) Option[V] {
|
||||
return OptionOf(iter.Reduce(iter.Seq[V](seq), fn))
|
||||
}
|
||||
|
||||
// ForEach iterates through all elements and applies the given function to each.
|
||||
//
|
||||
// The function applies the provided function to each element of the iterator.
|
||||
//
|
||||
// Params:
|
||||
//
|
||||
// - fn (func(V)): The function to apply to each element.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// heap := g.NewHeap(cmp.Cmp[int])
|
||||
// heap.Push(1, 2, 3, 4, 5)
|
||||
// heap.Iter().ForEach(func(val int) {
|
||||
// fmt.Println(val) // Replace this with the function logic you need.
|
||||
// })
|
||||
//
|
||||
// The provided function will be applied to each element in the iterator.
|
||||
func (seq SeqHeap[V]) ForEach(fn func(v V)) { iter.ForEach(iter.Seq[V](seq), fn) }
|
||||
|
||||
// Flatten flattens an iterator of iterators into a single iterator.
|
||||
//
|
||||
// The function creates a new iterator that flattens a sequence of iterators,
|
||||
// returning a single iterator containing elements from each iterator in sequence.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - SeqHeap[V]: A single iterator containing elements from the sequence of iterators.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// heap := g.NewHeap(cmp.Cmp[any])
|
||||
// heap.Push(
|
||||
// 1,
|
||||
// g.SliceOf(2, 3),
|
||||
// "abc",
|
||||
// g.SliceOf("def", "ghi"),
|
||||
// g.SliceOf(4.5, 6.7),
|
||||
// )
|
||||
//
|
||||
// heap.Iter().Flatten().ForEach(func(v any) { fmt.Print(v, " ") })
|
||||
//
|
||||
// Output: 1 2 3 abc def ghi 4.5 6.7
|
||||
//
|
||||
// The resulting iterator will contain elements from each iterator in sequence.
|
||||
func (seq SeqHeap[V]) Flatten() SeqHeap[V] {
|
||||
return func(yield func(V) bool) {
|
||||
var flatten func(item any) bool
|
||||
flatten = func(item any) bool {
|
||||
rv := reflect.ValueOf(item)
|
||||
switch rv.Kind() {
|
||||
case reflect.Slice, reflect.Array:
|
||||
for i := range rv.Len() {
|
||||
if !flatten(rv.Index(i).Interface()) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
default:
|
||||
if v, ok := item.(V); ok {
|
||||
if !yield(v) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
seq(func(item V) bool {
|
||||
return flatten(item)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Inspect creates a new iterator that wraps around the current iterator
|
||||
// and allows inspecting each element as it passes through.
|
||||
func (seq SeqHeap[V]) Inspect(fn func(v V)) SeqHeap[V] {
|
||||
return SeqHeap[V](iter.Inspect(iter.Seq[V](seq), fn))
|
||||
}
|
||||
|
||||
// Intersperse inserts the provided separator between elements of the iterator.
|
||||
//
|
||||
// The function creates a new iterator that inserts the given separator between each
|
||||
// consecutive pair of elements in the original iterator.
|
||||
//
|
||||
// Params:
|
||||
//
|
||||
// - sep (V): The separator to intersperse between elements.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - SeqHeap[V]: An iterator containing elements with the separator interspersed.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// heap := g.NewHeap(cmp.Cmp[string])
|
||||
// heap.Push("Hello", "World", "!")
|
||||
// heap.Iter().
|
||||
// Intersperse(" ").
|
||||
// ForEach(func(s string) { fmt.Print(s) })
|
||||
//
|
||||
// Output: "! Hello World".
|
||||
//
|
||||
// The resulting iterator will contain elements with the separator interspersed.
|
||||
func (seq SeqHeap[V]) Intersperse(sep V) SeqHeap[V] {
|
||||
return SeqHeap[V](iter.Intersperse(iter.Seq[V](seq), sep))
|
||||
}
|
||||
|
||||
// Map transforms each element in the iterator using the given function.
|
||||
//
|
||||
// The function creates a new iterator by applying the provided function to each element
|
||||
// of the original iterator.
|
||||
//
|
||||
// Params:
|
||||
//
|
||||
// - fn (func(V) V): The function used to transform elements.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - SeqHeap[V]: A iterator containing elements transformed by the provided function.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// heap := g.NewHeap(cmp.Cmp[int])
|
||||
// heap.Push(1, 2, 3)
|
||||
// doubled := heap.
|
||||
// Iter().
|
||||
// Map(
|
||||
// func(val int) int {
|
||||
// return val * 2
|
||||
// }).
|
||||
// CollectWith(cmp.Cmp[int])
|
||||
//
|
||||
// The resulting iterator will contain elements transformed by the provided function.
|
||||
func (seq SeqHeap[V]) Map(transform func(V) V) SeqHeap[V] {
|
||||
return SeqHeap[V](iter.Map(iter.Seq[V](seq), transform))
|
||||
}
|
||||
|
||||
// Partition divides the elements of the iterator into two separate heaps with custom comparison functions.
|
||||
func (seq SeqHeap[V]) Partition(fn func(v V) bool, leftCmp, rightCmp func(V, V) cmp.Ordering) (*Heap[V], *Heap[V]) {
|
||||
left := NewHeap(leftCmp)
|
||||
right := NewHeap(rightCmp)
|
||||
|
||||
seq(func(v V) bool {
|
||||
if fn(v) {
|
||||
left.Push(v)
|
||||
} else {
|
||||
right.Push(v)
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return left, right
|
||||
}
|
||||
|
||||
// Permutations generates iterators of all permutations of elements.
|
||||
//
|
||||
// The function uses a recursive approach to generate all the permutations of the elements.
|
||||
// If the iterator is empty or contains a single element, it returns the iterator itself
|
||||
// wrapped in a single-element iterator.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - SeqSlices[V]: An iterator of iterators containing all possible permutations of the
|
||||
// elements in the iterator.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// heap := g.NewHeap(cmp.Cmp[int])
|
||||
// heap.Push(1, 2, 3)
|
||||
// perms := heap.Iter().Permutations().Collect()
|
||||
// for _, perm := range perms {
|
||||
// fmt.Println(perm)
|
||||
// }
|
||||
//
|
||||
// Output:
|
||||
// Slice[1, 2, 3]
|
||||
// Slice[2, 1, 3]
|
||||
// Slice[3, 1, 2]
|
||||
// Slice[1, 3, 2]
|
||||
// Slice[2, 3, 1]
|
||||
// Slice[3, 2, 1]
|
||||
//
|
||||
// The resulting iterator will contain iterators representing all possible permutations
|
||||
// of the elements in the original iterator.
|
||||
func (seq SeqHeap[V]) Permutations() SeqSlices[V] {
|
||||
return SeqSlices[V](iter.Permutations(iter.Seq[V](seq)))
|
||||
}
|
||||
|
||||
// Range iterates through elements until the given function returns false.
|
||||
//
|
||||
// The function iterates through the elements of the iterator and applies the provided function
|
||||
// to each element. It stops iteration when the function returns false for an element.
|
||||
//
|
||||
// Params:
|
||||
//
|
||||
// - fn (func(V) bool): The function that evaluates elements for continuation of iteration.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// heap := g.NewHeap(cmp.Cmp[int])
|
||||
// heap.Push(1, 2, 3, 4, 5)
|
||||
// heap.Iter().Range(func(val int) bool {
|
||||
// fmt.Println(val) // Replace this with the function logic you need.
|
||||
// return val < 5 // Replace this with the condition for continuing iteration.
|
||||
// })
|
||||
//
|
||||
// The iteration will stop when the provided function returns false for an element.
|
||||
func (seq SeqHeap[V]) Range(fn func(v V) bool) { iter.Range(iter.Seq[V](seq), fn) }
|
||||
|
||||
// Skip returns a new iterator skipping the first n elements.
|
||||
//
|
||||
// The function creates a new iterator that skips the first n elements of the current iterator
|
||||
// and returns an iterator starting from the (n+1)th element.
|
||||
//
|
||||
// Params:
|
||||
//
|
||||
// - n (uint): The number of elements to skip from the beginning of the iterator.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - SeqHeap[V]: An iterator that starts after skipping the first n elements.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// heap := g.NewHeap(cmp.Cmp[int])
|
||||
// heap.Push(1, 2, 3, 4, 5, 6)
|
||||
// heap.Iter().Skip(3).ForEach(func(v int) { fmt.Print(v, " ") })
|
||||
//
|
||||
// Output: 4 5 6
|
||||
//
|
||||
// The resulting iterator will start after skipping the specified number of elements.
|
||||
func (seq SeqHeap[V]) Skip(n uint) SeqHeap[V] {
|
||||
return SeqHeap[V](iter.Skip(iter.Seq[V](seq), int(n)))
|
||||
}
|
||||
|
||||
// StepBy creates a new iterator that iterates over every N-th element of the original iterator.
|
||||
// This function is useful when you want to skip a specific number of elements between each iteration.
|
||||
//
|
||||
// Parameters:
|
||||
// - n uint: The step size, indicating how many elements to skip between each iteration.
|
||||
//
|
||||
// Returns:
|
||||
// - SeqHeap[V]: A new iterator that produces elements from the original iterator with a step size of N.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// heap := g.NewHeap(cmp.Cmp[int])
|
||||
// heap.Push(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
|
||||
// heap.Iter().StepBy(3).ForEach(func(v int) { fmt.Print(v, " ") })
|
||||
//
|
||||
// Output: 1 4 7 10
|
||||
//
|
||||
// The resulting iterator will produce elements from the original iterator with a step size of N.
|
||||
func (seq SeqHeap[V]) StepBy(n uint) SeqHeap[V] {
|
||||
return SeqHeap[V](iter.StepBy(iter.Seq[V](seq), int(n)))
|
||||
}
|
||||
|
||||
// SortBy applies a custom sorting function to the elements in the iterator
|
||||
// and returns a new iterator containing the sorted elements.
|
||||
//
|
||||
// The sorting function 'fn' should take two arguments, 'a' and 'b' of type V,
|
||||
// and return the ordering between them.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// heap := g.NewHeap(cmp.Cmp[string])
|
||||
// heap.Push("a", "c", "b")
|
||||
// heap.Iter().
|
||||
// SortBy(func(a, b string) cmp.Ordering { return cmp.Cmp(b, a) }).
|
||||
// ForEach(func(s string) { fmt.Print(s, " ") })
|
||||
//
|
||||
// Output: c b a
|
||||
//
|
||||
// The returned iterator is of type SeqHeap[V], which implements the iterator
|
||||
// interface for further iteration over the sorted elements.
|
||||
func (seq SeqHeap[V]) SortBy(fn func(a, b V) cmp.Ordering) SeqHeap[V] {
|
||||
return SeqHeap[V](iter.SortBy(iter.Seq[V](seq), func(a, b V) bool { return fn(a, b) == cmp.Less }))
|
||||
}
|
||||
|
||||
// Take returns a new iterator with the first n elements.
|
||||
// The function creates a new iterator containing the first n elements from the original iterator.
|
||||
func (seq SeqHeap[V]) Take(n uint) SeqHeap[V] {
|
||||
return SeqHeap[V](iter.Take(iter.Seq[V](seq), int(n)))
|
||||
}
|
||||
|
||||
// First returns the first element from the sequence.
|
||||
func (seq SeqHeap[V]) First() Option[V] {
|
||||
return OptionOf(iter.First(iter.Seq[V](seq)))
|
||||
}
|
||||
|
||||
// Last returns the last element from the sequence.
|
||||
func (seq SeqHeap[V]) Last() Option[V] {
|
||||
return OptionOf(iter.Last(iter.Seq[V](seq)))
|
||||
}
|
||||
|
||||
// Nth returns the nth element (0-indexed) in the sequence.
|
||||
func (seq SeqHeap[V]) Nth(n Int) Option[V] {
|
||||
return OptionOf(iter.Nth(iter.Seq[V](seq), int(n)))
|
||||
}
|
||||
|
||||
// ToChan converts the iterator into a channel, optionally with context(s).
|
||||
//
|
||||
// The function converts the elements of the iterator into a channel for streaming purposes.
|
||||
// Optionally, it accepts context(s) to handle cancellation or timeout scenarios.
|
||||
//
|
||||
// Params:
|
||||
//
|
||||
// - ctxs (context.Context): Optional context(s) to control the channel behavior (e.g., cancellation).
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - chan V: A channel containing the elements from the iterator.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// heap := g.NewHeap(cmp.Cmp[int])
|
||||
// heap.Push(1, 2, 3)
|
||||
// ctx, cancel := context.WithCancel(context.Background())
|
||||
// defer cancel() // Ensure cancellation to avoid goroutine leaks.
|
||||
// ch := heap.Iter().ToChan(ctx)
|
||||
// for val := range ch {
|
||||
// fmt.Println(val)
|
||||
// }
|
||||
//
|
||||
// The resulting channel allows streaming elements from the iterator with optional context handling.
|
||||
func (seq SeqHeap[V]) ToChan(ctxs ...context.Context) chan V {
|
||||
ctx := context.Background()
|
||||
if len(ctxs) > 0 {
|
||||
ctx = ctxs[0]
|
||||
}
|
||||
|
||||
return iter.ToChan(iter.Seq[V](seq), ctx)
|
||||
}
|
||||
|
||||
// Unique returns an iterator with only unique elements.
|
||||
//
|
||||
// The function returns an iterator containing only the unique elements from the original iterator.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - SeqHeap[V]: An iterator containing unique elements from the original iterator.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// heap := g.NewHeap(cmp.Cmp[int])
|
||||
// heap.Push(1, 2, 3, 2, 4, 5, 3)
|
||||
// heap.Iter().Unique().ForEach(func(v int) { fmt.Print(v, " ") })
|
||||
//
|
||||
// Output: 1 2 3 4 5
|
||||
//
|
||||
// The resulting iterator will contain only unique elements from the original iterator.
|
||||
func (seq SeqHeap[V]) Unique() SeqHeap[V] {
|
||||
return SeqHeap[V](iter.Unique(iter.Seq[V](seq)))
|
||||
}
|
||||
|
||||
// Zip combines elements from the current sequence and another sequence into pairs,
|
||||
// creating an ordered map with identical keys and values of type V.
|
||||
func (seq SeqHeap[V]) Zip(two SeqHeap[V]) SeqMapOrd[any, any] {
|
||||
return func(yield func(any, any) bool) {
|
||||
zipSeq := iter.Zip(iter.Seq[V](seq), iter.Seq[V](two))
|
||||
zipSeq(func(a, b V) bool {
|
||||
return yield(a, b)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Find searches for an element in the iterator that satisfies the provided function.
|
||||
//
|
||||
// The function iterates through the elements of the iterator and returns the first element
|
||||
// for which the provided function returns true.
|
||||
//
|
||||
// Params:
|
||||
//
|
||||
// - fn (func(V) bool): The function used to test elements for a condition.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - Option[V]: An Option containing the first element that satisfies the condition; None if not found.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// heap := g.NewHeap(cmp.Cmp[int])
|
||||
// heap.Push(1, 2, 3, 4, 5)
|
||||
// found := heap.Iter().Find(
|
||||
// func(i int) bool {
|
||||
// return i == 2
|
||||
// })
|
||||
//
|
||||
// if found.IsSome() {
|
||||
// fmt.Println("Found:", found.Some())
|
||||
// } else {
|
||||
// fmt.Println("Not found.")
|
||||
// }
|
||||
//
|
||||
// The resulting Option may contain the first element that satisfies the condition, or None if not found.
|
||||
func (seq SeqHeap[V]) Find(fn func(v V) bool) Option[V] {
|
||||
return OptionOf(iter.Find(iter.Seq[V](seq), fn))
|
||||
}
|
||||
|
||||
// Windows returns an iterator that yields sliding windows of elements of the specified size.
|
||||
//
|
||||
// The function creates a new iterator that yields windows of elements from the original iterator,
|
||||
// where each window is a slice containing elements of the specified size and moves one element at a time.
|
||||
//
|
||||
// Params:
|
||||
//
|
||||
// - n (int): The size of each window.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - SeqSlices[V]: An iterator yielding sliding windows of elements of the specified size.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// heap := g.NewHeap(cmp.Cmp[int])
|
||||
// heap.Push(1, 2, 3, 4, 5, 6)
|
||||
// windows := heap.Iter().Windows(3).Collect()
|
||||
//
|
||||
// Output: [Slice[1, 2, 3] Slice[2, 3, 4] Slice[3, 4, 5] Slice[4, 5, 6]]
|
||||
//
|
||||
// The resulting iterator will yield sliding windows of elements, each containing the specified number of elements.
|
||||
func (seq SeqHeap[V]) Windows(n Int) SeqSlices[V] {
|
||||
return SeqSlices[V](iter.Windows(iter.Seq[V](seq), int(n)))
|
||||
}
|
||||
|
||||
// Context allows the iteration to be controlled with a context.Context.
|
||||
func (seq SeqHeap[V]) Context(ctx context.Context) SeqHeap[V] {
|
||||
return SeqHeap[V](iter.Context(iter.Seq[V](seq), ctx))
|
||||
}
|
||||
|
||||
// MaxBy returns the maximum element in the sequence using the provided comparison function.
|
||||
func (seq SeqHeap[V]) MaxBy(fn func(V, V) cmp.Ordering) Option[V] {
|
||||
return OptionOf(iter.MaxBy(iter.Seq[V](seq), func(a, b V) bool { return fn(a, b) == cmp.Less }))
|
||||
}
|
||||
|
||||
// MinBy returns the minimum element in the sequence using the provided comparison function.
|
||||
func (seq SeqHeap[V]) MinBy(fn func(V, V) cmp.Ordering) Option[V] {
|
||||
return OptionOf(iter.MinBy(iter.Seq[V](seq), func(a, b V) bool { return fn(a, b) == cmp.Less }))
|
||||
}
|
||||
|
||||
// Eq checks whether two heap sequences are equal.
|
||||
func (seq SeqHeap[T]) Eq(other SeqHeap[T]) bool {
|
||||
return iter.Equal(iter.Seq[T](seq), iter.Seq[T](other))
|
||||
}
|
||||
|
||||
// FlatMap applies a function to each element and flattens the results into a single sequence.
|
||||
//
|
||||
// The function transforms each element into a new SeqHeap and then flattens all resulting
|
||||
// sequences into a single sequence.
|
||||
//
|
||||
// Params:
|
||||
//
|
||||
// - fn (func(V) SeqHeap[V]): The function that transforms each element into a SeqHeap.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - SeqHeap[V]: A flattened sequence containing all elements from the transformed sequences.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// heap := g.NewHeap(cmp.Cmp[int])
|
||||
// heap.Push(1, 2, 3)
|
||||
// result := heap.Iter().FlatMap(func(n int) g.SeqHeap[int] {
|
||||
// subHeap := g.NewHeap(cmp.Cmp[int])
|
||||
// subHeap.Push(n, n*10)
|
||||
// return subHeap.Iter()
|
||||
// }).CollectWith(cmp.Cmp[int])
|
||||
// // result contains: 1, 10, 2, 20, 3, 30 (order depends on heap implementation)
|
||||
func (seq SeqHeap[V]) FlatMap(fn func(V) SeqHeap[V]) SeqHeap[V] {
|
||||
mapped := iter.MapTo(iter.Seq[V](seq), func(v V) iter.Seq[V] {
|
||||
return iter.Seq[V](fn(v))
|
||||
})
|
||||
return SeqHeap[V](iter.FlattenSeq(mapped))
|
||||
}
|
||||
|
||||
// FilterMap applies a function to each element and filters out None results.
|
||||
//
|
||||
// The function transforms and filters elements in a single pass. Elements where the function
|
||||
// returns None are filtered out, and elements where it returns Some are unwrapped
|
||||
// and included in the result.
|
||||
//
|
||||
// Params:
|
||||
//
|
||||
// - fn (func(V) Option[V]): The function that transforms and filters elements.
|
||||
// Returns Some(value) to include the transformed element, or None to filter it out.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - SeqHeap[V]: A sequence containing only the successfully transformed elements.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// heap := g.NewHeap(cmp.Cmp[int])
|
||||
// heap.Push(1, 2, 3, 4, 5)
|
||||
// result := heap.Iter().FilterMap(func(n int) g.Option[int] {
|
||||
// if n%2 == 0 {
|
||||
// return g.Some(n * 10)
|
||||
// }
|
||||
// return g.None[int]()
|
||||
// }).CollectWith(cmp.Cmp[int])
|
||||
// // result contains only even numbers multiplied by 10
|
||||
func (seq SeqHeap[V]) FilterMap(fn func(V) Option[V]) SeqHeap[V] {
|
||||
return SeqHeap[V](iter.FilterMap(iter.Seq[V](seq), func(v V) (V, bool) {
|
||||
return fn(v).Option()
|
||||
}))
|
||||
}
|
||||
|
||||
// Scan applies a function to each element and produces a sequence of successive accumulated results.
|
||||
//
|
||||
// The function takes an initial value and applies the provided function to each element along
|
||||
// with the accumulated value, producing a new sequence where each element is the result of
|
||||
// the accumulation. The initial value is included as the first element.
|
||||
//
|
||||
// Params:
|
||||
//
|
||||
// - init (V): The initial value for the accumulation.
|
||||
// - fn (func(acc, val V) V): The function that combines the accumulator with each element.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - SeqHeap[V]: A sequence containing the initial value and all accumulated results.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// heap := g.NewHeap(cmp.Cmp[int])
|
||||
// heap.Push(1, 2, 3, 4, 5)
|
||||
// result := heap.Iter().Scan(0, func(acc, val int) int {
|
||||
// return acc + val
|
||||
// }).CollectWith(cmp.Cmp[int])
|
||||
// // result contains: 0, plus cumulative sums of heap elements
|
||||
func (seq SeqHeap[V]) Scan(init V, fn func(acc, val V) V) SeqHeap[V] {
|
||||
return func(yield func(V) bool) {
|
||||
if !yield(init) {
|
||||
return
|
||||
}
|
||||
iter.Scan(iter.Seq[V](seq), init, fn)(yield)
|
||||
}
|
||||
}
|
||||
|
||||
// Next extracts the next element from the iterator and advances it.
|
||||
//
|
||||
// This method consumes the next element from the iterator and returns it wrapped in an Option.
|
||||
// The iterator itself is modified to point to the remaining elements.
|
||||
//
|
||||
// Returns:
|
||||
// - Option[V]: Some(value) if an element exists, None if the iterator is exhausted.
|
||||
func (seq *SeqHeap[V]) Next() Option[V] {
|
||||
if value, remaining, ok := iter.Next(iter.Seq[V](*seq)); ok {
|
||||
*seq = SeqHeap[V](remaining)
|
||||
return Some(value)
|
||||
}
|
||||
|
||||
return None[V]()
|
||||
}
|
||||
-672
@@ -1,672 +0,0 @@
|
||||
package g
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/enetx/g/cmp"
|
||||
)
|
||||
|
||||
// All returns true only if fn returns true for every element.
|
||||
// It stops early on the first false.
|
||||
func (p SeqHeapPar[V]) All(fn func(V) bool) bool {
|
||||
var ok atomic.Bool
|
||||
ok.Store(true)
|
||||
|
||||
p.Range(func(v V) bool {
|
||||
if !fn(v) {
|
||||
ok.Store(false)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return ok.Load()
|
||||
}
|
||||
|
||||
// Any returns true if fn returns true for any element.
|
||||
// It stops early on the first true.
|
||||
func (p SeqHeapPar[V]) Any(fn func(V) bool) bool {
|
||||
var ok atomic.Bool
|
||||
|
||||
p.Range(func(v V) bool {
|
||||
if fn(v) {
|
||||
ok.Store(true)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return ok.Load()
|
||||
}
|
||||
|
||||
// Chain concatenates this SeqHeapPar with others, preserving full parallelism.
|
||||
// Each sequence runs with its own worker pool in parallel.
|
||||
func (p SeqHeapPar[V]) Chain(others ...SeqHeapPar[V]) SeqHeapPar[V] {
|
||||
return SeqHeapPar[V]{
|
||||
seq: func(yield func(V) bool) {
|
||||
done := make(chan struct{})
|
||||
result := make(chan V, int(p.workers)*4)
|
||||
|
||||
var (
|
||||
wg sync.WaitGroup
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
runSequence := func(seq SeqHeapPar[V]) {
|
||||
defer wg.Done()
|
||||
seq.Range(func(v V) bool {
|
||||
select {
|
||||
case <-done:
|
||||
return false
|
||||
case result <- v:
|
||||
return true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer close(result)
|
||||
|
||||
wg.Add(1)
|
||||
go runSequence(p)
|
||||
|
||||
for _, o := range others {
|
||||
wg.Add(1)
|
||||
go runSequence(o)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
case v, ok := <-result:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if !yield(v) {
|
||||
once.Do(func() { close(done) })
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
workers: p.workers,
|
||||
process: func(v V) (V, bool) { return v, true },
|
||||
}
|
||||
}
|
||||
|
||||
// Collect gathers all processed elements into a Heap with a custom comparison function.
|
||||
func (p SeqHeapPar[V]) Collect(compareFn func(V, V) cmp.Ordering) *Heap[V] {
|
||||
ch := make(chan V)
|
||||
|
||||
go func() {
|
||||
defer close(ch)
|
||||
p.Range(func(v V) bool {
|
||||
ch <- v
|
||||
return true
|
||||
})
|
||||
}()
|
||||
|
||||
result := NewHeap(compareFn)
|
||||
for v := range ch {
|
||||
result.Push(v)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Count returns the total number of elements processed.
|
||||
func (p SeqHeapPar[V]) Count() Int {
|
||||
var count atomic.Int64
|
||||
p.Range(func(V) bool {
|
||||
count.Add(1)
|
||||
return true
|
||||
})
|
||||
|
||||
return Int(count.Load())
|
||||
}
|
||||
|
||||
// Exclude removes elements for which fn returns true, in parallel.
|
||||
func (p SeqHeapPar[V]) Exclude(fn func(V) bool) SeqHeapPar[V] {
|
||||
return p.Filter(func(v V) bool { return !fn(v) })
|
||||
}
|
||||
|
||||
// Filter retains only elements where fn returns true.
|
||||
func (p SeqHeapPar[V]) Filter(fn func(V) bool) SeqHeapPar[V] {
|
||||
prev := p.process
|
||||
|
||||
return SeqHeapPar[V]{
|
||||
seq: p.seq,
|
||||
workers: p.workers,
|
||||
process: func(v V) (V, bool) {
|
||||
if mid, ok := prev(v); ok && fn(mid) {
|
||||
return mid, true
|
||||
}
|
||||
var zero V
|
||||
return zero, false
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// FlatMap applies fn to each element in parallel, flattening the resulting sequences.
|
||||
func (p SeqHeapPar[V]) FlatMap(fn func(V) SeqHeap[V]) SeqHeapPar[V] {
|
||||
return SeqHeapPar[V]{
|
||||
seq: func(yield func(V) bool) {
|
||||
done := make(chan struct{})
|
||||
result := make(chan V, 100)
|
||||
|
||||
var (
|
||||
wg sync.WaitGroup
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
go func() {
|
||||
defer close(result)
|
||||
|
||||
p.Range(func(v V) bool {
|
||||
select {
|
||||
case <-done:
|
||||
return false
|
||||
default:
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
go func(val V) {
|
||||
defer wg.Done()
|
||||
fn(val)(func(item V) bool {
|
||||
select {
|
||||
case <-done:
|
||||
return false
|
||||
case result <- item:
|
||||
return true
|
||||
}
|
||||
})
|
||||
}(v)
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
wg.Wait()
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
case v, ok := <-result:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if !yield(v) {
|
||||
once.Do(func() { close(done) })
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
workers: p.workers,
|
||||
process: func(v V) (V, bool) { return v, true },
|
||||
}
|
||||
}
|
||||
|
||||
// FilterMap applies fn to each element in parallel, keeping only Some values.
|
||||
func (p SeqHeapPar[V]) FilterMap(fn func(V) Option[V]) SeqHeapPar[V] {
|
||||
prev := p.process
|
||||
|
||||
return SeqHeapPar[V]{
|
||||
seq: p.seq,
|
||||
workers: p.workers,
|
||||
process: func(v V) (V, bool) {
|
||||
if mid, ok := prev(v); ok {
|
||||
if opt := fn(mid); opt.IsSome() {
|
||||
return opt.Some(), true
|
||||
}
|
||||
}
|
||||
var zero V
|
||||
return zero, false
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// StepBy yields every nth element.
|
||||
func (p SeqHeapPar[V]) StepBy(n uint) SeqHeapPar[V] {
|
||||
if n == 0 {
|
||||
n = 1
|
||||
}
|
||||
|
||||
prev := p.process
|
||||
counter := &atomic.Uint64{}
|
||||
|
||||
return SeqHeapPar[V]{
|
||||
seq: p.seq,
|
||||
workers: p.workers,
|
||||
process: func(v V) (V, bool) {
|
||||
if mid, ok := prev(v); ok {
|
||||
count := counter.Add(1)
|
||||
if (count-1)%uint64(n) == 0 {
|
||||
return mid, true
|
||||
}
|
||||
}
|
||||
var zero V
|
||||
return zero, false
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// MaxBy returns the maximum element according to the comparison function.
|
||||
func (p SeqHeapPar[V]) MaxBy(fn func(V, V) cmp.Ordering) Option[V] {
|
||||
ch := make(chan V)
|
||||
|
||||
go func() {
|
||||
defer close(ch)
|
||||
p.Range(func(v V) bool {
|
||||
ch <- v
|
||||
return true
|
||||
})
|
||||
}()
|
||||
|
||||
var max V
|
||||
hasMax := false
|
||||
|
||||
for v := range ch {
|
||||
if !hasMax {
|
||||
max = v
|
||||
hasMax = true
|
||||
} else if fn(v, max).IsGt() {
|
||||
max = v
|
||||
}
|
||||
}
|
||||
|
||||
if hasMax {
|
||||
return Some(max)
|
||||
}
|
||||
return None[V]()
|
||||
}
|
||||
|
||||
// MinBy returns the minimum element according to the comparison function.
|
||||
func (p SeqHeapPar[V]) MinBy(fn func(V, V) cmp.Ordering) Option[V] {
|
||||
ch := make(chan V)
|
||||
|
||||
go func() {
|
||||
defer close(ch)
|
||||
p.Range(func(v V) bool {
|
||||
ch <- v
|
||||
return true
|
||||
})
|
||||
}()
|
||||
|
||||
var min V
|
||||
hasMin := false
|
||||
|
||||
for v := range ch {
|
||||
if !hasMin {
|
||||
min = v
|
||||
hasMin = true
|
||||
} else if fn(v, min).IsLt() {
|
||||
min = v
|
||||
}
|
||||
}
|
||||
|
||||
if hasMin {
|
||||
return Some(min)
|
||||
}
|
||||
return None[V]()
|
||||
}
|
||||
|
||||
// Find returns the first element satisfying fn, or None if no such element exists.
|
||||
func (p SeqHeapPar[V]) Find(fn func(V) bool) Option[V] {
|
||||
ch := make(chan V)
|
||||
|
||||
go func() {
|
||||
defer close(ch)
|
||||
p.Range(func(v V) bool {
|
||||
if fn(v) {
|
||||
ch <- v
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
}()
|
||||
|
||||
if v, ok := <-ch; ok {
|
||||
return Some(v)
|
||||
}
|
||||
|
||||
return None[V]()
|
||||
}
|
||||
|
||||
// Fold reduces all elements into a single value, using fn to accumulate results.
|
||||
// Note: This collects all processed elements first, then folds sequentially.
|
||||
// The parallel processing happens during the Range phase.
|
||||
func (p SeqHeapPar[V]) Fold(init V, fn func(acc, v V) V) V {
|
||||
ch := make(chan V)
|
||||
|
||||
go func() {
|
||||
defer close(ch)
|
||||
p.Range(func(v V) bool {
|
||||
ch <- v
|
||||
return true
|
||||
})
|
||||
}()
|
||||
|
||||
acc := init
|
||||
for v := range ch {
|
||||
acc = fn(acc, v)
|
||||
}
|
||||
|
||||
return acc
|
||||
}
|
||||
|
||||
// Flatten unpacks nested slices or arrays in the source, returning a flat parallel sequence.
|
||||
func (p SeqHeapPar[V]) Flatten() SeqHeapPar[V] {
|
||||
seq := func(yield func(V) bool) {
|
||||
var recurse func(any) bool
|
||||
|
||||
recurse = func(item any) bool {
|
||||
if item == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
rv := reflect.ValueOf(item)
|
||||
|
||||
if !rv.IsValid() {
|
||||
return true
|
||||
}
|
||||
|
||||
switch rv.Kind() {
|
||||
case reflect.Slice, reflect.Array:
|
||||
if rv.IsNil() {
|
||||
return true
|
||||
}
|
||||
|
||||
for i := range rv.Len() {
|
||||
elem := rv.Index(i)
|
||||
|
||||
if !elem.CanInterface() {
|
||||
continue
|
||||
}
|
||||
|
||||
if !recurse(elem.Interface()) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
default:
|
||||
if v, ok := item.(V); ok {
|
||||
if !yield(v) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
resultsChan := make(chan V, 100)
|
||||
doneChan := make(chan struct{})
|
||||
var once sync.Once
|
||||
|
||||
go func() {
|
||||
defer close(resultsChan)
|
||||
|
||||
p.Range(func(v V) bool {
|
||||
select {
|
||||
case <-doneChan:
|
||||
return false
|
||||
default:
|
||||
}
|
||||
|
||||
flattenedItems := flattenToSlice(v)
|
||||
for _, item := range flattenedItems {
|
||||
if flatItem, ok := item.(V); ok {
|
||||
select {
|
||||
case resultsChan <- flatItem:
|
||||
case <-doneChan:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case v, ok := <-resultsChan:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if !yield(v) {
|
||||
once.Do(func() { close(doneChan) })
|
||||
return
|
||||
}
|
||||
case <-doneChan:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return SeqHeapPar[V]{
|
||||
seq: seq,
|
||||
workers: p.workers,
|
||||
process: func(v V) (V, bool) { return v, true },
|
||||
}
|
||||
}
|
||||
|
||||
// Reduce aggregates elements of the parallel sequence using the provided function.
|
||||
// The first received element is used as the initial accumulator.
|
||||
// If the sequence is empty, returns None[V].
|
||||
// Note: This collects all processed elements first, then reduces sequentially.
|
||||
// The parallel processing happens during the Range phase.
|
||||
func (p SeqHeapPar[V]) Reduce(fn func(a, b V) V) Option[V] {
|
||||
ch := make(chan V)
|
||||
|
||||
go func() {
|
||||
defer close(ch)
|
||||
p.Range(func(v V) bool {
|
||||
ch <- v
|
||||
return true
|
||||
})
|
||||
}()
|
||||
|
||||
var (
|
||||
acc V
|
||||
first = true
|
||||
)
|
||||
|
||||
for v := range ch {
|
||||
if first {
|
||||
acc = v
|
||||
first = false
|
||||
continue
|
||||
}
|
||||
|
||||
acc = fn(acc, v)
|
||||
}
|
||||
|
||||
if first {
|
||||
return None[V]()
|
||||
}
|
||||
|
||||
return Some(acc)
|
||||
}
|
||||
|
||||
// ForEach applies fn to each element without early exit.
|
||||
func (p SeqHeapPar[V]) ForEach(fn func(V)) {
|
||||
p.Range(func(v V) bool {
|
||||
fn(v)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// Inspect invokes fn on each element without altering the resulting sequence.
|
||||
func (p SeqHeapPar[V]) Inspect(fn func(V)) SeqHeapPar[V] {
|
||||
prev := p.process
|
||||
|
||||
return SeqHeapPar[V]{
|
||||
seq: p.seq,
|
||||
workers: p.workers,
|
||||
process: func(x V) (V, bool) {
|
||||
if mid, ok := prev(x); ok {
|
||||
fn(mid)
|
||||
return mid, true
|
||||
}
|
||||
var zero V
|
||||
return zero, false
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Map applies fn to each element.
|
||||
func (p SeqHeapPar[V]) Map(fn func(V) V) SeqHeapPar[V] {
|
||||
prev := p.process
|
||||
|
||||
return SeqHeapPar[V]{
|
||||
seq: p.seq,
|
||||
workers: p.workers,
|
||||
process: func(v V) (V, bool) {
|
||||
if mid, ok := prev(v); ok {
|
||||
return fn(mid), true
|
||||
}
|
||||
var zero V
|
||||
return zero, false
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Partition partitions elements using custom comparison functions for each heap.
|
||||
func (p SeqHeapPar[V]) Partition(fn func(V) bool, leftCmp, rightCmp func(V, V) cmp.Ordering) (*Heap[V], *Heap[V]) {
|
||||
type item struct {
|
||||
value V
|
||||
isLeft bool
|
||||
}
|
||||
|
||||
ch := make(chan item)
|
||||
|
||||
go func() {
|
||||
defer close(ch)
|
||||
p.Range(func(v V) bool {
|
||||
ch <- item{
|
||||
value: v,
|
||||
isLeft: fn(v),
|
||||
}
|
||||
return true
|
||||
})
|
||||
}()
|
||||
|
||||
left := NewHeap(leftCmp)
|
||||
right := NewHeap(rightCmp)
|
||||
|
||||
for it := range ch {
|
||||
if it.isLeft {
|
||||
left.Push(it.value)
|
||||
} else {
|
||||
right.Push(it.value)
|
||||
}
|
||||
}
|
||||
|
||||
return left, right
|
||||
}
|
||||
|
||||
// Range applies fn to each processed element in parallel, stopping on false.
|
||||
func (p SeqHeapPar[V]) Range(fn func(V) bool) {
|
||||
in := make(chan V)
|
||||
done := make(chan struct{})
|
||||
|
||||
var (
|
||||
wg sync.WaitGroup
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
go func() {
|
||||
defer close(in)
|
||||
p.seq(func(v V) bool {
|
||||
select {
|
||||
case <-done:
|
||||
return false
|
||||
case in <- v:
|
||||
return true
|
||||
}
|
||||
})
|
||||
}()
|
||||
|
||||
wg.Add(int(p.workers))
|
||||
for range p.workers {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for v := range in {
|
||||
if mid, ok := p.process(v); ok {
|
||||
if !fn(mid) {
|
||||
once.Do(func() { close(done) })
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func (p SeqHeapPar[V]) Skip(n uint) SeqHeapPar[V] {
|
||||
prev := p.process
|
||||
|
||||
return SeqHeapPar[V]{
|
||||
seq: func(yield func(V) bool) {
|
||||
var cnt uint64
|
||||
p.seq(func(v V) bool {
|
||||
if atomic.AddUint64(&cnt, 1) > uint64(n) {
|
||||
return yield(v)
|
||||
}
|
||||
return true
|
||||
})
|
||||
},
|
||||
workers: p.workers,
|
||||
process: prev,
|
||||
}
|
||||
}
|
||||
|
||||
func (p SeqHeapPar[V]) Take(n uint) SeqHeapPar[V] {
|
||||
prev := p.process
|
||||
|
||||
return SeqHeapPar[V]{
|
||||
seq: func(yield func(V) bool) {
|
||||
var cnt uint64
|
||||
p.seq(func(v V) bool {
|
||||
if atomic.AddUint64(&cnt, 1) <= uint64(n) {
|
||||
return yield(v)
|
||||
}
|
||||
return false
|
||||
})
|
||||
},
|
||||
workers: p.workers,
|
||||
process: prev,
|
||||
}
|
||||
}
|
||||
|
||||
// Unique removes duplicate elements, preserving the first occurrence.
|
||||
func (p SeqHeapPar[V]) Unique() SeqHeapPar[V] {
|
||||
prev := p.process
|
||||
seen := NewMapSafe[any, struct{}]()
|
||||
|
||||
return SeqHeapPar[V]{
|
||||
seq: p.seq,
|
||||
workers: p.workers,
|
||||
process: func(v V) (V, bool) {
|
||||
if mid, ok := prev(v); ok {
|
||||
if loaded := seen.Entry(mid).OrSet(struct{}{}); loaded.IsSome() {
|
||||
var zero V
|
||||
return zero, false
|
||||
}
|
||||
|
||||
return mid, true
|
||||
}
|
||||
|
||||
var zero V
|
||||
return zero, false
|
||||
},
|
||||
}
|
||||
}
|
||||
+117
-58
@@ -1,6 +1,7 @@
|
||||
package g
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math/big"
|
||||
@@ -8,14 +9,16 @@ import (
|
||||
|
||||
"github.com/enetx/g/cmp"
|
||||
"github.com/enetx/g/constraints"
|
||||
"github.com/enetx/g/rand"
|
||||
)
|
||||
|
||||
// Int is a wrapper around the int type.
|
||||
type Int int
|
||||
|
||||
// NewInt creates a new Int with the provided int value.
|
||||
func NewInt[T constraints.Integer | rune | byte](i T) Int { return Int(i) }
|
||||
|
||||
// Transform applies a transformation function to the Int and returns the result.
|
||||
func (i Int) Transform(fn func(Int) Int) Int { return fn(i) }
|
||||
func (i Int) Transform[U any](fn func(Int) U) U { return fn(i) }
|
||||
|
||||
// Min returns the minimum of Ints.
|
||||
func (i Int) Min(b ...Int) Int { return cmp.Min(append(b, i)...) }
|
||||
@@ -23,38 +26,9 @@ func (i Int) Min(b ...Int) Int { return cmp.Min(append(b, i)...) }
|
||||
// Max returns the maximum of Ints.
|
||||
func (i Int) Max(b ...Int) Int { return cmp.Max(append(b, i)...) }
|
||||
|
||||
// RandomRange returns a random Int in the inclusive range [i, to].
|
||||
// The order of bounds does not matter (it normalizes to [min, max]).
|
||||
// Works for negative bounds and the full int64 range without overflow or bias.
|
||||
func (i Int) RandomRange(to Int) Int {
|
||||
lo, hi := i, to
|
||||
|
||||
if lo > hi {
|
||||
lo, hi = hi, lo
|
||||
}
|
||||
|
||||
if lo == hi {
|
||||
return lo
|
||||
}
|
||||
|
||||
const bias = uint64(1) << 63 // 2^63 = 9223372036854775808
|
||||
|
||||
ulo := uint64(lo) + bias
|
||||
uhi := uint64(hi) + bias
|
||||
|
||||
w := uhi - ulo + 1
|
||||
|
||||
if w == 0 {
|
||||
return Int(int64(rand.U64()))
|
||||
}
|
||||
|
||||
randv := rand.N(w)
|
||||
result := int64((ulo + randv) - bias)
|
||||
|
||||
return Int(result)
|
||||
}
|
||||
|
||||
// Abs returns the absolute value of the Int.
|
||||
// Like Go's native arithmetic it wraps on overflow: Abs of math.MinInt is math.MinInt.
|
||||
// Use CheckedAbs for a guarded variant.
|
||||
func (i Int) Abs() Int {
|
||||
if i < 0 {
|
||||
return -i
|
||||
@@ -64,12 +38,37 @@ func (i Int) Abs() Int {
|
||||
}
|
||||
|
||||
// Add adds two Ints and returns the result.
|
||||
// Like Go's native arithmetic it wraps on overflow (two's complement).
|
||||
// Use CheckedAdd, SaturatingAdd or OverflowingAdd for guarded variants.
|
||||
func (i Int) Add(b Int) Int { return i + b }
|
||||
|
||||
// Neg returns the Int with its sign inverted.
|
||||
// Like Go's native arithmetic it wraps on overflow: Neg of math.MinInt is math.MinInt.
|
||||
// Use CheckedNeg for a guarded variant.
|
||||
func (i Int) Neg() Int { return -i }
|
||||
|
||||
// Signum returns the sign of the Int:
|
||||
// -1 if the Int is negative, 0 if it is zero, and 1 if it is positive.
|
||||
func (i Int) Signum() Int {
|
||||
switch {
|
||||
case i < 0:
|
||||
return -1
|
||||
case i > 0:
|
||||
return 1
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// BigInt returns the Int as a *big.Int.
|
||||
func (i Int) BigInt() *big.Int { return big.NewInt(i.Int64()) }
|
||||
|
||||
// Div divides two Ints and returns the result.
|
||||
//
|
||||
// Div panics with a runtime "integer divide by zero" error if b is 0.
|
||||
// Dividing by zero is treated as a programmer error; guard against a zero
|
||||
// divisor at the call site. This differs from Float.Div, which follows IEEE
|
||||
// 754 and yields ±Inf or NaN instead of panicking.
|
||||
func (i Int) Div(b Int) Int { return i / b }
|
||||
|
||||
// Eq checks if two Ints are equal.
|
||||
@@ -111,8 +110,11 @@ func (i Int) IsZero() bool { return i == 0 }
|
||||
// IsNegative checks if the Int is negative.
|
||||
func (i Int) IsNegative() bool { return i < 0 }
|
||||
|
||||
// IsPositive checks if the Int is positive.
|
||||
func (i Int) IsPositive() bool { return i >= 0 }
|
||||
// IsPositive reports whether the Int is strictly greater than zero.
|
||||
// Zero is neither positive nor negative: both
|
||||
// Int(0).IsPositive() and Int(0).IsNegative() return false. For a
|
||||
// non-negative check use !i.IsNegative().
|
||||
func (i Int) IsPositive() bool { return i > 0 }
|
||||
|
||||
// Lt checks if the Int is less than the specified Int.
|
||||
func (i Int) Lt(b Int) bool { return i < b }
|
||||
@@ -121,49 +123,73 @@ func (i Int) Lt(b Int) bool { return i < b }
|
||||
func (i Int) Lte(b Int) bool { return i <= b }
|
||||
|
||||
// Mul multiplies two Ints and returns the result.
|
||||
// Like Go's native arithmetic it wraps on overflow (two's complement).
|
||||
// Use CheckedMul, SaturatingMul or OverflowingMul for guarded variants.
|
||||
func (i Int) Mul(b Int) Int { return i * b }
|
||||
|
||||
// Ne checks if two Ints are not equal.
|
||||
func (i Int) Ne(b Int) bool { return i != b }
|
||||
|
||||
// Random returns a random Int in the range [0, hi].
|
||||
func (i Int) Random() Int {
|
||||
if i <= 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
return Int(rand.N(uint64(i)))
|
||||
}
|
||||
|
||||
// Rem returns the remainder of the division between the receiver and the input value.
|
||||
//
|
||||
// Rem panics with a runtime "integer divide by zero" error if b is 0.
|
||||
// A zero divisor is treated as a programmer error; guard against it at the
|
||||
// call site.
|
||||
func (i Int) Rem(b Int) Int { return i % b }
|
||||
|
||||
// Sub subtracts two Ints and returns the result.
|
||||
// Like Go's native arithmetic it wraps on overflow (two's complement).
|
||||
// Use CheckedSub, SaturatingSub or OverflowingSub for guarded variants.
|
||||
func (i Int) Sub(b Int) Int { return i - b }
|
||||
|
||||
// Binary returns the Int as a binary string.
|
||||
func (i Int) Binary() String { return String(fmt.Sprintf("%08b", i)) }
|
||||
// Binary returns the Int as a binary string, zero-padded to a minimum width of
|
||||
// 8 characters (the sign counts toward the width for negative values).
|
||||
func (i Int) Binary() String {
|
||||
var storage [65]byte
|
||||
digits := strconv.AppendInt(storage[:0], int64(i), 2)
|
||||
if len(digits) >= 8 {
|
||||
return String(digits)
|
||||
}
|
||||
|
||||
var padded [8]byte
|
||||
start := 8 - len(digits)
|
||||
if digits[0] == '-' {
|
||||
padded[0] = '-'
|
||||
start++
|
||||
for j := 1; j < start; j++ {
|
||||
padded[j] = '0'
|
||||
}
|
||||
copy(padded[start:], digits[1:])
|
||||
} else {
|
||||
for j := 0; j < start; j++ {
|
||||
padded[j] = '0'
|
||||
}
|
||||
copy(padded[start:], digits)
|
||||
}
|
||||
|
||||
return String(padded[:])
|
||||
}
|
||||
|
||||
// Hex returns the Int as a hexadecimal string.
|
||||
func (i Int) Hex() String { return String(fmt.Sprintf("%x", i)) }
|
||||
func (i Int) Hex() String { return String(strconv.FormatInt(int64(i), 16)) }
|
||||
|
||||
// Octal returns the Int as an octal string.
|
||||
func (i Int) Octal() String { return String(fmt.Sprintf("%o", i)) }
|
||||
func (i Int) Octal() String { return String(strconv.FormatInt(int64(i), 8)) }
|
||||
|
||||
// UInt returns the Int as a uint.
|
||||
func (i Int) UInt() uint { return uint(i) }
|
||||
// Uint returns the Int as a uint.
|
||||
func (i Int) Uint() uint { return uint(i) }
|
||||
|
||||
// UInt16 returns the Int as a uint16.
|
||||
func (i Int) UInt16() uint16 { return uint16(i) }
|
||||
// Uint16 returns the Int as a uint16.
|
||||
func (i Int) Uint16() uint16 { return uint16(i) }
|
||||
|
||||
// UInt32 returns the Int as a uint32.
|
||||
func (i Int) UInt32() uint32 { return uint32(i) }
|
||||
// Uint32 returns the Int as a uint32.
|
||||
func (i Int) Uint32() uint32 { return uint32(i) }
|
||||
|
||||
// UInt64 returns the Int as a uint64.
|
||||
func (i Int) UInt64() uint64 { return uint64(i) }
|
||||
// Uint64 returns the Int as a uint64.
|
||||
func (i Int) Uint64() uint64 { return uint64(i) }
|
||||
|
||||
// UInt8 returns the Int as a uint8.
|
||||
func (i Int) UInt8() uint8 { return uint8(i) }
|
||||
// Uint8 returns the Int as a uint8.
|
||||
func (i Int) Uint8() uint8 { return uint8(i) }
|
||||
|
||||
// bytesFromInt converts Int to Bytes using the given byte order.
|
||||
// For BE: removes leading zeros while preserving the sign bit.
|
||||
@@ -227,3 +253,36 @@ func (i Int) Print() Int { fmt.Print(i); return i }
|
||||
// Println writes the value of the Int to the standard output (console) with a newline
|
||||
// and returns the Int unchanged.
|
||||
func (i Int) Println() Int { fmt.Println(i); return i }
|
||||
|
||||
// Scan implements the database/sql.Scanner interface for g.Int.
|
||||
//
|
||||
// Behavior:
|
||||
// - If src is nil, the value is set to 0 (SQL NULL).
|
||||
// - If src is an int64 (common SQL INTEGER type), it is assigned.
|
||||
// - Otherwise, an error is returned.
|
||||
//
|
||||
// Supported SQL types (common):
|
||||
// - INTEGER → int64
|
||||
//
|
||||
// Notes:
|
||||
// - This allows g.Int to be used directly with database/sql and compatible drivers.
|
||||
func (i *Int) Scan(src any) error {
|
||||
if src == nil {
|
||||
*i = 0
|
||||
return nil
|
||||
}
|
||||
|
||||
if i64, ok := src.(int64); ok {
|
||||
*i = Int(i64)
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("g.Int.Scan: cannot scan %T into g.Int", src)
|
||||
}
|
||||
|
||||
// Value implements the database/sql/driver.Valuer interface for g.Int.
|
||||
//
|
||||
// Behavior:
|
||||
// - Returns the underlying int64 value, ready for database insertion.
|
||||
// - Always returns a value compatible with SQL INTEGER type.
|
||||
func (i Int) Value() (driver.Value, error) { return int64(i), nil }
|
||||
+1
-11
@@ -1,14 +1,11 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package filelock provides a platform-independent API for advisory file
|
||||
// locking. Calls to functions in this package on platforms that do not support
|
||||
// advisory locks will return errors for which IsNotSupported returns true.
|
||||
// advisory locks will return errors that wrap errors.ErrUnsupported.
|
||||
package filelock
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/fs"
|
||||
)
|
||||
|
||||
@@ -74,10 +71,3 @@ func (lt lockType) String() string {
|
||||
return "Unlock"
|
||||
}
|
||||
}
|
||||
|
||||
// IsNotSupported returns a boolean indicating whether the error is known to
|
||||
// report that a function is not supported (possibly for a specific input).
|
||||
// It is satisfied by errors.ErrUnsupported as well as some syscall errors.
|
||||
func IsNotSupported(err error) bool {
|
||||
return errors.Is(err, errors.ErrUnsupported)
|
||||
}
|
||||
-2
@@ -1,6 +1,4 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build aix || (solaris && !illumos)
|
||||
|
||||
|
||||
-2
@@ -1,6 +1,4 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !unix && !windows
|
||||
|
||||
|
||||
-2
@@ -1,6 +1,4 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build darwin || dragonfly || freebsd || illumos || linux || netbsd || openbsd
|
||||
|
||||
|
||||
-2
@@ -1,6 +1,4 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build windows
|
||||
|
||||
|
||||
Generated
Vendored
-2
@@ -1,6 +1,4 @@
|
||||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package windows
|
||||
|
||||
|
||||
Generated
Vendored
-2
@@ -1,6 +1,4 @@
|
||||
// Copyright 2021 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package windows
|
||||
|
||||
|
||||
Generated
Vendored
-2
@@ -1,6 +1,4 @@
|
||||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package windows
|
||||
|
||||
|
||||
Generated
Vendored
-2
@@ -1,6 +1,4 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package windows
|
||||
|
||||
|
||||
Generated
Vendored
-2
@@ -1,6 +1,4 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package windows
|
||||
|
||||
|
||||
Generated
Vendored
-2
@@ -1,6 +1,4 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package windows
|
||||
|
||||
|
||||
Generated
Vendored
-2
@@ -1,6 +1,4 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package windows
|
||||
|
||||
|
||||
Generated
Vendored
-2
@@ -1,6 +1,4 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build windows
|
||||
|
||||
|
||||
+69
-73
@@ -3,41 +3,40 @@ package g
|
||||
import (
|
||||
"fmt"
|
||||
"maps"
|
||||
|
||||
"github.com/enetx/g/f"
|
||||
"github.com/enetx/iter"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// Map is a generic alias for a map.
|
||||
type Map[K comparable, V any] map[K]V
|
||||
|
||||
// NewMap creates a new Map of the specified size or an empty Map if no size is provided.
|
||||
func NewMap[K comparable, V any](size ...Int) Map[K, V] {
|
||||
return make(Map[K, V], Slice[Int](size).Get(0).UnwrapOrDefault())
|
||||
if len(size) > 0 {
|
||||
return make(Map[K, V], size[0])
|
||||
}
|
||||
|
||||
return make(Map[K, V])
|
||||
}
|
||||
|
||||
// Transform applies a transformation function to the Map and returns the result.
|
||||
func (m Map[K, V]) Transform(fn func(Map[K, V]) Map[K, V]) Map[K, V] { return fn(m) }
|
||||
func (m Map[K, V]) Transform[U any](fn func(Map[K, V]) U) U { return fn(m) }
|
||||
|
||||
// Entry returns an MapEntry object for the given key, providing fine‑grained
|
||||
// control over insertion and modification of its value.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// m := g.NewMap[string, int]()
|
||||
// // Insert 1 if "foo" is absent, then increment it
|
||||
// e := m.Entry("foo")
|
||||
// e.OrSet(1)
|
||||
// e.Transform(func(v int) int { return v + 1 })
|
||||
//
|
||||
// The entire operation requires only a single key lookup and works without
|
||||
// additional allocations.
|
||||
func (m Map[K, V]) Entry(key K) MapEntry[K, V] { return MapEntry[K, V]{m, key} }
|
||||
// Entry returns an Entry for the given key.
|
||||
func (m Map[K, V]) Entry(key K) Entry[K, V] {
|
||||
if _, ok := m[key]; ok {
|
||||
return OccupiedEntry[K, V]{m: m, key: key}
|
||||
}
|
||||
|
||||
// Iter returns an iterator (SeqMap[K, V]) for the Map, allowing for sequential iteration
|
||||
return VacantEntry[K, V]{m: m, key: key}
|
||||
}
|
||||
|
||||
// Iter returns an iterator (Seq2[K, V]) for the Map, allowing for sequential iteration
|
||||
// over its key-value pairs. It is commonly used in combination with higher-order functions,
|
||||
// such as 'ForEach', to perform operations on each key-value pair of the Map.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - SeqMap[K, V], which can be used for sequential iteration over the key-value pairs of the Map.
|
||||
// - Seq2[K, V], which can be used for sequential iteration over the key-value pairs of the Map.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
@@ -50,27 +49,19 @@ func (m Map[K, V]) Entry(key K) MapEntry[K, V] { return MapEntry[K, V]{m, key} }
|
||||
//
|
||||
// The 'Iter' method provides a convenient way to traverse the key-value pairs of a Map
|
||||
// in a functional style, enabling operations like mapping or filtering.
|
||||
func (m Map[K, V]) Iter() SeqMap[K, V] { return SeqMap[K, V](iter.FromMap(m)) }
|
||||
|
||||
// Invert inverts the keys and values of the Map, returning a new Map with values as keys and
|
||||
// keys as values. Note that the inverted Map will have 'any' as the key type, since not all value
|
||||
// types are guaranteed to be comparable.
|
||||
func (m Map[K, V]) Invert() Map[any, K] {
|
||||
if m.Empty() {
|
||||
return NewMap[any, K]()
|
||||
func (m Map[K, V]) Iter() Seq2[K, V] {
|
||||
return func(yield func(K, V) bool) {
|
||||
for k, v := range m {
|
||||
if !yield(k, v) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result := make(Map[any, K], len(m))
|
||||
for k, v := range m {
|
||||
result[v] = k
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Keys returns a slice of the Map's keys.
|
||||
func (m Map[K, V]) Keys() Slice[K] {
|
||||
if m.Empty() {
|
||||
if m.IsEmpty() {
|
||||
return NewSlice[K]()
|
||||
}
|
||||
|
||||
@@ -84,7 +75,7 @@ func (m Map[K, V]) Keys() Slice[K] {
|
||||
|
||||
// Values returns a slice of the Map's values.
|
||||
func (m Map[K, V]) Values() Slice[V] {
|
||||
if m.Empty() {
|
||||
if m.IsEmpty() {
|
||||
return NewSlice[V]()
|
||||
}
|
||||
|
||||
@@ -108,55 +99,46 @@ func (m Map[K, V]) Clone() Map[K, V] { return maps.Clone(m) }
|
||||
// Copy copies the source Map's key-value pairs to the target Map.
|
||||
func (m Map[K, V]) Copy(src Map[K, V]) { maps.Copy(m, src) }
|
||||
|
||||
// Delete removes the specified keys from the Map.
|
||||
func (m Map[K, V]) Delete(keys ...K) {
|
||||
for _, key := range keys {
|
||||
// Remove removes the specified key from the Map and returns the removed value.
|
||||
func (m Map[K, V]) Remove(key K) Option[V] {
|
||||
if v, ok := m[key]; ok {
|
||||
delete(m, key)
|
||||
return Some(v)
|
||||
}
|
||||
|
||||
return None[V]()
|
||||
}
|
||||
|
||||
// Std converts the Map to a regular Go map.
|
||||
func (m Map[K, V]) Std() map[K]V { return m }
|
||||
|
||||
// ToMapOrd converts a standard Map to an ordered Map.
|
||||
func (m Map[K, V]) ToMapOrd() MapOrd[K, V] {
|
||||
mo := NewMapOrd[K, V](m.Len())
|
||||
for k, v := range m {
|
||||
mo.Set(k, v)
|
||||
}
|
||||
|
||||
return mo
|
||||
}
|
||||
|
||||
// ToMapSafe converts a standard Map to a thread-safe Map.
|
||||
func (m Map[K, V]) ToMapSafe() *MapSafe[K, V] {
|
||||
ms := NewMapSafe[K, V]()
|
||||
for k, v := range m {
|
||||
ms.Set(k, v)
|
||||
}
|
||||
|
||||
return ms
|
||||
}
|
||||
|
||||
// Eq checks if two Maps are equal.
|
||||
func (m Map[K, V]) Eq(other Map[K, V]) bool {
|
||||
n := len(m)
|
||||
if n != len(other) {
|
||||
return false
|
||||
}
|
||||
|
||||
if n == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
var zero V
|
||||
comparable := f.IsComparable(zero)
|
||||
comparable := isValueComparable[V]()
|
||||
|
||||
for k, value := range m {
|
||||
ovalue, ok := other[k]
|
||||
if !ok || comparable && !f.Eq[any](value)(ovalue) || !comparable && !f.Eqd(value)(ovalue) {
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
if comparable {
|
||||
if any(value) != any(ovalue) {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
if !reflect.DeepEqual(value, ovalue) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
@@ -169,6 +151,7 @@ func (m Map[K, V]) String() string {
|
||||
}
|
||||
|
||||
var b Builder
|
||||
b.Grow(Int(len(m)) * 16)
|
||||
b.WriteString("Map{")
|
||||
|
||||
first := true
|
||||
@@ -178,7 +161,9 @@ func (m Map[K, V]) String() string {
|
||||
}
|
||||
|
||||
first = false
|
||||
b.WriteString(Format("{}:{}", k, v))
|
||||
fmt.Fprint(&b, k)
|
||||
b.WriteByte(':')
|
||||
fmt.Fprint(&b, v)
|
||||
}
|
||||
|
||||
b.WriteString("}")
|
||||
@@ -189,8 +174,8 @@ func (m Map[K, V]) String() string {
|
||||
// Clear removes all key-value pairs from the Map.
|
||||
func (m Map[K, V]) Clear() { clear(m) }
|
||||
|
||||
// Empty checks if the Map is empty.
|
||||
func (m Map[K, V]) Empty() bool { return len(m) == 0 }
|
||||
// IsEmpty checks if the Map is empty.
|
||||
func (m Map[K, V]) IsEmpty() bool { return len(m) == 0 }
|
||||
|
||||
// Get retrieves the value associated with the given key.
|
||||
func (m Map[K, V]) Get(k K) Option[V] {
|
||||
@@ -207,11 +192,8 @@ func (m Map[K, V]) Len() Int { return Int(len(m)) }
|
||||
// Ne checks if two Maps are not equal.
|
||||
func (m Map[K, V]) Ne(other Map[K, V]) bool { return !m.Eq(other) }
|
||||
|
||||
// NotEmpty checks if the Map is not empty.
|
||||
func (m Map[K, V]) NotEmpty() bool { return !m.Empty() }
|
||||
|
||||
// Set sets the value for the key and returns the previous value if it existed.
|
||||
func (m Map[K, V]) Set(key K, value V) Option[V] {
|
||||
// Insert sets the value for the key and returns the previous value if it existed.
|
||||
func (m Map[K, V]) Insert(key K, value V) Option[V] {
|
||||
prev, ok := m[key]
|
||||
m[key] = value
|
||||
if ok {
|
||||
@@ -228,3 +210,17 @@ func (m Map[K, V]) Print() Map[K, V] { fmt.Print(m); return m }
|
||||
// Println writes the key-value pairs of the Map to the standard output (console) with a newline
|
||||
// and returns the Map unchanged.
|
||||
func (m Map[K, V]) Println() Map[K, V] { fmt.Println(m); return m }
|
||||
|
||||
// MapOf creates a Map from the provided key-value pairs.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// m := g.MapOf(g.PairOf("a", 1), g.PairOf("b", 2))
|
||||
func MapOf[K comparable, V any](pairs ...Pair[K, V]) Map[K, V] {
|
||||
m := NewMap[K, V](Int(len(pairs)))
|
||||
for _, p := range pairs {
|
||||
m[p.Key] = p.Value
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
-68
@@ -1,68 +0,0 @@
|
||||
package g
|
||||
|
||||
// Get returns Some(value) if the key exists, otherwise None.
|
||||
func (e MapEntry[K, V]) Get() Option[V] {
|
||||
return e.m.Get(e.key)
|
||||
}
|
||||
|
||||
// OrSet inserts value if the key is vacant. Returns Some(existing) or None if newly inserted.
|
||||
func (e MapEntry[K, V]) OrSet(value V) Option[V] {
|
||||
if existing, ok := e.m[e.key]; ok {
|
||||
return Some(existing)
|
||||
}
|
||||
|
||||
e.m[e.key] = value
|
||||
|
||||
return None[V]()
|
||||
}
|
||||
|
||||
// OrSetBy inserts the value from fn() if the key is vacant. Returns Some(existing) or None.
|
||||
func (e MapEntry[K, V]) OrSetBy(fn func() V) Option[V] {
|
||||
if existing, ok := e.m[e.key]; ok {
|
||||
return Some(existing)
|
||||
}
|
||||
|
||||
e.m[e.key] = fn()
|
||||
|
||||
return None[V]()
|
||||
}
|
||||
|
||||
// OrDefault inserts the zero value if the key is vacant. Returns Some(existing) or None.
|
||||
func (e MapEntry[K, V]) OrDefault() Option[V] {
|
||||
var zero V
|
||||
return e.OrSet(zero)
|
||||
}
|
||||
|
||||
// Transform applies fn to the existing value. Returns Some(updated) or None if key was absent.
|
||||
func (e MapEntry[K, V]) Transform(fn func(V) V) Option[V] {
|
||||
if value, ok := e.m[e.key]; ok {
|
||||
value = fn(value)
|
||||
e.m[e.key] = value
|
||||
|
||||
return Some(value)
|
||||
}
|
||||
|
||||
return None[V]()
|
||||
}
|
||||
|
||||
// Set sets the value and returns Some(previous) if the key existed, or None otherwise.
|
||||
func (e MapEntry[K, V]) Set(value V) Option[V] {
|
||||
old, ok := e.m[e.key]
|
||||
e.m[e.key] = value
|
||||
if ok {
|
||||
return Some(old)
|
||||
}
|
||||
|
||||
return None[V]()
|
||||
}
|
||||
|
||||
// Delete removes the key from the map.
|
||||
// Returns Some(removed_value) if present, None otherwise.
|
||||
func (e MapEntry[K, V]) Delete() Option[V] {
|
||||
if value, ok := e.m[e.key]; ok {
|
||||
delete(e.m, e.key)
|
||||
return Some(value)
|
||||
}
|
||||
|
||||
return None[V]()
|
||||
}
|
||||
-382
@@ -1,382 +0,0 @@
|
||||
package g
|
||||
|
||||
import (
|
||||
"context"
|
||||
"runtime"
|
||||
|
||||
"github.com/enetx/iter"
|
||||
)
|
||||
|
||||
// IterPar parallelizes the SeqMap using the specified number of workers.
|
||||
func (seq SeqMap[K, V]) Parallel(workers ...Int) SeqMapPar[K, V] {
|
||||
numCPU := Int(runtime.NumCPU())
|
||||
count := Slice[Int](workers).Get(0).UnwrapOr(numCPU)
|
||||
|
||||
if count.Lte(0) {
|
||||
count = numCPU
|
||||
}
|
||||
|
||||
return SeqMapPar[K, V]{
|
||||
seq: seq,
|
||||
workers: count,
|
||||
process: func(p Pair[K, V]) (Pair[K, V], bool) { return p, true },
|
||||
}
|
||||
}
|
||||
|
||||
// Pull converts the “push-style” iterator sequence seq
|
||||
// into a “pull-style” iterator accessed by the two functions
|
||||
// next and stop.
|
||||
//
|
||||
// Next returns the next pair in the sequence
|
||||
// and a boolean indicating whether the pair is valid.
|
||||
// When the sequence is over, next returns a pair of zero values and false.
|
||||
// It is valid to call next after reaching the end of the sequence
|
||||
// or after calling stop. These calls will continue
|
||||
// to return a pair of zero values and false.
|
||||
//
|
||||
// Stop ends the iteration. It must be called when the caller is
|
||||
// no longer interested in next values and next has not yet
|
||||
// signaled that the sequence is over (with a false boolean return).
|
||||
// It is valid to call stop multiple times and when next has
|
||||
// already returned false.
|
||||
//
|
||||
// It is an error to call next or stop from multiple goroutines
|
||||
// simultaneously.
|
||||
func (seq SeqMap[K, V]) Pull() (func() (K, V, bool), func()) { return iter.Pull2(iter.Seq2[K, V](seq)) }
|
||||
|
||||
// Take returns a new iterator with the first n elements.
|
||||
// The function creates a new iterator containing the first n elements from the original iterator.
|
||||
func (seq SeqMap[K, V]) Take(n uint) SeqMap[K, V] {
|
||||
return SeqMap[K, V](iter.Take2(iter.Seq2[K, V](seq), int(n)))
|
||||
}
|
||||
|
||||
// Nth returns the nth key-value pair (0-indexed) in the sequence.
|
||||
func (seq SeqMap[K, V]) Nth(n Int) Option[Pair[K, V]] {
|
||||
key, value, found := iter.Nth2(iter.Seq2[K, V](seq), int(n))
|
||||
if found {
|
||||
return Some(Pair[K, V]{Key: key, Value: value})
|
||||
}
|
||||
|
||||
return None[Pair[K, V]]()
|
||||
}
|
||||
|
||||
// Keys returns an iterator containing all the keys in the ordered Map.
|
||||
func (seq SeqMap[K, V]) Keys() SeqSlice[K] {
|
||||
return SeqSlice[K](iter.Keys(iter.Seq2[K, V](seq)))
|
||||
}
|
||||
|
||||
// Values returns an iterator containing all the values in the ordered Map.
|
||||
func (seq SeqMap[K, V]) Values() SeqSlice[V] {
|
||||
return SeqSlice[V](iter.Values(iter.Seq2[K, V](seq)))
|
||||
}
|
||||
|
||||
// Chain creates a new iterator by concatenating the current iterator with other iterators.
|
||||
//
|
||||
// The function concatenates the key-value pairs from the current iterator with the key-value pairs from the provided iterators,
|
||||
// producing a new iterator containing all concatenated elements.
|
||||
//
|
||||
// Params:
|
||||
//
|
||||
// - seqs ([]SeqMap[K, V]): Other iterators to be concatenated with the current iterator.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - SeqMap[K, V]: A new iterator containing elements from the current iterator and the provided iterators.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// iter1 := g.NewMap[int, string]().Set(1, "a").Iter()
|
||||
// iter2 := g.NewMap[int, string]().Set(2, "b").Iter()
|
||||
//
|
||||
// // Concatenating iterators and collecting the result.
|
||||
// iter1.Chain(iter2).Collect().Print()
|
||||
//
|
||||
// Output: Map{1:a, 2:b} // The output order may vary as Map is not ordered.
|
||||
//
|
||||
// The resulting iterator will contain elements from both iterators.
|
||||
func (seq SeqMap[K, V]) Chain(seqs ...SeqMap[K, V]) SeqMap[K, V] {
|
||||
iterSeqs := make([]iter.Seq2[K, V], len(seqs))
|
||||
for i, s := range seqs {
|
||||
iterSeqs[i] = iter.Seq2[K, V](s)
|
||||
}
|
||||
|
||||
return SeqMap[K, V](iter.Chain2(iter.Seq2[K, V](seq), iterSeqs...))
|
||||
}
|
||||
|
||||
// Count consumes the iterator, counting the number of iterations and returning it.
|
||||
func (seq SeqMap[K, V]) Count() Int { return Int(iter.Count2(iter.Seq2[K, V](seq))) }
|
||||
|
||||
// Collect collects all key-value pairs from the iterator and returns a Map.
|
||||
func (seq SeqMap[K, V]) Collect() Map[K, V] {
|
||||
collection := NewMap[K, V]()
|
||||
|
||||
seq(func(k K, v V) bool {
|
||||
collection[k] = v
|
||||
return true
|
||||
})
|
||||
|
||||
return collection
|
||||
}
|
||||
|
||||
// Filter returns a new iterator containing only the elements that satisfy the provided function.
|
||||
//
|
||||
// This function creates a new iterator containing key-value pairs for which the provided function returns true.
|
||||
// It iterates through the current iterator, applying the function to each key-value pair.
|
||||
// If the function returns true for a key-value pair, it will be included in the resulting iterator.
|
||||
//
|
||||
// Params:
|
||||
//
|
||||
// - fn (func(K, V) bool): The function applied to each key-value pair to determine inclusion.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - SeqMap[K, V]: An iterator containing elements that satisfy the given function.
|
||||
//
|
||||
// m := g.NewMap[int, int]().
|
||||
// Set(1, 1).
|
||||
// Set(2, 2).
|
||||
// Set(3, 3).
|
||||
// Set(4, 4).
|
||||
// Set(5, 5)
|
||||
//
|
||||
// even := m.Iter().
|
||||
// Filter(
|
||||
// func(k, v int) bool {
|
||||
// return v%2 == 0
|
||||
// }).
|
||||
// Collect()
|
||||
// even.Print()
|
||||
//
|
||||
// Output: Map{2:2, 4:4} // The output order may vary as Map is not ordered.
|
||||
//
|
||||
// The resulting iterator will contain elements for which the function returns true.
|
||||
func (seq SeqMap[K, V]) Filter(fn func(K, V) bool) SeqMap[K, V] {
|
||||
return SeqMap[K, V](iter.Filter2(iter.Seq2[K, V](seq), fn))
|
||||
}
|
||||
|
||||
// Exclude returns a new iterator excluding elements that satisfy the provided function.
|
||||
//
|
||||
// This function creates a new iterator excluding key-value pairs for which the provided function returns true.
|
||||
// It iterates through the current iterator, applying the function to each key-value pair.
|
||||
// If the function returns true for a key-value pair, it will be excluded from the resulting iterator.
|
||||
//
|
||||
// Params:
|
||||
//
|
||||
// - fn (func(K, V) bool): The function applied to each key-value pair to determine exclusion.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - SeqMap[K, V]: An iterator excluding elements that satisfy the given function.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// m := g.NewMap[int, int]().
|
||||
// Set(1, 1).
|
||||
// Set(2, 2).
|
||||
// Set(3, 3).
|
||||
// Set(4, 4).
|
||||
// Set(5, 5)
|
||||
//
|
||||
// notEven := m.Iter().
|
||||
// Exclude(
|
||||
// func(k, v int) bool {
|
||||
// return v%2 == 0
|
||||
// }).
|
||||
// Collect()
|
||||
// notEven.Print()
|
||||
//
|
||||
// Output: Map{1:1, 3:3, 5:5} // The output order may vary as Map is not ordered.
|
||||
//
|
||||
// The resulting iterator will exclude elements for which the function returns true.
|
||||
func (seq SeqMap[K, V]) Exclude(fn func(K, V) bool) SeqMap[K, V] {
|
||||
return SeqMap[K, V](iter.Exclude2(iter.Seq2[K, V](seq), fn))
|
||||
}
|
||||
|
||||
// Find searches for an element in the iterator that satisfies the provided function.
|
||||
//
|
||||
// The function iterates through the elements of the iterator and returns the first element
|
||||
// for which the provided function returns true.
|
||||
//
|
||||
// Params:
|
||||
//
|
||||
// - fn (func(K, V) bool): The function used to test elements for a condition.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - Option[K, V]: An Option containing the first element that satisfies the condition; None if not found.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// m := g.NewMap[int, int]()
|
||||
// m.Set(1, 1)
|
||||
// f := m.Iter().Find(func(_ int, v int) bool { return v == 1 })
|
||||
// if f.IsSome() {
|
||||
// print(f.Some().Key)
|
||||
// }
|
||||
//
|
||||
// The resulting Option may contain the first element that satisfies the condition, or None if not found.
|
||||
func (seq SeqMap[K, V]) Find(fn func(k K, v V) bool) Option[Pair[K, V]] {
|
||||
key, value, found := iter.Find2(iter.Seq2[K, V](seq), fn)
|
||||
if found {
|
||||
return Some(Pair[K, V]{Key: key, Value: value})
|
||||
}
|
||||
|
||||
return None[Pair[K, V]]()
|
||||
}
|
||||
|
||||
// ForEach iterates through all elements and applies the given function to each key-value pair.
|
||||
//
|
||||
// This function traverses the entire iterator and applies the provided function to each key-value pair.
|
||||
// It iterates through the current iterator, executing the function on each key-value pair.
|
||||
//
|
||||
// Params:
|
||||
//
|
||||
// - fn (func(K, V)): The function to be applied to each key-value pair in the iterator.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// m := g.NewMap[int, int]().
|
||||
// Set(1, 1).
|
||||
// Set(2, 2).
|
||||
// Set(3, 3).
|
||||
// Set(4, 4).
|
||||
// Set(5, 5)
|
||||
//
|
||||
// mmap := m.Iter().
|
||||
// Map(
|
||||
// func(k, v int) (int, int) {
|
||||
// return k * k, v * v
|
||||
// }).
|
||||
// Collect()
|
||||
//
|
||||
// mmap.Print()
|
||||
//
|
||||
// Output: Map{1:1, 4:4, 9:9, 16:16, 25:25} // The output order may vary as Map is not ordered.
|
||||
//
|
||||
// The function fn will be executed for each key-value pair in the iterator.
|
||||
func (seq SeqMap[K, V]) ForEach(fn func(k K, v V)) { iter.ForEach2(iter.Seq2[K, V](seq), fn) }
|
||||
|
||||
// Inspect creates a new iterator that wraps around the current iterator
|
||||
// and allows inspecting each key-value pair as it passes through.
|
||||
func (seq SeqMap[K, V]) Inspect(fn func(k K, v V)) SeqMap[K, V] {
|
||||
return SeqMap[K, V](iter.Inspect2(iter.Seq2[K, V](seq), fn))
|
||||
}
|
||||
|
||||
// Map creates a new iterator by applying the given function to each key-value pair.
|
||||
//
|
||||
// This function generates a new iterator by traversing the current iterator and applying the provided
|
||||
// function to each key-value pair. It transforms the key-value pairs according to the given function.
|
||||
//
|
||||
// Params:
|
||||
//
|
||||
// - fn (func(K, V) (K, V)): The function to be applied to each key-value pair in the iterator.
|
||||
// It takes a key-value pair and returns a new transformed key-value pair.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - SeqMap[K, V]: A new iterator containing key-value pairs transformed by the provided function.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// m := g.NewMap[int, int]().
|
||||
// Set(1, 1).
|
||||
// Set(2, 2).
|
||||
// Set(3, 3).
|
||||
// Set(4, 4).
|
||||
// Set(5, 5)
|
||||
//
|
||||
// mmap := m.Iter().
|
||||
// Map(
|
||||
// func(k, v int) (int, int) {
|
||||
// return k * k, v * v
|
||||
// }).
|
||||
// Collect()
|
||||
//
|
||||
// mmap.Print()
|
||||
//
|
||||
// Output: Map{1:1, 4:4, 9:9, 16:16, 25:25} // The output order may vary as Map is not ordered.
|
||||
//
|
||||
// The resulting iterator will contain key-value pairs transformed by the given function.
|
||||
func (seq SeqMap[K, V]) Map(transform func(K, V) (K, V)) SeqMap[K, V] {
|
||||
return SeqMap[K, V](iter.Map2(iter.Seq2[K, V](seq), transform))
|
||||
}
|
||||
|
||||
// FilterMap applies a function to each key-value pair and filters out None results.
|
||||
//
|
||||
// The function transforms and filters pairs in a single pass. Pairs where the function
|
||||
// returns None are filtered out, and pairs where it returns Some are unwrapped
|
||||
// and included in the result.
|
||||
//
|
||||
// Params:
|
||||
//
|
||||
// - fn (func(K, V) Option[Pair[K, V]]): The function that transforms and filters pairs.
|
||||
// Returns Some(Pair{key, value}) to include the transformed pair, or None to filter it out.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - SeqMap[K, V]: A sequence containing only the successfully transformed pairs.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// configs := g.Map[string, string]{"host": "localhost", "port": "8080", "debug": "invalid"}
|
||||
// validConfigs := configs.Iter().FilterMap(func(k string, v string) Option[Pair[string, string]] {
|
||||
// if k == "port" || k == "host" {
|
||||
// return Some(Pair[string, string]{Key: k, Value: v + "_validated"})
|
||||
// }
|
||||
// return None[Pair[string, string]]()
|
||||
// })
|
||||
// // validConfigs will yield: {"host": "localhost_validated", "port": "8080_validated"}
|
||||
//
|
||||
// users := g.Map[string, int]{"alice": 25, "bob": 17, "charlie": 30}
|
||||
// adults := users.Iter().FilterMap(func(name string, age int) Option[Pair[string, int]] {
|
||||
// if age >= 18 {
|
||||
// return Some(Pair[string, int]{Key: name, Value: age})
|
||||
// }
|
||||
// return None[Pair[string, int]]()
|
||||
// })
|
||||
// // adults will yield: {"alice": 25, "charlie": 30}
|
||||
func (seq SeqMap[K, V]) FilterMap(fn func(K, V) Option[Pair[K, V]]) SeqMap[K, V] {
|
||||
return SeqMap[K, V](iter.FilterMap2(iter.Seq2[K, V](seq), func(k K, v V) (iter.Pair[K, V], bool) {
|
||||
return fn(k, v).Option()
|
||||
}))
|
||||
}
|
||||
|
||||
// The iteration will stop when the provided function returns false for an element.
|
||||
func (seq SeqMap[K, V]) Range(fn func(k K, v V) bool) { iter.Range2(iter.Seq2[K, V](seq), fn) }
|
||||
|
||||
// Context allows the iteration to be controlled with a context.Context.
|
||||
func (seq SeqMap[K, V]) Context(ctx context.Context) SeqMap[K, V] {
|
||||
return SeqMap[K, V](iter.Context2(iter.Seq2[K, V](seq), ctx))
|
||||
}
|
||||
|
||||
// Next extracts the next key-value pair from the iterator and advances it.
|
||||
//
|
||||
// This method consumes the next key-value pair from the iterator and returns them wrapped in an Option.
|
||||
// The iterator itself is modified to point to the remaining elements.
|
||||
//
|
||||
// Returns:
|
||||
// - Option[Pair[K, V]]: Some(Pair{Key, Value}) if a pair exists, None if the iterator is exhausted.
|
||||
func (seq *SeqMap[K, V]) Next() Option[Pair[K, V]] {
|
||||
var pairs []Pair[K, V]
|
||||
|
||||
(*seq)(func(k K, v V) bool {
|
||||
pairs = append(pairs, Pair[K, V]{Key: k, Value: v})
|
||||
return true
|
||||
})
|
||||
|
||||
if len(pairs) == 0 {
|
||||
return None[Pair[K, V]]()
|
||||
}
|
||||
|
||||
first := Some(pairs[0])
|
||||
|
||||
*seq = func(yield func(K, V) bool) {
|
||||
for _, pair := range pairs[1:] {
|
||||
if !yield(pair.Key, pair.Value) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return first
|
||||
}
|
||||
-293
@@ -1,293 +0,0 @@
|
||||
package g
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// All returns true if fn returns true for every pair.
|
||||
func (p SeqMapPar[K, V]) All(fn func(K, V) bool) bool {
|
||||
var ok atomic.Bool
|
||||
ok.Store(true)
|
||||
|
||||
p.Range(func(k K, v V) bool {
|
||||
if !fn(k, v) {
|
||||
ok.Store(false)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return ok.Load()
|
||||
}
|
||||
|
||||
// Any returns true if fn returns true for any pair.
|
||||
func (p SeqMapPar[K, V]) Any(fn func(K, V) bool) bool {
|
||||
var ok atomic.Bool
|
||||
|
||||
p.Range(func(k K, v V) bool {
|
||||
if fn(k, v) {
|
||||
ok.Store(true)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return ok.Load()
|
||||
}
|
||||
|
||||
// Chain concatenates this SeqMapPar with others, preserving full parallelism.
|
||||
// Each sequence runs with its own worker pool in parallel..
|
||||
func (p SeqMapPar[K, V]) Chain(others ...SeqMapPar[K, V]) SeqMapPar[K, V] {
|
||||
return SeqMapPar[K, V]{
|
||||
seq: func(yield func(K, V) bool) {
|
||||
done := make(chan struct{})
|
||||
result := make(chan Pair[K, V], 100)
|
||||
|
||||
var (
|
||||
wg sync.WaitGroup
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
runSequence := func(seq SeqMapPar[K, V]) {
|
||||
defer wg.Done()
|
||||
seq.Range(func(k K, v V) bool {
|
||||
select {
|
||||
case <-done:
|
||||
return false
|
||||
case result <- Pair[K, V]{Key: k, Value: v}:
|
||||
return true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer close(result)
|
||||
|
||||
wg.Add(1)
|
||||
go runSequence(p)
|
||||
|
||||
for _, o := range others {
|
||||
wg.Add(1)
|
||||
go runSequence(o)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
case pair, ok := <-result:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if !yield(pair.Key, pair.Value) {
|
||||
once.Do(func() { close(done) })
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
workers: p.workers,
|
||||
process: func(pair Pair[K, V]) (Pair[K, V], bool) { return pair, true },
|
||||
}
|
||||
}
|
||||
|
||||
// Collect gathers all processed pairs into a Map.
|
||||
func (p SeqMapPar[K, V]) Collect() Map[K, V] {
|
||||
ch := make(chan Pair[K, V])
|
||||
|
||||
go func() {
|
||||
defer close(ch)
|
||||
p.Range(func(k K, v V) bool {
|
||||
ch <- Pair[K, V]{Key: k, Value: v}
|
||||
return true
|
||||
})
|
||||
}()
|
||||
|
||||
m := NewMap[K, V]()
|
||||
for pair := range ch {
|
||||
m.Set(pair.Key, pair.Value)
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// Count returns the total number of processed pairs.
|
||||
func (p SeqMapPar[K, V]) Count() Int {
|
||||
var cnt atomic.Int64
|
||||
|
||||
p.Range(func(_ K, _ V) bool {
|
||||
cnt.Add(1)
|
||||
return true
|
||||
})
|
||||
|
||||
return Int(cnt.Load())
|
||||
}
|
||||
|
||||
// Exclude removes pairs where fn returns true.
|
||||
func (p SeqMapPar[K, V]) Exclude(fn func(K, V) bool) SeqMapPar[K, V] {
|
||||
return p.Filter(func(k K, v V) bool { return !fn(k, v) })
|
||||
}
|
||||
|
||||
// Filter retains only pairs where fn returns true.
|
||||
func (p SeqMapPar[K, V]) Filter(fn func(K, V) bool) SeqMapPar[K, V] {
|
||||
prev := p.process
|
||||
|
||||
return SeqMapPar[K, V]{
|
||||
seq: p.seq,
|
||||
workers: p.workers,
|
||||
process: func(pair Pair[K, V]) (Pair[K, V], bool) {
|
||||
if mid, ok := prev(pair); ok && fn(mid.Key, mid.Value) {
|
||||
return mid, true
|
||||
}
|
||||
return Pair[K, V]{}, false
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Find returns the first pair matching fn, or a zero Option if none.
|
||||
func (p SeqMapPar[K, V]) Find(fn func(K, V) bool) Option[Pair[K, V]] {
|
||||
ch := make(chan Pair[K, V])
|
||||
|
||||
go func() {
|
||||
defer close(ch)
|
||||
p.Range(func(k K, v V) bool {
|
||||
if fn(k, v) {
|
||||
ch <- Pair[K, V]{Key: k, Value: v}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
}()
|
||||
|
||||
if pair, ok := <-ch; ok {
|
||||
return Some(pair)
|
||||
}
|
||||
|
||||
return None[Pair[K, V]]()
|
||||
}
|
||||
|
||||
// ForEach invokes fn on each key/value pair for side-effects,
|
||||
// processing all pairs in parallel without early exit.
|
||||
func (p SeqMapPar[K, V]) ForEach(fn func(K, V)) {
|
||||
p.Range(func(k K, v V) bool {
|
||||
fn(k, v)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// Inspect invokes fn on each key/value pair for side-effects,
|
||||
// without modifying the resulting sequence.
|
||||
func (p SeqMapPar[K, V]) Inspect(fn func(K, V)) SeqMapPar[K, V] {
|
||||
prev := p.process
|
||||
|
||||
return SeqMapPar[K, V]{
|
||||
seq: p.seq,
|
||||
workers: p.workers,
|
||||
process: func(pair Pair[K, V]) (Pair[K, V], bool) {
|
||||
if mid, ok := prev(pair); ok {
|
||||
fn(mid.Key, mid.Value)
|
||||
return mid, true
|
||||
}
|
||||
return Pair[K, V]{}, false
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Map applies transform to each pair.
|
||||
func (p SeqMapPar[K, V]) Map(transform func(K, V) (K, V)) SeqMapPar[K, V] {
|
||||
prev := p.process
|
||||
|
||||
return SeqMapPar[K, V]{
|
||||
seq: p.seq,
|
||||
workers: p.workers,
|
||||
process: func(pair Pair[K, V]) (Pair[K, V], bool) {
|
||||
if mid, ok := prev(pair); ok {
|
||||
k2, v2 := transform(mid.Key, mid.Value)
|
||||
return Pair[K, V]{Key: k2, Value: v2}, true
|
||||
}
|
||||
return Pair[K, V]{}, false
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Range applies fn to each processed pair in parallel, stopping early if fn returns false.
|
||||
func (p SeqMapPar[K, V]) Range(fn func(K, V) bool) {
|
||||
in := make(chan Pair[K, V])
|
||||
done := make(chan struct{})
|
||||
|
||||
var (
|
||||
wg sync.WaitGroup
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
go func() {
|
||||
defer close(in)
|
||||
p.seq(func(k K, v V) bool {
|
||||
select {
|
||||
case <-done:
|
||||
return false
|
||||
case in <- Pair[K, V]{Key: k, Value: v}:
|
||||
return true
|
||||
}
|
||||
})
|
||||
}()
|
||||
|
||||
wg.Add(int(p.workers))
|
||||
for range p.workers {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for pair := range in {
|
||||
if mid, ok := p.process(pair); ok {
|
||||
if !fn(mid.Key, mid.Value) {
|
||||
once.Do(func() { close(done) })
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// Skip drops the first n pairs.
|
||||
func (p SeqMapPar[K, V]) Skip(n Int) SeqMapPar[K, V] {
|
||||
prev := p.process
|
||||
|
||||
return SeqMapPar[K, V]{
|
||||
seq: func(yield func(K, V) bool) {
|
||||
var cnt int64
|
||||
p.seq(func(k K, v V) bool {
|
||||
if atomic.AddInt64(&cnt, 1) > int64(n) {
|
||||
return yield(k, v)
|
||||
}
|
||||
return true
|
||||
})
|
||||
},
|
||||
workers: p.workers,
|
||||
process: prev,
|
||||
}
|
||||
}
|
||||
|
||||
// Take yields at most n pairs.
|
||||
func (p SeqMapPar[K, V]) Take(n Int) SeqMapPar[K, V] {
|
||||
prev := p.process
|
||||
|
||||
return SeqMapPar[K, V]{
|
||||
seq: func(yield func(K, V) bool) {
|
||||
var cnt int64
|
||||
p.seq(func(k K, v V) bool {
|
||||
if atomic.AddInt64(&cnt, 1) <= int64(n) {
|
||||
return yield(k, v)
|
||||
}
|
||||
return false
|
||||
})
|
||||
},
|
||||
workers: p.workers,
|
||||
process: prev,
|
||||
}
|
||||
}
|
||||
+145
-143
@@ -2,20 +2,43 @@ package g
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"slices"
|
||||
|
||||
"github.com/enetx/g/cmp"
|
||||
"github.com/enetx/g/f"
|
||||
"github.com/enetx/g/rand"
|
||||
)
|
||||
|
||||
// Pair is a key-value pair yielded by the key-value sequences.
|
||||
//
|
||||
// It is a defined struct rather than an alias so that g's iterator core owns
|
||||
// it outright and carries no non-stdlib dependency.
|
||||
type Pair[K, V any] struct {
|
||||
Key K
|
||||
Value V
|
||||
}
|
||||
|
||||
// Unpack returns the pair's key and value, enabling tuple-style destructuring.
|
||||
//
|
||||
// k, v := p.Unpack()
|
||||
//
|
||||
// The two-value result can also feed a (K, V) call site directly:
|
||||
//
|
||||
// yield(p.Unpack())
|
||||
func (p Pair[K, V]) Unpack() (K, V) { return p.Key, p.Value }
|
||||
|
||||
// MapOrd is an ordered map that maintains insertion order using a slice of
|
||||
// key-value pairs. Key lookups (Get, Insert, Contains, Remove, Entry) scan the
|
||||
// slice linearly and are therefore O(n); use Map for O(1) lookups when order is
|
||||
// not required.
|
||||
type MapOrd[K comparable, V any] []Pair[K, V] // ordered key-value pairs
|
||||
|
||||
// NewMapOrd creates a new ordered Map with the specified size (if provided).
|
||||
// An ordered Map is an Map that maintains the order of its key-value pairs based on the
|
||||
// insertion order. If no size is provided, the default size will be used.
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// - size ...int: (Optional) The initial size of the ordered Map. If not provided, a default size
|
||||
// - size ...Int: (Optional) The initial size of the ordered Map. If not provided, a default size
|
||||
// will be used.
|
||||
//
|
||||
// Returns:
|
||||
@@ -29,42 +52,39 @@ import (
|
||||
//
|
||||
// Creates a new ordered Map with an initial size of 10.
|
||||
func NewMapOrd[K comparable, V any](size ...Int) MapOrd[K, V] {
|
||||
return make(MapOrd[K, V], 0, Slice[Int](size).Get(0).UnwrapOrDefault())
|
||||
if len(size) > 0 {
|
||||
return make(MapOrd[K, V], 0, size[0])
|
||||
}
|
||||
|
||||
return make(MapOrd[K, V], 0)
|
||||
}
|
||||
|
||||
// Transform applies a transformation function to the MapOrd and returns the result.
|
||||
func (mo MapOrd[K, V]) Transform(fn func(MapOrd[K, V]) MapOrd[K, V]) MapOrd[K, V] { return fn(mo) }
|
||||
func (mo MapOrd[K, V]) Transform[U any](fn func(MapOrd[K, V]) U) U { return fn(mo) }
|
||||
|
||||
// Entry returns a MapOrdEntry object for the given key, providing fine-grained
|
||||
// control over insertion, mutation, and deletion of its value in the ordered Map,
|
||||
// while preserving the insertion order.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// mo := g.NewMapOrd[string, int]()
|
||||
// // Insert 1 if "foo" is absent, then increment it
|
||||
// e := mo.Entry("foo")
|
||||
// e.OrSet(1).
|
||||
// e.Transform(func(v int) int { return v + 1 })
|
||||
//
|
||||
// The entire operation requires only a single key lookup and works without
|
||||
// additional allocations.
|
||||
func (mo *MapOrd[K, V]) Entry(key K) MapOrdEntry[K, V] { return MapOrdEntry[K, V]{mo, key} }
|
||||
// Entry returns an OrdEntry for the given key.
|
||||
func (mo *MapOrd[K, V]) Entry(key K) OrdEntry[K, V] {
|
||||
if i := mo.index(key); i != -1 {
|
||||
return OccupiedOrdEntry[K, V]{mo: mo, key: key, idx: i}
|
||||
}
|
||||
|
||||
// Iter returns an iterator (SeqMapOrd[K, V]) for the ordered Map, allowing for sequential iteration
|
||||
return VacantOrdEntry[K, V]{mo: mo, key: key}
|
||||
}
|
||||
|
||||
// Iter returns an iterator (Seq2[K, V]) for the ordered Map, allowing for sequential iteration
|
||||
// over its key-value pairs. It is commonly used in combination with higher-order functions,
|
||||
// such as 'ForEach', to perform operations on each key-value pair of the ordered Map.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// A SeqMapOrd[K, V], which can be used for sequential iteration over the key-value pairs of the ordered Map.
|
||||
// A Seq2[K, V], which can be used for sequential iteration over the key-value pairs of the ordered Map.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// m := g.NewMapOrd[int, int]()
|
||||
// m.Set(1, 1)
|
||||
// m.Set(2, 2)
|
||||
// m.Set(3, 3).
|
||||
// m.Insert(1, 1)
|
||||
// m.Insert(2, 2)
|
||||
// m.Insert(3, 3)
|
||||
//
|
||||
// m.Iter().ForEach(func(k, v int) {
|
||||
// // Process key-value pair
|
||||
@@ -72,30 +92,30 @@ func (mo *MapOrd[K, V]) Entry(key K) MapOrdEntry[K, V] { return MapOrdEntry[K, V
|
||||
//
|
||||
// The 'Iter' method provides a convenient way to traverse the key-value pairs of an ordered Map
|
||||
// in a functional style, enabling operations like mapping or filtering.
|
||||
func (mo MapOrd[K, V]) Iter() SeqMapOrd[K, V] {
|
||||
func (mo MapOrd[K, V]) Iter() Seq2[K, V] {
|
||||
return func(yield func(K, V) bool) {
|
||||
for _, v := range mo {
|
||||
if !yield(v.Key, v.Value) {
|
||||
if !yield(v.Unpack()) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IterReverse returns an iterator (SeqMapOrd[K, V]) for the ordered Map that allows for sequential iteration
|
||||
// IterReverse returns an iterator (Seq2[K, V]) for the ordered Map that allows for sequential iteration
|
||||
// over its key-value pairs in reverse order. This method is useful when you need to process the elements
|
||||
// from the last to the first.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// A SeqMapOrd[K, V], which can be used for sequential iteration over the key-value pairs of the ordered Map in reverse order.
|
||||
// A Seq2[K, V], which can be used for sequential iteration over the key-value pairs of the ordered Map in reverse order.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// m := g.NewMapOrd[int, int]()
|
||||
// m.Set(1, 1)
|
||||
// m.Set(2, 2)
|
||||
// m.Set(3, 3)
|
||||
// m.Insert(1, 1)
|
||||
// m.Insert(2, 2)
|
||||
// m.Insert(3, 3)
|
||||
//
|
||||
// m.IterReverse().ForEach(func(k, v int) {
|
||||
// // Process key-value pair in reverse order
|
||||
@@ -104,38 +124,17 @@ func (mo MapOrd[K, V]) Iter() SeqMapOrd[K, V] {
|
||||
//
|
||||
// The 'IterReverse' method complements the 'Iter' method by providing a way to access the elements
|
||||
// in a reverse sequence, offering additional flexibility in data processing scenarios.
|
||||
func (mo MapOrd[K, V]) IterReverse() SeqMapOrd[K, V] {
|
||||
func (mo MapOrd[K, V]) IterReverse() Seq2[K, V] {
|
||||
return func(yield func(K, V) bool) {
|
||||
for i := len(mo) - 1; i >= 0; i-- {
|
||||
v := mo[i]
|
||||
if !yield(v.Key, v.Value) {
|
||||
if !yield(v.Unpack()) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MapOrdFromStd converts a standard Go map to an ordered Map.
|
||||
// The resulting ordered Map will maintain the order of its key-value pairs based on the order of
|
||||
// insertion.
|
||||
// This function is useful when you want to create an ordered Map from an existing Go map.
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// - m map[K]V: The input Go map to be converted to an ordered Map.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - MapOrd[K, V]: New ordered Map containing the same key-value pairs as the
|
||||
// input Go map.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// mapOrd := g.MapOrdFromStd[string, int](goMap)
|
||||
//
|
||||
// Converts the standard Go map 'map[K]V' to an ordered Map.
|
||||
func MapOrdFromStd[K comparable, V any](m map[K]V) MapOrd[K, V] { return Map[K, V](m).ToMapOrd() }
|
||||
|
||||
// SortBy sorts the ordered Map by a custom comparison function.
|
||||
//
|
||||
// Parameters:
|
||||
@@ -279,25 +278,9 @@ func (mo *MapOrd[K, V]) Copy(src MapOrd[K, V]) {
|
||||
}
|
||||
}
|
||||
|
||||
// ToMap converts the ordered Map to a standard Map.
|
||||
func (mo MapOrd[K, V]) ToMap() Map[K, V] {
|
||||
m := NewMap[K, V](mo.Len())
|
||||
mo.Iter().ForEach(func(k K, v V) { m[k] = v })
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// ToMapSafe converts a ordered Map to a thread-safe Map.
|
||||
func (mo MapOrd[K, V]) ToMapSafe() *MapSafe[K, V] {
|
||||
ms := NewMapSafe[K, V]()
|
||||
mo.Iter().ForEach(func(k K, v V) { ms.Set(k, v) })
|
||||
|
||||
return ms
|
||||
}
|
||||
|
||||
// Set sets the value for the specified key in the ordered Map,
|
||||
// Insert sets the value for the specified key in the ordered Map,
|
||||
// and returns the previous value if it existed.
|
||||
func (mo *MapOrd[K, V]) Set(key K, value V) Option[V] {
|
||||
func (mo *MapOrd[K, V]) Insert(key K, value V) Option[V] {
|
||||
if i := mo.index(key); i != -1 {
|
||||
prev := (*mo)[i].Value
|
||||
(*mo)[i].Value = value
|
||||
@@ -321,33 +304,6 @@ func (mo MapOrd[K, V]) Get(key K) Option[V] {
|
||||
|
||||
return None[V]()
|
||||
}
|
||||
|
||||
// Shuffle randomly reorders the elements of the ordered Map.
|
||||
// It operates in place and affects the original order of the map's entries.
|
||||
//
|
||||
// The function uses the crypto/rand package to generate random indices.
|
||||
func (mo MapOrd[K, V]) Shuffle() {
|
||||
for i := mo.Len() - 1; i > 0; i-- {
|
||||
j := rand.N(i + 1)
|
||||
mo[i], mo[j] = mo[j], mo[i]
|
||||
}
|
||||
}
|
||||
|
||||
// Invert inverts the key-value pairs in the ordered Map, creating a new ordered Map with the
|
||||
// values as keys and the original keys as values.
|
||||
func (mo MapOrd[K, V]) Invert() MapOrd[any, K] {
|
||||
if mo.Empty() {
|
||||
return NewMapOrd[any, K]()
|
||||
}
|
||||
|
||||
result := make(MapOrd[any, K], 0, len(mo))
|
||||
for _, pair := range mo {
|
||||
result = append(result, Pair[any, K]{Key: pair.Value, Value: pair.Key})
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (mo MapOrd[K, V]) index(key K) int {
|
||||
for i, mp := range mo {
|
||||
if mp.Key == key {
|
||||
@@ -359,40 +315,47 @@ func (mo MapOrd[K, V]) index(key K) int {
|
||||
}
|
||||
|
||||
// Keys returns an Slice containing all the keys in the ordered Map.
|
||||
func (mo MapOrd[K, V]) Keys() Slice[K] { return mo.Iter().Keys().Collect() }
|
||||
|
||||
// Values returns an Slice containing all the values in the ordered Map.
|
||||
func (mo MapOrd[K, V]) Values() Slice[V] { return mo.Iter().Values().Collect() }
|
||||
|
||||
// Delete removes the specified keys from the ordered Map.
|
||||
//
|
||||
// It preserves the original insertion order of the remaining elements
|
||||
// and performs the deletion in a single pass with O(n) complexity.
|
||||
//
|
||||
// Internally, it builds a set of keys to delete and reconstructs the map
|
||||
// without the removed entries. Key lookup is optimized via a map[K]int index.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// mo.Delete("a", "b", "c")
|
||||
func (mo *MapOrd[K, V]) Delete(keys ...K) {
|
||||
if len(keys) == 0 || mo.Empty() {
|
||||
return
|
||||
func (mo MapOrd[K, V]) Keys() Slice[K] {
|
||||
if len(mo) == 0 {
|
||||
return NewSlice[K]()
|
||||
}
|
||||
|
||||
idx := mo.indexMap()
|
||||
seen := SetOf(keys...)
|
||||
nmo := make(MapOrd[K, V], 0, len(*mo)-len(keys))
|
||||
keys := make(Slice[K], len(mo))
|
||||
for i, p := range mo {
|
||||
keys[i] = p.Key
|
||||
}
|
||||
|
||||
for _, p := range *mo {
|
||||
if !seen.Contains(p.Key) {
|
||||
nmo = append(nmo, p)
|
||||
} else {
|
||||
delete(idx, p.Key)
|
||||
return keys
|
||||
}
|
||||
|
||||
// Values returns an Slice containing all the values in the ordered Map.
|
||||
func (mo MapOrd[K, V]) Values() Slice[V] {
|
||||
if len(mo) == 0 {
|
||||
return NewSlice[V]()
|
||||
}
|
||||
|
||||
values := make(Slice[V], len(mo))
|
||||
for i, p := range mo {
|
||||
values[i] = p.Value
|
||||
}
|
||||
|
||||
return values
|
||||
}
|
||||
|
||||
// Remove removes the specified key from the ordered Map and returns the removed value.
|
||||
func (mo *MapOrd[K, V]) Remove(key K) Option[V] {
|
||||
if mo.IsEmpty() {
|
||||
return None[V]()
|
||||
}
|
||||
|
||||
for i, p := range *mo {
|
||||
if p.Key == key {
|
||||
*mo = slices.Delete(*mo, i, i+1)
|
||||
return Some(p.Value)
|
||||
}
|
||||
}
|
||||
|
||||
*mo = nmo
|
||||
return None[V]()
|
||||
}
|
||||
|
||||
// Eq compares the current ordered Map to another ordered Map and returns true if they are equal.
|
||||
@@ -406,19 +369,22 @@ func (mo MapOrd[K, V]) Eq(other MapOrd[K, V]) bool {
|
||||
|
||||
idx := other.indexMap()
|
||||
|
||||
var zero V
|
||||
comparable := f.IsComparable(zero)
|
||||
|
||||
comparable := isValueComparable[V]()
|
||||
for i, mp := range mo {
|
||||
j, ok := idx[mp.Key]
|
||||
|
||||
if !ok || j != i {
|
||||
return false
|
||||
}
|
||||
|
||||
value := other[j].Value
|
||||
|
||||
if comparable && !f.Eq[any](value)(mp.Value) || !comparable && !f.Eqd(value)(mp.Value) {
|
||||
return false
|
||||
if comparable {
|
||||
if any(other[j].Value) != any(mp.Value) {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
if !reflect.DeepEqual(other[j].Value, mp.Value) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -432,6 +398,7 @@ func (mo MapOrd[K, V]) String() string {
|
||||
}
|
||||
|
||||
var b Builder
|
||||
b.Grow(Int(len(mo)) * 16)
|
||||
b.WriteString("MapOrd{")
|
||||
|
||||
first := true
|
||||
@@ -441,7 +408,9 @@ func (mo MapOrd[K, V]) String() string {
|
||||
}
|
||||
|
||||
first = false
|
||||
b.WriteString(Format("{}:{}", pair.Key, pair.Value))
|
||||
fmt.Fprint(&b, pair.Key)
|
||||
b.WriteByte(':')
|
||||
fmt.Fprint(&b, pair.Value)
|
||||
}
|
||||
|
||||
b.WriteString("}")
|
||||
@@ -450,13 +419,16 @@ func (mo MapOrd[K, V]) String() string {
|
||||
}
|
||||
|
||||
// Clear removes all key-value pairs from the ordered Map.
|
||||
func (mo *MapOrd[K, V]) Clear() { *mo = (*mo)[:0] }
|
||||
func (mo *MapOrd[K, V]) Clear() {
|
||||
clear(*mo)
|
||||
*mo = (*mo)[:0]
|
||||
}
|
||||
|
||||
// Contains checks if the ordered Map contains the specified key.
|
||||
func (mo MapOrd[K, V]) Contains(key K) bool { return mo.index(key) != -1 }
|
||||
|
||||
// Empty checks if the ordered Map is empty.
|
||||
func (mo MapOrd[K, V]) Empty() bool { return len(mo) == 0 }
|
||||
// IsEmpty checks if the ordered Map is empty.
|
||||
func (mo MapOrd[K, V]) IsEmpty() bool { return len(mo) == 0 }
|
||||
|
||||
// Len returns the number of key-value pairs in the ordered Map.
|
||||
func (mo MapOrd[K, V]) Len() Int { return Int(len(mo)) }
|
||||
@@ -464,9 +436,6 @@ func (mo MapOrd[K, V]) Len() Int { return Int(len(mo)) }
|
||||
// Ne compares the current ordered Map to another ordered Map and returns true if they are not equal.
|
||||
func (mo MapOrd[K, V]) Ne(other MapOrd[K, V]) bool { return !mo.Eq(other) }
|
||||
|
||||
// NotEmpty checks if the ordered Map is not empty.
|
||||
func (mo MapOrd[K, V]) NotEmpty() bool { return !mo.Empty() }
|
||||
|
||||
// Print writes the key-value pairs of the MapOrd to the standard output (console)
|
||||
// and returns the MapOrd unchanged.
|
||||
func (mo MapOrd[K, V]) Print() MapOrd[K, V] { fmt.Print(mo); return mo }
|
||||
@@ -479,7 +448,8 @@ func (mo MapOrd[K, V]) Println() MapOrd[K, V] { fmt.Println(mo); return mo }
|
||||
//
|
||||
// This function is used to create a temporary indexMap that maps each key in the
|
||||
// ordered map to its position (insertion order) within the slice. It is useful
|
||||
// for optimizing lookup operations such as Set, Delete, Copy, or Eq.
|
||||
// for amortizing the cost of repeated lookups within a single bulk operation
|
||||
// such as Copy or Eq, where the per-key linear scan would otherwise be O(n^2).
|
||||
//
|
||||
// Time complexity: O(n), where n is the number of key-value pairs in the MapOrd.
|
||||
func (mo MapOrd[K, V]) indexMap() map[K]int {
|
||||
@@ -491,3 +461,35 @@ func (mo MapOrd[K, V]) indexMap() map[K]int {
|
||||
|
||||
return idx
|
||||
}
|
||||
|
||||
// PairOf creates a Pair from the provided key and value.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// p := g.PairOf("answer", 42) // Pair[string, int]
|
||||
func PairOf[K, V any](key K, value V) Pair[K, V] { return Pair[K, V]{Key: key, Value: value} }
|
||||
|
||||
// MapOrdOf creates a MapOrd from the provided key-value pairs, preserving their order.
|
||||
//
|
||||
// Duplicate keys keep their first-seen position, while the value is updated
|
||||
// to the most recent one (last-write-wins).
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// mo := g.MapOrdOf(g.PairOf("a", 1), g.PairOf("b", 2))
|
||||
func MapOrdOf[K comparable, V any](pairs ...Pair[K, V]) MapOrd[K, V] {
|
||||
mo := NewMapOrd[K, V](Int(len(pairs)))
|
||||
idx := make(map[K]int, len(pairs))
|
||||
|
||||
for _, p := range pairs {
|
||||
if i, ok := idx[p.Key]; ok {
|
||||
mo[i].Value = p.Value
|
||||
continue
|
||||
}
|
||||
|
||||
idx[p.Key] = len(mo)
|
||||
mo = append(mo, p)
|
||||
}
|
||||
|
||||
return mo
|
||||
}
|
||||
-71
@@ -1,71 +0,0 @@
|
||||
package g
|
||||
|
||||
import "slices"
|
||||
|
||||
// Get returns Some(value) if present, otherwise None.
|
||||
func (e MapOrdEntry[K, V]) Get() Option[V] {
|
||||
return e.mo.Get(e.key)
|
||||
}
|
||||
|
||||
// OrSet inserts value if the key is vacant.
|
||||
// Returns Some(existing_value) if key was present, None otherwise.
|
||||
func (e MapOrdEntry[K, V]) OrSet(value V) Option[V] {
|
||||
if i := e.mo.index(e.key); i != -1 {
|
||||
return Some((*e.mo)[i].Value)
|
||||
}
|
||||
|
||||
e.mo.Set(e.key, value)
|
||||
|
||||
return None[V]()
|
||||
}
|
||||
|
||||
// OrSetBy inserts the value produced by fn if the key is vacant.
|
||||
// Returns Some(existing_value) if key was present, None otherwise.
|
||||
func (e MapOrdEntry[K, V]) OrSetBy(fn func() V) Option[V] {
|
||||
if i := e.mo.index(e.key); i != -1 {
|
||||
return Some((*e.mo)[i].Value)
|
||||
}
|
||||
|
||||
e.mo.Set(e.key, fn())
|
||||
|
||||
return None[V]()
|
||||
}
|
||||
|
||||
// OrDefault inserts V's zero value if the key is vacant.
|
||||
// Returns Some(existing_value) if key was present, None otherwise.
|
||||
func (e MapOrdEntry[K, V]) OrDefault() Option[V] {
|
||||
var zero V
|
||||
return e.OrSet(zero)
|
||||
}
|
||||
|
||||
// Transform applies fn to the value if it exists.
|
||||
// Returns Some(updated_value) if key was present, None otherwise.
|
||||
func (e MapOrdEntry[K, V]) Transform(fn func(V) V) Option[V] {
|
||||
if i := e.mo.index(e.key); i != -1 {
|
||||
value := fn((*e.mo)[i].Value)
|
||||
(*e.mo)[i].Value = value
|
||||
|
||||
return Some(value)
|
||||
}
|
||||
|
||||
return None[V]()
|
||||
}
|
||||
|
||||
// Set sets the value for the specified key in the ordered map.
|
||||
// Returns Some(previous_value) if the key existed, or None if it was newly inserted.
|
||||
func (e MapOrdEntry[K, V]) Set(value V) Option[V] {
|
||||
return e.mo.Set(e.key, value)
|
||||
}
|
||||
|
||||
// Delete removes the key from the Map.
|
||||
// Returns Some(removed_value) if present, None otherwise.
|
||||
func (e MapOrdEntry[K, V]) Delete() Option[V] {
|
||||
if i := e.mo.index(e.key); i != -1 {
|
||||
value := (*e.mo)[i].Value
|
||||
*e.mo = slices.Delete(*e.mo, i, i+1)
|
||||
|
||||
return Some(value)
|
||||
}
|
||||
|
||||
return None[V]()
|
||||
}
|
||||
-549
@@ -1,549 +0,0 @@
|
||||
package g
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/enetx/g/cmp"
|
||||
"github.com/enetx/iter"
|
||||
)
|
||||
|
||||
// Pull converts the “push-style” iterator sequence seq
|
||||
// into a “pull-style” iterator accessed by the two functions
|
||||
// next and stop.
|
||||
//
|
||||
// Next returns the next pair in the sequence
|
||||
// and a boolean indicating whether the pair is valid.
|
||||
// When the sequence is over, next returns a pair of zero values and false.
|
||||
// It is valid to call next after reaching the end of the sequence
|
||||
// or after calling stop. These calls will continue
|
||||
// to return a pair of zero values and false.
|
||||
//
|
||||
// Stop ends the iteration. It must be called when the caller is
|
||||
// no longer interested in next values and next has not yet
|
||||
// signaled that the sequence is over (with a false boolean return).
|
||||
// It is valid to call stop multiple times and when next has
|
||||
// already returned false.
|
||||
//
|
||||
// It is an error to call next or stop from multiple goroutines
|
||||
// simultaneously.
|
||||
func (seq SeqMapOrd[K, V]) Pull() (func() (K, V, bool), func()) {
|
||||
return iter.Pull2(iter.Seq2[K, V](seq))
|
||||
}
|
||||
|
||||
// Keys returns an iterator containing all the keys in the ordered Map.
|
||||
func (seq SeqMapOrd[K, V]) Keys() SeqSlice[K] {
|
||||
return SeqSlice[K](iter.Keys(iter.Seq2[K, V](seq)))
|
||||
}
|
||||
|
||||
// Values returns an iterator containing all the values in the ordered Map.
|
||||
func (seq SeqMapOrd[K, V]) Values() SeqSlice[V] {
|
||||
return SeqSlice[V](iter.Values(iter.Seq2[K, V](seq)))
|
||||
}
|
||||
|
||||
// Unzip returns a tuple of slices containing keys and values from the ordered map.
|
||||
func (seq SeqMapOrd[K, V]) Unzip() (SeqSlice[K], SeqSlice[V]) { return seq.Keys(), seq.Values() }
|
||||
|
||||
// SortBy applies a custom sorting function to the elements in the iterator
|
||||
// and returns a new iterator containing the sorted elements.
|
||||
//
|
||||
// The sorting function 'fn' should take two arguments, 'a' and 'b', of type Pair[K, V],
|
||||
// and return true if 'a' should be ordered before 'b', and false otherwise.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// m := g.NewMapOrd[g.Int, g.String]()
|
||||
// m.
|
||||
// Set(6, "bb").
|
||||
// Set(0, "dd").
|
||||
// Set(1, "aa").
|
||||
// Set(5, "xx").
|
||||
// Set(2, "cc").
|
||||
// Set(3, "ff").
|
||||
// Set(4, "zz").
|
||||
// Iter().
|
||||
// SortBy(
|
||||
// func(a, b g.Pair[g.Int, g.String]) cmp.Ordering {
|
||||
// return a.Key.Cmp(b.Key)
|
||||
// // return a.Value.Cmp(b.Value)
|
||||
// }).
|
||||
// Collect().
|
||||
// Print()
|
||||
//
|
||||
// Output: MapOrd{0:dd, 1:aa, 2:cc, 3:ff, 4:zz, 5:xx, 6:bb}
|
||||
//
|
||||
// The returned iterator is of type SeqMapOrd[K, V], which implements the iterator
|
||||
// interface for further iteration over the sorted elements.
|
||||
func (seq SeqMapOrd[K, V]) SortBy(fn func(a, b Pair[K, V]) cmp.Ordering) SeqMapOrd[K, V] {
|
||||
return SeqMapOrd[K, V](
|
||||
iter.SortBy2(iter.Seq2[K, V](seq), func(a, b iter.Pair[K, V]) bool { return fn(a, b) == cmp.Less }),
|
||||
)
|
||||
}
|
||||
|
||||
// SortByKey applies a custom sorting function to the keys in the iterator
|
||||
// and returns a new iterator containing the sorted elements.
|
||||
//
|
||||
// The sorting function 'fn' should take two arguments, 'a' and 'b', of type K,
|
||||
// and return true if 'a' should be ordered before 'b', and false otherwise.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// m := g.NewMapOrd[g.Int, g.String]()
|
||||
// m.
|
||||
// Set(6, "bb").
|
||||
// Set(0, "dd").
|
||||
// Set(1, "aa").
|
||||
// Set(5, "xx").
|
||||
// Set(2, "cc").
|
||||
// Set(3, "ff").
|
||||
// Set(4, "zz").
|
||||
// Iter().
|
||||
// SortByKey(g.Int.Cmp).
|
||||
// Collect().
|
||||
// Print()
|
||||
//
|
||||
// Output: MapOrd{0:dd, 1:aa, 2:cc, 3:ff, 4:zz, 5:xx, 6:bb}
|
||||
func (seq SeqMapOrd[K, V]) SortByKey(fn func(a, b K) cmp.Ordering) SeqMapOrd[K, V] {
|
||||
return SeqMapOrd[K, V](iter.OrderByKey(iter.Seq2[K, V](seq), func(a, b K) bool { return fn(a, b) == cmp.Less }))
|
||||
}
|
||||
|
||||
// SortByValue applies a custom sorting function to the values in the iterator
|
||||
// and returns a new iterator containing the sorted elements.
|
||||
//
|
||||
// The sorting function 'fn' should take two arguments, 'a' and 'b', of type V,
|
||||
// and return true if 'a' should be ordered before 'b', and false otherwise.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// m := g.NewMapOrd[g.Int, g.String]()
|
||||
// m.
|
||||
// Set(6, "bb").
|
||||
// Set(0, "dd").
|
||||
// Set(1, "aa").
|
||||
// Set(5, "xx").
|
||||
// Set(2, "cc").
|
||||
// Set(3, "ff").
|
||||
// Set(4, "zz").
|
||||
// Iter().
|
||||
// SortByValue(g.String.Cmp).
|
||||
// Collect().
|
||||
// Print()
|
||||
//
|
||||
// Output: MapOrd{1:aa, 6:bb, 2:cc, 0:dd, 3:ff, 5:xx, 4:zz}
|
||||
func (seq SeqMapOrd[K, V]) SortByValue(fn func(a, b V) cmp.Ordering) SeqMapOrd[K, V] {
|
||||
return SeqMapOrd[K, V](iter.OrderByValue(iter.Seq2[K, V](seq), func(a, b V) bool { return fn(a, b) == cmp.Less }))
|
||||
}
|
||||
|
||||
// Inspect creates a new iterator that wraps around the current iterator
|
||||
// and allows inspecting each key-value pair as it passes through.
|
||||
func (seq SeqMapOrd[K, V]) Inspect(fn func(k K, v V)) SeqMapOrd[K, V] {
|
||||
return SeqMapOrd[K, V](iter.Inspect2(iter.Seq2[K, V](seq), fn))
|
||||
}
|
||||
|
||||
// StepBy creates a new iterator that iterates over every N-th element of the original iterator.
|
||||
// This function is useful when you want to skip a specific number of elements between each iteration.
|
||||
//
|
||||
// Parameters:
|
||||
// - n int: The step size, indicating how many elements to skip between each iteration.
|
||||
//
|
||||
// Returns:
|
||||
// - SeqMapOrd[K, V]: A new iterator that produces key-value pairs from the original iterator with a step size of N.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// mapIter := g.MapOrd[string, int]{{"one", 1}, {"two", 2}, {"three", 3}}.Iter()
|
||||
// iter := mapIter.StepBy(2)
|
||||
// result := iter.Collect()
|
||||
// result.Print()
|
||||
//
|
||||
// Output: MapOrd{one:1, three:3}
|
||||
//
|
||||
// The resulting iterator will produce key-value pairs from the original iterator with a step size of N.
|
||||
func (seq SeqMapOrd[K, V]) StepBy(n uint) SeqMapOrd[K, V] {
|
||||
return SeqMapOrd[K, V](iter.StepBy2(iter.Seq2[K, V](seq), int(n)))
|
||||
}
|
||||
|
||||
// Chain concatenates the current iterator with other iterators, returning a new iterator.
|
||||
//
|
||||
// The function creates a new iterator that combines the elements of the current iterator
|
||||
// with elements from the provided iterators in the order they are given.
|
||||
//
|
||||
// Params:
|
||||
//
|
||||
// - seqs ([]seqMapOrd[K, V]): Other iterators to be concatenated with the current iterator.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - SeqMapOrd[K, V]: A new iterator containing elements from the current iterator and the provided iterators.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// iter1 := g.NewMapOrd[int, string]()
|
||||
// iter1.Set(1, "a").Iter()
|
||||
//
|
||||
// iter2 := g.NewMapOrd[int, string]()
|
||||
// iter2.Set(2, "b").Iter()
|
||||
//
|
||||
// // Concatenating iterators and collecting the result.
|
||||
// iter1.Chain(iter2).Collect().Print()
|
||||
//
|
||||
// Output: MapOrd{1:a, 2:b}
|
||||
//
|
||||
// The resulting iterator will contain elements from both iterators in the specified order.
|
||||
func (seq SeqMapOrd[K, V]) Chain(seqs ...SeqMapOrd[K, V]) SeqMapOrd[K, V] {
|
||||
iterSeqs := make([]iter.Seq2[K, V], len(seqs))
|
||||
for i, s := range seqs {
|
||||
iterSeqs[i] = iter.Seq2[K, V](s)
|
||||
}
|
||||
|
||||
return SeqMapOrd[K, V](iter.Chain2(iter.Seq2[K, V](seq), iterSeqs...))
|
||||
}
|
||||
|
||||
// Count consumes the iterator, counting the number of iterations and returning it.
|
||||
func (seq SeqMapOrd[K, V]) Count() Int { return Int(iter.Count2(iter.Seq2[K, V](seq))) }
|
||||
|
||||
// Collect collects all key-value pairs from the iterator and returns a MapOrd.
|
||||
func (seq SeqMapOrd[K, V]) Collect() MapOrd[K, V] {
|
||||
collection := NewMapOrd[K, V]()
|
||||
|
||||
seq(func(k K, v V) bool {
|
||||
collection.Set(k, v)
|
||||
return true
|
||||
})
|
||||
|
||||
return collection
|
||||
}
|
||||
|
||||
// Skip returns a new iterator skipping the first n elements.
|
||||
//
|
||||
// The function creates a new iterator that skips the first n elements of the current iterator
|
||||
// and returns an iterator starting from the (n+1)th element.
|
||||
//
|
||||
// Params:
|
||||
//
|
||||
// - n (uint): The number of elements to skip from the beginning of the iterator.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - SeqMapOrd[K, V]: An iterator that starts after skipping the first n elements.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
|
||||
// iter := g.NewMapOrd[int, string]()
|
||||
// iter.
|
||||
// Set(1, "a").
|
||||
// Set(2, "b").
|
||||
// Set(3, "c").
|
||||
// Set(4, "d").
|
||||
// Iter()
|
||||
//
|
||||
// // Skipping the first two elements and collecting the rest.
|
||||
// iter.Skip(2).Collect().Print()
|
||||
//
|
||||
// Output: MapOrd{3:c, 4:d}
|
||||
//
|
||||
// The resulting iterator will start after skipping the specified number of elements.
|
||||
func (seq SeqMapOrd[K, V]) Skip(n uint) SeqMapOrd[K, V] {
|
||||
return SeqMapOrd[K, V](iter.Skip2(iter.Seq2[K, V](seq), int(n)))
|
||||
}
|
||||
|
||||
// Exclude returns a new iterator excluding elements that satisfy the provided function.
|
||||
//
|
||||
// The function creates a new iterator excluding elements from the current iterator
|
||||
// for which the provided function returns true.
|
||||
//
|
||||
// Params:
|
||||
//
|
||||
// - fn (func(K, V) bool): The function used to determine exclusion criteria for elements.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - SeqMapOrd[K, V]: A new iterator excluding elements that satisfy the given condition.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// mo := g.NewMapOrd[int, int]()
|
||||
// mo.
|
||||
// Set(1, 1).
|
||||
// Set(2, 2).
|
||||
// Set(3, 3).
|
||||
// Set(4, 4).
|
||||
// Set(5, 5)
|
||||
//
|
||||
// notEven := mo.Iter().
|
||||
// Exclude(
|
||||
// func(k, v int) bool {
|
||||
// return v%2 == 0
|
||||
// }).
|
||||
// Collect()
|
||||
// notEven.Print()
|
||||
//
|
||||
// Output: MapOrd{1:1, 3:3, 5:5}
|
||||
//
|
||||
// The resulting iterator will exclude elements based on the provided condition.
|
||||
func (seq SeqMapOrd[K, V]) Exclude(fn func(K, V) bool) SeqMapOrd[K, V] {
|
||||
return SeqMapOrd[K, V](iter.Exclude2(iter.Seq2[K, V](seq), fn))
|
||||
}
|
||||
|
||||
// Filter returns a new iterator containing only the elements that satisfy the provided function.
|
||||
//
|
||||
// The function creates a new iterator including elements from the current iterator
|
||||
// for which the provided function returns true.
|
||||
//
|
||||
// Params:
|
||||
//
|
||||
// - fn (func(K, V) bool): The function used to determine inclusion criteria for elements.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - SeqMapOrd[K, V]: A new iterator containing elements that satisfy the given condition.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// mo := g.NewMapOrd[int, int]()
|
||||
// mo.
|
||||
// Set(1, 1).
|
||||
// Set(2, 2).
|
||||
// Set(3, 3).
|
||||
// Set(4, 4).
|
||||
// Set(5, 5)
|
||||
//
|
||||
// even := mo.Iter().
|
||||
// Filter(
|
||||
// func(k, v int) bool {
|
||||
// return v%2 == 0
|
||||
// }).
|
||||
// Collect()
|
||||
// even.Print()
|
||||
//
|
||||
// Output: MapOrd{2:2, 4:4}
|
||||
//
|
||||
// The resulting iterator will include elements based on the provided condition.
|
||||
func (seq SeqMapOrd[K, V]) Filter(fn func(K, V) bool) SeqMapOrd[K, V] {
|
||||
return SeqMapOrd[K, V](iter.Filter2(iter.Seq2[K, V](seq), fn))
|
||||
}
|
||||
|
||||
// Find searches for an element in the iterator that satisfies the provided function.
|
||||
//
|
||||
// The function iterates through the elements of the iterator and returns the first element
|
||||
// for which the provided function returns true.
|
||||
//
|
||||
// Params:
|
||||
//
|
||||
// - fn (func(K, V) bool): The function used to test elements for a condition.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - Option[K, V]: An Option containing the first element that satisfies the condition; None if not found.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// m := g.NewMapOrd[int, int]()
|
||||
// m.Set(1, 1)
|
||||
// f := m.Iter().Find(func(_ int, v int) bool { return v == 1 })
|
||||
// if f.IsSome() {
|
||||
// print(f.Some().Key)
|
||||
// }
|
||||
//
|
||||
// The resulting Option may contain the first element that satisfies the condition, or None if not found.
|
||||
func (seq SeqMapOrd[K, V]) Find(fn func(k K, v V) bool) Option[Pair[K, V]] {
|
||||
key, value, found := iter.Find2(iter.Seq2[K, V](seq), fn)
|
||||
if found {
|
||||
return Some(Pair[K, V]{Key: key, Value: value})
|
||||
}
|
||||
|
||||
return None[Pair[K, V]]()
|
||||
}
|
||||
|
||||
// ForEach iterates through all elements and applies the given function to each key-value pair.
|
||||
//
|
||||
// The function applies the provided function to each key-value pair in the iterator.
|
||||
//
|
||||
// Params:
|
||||
//
|
||||
// - fn (func(K, V)): The function to be applied to each key-value pair in the iterator.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// iter := g.NewMapOrd[int, int]()
|
||||
// iter.
|
||||
// Set(1, 1).
|
||||
// Set(2, 2).
|
||||
// Set(3, 3).
|
||||
// Set(4, 4).
|
||||
// Set(5, 5).
|
||||
// Iter()
|
||||
//
|
||||
// iter.ForEach(func(key K, val V) {
|
||||
// // Process key-value pair
|
||||
// })
|
||||
//
|
||||
// The provided function will be applied to each key-value pair in the iterator.
|
||||
func (seq SeqMapOrd[K, V]) ForEach(fn func(k K, v V)) {
|
||||
iter.ForEach2(iter.Seq2[K, V](seq), fn)
|
||||
}
|
||||
|
||||
// Map creates a new iterator by applying the given function to each key-value pair.
|
||||
//
|
||||
// The function creates a new iterator by applying the provided function to each key-value pair in the iterator.
|
||||
//
|
||||
// Params:
|
||||
//
|
||||
// - fn (func(K, V) (K, V)): The function used to transform each key-value pair in the iterator.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - SeqMapOrd[K, V]: A new iterator containing transformed key-value pairs.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// mo := g.NewMapOrd[int, int]()
|
||||
// mo.
|
||||
// Set(1, 1).
|
||||
// Set(2, 2).
|
||||
// Set(3, 3).
|
||||
// Set(4, 4).
|
||||
// Set(5, 5)
|
||||
//
|
||||
// momap := mo.Iter().
|
||||
// Map(
|
||||
// func(k, v int) (int, int) {
|
||||
// return k * k, v * v
|
||||
// }).
|
||||
// Collect()
|
||||
//
|
||||
// momap.Print()
|
||||
//
|
||||
// Output: MapOrd{1:1, 4:4, 9:9, 16:16, 25:25}
|
||||
//
|
||||
// The resulting iterator will contain transformed key-value pairs.
|
||||
func (seq SeqMapOrd[K, V]) Map(transform func(K, V) (K, V)) SeqMapOrd[K, V] {
|
||||
return SeqMapOrd[K, V](iter.Map2(iter.Seq2[K, V](seq), transform))
|
||||
}
|
||||
|
||||
// Range iterates through elements until the given function returns false.
|
||||
//
|
||||
// The function iterates through the key-value pairs in the iterator, applying the provided function to each pair.
|
||||
// It continues iterating until the function returns false.
|
||||
//
|
||||
// Params:
|
||||
//
|
||||
// - fn (func(K, V) bool): The function to be applied to each key-value pair in the iterator.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// iter := g.NewMapOrd[int, int]()
|
||||
// iter.
|
||||
// Set(1, 1).
|
||||
// Set(2, 2).
|
||||
// Set(3, 3).
|
||||
// Set(4, 4).
|
||||
// Set(5, 5).
|
||||
// Iter()
|
||||
//
|
||||
// iter.Range(func(k, v int) bool {
|
||||
// fmt.Println(v) // Replace this with the function logic you need.
|
||||
// return v < 5 // Replace this with the condition for continuing iteration.
|
||||
// })
|
||||
//
|
||||
// The iteration will stop when the provided function returns false.
|
||||
func (seq SeqMapOrd[K, V]) Range(fn func(k K, v V) bool) {
|
||||
iter.Range2(iter.Seq2[K, V](seq), fn)
|
||||
}
|
||||
|
||||
// Context allows the iteration to be controlled with a context.Context.
|
||||
func (seq SeqMapOrd[K, V]) Context(ctx context.Context) SeqMapOrd[K, V] {
|
||||
return SeqMapOrd[K, V](iter.Context2(iter.Seq2[K, V](seq), ctx))
|
||||
}
|
||||
|
||||
// Take returns a new iterator with the first n elements.
|
||||
// The function creates a new iterator containing the first n elements from the original iterator.
|
||||
func (seq SeqMapOrd[K, V]) Take(n uint) SeqMapOrd[K, V] {
|
||||
return SeqMapOrd[K, V](iter.Take2(iter.Seq2[K, V](seq), int(n)))
|
||||
}
|
||||
|
||||
// First returns the first key-value pair from the sequence.
|
||||
func (seq SeqMapOrd[K, V]) First() Option[Pair[K, V]] {
|
||||
if key, value, ok := iter.First2(iter.Seq2[K, V](seq)); ok {
|
||||
return Some(Pair[K, V]{Key: key, Value: value})
|
||||
}
|
||||
|
||||
return None[Pair[K, V]]()
|
||||
}
|
||||
|
||||
// Last returns the last key-value pair from the sequence.
|
||||
func (seq SeqMapOrd[K, V]) Last() Option[Pair[K, V]] {
|
||||
if key, value, ok := iter.Last2(iter.Seq2[K, V](seq)); ok {
|
||||
return Some(Pair[K, V]{Key: key, Value: value})
|
||||
}
|
||||
|
||||
return None[Pair[K, V]]()
|
||||
}
|
||||
|
||||
// Nth returns the nth key-value pair (0-indexed) in the sequence.
|
||||
func (seq SeqMapOrd[K, V]) Nth(n Int) Option[Pair[K, V]] {
|
||||
key, value, found := iter.Nth2(iter.Seq2[K, V](seq), int(n))
|
||||
if found {
|
||||
return Some(Pair[K, V]{Key: key, Value: value})
|
||||
}
|
||||
|
||||
return None[Pair[K, V]]()
|
||||
}
|
||||
|
||||
// ToChan converts the iterator into a channel, optionally with context(s).
|
||||
//
|
||||
// The function converts the key-value pairs from the iterator into a channel, allowing iterative processing
|
||||
// using channels. It can be used to stream key-value pairs for concurrent or asynchronous operations.
|
||||
//
|
||||
// Params:
|
||||
//
|
||||
// - ctxs (...context.Context): Optional context(s) that can be used to cancel or set deadlines for the operation.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - chan Pair[K, V]: A channel emitting key-value pairs from the iterator.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// iter := g.NewMapOrd[int, int]()
|
||||
// iter.
|
||||
// Set(1, 1).
|
||||
// Set(2, 2).
|
||||
// Set(3, 3).
|
||||
// Set(4, 4).
|
||||
// Set(5, 5).
|
||||
// Iter()
|
||||
//
|
||||
// ctx, cancel := context.WithCancel(context.Background())
|
||||
// defer cancel() // Ensure cancellation to avoid goroutine leaks.
|
||||
//
|
||||
// ch := iter.ToChan(ctx)
|
||||
// for pair := range ch {
|
||||
// // Process key-value pair from the channel
|
||||
// }
|
||||
//
|
||||
// The function converts the iterator into a channel to allow sequential or concurrent processing of key-value pairs.
|
||||
func (seq SeqMapOrd[K, V]) ToChan(ctxs ...context.Context) chan Pair[K, V] {
|
||||
ctx := context.Background()
|
||||
if len(ctxs) > 0 {
|
||||
ctx = ctxs[0]
|
||||
}
|
||||
|
||||
return iter.ToChan2(iter.Seq2[K, V](seq), ctx)
|
||||
}
|
||||
|
||||
// Next extracts the next key-value pair from the iterator and advances it.
|
||||
//
|
||||
// This method consumes the next key-value pair from the iterator and returns them wrapped in an Option.
|
||||
// The iterator itself is modified to point to the remaining elements.
|
||||
//
|
||||
// Returns:
|
||||
// - Option[Pair[K, V]]: Some(Pair{Key, Value}) if a pair exists, None if the iterator is exhausted.
|
||||
func (seq *SeqMapOrd[K, V]) Next() Option[Pair[K, V]] {
|
||||
if key, value, remaining, ok := iter.Next2(iter.Seq2[K, V](*seq)); ok {
|
||||
*seq = SeqMapOrd[K, V](remaining)
|
||||
return Some(Pair[K, V]{Key: key, Value: value})
|
||||
}
|
||||
|
||||
return None[Pair[K, V]]()
|
||||
}
|
||||
+116
-60
@@ -2,15 +2,39 @@ package g
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/enetx/g/f"
|
||||
"reflect"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// MapSafe is a concurrent-safe generic map built on sync.Map.
|
||||
type MapSafe[K comparable, V any] struct {
|
||||
data sync.Map
|
||||
count atomic.Int64
|
||||
structMu sync.RWMutex // coordinates key-presence changes with Clear
|
||||
}
|
||||
|
||||
// NewMapSafe creates a new instance of MapSafe.
|
||||
func NewMapSafe[K comparable, V any]() *MapSafe[K, V] { return &MapSafe[K, V]{} }
|
||||
|
||||
// MapSafeOf creates a new MapSafe from the provided key-value pairs.
|
||||
// Duplicate keys keep the last-written value, mirroring MapOf / MapOrdOf.
|
||||
func MapSafeOf[K comparable, V any](pairs ...Pair[K, V]) *MapSafe[K, V] {
|
||||
ms := NewMapSafe[K, V]()
|
||||
for _, p := range pairs {
|
||||
ms.Insert(p.Unpack())
|
||||
}
|
||||
|
||||
return ms
|
||||
}
|
||||
|
||||
// Transform applies a transformation function to the MapSafe and returns the result.
|
||||
func (ms *MapSafe[K, V]) Transform[U any](fn func(*MapSafe[K, V]) U) U {
|
||||
return fn(ms)
|
||||
}
|
||||
|
||||
// Iter provides a thread-safe iterator over the MapSafe's key-value pairs.
|
||||
func (ms *MapSafe[K, V]) Iter() SeqMap[K, V] {
|
||||
func (ms *MapSafe[K, V]) Iter() Seq2[K, V] {
|
||||
return func(yield func(K, V) bool) {
|
||||
ms.data.Range(func(key, value any) bool {
|
||||
return yield(key.(K), *(value.(*V)))
|
||||
@@ -18,14 +42,18 @@ func (ms *MapSafe[K, V]) Iter() SeqMap[K, V] {
|
||||
}
|
||||
}
|
||||
|
||||
// Entry returns a MapSafeEntry for a given key, allowing for more complex atomic operations.
|
||||
func (ms *MapSafe[K, V]) Entry(key K) MapSafeEntry[K, V] {
|
||||
return MapSafeEntry[K, V]{m: ms, key: key}
|
||||
// Entry returns a SafeEntry for the given key.
|
||||
func (ms *MapSafe[K, V]) Entry(key K) SafeEntry[K, V] {
|
||||
if _, ok := ms.data.Load(key); ok {
|
||||
return OccupiedSafeEntry[K, V]{m: ms, key: key}
|
||||
}
|
||||
|
||||
return VacantSafeEntry[K, V]{m: ms, key: key}
|
||||
}
|
||||
|
||||
// Keys returns a slice of the MapSafe's keys.
|
||||
func (ms *MapSafe[K, V]) Keys() Slice[K] {
|
||||
var keys Slice[K]
|
||||
keys := NewSlice[K](0, ms.Len())
|
||||
|
||||
ms.data.Range(func(key, _ any) bool {
|
||||
keys = append(keys, key.(K))
|
||||
@@ -37,7 +65,7 @@ func (ms *MapSafe[K, V]) Keys() Slice[K] {
|
||||
|
||||
// Values returns a slice of the MapSafe's values.
|
||||
func (ms *MapSafe[K, V]) Values() Slice[V] {
|
||||
var values Slice[V]
|
||||
values := NewSlice[V](0, ms.Len())
|
||||
|
||||
ms.data.Range(func(_, value any) bool {
|
||||
values = append(values, *(value.(*V)))
|
||||
@@ -47,19 +75,6 @@ func (ms *MapSafe[K, V]) Values() Slice[V] {
|
||||
return values
|
||||
}
|
||||
|
||||
// Invert inverts keys and values. The new map will also follow the pointer-storage rule.
|
||||
func (ms *MapSafe[K, V]) Invert() *MapSafe[any, K] {
|
||||
res := NewMapSafe[any, K]()
|
||||
|
||||
ms.data.Range(func(key, value any) bool {
|
||||
k := key.(K)
|
||||
res.data.Store(*(value.(*V)), &k)
|
||||
return true
|
||||
})
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// Contains checks if the MapSafe contains the specified key.
|
||||
func (ms *MapSafe[K, V]) Contains(key K) bool {
|
||||
_, ok := ms.data.Load(key)
|
||||
@@ -73,6 +88,7 @@ func (ms *MapSafe[K, V]) Clone() *MapSafe[K, V] {
|
||||
ms.data.Range(func(key, value any) bool {
|
||||
v := *(value.(*V))
|
||||
res.data.Store(key, &v)
|
||||
res.count.Add(1)
|
||||
return true
|
||||
})
|
||||
|
||||
@@ -81,18 +97,31 @@ func (ms *MapSafe[K, V]) Clone() *MapSafe[K, V] {
|
||||
|
||||
// Copy performs a deep copy of the source MapSafe's pairs into the current map.
|
||||
func (ms *MapSafe[K, V]) Copy(src *MapSafe[K, V]) {
|
||||
ms.structMu.RLock()
|
||||
defer ms.structMu.RUnlock()
|
||||
|
||||
src.data.Range(func(key, value any) bool {
|
||||
v := *(value.(*V))
|
||||
ms.data.Store(key, &v)
|
||||
_, loaded := ms.data.Swap(key, &v)
|
||||
if !loaded {
|
||||
ms.count.Add(1)
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// Delete removes the specified keys from the MapSafe.
|
||||
func (ms *MapSafe[K, V]) Delete(keys ...K) {
|
||||
for _, k := range keys {
|
||||
ms.data.Delete(k)
|
||||
// Remove removes the specified key from the MapSafe and returns the removed value.
|
||||
func (ms *MapSafe[K, V]) Remove(key K) Option[V] {
|
||||
ms.structMu.RLock()
|
||||
defer ms.structMu.RUnlock()
|
||||
|
||||
if v, loaded := ms.data.LoadAndDelete(key); loaded {
|
||||
ms.count.Add(-1)
|
||||
return Some(*(v.(*V)))
|
||||
}
|
||||
|
||||
return None[V]()
|
||||
}
|
||||
|
||||
// Eq checks if two MapSafes are equal by deep-comparing their values.
|
||||
@@ -106,9 +135,7 @@ func (ms *MapSafe[K, V]) Eq(other *MapSafe[K, V]) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
var zero V
|
||||
comparable := f.IsComparable(zero)
|
||||
|
||||
comparable := isValueComparable[V]()
|
||||
equal := true
|
||||
|
||||
ms.data.Range(func(key, value any) bool {
|
||||
@@ -121,9 +148,16 @@ func (ms *MapSafe[K, V]) Eq(other *MapSafe[K, V]) bool {
|
||||
v1 := *(value.(*V))
|
||||
v2 := *(ovalue.(*V))
|
||||
|
||||
if comparable && !f.Eq[any](v1)(v2) || !comparable && !f.Eqd(v1)(v2) {
|
||||
equal = false
|
||||
return false
|
||||
if comparable {
|
||||
if any(v1) != any(v2) {
|
||||
equal = false
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
if !reflect.DeepEqual(v1, v2) {
|
||||
equal = false
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
@@ -141,51 +175,70 @@ func (ms *MapSafe[K, V]) Get(key K) Option[V] {
|
||||
return None[V]()
|
||||
}
|
||||
|
||||
// Set stores the value for the given key, returning the previous value if it existed.
|
||||
func (ms *MapSafe[K, V]) Set(key K, value V) Option[V] {
|
||||
// Insert stores the value for the given key.
|
||||
// Returns Some(previous_value) if the key existed, None if it was newly inserted.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// ms := NewMapSafe[string, int]()
|
||||
// ms.Insert("a", 1) // None (new key)
|
||||
// ms.Insert("a", 2) // Some(1) (replaced)
|
||||
// ms.Get("a").Some() // 2
|
||||
func (ms *MapSafe[K, V]) Insert(key K, value V) Option[V] {
|
||||
ms.structMu.RLock()
|
||||
defer ms.structMu.RUnlock()
|
||||
|
||||
if previous, loaded := ms.data.Swap(key, &value); loaded {
|
||||
return Some(*(previous.(*V)))
|
||||
}
|
||||
|
||||
ms.count.Add(1)
|
||||
return None[V]()
|
||||
}
|
||||
|
||||
// TryInsert inserts value only if the key is absent.
|
||||
// Returns Some(existing_value) if key already existed (no insert), None if inserted.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// ms := NewMapSafe[string, int]()
|
||||
// ms.TryInsert("a", 1) // None (inserted)
|
||||
// ms.TryInsert("a", 2) // Some(1) (already existed, not replaced)
|
||||
// ms.Get("a").Some() // 1
|
||||
func (ms *MapSafe[K, V]) TryInsert(key K, value V) Option[V] {
|
||||
ms.structMu.RLock()
|
||||
defer ms.structMu.RUnlock()
|
||||
|
||||
if actual, loaded := ms.data.LoadOrStore(key, &value); loaded {
|
||||
return Some(*(actual.(*V)))
|
||||
}
|
||||
|
||||
ms.count.Add(1)
|
||||
return None[V]()
|
||||
}
|
||||
|
||||
// Len returns the number of key-value pairs in the MapSafe.
|
||||
func (ms *MapSafe[K, V]) Len() int {
|
||||
count := 0
|
||||
|
||||
ms.data.Range(func(_, _ any) bool {
|
||||
count++
|
||||
return true
|
||||
})
|
||||
|
||||
return count
|
||||
}
|
||||
func (ms *MapSafe[K, V]) Len() Int { return Int(ms.count.Load()) }
|
||||
|
||||
// Ne checks if two MapSafes are not equal.
|
||||
func (ms *MapSafe[K, V]) Ne(other *MapSafe[K, V]) bool { return !ms.Eq(other) }
|
||||
|
||||
// NotEmpty checks if the MapSafe is not empty.
|
||||
func (ms *MapSafe[K, V]) NotEmpty() bool { return !ms.Empty() }
|
||||
|
||||
// Clear removes all key-value pairs from the MapSafe.
|
||||
func (ms *MapSafe[K, V]) Clear() { ms.data.Clear() }
|
||||
func (ms *MapSafe[K, V]) Clear() {
|
||||
ms.structMu.Lock()
|
||||
defer ms.structMu.Unlock()
|
||||
|
||||
// Empty checks if the MapSafe is empty.
|
||||
func (ms *MapSafe[K, V]) Empty() bool {
|
||||
empty := true
|
||||
|
||||
ms.data.Range(func(_, _ any) bool {
|
||||
empty = false
|
||||
return false
|
||||
})
|
||||
|
||||
return empty
|
||||
ms.data.Clear()
|
||||
ms.count.Store(0)
|
||||
}
|
||||
|
||||
// IsEmpty checks if the MapSafe is empty.
|
||||
func (ms *MapSafe[K, V]) IsEmpty() bool { return ms.count.Load() == 0 }
|
||||
|
||||
// String returns a string representation of the MapSafe.
|
||||
func (ms *MapSafe[K, V]) String() string {
|
||||
var b Builder
|
||||
b.Grow(ms.Len() * 16)
|
||||
b.WriteString("MapSafe{")
|
||||
|
||||
first := true
|
||||
@@ -198,9 +251,12 @@ func (ms *MapSafe[K, V]) String() string {
|
||||
first = false
|
||||
|
||||
if vptr, ok := value.(*V); ok && vptr != nil {
|
||||
b.WriteString(Format("{}:{}", key, *vptr))
|
||||
fmt.Fprint(&b, key)
|
||||
b.WriteByte(':')
|
||||
fmt.Fprint(&b, *vptr)
|
||||
} else {
|
||||
b.WriteString(Format("{}:<invalid>", key))
|
||||
fmt.Fprint(&b, key)
|
||||
b.WriteString(":<invalid>")
|
||||
}
|
||||
|
||||
return true
|
||||
|
||||
-76
@@ -1,76 +0,0 @@
|
||||
package g
|
||||
|
||||
import "github.com/enetx/g/ref"
|
||||
|
||||
// Get returns Some(value) if the key exists, otherwise None.
|
||||
func (e MapSafeEntry[K, V]) Get() Option[V] {
|
||||
return e.m.Get(e.key)
|
||||
}
|
||||
|
||||
// Set unconditionally sets the value for the key.
|
||||
// Returns Some(old_value) if the key was already present, otherwise None.
|
||||
func (e MapSafeEntry[K, V]) Set(value V) Option[V] {
|
||||
return e.m.Set(e.key, value)
|
||||
}
|
||||
|
||||
// Delete atomically retrieves and removes the value for the key from the map.
|
||||
// Returns Some(value) if it existed, otherwise None.
|
||||
func (e MapSafeEntry[K, V]) Delete() Option[V] {
|
||||
if value, loaded := e.m.data.LoadAndDelete(e.key); loaded {
|
||||
return Some(*(value.(*V)))
|
||||
}
|
||||
|
||||
return None[V]()
|
||||
}
|
||||
|
||||
// OrSet inserts `value` if the key is vacant.
|
||||
// Returns the value that is in the map after the operation (either the old or the new one).
|
||||
func (e MapSafeEntry[K, V]) OrSet(value V) Option[V] {
|
||||
actual, loaded := e.m.data.LoadOrStore(e.key, &value)
|
||||
if loaded {
|
||||
return Some(*(actual.(*V)))
|
||||
}
|
||||
|
||||
return None[V]()
|
||||
}
|
||||
|
||||
// OrSetBy inserts the value produced by `fn` if the key is vacant. `fn` is only called if needed.
|
||||
// Returns the value that is in the map after the operation.
|
||||
func (e MapSafeEntry[K, V]) OrSetBy(fn func() V) Option[V] {
|
||||
if actual, loaded := e.m.data.Load(e.key); loaded {
|
||||
return Some(*(actual.(*V)))
|
||||
}
|
||||
|
||||
if actual, loaded := e.m.data.LoadOrStore(e.key, ref.Of(fn())); loaded {
|
||||
return Some(*(actual.(*V)))
|
||||
}
|
||||
|
||||
return None[V]()
|
||||
}
|
||||
|
||||
// OrDefault inserts V's zero value if the key is vacant.
|
||||
// Returns the value that is in the map after the operation.
|
||||
func (e MapSafeEntry[K, V]) OrDefault() Option[V] {
|
||||
var zero V
|
||||
return e.OrSet(zero)
|
||||
}
|
||||
|
||||
// Transform atomically applies `fn` to the existing value if present.
|
||||
// The function `fn` takes the old value and returns the new value.
|
||||
// This operation is implemented using a lock-free Compare-And-Swap (CAS) loop.
|
||||
// Returns Some(updated_value) if successful, or None if the key was missing.
|
||||
func (e MapSafeEntry[K, V]) Transform(fn func(V) V) Option[V] {
|
||||
for {
|
||||
avalue, ok := e.m.data.Load(e.key)
|
||||
if !ok {
|
||||
return None[V]()
|
||||
}
|
||||
|
||||
ovalue := avalue.(*V)
|
||||
nvalue := fn(*ovalue)
|
||||
|
||||
if e.m.data.CompareAndSwap(e.key, ovalue, &nvalue) {
|
||||
return Some(nvalue)
|
||||
}
|
||||
}
|
||||
}
|
||||
+227
-34
@@ -7,6 +7,17 @@ import (
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// Option is a generic struct for representing an optional value.
|
||||
type Option[T any] struct {
|
||||
v T // Value.
|
||||
isSome bool // Indicator of value presence.
|
||||
}
|
||||
|
||||
// Unit represents an empty value.
|
||||
// Used in contexts where a function needs to return "something" but
|
||||
// the actual value doesn't matter, only success/failure status.
|
||||
type Unit struct{}
|
||||
|
||||
// Some creates an Option containing a value.
|
||||
func Some[T any](value T) Option[T] { return Option[T]{v: value, isSome: true} }
|
||||
|
||||
@@ -24,21 +35,14 @@ func OptionOf[T any](value T, ok bool) Option[T] {
|
||||
return None[T]()
|
||||
}
|
||||
|
||||
// TransformOption applies the given function to the value inside the Option, producing a new Option with the transformed value.
|
||||
// If the input Option is None, the output Option will also be None.
|
||||
// Parameters:
|
||||
// - o: The input Option to map over.
|
||||
// - fn: The function that returns an Option to apply to the value inside the Option.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// A new Option with the transformed value, or None if the input was None.
|
||||
func TransformOption[T, U any](o Option[T], fn func(T) Option[U]) Option[U] {
|
||||
if o.isSome {
|
||||
return fn(o.v)
|
||||
// OptionFromPtr converts a pointer into an Option.
|
||||
// Returns None if ptr is nil.
|
||||
func OptionFromPtr[T any](ptr *T) Option[T] {
|
||||
if ptr == nil {
|
||||
return None[T]()
|
||||
}
|
||||
|
||||
return None[U]()
|
||||
return Some(*ptr)
|
||||
}
|
||||
|
||||
// Some returns the contained value of the Option.
|
||||
@@ -54,20 +58,27 @@ func (o Option[T]) IsSome() bool { return o.isSome }
|
||||
// IsNone returns true if the Option represents no value.
|
||||
func (o Option[T]) IsNone() bool { return !o.isSome }
|
||||
|
||||
// optionPanic prints caller information for msg to stderr and then panics with msg.
|
||||
// skip is the number of stack frames between the original caller and runtime.Caller,
|
||||
// so that the reported file:line and function point at the user's call site rather
|
||||
// than at this helper.
|
||||
func optionPanic(skip int, msg string) {
|
||||
if pc, file, line, ok := runtime.Caller(skip); ok {
|
||||
out := fmt.Sprintf("[%s:%d] [%s] %s", filepath.Base(file), line, runtime.FuncForPC(pc).Name(), msg)
|
||||
fmt.Fprintln(os.Stderr, out)
|
||||
}
|
||||
|
||||
panic(msg)
|
||||
}
|
||||
|
||||
// Unwrap returns the value held in the Option. If the Option is None, it panics.
|
||||
func (o Option[T]) Unwrap() T {
|
||||
if o.isSome {
|
||||
return o.v
|
||||
}
|
||||
|
||||
const panicMsg = "called Option.Unwrap() on a None value"
|
||||
|
||||
if pc, file, line, ok := runtime.Caller(1); ok {
|
||||
out := fmt.Sprintf("[%s:%d] [%s] %s", filepath.Base(file), line, runtime.FuncForPC(pc).Name(), panicMsg)
|
||||
fmt.Fprintln(os.Stderr, out)
|
||||
}
|
||||
|
||||
panic(panicMsg)
|
||||
optionPanic(2, "called Option.Unwrap() on a None value")
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// UnwrapOr returns the value held in the Option. If the Option is None, it returns the provided default value.
|
||||
@@ -95,32 +106,208 @@ func (o Option[T]) Expect(msg string) T {
|
||||
return o.v
|
||||
}
|
||||
|
||||
out := fmt.Sprintf("Expect() failed: %s", msg)
|
||||
fmt.Fprintln(os.Stderr, out)
|
||||
panic(out)
|
||||
optionPanic(2, fmt.Sprintf("Expect() failed: %s", msg))
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// Then applies the function fn to the value inside the Option and returns a new Option.
|
||||
// If the Option is None, it returns the same Option without applying fn.
|
||||
func (o Option[T]) Then(fn func(T) Option[T]) Option[T] {
|
||||
// Then applies the function fn to the value inside the Option and returns the resulting Option.
|
||||
// If the Option is None, fn is not called and None is returned.
|
||||
// The result type may differ from the input type.
|
||||
func (o Option[T]) Then[U any](fn func(T) Option[U]) Option[U] {
|
||||
if o.isSome {
|
||||
return fn(o.v)
|
||||
}
|
||||
|
||||
return None[U]()
|
||||
}
|
||||
|
||||
// ThenOf applies fn to the value inside the Option and returns a new Option based
|
||||
// on the returned (U, bool) comma-ok tuple: ok=true yields Some(value), ok=false
|
||||
// yields None. If the Option is None, fn is not called and None is returned.
|
||||
// It mirrors Result.ThenOf for the comma-ok idiom.
|
||||
func (o Option[T]) ThenOf[U any](fn func(T) (U, bool)) Option[U] {
|
||||
if o.isSome {
|
||||
return OptionOf(fn(o.v))
|
||||
}
|
||||
|
||||
return None[U]()
|
||||
}
|
||||
|
||||
// Map applies the function fn to the value inside the Option and returns a new Option
|
||||
// holding the transformed value. If the Option is None, fn is not called and None is returned.
|
||||
// Unlike Then, fn returns a plain U (always Some on a Some input) rather than an Option[U].
|
||||
func (o Option[T]) Map[U any](fn func(T) U) Option[U] {
|
||||
if o.isSome {
|
||||
return Some(fn(o.v))
|
||||
}
|
||||
|
||||
return None[U]()
|
||||
}
|
||||
|
||||
// MapOr applies fn to the contained value if Some and returns the result;
|
||||
// otherwise returns the provided default value.
|
||||
func (o Option[T]) MapOr[U any](def U, fn func(T) U) U {
|
||||
if o.isSome {
|
||||
return fn(o.v)
|
||||
}
|
||||
|
||||
return def
|
||||
}
|
||||
|
||||
// MapOrElse applies fn to the contained value if Some and returns the result;
|
||||
// otherwise computes and returns the default lazily via defFn.
|
||||
func (o Option[T]) MapOrElse[U any](defFn func() U, fn func(T) U) U {
|
||||
if o.isSome {
|
||||
return fn(o.v)
|
||||
}
|
||||
|
||||
return defFn()
|
||||
}
|
||||
|
||||
// Inspect calls fn with the contained value if the Option is Some, then returns
|
||||
// the Option unchanged. If the Option is None, fn is not called. It is intended
|
||||
// for side effects (logging, debugging) within a chain and never mutates the Option.
|
||||
func (o Option[T]) Inspect(fn func(T)) Option[T] {
|
||||
if o.isSome {
|
||||
fn(o.v)
|
||||
}
|
||||
|
||||
return o
|
||||
}
|
||||
|
||||
// Result converts an Option into a Result.
|
||||
// If the Option is Some, it returns an Ok Result with the value.
|
||||
// If the Option is None, it returns an Err Result with the provided error.
|
||||
func (o Option[T]) Result(err error) Result[T] {
|
||||
if o.isSome {
|
||||
return Ok(o.v)
|
||||
// Filter returns Some(value) if the Option is Some and the predicate returns true.
|
||||
// Otherwise, it returns None.
|
||||
func (o Option[T]) Filter(pred func(T) bool) Option[T] {
|
||||
if o.isSome && pred(o.v) {
|
||||
return o
|
||||
}
|
||||
|
||||
return Err[T](err)
|
||||
return None[T]()
|
||||
}
|
||||
|
||||
// Or returns the Option if it contains a value.
|
||||
// Otherwise, it returns the provided alternative Option.
|
||||
func (o Option[T]) Or(other Option[T]) Option[T] {
|
||||
if o.isSome {
|
||||
return o
|
||||
}
|
||||
|
||||
return other
|
||||
}
|
||||
|
||||
// OrElse returns the Option if it contains a value.
|
||||
// Otherwise, it calls fn and returns its result.
|
||||
func (o Option[T]) OrElse(fn func() Option[T]) Option[T] {
|
||||
if o.isSome {
|
||||
return o
|
||||
}
|
||||
|
||||
return fn()
|
||||
}
|
||||
|
||||
// And returns None if the Option is None, otherwise returns other.
|
||||
// It is the eager counterpart of Then (which calls a function instead).
|
||||
func (o Option[T]) And[U any](other Option[U]) Option[U] {
|
||||
if o.isSome {
|
||||
return other
|
||||
}
|
||||
|
||||
return None[U]()
|
||||
}
|
||||
|
||||
// Xor returns Some if exactly one of the two Options is Some, otherwise None.
|
||||
func (o Option[T]) Xor(other Option[T]) Option[T] {
|
||||
if o.isSome == other.isSome {
|
||||
return None[T]()
|
||||
}
|
||||
|
||||
if o.isSome {
|
||||
return o
|
||||
}
|
||||
|
||||
return other
|
||||
}
|
||||
|
||||
// IsSomeAnd returns true if the Option is Some
|
||||
// and the predicate returns true for the contained value.
|
||||
func (o Option[T]) IsSomeAnd(pred func(T) bool) bool {
|
||||
return o.isSome && pred(o.v)
|
||||
}
|
||||
|
||||
// Insert inserts the given value into the Option,
|
||||
// replacing any existing value, and returns a pointer
|
||||
// to the inserted value.
|
||||
func (o *Option[T]) Insert(value T) *T {
|
||||
o.v = value
|
||||
o.isSome = true
|
||||
|
||||
return &o.v
|
||||
}
|
||||
|
||||
// GetOrInsert inserts the given value if the Option is None,
|
||||
// and returns a pointer to the contained value.
|
||||
// If the Option already contains a value, it is left unchanged.
|
||||
func (o *Option[T]) GetOrInsert(value T) *T {
|
||||
if !o.isSome {
|
||||
o.v = value
|
||||
o.isSome = true
|
||||
}
|
||||
return &o.v
|
||||
}
|
||||
|
||||
// GetOrInsertWith inserts a value computed by fn if the Option is None,
|
||||
// and returns a pointer to the contained value.
|
||||
// The function fn is evaluated lazily.
|
||||
func (o *Option[T]) GetOrInsertWith(fn func() T) *T {
|
||||
if !o.isSome {
|
||||
o.v = fn()
|
||||
o.isSome = true
|
||||
}
|
||||
|
||||
return &o.v
|
||||
}
|
||||
|
||||
// Take takes the value out of the Option, leaving None in its place.
|
||||
// It returns Some(value) if the Option was Some,
|
||||
// otherwise returns None.
|
||||
func (o *Option[T]) Take() Option[T] {
|
||||
if !o.isSome {
|
||||
return None[T]()
|
||||
}
|
||||
|
||||
val := o.v
|
||||
var zero T
|
||||
|
||||
o.v = zero
|
||||
o.isSome = false
|
||||
|
||||
return Some(val)
|
||||
}
|
||||
|
||||
// Replace replaces the contained value with the given value,
|
||||
// returning the old value as an Option.
|
||||
// If the Option was None, it inserts the value and returns None.
|
||||
func (o *Option[T]) Replace(value T) Option[T] {
|
||||
old := o.Take()
|
||||
o.v = value
|
||||
o.isSome = true
|
||||
|
||||
return old
|
||||
}
|
||||
|
||||
// Ptr returns a pointer to the contained value if Some.
|
||||
// Otherwise, it returns nil.
|
||||
func (o Option[T]) Ptr() *T {
|
||||
if o.isSome {
|
||||
return &o.v
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Option returns the contained value and a boolean reporting whether the Option is Some,
|
||||
// conforming to the standard Go comma-ok pattern.
|
||||
// If the Option is None, it returns the zero value for T and false.
|
||||
func (o Option[T]) Option() (T, bool) {
|
||||
if o.IsSome() {
|
||||
return o.Some(), true
|
||||
@@ -130,6 +317,12 @@ func (o Option[T]) Option() (T, bool) {
|
||||
return zero, false
|
||||
}
|
||||
|
||||
// IsNoneOr returns true if the Option is None or the predicate returns true
|
||||
// for the contained value. It is the complement of IsSomeAnd.
|
||||
func (o Option[T]) IsNoneOr(pred func(T) bool) bool {
|
||||
return !o.isSome || pred(o.v)
|
||||
}
|
||||
|
||||
// String returns a string representation of the Option.
|
||||
// If the Option contains a value, it returns a string in the format "Some(value)".
|
||||
// Otherwise, it returns "None".
|
||||
|
||||
+377
-124
@@ -7,10 +7,15 @@ import (
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
"github.com/enetx/g/f"
|
||||
)
|
||||
|
||||
// Formattable lets a type handle g.Format specifications without reflection.
|
||||
// The spec is the text after ':' without the colon; an empty spec requests the
|
||||
// type's default representation.
|
||||
type Formattable interface {
|
||||
FormatValue(spec String) String
|
||||
}
|
||||
|
||||
// Write formats according to a format specifier and writes to w.
|
||||
// It returns a Result containing the number of bytes written or an error.
|
||||
//
|
||||
@@ -30,7 +35,7 @@ func Write[T ~string](w io.Writer, format T, args ...any) Result[int] {
|
||||
// res := g.Writeln(os.Stdout, "Hello, {}", "world")
|
||||
// if res.IsErr() { log.Fatal(res.Err()) }
|
||||
func Writeln[T ~string](w io.Writer, format T, args ...any) Result[int] {
|
||||
return ResultOf(io.WriteString(w, Format(format, args...).Append("\n").Std()))
|
||||
return ResultOf(io.WriteString(w, formatTemplate(format, args, "\n").Std()))
|
||||
}
|
||||
|
||||
// Print formats according to a format specifier and writes to os.Stdout.
|
||||
@@ -74,19 +79,33 @@ func Eprintln[T ~string](format T, args ...any) Result[int] {
|
||||
}
|
||||
|
||||
// Errorf formats according to a format specifier and returns it as an error.
|
||||
// If any argument is referenced via the {:w} format verb, it is both displayed
|
||||
// and wrapped into the returned error, making errors.Is and errors.As work
|
||||
// through the chain. Multiple {:w} references wrap multiple errors (Go 1.20+).
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// err := g.Errorf("could not open {}: {}", filename, err)
|
||||
// if err != nil { /* ... */ }
|
||||
// err := g.Errorf("could not open {1}: {2:w}", filename, err)
|
||||
// errors.Is(err, os.ErrNotExist) // true
|
||||
func Errorf[T ~string](format T, args ...any) error {
|
||||
return errors.New(Format(format, args...).Std())
|
||||
tmpl := String(format)
|
||||
named, positional := formatArgs(args)
|
||||
|
||||
var wraps []error
|
||||
|
||||
msg := parseTmpl(tmpl, named, positional, &wraps)
|
||||
|
||||
if len(wraps) == 0 {
|
||||
return errors.New(msg.Std())
|
||||
}
|
||||
|
||||
return &wrappedError{msg: msg.Std(), errs: wraps}
|
||||
}
|
||||
|
||||
// Format processes a template string and replaces placeholders with corresponding values from the provided arguments.
|
||||
// It supports numeric, named, and auto-indexed placeholders, as well as dynamic invocation of methods on values.
|
||||
// It supports numeric, named, and auto-indexed placeholders, with dot-access into map keys, slice/array indices, and struct fields.
|
||||
//
|
||||
// If a placeholder cannot resolve a value or an invoked method fails, the placeholder remains unchanged in the output.
|
||||
// If a placeholder cannot resolve a value, it remains unchanged in the output.
|
||||
//
|
||||
// Parameters:
|
||||
// - template (T ~string): A string containing placeholders enclosed in `{}`.
|
||||
@@ -96,17 +115,26 @@ func Errorf[T ~string](format T, args ...any) error {
|
||||
//
|
||||
// Placeholder Forms:
|
||||
// - Numeric: `{1}`, `{2}` - References positional arguments by their 1-based index.
|
||||
// - Named: `{key}`, `{key.MethodName(param1, param2)}` - References keys from a `Named` map and allows method invocation.
|
||||
// - Fallback: `{key?fallback}` - Uses `fallback` if the key is not found in the named map.
|
||||
// - Named: `{key}`, `{key.field}`, `{key.0}` - References keys from a `Named` map with data access into struct fields, map/MapOrd keys, and slice indices (methods are never invoked).
|
||||
// - Fallback: `{key?fallback}` - If `key` is not found in the named map, uses the value
|
||||
// of the named key `fallback` instead (not the literal text).
|
||||
// - Auto-index: `{}` - Automatically uses the next positional argument if the placeholder is empty.
|
||||
// - Escaping: `\{` and `\}` - Escapes literal braces in the template string.
|
||||
// - Escaping: `{{` and `}}` - Emits literal braces.
|
||||
//
|
||||
// Returns:
|
||||
// - String: A formatted string with all resolved placeholders replaced by their corresponding values.
|
||||
//
|
||||
// Notes:
|
||||
// - If a placeholder cannot resolve a value (e.g., missing key or out-of-range index), it remains unchanged in the output.
|
||||
// - Method invocation supports any type with accessible methods. If the method or its parameters are invalid, the value remains unmodified.
|
||||
// - Dot-segments access data only (struct fields, map keys, MapOrd keys, slice/array indices); methods are never invoked. An unresolvable segment leaves the value unmodified.
|
||||
// - Only a single `Named` map is used for named placeholders. If multiple `Named`
|
||||
// maps are passed in args, the last one silently wins; merge them into one map
|
||||
// beforehand if you need keys from several sources.
|
||||
//
|
||||
// Security:
|
||||
// - Placeholders resolve data only: map keys, MapOrd keys, slice/array indices,
|
||||
// and struct fields. Methods are never invoked, so a template cannot execute
|
||||
// code on the supplied arguments.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
@@ -120,27 +148,185 @@ func Errorf[T ~string](format T, args ...any) error {
|
||||
// }
|
||||
// result := g.Format("My name is {name} and I am {age} years old.", named)
|
||||
//
|
||||
// // Example 3: Method invocation on values
|
||||
// result := g.Format("Hex: {1.Hex}, Binary: {1.Binary}", g.Int(255))
|
||||
// // Example 3: Field/key access
|
||||
// type User struct{ Name g.String }
|
||||
// user := User{Name: "Alice"}
|
||||
// result := g.Format("Name: {1.Name}", user)
|
||||
//
|
||||
// // Example 4: Fallbacks and chaining
|
||||
// // Example 4: Fallbacks
|
||||
// named := g.Named{
|
||||
// "name": g.String(" john "),
|
||||
// "name": g.String("John"),
|
||||
// "city": g.String("New York"),
|
||||
// }
|
||||
// result := g.Format("Hello, {name.Trim.Title}. Welcome to {city?Unknown}!", named)
|
||||
// result := g.Format("Hello, {name}. Welcome to {city?Unknown}!", named)
|
||||
func Format[T ~string](template T, args ...any) String {
|
||||
return formatTemplate(template, args, "")
|
||||
}
|
||||
|
||||
// FormatTo formats template and appends the result to builder without resetting it.
|
||||
// It is intended for allocation-sensitive code that reuses a Builder across calls.
|
||||
func FormatTo[T ~string](builder *Builder, template T, args ...any) {
|
||||
named, positional := formatArgs(args)
|
||||
parseTmplInto(builder, String(template), named, positional, nil, "")
|
||||
}
|
||||
|
||||
// TryFormat validates template structure and argument resolution before
|
||||
// formatting. Unlike Format, it returns an error for unmatched braces,
|
||||
// missing values, malformed modifiers, and unsupported format verbs.
|
||||
func TryFormat[T ~string](template T, args ...any) (result Result[String]) {
|
||||
defer func() {
|
||||
if recovered := recover(); recovered != nil {
|
||||
result = Err[String](fmt.Errorf("format: panic: %v", recovered))
|
||||
}
|
||||
}()
|
||||
|
||||
tmpl := String(template)
|
||||
named, positional := formatArgs(args)
|
||||
if err := validateFormatTemplate(tmpl, named, positional); err != nil {
|
||||
return Err[String](err)
|
||||
}
|
||||
|
||||
var (
|
||||
named Named
|
||||
positional Slice[any]
|
||||
)
|
||||
return Ok(parseTmpl(tmpl, named, positional, nil))
|
||||
}
|
||||
|
||||
// TryFormatTo validates and formats into a temporary buffer, appending to
|
||||
// builder only on success so an error never leaves a partial result behind.
|
||||
func TryFormatTo[T ~string](builder *Builder, template T, args ...any) Result[Unit] {
|
||||
result := TryFormat(template, args...)
|
||||
if result.IsErr() {
|
||||
return Err[Unit](result.Err())
|
||||
}
|
||||
|
||||
builder.WriteString(result.Ok())
|
||||
return Ok(Unit{})
|
||||
}
|
||||
|
||||
func validateFormatTemplate(tmpl String, named Named, positional Slice[any]) error {
|
||||
length := tmpl.Len()
|
||||
var autoidx, idx Int
|
||||
|
||||
for idx < length {
|
||||
char := tmpl[idx]
|
||||
if idx+1 < length && ((char == '{' && tmpl[idx+1] == '{') ||
|
||||
(char == '}' && tmpl[idx+1] == '}')) {
|
||||
idx += 2
|
||||
continue
|
||||
}
|
||||
|
||||
if char == '}' {
|
||||
return fmt.Errorf("format: unmatched closing brace at byte %d", idx)
|
||||
}
|
||||
if char != '{' {
|
||||
idx++
|
||||
continue
|
||||
}
|
||||
|
||||
cidx := tmpl[idx+1:].Index("}")
|
||||
if cidx.IsNegative() {
|
||||
return fmt.Errorf("format: unmatched opening brace at byte %d", idx)
|
||||
}
|
||||
|
||||
eidx := idx + 1 + cidx
|
||||
placeholder, spec := splitFmtSpec(tmpl[idx+1 : eidx])
|
||||
|
||||
trimmed := placeholder.Trim()
|
||||
if trimmed.IsEmpty() || trimmed[0] == '.' {
|
||||
autoidx++
|
||||
if autoidx > positional.Len() {
|
||||
return fmt.Errorf("format: missing automatic argument %d", autoidx)
|
||||
}
|
||||
if _, custom := positional[autoidx-1].(Formattable); !custom && !spec.IsEmpty() && !validFmtSpec(spec) {
|
||||
return fmt.Errorf("format: invalid format specifier %q", spec)
|
||||
}
|
||||
if !trimmed.IsEmpty() && !validModifierChain(trimmed[1:]) {
|
||||
return fmt.Errorf("format: malformed modifier chain %q", trimmed[1:])
|
||||
}
|
||||
idx = eidx + 1
|
||||
continue
|
||||
}
|
||||
|
||||
keyfall, mods := placeholder, String("")
|
||||
if dot := placeholder.Index("."); !dot.IsNegative() {
|
||||
keyfall, mods = placeholder[:dot], placeholder[dot+1:]
|
||||
}
|
||||
key, fall := keyfall, String("")
|
||||
if q := keyfall.Index("?"); !q.IsNegative() {
|
||||
key, fall = keyfall[:q], keyfall[q+1:]
|
||||
}
|
||||
value := resolveValue(key, fall, named, positional)
|
||||
if value == nil {
|
||||
return fmt.Errorf("format: unresolved placeholder %q", placeholder)
|
||||
}
|
||||
if _, custom := value.(Formattable); !custom && !spec.IsEmpty() && !validFmtSpec(spec) {
|
||||
return fmt.Errorf("format: invalid format specifier %q", spec)
|
||||
}
|
||||
if !validModifierChain(mods) {
|
||||
return fmt.Errorf("format: malformed modifier chain %q", mods)
|
||||
}
|
||||
|
||||
idx = eidx + 1
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validModifierChain(mods String) bool {
|
||||
valid := true
|
||||
forEachMod(mods, func(segment String) {
|
||||
if !valid {
|
||||
return
|
||||
}
|
||||
open := segment.Index("(")
|
||||
close := segment.LastIndex(")")
|
||||
if open.IsNegative() != close.IsNegative() || (!open.IsNegative() && close != segment.Len()-1) {
|
||||
valid = false
|
||||
}
|
||||
})
|
||||
|
||||
return valid
|
||||
}
|
||||
|
||||
func formatTemplate[T ~string](template T, args []any, suffix String) String {
|
||||
tmpl := String(template)
|
||||
named, positional := formatArgs(args)
|
||||
|
||||
return parseTmplSuffix(tmpl, named, positional, nil, suffix)
|
||||
}
|
||||
|
||||
// formatArgs separates the optional Named argument from positional arguments.
|
||||
// The overwhelmingly common case contains neither Named nor nil, so reuse the
|
||||
// caller-provided variadic slice instead of allocating and copying it.
|
||||
func formatArgs(args []any) (Named, Slice[any]) {
|
||||
var named Named
|
||||
|
||||
needsCopy, positionalLen := false, 0
|
||||
for _, arg := range args {
|
||||
switch x := arg.(type) {
|
||||
case Named:
|
||||
named = x
|
||||
needsCopy = true
|
||||
case nil:
|
||||
needsCopy = true
|
||||
positionalLen++
|
||||
default:
|
||||
positionalLen++
|
||||
}
|
||||
}
|
||||
|
||||
if !needsCopy {
|
||||
return named, Slice[any](args)
|
||||
}
|
||||
|
||||
if positionalLen == 0 {
|
||||
return named, nil
|
||||
}
|
||||
|
||||
positional := make(Slice[any], 0, positionalLen)
|
||||
for _, arg := range args {
|
||||
switch x := arg.(type) {
|
||||
case Named:
|
||||
// Named arguments are metadata, not positional values. The last one
|
||||
// wins, matching the existing public contract.
|
||||
case nil:
|
||||
positional = append(positional, "<nil>")
|
||||
default:
|
||||
@@ -148,26 +334,32 @@ func Format[T ~string](template T, args ...any) String {
|
||||
}
|
||||
}
|
||||
|
||||
return parseTmpl(tmpl, named, positional)
|
||||
return named, positional
|
||||
}
|
||||
|
||||
func parseTmpl(tmpl String, named Named, positional Slice[any]) String {
|
||||
func parseTmpl(tmpl String, named Named, positional Slice[any], wraps *[]error) String {
|
||||
return parseTmplSuffix(tmpl, named, positional, wraps, "")
|
||||
}
|
||||
|
||||
func parseTmplSuffix(tmpl String, named Named, positional Slice[any], wraps *[]error, suffix String) String {
|
||||
var builder Builder
|
||||
parseTmplInto(&builder, tmpl, named, positional, wraps, suffix)
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
func parseTmplInto(builder *Builder, tmpl String, named Named, positional Slice[any], wraps *[]error, suffix String) {
|
||||
length := tmpl.Len()
|
||||
builder.Grow(length)
|
||||
builder.Grow(length + suffix.Len())
|
||||
|
||||
var autoidx, idx Int
|
||||
|
||||
for idx < length {
|
||||
char := tmpl[idx]
|
||||
if char == '\\' && idx+1 < length {
|
||||
next := tmpl[idx+1]
|
||||
if next == '{' || next == '}' {
|
||||
builder.WriteByte(next)
|
||||
idx += 2
|
||||
|
||||
continue
|
||||
}
|
||||
if idx+1 < length && ((char == '{' && tmpl[idx+1] == '{') ||
|
||||
(char == '}' && tmpl[idx+1] == '}')) {
|
||||
builder.WriteByte(char)
|
||||
idx += 2
|
||||
continue
|
||||
}
|
||||
|
||||
if char == '{' {
|
||||
@@ -182,15 +374,54 @@ func parseTmpl(tmpl String, named Named, positional Slice[any]) String {
|
||||
eidx := idx + 1 + cidx
|
||||
placeholder := tmpl[idx+1 : eidx]
|
||||
|
||||
// extract format spec before auto-index check
|
||||
var fmtSuffix String
|
||||
if ci := findUnparenColon(placeholder); ci >= 0 {
|
||||
fmtSuffix = placeholder[ci:] // includes ':'
|
||||
placeholder = placeholder[:ci]
|
||||
}
|
||||
|
||||
trimmed := placeholder.Trim()
|
||||
if trimmed.Empty() || trimmed[0] == '.' {
|
||||
if trimmed.IsEmpty() || trimmed[0] == '.' {
|
||||
autoidx++
|
||||
if autoidx <= positional.Len() {
|
||||
placeholder = autoidx.String() + trimmed
|
||||
mods := trimmed
|
||||
if !mods.IsEmpty() {
|
||||
mods = mods[1:]
|
||||
}
|
||||
|
||||
formatSpec := fmtSuffix
|
||||
if !formatSpec.IsEmpty() {
|
||||
formatSpec = formatSpec[1:]
|
||||
}
|
||||
|
||||
value := positional[autoidx-1]
|
||||
if mods.IsEmpty() && formatSpec.IsEmpty() {
|
||||
writeFormatValue(builder, value)
|
||||
} else if !mods.IsEmpty() ||
|
||||
!tryAppendNativeSpec(builder, value, parseFmtSpec(formatSpec)) {
|
||||
builder.WriteString(formatResolved(value, mods, formatSpec, wraps))
|
||||
}
|
||||
|
||||
idx = eidx + 1
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
replaced := processPlaceholder(placeholder, named, positional)
|
||||
// re-attach format spec
|
||||
if !fmtSuffix.IsEmpty() {
|
||||
placeholder += fmtSuffix
|
||||
}
|
||||
|
||||
if fmtSuffix.IsEmpty() && placeholder.Index(".").IsNegative() && placeholder.Index("?").IsNegative() {
|
||||
if value := resolveValue(placeholder, "", named, positional); value != nil {
|
||||
writeFormatValue(builder, value)
|
||||
idx = eidx + 1
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
replaced := processPlaceholder(placeholder, named, positional, wraps)
|
||||
builder.WriteString(replaced)
|
||||
|
||||
idx = eidx + 1
|
||||
@@ -200,10 +431,26 @@ func parseTmpl(tmpl String, named Named, positional Slice[any]) String {
|
||||
}
|
||||
}
|
||||
|
||||
return builder.String()
|
||||
builder.WriteString(suffix)
|
||||
}
|
||||
|
||||
func processPlaceholder(placeholder String, named Named, positional Slice[any]) String {
|
||||
func writeFormatValue(builder *Builder, value any) {
|
||||
switch v := value.(type) {
|
||||
case Formattable:
|
||||
builder.WriteString(v.FormatValue(""))
|
||||
case string:
|
||||
builder.WriteString(String(v))
|
||||
case String:
|
||||
builder.WriteString(v)
|
||||
default:
|
||||
builder.WriteString(String(fmt.Sprint(value)))
|
||||
}
|
||||
}
|
||||
|
||||
func processPlaceholder(placeholder String, named Named, positional Slice[any], wraps *[]error) String {
|
||||
// split off format spec
|
||||
placeholder, formatSpec := splitFmtSpec(placeholder)
|
||||
|
||||
var (
|
||||
keyfall String
|
||||
mods String
|
||||
@@ -211,14 +458,14 @@ func processPlaceholder(placeholder String, named Named, positional Slice[any])
|
||||
fall String
|
||||
)
|
||||
|
||||
if idx := placeholder.Index("."); idx.IsPositive() {
|
||||
if idx := placeholder.Index("."); !idx.IsNegative() {
|
||||
keyfall = placeholder[:idx]
|
||||
mods = placeholder[idx+1:]
|
||||
} else {
|
||||
keyfall = placeholder
|
||||
}
|
||||
|
||||
if idx := keyfall.Index("?"); idx.IsPositive() {
|
||||
if idx := keyfall.Index("?"); !idx.IsNegative() {
|
||||
key = keyfall[:idx]
|
||||
fall = keyfall[idx+1:]
|
||||
} else {
|
||||
@@ -230,35 +477,77 @@ func processPlaceholder(placeholder String, named Named, positional Slice[any])
|
||||
return "{" + placeholder + "}"
|
||||
}
|
||||
|
||||
if mods.NotEmpty() {
|
||||
mods.
|
||||
Split(".").
|
||||
Exclude(f.IsZero).
|
||||
ForEach(func(segment String) {
|
||||
name, params := parseMod(segment)
|
||||
value = applyMod(value, name, params)
|
||||
})
|
||||
return formatResolved(value, mods, formatSpec, wraps)
|
||||
}
|
||||
|
||||
func formatResolved(value any, mods, formatSpec String, wraps *[]error) String {
|
||||
if !mods.IsEmpty() {
|
||||
forEachMod(mods, func(segment String) {
|
||||
name, _ := parseMod(segment)
|
||||
value = applyMod(value, name)
|
||||
})
|
||||
}
|
||||
|
||||
if formattable, ok := value.(Formattable); ok {
|
||||
return formattable.FormatValue(formatSpec)
|
||||
}
|
||||
|
||||
if !formatSpec.IsEmpty() {
|
||||
spec := parseFmtSpec(formatSpec)
|
||||
if spec.verb == 'w' {
|
||||
if wraps != nil {
|
||||
if err, ok := value.(error); ok {
|
||||
*wraps = append(*wraps, err)
|
||||
}
|
||||
}
|
||||
|
||||
return String(fmt.Sprint(value))
|
||||
}
|
||||
|
||||
return applyFmtSpec(value, spec)
|
||||
}
|
||||
|
||||
return String(fmt.Sprint(value))
|
||||
}
|
||||
|
||||
func resolveValue(key, fall String, named Named, positional Slice[any]) any {
|
||||
if num := key.ToInt(); num.IsOk() {
|
||||
idx := num.v - 1
|
||||
if idx.IsNegative() || idx.Gte(positional.Len()) {
|
||||
return nil
|
||||
// forEachMod scans a modifier chain without constructing an iterator, a slice
|
||||
// of segments, or closures for Filter/ForEach.
|
||||
func forEachMod(mods String, fn func(String)) {
|
||||
start := Int(0)
|
||||
for i := Int(0); i <= mods.Len(); i++ {
|
||||
if i < mods.Len() && mods[i] != '.' {
|
||||
continue
|
||||
}
|
||||
|
||||
return positional[idx]
|
||||
if segment := mods[start:i]; !segment.IsEmpty() {
|
||||
fn(segment)
|
||||
}
|
||||
|
||||
start = i + 1
|
||||
}
|
||||
}
|
||||
|
||||
func resolveValue(key, fall String, named Named, positional Slice[any]) any {
|
||||
if !key.IsEmpty() {
|
||||
first := key[0]
|
||||
if first >= '0' && first <= '9' || (first == '+' || first == '-') && key.Len() > 1 {
|
||||
if num := key.TryInt(); num.IsOk() {
|
||||
idx := num.v - 1
|
||||
if idx.IsNegative() || idx.Gte(positional.Len()) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return positional[idx]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
value := Map[String, any](named).Get(key)
|
||||
if value.IsNone() && fall.NotEmpty() {
|
||||
value = Map[String, any](named).Get(fall)
|
||||
value, ok := named[key]
|
||||
if !ok && !fall.IsEmpty() {
|
||||
value = named[fall]
|
||||
}
|
||||
|
||||
return value.UnwrapOrDefault()
|
||||
return value
|
||||
}
|
||||
|
||||
func parseMod(segment String) (String, Slice[String]) {
|
||||
@@ -272,7 +561,25 @@ func parseMod(segment String) (String, Slice[String]) {
|
||||
return segment, nil
|
||||
}
|
||||
|
||||
params := segment[oidx+1 : cidx].Split(",").Collect()
|
||||
raw := segment[oidx+1 : cidx]
|
||||
count := 1
|
||||
for i := Int(0); i < raw.Len(); i++ {
|
||||
if raw[i] == ',' {
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
params := make(Slice[String], 0, count)
|
||||
start := Int(0)
|
||||
for i := Int(0); i <= raw.Len(); i++ {
|
||||
if i < raw.Len() && raw[i] != ',' {
|
||||
continue
|
||||
}
|
||||
|
||||
params = append(params, raw[start:i])
|
||||
start = i + 1
|
||||
}
|
||||
|
||||
name := segment[:oidx]
|
||||
|
||||
return name, params
|
||||
@@ -307,9 +614,9 @@ func toType(param String, targetType reflect.Type) Result[reflect.Value] {
|
||||
return Ok(reflect.ValueOf(fl).Convert(targetType))
|
||||
default:
|
||||
switch targetType {
|
||||
case reflect.TypeOf(""):
|
||||
case reflect.TypeFor[string]():
|
||||
return Ok(reflect.ValueOf(param.Std()))
|
||||
case reflect.TypeOf(String("")):
|
||||
case reflect.TypeFor[String]():
|
||||
return Ok(reflect.ValueOf(param))
|
||||
default:
|
||||
return Err[reflect.Value](fmt.Errorf("unsupported type: %s", targetType))
|
||||
@@ -415,64 +722,17 @@ func resolveIndirect(targetType reflect.Value) reflect.Value {
|
||||
return targetType
|
||||
}
|
||||
|
||||
func callMethod(method reflect.Value, params Slice[String]) Option[any] {
|
||||
methodType := method.Type()
|
||||
numIn := methodType.NumIn()
|
||||
isVariadic := methodType.IsVariadic()
|
||||
|
||||
if isVariadic {
|
||||
numIn--
|
||||
}
|
||||
|
||||
var args []reflect.Value
|
||||
|
||||
for i := range numIn {
|
||||
arg := toType(params[i], methodType.In(i))
|
||||
if arg.IsErr() {
|
||||
return None[any]()
|
||||
}
|
||||
|
||||
args = append(args, arg.v)
|
||||
}
|
||||
|
||||
if isVariadic {
|
||||
elemType := methodType.In(numIn).Elem()
|
||||
for _, param := range params[numIn:] {
|
||||
arg := toType(param, elemType)
|
||||
if arg.IsErr() {
|
||||
return None[any]()
|
||||
}
|
||||
|
||||
args = append(args, arg.v)
|
||||
}
|
||||
}
|
||||
|
||||
results := method.Call(args)
|
||||
|
||||
if len(results) > 0 {
|
||||
return Some(results[0].Interface())
|
||||
}
|
||||
|
||||
return None[any]()
|
||||
}
|
||||
|
||||
func applyMod(value any, name String, params Slice[String]) any {
|
||||
switch name {
|
||||
case "type":
|
||||
return fmt.Sprintf("%T", value)
|
||||
case "debug":
|
||||
return fmt.Sprintf("%#v", value)
|
||||
}
|
||||
|
||||
// applyMod resolves one dot-segment of a placeholder as DATA access only: a
|
||||
// map key, a MapOrd key, a slice/array index, or a struct field.
|
||||
//
|
||||
// It deliberately does NOT invoke methods. Method modifiers ({x.Trim.Upper})
|
||||
// were removed: they silently broke whenever a method moved out of the root
|
||||
// package, and their dynamic MethodByName call disabled the linker's
|
||||
// dead-method elimination for every binary linking g, bloating binaries far
|
||||
// beyond this package. Transform the value before handing it to Format.
|
||||
func applyMod(value any, name String) any {
|
||||
current := reflect.ValueOf(value)
|
||||
|
||||
if method := current.MethodByName(name.Std()); method.IsValid() && method.Kind() == reflect.Func {
|
||||
if result := callMethod(method, params); result.IsSome() {
|
||||
return result.v
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
for current.Kind() == reflect.Pointer || current.Kind() == reflect.Interface {
|
||||
if current.IsNil() {
|
||||
return value
|
||||
@@ -480,13 +740,6 @@ func applyMod(value any, name String, params Slice[String]) any {
|
||||
current = current.Elem()
|
||||
}
|
||||
|
||||
if method := current.MethodByName(name.Std()); method.IsValid() && method.Kind() == reflect.Func {
|
||||
if result := callMethod(method, params); result.IsSome() {
|
||||
return result.v
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
switch current.Kind() {
|
||||
case reflect.Map:
|
||||
key := toType(name, current.Type().Key())
|
||||
@@ -500,7 +753,7 @@ func applyMod(value any, name String, params Slice[String]) any {
|
||||
return pair.v
|
||||
}
|
||||
|
||||
idx := name.ToInt()
|
||||
idx := name.TryInt()
|
||||
if idx.IsErr() || idx.v.Gte(Int(current.Len())) {
|
||||
return value
|
||||
}
|
||||
|
||||
+240
-40
@@ -1,62 +1,262 @@
|
||||
// Package rand is the single home for randomness over g types, built on
|
||||
// math/rand/v2.
|
||||
//
|
||||
// rand.N(10) // Int-like value in [0, 10)
|
||||
// rand.Range(5, 10) // half-open [5, 10)
|
||||
// rand.RangeInclusive(1, 6) // closed [1, 6]
|
||||
// rand.Float() // Float in [0, 1)
|
||||
// rand.Chance(0.25) // true with probability 0.25
|
||||
// rand.String(10) // 10 alphanumeric characters
|
||||
// rand.Choice(users) // Option with a random element
|
||||
// rand.Sample(deck, 5) // 5 distinct random elements
|
||||
// rand.Shuffle(deck) // in place; accepts Slice, MapOrd, plain slices
|
||||
// rand.SecureString(32) // crypto/rand-backed token
|
||||
//
|
||||
// The container types deliberately carry NO random methods — everything lives
|
||||
// here, one way to do it.
|
||||
//
|
||||
// The generators come from math/rand/v2 and are NOT cryptographically
|
||||
// secure. For keys, tokens and anything security-sensitive use [SecureBytes]
|
||||
// and [SecureString], which draw from crypto/rand.
|
||||
package rand
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"math/rand/v2"
|
||||
|
||||
"github.com/enetx/g"
|
||||
"github.com/enetx/g/constraints"
|
||||
)
|
||||
|
||||
// U64 returns a cryptographically secure random uint64 value.
|
||||
// It reads 8 random bytes from crypto/rand.Reader and interprets
|
||||
// them as a little-endian unsigned integer.
|
||||
// Panics if the system random number generator is unavailable.
|
||||
func U64() uint64 {
|
||||
var b [8]byte
|
||||
if _, err := io.ReadFull(rand.Reader, b[:]); err != nil {
|
||||
panic(err)
|
||||
// N returns a random integer in [0, n). It panics if n <= 0, matching
|
||||
// math/rand/v2.N.
|
||||
func N[T constraints.Integer](n T) T { return rand.N(n) }
|
||||
|
||||
// Range returns a random integer in the half-open interval [lo, hi).
|
||||
// It panics if hi <= lo.
|
||||
func Range[T constraints.Integer](lo, hi T) T {
|
||||
if hi <= lo {
|
||||
panic("rand.Range: hi must be greater than lo")
|
||||
}
|
||||
|
||||
return binary.LittleEndian.Uint64(b[:])
|
||||
return lo + rand.N(hi-lo)
|
||||
}
|
||||
|
||||
// N generates a random non-negative integer within the range [0, max).
|
||||
// The generated integer will be less than the provided maximum value.
|
||||
// If max is less than or equal to 0, the function will treat it as if max is 1.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// n := 10
|
||||
// randomInt := rand.N(n)
|
||||
// fmt.Printf("Random integer between 0 and %d: %d\n", max, randomInt)
|
||||
//
|
||||
// Parameters:
|
||||
// - n (int): The maximum bound for the random integer to be generated.
|
||||
//
|
||||
// Returns:
|
||||
// - int: A random non-negative integer within the specified range.
|
||||
func N[T constraints.Integer](n T) T {
|
||||
// RangeInclusive returns a random Int in the closed interval [lo, hi].
|
||||
// The order of bounds does not matter (it normalizes to
|
||||
// [min, max]); it works for negative bounds and the full int64 range without
|
||||
// overflow or bias.
|
||||
func RangeInclusive(lo, hi g.Int) g.Int {
|
||||
if lo > hi {
|
||||
lo, hi = hi, lo
|
||||
}
|
||||
|
||||
if lo == hi {
|
||||
return lo
|
||||
}
|
||||
|
||||
const bias = uint64(1) << 63 // 2^63 = 9223372036854775808
|
||||
|
||||
ulo := uint64(lo) + bias
|
||||
uhi := uint64(hi) + bias
|
||||
|
||||
w := uhi - ulo + 1
|
||||
|
||||
if w == 0 {
|
||||
return g.Int(rand.Uint64())
|
||||
}
|
||||
|
||||
randv := rand.N(w)
|
||||
result := int64((ulo + randv) - bias)
|
||||
|
||||
return g.Int(result)
|
||||
}
|
||||
|
||||
// Float returns a random Float in [0, 1).
|
||||
func Float() g.Float { return g.Float(rand.Float64()) }
|
||||
|
||||
// Uniform returns a random Float in [lo, hi).
|
||||
func Uniform(lo, hi g.Float) g.Float { return lo + (hi-lo)*g.Float(rand.Float64()) }
|
||||
|
||||
// NormFloat returns a normally distributed Float with mean 0 and standard
|
||||
// deviation 1.
|
||||
func NormFloat() g.Float { return g.Float(rand.NormFloat64()) }
|
||||
|
||||
// Bool returns true or false with equal probability.
|
||||
func Bool() bool { return rand.N(2) == 0 }
|
||||
|
||||
// Chance returns true with probability p. Values outside [0, 1] clamp to
|
||||
// always-false / always-true.
|
||||
func Chance(p g.Float) bool {
|
||||
if p <= 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if p >= 1 {
|
||||
return true
|
||||
}
|
||||
|
||||
return rand.Float64() < p.Std()
|
||||
}
|
||||
|
||||
// Perm returns a random permutation of the integers [0, n) as a Slice.
|
||||
func Perm(n g.Int) g.Slice[g.Int] {
|
||||
if n <= 0 {
|
||||
return 0
|
||||
return nil
|
||||
}
|
||||
|
||||
w := uint64(n)
|
||||
if w == 1 {
|
||||
return 0
|
||||
result := make(g.Slice[g.Int], n)
|
||||
for i, v := range rand.Perm(n.Std()) {
|
||||
result[i] = g.Int(v)
|
||||
}
|
||||
|
||||
if w&(w-1) == 0 {
|
||||
return T(U64() & (w - 1))
|
||||
return result
|
||||
}
|
||||
|
||||
// Choice returns a random element of the slice. An
|
||||
// empty slice yields None. It accepts any slice-shaped type (Slice, MapOrd,
|
||||
// plain slices).
|
||||
func Choice[S ~[]E, E any](sl S) g.Option[E] {
|
||||
if len(sl) == 0 {
|
||||
return g.None[E]()
|
||||
}
|
||||
|
||||
return g.Some(sl[rand.N(len(sl))])
|
||||
}
|
||||
|
||||
// Choices returns k elements drawn WITH replacement.
|
||||
// An empty source or non-positive k yields an empty result.
|
||||
func Choices[S ~[]E, E any](sl S, k g.Int) S {
|
||||
if len(sl) == 0 || k <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
result := make(S, k)
|
||||
for i := range result {
|
||||
result[i] = sl[rand.N(len(sl))]
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Sample returns k distinct elements drawn WITHOUT replacement. If k is not
|
||||
// less than the slice length, a shuffled copy of the whole
|
||||
// slice is returned. The source slice is not modified.
|
||||
func Sample[S ~[]E, E any](sl S, k g.Int) S {
|
||||
n := len(sl)
|
||||
|
||||
if n == 0 || k <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
const maxU64 = ^uint64(0)
|
||||
limit := maxU64 - (maxU64 % w)
|
||||
if k >= g.Int(n) {
|
||||
out := make(S, n)
|
||||
copy(out, sl)
|
||||
Shuffle(out)
|
||||
|
||||
for {
|
||||
randv := U64()
|
||||
if randv < limit {
|
||||
return T(randv % w)
|
||||
return out
|
||||
}
|
||||
|
||||
// For small samples, track displaced indices in a map instead of copying
|
||||
// the whole slice: O(k) time and space.
|
||||
if g.Float(k) < g.Float(n)*0.25 {
|
||||
result := make(S, k)
|
||||
swapped := make(map[int]int, k)
|
||||
|
||||
for i := range k.Std() {
|
||||
j := i + rand.N(n-i)
|
||||
|
||||
vi, foundI := swapped[i]
|
||||
if !foundI {
|
||||
vi = i
|
||||
}
|
||||
|
||||
vj, foundJ := swapped[j]
|
||||
if !foundJ {
|
||||
vj = j
|
||||
}
|
||||
|
||||
swapped[i] = vj
|
||||
if i != j {
|
||||
swapped[j] = vi
|
||||
}
|
||||
|
||||
result[i] = sl[vj]
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
out := make(S, n)
|
||||
copy(out, sl)
|
||||
Shuffle(out)
|
||||
|
||||
return out[:k]
|
||||
}
|
||||
|
||||
// Shuffle permutes the slice in place. It accepts any
|
||||
// slice-shaped type (Slice, MapOrd, plain slices).
|
||||
func Shuffle[S ~[]E, E any](sl S) {
|
||||
rand.Shuffle(len(sl), func(i, j int) { sl[i], sl[j] = sl[j], sl[i] })
|
||||
}
|
||||
|
||||
// Bytes returns length random bytes. NOT cryptographically secure — use
|
||||
// [SecureBytes] for keys and tokens.
|
||||
func Bytes(length g.Int) g.Bytes {
|
||||
if length <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
buf := make(g.Bytes, length)
|
||||
for i := range buf {
|
||||
buf[i] = byte(rand.N(256))
|
||||
}
|
||||
|
||||
return buf
|
||||
}
|
||||
|
||||
// String generates a random String of the specified length, selecting
|
||||
// characters from predefined sets. If additional character sets are provided,
|
||||
// only those are used; the default set (g.ASCIILetters and g.Digits) is
|
||||
// excluded unless explicitly provided.
|
||||
//
|
||||
// If length is zero or negative, an empty String is returned. If an explicit
|
||||
// letter set is provided but resolves to empty, an empty String is returned as
|
||||
// well.
|
||||
//
|
||||
// rand.String(10) // 10 alphanumeric characters
|
||||
// rand.String(6, g.Digits) // 6-digit code
|
||||
func String(length g.Int, letters ...g.String) g.String {
|
||||
if length <= 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
if len(letters) != 0 {
|
||||
var buf g.Builder
|
||||
for _, set := range letters {
|
||||
_, _ = buf.WriteString(set)
|
||||
}
|
||||
|
||||
chars := buf.String().Runes()
|
||||
n := len(chars)
|
||||
if n == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
var b g.Builder
|
||||
b.Grow(length)
|
||||
for range length {
|
||||
b.WriteRune(chars[rand.N(n)])
|
||||
}
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
const charset = g.ASCIILetters + g.Digits
|
||||
n := len(charset)
|
||||
buf := make(g.Bytes, length)
|
||||
for i := range buf {
|
||||
buf[i] = charset[rand.N(n)]
|
||||
}
|
||||
|
||||
return buf.StringUnsafe()
|
||||
}
|
||||
-22
@@ -1,22 +0,0 @@
|
||||
// Package ref provides a utility function for creating a pointer to a value.
|
||||
// It is designed to simplify the process of obtaining a pointer to a value of any type.
|
||||
package ref
|
||||
|
||||
// Of creates a pointer to the provided value of type 'E'.
|
||||
// The primary purpose of this function is to simplify the creation of pointers to values
|
||||
// without needing to use temporary variables.
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// e: The value of type 'E' to create a pointer for.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// *E: A pointer to the provided value 'e'.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// intValue := 42
|
||||
// intPtr := ref.Of(intValue)
|
||||
// fmt.Println(*intPtr)
|
||||
func Of[E any](e E) *E { return &e }
|
||||
+201
-38
@@ -8,6 +8,12 @@ import (
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// Result is a generic struct for representing a result value along with an error.
|
||||
type Result[T any] struct {
|
||||
v T // Value.
|
||||
err error // Associated error.
|
||||
}
|
||||
|
||||
// Ok returns a new Result[T] containing the given value.
|
||||
func Ok[T any](value T) Result[T] { return Result[T]{v: value} }
|
||||
|
||||
@@ -31,32 +37,12 @@ func ResultOf[T any](value T, err error) Result[T] {
|
||||
return Ok(value)
|
||||
}
|
||||
|
||||
// TransformResult applies a function to the contained Ok value, returning a new Result.
|
||||
// If the input Result is Err, the error is propagated.
|
||||
// This is also known as 'and_then' or 'flat_map'.
|
||||
func TransformResult[T, U any](r Result[T], fn func(T) Result[U]) Result[U] {
|
||||
if r.IsOk() {
|
||||
return fn(r.v)
|
||||
}
|
||||
|
||||
return Err[U](r.err)
|
||||
}
|
||||
|
||||
// TransformResultOf applies a function that returns a (value, error) tuple to the contained Ok value.
|
||||
// If the input Result is Err, the error is propagated.
|
||||
func TransformResultOf[T, U any](r Result[T], fn func(T) (U, error)) Result[U] {
|
||||
if r.IsOk() {
|
||||
return ResultOf(fn(r.v))
|
||||
}
|
||||
|
||||
return Err[U](r.err)
|
||||
}
|
||||
|
||||
// Ok returns the value held in the Result.
|
||||
//
|
||||
// WARNING: If the Result contains an error, this method will return the zero value
|
||||
// for type T. Always check IsOk() before calling this method, or use safer alternatives
|
||||
// like Result(), Unwrap(), or UnwrapOr().
|
||||
// for type T. Always check IsOk() before calling this method, or use safer
|
||||
// alternatives like Result(), UnwrapOr(), or UnwrapOrDefault(). (Unwrap() and
|
||||
// Expect() are NOT safe alternatives — they panic on an Err value.)
|
||||
func (r Result[T]) Ok() T { return r.v }
|
||||
|
||||
// Err returns the error held in the Result. If the result is Ok, it returns nil.
|
||||
@@ -78,19 +64,27 @@ func (r Result[T]) Result() (T, error) {
|
||||
return zero, r.err
|
||||
}
|
||||
|
||||
// resultPanic prints caller information for msg to stderr and then panics with v.
|
||||
// skip is the number of stack frames between the original caller and runtime.Caller,
|
||||
// so that the reported file:line and function point at the user's call site rather
|
||||
// than at this helper.
|
||||
func resultPanic(skip int, msg string, v any) {
|
||||
if pc, file, line, ok := runtime.Caller(skip); ok {
|
||||
out := fmt.Sprintf("[%s:%d] [%s] %s", filepath.Base(file), line, runtime.FuncForPC(pc).Name(), msg)
|
||||
fmt.Fprintln(os.Stderr, out)
|
||||
}
|
||||
|
||||
panic(v)
|
||||
}
|
||||
|
||||
// Unwrap returns the value held in the Result. If the Result is Err, it panics.
|
||||
func (r Result[T]) Unwrap() T {
|
||||
if r.IsOk() {
|
||||
return r.v
|
||||
}
|
||||
|
||||
if pc, file, line, ok := runtime.Caller(1); ok {
|
||||
out := fmt.Sprintf(
|
||||
"[%s:%d] [%s] unwrapped an Err value: %v", filepath.Base(file), line, runtime.FuncForPC(pc).Name(), r.err)
|
||||
fmt.Fprintln(os.Stderr, out)
|
||||
}
|
||||
|
||||
panic(r.err)
|
||||
resultPanic(2, fmt.Sprintf("called Result.Unwrap() on an Err value: %v", r.err), r.err)
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// UnwrapOr returns the value held in the Result. If the Result is Err, it returns the provided default value.
|
||||
@@ -119,28 +113,72 @@ func (r Result[T]) Expect(msg string) T {
|
||||
}
|
||||
|
||||
out := fmt.Sprintf("Expect() failed: %s: %v", msg, r.err)
|
||||
fmt.Fprintln(os.Stderr, out)
|
||||
panic(out)
|
||||
resultPanic(2, out, out)
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// Then applies a function to the contained value (if Ok) and returns the result.
|
||||
// If the Result is Err, it returns the same Err without applying the function.
|
||||
func (r Result[T]) Then(fn func(T) Result[T]) Result[T] {
|
||||
// Then applies a function to the contained value (if Ok) and returns the resulting Result.
|
||||
// If the Result is Err, fn is not called and the error is propagated.
|
||||
// The result type may differ from the input type.
|
||||
func (r Result[T]) Then[U any](fn func(T) Result[U]) Result[U] {
|
||||
if r.IsOk() {
|
||||
return fn(r.v)
|
||||
}
|
||||
|
||||
return Err[U](r.err)
|
||||
}
|
||||
|
||||
// Map applies a function to the contained value (if Ok) and returns a new Result
|
||||
// holding the transformed value. If the Result is Err, fn is not called and the
|
||||
// error is propagated. Unlike Then, fn returns a plain U rather than a Result[U].
|
||||
func (r Result[T]) Map[U any](fn func(T) U) Result[U] {
|
||||
if r.IsOk() {
|
||||
return Ok(fn(r.v))
|
||||
}
|
||||
|
||||
return Err[U](r.err)
|
||||
}
|
||||
|
||||
// MapOr applies fn to the contained value if Ok and returns the result;
|
||||
// otherwise returns the provided default value.
|
||||
func (r Result[T]) MapOr[U any](def U, fn func(T) U) U {
|
||||
if r.IsOk() {
|
||||
return fn(r.v)
|
||||
}
|
||||
|
||||
return def
|
||||
}
|
||||
|
||||
// MapOrElse applies fn to the contained value if Ok and returns the result;
|
||||
// otherwise computes the default from the error via defFn.
|
||||
func (r Result[T]) MapOrElse[U any](defFn func(error) U, fn func(T) U) U {
|
||||
if r.IsOk() {
|
||||
return fn(r.v)
|
||||
}
|
||||
|
||||
return defFn(r.err)
|
||||
}
|
||||
|
||||
// Inspect calls fn with the contained value if the Result is Ok, then returns
|
||||
// the Result unchanged. If the Result is Err, fn is not called. It is intended
|
||||
// for side effects (logging, debugging) within a chain and never mutates the Result.
|
||||
func (r Result[T]) Inspect(fn func(T)) Result[T] {
|
||||
if r.IsOk() {
|
||||
fn(r.v)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// ThenOf applies a function to the contained value (if Ok) and returns a new Result
|
||||
// based on the returned (T, error) tuple.
|
||||
func (r Result[T]) ThenOf(fn func(T) (T, error)) Result[T] {
|
||||
// based on the returned (U, error) tuple. If the Result is Err, fn is not called
|
||||
// and the error is propagated.
|
||||
func (r Result[T]) ThenOf[U any](fn func(T) (U, error)) Result[U] {
|
||||
if r.IsOk() {
|
||||
return ResultOf(fn(r.v))
|
||||
}
|
||||
|
||||
return r
|
||||
return Err[U](r.err)
|
||||
}
|
||||
|
||||
// MapErr transforms the error in an Err Result by applying a function to it.
|
||||
@@ -173,3 +211,128 @@ func (r Result[T]) String() string {
|
||||
|
||||
return fmt.Sprintf("Err(%v)", r.err)
|
||||
}
|
||||
|
||||
// ErrIs reports whether the error in Result matches target (using errors.Is).
|
||||
// Returns false if Result is Ok.
|
||||
func (r Result[T]) ErrIs(target error) bool { return errors.Is(r.err, target) }
|
||||
|
||||
// ErrAs finds the first error in Result's error chain that matches target,
|
||||
// and if so, sets target to that error value and returns true (using errors.As).
|
||||
// Returns false if Result is Ok.
|
||||
func (r Result[T]) ErrAs(target any) bool { return errors.As(r.err, target) }
|
||||
|
||||
// ErrSource returns the underlying error wrapped by the Result's error, if any.
|
||||
// Returns None if Result is Ok or if the error doesn't wrap another error.
|
||||
func (r Result[T]) ErrSource() Option[error] {
|
||||
if source := errors.Unwrap(r.err); source != nil {
|
||||
return Some(source)
|
||||
}
|
||||
|
||||
return None[error]()
|
||||
}
|
||||
|
||||
// Wrap wraps the error in Result with additional context error.
|
||||
// Both errors are preserved in the chain and accessible via ErrIs.
|
||||
// If Result is Ok, returns unchanged.
|
||||
func (r Result[T]) Wrap(err error) Result[T] {
|
||||
if r.IsErr() {
|
||||
return Err[T](fmt.Errorf("%w: %w", err, r.err))
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// Or returns the Result if it is Ok, otherwise returns the provided alternative Result.
|
||||
func (r Result[T]) Or(other Result[T]) Result[T] {
|
||||
if r.IsOk() {
|
||||
return r
|
||||
}
|
||||
|
||||
return other
|
||||
}
|
||||
|
||||
// OrElse returns the Result if it is Ok, otherwise calls fn with the error and returns its result.
|
||||
func (r Result[T]) OrElse(fn func(error) Result[T]) Result[T] {
|
||||
if r.IsOk() {
|
||||
return r
|
||||
}
|
||||
|
||||
return fn(r.err)
|
||||
}
|
||||
|
||||
// And returns the receiver's error if it is Err, otherwise returns other.
|
||||
// It is the eager counterpart of Then (which calls a function instead).
|
||||
func (r Result[T]) And[U any](other Result[U]) Result[U] {
|
||||
if r.IsErr() {
|
||||
return Err[U](r.err)
|
||||
}
|
||||
|
||||
return other
|
||||
}
|
||||
|
||||
// ErrOption returns the contained error as an Option: Some(err) if the Result is
|
||||
// Err, or None if it is Ok. It is the error-side counterpart of Option, which
|
||||
// returns the Ok value as an Option.
|
||||
func (r Result[T]) ErrOption() Option[error] {
|
||||
if r.IsErr() {
|
||||
return Some(r.err)
|
||||
}
|
||||
|
||||
return None[error]()
|
||||
}
|
||||
|
||||
// IsOkAnd returns true if the Result is Ok and the predicate returns true for the contained value.
|
||||
func (r Result[T]) IsOkAnd(pred func(T) bool) bool {
|
||||
return r.IsOk() && pred(r.v)
|
||||
}
|
||||
|
||||
// IsErrAnd returns true if the Result is Err and the predicate returns true for the contained error.
|
||||
func (r Result[T]) IsErrAnd(pred func(error) bool) bool {
|
||||
return r.IsErr() && pred(r.err)
|
||||
}
|
||||
|
||||
// InspectErr calls fn with the contained error if the Result is Err, then returns
|
||||
// the Result unchanged. It is the error-side counterpart of Inspect.
|
||||
func (r Result[T]) InspectErr(fn func(error)) Result[T] {
|
||||
if r.IsErr() {
|
||||
fn(r.err)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// UnwrapErr returns the contained error. If the Result is Ok, it panics.
|
||||
func (r Result[T]) UnwrapErr() error {
|
||||
if r.IsErr() {
|
||||
return r.err
|
||||
}
|
||||
|
||||
out := fmt.Sprintf("called Result.UnwrapErr() on an Ok value: %v", r.v)
|
||||
resultPanic(2, out, out)
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// OkOr converts an Option into a Result: Ok(value) if Some, otherwise
|
||||
// Err(err).
|
||||
//
|
||||
// It is a free function rather than an Option method: as a method every
|
||||
// Option[T] instantiation would also instantiate Result[T] (and everything
|
||||
// Result's methods mention), even in packages that never convert between the
|
||||
// two.
|
||||
func OkOr[T any](o Option[T], err error) Result[T] {
|
||||
if o.isSome {
|
||||
return Ok(o.v)
|
||||
}
|
||||
|
||||
return Err[T](err)
|
||||
}
|
||||
|
||||
// OkOrElse converts an Option into a Result: Ok(value) if Some, otherwise
|
||||
// Err(fn()). See [OkOr] for why this is not a method.
|
||||
func OkOrElse[T any](o Option[T], fn func() error) Result[T] {
|
||||
if o.isSome {
|
||||
return Ok(o.v)
|
||||
}
|
||||
|
||||
return Err[T](fn())
|
||||
}
|
||||
+584
-68
@@ -2,11 +2,67 @@ package g
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
|
||||
"github.com/enetx/g/f"
|
||||
"github.com/enetx/iter"
|
||||
"github.com/enetx/g/cmp"
|
||||
"github.com/enetx/g/constraints"
|
||||
)
|
||||
|
||||
// SeqResult is an iterator over sequences of Result[V] values.
|
||||
type SeqResult[V any] func(yield func(Result[V]) bool)
|
||||
|
||||
// OkSeq wraps a single Ok value into a SeqResult iterator.
|
||||
// Useful for returning a successful single-element sequence without manually
|
||||
// constructing the yield function.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// return OkSeq(42)
|
||||
func OkSeq[V any](v V) SeqResult[V] {
|
||||
return func(yield func(Result[V]) bool) { yield(Ok(v)) }
|
||||
}
|
||||
|
||||
// ErrSeq wraps a single error into a SeqResult iterator.
|
||||
// Useful for early returns in functions that produce a SeqResult,
|
||||
// where a plain error needs to be lifted into the sequence type.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// return ErrSeq[int](errors.New("something went wrong"))
|
||||
func ErrSeq[V any](err error) SeqResult[V] {
|
||||
return func(yield func(Result[V]) bool) { yield(Err[V](err)) }
|
||||
}
|
||||
|
||||
// FlatMap transforms each Ok value into a sequence and flattens the results,
|
||||
// wrapping each produced element in Ok. The element type may differ from the
|
||||
// input type. If an Err is encountered, it is passed downstream as-is;
|
||||
// iteration continues for as long as the consumer keeps accepting values
|
||||
// (consumer-driven), matching Map.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// seq.FlatMap(Slice[Int].Iter) // SeqResult[Slice[Int]] -> SeqResult[Int]
|
||||
func (seq SeqResult[V]) FlatMap[U any](fn func(V) Seq[U]) SeqResult[U] {
|
||||
return func(yield func(Result[U]) bool) {
|
||||
seq(func(v Result[V]) bool {
|
||||
if v.IsErr() {
|
||||
return yield(Err[U](v.err))
|
||||
}
|
||||
|
||||
cont := true
|
||||
fn(v.v)(func(u U) bool {
|
||||
if !yield(Ok(u)) {
|
||||
cont = false
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return cont
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Pull converts the “push-style” sequence of Result[V] into a “pull-style” iterator accessed by two functions: next and stop.
|
||||
//
|
||||
// The next function returns the next Result[V] in the sequence and a boolean indicating whether the value is valid.
|
||||
@@ -19,7 +75,7 @@ import (
|
||||
//
|
||||
// It is an error to call next or stop from multiple goroutines simultaneously.
|
||||
func (seq SeqResult[V]) Pull() (func() (Result[V], bool), func()) {
|
||||
return iter.Pull(iter.Seq[Result[V]](seq))
|
||||
return Seq[Result[V]](seq).seqPull()
|
||||
}
|
||||
|
||||
// All checks whether all Ok values in the sequence satisfy the provided condition.
|
||||
@@ -70,12 +126,48 @@ func (seq SeqResult[V]) Any(fn func(v V) bool) Result[bool] {
|
||||
return result
|
||||
}
|
||||
|
||||
// Collect gathers all Ok values from the iterator into a Slice.
|
||||
// If any value is Err, the first such Err is returned immediately.
|
||||
func (seq SeqResult[V]) Collect() Slice[Result[V]] { return iter.ToSlice(iter.Seq[Result[V]](seq)) }
|
||||
// Collect returns a collector over the raw Result elements, matching the
|
||||
// Seq/Seq2 collector idiom: materialize with .Slice() (etc.). Collect itself
|
||||
// is lazy and does not consume the sequence; the materializer does. Both Ok
|
||||
// and Err elements flow through as-is; encountering an Err does not stop
|
||||
// collection. Use TryCollect for the short-circuiting Ok-only variant.
|
||||
func (seq SeqResult[V]) Collect() collector[Result[V]] {
|
||||
return collector[Result[V]]{Seq[Result[V]](seq)}
|
||||
}
|
||||
|
||||
// Count consumes the entire sequence, counting how many times the yield function is invoked.
|
||||
// Err elements do not stop the count but are still passed to the yield function (which returns false immediately, stopping iteration).
|
||||
// TryCollect gathers the Ok values from the sequence into a Slice: the first Err
|
||||
// short-circuits — iteration stops immediately, elements after it are not
|
||||
// consumed — and that error is returned as Err. An empty sequence yields Ok of
|
||||
// an empty Slice.
|
||||
//
|
||||
// Unlike Collect, which gathers every element (Ok and Err alike) into a
|
||||
// Slice[Result[V]], TryCollect returns Result[Slice[V]]: either all the
|
||||
// unwrapped Ok values, or the first error encountered.
|
||||
func (seq SeqResult[V]) TryCollect() Result[Slice[V]] {
|
||||
collection := NewSlice[V]()
|
||||
|
||||
var err error
|
||||
|
||||
seq(func(v Result[V]) bool {
|
||||
if v.IsErr() {
|
||||
err = v.err
|
||||
return false
|
||||
}
|
||||
|
||||
collection = append(collection, v.v)
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return Err[Slice[V]](err)
|
||||
}
|
||||
|
||||
return Ok(collection)
|
||||
}
|
||||
|
||||
// Count consumes the entire sequence, counting the number of elements it yields.
|
||||
// Err elements are counted like Ok elements and do not stop the count.
|
||||
func (seq SeqResult[V]) Count() Int {
|
||||
var counter Int
|
||||
seq(func(Result[V]) bool {
|
||||
@@ -87,14 +179,15 @@ func (seq SeqResult[V]) Count() Int {
|
||||
}
|
||||
|
||||
// Map transforms each Ok value in the sequence using the given function, returning a new sequence of Result.
|
||||
// The result type may differ from the input type.
|
||||
//
|
||||
// If an Err is encountered, it is passed downstream as-is and ends the iteration (yield returns false).
|
||||
func (seq SeqResult[V]) Map(transform func(V) V) SeqResult[V] {
|
||||
return func(yield func(Result[V]) bool) {
|
||||
// If an Err is encountered, it is passed downstream as-is; iteration continues
|
||||
// for as long as the consumer keeps accepting values (consumer-driven).
|
||||
func (seq SeqResult[V]) Map[U any](transform func(V) U) SeqResult[U] {
|
||||
return func(yield func(Result[U]) bool) {
|
||||
seq(func(v Result[V]) bool {
|
||||
if v.IsErr() {
|
||||
yield(v)
|
||||
return false
|
||||
return yield(Err[U](v.err))
|
||||
}
|
||||
return yield(Ok(transform(v.v)))
|
||||
})
|
||||
@@ -103,14 +196,14 @@ func (seq SeqResult[V]) Map(transform func(V) V) SeqResult[V] {
|
||||
|
||||
// Filter returns a new sequence containing only the Ok elements that satisfy the provided function.
|
||||
//
|
||||
// If an Err is encountered, it is yielded immediately as Err (and stops further iteration).
|
||||
// If an Err is encountered, it is yielded downstream as-is; the consumer decides
|
||||
// whether to continue (consumer-driven).
|
||||
// Only Ok elements for which fn returns true are yielded downstream as Ok.
|
||||
func (seq SeqResult[V]) Filter(fn func(V) bool) SeqResult[V] {
|
||||
return func(yield func(Result[V]) bool) {
|
||||
seq(func(v Result[V]) bool {
|
||||
if v.IsErr() {
|
||||
yield(v)
|
||||
return false
|
||||
return yield(v)
|
||||
}
|
||||
if fn(v.v) {
|
||||
return yield(v)
|
||||
@@ -122,14 +215,13 @@ func (seq SeqResult[V]) Filter(fn func(V) bool) SeqResult[V] {
|
||||
|
||||
// Exclude returns a new sequence that excludes Ok elements which satisfy the provided function.
|
||||
//
|
||||
// If an Err is encountered, it is yielded as Err (and stops iteration).
|
||||
// If an Err is encountered, it is yielded downstream as-is (consumer-driven).
|
||||
// Only Ok elements for which 'fn' returns false are yielded downstream.
|
||||
func (seq SeqResult[V]) Exclude(fn func(V) bool) SeqResult[V] {
|
||||
return func(yield func(Result[V]) bool) {
|
||||
seq(func(v Result[V]) bool {
|
||||
if v.IsErr() {
|
||||
yield(v)
|
||||
return false
|
||||
return yield(v)
|
||||
}
|
||||
if !fn(v.v) {
|
||||
return yield(v)
|
||||
@@ -141,18 +233,17 @@ func (seq SeqResult[V]) Exclude(fn func(V) bool) SeqResult[V] {
|
||||
|
||||
// Dedup removes consecutive duplicates of Ok values from the sequence, returning a new sequence.
|
||||
//
|
||||
// If an Err is encountered, it is yielded immediately and iteration stops.
|
||||
// If an Err is encountered, it is yielded downstream as-is (consumer-driven).
|
||||
// Consecutive Ok duplicates (based on equality) are filtered out so only the first occurrence is yielded.
|
||||
func (seq SeqResult[V]) Dedup() SeqResult[V] {
|
||||
return func(yield func(Result[V]) bool) {
|
||||
var current V
|
||||
hasFirst := false
|
||||
comparable := f.IsComparable(current)
|
||||
comparable := isValueComparable[V]()
|
||||
|
||||
seq(func(v Result[V]) bool {
|
||||
if v.IsErr() {
|
||||
yield(v)
|
||||
return false
|
||||
return yield(v)
|
||||
}
|
||||
|
||||
if !hasFirst {
|
||||
@@ -162,11 +253,11 @@ func (seq SeqResult[V]) Dedup() SeqResult[V] {
|
||||
}
|
||||
|
||||
if comparable {
|
||||
if f.Eq[any](current)(v.v) {
|
||||
if any(current) == any(v.v) {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
if f.Eqd(current)(v.v) {
|
||||
if reflect.DeepEqual(current, v.v) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -179,22 +270,44 @@ func (seq SeqResult[V]) Dedup() SeqResult[V] {
|
||||
|
||||
// Unique returns a new sequence that contains only the first occurrence of each distinct Ok value.
|
||||
//
|
||||
// If an Err is encountered, it is yielded immediately and iteration stops.
|
||||
// If an Err is encountered, it is yielded downstream as-is (consumer-driven).
|
||||
// Future occurrences of a previously seen Ok value are skipped.
|
||||
func (seq SeqResult[V]) Unique() SeqResult[V] {
|
||||
return func(yield func(Result[V]) bool) {
|
||||
seen := NewSet[any]()
|
||||
seq(func(v Result[V]) bool {
|
||||
if v.IsErr() {
|
||||
yield(v)
|
||||
return false
|
||||
}
|
||||
if !seen.Contains(v.v) {
|
||||
seen.Insert(v.v)
|
||||
if isValueComparable[V]() {
|
||||
seen := NewSet[any]()
|
||||
|
||||
seq(func(v Result[V]) bool {
|
||||
if v.IsErr() {
|
||||
return yield(v)
|
||||
}
|
||||
|
||||
k := any(v.v)
|
||||
if _, ok := seen[k]; !ok {
|
||||
seen[k] = Unit{}
|
||||
return yield(v)
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
} else {
|
||||
var seen Slice[V]
|
||||
|
||||
seq(func(v Result[V]) bool {
|
||||
if v.IsErr() {
|
||||
return yield(v)
|
||||
}
|
||||
|
||||
for _, s := range seen {
|
||||
if reflect.DeepEqual(s, v.v) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
seen = append(seen, v.v)
|
||||
return yield(v)
|
||||
}
|
||||
return true
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,14 +332,18 @@ func (seq SeqResult[V]) Range(fn func(v Result[V]) bool) {
|
||||
|
||||
// Skip returns a new sequence that skips the first n Ok elements.
|
||||
//
|
||||
// If an Err is encountered, it is yielded as is and iteration stops. Once n Ok elements have been skipped,
|
||||
// If an Err is encountered, it is yielded as-is without consuming the skip
|
||||
// budget (consumer-driven). Once n Ok elements have been skipped,
|
||||
// subsequent elements (Ok or Err) are yielded normally.
|
||||
func (seq SeqResult[V]) Skip(n uint) SeqResult[V] {
|
||||
func (seq SeqResult[V]) Skip(n Int) SeqResult[V] {
|
||||
return func(yield func(Result[V]) bool) {
|
||||
if n < 0 {
|
||||
n = 0
|
||||
}
|
||||
|
||||
seq(func(v Result[V]) bool {
|
||||
if v.IsErr() {
|
||||
yield(v)
|
||||
return false
|
||||
return yield(v)
|
||||
}
|
||||
if n > 0 {
|
||||
n--
|
||||
@@ -239,15 +356,18 @@ func (seq SeqResult[V]) Skip(n uint) SeqResult[V] {
|
||||
|
||||
// StepBy creates a new sequence that yields every nth Ok element from the original sequence.
|
||||
//
|
||||
// If an Err is encountered, it is yielded immediately and stops iteration.
|
||||
// If an Err is encountered, it is yielded downstream as-is (consumer-driven).
|
||||
// For Ok elements, only every n-th element is yielded.
|
||||
func (seq SeqResult[V]) StepBy(n uint) SeqResult[V] {
|
||||
func (seq SeqResult[V]) StepBy(n Int) SeqResult[V] {
|
||||
return func(yield func(Result[V]) bool) {
|
||||
i := uint(0)
|
||||
if n <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
i := Int(0)
|
||||
seq(func(v Result[V]) bool {
|
||||
if v.IsErr() {
|
||||
yield(v)
|
||||
return false
|
||||
return yield(v)
|
||||
}
|
||||
i++
|
||||
if (i-1)%n == 0 {
|
||||
@@ -259,20 +379,34 @@ func (seq SeqResult[V]) StepBy(n uint) SeqResult[V] {
|
||||
}
|
||||
|
||||
// Take returns a new sequence with the first n Ok elements.
|
||||
// If an Err is encountered, it is yielded immediately and iteration stops.
|
||||
// If an Err is encountered, it is yielded downstream as-is (consumer-driven).
|
||||
// After n Ok elements are yielded, the sequence ends.
|
||||
func (seq SeqResult[V]) Take(n uint) SeqResult[V] {
|
||||
func (seq SeqResult[V]) Take(n Int) SeqResult[V] {
|
||||
return func(yield func(Result[V]) bool) {
|
||||
if n <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
seq(func(v Result[V]) bool {
|
||||
if v.IsErr() {
|
||||
yield(v)
|
||||
return false
|
||||
}
|
||||
// Once n Ok values are taken, stop hard — nothing further (Ok or Err)
|
||||
// is yielded, even if the source ignores our stop signal.
|
||||
if n == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if v.IsErr() {
|
||||
return yield(v)
|
||||
}
|
||||
|
||||
if !yield(v) {
|
||||
return false
|
||||
}
|
||||
|
||||
n--
|
||||
return yield(v)
|
||||
|
||||
// Stop tightly once n elements are taken so a well-behaved source is
|
||||
// not pulled one extra time.
|
||||
return n > 0
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -313,24 +447,31 @@ func (seq SeqResult[V]) Nth(n Int) Result[Option[V]] {
|
||||
// Chain concatenates this sequence with other sequences, returning a new sequence of Result[V].
|
||||
//
|
||||
// The function yields all elements (Ok or Err) from the current sequence, then from each of the provided sequences in order.
|
||||
// If an Err is encountered, it is yielded immediately, ending further iteration.
|
||||
// Err elements are yielded like any other element (consumer-driven).
|
||||
func (seq SeqResult[V]) Chain(seqs ...SeqResult[V]) SeqResult[V] {
|
||||
return func(yield func(Result[V]) bool) {
|
||||
stopped := false
|
||||
|
||||
for _, seq := range append([]SeqResult[V]{seq}, seqs...) {
|
||||
seq(func(v Result[V]) bool {
|
||||
if v.IsErr() {
|
||||
yield(v)
|
||||
if !yield(v) {
|
||||
stopped = true
|
||||
return false
|
||||
}
|
||||
return yield(v)
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
if stopped {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Intersperse inserts the provided Ok separator between each Ok element of the sequence.
|
||||
//
|
||||
// If an Err is encountered, it is yielded as Err and iteration stops immediately.
|
||||
// If an Err is encountered, it is yielded as-is without a separator (consumer-driven).
|
||||
// For Ok elements, after the first yield, a separator is inserted before each subsequent Ok value.
|
||||
func (seq SeqResult[V]) Intersperse(sep V) SeqResult[V] {
|
||||
return func(yield func(Result[V]) bool) {
|
||||
@@ -338,8 +479,7 @@ func (seq SeqResult[V]) Intersperse(sep V) SeqResult[V] {
|
||||
|
||||
seq(func(v Result[V]) bool {
|
||||
if v.IsErr() {
|
||||
yield(v)
|
||||
return false
|
||||
return yield(v)
|
||||
}
|
||||
|
||||
if !first && !yield(Ok(sep)) {
|
||||
@@ -353,13 +493,12 @@ func (seq SeqResult[V]) Intersperse(sep V) SeqResult[V] {
|
||||
}
|
||||
|
||||
// Inspect calls fn for every Ok value without changing it.
|
||||
// An Err immediately stops iteration by returning false.
|
||||
// Err elements are passed through unchanged (consumer-driven).
|
||||
func (seq SeqResult[V]) Inspect(fn func(v V)) SeqResult[V] {
|
||||
return func(yield func(Result[V]) bool) {
|
||||
seq(func(v Result[V]) bool {
|
||||
if v.IsErr() {
|
||||
yield(v)
|
||||
return false
|
||||
return yield(v)
|
||||
}
|
||||
fn(v.v)
|
||||
return yield(v)
|
||||
@@ -453,7 +592,7 @@ func (seq SeqResult[V]) Last() Result[Option[V]] {
|
||||
// Returns:
|
||||
// - Option[Result[V]]: Some(Result[V]) if an element exists, None if the iterator is exhausted.
|
||||
func (seq *SeqResult[V]) Next() Option[Result[V]] {
|
||||
if value, remaining, ok := iter.Next(iter.Seq[Result[V]](*seq)); ok {
|
||||
if value, remaining, ok := Seq[Result[V]](*seq).seqNext(); ok {
|
||||
*seq = SeqResult[V](remaining)
|
||||
return Some(value)
|
||||
}
|
||||
@@ -481,8 +620,8 @@ func (seq SeqResult[V]) Partition() (Slice[V], Slice[error]) {
|
||||
|
||||
// Ok returns a new sequence containing only the Ok values from the original sequence.
|
||||
// All Err values are filtered out.
|
||||
func (seq SeqResult[V]) Ok() SeqSlice[V] {
|
||||
return SeqSlice[V](func(yield func(V) bool) {
|
||||
func (seq SeqResult[V]) Ok() Seq[V] {
|
||||
return Seq[V](func(yield func(V) bool) {
|
||||
seq(func(v Result[V]) bool {
|
||||
if v.IsOk() {
|
||||
return yield(v.v)
|
||||
@@ -494,8 +633,8 @@ func (seq SeqResult[V]) Ok() SeqSlice[V] {
|
||||
|
||||
// Err returns a new sequence containing only the error values from the original sequence.
|
||||
// All Ok values are filtered out.
|
||||
func (seq SeqResult[V]) Err() SeqSlice[error] {
|
||||
return SeqSlice[error](func(yield func(error) bool) {
|
||||
func (seq SeqResult[V]) Err() Seq[error] {
|
||||
return Seq[error](func(yield func(error) bool) {
|
||||
seq(func(v Result[V]) bool {
|
||||
if v.IsErr() {
|
||||
return yield(v.err)
|
||||
@@ -525,3 +664,380 @@ func (seq SeqResult[V]) FirstErr() Option[error] {
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// FromResultChan converts a channel of Results into a SeqResult iterator.
|
||||
// It consumes the channel until it's closed, yielding each Result to the iterator.
|
||||
// This is particularly useful with pool.Stream() for processing task results
|
||||
// as they complete in real-time.
|
||||
//
|
||||
// Example usage with pool.Stream:
|
||||
//
|
||||
// p := pool.New[int]().Limit(10)
|
||||
// ch := p.Stream(func() {
|
||||
// for i := range 100 {
|
||||
// p.Go(func() Result[int] {
|
||||
// if i%10 == 0 {
|
||||
// return Err[int](fmt.Errorf("task %d failed", i))
|
||||
// }
|
||||
// return Ok(i * i)
|
||||
// })
|
||||
// }
|
||||
// })
|
||||
//
|
||||
// successful, failed := FromResultChan(ch).Partition()
|
||||
// fmt.Printf("Successful: %d, Failed: %d\n", successful.Len(), failed.Len())
|
||||
func FromResultChan[V any](ch <-chan Result[V]) SeqResult[V] {
|
||||
return SeqResult[V](seqFromChan(ch))
|
||||
}
|
||||
|
||||
// Fold reduces the sequence to a single value using an accumulator.
|
||||
// The accumulator type may differ from the element type. The first Err
|
||||
// short-circuits the iteration and is returned as Err.
|
||||
func (seq SeqResult[V]) Fold[A any](init A, fn func(acc A, val V) A) Result[A] {
|
||||
acc := init
|
||||
var err error
|
||||
|
||||
seq(func(r Result[V]) bool {
|
||||
if r.IsErr() {
|
||||
err = r.err
|
||||
return false
|
||||
}
|
||||
|
||||
acc = fn(acc, r.v)
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return Err[A](err)
|
||||
}
|
||||
|
||||
return Ok(acc)
|
||||
}
|
||||
|
||||
// SumBy maps each Ok value to a numeric value via fn and returns their sum wrapped in Ok.
|
||||
// The first Err short-circuits: iteration stops and that error is returned as Err[S].
|
||||
// An empty (or all-consumed) sequence yields Ok of the zero value of S.
|
||||
func (seq SeqResult[V]) SumBy[S constraints.Number](fn func(V) S) Result[S] {
|
||||
var zero S
|
||||
return seq.Fold(zero, func(acc S, v V) S { return acc + fn(v) })
|
||||
}
|
||||
|
||||
// ProductBy maps each Ok value to a numeric value via fn and returns their product
|
||||
// wrapped in Ok. The first Err short-circuits and is returned as Err[S]. An empty
|
||||
// (or all-consumed) sequence yields Ok of the multiplicative identity, one.
|
||||
func (seq SeqResult[V]) ProductBy[S constraints.Number](fn func(V) S) Result[S] {
|
||||
return seq.Fold(S(1), func(acc S, v V) S { return acc * fn(v) })
|
||||
}
|
||||
|
||||
// FindMap applies fn to each Ok value and returns the first Some result wrapped in
|
||||
// Ok; None if fn returns None for every value. The first Err short-circuits and is
|
||||
// returned as Err.
|
||||
func (seq SeqResult[V]) FindMap[U any](fn func(V) Option[U]) Result[Option[U]] {
|
||||
result := Ok(None[U]())
|
||||
|
||||
seq(func(v Result[V]) bool {
|
||||
if v.IsErr() {
|
||||
result = Err[Option[U]](v.err)
|
||||
return false
|
||||
}
|
||||
|
||||
if o := fn(v.v); o.IsSome() {
|
||||
result = Ok(o)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Reduce aggregates Ok values using the provided function:
|
||||
// the first Err short-circuits and is returned as Err; an empty sequence yields Ok(None);
|
||||
// otherwise Ok(Some(accumulated)).
|
||||
func (seq SeqResult[V]) Reduce(fn func(a, b V) V) Result[Option[V]] {
|
||||
var (
|
||||
acc V
|
||||
first = true
|
||||
err error
|
||||
)
|
||||
|
||||
seq(func(r Result[V]) bool {
|
||||
if r.IsErr() {
|
||||
err = r.err
|
||||
return false
|
||||
}
|
||||
|
||||
if first {
|
||||
acc, first = r.v, false
|
||||
} else {
|
||||
acc = fn(acc, r.v)
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return Err[Option[V]](err)
|
||||
}
|
||||
|
||||
return Ok(OptionOf(acc, !first))
|
||||
}
|
||||
|
||||
// Scan accumulates Ok values, yielding the initial value followed by every
|
||||
// intermediate accumulator state. The accumulator type may differ from the
|
||||
// element type. An Err is passed downstream as-is without touching the
|
||||
// accumulator; iteration continues for as long as the consumer keeps
|
||||
// accepting values (consumer-driven).
|
||||
func (seq SeqResult[V]) Scan[A any](init A, fn func(acc A, val V) A) SeqResult[A] {
|
||||
return func(yield func(Result[A]) bool) {
|
||||
if !yield(Ok(init)) {
|
||||
return
|
||||
}
|
||||
|
||||
acc := init
|
||||
|
||||
seq(func(r Result[V]) bool {
|
||||
if r.IsErr() {
|
||||
return yield(Err[A](r.err))
|
||||
}
|
||||
|
||||
acc = fn(acc, r.v)
|
||||
|
||||
return yield(Ok(acc))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// FilterMap transforms each Ok value with fn and keeps only the Some results,
|
||||
// changing the element type from V to U.
|
||||
//
|
||||
// If an Err is encountered, it is passed downstream as-is (Err[U]); the consumer
|
||||
// decides whether to continue (consumer-driven). For an Ok value, fn is applied:
|
||||
// Some(u) is yielded as Ok(u), None drops the element.
|
||||
func (seq SeqResult[V]) FilterMap[U any](fn func(V) Option[U]) SeqResult[U] {
|
||||
return func(yield func(Result[U]) bool) {
|
||||
seq(func(v Result[V]) bool {
|
||||
if v.IsErr() {
|
||||
return yield(Err[U](v.err))
|
||||
}
|
||||
|
||||
if u, ok := fn(v.v).Option(); ok {
|
||||
return yield(Ok(u))
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TakeWhile yields Ok values while fn returns true, stopping at the first Ok
|
||||
// value for which fn returns false.
|
||||
//
|
||||
// If an Err is encountered, it is passed downstream as-is and does not stop the
|
||||
// taking (consumer-driven); only a failing predicate on an Ok value ends it.
|
||||
func (seq SeqResult[V]) TakeWhile(fn func(V) bool) SeqResult[V] {
|
||||
return func(yield func(Result[V]) bool) {
|
||||
seq(func(v Result[V]) bool {
|
||||
if v.IsErr() {
|
||||
return yield(v)
|
||||
}
|
||||
|
||||
if !fn(v.v) {
|
||||
return false
|
||||
}
|
||||
|
||||
return yield(v)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// SkipWhile skips Ok values while fn returns true, then yields every remaining
|
||||
// element.
|
||||
//
|
||||
// If an Err is encountered, it is passed downstream as-is regardless of the
|
||||
// skipping phase (consumer-driven); the skipping predicate is evaluated only on
|
||||
// Ok values.
|
||||
func (seq SeqResult[V]) SkipWhile(fn func(V) bool) SeqResult[V] {
|
||||
return func(yield func(Result[V]) bool) {
|
||||
skipping := true
|
||||
|
||||
seq(func(v Result[V]) bool {
|
||||
if v.IsErr() {
|
||||
return yield(v)
|
||||
}
|
||||
|
||||
if skipping && fn(v.v) {
|
||||
return true
|
||||
}
|
||||
|
||||
skipping = false
|
||||
|
||||
return yield(v)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// MaxBy returns the maximum Ok value according to fn, mirroring the short-circuit
|
||||
// terminals: the first Err stops iteration and is returned as Err; a sequence with
|
||||
// no Ok values yields Ok(None).
|
||||
func (seq SeqResult[V]) MaxBy(fn func(V, V) cmp.Ordering) Result[Option[V]] {
|
||||
var best V
|
||||
has := false
|
||||
|
||||
result := Ok(None[V]())
|
||||
|
||||
seq(func(v Result[V]) bool {
|
||||
if v.IsErr() {
|
||||
result = Err[Option[V]](v.err)
|
||||
return false
|
||||
}
|
||||
|
||||
if !has || fn(best, v.v).IsLt() {
|
||||
best = v.v
|
||||
has = true
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
if result.IsErr() {
|
||||
return result
|
||||
}
|
||||
|
||||
if has {
|
||||
return Ok(Some(best))
|
||||
}
|
||||
|
||||
return Ok(None[V]())
|
||||
}
|
||||
|
||||
// MinBy returns the minimum Ok value according to fn, mirroring the short-circuit
|
||||
// terminals: the first Err stops iteration and is returned as Err; a sequence with
|
||||
// no Ok values yields Ok(None).
|
||||
func (seq SeqResult[V]) MinBy(fn func(V, V) cmp.Ordering) Result[Option[V]] {
|
||||
var best V
|
||||
has := false
|
||||
|
||||
result := Ok(None[V]())
|
||||
|
||||
seq(func(v Result[V]) bool {
|
||||
if v.IsErr() {
|
||||
result = Err[Option[V]](v.err)
|
||||
return false
|
||||
}
|
||||
|
||||
if !has || fn(v.v, best).IsLt() {
|
||||
best = v.v
|
||||
has = true
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
if result.IsErr() {
|
||||
return result
|
||||
}
|
||||
|
||||
if has {
|
||||
return Ok(Some(best))
|
||||
}
|
||||
|
||||
return Ok(None[V]())
|
||||
}
|
||||
|
||||
// Flatten flattens one or more levels of nested slices/arrays inside each Ok
|
||||
// value, yielding the leaf elements as Ok. Err elements are passed downstream
|
||||
// as-is (consumer-driven). It mirrors Seq.Flatten and, like it, relies on
|
||||
// reflection: only leaves assignable to V are yielded.
|
||||
func (seq SeqResult[V]) Flatten() SeqResult[V] {
|
||||
return func(yield func(Result[V]) bool) {
|
||||
emit := func(v V) bool { return yield(Ok(v)) }
|
||||
|
||||
seq(func(v Result[V]) bool {
|
||||
if v.IsErr() {
|
||||
return yield(v)
|
||||
}
|
||||
|
||||
return flattenValue(v.v, emit)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// SortBy consumes the sequence, sorts the Ok values with fn, and re-emits them
|
||||
// in order as a SeqResult. Being a sort, it is eager: the whole sequence is
|
||||
// buffered first. The first Err short-circuits — buffering stops and only that
|
||||
// Err is yielded downstream.
|
||||
func (seq SeqResult[V]) SortBy(fn func(a, b V) cmp.Ordering) SeqResult[V] {
|
||||
return func(yield func(Result[V]) bool) {
|
||||
items := NewSlice[V]()
|
||||
|
||||
var err error
|
||||
|
||||
seq(func(v Result[V]) bool {
|
||||
if v.IsErr() {
|
||||
err = v.err
|
||||
return false
|
||||
}
|
||||
|
||||
items = append(items, v.v)
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
yield(Err[V](err))
|
||||
return
|
||||
}
|
||||
|
||||
items.SortBy(fn)
|
||||
|
||||
for _, v := range items {
|
||||
if !yield(Ok(v)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CounterBy counts how many Ok values map to each key produced by fn, returning
|
||||
// the tally as plain pairs in first-seen key order (convert with MapOrd[K, Int]
|
||||
// if map access is needed). It is a short-circuit terminal: the first Err stops
|
||||
// the count and is returned as Err. (A lazy SeqResult of the tally is
|
||||
// impossible here — it would instantiate SeqResult with a type built from V and
|
||||
// hit an instantiation cycle; returning MapOrd would weld SeqResult to the map
|
||||
// cluster.)
|
||||
func (seq SeqResult[V]) CounterBy[K comparable](fn func(V) K) Result[[]Pair[K, Int]] {
|
||||
order := NewSlice[K]()
|
||||
counts := NewMap[K, Int]()
|
||||
|
||||
var err error
|
||||
|
||||
seq(func(v Result[V]) bool {
|
||||
if v.IsErr() {
|
||||
err = v.err
|
||||
return false
|
||||
}
|
||||
|
||||
k := fn(v.v)
|
||||
if !counts.Contains(k) {
|
||||
order.Push(k)
|
||||
}
|
||||
|
||||
counts[k]++
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return Err[[]Pair[K, Int]](err)
|
||||
}
|
||||
|
||||
result := make([]Pair[K, Int], 0, order.Len())
|
||||
for _, k := range order {
|
||||
result = append(result, Pair[K, Int]{Key: k, Value: counts[k]})
|
||||
}
|
||||
|
||||
return Ok(result)
|
||||
}
|
||||
+120
-106
@@ -1,70 +1,49 @@
|
||||
package g
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
import "fmt"
|
||||
|
||||
// Set is a generic alias for a set implemented using a map.
|
||||
type Set[T comparable] map[T]Unit
|
||||
|
||||
// NewSet creates a new Set of the specified size or an empty Set if no size is provided.
|
||||
func NewSet[T comparable](size ...Int) Set[T] {
|
||||
return make(Set[T], Slice[Int](size).Get(0).UnwrapOrDefault())
|
||||
}
|
||||
|
||||
// TransformSet applies the given function to each element of a Set and returns a new Set
|
||||
// containing the transformed values.
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// - s: The input Set.
|
||||
// - fn: The function to apply to each element of the input Set.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// A new Set containing the results of applying the function to each element of the input Set.
|
||||
func TransformSet[T, U comparable](s Set[T], fn func(T) U) Set[U] {
|
||||
if len(s) == 0 {
|
||||
return NewSet[U]()
|
||||
if len(size) > 0 {
|
||||
return make(Set[T], size[0])
|
||||
}
|
||||
|
||||
result := make(Set[U], len(s))
|
||||
for v := range s {
|
||||
result[fn(v)] = struct{}{}
|
||||
}
|
||||
|
||||
return result
|
||||
return make(Set[T])
|
||||
}
|
||||
|
||||
// SetOf creates a new generic set containing the provided elements.
|
||||
func SetOf[T comparable](values ...T) Set[T] {
|
||||
set := make(Set[T], len(values))
|
||||
for _, v := range values {
|
||||
set[v] = struct{}{}
|
||||
set[v] = Unit{}
|
||||
}
|
||||
|
||||
return set
|
||||
}
|
||||
|
||||
// Transform applies a transformation function to the Set and returns the result.
|
||||
func (s Set[T]) Transform(fn func(Set[T]) Set[T]) Set[T] { return fn(s) }
|
||||
func (s Set[T]) Transform[U any](fn func(Set[T]) U) U { return fn(s) }
|
||||
|
||||
// Iter returns an iterator (SeqSet[T]) for the Set, allowing for sequential iteration
|
||||
// Iter returns an iterator (Seq[T]) for the Set, allowing for sequential iteration
|
||||
// over its elements. It is commonly used in combination with higher-order functions,
|
||||
// such as 'ForEach' or 'SetMap', to perform operations on each element of the Set.
|
||||
// such as 'ForEach' or 'Map', to perform operations on each element of the Set.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// A SeqSet[T], which can be used for sequential iteration over the elements of the Set.
|
||||
// A Seq[T], which can be used for sequential iteration over the elements of the Set.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// iter := g.SetOf(1, 2, 3).Iter()
|
||||
// iter.ForEach(func(val T) {
|
||||
// g.SetOf(1, 2, 3).Iter().ForEach(func(val int) {
|
||||
// fmt.Println(val) // Replace this with the function logic you need.
|
||||
// })
|
||||
//
|
||||
// The 'Iter' method provides a convenient way to traverse the elements of a Set
|
||||
// in a functional style, enabling operations like mapping or filtering.
|
||||
// func (s Set[T]) Iter() SeqSet[T] { return seqSet(s) }
|
||||
func (s Set[T]) Iter() SeqSet[T] {
|
||||
func (s Set[T]) Iter() Seq[T] {
|
||||
return func(yield func(T) bool) {
|
||||
for v := range s {
|
||||
if !yield(v) {
|
||||
@@ -74,20 +53,21 @@ func (s Set[T]) Iter() SeqSet[T] {
|
||||
}
|
||||
}
|
||||
|
||||
// func (s Set[T]) Iter() SeqSet[T] { return seqSet(s) }
|
||||
|
||||
// Insert adds the provided elements to the set.
|
||||
func (s Set[T]) Insert(values ...T) {
|
||||
for _, v := range values {
|
||||
s[v] = struct{}{}
|
||||
s[v] = Unit{}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove removes the specified values from the Set.
|
||||
func (s Set[T]) Remove(values ...T) {
|
||||
for _, v := range values {
|
||||
// Remove removes the specified value from the Set and returns true if it was present.
|
||||
func (s Set[T]) Remove(v T) bool {
|
||||
if _, ok := s[v]; ok {
|
||||
delete(s, v)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Len returns the number of values in the Set.
|
||||
@@ -99,36 +79,22 @@ func (s Set[T]) Contains(v T) bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
// ContainsAny checks if the Set contains any element from another Set.
|
||||
func (s Set[T]) ContainsAny(other Set[T]) bool {
|
||||
if s.Empty() || other.Empty() {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(s) <= len(other) {
|
||||
for v := range s {
|
||||
if _, ok := other[v]; ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for v := range other {
|
||||
if _, ok := s[v]; ok {
|
||||
return true
|
||||
}
|
||||
// ContainsAny checks if the Set contains any of the provided values, matching
|
||||
// the variadic shape of Slice.ContainsAny and String.ContainsAny.
|
||||
func (s Set[T]) ContainsAny(values ...T) bool {
|
||||
for _, v := range values {
|
||||
if _, ok := s[v]; ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// ContainsAll checks if the Set contains all elements from another Set.
|
||||
func (s Set[T]) ContainsAll(other Set[T]) bool {
|
||||
if len(s) < len(other) {
|
||||
return false
|
||||
}
|
||||
|
||||
for v := range other {
|
||||
// ContainsAll checks if the Set contains all of the provided values, matching
|
||||
// the variadic shape of Slice.ContainsAll and String.ContainsAll.
|
||||
func (s Set[T]) ContainsAll(values ...T) bool {
|
||||
for _, v := range values {
|
||||
if _, ok := s[v]; !ok {
|
||||
return false
|
||||
}
|
||||
@@ -139,32 +105,18 @@ func (s Set[T]) ContainsAll(other Set[T]) bool {
|
||||
|
||||
// Clone creates a new Set that is a copy of the original Set.
|
||||
func (s Set[T]) Clone() Set[T] {
|
||||
if s.Empty() {
|
||||
if s.IsEmpty() {
|
||||
return NewSet[T]()
|
||||
}
|
||||
|
||||
clone := make(Set[T], len(s))
|
||||
for k := range s {
|
||||
clone[k] = struct{}{}
|
||||
clone[k] = Unit{}
|
||||
}
|
||||
|
||||
return clone
|
||||
}
|
||||
|
||||
// ToSlice returns a new Slice with the same elements as the Set[T].
|
||||
func (s Set[T]) ToSlice() Slice[T] {
|
||||
if s.Empty() {
|
||||
return NewSlice[T]()
|
||||
}
|
||||
|
||||
sl := make(Slice[T], 0, len(s))
|
||||
for v := range s {
|
||||
sl = append(sl, v)
|
||||
}
|
||||
|
||||
return sl
|
||||
}
|
||||
|
||||
// Intersection returns the intersection of the current set and another set, i.e., elements
|
||||
// present in both sets.
|
||||
//
|
||||
@@ -183,12 +135,21 @@ func (s Set[T]) ToSlice() Slice[T] {
|
||||
// intersection := s1.Intersection(s2)
|
||||
//
|
||||
// The resulting intersection will be: [4, 5].
|
||||
func (s Set[T]) Intersection(other Set[T]) SeqSet[T] {
|
||||
if len(s) <= len(other) {
|
||||
return intersection(s.Iter(), other)
|
||||
func (s Set[T]) Intersection(other Set[T]) Set[T] {
|
||||
small, big := s, other
|
||||
if len(big) < len(small) {
|
||||
small, big = big, small
|
||||
}
|
||||
|
||||
return intersection(other.Iter(), s)
|
||||
result := make(Set[T], len(small))
|
||||
|
||||
for v := range small {
|
||||
if big.Contains(v) {
|
||||
result[v] = Unit{}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Difference returns the difference between the current set and another set,
|
||||
@@ -209,7 +170,17 @@ func (s Set[T]) Intersection(other Set[T]) SeqSet[T] {
|
||||
// diff := s1.Difference(s2)
|
||||
//
|
||||
// The resulting diff will be: [1, 2, 3].
|
||||
func (s Set[T]) Difference(other Set[T]) SeqSet[T] { return difference(s.Iter(), other) }
|
||||
func (s Set[T]) Difference(other Set[T]) Set[T] {
|
||||
result := make(Set[T], len(s))
|
||||
|
||||
for v := range s {
|
||||
if !other.Contains(v) {
|
||||
result[v] = Unit{}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Union returns a new set containing the unique elements of the current set and the provided
|
||||
// other set.
|
||||
@@ -230,12 +201,18 @@ func (s Set[T]) Difference(other Set[T]) SeqSet[T] { return difference(s.Iter(),
|
||||
// union := s1.Union(s2)
|
||||
//
|
||||
// The resulting union set will be: [1, 2, 3, 4, 5].
|
||||
func (s Set[T]) Union(other Set[T]) SeqSet[T] {
|
||||
if len(s) > len(other) {
|
||||
return s.Iter().Chain(other.Difference(s))
|
||||
func (s Set[T]) Union(other Set[T]) Set[T] {
|
||||
result := make(Set[T], len(s)+len(other))
|
||||
|
||||
for v := range s {
|
||||
result[v] = Unit{}
|
||||
}
|
||||
|
||||
return other.Iter().Chain(s.Difference(other))
|
||||
for v := range other {
|
||||
result[v] = Unit{}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// SymmetricDifference returns the symmetric difference between the current set and another
|
||||
@@ -256,8 +233,22 @@ func (s Set[T]) Union(other Set[T]) SeqSet[T] {
|
||||
// symDiff := s1.SymmetricDifference(s2)
|
||||
//
|
||||
// The resulting symDiff will be: [1, 2, 3, 6, 7, 8].
|
||||
func (s Set[T]) SymmetricDifference(other Set[T]) SeqSet[T] {
|
||||
return s.Difference(other).Chain(other.Difference(s))
|
||||
func (s Set[T]) SymmetricDifference(other Set[T]) Set[T] {
|
||||
result := make(Set[T])
|
||||
|
||||
for v := range s {
|
||||
if !other.Contains(v) {
|
||||
result[v] = Unit{}
|
||||
}
|
||||
}
|
||||
|
||||
for v := range other {
|
||||
if !s.Contains(v) {
|
||||
result[v] = Unit{}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Subset checks if the current set 's' is a subset of the provided 'other' set.
|
||||
@@ -276,7 +267,19 @@ func (s Set[T]) SymmetricDifference(other Set[T]) SeqSet[T] {
|
||||
// s1 := g.SetOf(1, 2, 3)
|
||||
// s2 := g.SetOf(1, 2, 3, 4, 5)
|
||||
// isSubset := s1.Subset(s2) // Returns true
|
||||
func (s Set[T]) Subset(other Set[T]) bool { return other.ContainsAll(s) }
|
||||
func (s Set[T]) Subset(other Set[T]) bool {
|
||||
if len(s) > len(other) {
|
||||
return false
|
||||
}
|
||||
|
||||
for v := range s {
|
||||
if _, ok := other[v]; !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Superset checks if the current set 's' is a superset of the provided 'other' set.
|
||||
// A set 's' is a superset of 'other' if all elements of 'other' are also elements of 's'.
|
||||
@@ -294,7 +297,7 @@ func (s Set[T]) Subset(other Set[T]) bool { return other.ContainsAll(s) }
|
||||
// s1 := g.SetOf(1, 2, 3, 4, 5)
|
||||
// s2 := g.SetOf(1, 2, 3)
|
||||
// isSuperset := s1.Superset(s2) // Returns true
|
||||
func (s Set[T]) Superset(other Set[T]) bool { return s.ContainsAll(other) }
|
||||
func (s Set[T]) Superset(other Set[T]) bool { return other.Subset(s) }
|
||||
|
||||
// Eq checks if two Sets are equal.
|
||||
func (s Set[T]) Eq(other Set[T]) bool {
|
||||
@@ -315,25 +318,19 @@ func (s Set[T]) Eq(other Set[T]) bool {
|
||||
func (s Set[T]) Ne(other Set[T]) bool { return !s.Eq(other) }
|
||||
|
||||
// Clear removes all values from the Set.
|
||||
func (s Set[T]) Clear() {
|
||||
for k := range s {
|
||||
delete(s, k)
|
||||
}
|
||||
}
|
||||
func (s Set[T]) Clear() { clear(s) }
|
||||
|
||||
// Empty checks if the Set is empty.
|
||||
func (s Set[T]) Empty() bool { return len(s) == 0 }
|
||||
|
||||
// NotEmpty checks if the Set is not empty.
|
||||
func (s Set[T]) NotEmpty() bool { return !s.Empty() }
|
||||
// IsEmpty checks if the Set is empty.
|
||||
func (s Set[T]) IsEmpty() bool { return len(s) == 0 }
|
||||
|
||||
// String returns a string representation of the Set.
|
||||
func (s Set[T]) String() string {
|
||||
if s.Empty() {
|
||||
if s.IsEmpty() {
|
||||
return "Set{}"
|
||||
}
|
||||
|
||||
var b Builder
|
||||
b.Grow(Int(len(s)) * 8)
|
||||
b.WriteString("Set{")
|
||||
|
||||
first := true
|
||||
@@ -343,7 +340,7 @@ func (s Set[T]) String() string {
|
||||
}
|
||||
|
||||
first = false
|
||||
b.WriteString(Format("{}", v))
|
||||
fmt.Fprint(&b, v)
|
||||
}
|
||||
|
||||
b.WriteString("}")
|
||||
@@ -351,6 +348,23 @@ func (s Set[T]) String() string {
|
||||
return b.String().Std()
|
||||
}
|
||||
|
||||
// Disjoint reports whether the set has no elements in common with other.
|
||||
// It is the complement of ContainsAny.
|
||||
func (s Set[T]) Disjoint(other Set[T]) bool {
|
||||
small, big := s, other
|
||||
if len(big) < len(small) {
|
||||
small, big = big, small
|
||||
}
|
||||
|
||||
for v := range small {
|
||||
if _, ok := big[v]; ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Print writes the elements of the Set to the standard output (console)
|
||||
// and returns the Set unchanged.
|
||||
func (s Set[T]) Print() Set[T] { fmt.Print(s); return s }
|
||||
|
||||
-315
@@ -1,315 +0,0 @@
|
||||
package g
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/enetx/iter"
|
||||
)
|
||||
|
||||
// Pull converts the “push-style” iterator sequence seq
|
||||
// into a “pull-style” iterator accessed by the two functions
|
||||
// next and stop.
|
||||
//
|
||||
// Next returns the next value in the sequence
|
||||
// and a boolean indicating whether the value is valid.
|
||||
// When the sequence is over, next returns the zero V and false.
|
||||
// It is valid to call next after reaching the end of the sequence
|
||||
// or after calling stop. These calls will continue
|
||||
// to return the zero V and false.
|
||||
//
|
||||
// Stop ends the iteration. It must be called when the caller is
|
||||
// no longer interested in next values and next has not yet
|
||||
// signaled that the sequence is over (with a false boolean return).
|
||||
// It is valid to call stop multiple times and when next has
|
||||
// already returned false.
|
||||
//
|
||||
// It is an error to call next or stop from multiple goroutines
|
||||
// simultaneously.
|
||||
func (seq SeqSet[V]) Pull() (func() (V, bool), func()) { return iter.Pull(iter.Seq[V](seq)) }
|
||||
|
||||
// Inspect creates a new iterator that wraps around the current iterator
|
||||
// and allows inspecting each element as it passes through.
|
||||
func (seq SeqSet[V]) Inspect(fn func(v V)) SeqSet[V] {
|
||||
return SeqSet[V](iter.Inspect(iter.Seq[V](seq), fn))
|
||||
}
|
||||
|
||||
// Collect gathers all elements from the iterator into a Set.
|
||||
func (seq SeqSet[V]) Collect() Set[V] {
|
||||
collection := make(Set[V])
|
||||
|
||||
seq(func(v V) bool {
|
||||
collection[v] = struct{}{}
|
||||
return true
|
||||
})
|
||||
|
||||
return collection
|
||||
}
|
||||
|
||||
// Chain concatenates the current iterator with other iterators, returning a new iterator.
|
||||
//
|
||||
// The function creates a new iterator that combines the elements of the current iterator
|
||||
// with elements from the provided iterators in the order they are given.
|
||||
//
|
||||
// Params:
|
||||
//
|
||||
// - seqs ([]SeqSet[V]): Other iterators to be concatenated with the current iterator.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - SeqSet[V]: A new iterator containing elements from the current iterator and the provided iterators.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// iter1 := g.SetOf(1, 2, 3).Iter()
|
||||
// iter2 := g.SetOf(4, 5, 6).Iter()
|
||||
// iter1.Chain(iter2).Collect().Print()
|
||||
//
|
||||
// Output: Set{3, 4, 5, 6, 1, 2} // The output order may vary as the Set type is not ordered.
|
||||
//
|
||||
// The resulting iterator will contain elements from both iterators.
|
||||
func (seq SeqSet[V]) Chain(seqs ...SeqSet[V]) SeqSet[V] {
|
||||
iterSeqs := make([]iter.Seq[V], len(seqs))
|
||||
for i, s := range seqs {
|
||||
iterSeqs[i] = iter.Seq[V](s)
|
||||
}
|
||||
|
||||
return SeqSet[V](iter.Chain(iter.Seq[V](seq), iterSeqs...))
|
||||
}
|
||||
|
||||
// Count consumes the iterator, counting the number of iterations and returning it.
|
||||
func (seq SeqSet[V]) Count() Int { return Int(iter.Count(iter.Seq[V](seq))) }
|
||||
|
||||
// ForEach iterates through all elements and applies the given function to each.
|
||||
//
|
||||
// The function applies the provided function to each element of the iterator.
|
||||
//
|
||||
// Params:
|
||||
//
|
||||
// - fn (func(V)): The function to apply to each element.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// iter := g.SetOf(1, 2, 3).Iter()
|
||||
// iter.ForEach(func(val V) {
|
||||
// fmt.Println(val) // Replace this with the function logic you need.
|
||||
// })
|
||||
//
|
||||
// The provided function will be applied to each element in the iterator.
|
||||
func (seq SeqSet[V]) ForEach(fn func(v V)) { iter.ForEach(iter.Seq[V](seq), fn) }
|
||||
|
||||
// Range iterates through elements until the given function returns false.
|
||||
//
|
||||
// The function iterates through the elements of the iterator and applies the provided function to each element.
|
||||
// The iteration will stop when the provided function returns false for an element.
|
||||
//
|
||||
// Params:
|
||||
// - fn (func(V) bool): The function that evaluates elements for continuation of iteration.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// iter := g.SetOf(1, 2, 2, 3, 4, 5).Iter()
|
||||
//
|
||||
// iter.Range(func(v int) bool {
|
||||
// if v == 3 {
|
||||
// return false
|
||||
// }
|
||||
// print(v)
|
||||
// return true
|
||||
// })
|
||||
func (seq SeqSet[V]) Range(fn func(v V) bool) { iter.Range(iter.Seq[V](seq), fn) }
|
||||
|
||||
// Filter returns a new iterator containing only the elements that satisfy the provided function.
|
||||
//
|
||||
// The function applies the provided function to each element of the iterator.
|
||||
// If the function returns true for an element, that element is included in the resulting iterator.
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// - fn (func(V) bool): The function to be applied to each element of the iterator
|
||||
// to determine if it should be included in the result.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - SeqSet[V]: A new iterator containing the elements that satisfy the given condition.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// set := g.SetOf(1, 2, 3, 4, 5)
|
||||
// even := set.Iter().
|
||||
// Filter(
|
||||
// func(val int) bool {
|
||||
// return val%2 == 0
|
||||
// }).
|
||||
// Collect()
|
||||
// even.Print()
|
||||
//
|
||||
// Output: Set{2, 4} // The output order may vary as the Set type is not ordered.
|
||||
//
|
||||
// The resulting iterator will contain only the elements that satisfy the provided function.
|
||||
func (seq SeqSet[V]) Filter(fn func(V) bool) SeqSet[V] {
|
||||
return SeqSet[V](iter.Filter(iter.Seq[V](seq), fn))
|
||||
}
|
||||
|
||||
// Exclude returns a new iterator excluding elements that satisfy the provided function.
|
||||
//
|
||||
// The function applies the provided function to each element of the iterator.
|
||||
// If the function returns true for an element, that element is excluded from the resulting iterator.
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// - fn (func(V) bool): The function to be applied to each element of the iterator
|
||||
// to determine if it should be excluded from the result.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - SeqSet[V]: A new iterator containing the elements that do not satisfy the given condition.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// set := g.SetOf(1, 2, 3, 4, 5)
|
||||
// notEven := set.Iter().
|
||||
// Exclude(
|
||||
// func(val int) bool {
|
||||
// return val%2 == 0
|
||||
// }).
|
||||
// Collect()
|
||||
// notEven.Print()
|
||||
//
|
||||
// Output: Set{1, 3, 5} // The output order may vary as the Set type is not ordered.
|
||||
//
|
||||
// The resulting iterator will contain only the elements that do not satisfy the provided function.
|
||||
func (seq SeqSet[V]) Exclude(fn func(V) bool) SeqSet[V] {
|
||||
return SeqSet[V](iter.Exclude(iter.Seq[V](seq), fn))
|
||||
}
|
||||
|
||||
// Map transforms each element in the iterator using the given function.
|
||||
//
|
||||
// The function creates a new iterator by applying the provided function to each element
|
||||
// of the original iterator.
|
||||
//
|
||||
// Params:
|
||||
//
|
||||
// - fn (func(V) V): The function used to transform elements.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - SeqSet[V]: A new iterator containing elements transformed by the provided function.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// set := g.SetOf(1, 2, 3)
|
||||
// doubled := set.Iter().
|
||||
// Map(
|
||||
// func(val int) int {
|
||||
// return val * 2
|
||||
// }).
|
||||
// Collect()
|
||||
// doubled.Print()
|
||||
//
|
||||
// Output: Set{2, 4, 6} // The output order may vary as the Set type is not ordered.
|
||||
//
|
||||
// The resulting iterator will contain elements transformed by the provided function.
|
||||
func (seq SeqSet[V]) Map(transform func(V) V) SeqSet[V] {
|
||||
return SeqSet[V](iter.Map(iter.Seq[V](seq), transform))
|
||||
}
|
||||
|
||||
// Find searches for an element in the iterator that satisfies the provided function.
|
||||
//
|
||||
// The function iterates through the elements of the iterator and returns the first element
|
||||
// for which the provided function returns true.
|
||||
//
|
||||
// Params:
|
||||
//
|
||||
// - fn (func(V) bool): The function used to test elements for a condition.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - Option[V]: An Option containing the first element that satisfies the condition; None if not found.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// iter := g.SetOf(1, 2, 3, 4, 5).Iter()
|
||||
//
|
||||
// found := iter.Find(
|
||||
// func(i int) bool {
|
||||
// return i == 2
|
||||
// })
|
||||
//
|
||||
// if found.IsSome() {
|
||||
// fmt.Println("Found:", found.Some())
|
||||
// } else {
|
||||
// fmt.Println("Not found.")
|
||||
// }
|
||||
//
|
||||
// The resulting Option may contain the first element that satisfies the condition, or None if not found.
|
||||
func (seq SeqSet[V]) Find(fn func(v V) bool) Option[V] {
|
||||
return OptionOf(iter.Find(iter.Seq[V](seq), fn))
|
||||
}
|
||||
|
||||
// Context allows the iteration to be controlled with a context.Context.
|
||||
func (seq SeqSet[V]) Context(ctx context.Context) SeqSet[V] {
|
||||
return SeqSet[V](iter.Context(iter.Seq[V](seq), ctx))
|
||||
}
|
||||
|
||||
// Take returns a new iterator with the first n elements.
|
||||
// The function creates a new iterator containing the first n elements from the original iterator.
|
||||
func (seq SeqSet[V]) Take(n uint) SeqSet[V] { return SeqSet[V](iter.Take(iter.Seq[V](seq), int(n))) }
|
||||
|
||||
// Nth returns the nth element (0-indexed) in the sequence.
|
||||
func (seq SeqSet[V]) Nth(n Int) Option[V] {
|
||||
return OptionOf(iter.Nth(iter.Seq[V](seq), int(n)))
|
||||
}
|
||||
|
||||
func difference[V comparable](seq SeqSet[V], other Set[V]) SeqSet[V] {
|
||||
return func(yield func(V) bool) {
|
||||
seq(func(v V) bool {
|
||||
if !other.Contains(v) {
|
||||
return yield(v)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Next extracts the next element from the iterator and advances it.
|
||||
//
|
||||
// This method consumes the next element from the iterator and returns it wrapped in an Option.
|
||||
// The iterator itself is modified to point to the remaining elements.
|
||||
//
|
||||
// Returns:
|
||||
// - Option[V]: Some(value) if an element exists, None if the iterator is exhausted.
|
||||
func (seq *SeqSet[V]) Next() Option[V] {
|
||||
var values []V
|
||||
|
||||
(*seq)(func(v V) bool {
|
||||
values = append(values, v)
|
||||
return true
|
||||
})
|
||||
|
||||
if len(values) == 0 {
|
||||
return None[V]()
|
||||
}
|
||||
|
||||
first := Some(values[0])
|
||||
|
||||
*seq = func(yield func(V) bool) {
|
||||
for _, value := range values[1:] {
|
||||
if !yield(value) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return first
|
||||
}
|
||||
|
||||
func intersection[V comparable](seq SeqSet[V], other Set[V]) SeqSet[V] {
|
||||
return func(yield func(V) bool) {
|
||||
seq(func(v V) bool {
|
||||
if other.Contains(v) {
|
||||
return yield(v)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
+357
-409
File diff suppressed because it is too large.
Load diff
-1062
File diff suppressed because it is too large.
Load diff
-713
@@ -1,713 +0,0 @@
|
||||
package g
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/enetx/g/cmp"
|
||||
)
|
||||
|
||||
// All returns true only if fn returns true for every element.
|
||||
// It stops early on the first false.
|
||||
func (p SeqSlicePar[V]) All(fn func(V) bool) bool {
|
||||
var ok atomic.Bool
|
||||
ok.Store(true)
|
||||
|
||||
p.Range(func(v V) bool {
|
||||
if !fn(v) {
|
||||
ok.Store(false)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return ok.Load()
|
||||
}
|
||||
|
||||
// Any returns true if fn returns true for any element.
|
||||
// It stops early on the first true.
|
||||
func (p SeqSlicePar[V]) Any(fn func(V) bool) bool {
|
||||
var ok atomic.Bool
|
||||
|
||||
p.Range(func(v V) bool {
|
||||
if fn(v) {
|
||||
ok.Store(true)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return ok.Load()
|
||||
}
|
||||
|
||||
// Chain concatenates this SeqSlicePar with others, preserving full parallelism.
|
||||
// Each sequence runs with its own worker pool in parallel.
|
||||
func (p SeqSlicePar[V]) Chain(others ...SeqSlicePar[V]) SeqSlicePar[V] {
|
||||
return SeqSlicePar[V]{
|
||||
seq: func(yield func(V) bool) {
|
||||
done := make(chan struct{})
|
||||
result := make(chan V, 100)
|
||||
|
||||
var (
|
||||
wg sync.WaitGroup
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
runSequence := func(seq SeqSlicePar[V]) {
|
||||
defer wg.Done()
|
||||
seq.Range(func(v V) bool {
|
||||
select {
|
||||
case <-done:
|
||||
return false
|
||||
case result <- v:
|
||||
return true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer close(result)
|
||||
|
||||
wg.Add(1)
|
||||
go runSequence(p)
|
||||
|
||||
for _, o := range others {
|
||||
wg.Add(1)
|
||||
go runSequence(o)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
case v, ok := <-result:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if !yield(v) {
|
||||
once.Do(func() { close(done) })
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
workers: p.workers,
|
||||
process: func(v V) (V, bool) { return v, true },
|
||||
}
|
||||
}
|
||||
|
||||
// Collect gathers all processed elements into a Slice.
|
||||
func (p SeqSlicePar[V]) Collect() Slice[V] {
|
||||
ch := make(chan V)
|
||||
|
||||
go func() {
|
||||
defer close(ch)
|
||||
p.Range(func(v V) bool {
|
||||
ch <- v
|
||||
return true
|
||||
})
|
||||
}()
|
||||
|
||||
var result []V
|
||||
for v := range ch {
|
||||
result = append(result, v)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Count returns the total number of elements processed.
|
||||
func (p SeqSlicePar[V]) Count() Int {
|
||||
var count atomic.Int64
|
||||
p.Range(func(V) bool {
|
||||
count.Add(1)
|
||||
return true
|
||||
})
|
||||
|
||||
return Int(count.Load())
|
||||
}
|
||||
|
||||
// Exclude removes elements for which fn returns true, in parallel.
|
||||
func (p SeqSlicePar[V]) Exclude(fn func(V) bool) SeqSlicePar[V] {
|
||||
return p.Filter(func(v V) bool { return !fn(v) })
|
||||
}
|
||||
|
||||
// Filter retains only elements where fn returns true.
|
||||
func (p SeqSlicePar[V]) Filter(fn func(V) bool) SeqSlicePar[V] {
|
||||
prev := p.process
|
||||
|
||||
return SeqSlicePar[V]{
|
||||
seq: p.seq,
|
||||
workers: p.workers,
|
||||
process: func(v V) (V, bool) {
|
||||
if mid, ok := prev(v); ok && fn(mid) {
|
||||
return mid, true
|
||||
}
|
||||
var zero V
|
||||
return zero, false
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Find returns the first element satisfying fn, or None if no such element exists.
|
||||
func (p SeqSlicePar[V]) Find(fn func(V) bool) Option[V] {
|
||||
ch := make(chan V)
|
||||
|
||||
go func() {
|
||||
defer close(ch)
|
||||
p.Range(func(v V) bool {
|
||||
if fn(v) {
|
||||
ch <- v
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
}()
|
||||
|
||||
if v, ok := <-ch; ok {
|
||||
return Some(v)
|
||||
}
|
||||
|
||||
return None[V]()
|
||||
}
|
||||
|
||||
// Fold reduces all elements into a single value, using fn to accumulate results.
|
||||
func (p SeqSlicePar[V]) Fold(init V, fn func(acc, v V) V) V {
|
||||
ch := make(chan V)
|
||||
|
||||
go func() {
|
||||
defer close(ch)
|
||||
p.Range(func(v V) bool {
|
||||
ch <- v
|
||||
return true
|
||||
})
|
||||
}()
|
||||
|
||||
acc := init
|
||||
for v := range ch {
|
||||
acc = fn(acc, v)
|
||||
}
|
||||
|
||||
return acc
|
||||
}
|
||||
|
||||
// Reduce aggregates elements of the parallel sequence using the provided function.
|
||||
// The first received element is used as the initial accumulator.
|
||||
// If the sequence is empty, returns None[V].
|
||||
func (p SeqSlicePar[V]) Reduce(fn func(a, b V) V) Option[V] {
|
||||
ch := make(chan V)
|
||||
|
||||
go func() {
|
||||
defer close(ch)
|
||||
p.Range(func(v V) bool {
|
||||
ch <- v
|
||||
return true
|
||||
})
|
||||
}()
|
||||
|
||||
var (
|
||||
acc V
|
||||
first = true
|
||||
)
|
||||
|
||||
for v := range ch {
|
||||
if first {
|
||||
acc = v
|
||||
first = false
|
||||
continue
|
||||
}
|
||||
|
||||
acc = fn(acc, v)
|
||||
}
|
||||
|
||||
if first {
|
||||
return None[V]()
|
||||
}
|
||||
|
||||
return Some(acc)
|
||||
}
|
||||
|
||||
// ForEach applies fn to each element without early exit.
|
||||
func (p SeqSlicePar[V]) ForEach(fn func(V)) {
|
||||
p.Range(func(v V) bool {
|
||||
fn(v)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// Inspect invokes fn on each element without altering the resulting sequence.
|
||||
func (p SeqSlicePar[V]) Inspect(fn func(V)) SeqSlicePar[V] {
|
||||
prev := p.process
|
||||
|
||||
return SeqSlicePar[V]{
|
||||
seq: p.seq,
|
||||
workers: p.workers,
|
||||
process: func(x V) (V, bool) {
|
||||
if mid, ok := prev(x); ok {
|
||||
fn(mid)
|
||||
return mid, true
|
||||
}
|
||||
var zero V
|
||||
return zero, false
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Map applies fn to each element.
|
||||
func (p SeqSlicePar[V]) Map(fn func(V) V) SeqSlicePar[V] {
|
||||
prev := p.process
|
||||
|
||||
return SeqSlicePar[V]{
|
||||
seq: p.seq,
|
||||
workers: p.workers,
|
||||
process: func(v V) (V, bool) {
|
||||
if mid, ok := prev(v); ok {
|
||||
return fn(mid), true
|
||||
}
|
||||
var zero V
|
||||
return zero, false
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (p SeqSlicePar[V]) Partition(fn func(V) bool) (Slice[V], Slice[V]) {
|
||||
type item struct {
|
||||
value V
|
||||
isLeft bool
|
||||
}
|
||||
|
||||
ch := make(chan item)
|
||||
|
||||
go func() {
|
||||
defer close(ch)
|
||||
p.Range(func(v V) bool {
|
||||
ch <- item{
|
||||
value: v,
|
||||
isLeft: fn(v),
|
||||
}
|
||||
return true
|
||||
})
|
||||
}()
|
||||
|
||||
var left, right Slice[V]
|
||||
for it := range ch {
|
||||
if it.isLeft {
|
||||
left.Push(it.value)
|
||||
} else {
|
||||
right.Push(it.value)
|
||||
}
|
||||
}
|
||||
|
||||
return left, right
|
||||
}
|
||||
|
||||
// Range applies fn to each processed element in parallel, stopping on false.
|
||||
func (p SeqSlicePar[V]) Range(fn func(V) bool) {
|
||||
in := make(chan V)
|
||||
done := make(chan struct{})
|
||||
|
||||
var (
|
||||
wg sync.WaitGroup
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
go func() {
|
||||
defer close(in)
|
||||
p.seq(func(v V) bool {
|
||||
select {
|
||||
case <-done:
|
||||
return false
|
||||
case in <- v:
|
||||
return true
|
||||
}
|
||||
})
|
||||
}()
|
||||
|
||||
wg.Add(int(p.workers))
|
||||
for range p.workers {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for v := range in {
|
||||
if mid, ok := p.process(v); ok {
|
||||
if !fn(mid) {
|
||||
once.Do(func() { close(done) })
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func (p SeqSlicePar[V]) Skip(n Int) SeqSlicePar[V] {
|
||||
prev := p.process
|
||||
|
||||
return SeqSlicePar[V]{
|
||||
seq: func(yield func(V) bool) {
|
||||
var cnt int64
|
||||
p.seq(func(v V) bool {
|
||||
if atomic.AddInt64(&cnt, 1) > int64(n) {
|
||||
return yield(v)
|
||||
}
|
||||
return true
|
||||
})
|
||||
},
|
||||
workers: p.workers,
|
||||
process: prev,
|
||||
}
|
||||
}
|
||||
|
||||
func (p SeqSlicePar[V]) Take(n Int) SeqSlicePar[V] {
|
||||
prev := p.process
|
||||
|
||||
return SeqSlicePar[V]{
|
||||
seq: func(yield func(V) bool) {
|
||||
var cnt int64
|
||||
p.seq(func(v V) bool {
|
||||
if atomic.AddInt64(&cnt, 1) <= int64(n) {
|
||||
return yield(v)
|
||||
}
|
||||
return false
|
||||
})
|
||||
},
|
||||
workers: p.workers,
|
||||
process: prev,
|
||||
}
|
||||
}
|
||||
|
||||
// Unique removes duplicate elements, preserving the first occurrence.
|
||||
func (p SeqSlicePar[V]) Unique() SeqSlicePar[V] {
|
||||
prev := p.process
|
||||
seen := NewMapSafe[any, struct{}]()
|
||||
|
||||
return SeqSlicePar[V]{
|
||||
seq: p.seq,
|
||||
workers: p.workers,
|
||||
process: func(v V) (V, bool) {
|
||||
if mid, ok := prev(v); ok {
|
||||
if loaded := seen.Entry(mid).OrSet(struct{}{}); loaded.IsSome() {
|
||||
var zero V
|
||||
return zero, false
|
||||
}
|
||||
|
||||
return mid, true
|
||||
}
|
||||
|
||||
var zero V
|
||||
return zero, false
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Flatten unpacks nested slices or arrays in the source, returning a flat parallel sequence.
|
||||
func (p SeqSlicePar[V]) Flatten() SeqSlicePar[V] {
|
||||
seq := func(yield func(V) bool) {
|
||||
var recurse func(any) bool
|
||||
|
||||
recurse = func(item any) bool {
|
||||
if item == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
rv := reflect.ValueOf(item)
|
||||
|
||||
if !rv.IsValid() {
|
||||
return true
|
||||
}
|
||||
|
||||
switch rv.Kind() {
|
||||
case reflect.Slice, reflect.Array:
|
||||
if rv.IsNil() {
|
||||
return true
|
||||
}
|
||||
|
||||
for i := range rv.Len() {
|
||||
elem := rv.Index(i)
|
||||
|
||||
if !elem.CanInterface() {
|
||||
continue
|
||||
}
|
||||
|
||||
if !recurse(elem.Interface()) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
default:
|
||||
if v, ok := item.(V); ok {
|
||||
if !yield(v) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
resultsChan := make(chan V, 100)
|
||||
doneChan := make(chan struct{})
|
||||
var once sync.Once
|
||||
|
||||
go func() {
|
||||
defer close(resultsChan)
|
||||
|
||||
p.Range(func(v V) bool {
|
||||
select {
|
||||
case <-doneChan:
|
||||
return false
|
||||
default:
|
||||
}
|
||||
|
||||
flattenedItems := flattenToSlice(v)
|
||||
for _, item := range flattenedItems {
|
||||
if flatItem, ok := item.(V); ok {
|
||||
select {
|
||||
case resultsChan <- flatItem:
|
||||
case <-doneChan:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case v, ok := <-resultsChan:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if !yield(v) {
|
||||
once.Do(func() { close(doneChan) })
|
||||
return
|
||||
}
|
||||
case <-doneChan:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return SeqSlicePar[V]{
|
||||
seq: seq,
|
||||
workers: p.workers,
|
||||
process: func(v V) (V, bool) { return v, true },
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to flatten an item into a slice
|
||||
func flattenToSlice(item any) []any {
|
||||
if item == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
rv := reflect.ValueOf(item)
|
||||
if !rv.IsValid() {
|
||||
return nil
|
||||
}
|
||||
|
||||
var result []any
|
||||
|
||||
var recurse func(any)
|
||||
recurse = func(item any) {
|
||||
if item == nil {
|
||||
return
|
||||
}
|
||||
|
||||
rv := reflect.ValueOf(item)
|
||||
if !rv.IsValid() {
|
||||
return
|
||||
}
|
||||
|
||||
switch rv.Kind() {
|
||||
case reflect.Slice, reflect.Array:
|
||||
if rv.IsNil() {
|
||||
return
|
||||
}
|
||||
|
||||
for i := range rv.Len() {
|
||||
elem := rv.Index(i)
|
||||
if elem.CanInterface() {
|
||||
recurse(elem.Interface())
|
||||
}
|
||||
}
|
||||
default:
|
||||
result = append(result, item)
|
||||
}
|
||||
}
|
||||
|
||||
recurse(item)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// FlatMap applies fn to each element in parallel, flattening the resulting sequences.
|
||||
func (p SeqSlicePar[V]) FlatMap(fn func(V) SeqSlice[V]) SeqSlicePar[V] {
|
||||
return SeqSlicePar[V]{
|
||||
seq: func(yield func(V) bool) {
|
||||
done := make(chan struct{})
|
||||
result := make(chan V, 100)
|
||||
|
||||
var (
|
||||
wg sync.WaitGroup
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
go func() {
|
||||
defer close(result)
|
||||
|
||||
p.Range(func(v V) bool {
|
||||
select {
|
||||
case <-done:
|
||||
return false
|
||||
default:
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
go func(val V) {
|
||||
defer wg.Done()
|
||||
fn(val)(func(item V) bool {
|
||||
select {
|
||||
case <-done:
|
||||
return false
|
||||
case result <- item:
|
||||
return true
|
||||
}
|
||||
})
|
||||
}(v)
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
wg.Wait()
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
case v, ok := <-result:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if !yield(v) {
|
||||
once.Do(func() { close(done) })
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
workers: p.workers,
|
||||
process: func(v V) (V, bool) { return v, true },
|
||||
}
|
||||
}
|
||||
|
||||
// FilterMap applies fn to each element in parallel, keeping only Some values.
|
||||
func (p SeqSlicePar[V]) FilterMap(fn func(V) Option[V]) SeqSlicePar[V] {
|
||||
prev := p.process
|
||||
|
||||
return SeqSlicePar[V]{
|
||||
seq: p.seq,
|
||||
workers: p.workers,
|
||||
process: func(v V) (V, bool) {
|
||||
if mid, ok := prev(v); ok {
|
||||
if opt := fn(mid); opt.IsSome() {
|
||||
return opt.Some(), true
|
||||
}
|
||||
}
|
||||
var zero V
|
||||
return zero, false
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// StepBy yields every nth element.
|
||||
func (p SeqSlicePar[V]) StepBy(n uint) SeqSlicePar[V] {
|
||||
if n == 0 {
|
||||
n = 1
|
||||
}
|
||||
|
||||
prev := p.process
|
||||
counter := &atomic.Uint64{}
|
||||
|
||||
return SeqSlicePar[V]{
|
||||
seq: p.seq,
|
||||
workers: p.workers,
|
||||
process: func(v V) (V, bool) {
|
||||
if mid, ok := prev(v); ok {
|
||||
count := counter.Add(1)
|
||||
if (count-1)%uint64(n) == 0 {
|
||||
return mid, true
|
||||
}
|
||||
}
|
||||
var zero V
|
||||
return zero, false
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// MaxBy returns the maximum element according to the comparison function.
|
||||
func (p SeqSlicePar[V]) MaxBy(fn func(V, V) cmp.Ordering) Option[V] {
|
||||
ch := make(chan V)
|
||||
|
||||
go func() {
|
||||
defer close(ch)
|
||||
p.Range(func(v V) bool {
|
||||
ch <- v
|
||||
return true
|
||||
})
|
||||
}()
|
||||
|
||||
var max V
|
||||
hasMax := false
|
||||
|
||||
for v := range ch {
|
||||
if !hasMax {
|
||||
max = v
|
||||
hasMax = true
|
||||
} else if fn(v, max).IsGt() {
|
||||
max = v
|
||||
}
|
||||
}
|
||||
|
||||
if hasMax {
|
||||
return Some(max)
|
||||
}
|
||||
|
||||
return None[V]()
|
||||
}
|
||||
|
||||
// MinBy returns the minimum element according to the comparison function.
|
||||
func (p SeqSlicePar[V]) MinBy(fn func(V, V) cmp.Ordering) Option[V] {
|
||||
ch := make(chan V)
|
||||
|
||||
go func() {
|
||||
defer close(ch)
|
||||
p.Range(func(v V) bool {
|
||||
ch <- v
|
||||
return true
|
||||
})
|
||||
}()
|
||||
|
||||
var min V
|
||||
hasMin := false
|
||||
|
||||
for v := range ch {
|
||||
if !hasMin {
|
||||
min = v
|
||||
hasMin = true
|
||||
} else if fn(v, min).IsLt() {
|
||||
min = v
|
||||
}
|
||||
}
|
||||
|
||||
if hasMin {
|
||||
return Some(min)
|
||||
}
|
||||
|
||||
return None[V]()
|
||||
}
|
||||
+380
-164
@@ -1,6 +1,7 @@
|
||||
package g
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"slices"
|
||||
@@ -10,12 +11,19 @@ import (
|
||||
"unicode/utf8"
|
||||
"unsafe"
|
||||
|
||||
"github.com/enetx/g/cmp"
|
||||
"github.com/enetx/g/f"
|
||||
"golang.org/x/text/unicode/norm"
|
||||
|
||||
"github.com/enetx/g/cmp"
|
||||
)
|
||||
|
||||
// NewString creates a new String from the provided string.
|
||||
// String is a wrapper around the string type.
|
||||
type String string
|
||||
|
||||
// Named is a map-like type that stores key-value pairs for resolving named
|
||||
// placeholders in Format.
|
||||
type Named Map[String, any]
|
||||
|
||||
// NewString creates a new String from the provided string, rune, byte, rune slice, or byte slice.
|
||||
func NewString[T ~string | rune | byte | ~[]rune | ~[]byte](str T) String { return String(str) }
|
||||
|
||||
// Clone returns a copy of the String.
|
||||
@@ -24,7 +32,7 @@ func NewString[T ~string | rune | byte | ~[]rune | ~[]byte](str T) String { retu
|
||||
func (s String) Clone() String { return String(strings.Clone(s.Std())) }
|
||||
|
||||
// Transform applies a transformation function to the String and returns the result.
|
||||
func (s String) Transform(fn func(String) String) String { return fn(s) }
|
||||
func (s String) Transform[U any](fn func(String) U) U { return fn(s) }
|
||||
|
||||
// Builder returns a new Builder initialized with the content of the String.
|
||||
func (s String) Builder() *Builder {
|
||||
@@ -39,44 +47,10 @@ func (s String) Min(b ...String) String { return cmp.Min(append(b, s)...) }
|
||||
// Max returns the maximum of Strings.
|
||||
func (s String) Max(b ...String) String { return cmp.Max(append(b, s)...) }
|
||||
|
||||
// Random generates a random String of the specified length, selecting characters from predefined sets.
|
||||
// If additional character sets are provided, only those will be used; the default set (ASCII_LETTERS and DIGITS)
|
||||
// is excluded unless explicitly provided.
|
||||
//
|
||||
// Parameters:
|
||||
// - count (Int): Length of the random String to generate.
|
||||
// - letters (...String): Additional character sets to consider for generating the random String (optional).
|
||||
//
|
||||
// Returns:
|
||||
// - String: Randomly generated String with the specified length.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// randomString := g.String.Random(10)
|
||||
// randomString contains a random String with 10 characters.
|
||||
func (String) Random(length Int, letters ...String) String {
|
||||
var chars Slice[rune]
|
||||
|
||||
if len(letters) != 0 {
|
||||
chars = letters[0].Runes()
|
||||
} else {
|
||||
chars = (ASCII_LETTERS + DIGITS).Runes()
|
||||
}
|
||||
|
||||
var b Builder
|
||||
b.Grow(length)
|
||||
|
||||
for range length {
|
||||
b.WriteRune(chars.Random())
|
||||
}
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// IsASCII checks if all characters in the String are ASCII bytes.
|
||||
func (s String) IsASCII() bool {
|
||||
for _, r := range s {
|
||||
if r > unicode.MaxASCII {
|
||||
for i := range s {
|
||||
if s[i] >= 0x80 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -86,7 +60,7 @@ func (s String) IsASCII() bool {
|
||||
|
||||
// IsDigit checks if all characters in the String are digits.
|
||||
func (s String) IsDigit() bool {
|
||||
if s.Empty() {
|
||||
if s.IsEmpty() {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -99,56 +73,98 @@ func (s String) IsDigit() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// ToInt tries to parse the String as an int and returns an Int.
|
||||
func (s String) ToInt() Result[Int] {
|
||||
// TryInt tries to parse the String as an int and returns an Int.
|
||||
func (s String) TryInt() Result[Int] {
|
||||
hint, err := strconv.ParseInt(s.Std(), 0, 64)
|
||||
if err != nil {
|
||||
return Err[Int](err)
|
||||
return Err[Int](Errorf("{:w}: \"{}\"", ErrParseInt, s))
|
||||
}
|
||||
|
||||
return Ok(Int(hint))
|
||||
}
|
||||
|
||||
// ToBigInt attempts to convert the String receiver into an Option containing a *big.Int.
|
||||
// TryBigInt attempts to convert the String receiver into a Result containing a *big.Int.
|
||||
// This function assumes the string represents a numerical value, which can be in decimal,
|
||||
// hexadecimal (prefixed with "0x"), or octal (prefixed with "0") format. The function
|
||||
// leverages the SetString method of the math/big package, automatically detecting the
|
||||
// numeric base when set to 0.
|
||||
//
|
||||
// If the string is correctly formatted and represents a valid number, ToBigInt returns
|
||||
// a Some containing the *big.Int parsed from the string. If the string is empty, contains
|
||||
// invalid characters, or does not conform to a recognizable numeric format, ToBigInt
|
||||
// returns a None, indicating that the conversion was unsuccessful.
|
||||
// If the string is correctly formatted and represents a valid number, TryBigInt returns
|
||||
// an Ok containing the *big.Int parsed from the string. If the string is empty, contains
|
||||
// invalid characters, or does not conform to a recognizable numeric format, TryBigInt
|
||||
// returns an Err describing the invalid input.
|
||||
//
|
||||
// Returns:
|
||||
// - An Option[*big.Int] encapsulating the conversion result. It returns Some[*big.Int]
|
||||
// with the parsed value if successful, otherwise None[*big.Int] if the parsing fails.
|
||||
func (s String) ToBigInt() Option[*big.Int] {
|
||||
// - A Result[*big.Int] encapsulating the conversion result. It returns Ok[*big.Int]
|
||||
// with the parsed value if successful, otherwise Err[*big.Int] if the parsing fails.
|
||||
func (s String) TryBigInt() Result[*big.Int] {
|
||||
if bigInt, ok := new(big.Int).SetString(s.Std(), 0); ok {
|
||||
return Some(bigInt)
|
||||
return Ok(bigInt)
|
||||
}
|
||||
|
||||
return None[*big.Int]()
|
||||
return Err[*big.Int](Errorf("{:w}: \"{}\"", ErrParseBigInt, s))
|
||||
}
|
||||
|
||||
// ToFloat tries to parse the String as a float64 and returns an Float.
|
||||
func (s String) ToFloat() Result[Float] {
|
||||
// TryFloat tries to parse the String as a float64 and returns an Float.
|
||||
func (s String) TryFloat() Result[Float] {
|
||||
float, err := strconv.ParseFloat(s.Std(), 64)
|
||||
if err != nil {
|
||||
return Err[Float](err)
|
||||
return Err[Float](Errorf("{:w}: \"{}\"", ErrParseFloat, s))
|
||||
}
|
||||
|
||||
return Ok(Float(float))
|
||||
}
|
||||
|
||||
// TryBool tries to parse the String as a bool and returns the result.
|
||||
// It accepts the values understood by strconv.ParseBool: 1, t, T, TRUE, true,
|
||||
// True, 0, f, F, FALSE, false, False.
|
||||
func (s String) TryBool() Result[bool] {
|
||||
b, err := strconv.ParseBool(s.Std())
|
||||
if err != nil {
|
||||
return Err[bool](Errorf("{:w}: \"{}\"", ErrParseBool, s))
|
||||
}
|
||||
|
||||
return Ok(b)
|
||||
}
|
||||
|
||||
// TryUint tries to parse the String as an unsigned integer and returns a uint.
|
||||
// The base is inferred from the prefix (0x, 0o/0, 0b), matching TryInt.
|
||||
func (s String) TryUint() Result[uint] {
|
||||
u, err := strconv.ParseUint(s.Std(), 0, 64)
|
||||
if err != nil {
|
||||
return Err[uint](Errorf("{:w}: \"{}\"", ErrParseUint, s))
|
||||
}
|
||||
|
||||
return Ok(uint(u))
|
||||
}
|
||||
|
||||
// TryComplex tries to parse the String as a complex number and returns a complex128.
|
||||
func (s String) TryComplex() Result[complex128] {
|
||||
c, err := strconv.ParseComplex(s.Std(), 128)
|
||||
if err != nil {
|
||||
return Err[complex128](Errorf("{:w}: \"{}\"", ErrParseComplex, s))
|
||||
}
|
||||
|
||||
return Ok(c)
|
||||
}
|
||||
|
||||
// Title converts the String to title case.
|
||||
func (s String) Title() String { return String(title.String(s.Std())) }
|
||||
|
||||
// Lower returns the String in lowercase.
|
||||
func (s String) Lower() String { return s.Bytes().Lower().String() }
|
||||
func (s String) Lower() String { return s.BytesUnsafe().Lower().StringUnsafe() }
|
||||
|
||||
// Upper returns the String in uppercase.
|
||||
func (s String) Upper() String { return s.Bytes().Upper().String() }
|
||||
func (s String) Upper() String { return s.BytesUnsafe().Upper().StringUnsafe() }
|
||||
|
||||
// IsLower checks if the String consists only of lowercase letters.
|
||||
func (s String) IsLower() bool { return s.BytesUnsafe().IsLower() }
|
||||
|
||||
// IsUpper checks if the String consists only of uppercase letters.
|
||||
func (s String) IsUpper() bool { return s.BytesUnsafe().IsUpper() }
|
||||
|
||||
// IsTitle checks if the String is in title case.
|
||||
func (s String) IsTitle() bool { return s.BytesUnsafe().IsTitle() }
|
||||
|
||||
// Trim removes leading and trailing white space from the String.
|
||||
func (s String) Trim() String { return String(strings.TrimSpace(s.Std())) }
|
||||
@@ -211,7 +227,7 @@ func (s String) ReplaceAll(oldS, newS String) String {
|
||||
// "world", "universe",
|
||||
// "test", "example",
|
||||
// )
|
||||
// // replaced contains "Greetings, universe! This is an example."
|
||||
// // replaced contains "Greetings, universe! This is a example."
|
||||
func (s String) ReplaceMulti(oldnew ...String) String {
|
||||
pairs := make([]string, len(oldnew))
|
||||
for i, str := range oldnew {
|
||||
@@ -286,7 +302,12 @@ func (s String) ReplaceNth(oldS, newS String, n Int) String {
|
||||
count++
|
||||
|
||||
if count == n || (n == -1 && s[pos+oldS.Len():].Index(oldS) == -1) {
|
||||
return s[:pos] + newS + s[pos+oldS.Len():]
|
||||
var b Builder
|
||||
b.WriteString(s[:pos])
|
||||
b.WriteString(newS)
|
||||
b.WriteString(s[pos+oldS.Len():])
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
i = pos + oldS.Len()
|
||||
@@ -296,7 +317,7 @@ func (s String) ReplaceNth(oldS, newS String, n Int) String {
|
||||
}
|
||||
|
||||
// Contains checks if the String contains the specified substring.
|
||||
func (s String) Contains(substr String) bool { return f.Contains(substr)(s) }
|
||||
func (s String) Contains(substr String) bool { return strings.Contains(s.Std(), substr.Std()) }
|
||||
|
||||
// ContainsAny checks if the String contains any of the specified substrings.
|
||||
func (s String) ContainsAny(substrs ...String) bool {
|
||||
@@ -315,11 +336,12 @@ func (s String) ContainsAll(substrs ...String) bool {
|
||||
}
|
||||
|
||||
// ContainsAnyChars checks if the String contains any characters from the specified String.
|
||||
func (s String) ContainsAnyChars(chars String) bool { return f.ContainsAnyChars(chars)(s) }
|
||||
func (s String) ContainsAnyChars(chars String) bool {
|
||||
return strings.ContainsAny(s.Std(), chars.Std())
|
||||
}
|
||||
|
||||
// StartsWith checks if the String starts with the specified prefix.
|
||||
// It uses a higher-order function to perform the check.
|
||||
func (s String) StartsWith(prefix String) bool { return f.StartsWith(prefix)(s) }
|
||||
func (s String) StartsWith(prefix String) bool { return strings.HasPrefix(s.Std(), prefix.Std()) }
|
||||
|
||||
// StartsWithAny checks if the String starts with any of the provided prefixes.
|
||||
// The method accepts a variable number of arguments, allowing for checking against multiple
|
||||
@@ -338,8 +360,7 @@ func (s String) StartsWithAny(prefixes ...String) bool {
|
||||
}
|
||||
|
||||
// EndsWith checks if the String ends with the specified suffix.
|
||||
// It uses a higher-order function to perform the check.
|
||||
func (s String) EndsWith(suffix String) bool { return f.EndsWith(suffix)(s) }
|
||||
func (s String) EndsWith(suffix String) bool { return strings.HasSuffix(s.Std(), suffix.Std()) }
|
||||
|
||||
// EndsWithAny checks if the String ends with any of the provided suffixes.
|
||||
// The method accepts a variable number of arguments, allowing for checking against multiple
|
||||
@@ -357,53 +378,71 @@ func (s String) EndsWithAny(suffixes ...String) bool {
|
||||
return slices.ContainsFunc(suffixes, s.EndsWith)
|
||||
}
|
||||
|
||||
// Lines splits the String by lines and returns the iterator.
|
||||
func (s String) Lines() SeqSlice[String] {
|
||||
return transformSeq(strings.Lines(s.Std()), NewString).Map(String.TrimEnd)
|
||||
}
|
||||
// Lines splits the String by lines, with trailing whitespace trimmed per
|
||||
// line. The substrings share the receiver's backing memory.
|
||||
//
|
||||
// It returns a plain []String — deliberately not a Seq or Slice: a named
|
||||
// generic type in the signature would make every package that names g.String
|
||||
// compile the whole container machinery. Convert with g.SliceOf(parts...) for
|
||||
// chaining; for lazy streaming over files use fs.File.Lines.
|
||||
func (s String) Lines() []String {
|
||||
var result []String
|
||||
|
||||
// Fields splits the String into a slice of substrings, removing any whitespace, and returns the iterator.
|
||||
func (s String) Fields() SeqSlice[String] {
|
||||
return transformSeq(strings.FieldsSeq(s.Std()), NewString)
|
||||
}
|
||||
|
||||
// FieldsBy splits the String into a slice of substrings using a custom function to determine the field boundaries,
|
||||
// and returns the iterator.
|
||||
func (s String) FieldsBy(fn func(r rune) bool) SeqSlice[String] {
|
||||
return transformSeq(strings.FieldsFuncSeq(s.Std(), fn), NewString)
|
||||
}
|
||||
|
||||
// Split splits the String by the specified separator and returns the iterator.
|
||||
func (s String) Split(sep ...String) SeqSlice[String] {
|
||||
var separator String
|
||||
if len(sep) != 0 {
|
||||
separator = sep[0]
|
||||
for line := range strings.Lines(s.Std()) {
|
||||
result = append(result, String(line).TrimEnd())
|
||||
}
|
||||
|
||||
return transformSeq(strings.SplitSeq(s.Std(), separator.Std()), NewString)
|
||||
return result
|
||||
}
|
||||
|
||||
// SplitAfter splits the String after each instance of the specified separator and returns the iterator.
|
||||
func (s String) SplitAfter(sep String) SeqSlice[String] {
|
||||
return transformSeq(strings.SplitAfterSeq(s.Std(), sep.Std()), NewString)
|
||||
// Fields splits the String around whitespace. See [String.Lines] for why the
|
||||
// return type is a plain slice.
|
||||
func (s String) Fields() []String {
|
||||
return castStrings(strings.Fields(s.Std()))
|
||||
}
|
||||
|
||||
// SplitN splits the String into substrings using the provided separator and returns an Slice[String] of the results.
|
||||
// The n parameter controls the number of substrings to return:
|
||||
// FieldsBy splits the String using a custom function to determine the field
|
||||
// boundaries. See [String.Lines] for why the return type is a plain slice.
|
||||
func (s String) FieldsBy(fn func(r rune) bool) []String {
|
||||
return castStrings(strings.FieldsFunc(s.Std(), fn))
|
||||
}
|
||||
|
||||
// Split splits the String by the specified separator. If sep is empty, the
|
||||
// String is split after each UTF-8 rune. See [String.Lines] for why the return
|
||||
// type is a plain slice.
|
||||
func (s String) Split(sep String) []String {
|
||||
return castStrings(strings.Split(s.Std(), sep.Std()))
|
||||
}
|
||||
|
||||
// SplitAfter splits the String after each instance of the specified separator.
|
||||
// See [String.Lines] for why the return type is a plain slice.
|
||||
func (s String) SplitAfter(sep String) []String {
|
||||
return castStrings(strings.SplitAfter(s.Std(), sep.Std()))
|
||||
}
|
||||
|
||||
// SplitN splits the String into substrings using the provided separator and
|
||||
// returns a plain []String of the results (convert with Slice[String] for
|
||||
// chaining). The n parameter controls the number of substrings to return:
|
||||
// - If n is negative, there is no limit on the number of substrings returned.
|
||||
// - If n is zero, an empty Slice[String] is returned.
|
||||
// - If n is zero, an empty slice is returned.
|
||||
// - If n is positive, at most n substrings are returned.
|
||||
func (s String) SplitN(sep String, n Int) Slice[String] {
|
||||
return TransformSlice(strings.SplitN(s.Std(), sep.Std(), n.Std()), NewString)
|
||||
func (s String) SplitN(sep String, n Int) []String {
|
||||
parts := strings.SplitN(s.Std(), sep.Std(), n.Std())
|
||||
|
||||
result := make([]String, len(parts))
|
||||
for i, p := range parts {
|
||||
result[i] = String(p)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Chunks splits the String into chunks of the specified size.
|
||||
//
|
||||
// This function iterates through the String, creating new String chunks of the specified size.
|
||||
// If size is less than or equal to 0 or the String is empty,
|
||||
// it returns an empty Slice[String].
|
||||
// If size is greater than or equal to the length of the String,
|
||||
// it returns an Slice[String] containing the original String.
|
||||
// If size is less than or equal to 0 or the String is empty, it returns nil.
|
||||
// If size is greater than or equal to the length of the String, it returns the
|
||||
// original String as the only chunk.
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
@@ -411,7 +450,7 @@ func (s String) SplitN(sep String, n Int) Slice[String] {
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - Slice[String]: A slice of String chunks of the specified size.
|
||||
// - []String: the String chunks of the specified size.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
@@ -419,25 +458,45 @@ func (s String) SplitN(sep String, n Int) Slice[String] {
|
||||
// chunks := text.Chunks(4)
|
||||
//
|
||||
// chunks contains {"Hell", "o, W", "orld", "!"}.
|
||||
func (s String) Chunks(size Int) SeqSlice[String] {
|
||||
if size.Lte(0) || s.Empty() {
|
||||
return func(func(String) bool) {}
|
||||
func (s String) Chunks(size Int) []String {
|
||||
if size.Lte(0) || s.IsEmpty() {
|
||||
return nil
|
||||
}
|
||||
|
||||
runes := s.Runes()
|
||||
if size.Gte(Int(len(runes))) {
|
||||
return func(yield func(String) bool) { yield(s) }
|
||||
if s.IsASCII() {
|
||||
n := size.Std()
|
||||
l := len(s)
|
||||
|
||||
if n >= l {
|
||||
return []String{s}
|
||||
}
|
||||
|
||||
result := make([]String, 0, (l+n-1)/n)
|
||||
for i := 0; i < l; i += n {
|
||||
result = append(result, s[i:min(i+n, l)])
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
n := size.Std()
|
||||
return func(yield func(String) bool) {
|
||||
for i := 0; i < len(runes); i += n {
|
||||
end := min(i+n, len(runes))
|
||||
if !yield(String(runes[i:end])) {
|
||||
return
|
||||
}
|
||||
|
||||
var result []String
|
||||
|
||||
rest := s
|
||||
for !rest.IsEmpty() {
|
||||
i := 0
|
||||
str := rest.Std()
|
||||
for count := 0; count < n && i < len(rest); count++ {
|
||||
_, sz := utf8.DecodeRuneInString(str[i:])
|
||||
i += sz
|
||||
}
|
||||
|
||||
result = append(result, rest[:i])
|
||||
rest = rest[i:]
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Cut returns two String values. The first String contains the remainder of the
|
||||
@@ -450,7 +509,7 @@ func (s String) Chunks(size Int) SeqSlice[String] {
|
||||
// between the first occurrences of 'start' and 'end' with tags removed if specified.
|
||||
//
|
||||
// If either 'start' or 'end' is empty or not found in the String, it returns the
|
||||
// original String as the second String, and an empty String as the first.
|
||||
// original String as the first String, and an empty String as the second.
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
@@ -459,25 +518,27 @@ func (s String) Chunks(size Int) SeqSlice[String] {
|
||||
// - end (String): The String marking the end of the text to be cut.
|
||||
//
|
||||
// - rmtags (bool, optional): An optional boolean parameter indicating whether
|
||||
// to remove 'start' and 'end' tags from the cut text. Defaults to false.
|
||||
// to remove the matched region (including the 'start' and 'end' tags) from the
|
||||
// remainder. Defaults to false, in which case the remainder equals the original
|
||||
// String and only the cut content is extracted.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - String: The first String containing the remainder of the original String
|
||||
// after the cut, with tags removed if specified,
|
||||
// or an empty String if 'start' or 'end' is empty or not found.
|
||||
// - String: The first String containing the remainder of the original String.
|
||||
// When rmtags is true the matched region is removed from it; otherwise it is
|
||||
// the original String. Returns the original String if 'start' or 'end' is empty or not found.
|
||||
//
|
||||
// - String: The second String containing the text between the first occurrences of
|
||||
// 'start' and 'end', or the original String if 'start' or 'end' is empty or not found.
|
||||
// 'start' and 'end', or an empty String if 'start' or 'end' is empty or not found.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// s := g.String("Hello, [world]! How are you?")
|
||||
// remainder, cut := s.Cut("[", "]")
|
||||
// remainder, cut := s.Cut("[", "]", true)
|
||||
// // remainder: "Hello, ! How are you?"
|
||||
// // cut: "world"
|
||||
func (s String) Cut(start, end String, rmtags ...bool) (String, String) {
|
||||
if start.Empty() || end.Empty() {
|
||||
if start.IsEmpty() || end.IsEmpty() {
|
||||
return s, ""
|
||||
}
|
||||
|
||||
@@ -494,9 +555,8 @@ func (s String) Cut(start, end String, rmtags ...bool) (String, String) {
|
||||
|
||||
cut := s[startEnd : startEnd+endIndex]
|
||||
|
||||
if len(rmtags) != 0 && !rmtags[0] {
|
||||
startEnd += end.Len()
|
||||
return s[:startIndex] + s[startIndex:startEnd+endIndex] + s[startEnd+endIndex:], cut
|
||||
if len(rmtags) == 0 || !rmtags[0] {
|
||||
return s, cut
|
||||
}
|
||||
|
||||
return s[:startIndex] + s[startEnd+endIndex+end.Len():], cut
|
||||
@@ -529,38 +589,37 @@ func (s String) Similarity(str String) Float {
|
||||
return 100
|
||||
}
|
||||
|
||||
if s.Empty() || str.Empty() {
|
||||
if s.IsEmpty() || str.IsEmpty() {
|
||||
return 0
|
||||
}
|
||||
|
||||
s1 := s.Runes()
|
||||
s2 := str.Runes()
|
||||
|
||||
lenS1 := s.LenRunes()
|
||||
lenS2 := str.LenRunes()
|
||||
n1, n2 := len(s1), len(s2)
|
||||
|
||||
if lenS1 > lenS2 {
|
||||
s1, s2, lenS1, lenS2 = s2, s1, lenS2, lenS1
|
||||
if n1 > n2 {
|
||||
s1, s2, n1, n2 = s2, s1, n2, n1
|
||||
}
|
||||
|
||||
distance := NewSlice[Int](lenS1 + 1)
|
||||
distance := make([]int, n1+1)
|
||||
|
||||
for i, r2 := range s2 {
|
||||
prev := Int(i) + 1
|
||||
prev := i + 1
|
||||
|
||||
for j, r1 := range s1 {
|
||||
current := distance[j]
|
||||
if r2 != r1 {
|
||||
current = distance[j].Add(1).Min(prev + 1).Min(distance[j+1] + 1)
|
||||
current = min(distance[j]+1, min(prev+1, distance[j+1]+1))
|
||||
}
|
||||
|
||||
distance[j], prev = prev, current
|
||||
}
|
||||
|
||||
distance[lenS1] = prev
|
||||
distance[n1] = prev
|
||||
}
|
||||
|
||||
return Float(1).Sub(distance[lenS1].Float() / lenS1.Max(lenS2).Float()).Mul(100)
|
||||
return Float(1-float64(distance[n1])/float64(max(n1, n2))) * 100
|
||||
}
|
||||
|
||||
// Cmp compares two Strings and returns an cmp.Ordering indicating their relative order.
|
||||
@@ -579,8 +638,8 @@ func (s String) ContainsRune(r rune) bool { return strings.ContainsRune(s.Std(),
|
||||
// Count returns the number of non-overlapping instances of the substring in the String.
|
||||
func (s String) Count(substr String) Int { return Int(strings.Count(s.Std(), substr.Std())) }
|
||||
|
||||
// Empty checks if the String is empty.
|
||||
func (s String) Empty() bool { return len(s) == 0 }
|
||||
// IsEmpty checks if the String is empty.
|
||||
func (s String) IsEmpty() bool { return len(s) == 0 }
|
||||
|
||||
// Eq checks if two Strings are equal.
|
||||
func (s String) Eq(str String) bool { return s == str }
|
||||
@@ -614,6 +673,14 @@ func (s String) LastIndex(substr String) Int { return Int(strings.LastIndex(s.St
|
||||
// IndexRune returns the index of the first instance of the specified rune in the String.
|
||||
func (s String) IndexRune(r rune) Int { return Int(strings.IndexRune(s.Std(), r)) }
|
||||
|
||||
// IndexByte returns the index of the first instance of the specified byte in the String, or -1
|
||||
// if b is not present in s.
|
||||
func (s String) IndexByte(b byte) Int { return Int(strings.IndexByte(s.Std(), b)) }
|
||||
|
||||
// LastIndexByte returns the index of the last instance of the specified byte in the String, or -1
|
||||
// if b is not present in s.
|
||||
func (s String) LastIndexByte(b byte) Int { return Int(strings.LastIndexByte(s.Std(), b)) }
|
||||
|
||||
// Len returns the length of the String.
|
||||
func (s String) Len() Int { return Int(len(s)) }
|
||||
|
||||
@@ -629,15 +696,9 @@ func (s String) Lte(str String) bool { return s <= str }
|
||||
// Map applies the provided function to all runes in the String and returns the resulting String.
|
||||
func (s String) Map(fn func(rune) rune) String { return String(strings.Map(fn, s.Std())) }
|
||||
|
||||
// NormalizeNFC returns a new String with its Unicode characters normalized using the NFC form.
|
||||
func (s String) NormalizeNFC() String { return String(norm.NFC.String(s.Std())) }
|
||||
|
||||
// Ne checks if two Strings are not equal.
|
||||
func (s String) Ne(str String) bool { return !s.Eq(str) }
|
||||
|
||||
// NotEmpty checks if the String is not empty.
|
||||
func (s String) NotEmpty() bool { return s.Len() != 0 }
|
||||
|
||||
// Reader returns a *strings.Reader initialized with the content of String.
|
||||
func (s String) Reader() *strings.Reader { return strings.NewReader(s.Std()) }
|
||||
|
||||
@@ -645,13 +706,14 @@ func (s String) Reader() *strings.Reader { return strings.NewReader(s.Std()) }
|
||||
func (s String) Repeat(count Int) String { return String(strings.Repeat(s.Std(), count.Std())) }
|
||||
|
||||
// Reverse reverses the String.
|
||||
func (s String) Reverse() String { return s.Bytes().Reverse().String() }
|
||||
func (s String) Reverse() String { return s.BytesUnsafe().Reverse().StringUnsafe() }
|
||||
|
||||
// Runes returns the String as a slice of runes.
|
||||
func (s String) Runes() Slice[rune] { return []rune(s) }
|
||||
// Runes returns the String as a plain slice of runes.
|
||||
func (s String) Runes() []rune { return []rune(s) }
|
||||
|
||||
// Chars splits the String into individual characters and returns the iterator.
|
||||
func (s String) Chars() SeqSlice[String] { return s.Split() }
|
||||
// Chars splits the String into individual UTF-8 characters, equivalent to
|
||||
// s.Split(""). Prefer Runes when only code points are needed.
|
||||
func (s String) Chars() []String { return s.Split("") }
|
||||
|
||||
// SubString extracts a substring from the String starting at the 'start' index and ending before the 'end' index.
|
||||
// The function also supports an optional 'step' parameter to define the increment between indices in the substring.
|
||||
@@ -659,11 +721,71 @@ func (s String) Chars() SeqSlice[String] { return s.Split() }
|
||||
// - A negative 'start' index indicates the position from the end of the String, moving backward.
|
||||
// - A negative 'end' index indicates the position from the end of the String.
|
||||
// The function ensures that indices are adjusted to fall within the valid range of the String's length.
|
||||
// If indices are out of bounds or if 'start' exceeds 'end', the function returns the original String unmodified.
|
||||
// Out-of-bounds indices are clamped to the String's bounds instead of panicking;
|
||||
// if 'start' exceeds 'end' (for a positive step) the result is an empty String.
|
||||
func (s String) SubString(start, end Int, step ...Int) String {
|
||||
return String(s.Runes().SubSlice(start, end, step...))
|
||||
runes := s.Runes()
|
||||
n := Int(len(runes))
|
||||
|
||||
clamp := func(i Int) Int {
|
||||
if i < 0 {
|
||||
i += n
|
||||
}
|
||||
|
||||
if i < 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
if i > n {
|
||||
return n
|
||||
}
|
||||
|
||||
return i
|
||||
}
|
||||
|
||||
start, end = clamp(start), clamp(end)
|
||||
|
||||
st := Int(1)
|
||||
if len(step) > 0 {
|
||||
st = step[0]
|
||||
}
|
||||
|
||||
// For a negative step the iteration starts AT start and moves down,
|
||||
// so a start clamped to n must begin at the last element.
|
||||
if st < 0 && start == n {
|
||||
start--
|
||||
}
|
||||
|
||||
if st == 1 {
|
||||
if start >= end {
|
||||
return ""
|
||||
}
|
||||
|
||||
return String(runes[start:end])
|
||||
}
|
||||
|
||||
if (start >= end && st > 0) || (start <= end && st < 0) || st == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
var out []rune
|
||||
|
||||
if st > 0 {
|
||||
for i := start; i < end; i += st {
|
||||
out = append(out, runes[i])
|
||||
}
|
||||
} else {
|
||||
for i := start; i > end; i += st {
|
||||
out = append(out, runes[i])
|
||||
}
|
||||
}
|
||||
|
||||
return String(out)
|
||||
}
|
||||
|
||||
// NormalizeNFC returns a new String with its Unicode characters normalized using the NFC form.
|
||||
func (s String) NormalizeNFC() String { return String(norm.NFC.String(s.Std())) }
|
||||
|
||||
// Std returns the String as a string.
|
||||
func (s String) Std() string { return string(s) }
|
||||
|
||||
@@ -700,11 +822,30 @@ func (s String) Format(template String) String { return Format(template, s) }
|
||||
// result3 := s3.Truncate(3)
|
||||
// // result3: "😊😊😊..."
|
||||
func (s String) Truncate(max Int) String {
|
||||
if max.IsNegative() || s.LenRunes().Lte(max) {
|
||||
if max.IsNegative() {
|
||||
return s
|
||||
}
|
||||
|
||||
return String(s.Runes().SubSlice(0, max)).Append("...")
|
||||
if s.IsASCII() {
|
||||
if Int(len(s)) <= max {
|
||||
return s
|
||||
}
|
||||
|
||||
return s[:max].Append("...")
|
||||
}
|
||||
|
||||
i := 0
|
||||
str := s.Std()
|
||||
for count := Int(0); i < len(s); count++ {
|
||||
if count == max {
|
||||
return s[:i].Append("...")
|
||||
}
|
||||
|
||||
_, sz := utf8.DecodeRuneInString(str[i:])
|
||||
i += sz
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// LeftJustify justifies the String to the left by adding padding to the right, up to the
|
||||
@@ -724,14 +865,17 @@ func (s String) Truncate(max Int) String {
|
||||
// result := s.LeftJustify(10, "...")
|
||||
// // result: "Hello....."
|
||||
func (s String) LeftJustify(length Int, pad String) String {
|
||||
if s.LenRunes() >= length || pad.Eq("") {
|
||||
rlen := s.LenRunes()
|
||||
if rlen >= length || pad.IsEmpty() {
|
||||
return s
|
||||
}
|
||||
|
||||
var b Builder
|
||||
padlen := pad.LenRunes()
|
||||
b.Grow(s.Len() + paddingCapacity(pad, padlen, length-rlen))
|
||||
|
||||
_, _ = b.WriteString(s)
|
||||
writePadding(&b, pad, pad.LenRunes(), length-s.LenRunes())
|
||||
writePadding(&b, pad, padlen, length-rlen)
|
||||
|
||||
return b.String()
|
||||
}
|
||||
@@ -753,13 +897,16 @@ func (s String) LeftJustify(length Int, pad String) String {
|
||||
// result := s.RightJustify(10, "...")
|
||||
// // result: ".....Hello"
|
||||
func (s String) RightJustify(length Int, pad String) String {
|
||||
if s.LenRunes() >= length || pad.Empty() {
|
||||
rlen := s.LenRunes()
|
||||
if rlen >= length || pad.IsEmpty() {
|
||||
return s
|
||||
}
|
||||
|
||||
var b Builder
|
||||
padlen := pad.LenRunes()
|
||||
b.Grow(s.Len() + paddingCapacity(pad, padlen, length-rlen))
|
||||
|
||||
writePadding(&b, pad, pad.LenRunes(), length-s.LenRunes())
|
||||
writePadding(&b, pad, padlen, length-rlen)
|
||||
_, _ = b.WriteString(s)
|
||||
|
||||
return b.String()
|
||||
@@ -783,32 +930,55 @@ func (s String) RightJustify(length Int, pad String) String {
|
||||
// result := s.Center(10, "...")
|
||||
// // result: "..Hello..."
|
||||
func (s String) Center(length Int, pad String) String {
|
||||
if s.LenRunes() >= length || pad.Empty() {
|
||||
slen := s.LenRunes()
|
||||
if slen >= length || pad.IsEmpty() {
|
||||
return s
|
||||
}
|
||||
|
||||
var b Builder
|
||||
|
||||
remains := length - s.LenRunes()
|
||||
padlen := pad.LenRunes()
|
||||
remains := length - slen
|
||||
b.Grow(s.Len() + paddingCapacity(pad, padlen, remains))
|
||||
|
||||
writePadding(&b, pad, pad.LenRunes(), remains/2)
|
||||
writePadding(&b, pad, padlen, remains/2)
|
||||
_, _ = b.WriteString(s)
|
||||
writePadding(&b, pad, pad.LenRunes(), (remains+1)/2)
|
||||
writePadding(&b, pad, padlen, (remains+1)/2)
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func paddingCapacity(pad String, padlen, runes Int) Int {
|
||||
return ((runes + padlen - 1) / padlen) * pad.Len()
|
||||
}
|
||||
|
||||
// writePadding writes the padding String to the output Builder to fill the remaining length.
|
||||
// It repeats the padding String as necessary and appends any remaining runes from the padding
|
||||
// String.
|
||||
func writePadding(b *Builder, pad String, padlen, remains Int) {
|
||||
if repeats := remains / padlen; repeats > 0 {
|
||||
_, _ = b.WriteString(pad.Repeat(repeats))
|
||||
for range remains / padlen {
|
||||
_, _ = b.WriteString(pad)
|
||||
}
|
||||
|
||||
padrunes := pad.Runes()
|
||||
for i := range remains % padlen {
|
||||
_, _ = b.WriteRune(padrunes[i])
|
||||
rem := remains % padlen
|
||||
if rem == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if pad.IsASCII() {
|
||||
for i := range rem {
|
||||
b.WriteByte(pad[i])
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
i := 0
|
||||
str := pad.Std()
|
||||
for range rem {
|
||||
r, sz := utf8.DecodeRuneInString(str[i:])
|
||||
_, _ = b.WriteRune(r)
|
||||
i += sz
|
||||
}
|
||||
}
|
||||
|
||||
@@ -819,3 +989,49 @@ func (s String) Print() String { fmt.Print(s); return s }
|
||||
// Println writes the content of the String to the standard output (console) with a newline
|
||||
// and returns the String unchanged.
|
||||
func (s String) Println() String { fmt.Println(s); return s }
|
||||
|
||||
// Scan implements the database/sql.Scanner interface for g.String.
|
||||
//
|
||||
// Behavior:
|
||||
// - If src is nil, the String is set to an empty string.
|
||||
// - If src is a string, it is directly assigned.
|
||||
// - If src is a []byte, it is converted to a string.
|
||||
// - Otherwise, an error is returned.
|
||||
//
|
||||
// Supported SQL types (common):
|
||||
// - TEXT / VARCHAR → string
|
||||
// - BLOB / BYTEA → []byte (converted to string)
|
||||
//
|
||||
// Notes:
|
||||
// - This method allows g.String to be used directly with database/sql and compatible drivers.
|
||||
func (s *String) Scan(src any) error {
|
||||
if src == nil {
|
||||
*s = ""
|
||||
return nil
|
||||
}
|
||||
|
||||
switch v := src.(type) {
|
||||
case string:
|
||||
*s = String(v)
|
||||
return nil
|
||||
case []byte:
|
||||
*s = String(v)
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("g.String.Scan: cannot scan %T into g.String", src)
|
||||
}
|
||||
}
|
||||
|
||||
// Value implements the database/sql/driver.Valuer interface for g.String.
|
||||
//
|
||||
// Behavior:
|
||||
// - Returns the underlying string value, ready for database insertion.
|
||||
// - Always returns a value compatible with SQL TEXT / VARCHAR types.
|
||||
func (s String) Value() (driver.Value, error) { return string(s), nil }
|
||||
|
||||
// castStrings reinterprets a []string as []String without copying: String is
|
||||
// defined as `type String string`, so the two slice types share one memory
|
||||
// layout.
|
||||
func castStrings(ss []string) []String {
|
||||
return unsafe.Slice((*String)(unsafe.SliceData(ss)), len(ss))
|
||||
}
|
||||
+16
-157
@@ -1,16 +1,5 @@
|
||||
package g
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/flate"
|
||||
"compress/gzip"
|
||||
"compress/zlib"
|
||||
"io"
|
||||
|
||||
"github.com/andybalholm/brotli"
|
||||
"github.com/klauspost/compress/zstd"
|
||||
)
|
||||
|
||||
type (
|
||||
// A struct that wraps a String for compression.
|
||||
compress struct{ str String }
|
||||
@@ -25,162 +14,32 @@ func (s String) Compress() compress { return compress{s} }
|
||||
// Decompress returns a decompress struct wrapping the given String.
|
||||
func (s String) Decompress() decompress { return decompress{s} }
|
||||
|
||||
// Zstd compresses the wrapped String using the zstd compression algorithm and
|
||||
// returns the compressed data as a String.
|
||||
func (c compress) Zstd() String {
|
||||
buffer := new(bytes.Buffer)
|
||||
writer, _ := zstd.NewWriter(buffer)
|
||||
// Zlib compresses the wrapped String with zlib; a zero-copy delegate to the
|
||||
// canonical Bytes implementation.
|
||||
func (c compress) Zlib() String { return c.str.BytesUnsafe().Compress().Zlib().StringUnsafe() }
|
||||
|
||||
_, _ = io.WriteString(writer, c.str.Std())
|
||||
_ = writer.Flush()
|
||||
_ = writer.Close()
|
||||
|
||||
return String(buffer.Bytes())
|
||||
}
|
||||
|
||||
// Zstd decompresses the wrapped String using the zstd compression algorithm and
|
||||
// returns the decompressed data as a Result[String].
|
||||
func (d decompress) Zstd() Result[String] {
|
||||
reader, err := zstd.NewReader(d.str.Reader())
|
||||
if err != nil {
|
||||
reader.Close()
|
||||
return Err[String](err)
|
||||
}
|
||||
|
||||
defer reader.Close()
|
||||
|
||||
buffer := new(bytes.Buffer)
|
||||
if _, err := io.Copy(buffer, reader); err != nil {
|
||||
return Err[String](err)
|
||||
}
|
||||
|
||||
return Ok(String(buffer.Bytes()))
|
||||
}
|
||||
|
||||
// Brotli compresses the wrapped String using the Brotli compression algorithm and
|
||||
// returns the compressed data as a String.
|
||||
func (c compress) Brotli() String {
|
||||
buffer := new(bytes.Buffer)
|
||||
writer := brotli.NewWriter(buffer)
|
||||
|
||||
_, _ = io.WriteString(writer, c.str.Std())
|
||||
_ = writer.Flush()
|
||||
_ = writer.Close()
|
||||
|
||||
return String(buffer.Bytes())
|
||||
}
|
||||
|
||||
// Brotli decompresses the wrapped String using the Brotli compression algorithm and
|
||||
// returns the decompressed data as a Result[String].
|
||||
func (d decompress) Brotli() Result[String] {
|
||||
reader := brotli.NewReader(d.str.Reader())
|
||||
|
||||
buffer := new(bytes.Buffer)
|
||||
if _, err := io.Copy(buffer, reader); err != nil {
|
||||
return Err[String](err)
|
||||
}
|
||||
|
||||
return Ok(String(buffer.Bytes()))
|
||||
}
|
||||
|
||||
// Zlib compresses the wrapped String using the zlib compression algorithm and
|
||||
// returns the compressed data as a String.
|
||||
func (c compress) Zlib() String {
|
||||
// gzcompress() php
|
||||
buffer := new(bytes.Buffer)
|
||||
writer := zlib.NewWriter(buffer)
|
||||
|
||||
_, _ = io.WriteString(writer, c.str.Std())
|
||||
_ = writer.Flush()
|
||||
_ = writer.Close()
|
||||
|
||||
return String(buffer.Bytes())
|
||||
}
|
||||
|
||||
// Zlib decompresses the wrapped String using the zlib compression algorithm and
|
||||
// returns the decompressed data as a Result[String].
|
||||
// Zlib decompresses the wrapped String with zlib.
|
||||
func (d decompress) Zlib() Result[String] {
|
||||
// gzuncompress() php
|
||||
reader, err := zlib.NewReader(d.str.Reader())
|
||||
if err != nil {
|
||||
return Err[String](err)
|
||||
}
|
||||
|
||||
defer reader.Close()
|
||||
|
||||
buffer := new(bytes.Buffer)
|
||||
if _, err := io.Copy(buffer, reader); err != nil {
|
||||
return Err[String](err)
|
||||
}
|
||||
|
||||
return Ok(String(buffer.Bytes()))
|
||||
return d.str.BytesUnsafe().Decompress().Zlib().Map(Bytes.StringUnsafe)
|
||||
}
|
||||
|
||||
// Gzip compresses the wrapped String using the gzip compression format and
|
||||
// returns the compressed data as a String.
|
||||
func (c compress) Gzip() String {
|
||||
// gzencode() php
|
||||
buffer := new(bytes.Buffer)
|
||||
writer := gzip.NewWriter(buffer)
|
||||
// Gzip compresses the wrapped String with gzip; a zero-copy delegate to the
|
||||
// canonical Bytes implementation.
|
||||
func (c compress) Gzip() String { return c.str.BytesUnsafe().Compress().Gzip().StringUnsafe() }
|
||||
|
||||
_, _ = io.WriteString(writer, c.str.Std())
|
||||
_ = writer.Flush()
|
||||
_ = writer.Close()
|
||||
|
||||
return String(buffer.Bytes())
|
||||
}
|
||||
|
||||
// Gzip decompresses the wrapped String using the gzip compression format and
|
||||
// returns the decompressed data as a Result[String].
|
||||
// Gzip decompresses the wrapped String with gzip.
|
||||
func (d decompress) Gzip() Result[String] {
|
||||
// gzdecode() php
|
||||
reader, err := gzip.NewReader(d.str.Reader())
|
||||
if err != nil {
|
||||
return Err[String](err)
|
||||
}
|
||||
|
||||
defer reader.Close()
|
||||
|
||||
buffer := new(bytes.Buffer)
|
||||
if _, err := io.Copy(buffer, reader); err != nil {
|
||||
return Err[String](err)
|
||||
}
|
||||
|
||||
return Ok(String(buffer.Bytes()))
|
||||
return d.str.BytesUnsafe().Decompress().Gzip().Map(Bytes.StringUnsafe)
|
||||
}
|
||||
|
||||
// Flate compresses the wrapped String using the flate (zlib) compression algorithm
|
||||
// and returns the compressed data as a String.
|
||||
// It accepts an optional compression level. If no level is provided, it defaults to 7.
|
||||
// Flate compresses the wrapped String with flate (deflate); a zero-copy
|
||||
// delegate to the canonical Bytes implementation. It accepts an optional
|
||||
// compression level, defaulting to 7 and clamping to the valid range [-2, 9].
|
||||
func (c compress) Flate(level ...int) String {
|
||||
// gzdeflate() php
|
||||
buffer := new(bytes.Buffer)
|
||||
|
||||
l := 7
|
||||
if len(level) != 0 {
|
||||
l = level[0]
|
||||
}
|
||||
|
||||
writer, _ := flate.NewWriter(buffer, l)
|
||||
|
||||
_, _ = io.WriteString(writer, c.str.Std())
|
||||
_ = writer.Flush()
|
||||
_ = writer.Close()
|
||||
|
||||
return String(buffer.Bytes())
|
||||
return c.str.BytesUnsafe().Compress().Flate(level...).StringUnsafe()
|
||||
}
|
||||
|
||||
// Flate decompresses the wrapped String using the flate (zlib) compression algorithm
|
||||
// and returns the decompressed data as a Result[String].
|
||||
// Flate decompresses the wrapped String with flate (deflate).
|
||||
func (d decompress) Flate() Result[String] {
|
||||
// gzinflate() php
|
||||
reader := flate.NewReader(d.str.Reader())
|
||||
defer reader.Close()
|
||||
|
||||
buffer := new(bytes.Buffer)
|
||||
if _, err := io.Copy(buffer, reader); err != nil {
|
||||
return Err[String](err)
|
||||
}
|
||||
|
||||
return Ok(String(buffer.Bytes()))
|
||||
return d.str.BytesUnsafe().Decompress().Flate().Map(Bytes.StringUnsafe)
|
||||
}
|
||||
+132
-108
@@ -1,12 +1,13 @@
|
||||
package g
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"unicode/utf8"
|
||||
|
||||
json "encoding/json/v2"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -23,20 +24,80 @@ func (s String) Encode() encode { return encode{s} }
|
||||
// Decode returns a decode struct wrapping the given String.
|
||||
func (s String) Decode() decode { return decode{s} }
|
||||
|
||||
// Base64 encodes the wrapped String using Base64 and returns the encoded result as an String.
|
||||
func (e encode) Base64() String { return String(base64.StdEncoding.EncodeToString(e.str.Bytes())) }
|
||||
// Base64 encodes the wrapped String using standard Base64 (with padding).
|
||||
func (e encode) Base64() String { return e.str.BytesUnsafe().Encode().Base64().StringUnsafe() }
|
||||
|
||||
// Base64 decodes the wrapped String using Base64 and returns the decoded result as Result[String].
|
||||
func (d decode) Base64() Result[String] {
|
||||
decoded, err := base64.StdEncoding.DecodeString(d.str.Std())
|
||||
if err != nil {
|
||||
return Err[String](err)
|
||||
}
|
||||
// Base64Raw encodes the wrapped String using standard Base64 without padding.
|
||||
func (e encode) Base64Raw() String { return e.str.BytesUnsafe().Encode().Base64Raw().StringUnsafe() }
|
||||
|
||||
return Ok(String(decoded))
|
||||
// Base64URL encodes the wrapped String using URL-safe Base64 (with padding).
|
||||
func (e encode) Base64URL() String { return e.str.BytesUnsafe().Encode().Base64URL().StringUnsafe() }
|
||||
|
||||
// Base64RawURL encodes the wrapped String using URL-safe Base64 without padding.
|
||||
func (e encode) Base64RawURL() String {
|
||||
return e.str.BytesUnsafe().Encode().Base64RawURL().StringUnsafe()
|
||||
}
|
||||
|
||||
// JSON encodes the provided string as JSON and returns the result as Result[String].
|
||||
// Base64 decodes the wrapped String using standard Base64 (with padding).
|
||||
func (d decode) Base64() Result[String] {
|
||||
return d.str.BytesUnsafe().Decode().Base64().Map(Bytes.String)
|
||||
}
|
||||
|
||||
// Base64Raw decodes the wrapped String using standard Base64 without padding.
|
||||
func (d decode) Base64Raw() Result[String] {
|
||||
return d.str.BytesUnsafe().Decode().Base64Raw().Map(Bytes.String)
|
||||
}
|
||||
|
||||
// Base64URL decodes the wrapped String using URL-safe Base64 (with padding).
|
||||
func (d decode) Base64URL() Result[String] {
|
||||
return d.str.BytesUnsafe().Decode().Base64URL().Map(Bytes.String)
|
||||
}
|
||||
|
||||
// Base64RawURL decodes the wrapped String using URL-safe Base64 without padding.
|
||||
func (d decode) Base64RawURL() Result[String] {
|
||||
return d.str.BytesUnsafe().Decode().Base64RawURL().Map(Bytes.String)
|
||||
}
|
||||
|
||||
// Hex hex-encodes the wrapped String and returns the encoded result as an String.
|
||||
func (e encode) Hex() String { return e.str.BytesUnsafe().Encode().Hex().StringUnsafe() }
|
||||
|
||||
// Hex hex-decodes the wrapped String and returns the decoded result as Result[String].
|
||||
func (d decode) Hex() Result[String] {
|
||||
return d.str.BytesUnsafe().Decode().Hex().Map(Bytes.String)
|
||||
}
|
||||
|
||||
// XOR encodes the wrapped String using a repeating-key XOR cipher with the given key.
|
||||
//
|
||||
// WARNING: XOR is NOT a security primitive. A repeating-key XOR cipher provides no
|
||||
// confidentiality against any serious analysis and offers no integrity or
|
||||
// authentication. Use it only for lightweight obfuscation, never to protect
|
||||
// sensitive data.
|
||||
func (e encode) XOR(key String) String {
|
||||
return String(e.str.BytesUnsafe().Encode().XOR(key.BytesUnsafe()))
|
||||
}
|
||||
|
||||
// XOR decodes the wrapped String using a repeating-key XOR cipher with the given key.
|
||||
//
|
||||
// WARNING: XOR is NOT a security primitive. See encode.XOR for details.
|
||||
func (d decode) XOR(key String) String { return d.str.Encode().XOR(key) }
|
||||
|
||||
// Binary converts the wrapped String to its binary representation.
|
||||
func (e encode) Binary() String { return e.str.BytesUnsafe().Encode().Binary().StringUnsafe() }
|
||||
|
||||
// Binary converts the wrapped binary String back to its original String.
|
||||
func (d decode) Binary() Result[String] {
|
||||
return d.str.BytesUnsafe().Decode().Binary().Map(Bytes.String)
|
||||
}
|
||||
|
||||
// JSON encodes the provided string as a JSON string using encoding/json/v2 and
|
||||
// returns the result as Result[String].
|
||||
//
|
||||
// Breaking change (v2 semantics): a String containing invalid UTF-8 now yields
|
||||
// Err. Previously (encoding/json v1) invalid sequences were silently replaced
|
||||
// with the Unicode replacement character (U+FFFD), making the encoding lossy.
|
||||
// Unlike encoding/json v1, the output does not HTML-escape '<', '>', '&' or the
|
||||
// line separators U+2028/U+2029 — they are emitted raw. Escape the output
|
||||
// yourself before embedding it in HTML or <script> contexts.
|
||||
func (e encode) JSON() Result[String] {
|
||||
jsonData, err := json.Marshal(e.str)
|
||||
if err != nil {
|
||||
@@ -46,10 +107,14 @@ func (e encode) JSON() Result[String] {
|
||||
return Ok(String(jsonData))
|
||||
}
|
||||
|
||||
// JSON decodes the provided JSON string and returns the result as Result[String].
|
||||
// JSON decodes the provided JSON string using encoding/json/v2 and returns the
|
||||
// result as Result[String].
|
||||
//
|
||||
// v2 semantics: a JSON string containing invalid UTF-8 yields Err instead of
|
||||
// being decoded with U+FFFD replacements.
|
||||
func (d decode) JSON() Result[String] {
|
||||
var data String
|
||||
err := json.Unmarshal(d.str.Bytes(), &data)
|
||||
err := json.Unmarshal(d.str.BytesUnsafe(), &data)
|
||||
if err != nil {
|
||||
return Err[String](err)
|
||||
}
|
||||
@@ -57,35 +122,40 @@ func (d decode) JSON() Result[String] {
|
||||
return Ok(data)
|
||||
}
|
||||
|
||||
// URL encodes the input string, escaping reserved characters as per RFC 2396.
|
||||
// If safe characters are provided, they will not be encoded.
|
||||
// URL encodes the input string, leaving the RFC 2396 reserved characters
|
||||
// (";/?:@&=+$,") unescaped and query-escaping the rest. If safe characters are
|
||||
// provided, they replace that default set and will not be encoded.
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// - safe (String): Optional. Characters to exclude from encoding.
|
||||
// If provided, the function will not encode these characters.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - String: Encoded URL string.
|
||||
// Note: the default reserved set leaves '+' unescaped, while decoding maps '+'
|
||||
// to a space (application/x-www-form-urlencoded), so Encode -> Decode is lossy
|
||||
// for input containing a literal '+'. Pass a custom safe set to escape it.
|
||||
func (e encode) URL(safe ...String) String {
|
||||
reserved := String(";/?:@&=+$,") // Reserved characters as per RFC 2396
|
||||
reserved := String(";/?:@&=+$,")
|
||||
if len(safe) != 0 {
|
||||
reserved = safe[0]
|
||||
}
|
||||
|
||||
var b Builder
|
||||
out := make(Bytes, 0, e.str.Len())
|
||||
|
||||
for _, r := range e.str {
|
||||
if reserved.ContainsRune(r) {
|
||||
b.WriteRune(r)
|
||||
out = Bytes(utf8.AppendRune(out, r))
|
||||
continue
|
||||
}
|
||||
|
||||
_, _ = b.WriteString(String(url.QueryEscape(string(r))))
|
||||
if r < utf8.RuneSelf {
|
||||
out = appendQueryEscaped(out, byte(r))
|
||||
continue
|
||||
}
|
||||
|
||||
var enc [utf8.UTFMax]byte
|
||||
n := utf8.EncodeRune(enc[:], r)
|
||||
for _, c := range enc[:n] {
|
||||
out = appendQueryEscaped(out, c)
|
||||
}
|
||||
}
|
||||
|
||||
return b.String()
|
||||
return out.StringUnsafe()
|
||||
}
|
||||
|
||||
// URL URL-decodes the wrapped String and returns the decoded result as Result[String].
|
||||
@@ -98,14 +168,16 @@ func (d decode) URL() Result[String] {
|
||||
return Ok(String(result))
|
||||
}
|
||||
|
||||
// HTML HTML-encodes the wrapped String and returns the encoded result as an String.
|
||||
// HTML HTML-encodes the wrapped String.
|
||||
func (e encode) HTML() String { return String(html.EscapeString(e.str.Std())) }
|
||||
|
||||
// HTML HTML-decodes the wrapped String and returns the decoded result as an String.
|
||||
// HTML HTML-decodes the wrapped String.
|
||||
func (d decode) HTML() String { return String(html.UnescapeString(d.str.Std())) }
|
||||
|
||||
// Rot13 encodes the wrapped String using ROT13 cipher and returns the encoded result as an
|
||||
// String.
|
||||
// Rot13 encodes the wrapped String using the ROT13 cipher.
|
||||
//
|
||||
// WARNING: ROT13 is NOT a security primitive. It is a fixed letter-substitution
|
||||
// cipher with no key and is trivially reversible. Use it only for obfuscation.
|
||||
func (e encode) Rot13() String {
|
||||
rot := func(r rune) rune {
|
||||
switch {
|
||||
@@ -121,98 +193,50 @@ func (e encode) Rot13() String {
|
||||
return e.str.Map(rot)
|
||||
}
|
||||
|
||||
// Rot13 decodes the wrapped String using ROT13 cipher and returns the decoded result as an
|
||||
// String.
|
||||
// Rot13 decodes the wrapped String using ROT13 cipher.
|
||||
func (d decode) Rot13() String { return d.str.Encode().Rot13() }
|
||||
|
||||
// XOR encodes the wrapped String using XOR cipher with the given key and returns the encoded
|
||||
// result as an String.
|
||||
func (e encode) XOR(key String) String {
|
||||
if key.Empty() {
|
||||
return e.str
|
||||
}
|
||||
|
||||
encrypted := e.str.Bytes()
|
||||
|
||||
for i := range len(e.str) {
|
||||
encrypted[i] ^= key[i%len(key)]
|
||||
}
|
||||
|
||||
return String(encrypted)
|
||||
}
|
||||
|
||||
// XOR decodes the wrapped String using XOR cipher with the given key and returns the decoded
|
||||
// result as an String.
|
||||
func (d decode) XOR(key String) String { return d.str.Encode().XOR(key) }
|
||||
|
||||
// Hex hex-encodes the wrapped String and returns the encoded result as an String.
|
||||
func (e encode) Hex() String {
|
||||
// Octal returns the octal representation of the encoded string.
|
||||
func (e encode) Octal() String {
|
||||
var b Builder
|
||||
for i := range len(e.str) {
|
||||
b.WriteString(Int(e.str[i]).Hex())
|
||||
var tmp [7]byte
|
||||
|
||||
first := true
|
||||
|
||||
for _, char := range e.str {
|
||||
if !first {
|
||||
b.WriteByte(' ')
|
||||
}
|
||||
|
||||
_, _ = b.Write(strconv.AppendInt(tmp[:0], int64(char), 8))
|
||||
first = false
|
||||
}
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// Hex hex-decodes the wrapped String and returns the decoded result as Result[String].
|
||||
func (d decode) Hex() Result[String] {
|
||||
result, err := hex.DecodeString(d.str.Std())
|
||||
if err != nil {
|
||||
return Err[String](err)
|
||||
}
|
||||
|
||||
return Ok(String(result))
|
||||
}
|
||||
|
||||
// Octal returns the octal representation of the encoded string.
|
||||
func (e encode) Octal() String {
|
||||
result := NewSlice[String](e.str.LenRunes())
|
||||
for i, char := range e.str.Runes() {
|
||||
result.Set(Int(i), Int(char).Octal())
|
||||
}
|
||||
|
||||
return result.Join(" ")
|
||||
}
|
||||
|
||||
// Octal returns the octal representation of the decimal-encoded string as Result[String].
|
||||
// Octal decodes the octal representation back to String.
|
||||
// An empty input returns an empty String, mirroring encode.Octal("").
|
||||
// Each token must represent a valid Unicode code point in the range [0, MaxRune].
|
||||
func (d decode) Octal() Result[String] {
|
||||
if d.str.IsEmpty() {
|
||||
return Ok(String(""))
|
||||
}
|
||||
|
||||
var b Builder
|
||||
|
||||
for v := range d.str.Split(" ") {
|
||||
for _, v := range d.str.Split(" ") {
|
||||
n, err := strconv.ParseUint(v.Std(), 8, 32)
|
||||
if err != nil {
|
||||
return Err[String](err)
|
||||
}
|
||||
|
||||
if n > utf8.MaxRune || (n >= 0xD800 && n <= 0xDFFF) {
|
||||
return Err[String](fmt.Errorf("g.decode.Octal: invalid code point %d", n))
|
||||
}
|
||||
|
||||
b.WriteRune(rune(n))
|
||||
}
|
||||
|
||||
return Ok(b.String())
|
||||
}
|
||||
|
||||
// Binary converts the wrapped String to its binary representation as an String.
|
||||
func (e encode) Binary() String {
|
||||
var b Builder
|
||||
for i := range len(e.str) {
|
||||
b.WriteString(Int(e.str[i]).Binary())
|
||||
}
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// Binary converts the wrapped binary String back to its original String representation as Result[String].
|
||||
func (d decode) Binary() Result[String] {
|
||||
var result Bytes
|
||||
|
||||
for i := 0; i+8 <= len(d.str); i += 8 {
|
||||
b, err := strconv.ParseUint(d.str[i:i+8].Std(), 2, 8)
|
||||
if err != nil {
|
||||
return Err[String](err)
|
||||
}
|
||||
|
||||
result = append(result, byte(b))
|
||||
}
|
||||
|
||||
return Ok(result.String())
|
||||
}
|
||||
+58
-8
@@ -6,14 +6,64 @@ type shash struct{ str String }
|
||||
// Hash returns a shash struct wrapping the given String.
|
||||
func (s String) Hash() shash { return shash{s} }
|
||||
|
||||
// MD5 computes the MD5 hash of the wrapped String and returns the hash as an String.
|
||||
func (sh shash) MD5() String { return sh.str.Bytes().Hash().MD5().String() }
|
||||
// MD5 computes the MD5 hash of the wrapped String and returns the hash as hex-encoded String.
|
||||
//
|
||||
// WARNING: MD5 is cryptographically broken and is NOT a security primitive.
|
||||
// Do not use it for passwords, signatures, or integrity against an adversary.
|
||||
// Use it only for checksums or legacy interop.
|
||||
func (sh shash) MD5() String { return sh.str.BytesUnsafe().Hash().MD5().StringUnsafe() }
|
||||
|
||||
// SHA1 computes the SHA1 hash of the wrapped String and returns the hash as an String.
|
||||
func (sh shash) SHA1() String { return sh.str.Bytes().Hash().SHA1().String() }
|
||||
// SHA1 computes the SHA1 hash of the wrapped String and returns the hash as hex-encoded String.
|
||||
//
|
||||
// WARNING: SHA1 is cryptographically broken (collision attacks are practical) and
|
||||
// is NOT a security primitive. Do not use it for signatures or integrity against
|
||||
// an adversary. Use SHA256/SHA512 instead.
|
||||
func (sh shash) SHA1() String { return sh.str.BytesUnsafe().Hash().SHA1().StringUnsafe() }
|
||||
|
||||
// SHA256 computes the SHA256 hash of the wrapped String and returns the hash as an String.
|
||||
func (sh shash) SHA256() String { return sh.str.Bytes().Hash().SHA256().String() }
|
||||
// SHA256 computes the SHA256 hash of the wrapped String and returns the hash as hex-encoded String.
|
||||
func (sh shash) SHA256() String { return sh.str.BytesUnsafe().Hash().SHA256().StringUnsafe() }
|
||||
|
||||
// SHA512 computes the SHA512 hash of the wrapped String and returns the hash as an String.
|
||||
func (sh shash) SHA512() String { return sh.str.Bytes().Hash().SHA512().String() }
|
||||
// SHA512 computes the SHA512 hash of the wrapped String and returns the hash as hex-encoded String.
|
||||
func (sh shash) SHA512() String { return sh.str.BytesUnsafe().Hash().SHA512().StringUnsafe() }
|
||||
|
||||
// HMACSHA256 computes the HMAC-SHA256 of the wrapped String using the provided key
|
||||
// and returns the result as hex-encoded String.
|
||||
func (sh shash) HMACSHA256(key String) String {
|
||||
return sh.str.BytesUnsafe().Hash().HMACSHA256(key.BytesUnsafe()).StringUnsafe()
|
||||
}
|
||||
|
||||
// HMACSHA512 computes the HMAC-SHA512 of the wrapped String using the provided key
|
||||
// and returns the result as hex-encoded String.
|
||||
func (sh shash) HMACSHA512(key String) String {
|
||||
return sh.str.BytesUnsafe().Hash().HMACSHA512(key.BytesUnsafe()).StringUnsafe()
|
||||
}
|
||||
|
||||
// MD5Raw computes the MD5 hash of the wrapped String and returns the raw digest as Bytes.
|
||||
//
|
||||
// WARNING: MD5 is cryptographically broken and is NOT a security primitive.
|
||||
// Use it only for checksums or legacy interop, never for security.
|
||||
func (sh shash) MD5Raw() Bytes { return sh.str.BytesUnsafe().Hash().MD5Raw() }
|
||||
|
||||
// SHA1Raw computes the SHA1 hash of the wrapped String and returns the raw digest as Bytes.
|
||||
//
|
||||
// WARNING: SHA1 is cryptographically broken and is NOT a security primitive.
|
||||
// Use SHA256/SHA512 instead for any security-sensitive purpose.
|
||||
func (sh shash) SHA1Raw() Bytes { return sh.str.BytesUnsafe().Hash().SHA1Raw() }
|
||||
|
||||
// SHA256Raw computes the SHA256 hash of the wrapped String and returns the raw digest as Bytes.
|
||||
func (sh shash) SHA256Raw() Bytes { return sh.str.BytesUnsafe().Hash().SHA256Raw() }
|
||||
|
||||
// SHA512Raw computes the SHA512 hash of the wrapped String and returns the raw digest as Bytes.
|
||||
func (sh shash) SHA512Raw() Bytes { return sh.str.BytesUnsafe().Hash().SHA512Raw() }
|
||||
|
||||
// HMACSHA256Raw computes the HMAC-SHA256 of the wrapped String using the provided key
|
||||
// and returns the raw digest as Bytes.
|
||||
func (sh shash) HMACSHA256Raw(key String) Bytes {
|
||||
return sh.str.BytesUnsafe().Hash().HMACSHA256Raw(key.BytesUnsafe())
|
||||
}
|
||||
|
||||
// HMACSHA512Raw computes the HMAC-SHA512 of the wrapped String using the provided key
|
||||
// and returns the raw digest as Bytes.
|
||||
func (sh shash) HMACSHA512Raw(key String) Bytes {
|
||||
return sh.str.BytesUnsafe().Hash().HMACSHA512Raw(key.BytesUnsafe())
|
||||
}
|
||||
-173
@@ -1,173 +0,0 @@
|
||||
package g
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
"github.com/enetx/g/f"
|
||||
)
|
||||
|
||||
// regexps struct wraps a String and provides regex-related methods.
|
||||
type regexps struct{ str String }
|
||||
|
||||
// Regexp wraps a String into an re struct to provide regex-related methods.
|
||||
func (s String) Regexp() regexps { return regexps{s} }
|
||||
|
||||
// Find searches the String for the first occurrence of the regulare xpression pattern
|
||||
// and returns an Option[String] containing the matched substring.
|
||||
// If no match is found, it returns None.
|
||||
func (r regexps) Find(pattern *regexp.Regexp) Option[String] {
|
||||
result := String(pattern.FindString(r.str.Std()))
|
||||
if result.Empty() {
|
||||
return None[String]()
|
||||
}
|
||||
|
||||
return Some(result)
|
||||
}
|
||||
|
||||
// Replace replaces all occurrences of the regular expression matches in the String
|
||||
// with the provided newS (as a String) and returns the resulting String after the replacement.
|
||||
func (r regexps) Replace(pattern *regexp.Regexp, newS String) String {
|
||||
return String(pattern.ReplaceAllString(r.str.Std(), newS.Std()))
|
||||
}
|
||||
|
||||
// ReplaceBy replaces all occurrences of the regular expression matches in the String
|
||||
// by applying a custom transformation function to each match.
|
||||
// The function `fn` takes a String representing a match and returns a String that will replace it.
|
||||
func (r regexps) ReplaceBy(pattern *regexp.Regexp, fn func(match String) String) String {
|
||||
return String(pattern.ReplaceAllStringFunc(r.str.Std(), func(s string) string { return fn(String(s)).Std() }))
|
||||
}
|
||||
|
||||
// Match checks if the String contains a match for the specified regular expression pattern.
|
||||
func (r regexps) Match(pattern *regexp.Regexp) bool { return f.Match[String](pattern)(r.str) }
|
||||
|
||||
// MatchAny checks if the String contains a match for any of the specified regular
|
||||
// expression patterns.
|
||||
func (r regexps) MatchAny(patterns ...*regexp.Regexp) bool {
|
||||
return Slice[*regexp.Regexp](patterns).
|
||||
Iter().
|
||||
Any(func(pattern *regexp.Regexp) bool { return r.Match(pattern) })
|
||||
}
|
||||
|
||||
// MatchAll checks if the String contains a match for all of the specified regular expression patterns.
|
||||
func (r regexps) MatchAll(patterns ...*regexp.Regexp) bool {
|
||||
return Slice[*regexp.Regexp](patterns).
|
||||
Iter().
|
||||
All(func(pattern *regexp.Regexp) bool { return r.Match(pattern) })
|
||||
}
|
||||
|
||||
// Split splits the String into substrings using the provided regular expression pattern and returns an Slice[String] of the results.
|
||||
// The regular expression pattern is provided as a regexp.Regexp parameter.
|
||||
func (r regexps) Split(pattern *regexp.Regexp) Slice[String] {
|
||||
return TransformSlice(pattern.Split(r.str.Std(), -1), NewString)
|
||||
}
|
||||
|
||||
// SplitN splits the String into substrings using the provided regular expression pattern and returns an Slice[String] of the results.
|
||||
// The regular expression pattern is provided as a regexp.Regexp parameter.
|
||||
// The n parameter controls the number of substrings to return:
|
||||
// - If n is negative, there is no limit on the number of substrings returned.
|
||||
// - If n is zero, an empty Slice[String] is returned.
|
||||
// - If n is positive, at most n substrings are returned.
|
||||
func (r regexps) SplitN(pattern *regexp.Regexp, n Int) Option[Slice[String]] {
|
||||
result := TransformSlice(pattern.Split(r.str.Std(), n.Std()), NewString)
|
||||
if result.Empty() {
|
||||
return None[Slice[String]]()
|
||||
}
|
||||
|
||||
return Some(result)
|
||||
}
|
||||
|
||||
// RxIndex searches for the first occurrence of the regular expression pattern in the String.
|
||||
// If a match is found, it returns an Option containing an Slice with the start and end indices of the match.
|
||||
// If no match is found, it returns None.
|
||||
func (r regexps) Index(pattern *regexp.Regexp) Option[Slice[Int]] {
|
||||
result := TransformSlice(pattern.FindStringIndex(r.str.Std()), NewInt)
|
||||
if result.Empty() {
|
||||
return None[Slice[Int]]()
|
||||
}
|
||||
|
||||
return Some(result)
|
||||
}
|
||||
|
||||
// FindAll searches the String for all occurrences of the regular expression pattern
|
||||
// and returns an Option[Slice[String]] containing a slice of matched substrings.
|
||||
// If no matches are found, the Option[Slice[String]] will be None.
|
||||
func (r regexps) FindAll(pattern *regexp.Regexp) Option[Slice[String]] {
|
||||
return r.FindAllN(pattern, -1)
|
||||
}
|
||||
|
||||
// FindAllN searches the String for up to n occurrences of the regular expression pattern
|
||||
// and returns an Option[Slice[String]] containing a slice of matched substrings.
|
||||
// If no matches are found, the Option[Slice[String]] will be None.
|
||||
// If n is negative, all occurrences will be returned.
|
||||
func (r regexps) FindAllN(pattern *regexp.Regexp, n Int) Option[Slice[String]] {
|
||||
result := TransformSlice(pattern.FindAllString(r.str.Std(), n.Std()), NewString)
|
||||
if result.Empty() {
|
||||
return None[Slice[String]]()
|
||||
}
|
||||
|
||||
return Some(result)
|
||||
}
|
||||
|
||||
// FindSubmatch searches the String for the first occurrence of the regular expression pattern
|
||||
// and returns an Option[Slice[String]] containing the matched substrings and submatches.
|
||||
// The Option will contain an Slice[String] with the full match at index 0, followed by any captured submatches.
|
||||
// If no match is found, it returns None.
|
||||
func (r regexps) FindSubmatch(pattern *regexp.Regexp) Option[Slice[String]] {
|
||||
result := TransformSlice(pattern.FindStringSubmatch(r.str.Std()), NewString)
|
||||
if result.Empty() {
|
||||
return None[Slice[String]]()
|
||||
}
|
||||
|
||||
return Some(result)
|
||||
}
|
||||
|
||||
// FindAllSubmatch searches the String for all occurrences of the regular expression pattern
|
||||
// and returns an Option[Slice[Slice[String]]] containing the matched substrings and submatches.
|
||||
// The Option[Slice[Slice[String]]] will contain an Slice[String] for each match,
|
||||
// where each Slice[String] will contain the full match at index 0, followed by any captured submatches.
|
||||
// If no match is found, the Option[Slice[Slice[String]]] will be None.
|
||||
// This method is equivalent to calling SubmatchAllRegexpN with n = -1, which means it finds all occurrences.
|
||||
func (r regexps) FindAllSubmatch(pattern *regexp.Regexp) Option[Slice[Slice[String]]] {
|
||||
return r.FindAllSubmatchN(pattern, -1)
|
||||
}
|
||||
|
||||
// FindAllSubmatchN searches the String for occurrences of the regular expression pattern
|
||||
// and returns an Option[Slice[Slice[String]]] containing the matched substrings and submatches.
|
||||
// The Option[Slice[Slice[String]]] will contain an Slice[String] for each match,
|
||||
// where each Slice[String] will contain the full match at index 0, followed by any captured submatches.
|
||||
// If no match is found, the Option[Slice[Slice[String]]] will be None.
|
||||
// The 'n' parameter specifies the maximum number of matches to find. If n is negative, it finds all occurrences.
|
||||
func (r regexps) FindAllSubmatchN(pattern *regexp.Regexp, n Int) Option[Slice[Slice[String]]] {
|
||||
var result Slice[Slice[String]]
|
||||
|
||||
for _, v := range pattern.FindAllStringSubmatch(r.str.Std(), n.Std()) {
|
||||
result = append(result, TransformSlice(v, NewString))
|
||||
}
|
||||
|
||||
if result.Empty() {
|
||||
return None[Slice[Slice[String]]]()
|
||||
}
|
||||
|
||||
return Some(result)
|
||||
}
|
||||
|
||||
// Compile compiles the String into a regular expression (regexp.Regexp).
|
||||
//
|
||||
// This method attempts to compile the String receiver into a regular expression using the
|
||||
// regexp.Compile function from the standard library. If the compilation is successful,
|
||||
// the function returns a Result containing the compiled *regexp.Regexp. If the compilation
|
||||
// fails due to an invalid regular expression pattern, the Result will contain the error.
|
||||
//
|
||||
// Returns:
|
||||
// - Result[*regexp.Regexp]: A Result containing the compiled *regexp.Regexp if successful, or an error otherwise.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// s := g.String(`^\d+$`)
|
||||
// compiledRegex := s.Regexp().Compile()
|
||||
// if compiledRegex.IsOk() {
|
||||
// fmt.Println("Regex compiled successfully")
|
||||
// } else {
|
||||
// fmt.Println("Failed to compile regex:", compiledRegex.Err())
|
||||
// }
|
||||
func (r regexps) Compile() Result[*regexp.Regexp] { return ResultOf(regexp.Compile(r.str.Std())) }
|
||||
-170
@@ -1,170 +0,0 @@
|
||||
package g
|
||||
|
||||
import (
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/enetx/g/cmp"
|
||||
"github.com/enetx/iter"
|
||||
)
|
||||
|
||||
type (
|
||||
// Result is a generic struct for representing a result value along with an error.
|
||||
Result[T any] struct {
|
||||
v T // Value.
|
||||
err error // Associated error.
|
||||
}
|
||||
|
||||
// Option is a generic struct for representing an optional value.
|
||||
Option[T any] struct {
|
||||
v T // Value.
|
||||
isSome bool // Indicator of value presence.
|
||||
}
|
||||
|
||||
// Unit represents an empty value.
|
||||
// Used in contexts where a function needs to return "something" but
|
||||
// the actual value doesn't matter, only success/failure status.
|
||||
Unit struct{}
|
||||
|
||||
// File is a struct that represents a file along with an iterator for reading lines.
|
||||
File struct {
|
||||
file *os.File // Underlying os.File.
|
||||
name String // File name.
|
||||
guard bool // Guard indicates whether the file is protected against concurrent access.
|
||||
}
|
||||
|
||||
// Dir is a struct representing a directory path.
|
||||
Dir struct {
|
||||
path String // Directory path.
|
||||
}
|
||||
|
||||
// String is an alias for the string type.
|
||||
String string
|
||||
|
||||
// Int is an alias for the int type.
|
||||
Int int
|
||||
|
||||
// Float is an alias for the float64 type.
|
||||
Float float64
|
||||
|
||||
// Bytes is an alias for the []byte type.
|
||||
Bytes []byte
|
||||
|
||||
// Slice is a generic alias for a slice.
|
||||
Slice[T any] []T
|
||||
|
||||
// Map is a generic alias for a map.
|
||||
Map[K comparable, V any] map[K]V
|
||||
|
||||
// MapEntry provides a view into a single key of a Map.
|
||||
// It exposes a fluent, chain-friendly interface for inspecting, inserting,
|
||||
// mutating, or deleting a value with a single key lookup.
|
||||
MapEntry[K comparable, V any] struct {
|
||||
m Map[K, V]
|
||||
key K
|
||||
}
|
||||
|
||||
// MapSafeEntry provides a view into a single key of a concurrent-safe Map.
|
||||
// It exposes a fluent, chain-friendly interface for inspecting, inserting,
|
||||
// mutating, or deleting a value with a single key lookup.
|
||||
MapSafeEntry[K comparable, V any] struct {
|
||||
m *MapSafe[K, V]
|
||||
key K
|
||||
}
|
||||
|
||||
// Set is a generic alias for a set implemented using a map.
|
||||
Set[T comparable] map[T]struct{}
|
||||
|
||||
// Pair is a struct representing a key-value Pair for MapOrd.
|
||||
Pair[K, V any] = iter.Pair[K, V]
|
||||
|
||||
// MapOrd is an ordered map that maintains insertion order using a slice for pairs
|
||||
// and a map for fast index lookups.
|
||||
MapOrd[K comparable, V any] []Pair[K, V] // ordered key-value pairs
|
||||
|
||||
// MapOrdEntry provides a view into a single key of an ordered Map (MapOrd),
|
||||
// enabling fluent insertion, mutation, and deletion while preserving entry order.
|
||||
MapOrdEntry[K comparable, V any] struct {
|
||||
mo *MapOrd[K, V]
|
||||
key K
|
||||
}
|
||||
|
||||
// MapSafe is a concurrent-safe generic map built on sync.Map.
|
||||
MapSafe[K comparable, V any] struct {
|
||||
data sync.Map
|
||||
}
|
||||
|
||||
// Heap is a generic binary heap data structure that maintains elements in heap order.
|
||||
// It can be configured as either a min-heap or max-heap based on the comparison function.
|
||||
Heap[T any] struct {
|
||||
data Slice[T]
|
||||
cmp func(T, T) cmp.Ordering
|
||||
}
|
||||
|
||||
// Deque is a double-ended queue implemented with a growable ring buffer.
|
||||
// It provides efficient insertion and removal of elements at both ends.
|
||||
Deque[T any] struct {
|
||||
data Slice[T]
|
||||
front Int
|
||||
len Int
|
||||
}
|
||||
|
||||
// Named is a map-like type that stores key-value pairs for resolving named
|
||||
// placeholders in Sprintf.
|
||||
Named Map[String, any]
|
||||
|
||||
// SeqSet is an iterator over sequences of unique values.
|
||||
SeqSet[V comparable] iter.Seq[V]
|
||||
|
||||
// SeqSlice is an iterator over sequences of individual values.
|
||||
SeqSlice[V any] iter.Seq[V]
|
||||
|
||||
// SeqHeap is an iterator over sequences of Heap values.
|
||||
SeqHeap[V any] iter.Seq[V]
|
||||
|
||||
// SeqDeque is an iterator over sequences of Deque values.
|
||||
SeqDeque[V any] iter.Seq[V]
|
||||
|
||||
// SeqResult is an iterator over sequences of Result[V] values.
|
||||
SeqResult[V any] iter.Seq[Result[V]]
|
||||
|
||||
// SeqSlices is an iterator over slices of sequences of individual values.
|
||||
SeqSlices[V any] iter.Seq[[]V]
|
||||
|
||||
// SeqMapOrd is an iterator over sequences of ordered pairs of values, most commonly ordered key-value pairs.
|
||||
SeqMapOrd[K comparable, V any] iter.Seq2[K, V]
|
||||
|
||||
// SeqMap is an iterator over sequences of pairs of values, most commonly key-value pairs.
|
||||
SeqMap[K comparable, V any] iter.Seq2[K, V]
|
||||
|
||||
// SeqSlicePar is a parallel iterator over a slice of elements of type T.
|
||||
// It uses a fixed-size pool of worker goroutines to process elements concurrently.
|
||||
SeqSlicePar[V any] struct {
|
||||
seq SeqSlice[V]
|
||||
workers Int
|
||||
process func(V) (V, bool)
|
||||
}
|
||||
|
||||
// SeqMapPar is the parallel version of SeqMap[K,V].
|
||||
SeqMapPar[K comparable, V any] struct {
|
||||
seq SeqMap[K, V]
|
||||
workers Int
|
||||
process func(Pair[K, V]) (Pair[K, V], bool)
|
||||
}
|
||||
|
||||
// SeqDequePar is a parallel iterator over a deque of elements of type T.
|
||||
// It uses a fixed-size pool of worker goroutines to process elements concurrently.
|
||||
SeqDequePar[V any] struct {
|
||||
seq SeqDeque[V]
|
||||
workers Int
|
||||
process func(V) (V, bool)
|
||||
}
|
||||
|
||||
// SeqHeapPar is a parallel iterator over a heap of elements of type T.
|
||||
// It uses a fixed-size pool of worker goroutines to process elements concurrently.
|
||||
SeqHeapPar[V any] struct {
|
||||
seq SeqHeap[V]
|
||||
workers Int
|
||||
process func(V) (V, bool)
|
||||
}
|
||||
)
|
||||
+20
-7
@@ -173,8 +173,13 @@ func refererForURL(lastReq, newReq *url.URL, explicitRef string) string {
|
||||
|
||||
// didTimeout is non-nil only if err != nil.
|
||||
func (c *Client) send(req *Request, deadline time.Time) (resp *Response, didTimeout func() bool, err error) {
|
||||
cookieURL := req.URL
|
||||
if req.Host != "" {
|
||||
cookieURL = cloneURL(cookieURL)
|
||||
cookieURL.Host = req.Host
|
||||
}
|
||||
if c.Jar != nil {
|
||||
for _, cookie := range c.Jar.Cookies(req.URL) {
|
||||
for _, cookie := range c.Jar.Cookies(cookieURL) {
|
||||
req.AddCookie(cookie)
|
||||
}
|
||||
}
|
||||
@@ -184,7 +189,7 @@ func (c *Client) send(req *Request, deadline time.Time) (resp *Response, didTime
|
||||
}
|
||||
if c.Jar != nil {
|
||||
if rc := resp.Cookies(); len(rc) > 0 {
|
||||
c.Jar.SetCookies(req.URL, rc)
|
||||
c.Jar.SetCookies(cookieURL, rc)
|
||||
}
|
||||
}
|
||||
return resp, nil, nil
|
||||
@@ -686,8 +691,7 @@ func (c *Client) do(req *Request) (retres *Response, reterr error) {
|
||||
stripSensitiveHeaders = true
|
||||
}
|
||||
}
|
||||
copyHeaders(req, stripSensitiveHeaders)
|
||||
|
||||
copyHeaders(req, stripSensitiveHeaders, !includeBody)
|
||||
// Add the Referer header from the most recent
|
||||
// request URL to the new one, if it's not https->http:
|
||||
if ref := refererForURL(reqs[len(reqs)-1].URL, req.URL, req.Header.Get("Referer")); ref != "" {
|
||||
@@ -754,7 +758,7 @@ func (c *Client) do(req *Request) (retres *Response, reterr error) {
|
||||
// makeHeadersCopier makes a function that copies headers from the
|
||||
// initial Request, ireq. For every redirect, this function must be called
|
||||
// so that it can copy headers into the upcoming Request.
|
||||
func (c *Client) makeHeadersCopier(ireq *Request) func(req *Request, stripSensitiveHeaders bool) {
|
||||
func (c *Client) makeHeadersCopier(ireq *Request) func(req *Request, stripSensitiveHeaders, stripBodyHeaders bool) {
|
||||
// The headers to copy are from the very initial request.
|
||||
// We use a closured callback to keep a reference to these original headers.
|
||||
var (
|
||||
@@ -768,7 +772,7 @@ func (c *Client) makeHeadersCopier(ireq *Request) func(req *Request, stripSensit
|
||||
}
|
||||
}
|
||||
|
||||
return func(req *Request, stripSensitiveHeaders bool) {
|
||||
return func(req *Request, stripSensitiveHeaders, stripBodyHeaders bool) {
|
||||
// If Jar is present and there was some initial cookies provided
|
||||
// via the request header, then we may need to alter the initial
|
||||
// cookies as we follow redirects since each redirect may end up
|
||||
@@ -806,12 +810,21 @@ func (c *Client) makeHeadersCopier(ireq *Request) func(req *Request, stripSensit
|
||||
// (at least the safe ones).
|
||||
for k, vv := range ireqhdr {
|
||||
sensitive := false
|
||||
body := false
|
||||
switch CanonicalHeaderKey(k) {
|
||||
case "Authorization", "Www-Authenticate", "Cookie", "Cookie2",
|
||||
"Proxy-Authorization", "Proxy-Authenticate":
|
||||
sensitive = true
|
||||
|
||||
case "Content-Encoding", "Content-Language", "Content-Location",
|
||||
"Content-Type":
|
||||
// Headers relating to the body which is removed for
|
||||
// POST to GET redirects
|
||||
// https://fetch.spec.whatwg.org/#http-redirect-fetch
|
||||
body = true
|
||||
|
||||
}
|
||||
if !(sensitive && stripSensitiveHeaders) {
|
||||
if !(sensitive && stripSensitiveHeaders) && !(body && stripBodyHeaders) {
|
||||
req.Header[k] = vv
|
||||
}
|
||||
}
|
||||
|
||||
+45
-10
@@ -59,16 +59,27 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
errBlankCookie = errors.New("http: blank cookie")
|
||||
errEqualNotFoundInCookie = errors.New("http: '=' not found in cookie")
|
||||
errInvalidCookieName = errors.New("http: invalid cookie name")
|
||||
errInvalidCookieValue = errors.New("http: invalid cookie value")
|
||||
errBlankCookie = errors.New("http: blank cookie")
|
||||
errEqualNotFoundInCookie = errors.New("http: '=' not found in cookie")
|
||||
errInvalidCookieName = errors.New("http: invalid cookie name")
|
||||
errInvalidCookieValue = errors.New("http: invalid cookie value")
|
||||
errCookieNumLimitExceeded = errors.New("http: number of cookies exceeded limit")
|
||||
)
|
||||
|
||||
const defaultCookieMaxNum = 3000
|
||||
|
||||
func cookieNumWithinMax(cookieNum int) bool {
|
||||
withinDefaultMax := cookieNum <= defaultCookieMaxNum
|
||||
return withinDefaultMax
|
||||
}
|
||||
|
||||
// ParseCookie parses a Cookie header value and returns all the cookies
|
||||
// which were set in it. Since the same cookie name can appear multiple times
|
||||
// the returned Values can contain more than one value for a given key.
|
||||
func ParseCookie(line string) ([]*Cookie, error) {
|
||||
if !cookieNumWithinMax(strings.Count(line, ";") + 1) {
|
||||
return nil, errCookieNumLimitExceeded
|
||||
}
|
||||
parts := strings.Split(textproto.TrimString(line), ";")
|
||||
if len(parts) == 1 && parts[0] == "" {
|
||||
return nil, errBlankCookie
|
||||
@@ -198,11 +209,21 @@ func ParseSetCookie(line string) (*Cookie, error) {
|
||||
|
||||
// readSetCookies parses all "Set-Cookie" values from
|
||||
// the header h and returns the successfully parsed Cookies.
|
||||
//
|
||||
// If the amount of cookies exceeds CookieNumLimit, and httpcookielimitnum
|
||||
// GODEBUG option is not explicitly turned off, this function will silently
|
||||
// fail and return an empty slice.
|
||||
func readSetCookies(h Header) []*Cookie {
|
||||
cookieCount := len(h["Set-Cookie"])
|
||||
if cookieCount == 0 {
|
||||
return []*Cookie{}
|
||||
}
|
||||
// Cookie limit was unfortunately introduced at a later point in time.
|
||||
// As such, we can only fail by returning an empty slice rather than
|
||||
// explicit error.
|
||||
if !cookieNumWithinMax(cookieCount) {
|
||||
return []*Cookie{}
|
||||
}
|
||||
cookies := make([]*Cookie, 0, cookieCount)
|
||||
for _, line := range h["Set-Cookie"] {
|
||||
if cookie, err := ParseSetCookie(line); err == nil {
|
||||
@@ -330,13 +351,28 @@ func (c *Cookie) Valid() error {
|
||||
// readCookies parses all "Cookie" values from the header h and
|
||||
// returns the successfully parsed Cookies.
|
||||
//
|
||||
// if filter isn't empty, only cookies of that name are returned.
|
||||
// If filter isn't empty, only cookies of that name are returned.
|
||||
//
|
||||
// If the amount of cookies exceeds CookieNumLimit, and httpcookielimitnum
|
||||
// GODEBUG option is not explicitly turned off, this function will silently
|
||||
// fail and return an empty slice.
|
||||
func readCookies(h Header, filter string) []*Cookie {
|
||||
lines := h["Cookie"]
|
||||
if len(lines) == 0 {
|
||||
return []*Cookie{}
|
||||
}
|
||||
|
||||
// Cookie limit was unfortunately introduced at a later point in time.
|
||||
// As such, we can only fail by returning an empty slice rather than
|
||||
// explicit error.
|
||||
cookieCount := 0
|
||||
for _, line := range lines {
|
||||
cookieCount += strings.Count(line, ";") + 1
|
||||
}
|
||||
if !cookieNumWithinMax(cookieCount) {
|
||||
return []*Cookie{}
|
||||
}
|
||||
|
||||
cookies := make([]*Cookie, 0, len(lines)+strings.Count(lines[0], ";"))
|
||||
for _, line := range lines {
|
||||
line = textproto.TrimString(line)
|
||||
@@ -395,7 +431,8 @@ func isCookieDomainName(s string) bool {
|
||||
}
|
||||
|
||||
if s[0] == '.' {
|
||||
// A cookie a domain attribute may start with a leading dot.
|
||||
// A cookie domain attribute may start with a leading dot.
|
||||
// Per RFC 6265 section 5.2.3, a leading dot is ignored.
|
||||
s = s[1:]
|
||||
}
|
||||
last := byte('.')
|
||||
@@ -460,9 +497,6 @@ func sanitizeCookieName(n string) string {
|
||||
// See https://golang.org/issue/7243 for the discussion.
|
||||
func sanitizeCookieValue(v string, quoted bool) string {
|
||||
v = sanitizeOrWarn("Cookie.Value", validCookieValueByte, v)
|
||||
if len(v) == 0 {
|
||||
return v
|
||||
}
|
||||
if strings.ContainsAny(v, " ,") || quoted {
|
||||
return `"` + v + `"`
|
||||
}
|
||||
@@ -470,7 +504,8 @@ func sanitizeCookieValue(v string, quoted bool) string {
|
||||
}
|
||||
|
||||
func validCookieValueByte(b byte) bool {
|
||||
return 0x20 <= b && b < 0x7f && b != '"' && b != ';' && b != '\\'
|
||||
// allow double quotes in cookie value
|
||||
return 0x20 <= b && b < 0x7f && /* b != '"' && */ b != ';' && b != '\\'
|
||||
}
|
||||
|
||||
// path-av = "Path=" path-value
|
||||
|
||||
+34
-1
@@ -11,6 +11,7 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -121,7 +122,7 @@ func (e *entry) id() string {
|
||||
// request to host/path. It is the caller's responsibility to check if the
|
||||
// cookie is expired.
|
||||
func (e *entry) shouldSend(https bool, host, path string) bool {
|
||||
return e.domainMatch(host) && e.pathMatch(path) && (https || !e.Secure)
|
||||
return e.domainMatch(host) && e.pathMatch(path) && e.secureMatch(https)
|
||||
}
|
||||
|
||||
// domainMatch checks whether e's Domain allows sending e back to host.
|
||||
@@ -149,6 +150,38 @@ func (e *entry) pathMatch(requestPath string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// secureMatch checks whether a cookie should be sent based on the protocol
|
||||
// and the Secure flag. Localhost is considered a secure origin regardless
|
||||
// of protocol, matching browser behavior.
|
||||
func (e *entry) secureMatch(https bool) bool {
|
||||
if !e.Secure {
|
||||
// Cookies not marked secure are always sent.
|
||||
return true
|
||||
}
|
||||
// Everything below is about cookies marked secure.
|
||||
if https {
|
||||
// HTTPS request matches secure cookies.
|
||||
return true
|
||||
}
|
||||
// Consider localhost to be secure like browsers.
|
||||
if isLocalhost(e.Domain) {
|
||||
return true
|
||||
}
|
||||
ip, err := netip.ParseAddr(e.Domain)
|
||||
if err == nil && ip.IsLoopback() {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isLocalhost(host string) bool {
|
||||
host = strings.TrimSuffix(host, ".")
|
||||
if idx := strings.LastIndex(host, "."); idx >= 0 {
|
||||
host = host[idx+1:]
|
||||
}
|
||||
return ascii.EqualFold(host, "localhost")
|
||||
}
|
||||
|
||||
// hasDotSuffix reports whether s ends in "."+suffix.
|
||||
func hasDotSuffix(s, suffix string) bool {
|
||||
return len(s) > len(suffix) && s[len(s)-len(suffix)-1] == '.' && s[len(s)-len(suffix):] == suffix
|
||||
|
||||
+4
@@ -90,6 +90,10 @@ var sentinelHandler Handler = &noopHandler{}
|
||||
// would redirect to a pattern (e.g. after cleaning the path or adding a
|
||||
// trailing slash) are not.
|
||||
//
|
||||
// AddInsecureBypassPattern panics if the pattern conflicts with one already
|
||||
// registered, or if the pattern is syntactically invalid (for example, an
|
||||
// improperly formed wildcard).
|
||||
//
|
||||
// AddInsecureBypassPattern can be called concurrently with other methods or
|
||||
// request handling, and applies to future requests.
|
||||
func (c *CrossOriginProtection) AddInsecureBypassPattern(pattern string) {
|
||||
|
||||
+15
-16
@@ -84,27 +84,26 @@ custom Server:
|
||||
|
||||
# HTTP/2
|
||||
|
||||
Starting with Go 1.6, the http package has transparent support for the
|
||||
HTTP/2 protocol when using HTTPS. Programs that must disable HTTP/2
|
||||
can do so by setting [Transport.TLSNextProto] (for clients) or
|
||||
[Server.TLSNextProto] (for servers) to a non-nil, empty
|
||||
map. Alternatively, the following GODEBUG settings are
|
||||
currently supported:
|
||||
The http package has transparent support for the HTTP/2 protocol.
|
||||
|
||||
[Server] and [DefaultTransport] automatically enable HTTP/2 support
|
||||
when using HTTPS. [Transport] does not enable HTTP/2 by default.
|
||||
|
||||
To enable or disable support for HTTP/1, HTTP/2, and/or unencrypted HTTP/2,
|
||||
see the [Server.Protocols] and [Transport.Protocols] configuration fields.
|
||||
|
||||
To configure advanced HTTP/2 features, see the [Server.HTTP2] and
|
||||
[Transport.HTTP2] configuration fields.
|
||||
|
||||
Alternatively, the following GODEBUG settings are currently supported:
|
||||
|
||||
GODEBUG=http2client=0 # disable HTTP/2 client support
|
||||
GODEBUG=http2server=0 # disable HTTP/2 server support
|
||||
GODEBUG=http2debug=1 # enable verbose HTTP/2 debug logs
|
||||
GODEBUG=http2debug=2 # ... even more verbose, with frame dumps
|
||||
|
||||
Please report any issues before disabling HTTP/2 support: https://golang.org/s/http2bug
|
||||
|
||||
The http package's [Transport] and [Server] both automatically enable
|
||||
HTTP/2 support for simple configurations. To enable HTTP/2 for more
|
||||
complex configurations, to use lower-level HTTP/2 features, or to use
|
||||
a newer version of Go's http2 package, import "golang.org/x/net/http2"
|
||||
directly and use its ConfigureTransport and/or ConfigureServer
|
||||
functions. Manually configuring HTTP/2 via the golang.org/x/net/http2
|
||||
package takes precedence over the net/http package's built-in HTTP/2
|
||||
support.
|
||||
The "omithttp2" build tag may be used to disable the HTTP/2 implementation
|
||||
contained in the http package.
|
||||
*/
|
||||
|
||||
package http
|
||||
+3
-2
@@ -57,7 +57,7 @@ func (t fileTransport) RoundTrip(req *Request) (resp *Response, err error) {
|
||||
// sends our *Response on, once the *Response itself has been
|
||||
// populated (even if the body itself is still being
|
||||
// written to the res.Body, a pipe)
|
||||
rw, resc := newPopulateResponseWriter()
|
||||
rw, resc := newPopulateResponseWriter(req)
|
||||
go func() {
|
||||
t.fh.ServeHTTP(rw, req)
|
||||
rw.finish()
|
||||
@@ -65,7 +65,7 @@ func (t fileTransport) RoundTrip(req *Request) (resp *Response, err error) {
|
||||
return <-resc, nil
|
||||
}
|
||||
|
||||
func newPopulateResponseWriter() (*populateResponse, <-chan *Response) {
|
||||
func newPopulateResponseWriter(req *Request) (*populateResponse, <-chan *Response) {
|
||||
pr, pw := io.Pipe()
|
||||
rw := &populateResponse{
|
||||
ch: make(chan *Response),
|
||||
@@ -76,6 +76,7 @@ func newPopulateResponseWriter() (*populateResponse, <-chan *Response) {
|
||||
Header: make(Header),
|
||||
Close: true,
|
||||
Body: pr,
|
||||
Request: req,
|
||||
},
|
||||
}
|
||||
return rw, rw.ch
|
||||
|
||||
+613
-118
File diff suppressed because it is too large.
Load diff
+15
-8
@@ -8,6 +8,7 @@ import (
|
||||
"io"
|
||||
"net/textproto"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -101,12 +102,12 @@ func (h Header) Del(key string) {
|
||||
}
|
||||
|
||||
// Write writes a header in wire format.
|
||||
func (h Header) Write(w io.Writer) error {
|
||||
return h.write(w, nil)
|
||||
func (h Header) Write(w io.Writer, cl int64) error {
|
||||
return h.write(w, nil, cl)
|
||||
}
|
||||
|
||||
func (h Header) write(w io.Writer, trace *httptrace.ClientTrace) error {
|
||||
return h.writeSubset(w, nil, trace)
|
||||
func (h Header) write(w io.Writer, trace *httptrace.ClientTrace, cl int64) error {
|
||||
return h.writeSubset(w, nil, trace, cl)
|
||||
}
|
||||
|
||||
// Clone returns a copy of h or nil if h is nil.
|
||||
@@ -255,13 +256,13 @@ func (h Header) SortedKeyValuesBy(order map[string]int, exclude map[string]bool)
|
||||
// WriteSubset writes a header in wire format.
|
||||
// If exclude is not nil, keys where exclude[key] == true are not written.
|
||||
// Keys are not canonicalized before checking the exclude map.
|
||||
func (h Header) WriteSubset(w io.Writer, exclude map[string]bool) error {
|
||||
return h.writeSubset(w, exclude, nil)
|
||||
func (h Header) WriteSubset(w io.Writer, exclude map[string]bool, cl int64) error {
|
||||
return h.writeSubset(w, exclude, nil, cl)
|
||||
}
|
||||
|
||||
// WriteSubset writes a header in wire format.
|
||||
// If exclude is not nil, keys where exclude[key] == true are not written.
|
||||
func (h Header) writeSubset(w io.Writer, exclude map[string]bool, trace *httptrace.ClientTrace) error {
|
||||
func (h Header) writeSubset(w io.Writer, exclude map[string]bool, trace *httptrace.ClientTrace, cl int64) error {
|
||||
ws, ok := w.(io.StringWriter)
|
||||
if !ok {
|
||||
ws = stringWriter{w}
|
||||
@@ -275,6 +276,12 @@ func (h Header) writeSubset(w io.Writer, exclude map[string]bool, trace *httptra
|
||||
order := make(map[string]int)
|
||||
for i, v := range headerOrder {
|
||||
order[v] = i
|
||||
|
||||
// If content-length is set in the header order,
|
||||
// we should add the value so it gets sorted
|
||||
if v == "content-length" && cl >= 0 {
|
||||
h[v] = []string{strconv.FormatInt(cl, 10)}
|
||||
}
|
||||
}
|
||||
|
||||
if exclude == nil {
|
||||
@@ -303,7 +310,7 @@ func (h Header) writeSubset(w io.Writer, exclude map[string]bool, trace *httptra
|
||||
for _, v := range kv.Values {
|
||||
v = headerNewlineToSpace.Replace(v)
|
||||
v = textproto.TrimString(v)
|
||||
for _, s := range []string{kv.Key, ": ", v, "\r\n"} {
|
||||
for _, s := range []string{CanonicalHeaderKey(kv.Key), ": ", v, "\r\n"} {
|
||||
if _, err := ws.WriteString(s); err != nil {
|
||||
headerSorterPool.Put(sorter)
|
||||
return err
|
||||
|
||||
+14
-5
@@ -119,10 +119,6 @@ func removeEmptyPort(host string) string {
|
||||
return host
|
||||
}
|
||||
|
||||
func isNotToken(r rune) bool {
|
||||
return !httpguts.IsTokenRune(r)
|
||||
}
|
||||
|
||||
// isToken reports whether v is a valid token (https://www.rfc-editor.org/rfc/rfc2616#section-2.2).
|
||||
func isToken(v string) bool {
|
||||
// For historical reasons, this function is called ValidHeaderFieldName (see issue #67031).
|
||||
@@ -235,10 +231,23 @@ type Pusher interface {
|
||||
// both [Transport] and [Server].
|
||||
type HTTP2Config struct {
|
||||
// MaxConcurrentStreams optionally specifies the number of
|
||||
// concurrent streams that a peer may have open at a time.
|
||||
// concurrent streams that a client may have open at a time.
|
||||
// If zero, MaxConcurrentStreams defaults to at least 100.
|
||||
//
|
||||
// This parameter only applies to Servers.
|
||||
MaxConcurrentStreams int
|
||||
|
||||
// StrictMaxConcurrentRequests controls whether an HTTP/2 server's
|
||||
// concurrency limit should be respected across all connections
|
||||
// to that server.
|
||||
// If true, new requests sent when a connection's concurrency limit
|
||||
// has been exceeded will block until an existing request completes.
|
||||
// If false, an additional connection will be opened if all
|
||||
// existing connections are at their limit.
|
||||
//
|
||||
// This parameter only applies to Transports.
|
||||
StrictMaxConcurrentRequests bool
|
||||
|
||||
// MaxDecoderHeaderTableSize optionally specifies an upper limit for the
|
||||
// size of the header compression table used for decoding headers sent
|
||||
// by the peer.
|
||||
|
||||
+1
-1
@@ -77,7 +77,7 @@ func WithClientTrace(ctx context.Context, trace *ClientTrace) context.Context {
|
||||
// during a single round trip and has no hooks that span a series
|
||||
// of redirected requests.
|
||||
//
|
||||
// See https://blog.golang.org/http-tracing for more.
|
||||
// See https://go.dev/blog/http-tracing for more.
|
||||
type ClientTrace struct {
|
||||
// GetConn is called before a connection is created or
|
||||
// retrieved from an idle pool. The hostPort is the
|
||||
|
||||
+1
-2
@@ -148,7 +148,6 @@ func DumpRequestOut(req *http.Request, body bool) ([]byte, error) {
|
||||
|
||||
req.Body = save
|
||||
if err != nil {
|
||||
pw.Close()
|
||||
dr.err = err
|
||||
close(quitReadCh)
|
||||
return nil, err
|
||||
@@ -260,7 +259,7 @@ func DumpRequest(req *http.Request, body bool) ([]byte, error) {
|
||||
fmt.Fprintf(&b, "Transfer-Encoding: %s\r\n", strings.Join(req.TransferEncoding, ","))
|
||||
}
|
||||
|
||||
err = req.Header.WriteSubset(&b, reqWriteExcludeHeaderDump)
|
||||
err = req.Header.WriteSubset(&b, reqWriteExcludeHeaderDump, -1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
+116
-30
@@ -18,6 +18,7 @@ import (
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/enetx/http"
|
||||
@@ -134,36 +135,6 @@ type ReverseProxy struct {
|
||||
// At most one of Rewrite or Director may be set.
|
||||
Rewrite func(*ProxyRequest)
|
||||
|
||||
// Director is a function which modifies
|
||||
// the request into a new request to be sent
|
||||
// using Transport. Its response is then copied
|
||||
// back to the original client unmodified.
|
||||
// Director must not access the provided Request
|
||||
// after returning.
|
||||
//
|
||||
// By default, the X-Forwarded-For header is set to the
|
||||
// value of the client IP address. If an X-Forwarded-For
|
||||
// header already exists, the client IP is appended to the
|
||||
// existing values. As a special case, if the header
|
||||
// exists in the Request.Header map but has a nil value
|
||||
// (such as when set by the Director func), the X-Forwarded-For
|
||||
// header is not modified.
|
||||
//
|
||||
// To prevent IP spoofing, be sure to delete any pre-existing
|
||||
// X-Forwarded-For header coming from the client or
|
||||
// an untrusted proxy.
|
||||
//
|
||||
// Hop-by-hop headers are removed from the request after
|
||||
// Director returns, which can remove headers added by
|
||||
// Director. Use a Rewrite function instead to ensure
|
||||
// modifications to the request are preserved.
|
||||
//
|
||||
// Unparsable query parameters are removed from the outbound
|
||||
// request if Request.Form is set after Director returns.
|
||||
//
|
||||
// At most one of Rewrite or Director may be set.
|
||||
Director func(*http.Request)
|
||||
|
||||
// The transport used to perform proxy requests.
|
||||
// If nil, http.DefaultTransport is used.
|
||||
Transport http.RoundTripper
|
||||
@@ -211,6 +182,88 @@ type ReverseProxy struct {
|
||||
// If nil, the default is to log the provided error and return
|
||||
// a 502 Status Bad Gateway response.
|
||||
ErrorHandler func(http.ResponseWriter, *http.Request, error)
|
||||
|
||||
// Director is deprecated. Use Rewrite instead.
|
||||
//
|
||||
// This function is insecure:
|
||||
//
|
||||
// - Hop-by-hop headers are removed from the request after Director
|
||||
// returns, which can remove headers added by Director.
|
||||
// A client can designate headers as hop-by-hop by listing them
|
||||
// in the Connection header, so this permits a malicious client
|
||||
// to remove any headers that may be added by Director.
|
||||
//
|
||||
// - X-Forwarded-For, X-Forwarded-Host, and X-Forwarded-Proto
|
||||
// headers in inbound requests are preserved by default,
|
||||
// which can permit IP spoofing if the Director function is
|
||||
// not careful to remove these headers.
|
||||
//
|
||||
// Rewrite addresses these issues.
|
||||
//
|
||||
// As an example of converting a Director function to Rewrite:
|
||||
//
|
||||
// // ReverseProxy with a Director function.
|
||||
// proxy := &httputil.ReverseProxy{
|
||||
// Director: func(req *http.Request) {
|
||||
// req.URL.Scheme = "https"
|
||||
// req.URL.Host = proxyHost
|
||||
//
|
||||
// // A malicious client can remove this header.
|
||||
// req.Header.Set("Some-Header", "some-header-value")
|
||||
//
|
||||
// // X-Forwarded-* headers sent by the client are preserved,
|
||||
// // since Director did not remove them.
|
||||
// },
|
||||
// }
|
||||
//
|
||||
// // ReverseProxy with a Rewrite function.
|
||||
// proxy := &httputil.ReverseProxy{
|
||||
// Rewrite: func(preq *httputil.ProxyRequest) {
|
||||
// // See also ProxyRequest.SetURL.
|
||||
// preq.Out.URL.Scheme = "https"
|
||||
// preq.Out.URL.Host = proxyHost
|
||||
//
|
||||
// // This header cannot be affected by a malicious client.
|
||||
// preq.Out.Header.Set("Some-Header", "some-header-value")
|
||||
//
|
||||
// // X-Forwarded- headers sent by the client have been
|
||||
// // removed from preq.Out.
|
||||
// // ProxyRequest.SetXForwarded optionally adds new ones.
|
||||
// preq.SetXForwarded()
|
||||
// },
|
||||
// }
|
||||
//
|
||||
// Director is a function which modifies
|
||||
// the request into a new request to be sent
|
||||
// using Transport. Its response is then copied
|
||||
// back to the original client unmodified.
|
||||
// Director must not access the provided Request
|
||||
// after returning.
|
||||
//
|
||||
// By default, the X-Forwarded-For header is set to the
|
||||
// value of the client IP address. If an X-Forwarded-For
|
||||
// header already exists, the client IP is appended to the
|
||||
// existing values. As a special case, if the header
|
||||
// exists in the Request.Header map but has a nil value
|
||||
// (such as when set by the Director func), the X-Forwarded-For
|
||||
// header is not modified.
|
||||
//
|
||||
// To prevent IP spoofing, be sure to delete any pre-existing
|
||||
// X-Forwarded-For header coming from the client or
|
||||
// an untrusted proxy.
|
||||
//
|
||||
// Hop-by-hop headers are removed from the request after
|
||||
// Director returns, which can remove headers added by
|
||||
// Director. Use a Rewrite function instead to ensure
|
||||
// modifications to the request are preserved.
|
||||
//
|
||||
// Unparsable query parameters are removed from the outbound
|
||||
// request if Request.Form is set after Director returns.
|
||||
//
|
||||
// At most one of Rewrite or Director may be set.
|
||||
//
|
||||
// Deprecated: Use Rewrite instead.
|
||||
Director func(*http.Request)
|
||||
}
|
||||
|
||||
// A BufferPool is an interface for getting and returning temporary
|
||||
@@ -260,6 +313,10 @@ func joinURLPath(a, b *url.URL) (path, rawpath string) {
|
||||
//
|
||||
// NewSingleHostReverseProxy does not rewrite the Host header.
|
||||
//
|
||||
// For backwards compatibility reasons, NewSingleHostReverseProxy
|
||||
// returns a ReverseProxy using the deprecated Director function.
|
||||
// This proxy preserves X-Forwarded-* headers sent by the client.
|
||||
//
|
||||
// To customize the ReverseProxy behavior beyond what
|
||||
// NewSingleHostReverseProxy provides, use ReverseProxy directly
|
||||
// with a Rewrite function. The ProxyRequest SetURL method
|
||||
@@ -380,6 +437,18 @@ func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
outreq.Body = nil // Issue 16036: nil Body for http.Transport retries
|
||||
}
|
||||
if outreq.Body != nil {
|
||||
// Wrap the body in a reader where Close does nothing. This is done
|
||||
// because p.Transport.RoundTrip would close the reverse proxy's
|
||||
// outbound request body if it fails to connect to upstream. If we do
|
||||
// not wrap the body, when we close the reverse proxy's outbound
|
||||
// request, it will also close the reverse proxy's inbound request body
|
||||
// (i.e. the client's outbound request body). This is because
|
||||
// http.(*Request).Clone creates a shallow copy of the body. This can
|
||||
// cause an infinite hang in cases where the body is not yet received
|
||||
// from the client (e.g. 100-continue requests): Close, which
|
||||
// internally tries to consume the body content, would be called too
|
||||
// early and would hang.
|
||||
outreq.Body = &noopCloseReader{readCloser: outreq.Body}
|
||||
// Reading from the request body after returning from a handler is not
|
||||
// allowed, and the RoundTrip goroutine that reads the Body can outlive
|
||||
// this handler. This can lead to a crash if the handler panics (see
|
||||
@@ -886,3 +955,20 @@ func ishex(c byte) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type noopCloseReader struct {
|
||||
readCloser io.ReadCloser
|
||||
closed atomic.Bool
|
||||
}
|
||||
|
||||
func (ncr *noopCloseReader) Close() error {
|
||||
ncr.closed.Store(true)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ncr *noopCloseReader) Read(p []byte) (int, error) {
|
||||
if ncr.closed.Load() {
|
||||
return 0, errors.New("ReverseProxy does an invalid Read on closed Body")
|
||||
}
|
||||
return ncr.readCloser.Read(p)
|
||||
}
|
||||
-8
@@ -394,14 +394,6 @@ func inverseRelationship(r relationship) relationship {
|
||||
}
|
||||
}
|
||||
|
||||
// isLitOrSingle reports whether the segment is a non-dollar literal or a single wildcard.
|
||||
func isLitOrSingle(seg segment) bool {
|
||||
if seg.wild {
|
||||
return !seg.multi
|
||||
}
|
||||
return seg.s != "/"
|
||||
}
|
||||
|
||||
// describeConflict returns an explanation of why two patterns conflict.
|
||||
func describeConflict(p1, p2 *pattern) string {
|
||||
mrel := p1.compareMethods(p2)
|
||||
|
||||
+23
-11
@@ -22,6 +22,7 @@ import (
|
||||
"net/url"
|
||||
urlpkg "net/url"
|
||||
"strconv"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
_ "unsafe" // for linkname
|
||||
@@ -677,12 +678,18 @@ func (r *Request) write(w io.Writer, usingProxy bool, extraHeaders Header, waitF
|
||||
}
|
||||
|
||||
// Header lines
|
||||
_, err = fmt.Fprintf(w, "Host: %s\r\n", host)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if trace != nil && trace.WroteHeaderField != nil {
|
||||
trace.WroteHeaderField("Host", []string{host})
|
||||
// Write Host immediately unless it's in HeaderOrder (will be written in sorted order by writeSubset)
|
||||
headerOrder, hoexist := r.Header[HeaderOrderKey]
|
||||
hostInOrder := hoexist && slices.Contains(headerOrder, "host")
|
||||
|
||||
if !hostInOrder {
|
||||
_, err = fmt.Fprintf(w, "Host: %s\r\n", host)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if trace != nil && trace.WroteHeaderField != nil {
|
||||
trace.WroteHeaderField("Host", []string{host})
|
||||
}
|
||||
}
|
||||
|
||||
// Use the defaultUserAgent unless the Header contains one, which
|
||||
@@ -710,13 +717,18 @@ func (r *Request) write(w io.Writer, usingProxy bool, extraHeaders Header, waitF
|
||||
return err
|
||||
}
|
||||
|
||||
err = r.Header.writeSubset(w, reqWriteExcludeHeader, trace)
|
||||
// If host is in HeaderOrder, add its value to headers so writeSubset can write it in order
|
||||
if hostInOrder {
|
||||
r.Header["host"] = []string{host}
|
||||
}
|
||||
|
||||
err = r.Header.writeSubset(w, reqWriteExcludeHeader, trace, tw.ContentLength)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if extraHeaders != nil {
|
||||
err = extraHeaders.write(w, trace)
|
||||
err = extraHeaders.write(w, trace, tw.ContentLength)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1261,7 +1273,7 @@ func copyValues(dst, src url.Values) {
|
||||
func parsePostForm(r *Request) (vs url.Values, err error) {
|
||||
if r.Body == nil {
|
||||
err = errors.New("missing form body")
|
||||
return
|
||||
return vs, err
|
||||
}
|
||||
ct := r.Header.Get("Content-Type")
|
||||
// RFC 7231, section 3.1.1.5 - empty type
|
||||
@@ -1287,7 +1299,7 @@ func parsePostForm(r *Request) (vs url.Values, err error) {
|
||||
}
|
||||
if int64(len(b)) > maxFormSize {
|
||||
err = errors.New("http: POST too large")
|
||||
return
|
||||
return vs, err
|
||||
}
|
||||
vs, e = url.ParseQuery(string(b))
|
||||
if err == nil {
|
||||
@@ -1301,7 +1313,7 @@ func parsePostForm(r *Request) (vs url.Values, err error) {
|
||||
// request_test.go contains the start of this,
|
||||
// in TestParseMultipartFormOrder and others.
|
||||
}
|
||||
return
|
||||
return vs, err
|
||||
}
|
||||
|
||||
// ParseForm populates r.Form and r.PostForm.
|
||||
|
||||
+2
-1
@@ -304,7 +304,7 @@ func (r *Response) Write(w io.Writer) error {
|
||||
}
|
||||
|
||||
// Rest of header
|
||||
err = r.Header.WriteSubset(w, respExcludeHeader)
|
||||
err = r.Header.WriteSubset(w, respExcludeHeader, r1.ContentLength)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -313,6 +313,7 @@ func (r *Response) Write(w io.Writer) error {
|
||||
// POST/PUT requests, even if zero length. See Issue 8180.
|
||||
contentLengthAlreadySent := tw.shouldSendContentLength()
|
||||
if r1.ContentLength == 0 && !chunked(r1.TransferEncoding) && !contentLengthAlreadySent && bodyAllowedForStatus(r.StatusCode) {
|
||||
// TODO: ??? _, ok := t.Header[HeaderOrderKey]
|
||||
if _, err := io.WriteString(w, "Content-Length: 0\r\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+7
@@ -196,6 +196,13 @@ func (t *Transport) RoundTrip(req *Request) (*Response, error) {
|
||||
uncompressed = true
|
||||
}
|
||||
|
||||
if result.Get("redirected").Bool() {
|
||||
u, err := url.Parse(result.Get("url").String())
|
||||
if err == nil {
|
||||
req = req.Clone(req.ctx)
|
||||
req.URL = u
|
||||
}
|
||||
}
|
||||
respCh <- &Response{
|
||||
Status: fmt.Sprintf("%d %s", code, StatusText(code)),
|
||||
StatusCode: code,
|
||||
|
||||
+36
-54
@@ -413,7 +413,7 @@ func (cw *chunkWriter) close() {
|
||||
// zero chunk to mark EOF
|
||||
bw.WriteString("0\r\n")
|
||||
if trailers := cw.res.finalTrailers(); trailers != nil {
|
||||
trailers.Write(bw) // the writer handles noting errors
|
||||
trailers.Write(bw, -1) // the writer handles noting errors
|
||||
}
|
||||
// final blank line after the trailers (whether
|
||||
// present or not)
|
||||
@@ -855,15 +855,6 @@ func bufioWriterPool(size int) *sync.Pool {
|
||||
return nil
|
||||
}
|
||||
|
||||
// newBufioReader should be an internal detail,
|
||||
// but widely used packages access it using linkname.
|
||||
// Notable members of the hall of shame include:
|
||||
// - github.com/gobwas/ws
|
||||
//
|
||||
// Do not remove or change the type signature.
|
||||
// See go.dev/issue/67401.
|
||||
//
|
||||
//go:linkname newBufioReader
|
||||
func newBufioReader(r io.Reader) *bufio.Reader {
|
||||
if v := bufioReaderPool.Get(); v != nil {
|
||||
br := v.(*bufio.Reader)
|
||||
@@ -875,29 +866,11 @@ func newBufioReader(r io.Reader) *bufio.Reader {
|
||||
return bufio.NewReader(r)
|
||||
}
|
||||
|
||||
// putBufioReader should be an internal detail,
|
||||
// but widely used packages access it using linkname.
|
||||
// Notable members of the hall of shame include:
|
||||
// - github.com/gobwas/ws
|
||||
//
|
||||
// Do not remove or change the type signature.
|
||||
// See go.dev/issue/67401.
|
||||
//
|
||||
//go:linkname putBufioReader
|
||||
func putBufioReader(br *bufio.Reader) {
|
||||
br.Reset(nil)
|
||||
bufioReaderPool.Put(br)
|
||||
}
|
||||
|
||||
// newBufioWriterSize should be an internal detail,
|
||||
// but widely used packages access it using linkname.
|
||||
// Notable members of the hall of shame include:
|
||||
// - github.com/gobwas/ws
|
||||
//
|
||||
// Do not remove or change the type signature.
|
||||
// See go.dev/issue/67401.
|
||||
//
|
||||
//go:linkname newBufioWriterSize
|
||||
func newBufioWriterSize(w io.Writer, size int) *bufio.Writer {
|
||||
pool := bufioWriterPool(size)
|
||||
if pool != nil {
|
||||
@@ -910,15 +883,6 @@ func newBufioWriterSize(w io.Writer, size int) *bufio.Writer {
|
||||
return bufio.NewWriterSize(w, size)
|
||||
}
|
||||
|
||||
// putBufioWriter should be an internal detail,
|
||||
// but widely used packages access it using linkname.
|
||||
// Notable members of the hall of shame include:
|
||||
// - github.com/gobwas/ws
|
||||
//
|
||||
// Do not remove or change the type signature.
|
||||
// See go.dev/issue/67401.
|
||||
//
|
||||
//go:linkname putBufioWriter
|
||||
func putBufioWriter(bw *bufio.Writer) {
|
||||
bw.Reset(nil)
|
||||
if pool := bufioWriterPool(bw.Available()); pool != nil {
|
||||
@@ -1215,7 +1179,7 @@ func (w *response) WriteHeader(code int) {
|
||||
writeStatusLine(w.conn.bufw, w.req.ProtoAtLeast(1, 1), code, w.statusBuf[:])
|
||||
|
||||
// Per RFC 8297 we must not clear the current header map
|
||||
w.handlerHeader.WriteSubset(w.conn.bufw, excludedHeadersNoBody)
|
||||
w.handlerHeader.WriteSubset(w.conn.bufw, excludedHeadersNoBody, -1)
|
||||
w.conn.bufw.Write(crlf)
|
||||
w.conn.bufw.Flush()
|
||||
|
||||
@@ -1572,7 +1536,7 @@ func (cw *chunkWriter) writeHeader(p []byte) {
|
||||
}
|
||||
|
||||
writeStatusLine(w.conn.bufw, w.req.ProtoAtLeast(1, 1), code, w.statusBuf[:])
|
||||
cw.header.WriteSubset(w.conn.bufw, excludeHeader)
|
||||
cw.header.WriteSubset(w.conn.bufw, excludeHeader, -1)
|
||||
setHeader.Write(w.conn.bufw)
|
||||
w.conn.bufw.Write(crlf)
|
||||
}
|
||||
@@ -1620,7 +1584,7 @@ func writeStatusLine(bw *bufio.Writer, is11 bool, code int, scratch []byte) {
|
||||
// It's illegal to call this before the header has been flushed.
|
||||
func (w *response) bodyAllowed() bool {
|
||||
if !w.wroteHeader {
|
||||
panic("")
|
||||
panic("net/http: bodyAllowed called before the header was written")
|
||||
}
|
||||
return bodyAllowedForStatus(w.status)
|
||||
}
|
||||
@@ -2414,7 +2378,7 @@ func Redirect(w ResponseWriter, r *Request, url string, code int) {
|
||||
// but doing it ourselves is more reliable.
|
||||
// See RFC 7231, section 7.1.2
|
||||
if u.Scheme == "" && u.Host == "" {
|
||||
oldpath := r.URL.Path
|
||||
oldpath := r.URL.EscapedPath()
|
||||
if oldpath == "" { // should not happen, but avoid a crash if it does
|
||||
oldpath = "/"
|
||||
}
|
||||
@@ -2710,7 +2674,7 @@ func (mux *ServeMux) findHandler(r *Request) (h Handler, patStr string, _ *patte
|
||||
// but the path canonicalization does not.
|
||||
_, _, u := mux.matchOrRedirect(host, r.Method, path, r.URL)
|
||||
if u != nil {
|
||||
return RedirectHandler(u.String(), StatusMovedPermanently), u.Path, nil, nil
|
||||
return RedirectHandler(u.String(), StatusTemporaryRedirect), u.Path, nil, nil
|
||||
}
|
||||
// Redo the match, this time with r.Host instead of r.URL.Host.
|
||||
// Pass a nil URL to skip the trailing-slash redirect logic.
|
||||
@@ -2726,7 +2690,7 @@ func (mux *ServeMux) findHandler(r *Request) (h Handler, patStr string, _ *patte
|
||||
var u *url.URL
|
||||
n, matches, u = mux.matchOrRedirect(host, r.Method, path, r.URL)
|
||||
if u != nil {
|
||||
return RedirectHandler(u.String(), StatusMovedPermanently), n.pattern.String(), nil, nil
|
||||
return RedirectHandler(u.String(), StatusTemporaryRedirect), n.pattern.String(), nil, nil
|
||||
}
|
||||
if path != escapedPath {
|
||||
// Redirect to cleaned path.
|
||||
@@ -2735,7 +2699,7 @@ func (mux *ServeMux) findHandler(r *Request) (h Handler, patStr string, _ *patte
|
||||
patStr = n.pattern.String()
|
||||
}
|
||||
u := &url.URL{Path: path, RawQuery: r.URL.RawQuery}
|
||||
return RedirectHandler(u.String(), StatusMovedPermanently), patStr, nil, nil
|
||||
return RedirectHandler(u.String(), StatusTemporaryRedirect), patStr, nil, nil
|
||||
}
|
||||
}
|
||||
if n == nil {
|
||||
@@ -2765,9 +2729,12 @@ func (mux *ServeMux) matchOrRedirect(host, method, path string, u *url.URL) (_ *
|
||||
defer mux.mu.RUnlock()
|
||||
|
||||
n, matches := mux.tree.match(host, method, path)
|
||||
// If we have an exact match, or we were asked not to try trailing-slash redirection,
|
||||
// or the URL already has a trailing slash, then we're done.
|
||||
if !exactMatch(n, path) && u != nil && !strings.HasSuffix(path, "/") {
|
||||
// We can terminate here if any of the following is true:
|
||||
// - We have an exact match already.
|
||||
// - We were asked not to try trailing slash redirection.
|
||||
// - The URL already has a trailing slash.
|
||||
// - The URL is an empty string.
|
||||
if !exactMatch(n, path) && u != nil && !strings.HasSuffix(path, "/") && path != "" {
|
||||
// If there is an exact match with a trailing slash, then redirect.
|
||||
path += "/"
|
||||
n2, _ := mux.tree.match(host, method, path)
|
||||
@@ -2871,8 +2838,10 @@ func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
|
||||
// always refers to user code.
|
||||
|
||||
// Handle registers the handler for the given pattern.
|
||||
// If the given pattern conflicts with one that is already registered, Handle
|
||||
// panics.
|
||||
// If the given pattern conflicts with one that is already registered
|
||||
// or if the pattern is invalid, Handle panics.
|
||||
//
|
||||
// See [ServeMux] for details on valid patterns and conflict rules.
|
||||
func (mux *ServeMux) Handle(pattern string, handler Handler) {
|
||||
if use121 {
|
||||
mux.mux121.handle(pattern, handler)
|
||||
@@ -2882,8 +2851,10 @@ func (mux *ServeMux) Handle(pattern string, handler Handler) {
|
||||
}
|
||||
|
||||
// HandleFunc registers the handler function for the given pattern.
|
||||
// If the given pattern conflicts with one that is already registered, HandleFunc
|
||||
// panics.
|
||||
// If the given pattern conflicts with one that is already registered
|
||||
// or if the pattern is invalid, HandleFunc panics.
|
||||
//
|
||||
// See [ServeMux] for details on valid patterns and conflict rules.
|
||||
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
|
||||
if use121 {
|
||||
mux.mux121.handleFunc(pattern, handler)
|
||||
@@ -3065,6 +3036,9 @@ type Server struct {
|
||||
// automatically closed when the function returns.
|
||||
// If TLSNextProto is not nil, HTTP/2 support is not enabled
|
||||
// automatically.
|
||||
//
|
||||
// Historically, TLSNextProto was used to disable HTTP/2 support.
|
||||
// The Server.Protocols field now provides a simpler way to do this.
|
||||
TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
|
||||
|
||||
// ConnState specifies an optional callback function that is
|
||||
@@ -3093,9 +3067,6 @@ type Server struct {
|
||||
ConnContext func(ctx context.Context, c net.Conn) context.Context
|
||||
|
||||
// HTTP2 configures HTTP/2 connections.
|
||||
//
|
||||
// This field does not yet have any effect.
|
||||
// See https://go.dev/issue/67813.
|
||||
HTTP2 *HTTP2Config
|
||||
|
||||
// Protocols is the set of protocols accepted by the server.
|
||||
@@ -3109,6 +3080,17 @@ type Server struct {
|
||||
// the default is HTTP/1 only.
|
||||
Protocols *Protocols
|
||||
|
||||
// DisableClientPriority specifies whether client-specified priority, as
|
||||
// specified in RFC 9218, should be respected or not.
|
||||
//
|
||||
// This field only takes effect if using HTTP/2, and if no custom write
|
||||
// scheduler is defined for the HTTP/2 server. Otherwise, this field is a
|
||||
// no-op.
|
||||
//
|
||||
// If set to true, requests will be served in a round-robin manner, without
|
||||
// prioritization.
|
||||
DisableClientPriority bool
|
||||
|
||||
inShutdown atomic.Bool // true when server is in shutdown
|
||||
|
||||
disableKeepAlives atomic.Bool
|
||||
|
||||
+1
-1
@@ -453,7 +453,7 @@ func (up *socksUsernamePassword) Authenticate(ctx context.Context, rw io.ReadWri
|
||||
b = append(b, up.Username...)
|
||||
b = append(b, byte(len(up.Password)))
|
||||
b = append(b, up.Password...)
|
||||
// TODO(mikio): handle IO deadlines and cancelation if
|
||||
// TODO(mikio): handle IO deadlines and cancellation if
|
||||
// necessary
|
||||
if _, err := rw.Write(b); err != nil {
|
||||
return err
|
||||
|
||||
+30
-18
@@ -10,8 +10,8 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"maps"
|
||||
"net/textproto"
|
||||
"maps"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strconv"
|
||||
@@ -289,21 +289,33 @@ func (t *transferWriter) writeHeader(w io.Writer, trace *httptrace.ClientTrace)
|
||||
// function of the sanitized field triple (Body, ContentLength,
|
||||
// TransferEncoding)
|
||||
if t.shouldSendContentLength() {
|
||||
if _, err := io.WriteString(w, "Content-Length: "); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := io.WriteString(w, strconv.FormatInt(t.ContentLength, 10)+"\r\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
if trace != nil && trace.WroteHeaderField != nil {
|
||||
trace.WroteHeaderField("Content-Length", []string{strconv.FormatInt(t.ContentLength, 10)})
|
||||
headers, hoexist := t.Header[HeaderOrderKey]
|
||||
clexist := slices.Contains(headers, "content-length")
|
||||
|
||||
if !hoexist || !clexist {
|
||||
if _, err := io.WriteString(w, "Content-Length: "); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := io.WriteString(w, strconv.FormatInt(t.ContentLength, 10)+"\r\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
if trace != nil && trace.WroteHeaderField != nil {
|
||||
trace.WroteHeaderField("Content-Length", []string{strconv.FormatInt(t.ContentLength, 10)})
|
||||
}
|
||||
}
|
||||
} else if chunked(t.TransferEncoding) {
|
||||
if _, err := io.WriteString(w, "Transfer-Encoding: chunked\r\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
if trace != nil && trace.WroteHeaderField != nil {
|
||||
trace.WroteHeaderField("Transfer-Encoding", []string{"chunked"})
|
||||
headers, hoexist := t.Header[HeaderOrderKey]
|
||||
texist := slices.Contains(headers, "transfer-encoding")
|
||||
|
||||
if !hoexist || !texist {
|
||||
if _, err := io.WriteString(w, "Transfer-Encoding: chunked\r\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
if trace != nil && trace.WroteHeaderField != nil {
|
||||
trace.WroteHeaderField("Transfer-Encoding", []string{"chunked"})
|
||||
}
|
||||
} else {
|
||||
t.Header["transfer-encoding"] = []string{"chunked"}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -396,7 +408,7 @@ func (t *transferWriter) writeBody(w io.Writer) (err error) {
|
||||
if !t.ResponseToHEAD && chunked(t.TransferEncoding) {
|
||||
// Write Trailer header
|
||||
if t.Trailer != nil {
|
||||
if err := t.Trailer.Write(w); err != nil {
|
||||
if err := t.Trailer.Write(w, t.ContentLength); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -1086,9 +1098,9 @@ func (fr finishAsyncByteRead) Read(p []byte) (n int, err error) {
|
||||
|
||||
var nopCloserType = reflect.TypeOf(io.NopCloser(nil))
|
||||
var nopCloserWriterToType = reflect.TypeOf(io.NopCloser(struct {
|
||||
io.Reader
|
||||
io.WriterTo
|
||||
}{}))
|
||||
io.Reader
|
||||
io.WriterTo
|
||||
}{}))
|
||||
|
||||
// unwrapNopCloser return the underlying reader and true if r is a NopCloser
|
||||
// else it return false.
|
||||
|
||||
Loaded 100 of 845 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user