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:
RonniSkansing committed 2026-09-16 23:11:07 +02:00
1 parent 59cd31de17
commit 98ad0bdf4a
845 files changed
+12486 -157341

No files matched your search

+2 -2
View File
@@ -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"
+2 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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=
+8 -4
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
[![Stability:
Active](https://masterminds.github.io/stability/active.svg)](https://masterminds.github.io/stability/active.html)
[![](https://github.com/Masterminds/semver/workflows/Tests/badge.svg)](https://github.com/Masterminds/semver/actions)
[![GoDoc](https://img.shields.io/static/v1?label=godoc&message=reference&color=blue)](https://pkg.go.dev/github.com/Masterminds/semver/v3)
[![Go Report Card](https://goreportcard.com/badge/github.com/Masterminds/semver)](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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -1,5 +1,4 @@
profile.cov
string_crypt.go
pool_ch.go_
archive/
tests/string_crypt_test.go
+869 -180
View File
File diff suppressed because it is too large. Load diff
+332 -100
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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)
}
@@ -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)
@@ -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
View File
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because it is too large. Load diff
-1062
View File
File diff suppressed because it is too large. Load diff
-713
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because it is too large. Load diff
+15 -8
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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