Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f8ead2131 | ||
|
|
5694d6c387 | ||
|
|
a8d9c3984e |
@@ -1 +0,0 @@
|
||||
vendor/** linguist-generated=true
|
||||
@@ -19,5 +19,6 @@ var (
|
||||
Minor bool
|
||||
NoDomainChecks bool
|
||||
Patch bool
|
||||
PinDigests bool
|
||||
ShowUnchanged bool
|
||||
)
|
||||
|
||||
+29
-2
@@ -293,12 +293,31 @@ interface.`),
|
||||
}
|
||||
}
|
||||
if upgradeTag != "skip" {
|
||||
ok, err := recipe.UpdateTag(image, upgradeTag)
|
||||
var ok bool
|
||||
var err error
|
||||
var resolvedDigest string
|
||||
|
||||
if internal.PinDigests {
|
||||
digest, dErr := client.GetImageDigest(img, upgradeTag)
|
||||
if dErr != nil {
|
||||
log.Fatal(i18n.G("failed to resolve digest for %s:%s: %s", image, upgradeTag, dErr.Error()))
|
||||
}
|
||||
resolvedDigest = digest
|
||||
|
||||
ok, err = recipe.UpdatePinnedTag(image, upgradeTag, digest)
|
||||
} else {
|
||||
ok, err = recipe.UpdateTag(image, upgradeTag)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if ok {
|
||||
log.Info(i18n.G("tag upgraded from %s to %s for %s", tag.String(), upgradeTag, image))
|
||||
if internal.PinDigests {
|
||||
log.Info(i18n.G("tag upgraded and pinned from %s to %s@%s for %s", tag.String(), upgradeTag, resolvedDigest, image))
|
||||
} else {
|
||||
log.Info(i18n.G("tag upgraded from %s to %s for %s", tag.String(), upgradeTag, image))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if !internal.NoInput {
|
||||
@@ -417,4 +436,12 @@ func init() {
|
||||
false,
|
||||
i18n.G("commit changes"),
|
||||
)
|
||||
|
||||
RecipeUpgradeCommand.Flags().BoolVarP(
|
||||
&internal.PinDigests,
|
||||
i18n.G("pindigests"),
|
||||
i18n.GC("p", "pin container image by digest"),
|
||||
false,
|
||||
i18n.G("pin the container image version by manifest digest (image:tag@sha256:...)"),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module coopcloud.tech/abra
|
||||
|
||||
go 1.27
|
||||
go 1.26.0
|
||||
|
||||
require (
|
||||
coopcloud.tech/tagcmp v0.0.0-20260515102403-c26951b55977
|
||||
@@ -9,24 +9,19 @@ require (
|
||||
github.com/charmbracelet/bubbletea v1.3.10
|
||||
github.com/charmbracelet/lipgloss v1.1.0
|
||||
github.com/charmbracelet/log v1.0.0
|
||||
github.com/containers/image v3.0.2+incompatible
|
||||
github.com/decentral1se/passgen v1.0.1
|
||||
github.com/distribution/reference v0.6.0
|
||||
github.com/docker/cli v28.4.0+incompatible
|
||||
github.com/docker/docker v28.5.2+incompatible
|
||||
github.com/docker/go-units v0.5.0
|
||||
github.com/go-git/go-git/v5 v5.19.3
|
||||
github.com/go-git/go-git/v5 v5.19.2
|
||||
github.com/google/go-cmp v0.7.0
|
||||
github.com/hashicorp/go-retryablehttp v0.7.8
|
||||
github.com/leonelquinteros/gotext v1.8.0
|
||||
github.com/leonelquinteros/gotext v1.7.2
|
||||
github.com/moby/sys/signal v0.7.1
|
||||
github.com/moby/term v0.5.2
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/regclient/regclient v0.11.5
|
||||
github.com/schollz/progressbar/v3 v3.19.1
|
||||
github.com/spf13/cobra v1.10.1
|
||||
github.com/stretchr/testify v1.12.1
|
||||
golang.org/x/sys v0.49.0
|
||||
golang.org/x/term v0.47.0
|
||||
golang.org/x/term v0.45.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gotest.tools/v3 v3.5.2
|
||||
)
|
||||
@@ -54,36 +49,32 @@ require (
|
||||
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/containerd/platforms v0.2.1 // indirect
|
||||
github.com/containers/storage v1.38.2 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
|
||||
github.com/docker/distribution v2.8.3+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.9.5 // indirect
|
||||
github.com/docker/go-connections v0.6.0 // indirect
|
||||
github.com/docker/go-metrics v0.0.1 // indirect
|
||||
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fvbommel/sortorder v1.1.0 // indirect
|
||||
github.com/ghodss/yaml v1.0.0 // indirect
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.9.2 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.9.0 // indirect
|
||||
github.com/go-logfmt/logfmt v0.6.1 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gorilla/mux v1.8.1 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/kevinburke/ssh_config v1.6.0 // indirect
|
||||
github.com/klauspost/compress v1.18.5 // indirect
|
||||
github.com/klauspost/compress v1.18.6 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.4.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.22 // indirect
|
||||
@@ -94,9 +85,7 @@ require (
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/moby/go-archive v0.1.0 // indirect
|
||||
github.com/moby/patternmatcher v0.6.1 // indirect
|
||||
github.com/moby/sys/atomicwriter v0.1.0 // indirect
|
||||
github.com/moby/sys/sequential v0.6.0 // indirect
|
||||
github.com/moby/sys/user v0.4.0 // indirect
|
||||
github.com/moby/sys/userns v0.1.0 // indirect
|
||||
github.com/morikuni/aec v1.1.0 // indirect
|
||||
@@ -105,23 +94,19 @@ require (
|
||||
github.com/muesli/termenv v0.16.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.1 // indirect
|
||||
github.com/opencontainers/runc v1.1.13 // indirect
|
||||
github.com/opencontainers/runtime-spec v1.1.0 // indirect
|
||||
github.com/pjbgf/sha1cd v0.7.0 // indirect
|
||||
github.com/prometheus/client_golang v1.23.2 // indirect
|
||||
github.com/pjbgf/sha1cd v0.6.0 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.67.5 // indirect
|
||||
github.com/prometheus/procfs v0.20.1 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/sergi/go-diff v1.4.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.4 // indirect
|
||||
github.com/skeema/knownhosts v1.3.2 // indirect
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
github.com/theupdateframework/notary v0.7.0 // indirect
|
||||
github.com/ulikunitz/xz v0.5.15 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
@@ -139,10 +124,10 @@ require (
|
||||
go.opentelemetry.io/proto/otlp v1.10.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.4 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.5 // indirect
|
||||
golang.org/x/crypto v0.56.0 // indirect
|
||||
golang.org/x/crypto v0.53.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect
|
||||
golang.org/x/net v0.57.0 // indirect
|
||||
golang.org/x/text v0.41.0 // indirect
|
||||
golang.org/x/net v0.56.0 // indirect
|
||||
golang.org/x/text v0.39.0 // indirect
|
||||
golang.org/x/time v0.15.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect
|
||||
@@ -152,6 +137,27 @@ require (
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/containers/image v3.0.2+incompatible
|
||||
github.com/containers/storage v1.38.2 // indirect
|
||||
github.com/decentral1se/passgen v1.0.1
|
||||
github.com/docker/docker-credential-helpers v0.9.5 // indirect
|
||||
github.com/fvbommel/sortorder v1.1.0 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/gorilla/mux v1.8.1 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.8
|
||||
github.com/moby/patternmatcher v0.6.1 // indirect
|
||||
github.com/moby/sys/sequential v0.6.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.1 // indirect
|
||||
github.com/prometheus/client_golang v1.23.2 // indirect
|
||||
github.com/sergi/go-diff v1.4.0 // indirect
|
||||
github.com/spf13/cobra v1.10.2
|
||||
github.com/stretchr/testify v1.12.1
|
||||
github.com/theupdateframework/notary v0.7.0 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
golang.org/x/sys v0.47.0
|
||||
)
|
||||
|
||||
replace github.com/docker/cli v28.4.0+incompatible => git.coopcloud.tech/toolshed/docker-cli v28.5.3-0.20260202112816-30df2d0b3a00+incompatible
|
||||
|
||||
replace github.com/spf13/cobra => github.com/decentral1se/cobra v1.10.2
|
||||
|
||||
@@ -388,12 +388,12 @@ github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
||||
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
||||
github.com/go-git/go-billy/v5 v5.9.2 h1:OXFSRyz4g20upsGDJgQG9Bak1l/ZEv8GHVYB52O71sE=
|
||||
github.com/go-git/go-billy/v5 v5.9.2/go.mod h1:ExsU+jcGwXTBOnyilvAnEM1wug1IxHr4yP2ZXsNRtV0=
|
||||
github.com/go-git/go-billy/v5 v5.9.0 h1:jItGXszUDRtR/AlferWPTMN4j38BQ88XnXKbilmmBPA=
|
||||
github.com/go-git/go-billy/v5 v5.9.0/go.mod h1:jCnQMLj9eUgGU7+ludSTYoZL/GGmii14RxKFj7ROgHw=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
||||
github.com/go-git/go-git/v5 v5.19.3 h1:qHttbZ+Am7wEjdTpR1XMQ4rl42FK9SIXzZ4o9x7s6M4=
|
||||
github.com/go-git/go-git/v5 v5.19.3/go.mod h1:Ye4C8dVigkqrv9UsB4NMdSLV4yAIkLiIhW61gcOyhl4=
|
||||
github.com/go-git/go-git/v5 v5.19.2 h1:wkfn7vOlUBu8ivAWKBWisTiwJK4jYHzTF8Ndv1LyGqY=
|
||||
github.com/go-git/go-git/v5 v5.19.2/go.mod h1:QqCBE1EFN5ddFmrliLQ3/ntRCUjZU3EJuwuB/jWEHjk=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
@@ -426,6 +426,8 @@ github.com/go-sql-driver/mysql v1.3.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
|
||||
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||
github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
|
||||
github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
|
||||
github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
|
||||
@@ -583,8 +585,10 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
|
||||
github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/klauspost/compress v1.14.2/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=
|
||||
github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao=
|
||||
github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
@@ -602,8 +606,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/leonelquinteros/gotext v1.8.0 h1:wwpxINWmWq3JN3WFI9BoSupBFrvAqxZTtf3qppijYkU=
|
||||
github.com/leonelquinteros/gotext v1.8.0/go.mod h1:Wqke380+8unZq2F4oTmmqmbUY9zWQMzwImdC34XOAo8=
|
||||
github.com/leonelquinteros/gotext v1.7.2 h1:bDPndU8nt+/kRo1m4l/1OXiiy2v7Z7dfPQ9+YP7G1Mc=
|
||||
github.com/leonelquinteros/gotext v1.7.2/go.mod h1:9/haCkm5P7Jay1sxKDGJ5WIg4zkz8oZKw4ekNpALob8=
|
||||
github.com/lib/pq v0.0.0-20150723085316-0dad96c0b94f/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo=
|
||||
github.com/lucasb-eyer/go-colorful v1.4.0 h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW4TvVgFr4=
|
||||
@@ -696,6 +700,8 @@ github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+
|
||||
github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/olareg/olareg v0.2.1 h1:RPHGIaqlVWPbKAsOYUj7e2WfEEW5M7F8I6hKMrwd4jU=
|
||||
github.com/olareg/olareg v0.2.1/go.mod h1:dhr8QetC7U7jJ2m93oxDhEEOKCRbPgOK1oGyKfB4QNo=
|
||||
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||
github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
@@ -750,8 +756,8 @@ github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuh
|
||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||
github.com/pjbgf/sha1cd v0.7.0 h1:ZRNPKHj+gfkLBf0KJv/p1Hmz+7bqCT5o311YX0nq+DA=
|
||||
github.com/pjbgf/sha1cd v0.7.0/go.mod h1:pKR5Li+qTCo+ebqITZfwPlJGoCFCvSO00qxjbNtTUFQ=
|
||||
github.com/pjbgf/sha1cd v0.6.0 h1:3WJ8Wz8gvDz29quX1OcEmkAlUg9diU4GxJHqs0/XiwU=
|
||||
github.com/pjbgf/sha1cd v0.6.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
@@ -792,6 +798,8 @@ github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O
|
||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc=
|
||||
github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo=
|
||||
github.com/regclient/regclient v0.11.5 h1:OHRsXO0F3qHGfa4HEUv+EkMH9NXNcCTBKjNzyC/UhIA=
|
||||
github.com/regclient/regclient v0.11.5/go.mod h1:DZUOfIT14WFTK2Pj4vjd93avy9O4Fdpjrf9ir23TbRE=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
@@ -855,6 +863,8 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE=
|
||||
github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg=
|
||||
github.com/sudo-bmitch/oci-digest v0.1.2 h1:are0qzWTsFZGZ3Uvdi9OSztJszSWaab6iqquMEEB7rw=
|
||||
github.com/sudo-bmitch/oci-digest v0.1.2/go.mod h1:SH6l5OIe0islKBZBedjiPOeET/0QwGL+/oYfQt51uQo=
|
||||
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtseVEc4yCz2qF8ZrQvIDBJLl4S1c3GCXmoI=
|
||||
@@ -866,6 +876,8 @@ github.com/theupdateframework/notary v0.7.0/go.mod h1:c9DRxcmhHmVLDay4/2fUYdISnH
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
|
||||
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
@@ -963,8 +975,8 @@ golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWP
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.56.0 h1:GUh5Ii4J5jtcseSMiRqr1jXCNHoxjeV9Fmekc2oLy6Y=
|
||||
golang.org/x/crypto v0.56.0/go.mod h1:OMW5y6CY9l38uPLmxU6l6pwcXp1obtLo3e6gT7gQR2I=
|
||||
golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
|
||||
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -1040,8 +1052,8 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
|
||||
golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE=
|
||||
golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU=
|
||||
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/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -1136,13 +1148,13 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.49.0 h1:XbzkgYJHdqh/8m2Uu0W/dQv8nktxx4BFHp1M0gROTXA=
|
||||
golang.org/x/sys v0.49.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
|
||||
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-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
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.47.0 h1:cpyOwQkr2Nzxe2p6KnrTG7Ei2wtzjsuSjhpXXepZ/BM=
|
||||
golang.org/x/term v0.47.0/go.mod h1:efAfAuG6RKABvg1ArGebRkvX6LXul14SM67o2e4mUCY=
|
||||
golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0=
|
||||
golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -1152,8 +1164,8 @@ golang.org/x/text v0.3.4/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.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
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/text v0.39.0 h1:UbZz4pLOvn600D6Oh6GGEI6VAmndrEBLv8/6BEXzyus=
|
||||
golang.org/x/text v0.39.0/go.mod h1:3UwRclnC2g0TU9x8PZiyfOajCd1zaUNHF9cvqcQZ+ZM=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
||||
@@ -9,8 +9,12 @@ import (
|
||||
"github.com/containers/image/docker"
|
||||
"github.com/containers/image/types"
|
||||
"github.com/distribution/reference"
|
||||
"github.com/regclient/regclient"
|
||||
"github.com/regclient/regclient/types/ref"
|
||||
)
|
||||
|
||||
var rc = regclient.New()
|
||||
|
||||
// GetRegistryTags retrieves all tags of an image from a container registry.
|
||||
func GetRegistryTags(img reference.Named) ([]string, error) {
|
||||
var tags []string
|
||||
@@ -28,3 +32,23 @@ func GetRegistryTags(img reference.Named) ([]string, error) {
|
||||
|
||||
return tags, nil
|
||||
}
|
||||
|
||||
// GetImageDigest resolves the content digest (sha256:...) for a specific
|
||||
// image:tag reference by querying the registry manifest. This gives us a
|
||||
// digest to pin to while keeping support for multi-arch deployments: when
|
||||
// the tag points at a manifest list/OCI index, the digest returned is the
|
||||
// digest of that top-level index, so the pin still resolves correctly
|
||||
// regardless of the deploying host's architecture.
|
||||
func GetImageDigest(img reference.Named, tag string) (string, error) {
|
||||
r, err := ref.New(fmt.Sprintf("%s:%s", img.Name(), tag))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("parsing reference %s:%s: %w", img.Name(), tag, err)
|
||||
}
|
||||
|
||||
m, err := rc.ManifestHead(context.Background(), r)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("fetching manifest for %s:%s: %w", img.Name(), tag, err)
|
||||
}
|
||||
|
||||
return m.GetDescriptor().Digest.String(), nil
|
||||
}
|
||||
|
||||
@@ -34,7 +34,8 @@ func SmallSHA(hash string) string {
|
||||
return hash[:8]
|
||||
}
|
||||
|
||||
// RemoveSha remove image sha from a string that are added in some docker outputs
|
||||
// RemoveSha remove image sha (digest suffix) from a string that are added in some docker outputs
|
||||
// e.g., "image:tag@sha256:..." -> "image:tag"
|
||||
func RemoveSha(str string) string {
|
||||
return strings.Split(str, "@")[0]
|
||||
}
|
||||
|
||||
@@ -6,6 +6,13 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRemoveSha(t *testing.T) {
|
||||
assert.Equal(t, "ubuntu:latest", RemoveSha("ubuntu:latest@sha256:1234567890abcdef"))
|
||||
assert.Equal(t, "ubuntu:latest", RemoveSha("ubuntu:latest"))
|
||||
assert.Equal(t, "my-repo/my-image:v1", RemoveSha("my-repo/my-image:v1@sha256:abcdef1234567890"))
|
||||
assert.Equal(t, "my-repo/my-image", RemoveSha("my-repo/my-image@sha256:abcdef1234567890"))
|
||||
}
|
||||
|
||||
func TestBoldDirtyDefault(t *testing.T) {
|
||||
assert.Equal(t, "foo", BoldDirtyDefault("foo"))
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ func Clone(dir, url string) error {
|
||||
URL: url,
|
||||
Tags: git.AllTags,
|
||||
ReferenceName: plumbing.ReferenceName("refs/heads/main"),
|
||||
SingleBranch: true,
|
||||
})
|
||||
|
||||
if err != nil && gitCloneIgnoreErr(err) {
|
||||
@@ -70,6 +71,7 @@ func Clone(dir, url string) error {
|
||||
URL: url,
|
||||
Tags: git.AllTags,
|
||||
ReferenceName: plumbing.ReferenceName("refs/heads/master"),
|
||||
SingleBranch: true,
|
||||
})
|
||||
|
||||
if err != nil && gitCloneIgnoreErr(err) {
|
||||
|
||||
+37
-37
@@ -7,7 +7,7 @@
|
||||
msgid ""
|
||||
msgstr "Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2026-10-14 10:32-0400\n"
|
||||
"POT-Creation-Date: 2026-06-14 17:56+0200\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -189,7 +189,7 @@ msgstr ""
|
||||
msgid "%d volumes removed successfully"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/recipe/git.go:198
|
||||
#: ./pkg/recipe/git.go:197
|
||||
#, c-format
|
||||
msgid "%s (%s) has locally unstaged changes?"
|
||||
msgstr ""
|
||||
@@ -419,7 +419,7 @@ msgstr ""
|
||||
msgid "%s sanitised as %s for new app"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/recipe/git.go:451
|
||||
#: ./pkg/recipe/git.go:445
|
||||
#, c-format
|
||||
msgid "%s service is missing image tag?"
|
||||
msgstr ""
|
||||
@@ -902,7 +902,7 @@ msgstr ""
|
||||
msgid "Generate the recipe catalogue"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/recipe/git.go:465
|
||||
#: ./pkg/recipe/git.go:459
|
||||
#, c-format
|
||||
msgid "GetRecipeVersions encountered error for %s: %s (collected %d versions)"
|
||||
msgstr ""
|
||||
@@ -1815,7 +1815,7 @@ msgstr ""
|
||||
msgid "are you sure?"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/recipe/git.go:163
|
||||
#: ./pkg/recipe/git.go:162
|
||||
#, c-format
|
||||
msgid "attempting to checkout '%s' as chaos commit"
|
||||
msgstr ""
|
||||
@@ -1951,7 +1951,7 @@ msgstr ""
|
||||
msgid "cancelled"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/catalogue/catalogue.go:59 ./pkg/recipe/git.go:252
|
||||
#: ./pkg/catalogue/catalogue.go:59 ./pkg/recipe/git.go:251
|
||||
#, c-format
|
||||
msgid "cannot ensure %s is up-to-date, no git remotes configured"
|
||||
msgstr ""
|
||||
@@ -1966,7 +1966,7 @@ msgstr ""
|
||||
msgid "cannot get label %s for %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/recipe/git.go:59
|
||||
#: ./pkg/recipe/git.go:58
|
||||
#, c-format
|
||||
msgid "cannot redeploy previous chaos version (%s), did you mean to use \"--chaos\"?"
|
||||
msgstr ""
|
||||
@@ -2118,7 +2118,7 @@ msgstr ""
|
||||
msgid "cmd"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/recipe/git.go:476
|
||||
#: ./pkg/recipe/git.go:470
|
||||
#, c-format
|
||||
msgid "collected %s for %s"
|
||||
msgstr ""
|
||||
@@ -2456,7 +2456,7 @@ msgstr ""
|
||||
msgid "destination directory does not exist"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/recipe/git.go:379
|
||||
#: ./pkg/recipe/git.go:373
|
||||
#, c-format
|
||||
msgid "detected %s as tags for recipe %s"
|
||||
msgstr ""
|
||||
@@ -2653,7 +2653,7 @@ msgstr ""
|
||||
msgid "ensure recipe: %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/recipe/git.go:57
|
||||
#: ./pkg/recipe/git.go:56
|
||||
#, c-format
|
||||
msgid "ensuring env version %s"
|
||||
msgstr ""
|
||||
@@ -2751,12 +2751,12 @@ msgstr ""
|
||||
msgid "failed to check git status of %s: %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/git/branch.go:95 ./pkg/recipe/git.go:232
|
||||
#: ./pkg/git/branch.go:95 ./pkg/recipe/git.go:231
|
||||
#, c-format
|
||||
msgid "failed to check out %s in %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/recipe/git.go:418
|
||||
#: ./pkg/recipe/git.go:412
|
||||
#, c-format
|
||||
msgid "failed to check out %s in %s: %s"
|
||||
msgstr ""
|
||||
@@ -2806,7 +2806,7 @@ msgstr ""
|
||||
msgid "failed to generate random bytes: %w"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/recipe/git.go:427
|
||||
#: ./pkg/recipe/git.go:421
|
||||
#, c-format
|
||||
msgid "failed to get compose config for %s: %s"
|
||||
msgstr ""
|
||||
@@ -2844,7 +2844,7 @@ msgstr ""
|
||||
msgid "failed to parse image %s, saw: %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/recipe/git.go:437
|
||||
#: ./pkg/recipe/git.go:431
|
||||
#, c-format
|
||||
msgid "failed to parse image for %s in %s: %s"
|
||||
msgstr ""
|
||||
@@ -2952,7 +2952,7 @@ msgstr ""
|
||||
msgid "fetch all recipes"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/catalogue/catalogue.go:84 ./pkg/recipe/git.go:290
|
||||
#: ./pkg/catalogue/catalogue.go:84 ./pkg/recipe/git.go:284
|
||||
#, c-format
|
||||
msgid "fetched latest git changes for %s"
|
||||
msgstr ""
|
||||
@@ -3070,12 +3070,12 @@ msgstr ""
|
||||
msgid "git changes pushed"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/recipe/git.go:423
|
||||
#: ./pkg/recipe/git.go:417
|
||||
#, c-format
|
||||
msgid "git checkout: %s in %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/git/clone.go:63 ./pkg/git/clone.go:100
|
||||
#: ./pkg/git/clone.go:64 ./pkg/git/clone.go:102
|
||||
#, c-format
|
||||
msgid "git clone %s: cancelled due to interrupt"
|
||||
msgstr ""
|
||||
@@ -3085,17 +3085,17 @@ msgstr ""
|
||||
msgid "git clone: %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/git/clone.go:87
|
||||
#: ./pkg/git/clone.go:89
|
||||
#, c-format
|
||||
msgid "git clone: %s already exists"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/git/clone.go:58 ./pkg/git/clone.go:76 ./pkg/git/clone.go:85
|
||||
#: ./pkg/git/clone.go:59 ./pkg/git/clone.go:78 ./pkg/git/clone.go:87
|
||||
#, c-format
|
||||
msgid "git clone: %s cloned successfully"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/git/clone.go:67
|
||||
#: ./pkg/git/clone.go:68
|
||||
msgid "git clone: main branch failed, attempting master branch"
|
||||
msgstr ""
|
||||
|
||||
@@ -3150,7 +3150,7 @@ msgstr ""
|
||||
msgid "git.coopcloud.tech repo exists"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/recipe/git.go:390
|
||||
#: ./pkg/recipe/git.go:384
|
||||
#, c-format
|
||||
msgid "git: opening repository in %s"
|
||||
msgstr ""
|
||||
@@ -4061,7 +4061,7 @@ msgstr ""
|
||||
msgid "proceed?"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/recipe/git.go:410
|
||||
#: ./pkg/recipe/git.go:404
|
||||
#, c-format
|
||||
msgid "processing %s for %s"
|
||||
msgstr ""
|
||||
@@ -4129,7 +4129,7 @@ msgstr ""
|
||||
msgid "re"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/recipe/git.go:158
|
||||
#: ./pkg/recipe/git.go:157
|
||||
#, c-format
|
||||
msgid "read %s as tags for recipe %s"
|
||||
msgstr ""
|
||||
@@ -4475,7 +4475,7 @@ msgstr ""
|
||||
msgid "retrieving docker auth token: failed create docker cli: %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/recipe/recipe.go:656
|
||||
#: ./pkg/recipe/recipe.go:659
|
||||
msgid "retrieving recipes"
|
||||
msgstr ""
|
||||
|
||||
@@ -4865,17 +4865,17 @@ msgstr ""
|
||||
msgid "skipping secret (because it already exists) on %s: %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/recipe/git.go:419
|
||||
#: ./pkg/recipe/git.go:413
|
||||
#, c-format
|
||||
msgid "skipping tag %s: checkout failed: %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/recipe/git.go:428
|
||||
#: ./pkg/recipe/git.go:422
|
||||
#, c-format
|
||||
msgid "skipping tag %s: invalid compose config: %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/recipe/git.go:438
|
||||
#: ./pkg/recipe/git.go:432
|
||||
#, c-format
|
||||
msgid "skipping tag %s: invalid image reference in service %s: %s"
|
||||
msgstr ""
|
||||
@@ -4978,7 +4978,7 @@ msgstr ""
|
||||
msgid "succeeded"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/recipe/git.go:185
|
||||
#: ./pkg/recipe/git.go:184
|
||||
#, c-format
|
||||
msgid "successfully checked %s out to %s in %s"
|
||||
msgstr ""
|
||||
@@ -5138,17 +5138,17 @@ msgstr ""
|
||||
msgid "un"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/recipe/git.go:194
|
||||
#: ./pkg/recipe/git.go:193
|
||||
#, c-format
|
||||
msgid "unable to check git clean status in %s: %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/recipe/git.go:263
|
||||
#: ./pkg/recipe/git.go:262
|
||||
#, c-format
|
||||
msgid "unable to check out default branch in %s: %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/git/clone.go:98
|
||||
#: ./pkg/git/clone.go:100
|
||||
#, c-format
|
||||
msgid "unable to clean up git clone of %s: %s"
|
||||
msgstr ""
|
||||
@@ -5222,7 +5222,7 @@ msgstr ""
|
||||
msgid "unable to discover SSH remote for %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/recipe/git.go:274
|
||||
#: ./pkg/recipe/git.go:268
|
||||
#, c-format
|
||||
msgid "unable to fetch tags in %s: %s"
|
||||
msgstr ""
|
||||
@@ -5232,7 +5232,7 @@ msgstr ""
|
||||
msgid "unable to get container matching %s: %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/recipe/git.go:286
|
||||
#: ./pkg/recipe/git.go:280
|
||||
#, c-format
|
||||
msgid "unable to git pull in %s: %s"
|
||||
msgstr ""
|
||||
@@ -5261,12 +5261,12 @@ msgstr ""
|
||||
msgid "unable to look up server context for %s: %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/recipe/fetch.go:77 ./pkg/git/read.go:26 ./pkg/lint/recipe.go:491 ./pkg/recipe/git.go:243
|
||||
#: ./cli/recipe/fetch.go:77 ./pkg/git/read.go:26 ./pkg/lint/recipe.go:491 ./pkg/recipe/git.go:242
|
||||
#, c-format
|
||||
msgid "unable to open %s: %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/recipe/git.go:258
|
||||
#: ./pkg/recipe/git.go:257
|
||||
#, c-format
|
||||
msgid "unable to open git work tree in %s: %s"
|
||||
msgstr ""
|
||||
@@ -5326,7 +5326,7 @@ msgstr ""
|
||||
msgid "unable to read new env %s: %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/recipe/git.go:248
|
||||
#: ./pkg/recipe/git.go:247
|
||||
#, c-format
|
||||
msgid "unable to read remotes in %s: %s"
|
||||
msgstr ""
|
||||
@@ -5361,7 +5361,7 @@ msgstr ""
|
||||
msgid "unable to reset commit after failed release attempt: %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/recipe/git.go:167
|
||||
#: ./pkg/recipe/git.go:166
|
||||
#, c-format
|
||||
msgid "unable to resolve '%s': %s"
|
||||
msgstr ""
|
||||
|
||||
Binary file not shown.
+54
-168
@@ -1,17 +1,5 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2026-10-14 10:32-0400\n"
|
||||
"PO-Revision-Date: 2026-09-01 22:35+0000\n"
|
||||
"Last-Translator: chasqui <chasqui@cryptolab.net>\n"
|
||||
"Language-Team: Spanish <https://translate.coopcloud.tech/projects/co-op-cloud/abra/es/>\n"
|
||||
"Language: es\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 5.12.2\n"
|
||||
msgstr "Project-Id-Version: \nReport-Msgid-Bugs-To: EMAIL\nPOT-Creation-Date: 2026-06-14 17:56+0200\nPO-Revision-Date: 2026-08-11 06:51+0000\nLast-Translator: chasqui <chasqui@cryptolab.net>\nLanguage-Team: Spanish <https://translate.coopcloud.tech/projects/co-op-cloud/abra/es/>\nLanguage: es\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\nPlural-Forms: nplurals=2; plural=n != 1;\nX-Generator: Weblate 5.12.2\n"
|
||||
|
||||
#: cli/app/cp.go:38
|
||||
msgid ""
|
||||
@@ -112,26 +100,7 @@ msgid ""
|
||||
"\n"
|
||||
" # list apps of all servers which match a specific recipe\n"
|
||||
" abra app ls -r gitea"
|
||||
msgstr ""
|
||||
" OPCIONES Y FLAGS\n"
|
||||
" ────────────────\n"
|
||||
" ejemplos de uso:\n"
|
||||
"\n"
|
||||
"┌─ Información detallada ─────────────────────────────────────┐\n"
|
||||
"│ $ abra aplicacion listar --estado │\n"
|
||||
"└─────────────────────────────────────────────────────────────┘\n"
|
||||
"\n"
|
||||
"┌─ Aplicaciones de una receta ────────────────────────────────┐\n"
|
||||
"│ $ abra aplicacion listar --receta wordpress │\n"
|
||||
"└─────────────────────────────────────────────────────────────┘\n"
|
||||
"\n"
|
||||
"┌─ Receta + información detallada ────────────────────────────┐\n"
|
||||
"│ $ abra aplicacion listar --receta wordpress --estado │\n"
|
||||
"└─────────────────────────────────────────────────────────────┘\n"
|
||||
"\n"
|
||||
"┌─ Forma abreviada ───────────────────────────────────────────┐\n"
|
||||
"│ $ abra app ls -S │\n"
|
||||
"└─────────────────────────────────────────────────────────────┘"
|
||||
msgstr " # Listar todas las aplicaciones instaladas con mayor detalle\n abra aplicacion listar --estado\n\n # Listar todas las aplicaciones \"wordpress\" (puedes probar otras recetas)\n abra aplicacion listar --receta wordpress\n\n # Listar todas las aplicaciones \"wordpress\" con mayor detalle\n abra aplicacion listar --receta wordpress --estado\n\n # Listar todas las aplicaciones instaladas con mayor detalle usando los comandos abreviados\n abra app ls -S"
|
||||
|
||||
#: cli/app/move.go:59
|
||||
msgid ""
|
||||
@@ -244,21 +213,7 @@ msgstr " abra aplicacion entorno listar \"nombre_app.dominio\""
|
||||
|
||||
#: cli/app/remove.go:45
|
||||
msgid " abra app remove 1312.net"
|
||||
msgstr ""
|
||||
" EJEMPLOS DE USO\n"
|
||||
" ────────────────\n"
|
||||
"\n"
|
||||
"┌─ Borrar una aplicación ─────────────────────────────────────┐\n"
|
||||
"│ $ abra aplicacion borrar \"nombre_app.dominio\" │\n"
|
||||
"└─────────────────────────────────────────────────────────────┘\n"
|
||||
"\n"
|
||||
"┌─ Borrar sin confirmación ───────────────────────────────────┐\n"
|
||||
"│ $ abra aplicacion borrar \"nombre_app.dominio\" --forzar │\n"
|
||||
"└─────────────────────────────────────────────────────────────┘\n"
|
||||
"\n"
|
||||
"┌─ Borrar sin confirmación usando comando abreviado ──────────┐\n"
|
||||
"│ $ abra app rm \"nombre_app.dominio\" -f │\n"
|
||||
"└─────────────────────────────────────────────────────────────┘"
|
||||
msgstr " abra aplicacion borrar \"nombre_app.dominio\""
|
||||
|
||||
#: cli/app/secret.go:346
|
||||
msgid " abra app secret rm 1312.net oauth_key"
|
||||
@@ -314,7 +269,7 @@ msgstr "%d volumen(es) eliminados con éxito"
|
||||
msgid "%d volumes removed successfully"
|
||||
msgstr "%d volúmenes eliminados correctamente"
|
||||
|
||||
#: pkg/recipe/git.go:198
|
||||
#: pkg/recipe/git.go:197
|
||||
#, c-format
|
||||
msgid "%s (%s) has locally unstaged changes?"
|
||||
msgstr "%s (%s) ¿tiene cambios locales?"
|
||||
@@ -547,7 +502,7 @@ msgstr "%s requiere inserción de secretos antes del despliegue (#generate=false
|
||||
msgid "%s sanitised as %s for new app"
|
||||
msgstr "%s sanitisado como %s para nueva aplicación"
|
||||
|
||||
#: pkg/recipe/git.go:451
|
||||
#: pkg/recipe/git.go:445
|
||||
#, c-format
|
||||
msgid "%s service is missing image tag?"
|
||||
msgstr "¿falta la etiqueta de imagen del servicio %s?"
|
||||
@@ -1129,26 +1084,7 @@ msgid ""
|
||||
"Generate a report of all managed apps.\n"
|
||||
"\n"
|
||||
"Use \"--status/-S\" flag to query all servers for the live deployment status."
|
||||
msgstr ""
|
||||
"┌──────────────────────────────────────────────────────────────┐\n"
|
||||
"│ MANUAL DE USO · COMANDO «LISTAR» │\n"
|
||||
"└──────────────────────────────────────────────────────────────┘\n"
|
||||
" DESCRIPCIÓN\n"
|
||||
" ───────────\n"
|
||||
" El comando:\n"
|
||||
"\n"
|
||||
" abra aplicacion listar\n"
|
||||
"\n"
|
||||
"Permite consultar rápidamente qué aplicaciones están siendo\n"
|
||||
"gestionadas por Abra.\n"
|
||||
"\n"
|
||||
"Al ejecutarlo, Abra genera un informe con la lista de\n"
|
||||
"aplicaciones disponibles y la información básica de cada una.\n"
|
||||
"\n"
|
||||
"En pocas palabras, puedes imaginar `listar` como una forma\n"
|
||||
"de decirle a Abra:\n"
|
||||
"\n"
|
||||
" «muéstrame qué aplicaciones tengo»"
|
||||
msgstr "BIENVENIDE AL MANUAL DE USO COMANDO \"LISTAR\"\n\nEl comando \"abra aplicacion listar\" permite consultar de forma rápida qué aplicaciones están siendo gestionadas. \nAl ejecutarlo, Abra genera un informe con la lista de aplicaciones disponibles y la información básica de cada una.\nPuedes imaginar el comando listar como una forma de decirle a Abra: «muéstrame qué aplicaciones tengo»\n\nLos comandos pueden aceptar opciones y flags que permiten modificar o ampliar la información que muestran, \nen este caso es bastante útil el uso de \"--estado\". Estas opciones se escriben después del comando siguiendo \nla sintaxis detallada más adelante."
|
||||
|
||||
#: cli/app/new.go:321
|
||||
msgid "Generate app secrets?"
|
||||
@@ -1174,7 +1110,7 @@ msgstr "Genera 🥷 secretos (contraseñas) automáticamente"
|
||||
msgid "Generate the recipe catalogue"
|
||||
msgstr "📋 Muestra el catálogo de 📜 recetas"
|
||||
|
||||
#: pkg/recipe/git.go:465
|
||||
#: pkg/recipe/git.go:459
|
||||
#, c-format
|
||||
msgid "GetRecipeVersions encountered error for %s: %s (collected %d versions)"
|
||||
msgstr "GetRecipeVersions encontró un error para %s: %s (recopiladas %d versiones)"
|
||||
@@ -1516,48 +1452,24 @@ msgid ""
|
||||
"To delete everything without prompt, use the \"--force/-f\" or the \"--no-input/n\"\n"
|
||||
"flag."
|
||||
msgstr ""
|
||||
"┌──────────────────────────────────────────────────────────────┐\n"
|
||||
"│ MANUAL DE USO · COMANDO «BORRAR» │\n"
|
||||
"└──────────────────────────────────────────────────────────────┘\n"
|
||||
" DESCRIPCIÓN\n"
|
||||
" ───────────\n"
|
||||
" El comando:\n"
|
||||
"Elimina todo lo relacionado con una aplicación que ya está plegada.\n"
|
||||
"\n"
|
||||
" abra aplicacion borrar \"nombre_app.dominio\"\n"
|
||||
"Por defecto, pedirá confirmación antes de continuar. Se borrarán \n"
|
||||
"todos los secretos, volúmenes y el archivo .env local de la aplicación.\n"
|
||||
"\n"
|
||||
"elimina todo lo relacionado con una aplicación que ya está\n"
|
||||
"desplegada.\n"
|
||||
"Ejecuta este comando solo si estás seguro de querer quitar por \n"
|
||||
"completo la aplicación y todos sus datos asociados. Esta acción \n"
|
||||
"es destructiva. ¡Ten cuidado!\n"
|
||||
"\n"
|
||||
"Por defecto, Abra pedirá confirmación antes de continuar.\n"
|
||||
"Si quieres borrar volúmenes o secretos específicos, \n"
|
||||
"usa los subcomandos de eliminación bajo \"aplicación volumen\" \n"
|
||||
"y \"aplicación secreto\" en su lugar.\n"
|
||||
"\n"
|
||||
"La eliminación incluye:\n"
|
||||
"Ten en cuenta que si borras el archivo .env local sin eliminar primero \n"
|
||||
"volúmenes y secretos, Abra *no* podrá ayudarte a eliminarlos después.\n"
|
||||
"\n"
|
||||
" ├── secretos\n"
|
||||
" ├── volúmenes\n"
|
||||
" └── archivo .env local\n"
|
||||
"\n"
|
||||
"\n"
|
||||
" ¡ATENCIÓN!\n"
|
||||
" ──────────\n"
|
||||
"Esta es una acción destructiva.\n"
|
||||
"\n"
|
||||
"La aplicación y todos los datos asociados serán eliminados.\n"
|
||||
"Ejecuta este comando únicamente si estás seguro de querer\n"
|
||||
"quitar la aplicación por completo.\n"
|
||||
"\n"
|
||||
"\n"
|
||||
"Si quieres eliminar volúmenes o secretos específicos, utiliza\n"
|
||||
"los subcomandos correspondientes:\n"
|
||||
"\n"
|
||||
" abra aplicacion volumen\n"
|
||||
" abra aplicacion secreto\n"
|
||||
"\n"
|
||||
"\n"
|
||||
" IMPORTANTE\n"
|
||||
" ──────────\n"
|
||||
"Si eliminas el archivo .env local sin eliminar primero los\n"
|
||||
"volúmenes y secretos, Abra no podrá ayudarte a eliminarlos\n"
|
||||
"después."
|
||||
"Para borrar todo sin confirmación, usa la opción \"--forzar/-f\" \n"
|
||||
"o la opción \"--no-input/-n\"."
|
||||
|
||||
#. translators: Short description for `app volume remove` command
|
||||
#: cli/app/volume.go:90
|
||||
@@ -2086,33 +1998,7 @@ msgid ""
|
||||
" {{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableSubCommands}}\n"
|
||||
"\n"
|
||||
"Use \"{{.CommandPath}} [command] --help\" for more information about a command.{{end}}\n"
|
||||
msgstr ""
|
||||
"COMANDO ABREVIADO\n"
|
||||
"─────────────────\n"
|
||||
" {{range .Aliases}}{{.}}{{end}}\n"
|
||||
"\n"
|
||||
"{{if .HasExample}}\n"
|
||||
"{{.Example}}{{end}}{{if .HasAvailableSubCommands}}\n"
|
||||
"\n"
|
||||
"Comandos disponibles:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name \"help\"))}}\n"
|
||||
" {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasHelpSubCommands}}\n"
|
||||
"\n"
|
||||
"Temas de ayuda adicionales:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}}\n"
|
||||
" {{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}\n"
|
||||
"\n"
|
||||
" SINTAXIS\n"
|
||||
" ────────{{if .Runnable}}\n"
|
||||
" {{.UseLine}}{{end}}{{if .HasAvailableSubCommands}}\n"
|
||||
" {{.CommandPath}} [command]{{end}}\n"
|
||||
"\n"
|
||||
"Opciones:{{if .HasAvailableLocalFlags}}\n"
|
||||
"{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}\n"
|
||||
"\n"
|
||||
"Opciones globales:{{if .HasAvailableInheritedFlags}}\n"
|
||||
"{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}\n"
|
||||
"\n"
|
||||
"{{if .HasAvailableSubCommands}}\n"
|
||||
"Use \"{{.CommandPath}} [comando] --help\" para más información sobre un comando.{{end}}\n"
|
||||
msgstr "Sintaxis del comando:{{if .Runnable}}\n {{.UseLine}}{{end}}{{if .HasAvailableSubCommands}}\n {{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}}\n\nComando y comando abreviado:\n {{.NameAndAliases}}{{end}}{{if .HasExample}}\n\nUSO DE\"OPCIONES\" y \"FLAGS\":\nImportante: 1312.net es únicamente un ejemplo utilizado para mostrar cómo debe configurarse este campo. No es necesario utilizar este nombre.\nAl ejecutar el comando, reemplaza 1312.net por el nombre o dominio real de tu aplicación.\n\n{{.Example}}{{end}}{{if .HasAvailableSubCommands}}\n\nComandos disponibles:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name \"help\"))}}\n {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}}\n\nOpciones:\n{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}}\n\nOpciones globales:\n{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasHelpSubCommands}}\n\nTemas de ayuda adicionales:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}}\n {{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableSubCommands}}\n\nUse \"{{.CommandPath}} [command] --help\" para más información sobre un comando.{{end}}\n"
|
||||
|
||||
#: cli/recipe/fetch.go:28
|
||||
msgid "Using \"--force/-f\" Git syncs an existing recipe. It does not erase unstaged changes."
|
||||
@@ -2410,7 +2296,7 @@ msgstr "¿A la aplicación le falta la etiqueta de imagen?"
|
||||
msgid "are you sure?"
|
||||
msgstr "¿Estás segurx?"
|
||||
|
||||
#: pkg/recipe/git.go:163
|
||||
#: pkg/recipe/git.go:162
|
||||
#, c-format
|
||||
msgid "attempting to checkout '%s' as chaos commit"
|
||||
msgstr "Intentando extraer '%s' como el commit de caos"
|
||||
@@ -2553,7 +2439,7 @@ msgstr "no se pueden leer las recetas locales: %s"
|
||||
msgid "cancelled"
|
||||
msgstr "cancelado"
|
||||
|
||||
#: pkg/catalogue/catalogue.go:59 pkg/recipe/git.go:252
|
||||
#: pkg/catalogue/catalogue.go:59 pkg/recipe/git.go:251
|
||||
#, c-format
|
||||
msgid "cannot ensure %s is up-to-date, no git remotes configured"
|
||||
msgstr "no se puede asegurar que %s esté actualizado, no hay remotos de git configurados"
|
||||
@@ -2568,7 +2454,7 @@ msgstr "no se puede encontrar la aplicación con el nombre %s"
|
||||
msgid "cannot get label %s for %s"
|
||||
msgstr "no se puede obtener la etiqueta %s para %s"
|
||||
|
||||
#: pkg/recipe/git.go:59
|
||||
#: pkg/recipe/git.go:58
|
||||
#, c-format
|
||||
msgid "cannot redeploy previous chaos version (%s), did you mean to use \"--chaos\"?"
|
||||
msgstr "no se puede redeplegar la versión anterior de caos (%s), ¿Era tu intención usar \"--caos\"?"
|
||||
@@ -2728,7 +2614,7 @@ msgstr "tarea: Actualizar tags de una imagen"
|
||||
msgid "cmd"
|
||||
msgstr ""
|
||||
|
||||
#: pkg/recipe/git.go:476
|
||||
#: pkg/recipe/git.go:470
|
||||
#, c-format
|
||||
msgid "collected %s for %s"
|
||||
msgstr "colectado %s para %s"
|
||||
@@ -3067,7 +2953,7 @@ msgstr "tiempo de espera agotado 🟠"
|
||||
msgid "destination directory does not exist"
|
||||
msgstr "el directorio de destino no existe"
|
||||
|
||||
#: pkg/recipe/git.go:379
|
||||
#: pkg/recipe/git.go:373
|
||||
#, c-format
|
||||
msgid "detected %s as tags for recipe %s"
|
||||
msgstr "detectado(s) %s como etiquetas para la receta %s"
|
||||
@@ -3264,7 +3150,7 @@ msgstr "asegúrate que \"image: ...\" esté establecido en todos los servicios"
|
||||
msgid "ensure recipe: %s"
|
||||
msgstr "asegura receta: %s"
|
||||
|
||||
#: pkg/recipe/git.go:57
|
||||
#: pkg/recipe/git.go:56
|
||||
#, c-format
|
||||
msgid "ensuring env version %s"
|
||||
msgstr "asegurando la versión del entorno (env) %s"
|
||||
@@ -3366,12 +3252,12 @@ msgstr "🛑 error al agregar notas de la versión: %s"
|
||||
msgid "failed to check git status of %s: %s"
|
||||
msgstr "🛑 error al comprobar el estado de git de %s: %s"
|
||||
|
||||
#: pkg/git/branch.go:95 pkg/recipe/git.go:232
|
||||
#: pkg/git/branch.go:95 pkg/recipe/git.go:231
|
||||
#, c-format
|
||||
msgid "failed to check out %s in %s"
|
||||
msgstr "🛑 error al cambiar a %s en %s"
|
||||
|
||||
#: pkg/recipe/git.go:418
|
||||
#: pkg/recipe/git.go:412
|
||||
#, c-format
|
||||
msgid "failed to check out %s in %s: %s"
|
||||
msgstr "🛑 error al cambiar a %s en %s: %s"
|
||||
@@ -3421,7 +3307,7 @@ msgstr "🛑 error al crear el volumen %s en %s: %s"
|
||||
msgid "failed to generate random bytes: %w"
|
||||
msgstr "🛑 error al generar bytes aleatorios: %w"
|
||||
|
||||
#: pkg/recipe/git.go:427
|
||||
#: pkg/recipe/git.go:421
|
||||
#, c-format
|
||||
msgid "failed to get compose config for %s: %s"
|
||||
msgstr "🛑 error al obtener la configuración para %s: %s"
|
||||
@@ -3459,7 +3345,7 @@ msgstr "🛑 error al migrar la configuración de la aplicación: %s"
|
||||
msgid "failed to parse image %s, saw: %s"
|
||||
msgstr "🛑 error al analizar la imagen %s; detectado: %s"
|
||||
|
||||
#: pkg/recipe/git.go:437
|
||||
#: pkg/recipe/git.go:431
|
||||
#, c-format
|
||||
msgid "failed to parse image for %s in %s: %s"
|
||||
msgstr "🛑 error al analizar la imagen %s; en: %s: %s"
|
||||
@@ -3567,7 +3453,7 @@ msgstr "obtener [receta | --todos] [opciones]"
|
||||
msgid "fetch all recipes"
|
||||
msgstr "Obtener todas las recetas"
|
||||
|
||||
#: pkg/catalogue/catalogue.go:84 pkg/recipe/git.go:290
|
||||
#: pkg/catalogue/catalogue.go:84 pkg/recipe/git.go:284
|
||||
#, c-format
|
||||
msgid "fetched latest git changes for %s"
|
||||
msgstr "Se obtuvieron los últimos cambios de Git para %s"
|
||||
@@ -3687,12 +3573,12 @@ msgstr "cambios de git confirmados"
|
||||
msgid "git changes pushed"
|
||||
msgstr "cambios de git enviados (push)"
|
||||
|
||||
#: pkg/recipe/git.go:423
|
||||
#: pkg/recipe/git.go:417
|
||||
#, c-format
|
||||
msgid "git checkout: %s in %s"
|
||||
msgstr ""
|
||||
|
||||
#: pkg/git/clone.go:63 pkg/git/clone.go:100
|
||||
#: pkg/git/clone.go:64 pkg/git/clone.go:102
|
||||
#, c-format
|
||||
msgid "git clone %s: cancelled due to interrupt"
|
||||
msgstr "git clone %s: cancelado por interrupción"
|
||||
@@ -3702,17 +3588,17 @@ msgstr "git clone %s: cancelado por interrupción"
|
||||
msgid "git clone: %s"
|
||||
msgstr ""
|
||||
|
||||
#: pkg/git/clone.go:87
|
||||
#: pkg/git/clone.go:89
|
||||
#, c-format
|
||||
msgid "git clone: %s already exists"
|
||||
msgstr "git clone: %s ya existe"
|
||||
|
||||
#: pkg/git/clone.go:58 pkg/git/clone.go:76 pkg/git/clone.go:85
|
||||
#: pkg/git/clone.go:59 pkg/git/clone.go:78 pkg/git/clone.go:87
|
||||
#, c-format
|
||||
msgid "git clone: %s cloned successfully"
|
||||
msgstr "git clone: %s clonado correctamente"
|
||||
|
||||
#: pkg/git/clone.go:67
|
||||
#: pkg/git/clone.go:68
|
||||
msgid "git clone: main branch failed, attempting master branch"
|
||||
msgstr "git clone: error en la rama principal; intentando la rama master"
|
||||
|
||||
@@ -3767,7 +3653,7 @@ msgstr "git-nombre"
|
||||
msgid "git.coopcloud.tech repo exists"
|
||||
msgstr "el repositorio git.coopcloud.tech existe"
|
||||
|
||||
#: pkg/recipe/git.go:390
|
||||
#: pkg/recipe/git.go:384
|
||||
#, c-format
|
||||
msgid "git: opening repository in %s"
|
||||
msgstr "git: abriendo repositorio en %s"
|
||||
@@ -4705,7 +4591,7 @@ msgstr "mostrar salida legible por máquina"
|
||||
msgid "proceed?"
|
||||
msgstr "¿procedemos?"
|
||||
|
||||
#: pkg/recipe/git.go:410
|
||||
#: pkg/recipe/git.go:404
|
||||
#, c-format
|
||||
msgid "processing %s for %s"
|
||||
msgstr "procesando %s para %s"
|
||||
@@ -4775,7 +4661,7 @@ msgstr ""
|
||||
msgid "re"
|
||||
msgstr ""
|
||||
|
||||
#: pkg/recipe/git.go:158
|
||||
#: pkg/recipe/git.go:157
|
||||
#, c-format
|
||||
msgid "read %s as tags for recipe %s"
|
||||
msgstr "leer %s como etiquetas para la receta %s"
|
||||
@@ -5125,7 +5011,7 @@ msgstr "versiones recuperadas del repositorio local de recetas"
|
||||
msgid "retrieving docker auth token: failed create docker cli: %s"
|
||||
msgstr "recuperando token de autenticación de Docker: error al crear docker cli: %s"
|
||||
|
||||
#: pkg/recipe/recipe.go:656
|
||||
#: pkg/recipe/recipe.go:659
|
||||
msgid "retrieving recipes"
|
||||
msgstr "obteniendo 📜 recetas"
|
||||
|
||||
@@ -5521,17 +5407,17 @@ msgstr "omitiendo generación de %s (generate=false)"
|
||||
msgid "skipping secret (because it already exists) on %s: %s"
|
||||
msgstr "omitiendo secreto (porque ya existe) en %s: %s"
|
||||
|
||||
#: pkg/recipe/git.go:419
|
||||
#: pkg/recipe/git.go:413
|
||||
#, c-format
|
||||
msgid "skipping tag %s: checkout failed: %s"
|
||||
msgstr "omitiendo la etiqueta %s: error en el checkout: %s"
|
||||
|
||||
#: pkg/recipe/git.go:428
|
||||
#: pkg/recipe/git.go:422
|
||||
#, c-format
|
||||
msgid "skipping tag %s: invalid compose config: %s"
|
||||
msgstr "omitiendo la etiqueta %s: configuración de compose inválida: %s"
|
||||
|
||||
#: pkg/recipe/git.go:438
|
||||
#: pkg/recipe/git.go:432
|
||||
#, c-format
|
||||
msgid "skipping tag %s: invalid image reference in service %s: %s"
|
||||
msgstr "omitiendo la etiqueta %s: referencia de imagen inválida en el servicio %s: %s"
|
||||
@@ -5634,7 +5520,7 @@ msgstr "recortado %s a %s para el análisis"
|
||||
msgid "succeeded"
|
||||
msgstr "exitoso"
|
||||
|
||||
#: pkg/recipe/git.go:185
|
||||
#: pkg/recipe/git.go:184
|
||||
#, c-format
|
||||
msgid "successfully checked %s out to %s in %s"
|
||||
msgstr "se comprobó correctamente %s de %s en %s"
|
||||
@@ -5796,17 +5682,17 @@ msgstr ""
|
||||
msgid "un"
|
||||
msgstr ""
|
||||
|
||||
#: pkg/recipe/git.go:194
|
||||
#: pkg/recipe/git.go:193
|
||||
#, c-format
|
||||
msgid "unable to check git clean status in %s: %s"
|
||||
msgstr "no se pudo comprobar el estado de git clean en %s: %s"
|
||||
|
||||
#: pkg/recipe/git.go:263
|
||||
#: pkg/recipe/git.go:262
|
||||
#, c-format
|
||||
msgid "unable to check out default branch in %s: %s"
|
||||
msgstr "no se pudo cambiar a la rama predeterminada en %s: %s"
|
||||
|
||||
#: pkg/git/clone.go:98
|
||||
#: pkg/git/clone.go:100
|
||||
#, c-format
|
||||
msgid "unable to clean up git clone of %s: %s"
|
||||
msgstr "no se pudo limpiar la clonación de Git de %s: %s"
|
||||
@@ -5880,7 +5766,7 @@ msgstr "no se pudieron determinar las reglas de versionado de %s; listando todas
|
||||
msgid "unable to discover SSH remote for %s"
|
||||
msgstr "no se pudo detectar el remoto SSH para %s"
|
||||
|
||||
#: pkg/recipe/git.go:274
|
||||
#: pkg/recipe/git.go:268
|
||||
#, c-format
|
||||
msgid "unable to fetch tags in %s: %s"
|
||||
msgstr "no se pudieron obtener las etiquetas en %s: %s"
|
||||
@@ -5890,7 +5776,7 @@ msgstr "no se pudieron obtener las etiquetas en %s: %s"
|
||||
msgid "unable to get container matching %s: %s"
|
||||
msgstr "no se pudo obtener el contenedor que coincide con %s: %s"
|
||||
|
||||
#: pkg/recipe/git.go:286
|
||||
#: pkg/recipe/git.go:280
|
||||
#, c-format
|
||||
msgid "unable to git pull in %s: %s"
|
||||
msgstr "no se pudo hacer git pull en %s: %s"
|
||||
@@ -5920,12 +5806,12 @@ msgid "unable to look up server context for %s: %s"
|
||||
msgstr "no se pudo obtener el contexto del servidor para %s: %s"
|
||||
|
||||
#: cli/recipe/fetch.go:77 pkg/git/read.go:26 pkg/lint/recipe.go:491
|
||||
#: pkg/recipe/git.go:243
|
||||
#: pkg/recipe/git.go:242
|
||||
#, c-format
|
||||
msgid "unable to open %s: %s"
|
||||
msgstr "no se pudo abrir %s: %s"
|
||||
|
||||
#: pkg/recipe/git.go:258
|
||||
#: pkg/recipe/git.go:257
|
||||
#, c-format
|
||||
msgid "unable to open git work tree in %s: %s"
|
||||
msgstr "no se pudo abrir el git work tree en %s: %s"
|
||||
@@ -5985,7 +5871,7 @@ msgstr "no se pudo consultar el estado de %s: %s"
|
||||
msgid "unable to read new env %s: %s"
|
||||
msgstr "no se pudo leer el nuevo env %s: %s"
|
||||
|
||||
#: pkg/recipe/git.go:248
|
||||
#: pkg/recipe/git.go:247
|
||||
#, c-format
|
||||
msgid "unable to read remotes in %s: %s"
|
||||
msgstr "no se pudieron leer los remotos en %s: %s"
|
||||
@@ -6021,7 +5907,7 @@ msgstr "no se pudo renderizar a JSON: %s"
|
||||
msgid "unable to reset commit after failed release attempt: %s"
|
||||
msgstr "no se pudo establecer los streams de IO como terminal raw: %s"
|
||||
|
||||
#: pkg/recipe/git.go:167
|
||||
#: pkg/recipe/git.go:166
|
||||
#, c-format
|
||||
msgid "unable to resolve '%s': %s"
|
||||
msgstr "no se pudo resolver '%s': %s"
|
||||
|
||||
+67
-1
@@ -122,6 +122,7 @@ func (r Recipe) UpdateTag(image, tag string) (bool, error) {
|
||||
|
||||
log.Debug(i18n.G("considering %s config(s) for tag update", strings.Join(composeFiles, ", ")))
|
||||
|
||||
updated := false
|
||||
for _, composeFile := range composeFiles {
|
||||
opts := stack.Deploy{Composefiles: []string{composeFile}}
|
||||
|
||||
@@ -166,6 +167,10 @@ func (r Recipe) UpdateTag(image, tag string) (bool, error) {
|
||||
|
||||
old := fmt.Sprintf("%s:%s", composeImage, composeTag)
|
||||
new := fmt.Sprintf("%s:%s", composeImage, tag)
|
||||
if old == new {
|
||||
continue
|
||||
}
|
||||
|
||||
replacedBytes := strings.Replace(string(bytes), old, new, -1)
|
||||
|
||||
log.Debug(i18n.G("updating %s to %s in %s", old, new, compose.Filename))
|
||||
@@ -173,11 +178,72 @@ func (r Recipe) UpdateTag(image, tag string) (bool, error) {
|
||||
if err := os.WriteFile(compose.Filename, []byte(replacedBytes), 0o764); err != nil {
|
||||
return false, err
|
||||
}
|
||||
updated = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
return updated, nil
|
||||
}
|
||||
|
||||
// UpdatePinnedTag updates an image reference to a pinned version (image:tag@sha256:...) in-place on local compose files.
|
||||
func (r Recipe) UpdatePinnedTag(image, tag, digest string) (bool, error) {
|
||||
glob := fmt.Sprintf("%s/compose**yml", r.Dir)
|
||||
image = formatter.StripTagMeta(image)
|
||||
|
||||
composeFiles, err := filepath.Glob(glob)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
updated := false
|
||||
for _, composeFile := range composeFiles {
|
||||
opts := stack.Deploy{Composefiles: []string{composeFile}}
|
||||
|
||||
sampleEnv, err := r.SampleEnv()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
compose, err := loader.LoadComposefile(opts, sampleEnv)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, service := range compose.Services {
|
||||
if service.Image == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
img, _ := reference.ParseNormalizedNamed(formatter.StripDigest(service.Image))
|
||||
composeImage := formatter.StripTagMeta(reference.Path(img))
|
||||
|
||||
if image == composeImage {
|
||||
bytes, err := ioutil.ReadFile(composeFile)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
old := service.Image
|
||||
new := fmt.Sprintf("%s:%s@%s", composeImage, tag, digest)
|
||||
|
||||
if old == new {
|
||||
continue
|
||||
}
|
||||
|
||||
replacedBytes := strings.Replace(string(bytes), old, new, -1)
|
||||
|
||||
log.Debug(i18n.G("updating pinned reference %s to %s in %s", old, new, compose.Filename))
|
||||
|
||||
if err := os.WriteFile(compose.Filename, []byte(replacedBytes), 0o764); err != nil {
|
||||
return false, err
|
||||
}
|
||||
updated = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return updated, nil
|
||||
}
|
||||
|
||||
// UpdateLabel updates a label in-place on file system local compose files.
|
||||
|
||||
+1
-7
@@ -16,7 +16,6 @@ import (
|
||||
"coopcloud.tech/tagcmp"
|
||||
"github.com/distribution/reference"
|
||||
"github.com/go-git/go-git/v5"
|
||||
gitCfg "github.com/go-git/go-git/v5/config"
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
)
|
||||
|
||||
@@ -263,12 +262,7 @@ func (r Recipe) EnsureUpToDate() error {
|
||||
return errors.New(i18n.G("unable to check out default branch in %s: %s", r.Dir, err))
|
||||
}
|
||||
|
||||
// the refspec is passed explicitly, because a repository cloned by an older abra has a
|
||||
// single-branch refspec stored in its config and would otherwise never see other branches
|
||||
fetchOpts := &git.FetchOptions{
|
||||
Tags: git.AllTags,
|
||||
RefSpecs: []gitCfg.RefSpec{"+refs/heads/*:refs/remotes/origin/*"},
|
||||
}
|
||||
fetchOpts := &git.FetchOptions{Tags: git.AllTags}
|
||||
if err := repo.Fetch(fetchOpts); err != nil {
|
||||
if !strings.Contains(err.Error(), "already up-to-date") {
|
||||
return errors.New(i18n.G("unable to fetch tags in %s: %s", r.Dir, err))
|
||||
|
||||
+18
-15
@@ -592,7 +592,15 @@ func ReadReposMetadata(debug bool) (RepoCatalogue, error) {
|
||||
// sortRecipeVersions sorts the recipe semver versions
|
||||
func sortRecipeVersions(versions RecipeVersions) {
|
||||
sort.Slice(versions, func(i, j int) bool {
|
||||
return safeCompareVersions(getVersionString(versions[i]), getVersionString(versions[j]))
|
||||
version1, err := tagcmp.Parse(getVersionString(versions[i]))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
version2, err := tagcmp.Parse(getVersionString(versions[j]))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return version1.IsLessThan(version2)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -608,23 +616,18 @@ func getVersionString(versionMap map[string]map[string]ServiceMeta) string {
|
||||
// sortVersionStrings sorts a list of semver version strings
|
||||
func sortVersionStrings(versions []string) {
|
||||
sort.Slice(versions, func(i, j int) bool {
|
||||
return safeCompareVersions(versions[i], versions[j])
|
||||
version1, err := tagcmp.Parse(versions[i])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
version2, err := tagcmp.Parse(versions[j])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return version1.IsLessThan(version2)
|
||||
})
|
||||
}
|
||||
|
||||
// Invalid versions are sorted as less than valid ones.
|
||||
func safeCompareVersions(v1, v2 string) bool {
|
||||
version1, err1 := tagcmp.Parse(v1)
|
||||
if err1 != nil {
|
||||
return true
|
||||
}
|
||||
version2, err2 := tagcmp.Parse(v2)
|
||||
if err2 != nil {
|
||||
return false
|
||||
}
|
||||
return version1.IsLessThan(version2)
|
||||
}
|
||||
|
||||
// GetRecipeCatalogueVersions list the recipe versions listed in the recipe catalogue.
|
||||
func GetRecipeCatalogueVersions(recipeName string, catl RecipeCatalogue) ([]string, error) {
|
||||
var versions []string
|
||||
|
||||
@@ -123,41 +123,3 @@ func TestGetVersionLabelLocalDoesNotUseTimeoutLabel(t *testing.T) {
|
||||
assert.NotEqual(t, label, timeout)
|
||||
}
|
||||
}
|
||||
|
||||
// This is not a full test of comparison, as that occurs in the external
|
||||
// tagcmp package. This is a sunny-day integration test
|
||||
func TestSortRecipeVersionsSortsTwoValidStrings(t *testing.T) {
|
||||
versions := RecipeVersions{
|
||||
{"2.0.0": {}}, // Index 0
|
||||
{"1.0.0": {}}, // Index 1
|
||||
}
|
||||
|
||||
sortRecipeVersions(versions)
|
||||
|
||||
assert.Equal(t, "1.0.0", getVersionString(versions[0]), "Version 1.0.0 should be first")
|
||||
assert.Equal(t, "2.0.0", getVersionString(versions[1]), "Version 2.0.0 should be second")
|
||||
}
|
||||
|
||||
func TestSortRecipeVersionsSortsInvalidVersionStringToTheBottom(t *testing.T) {
|
||||
versions := RecipeVersions{
|
||||
{"2.0.0": {}}, // Index 0
|
||||
{"invalid": {}}, // Index 1
|
||||
}
|
||||
|
||||
sortRecipeVersions(versions)
|
||||
|
||||
assert.Equal(t, "invalid", getVersionString(versions[0]), "The invalid version should be first")
|
||||
assert.Equal(t, "2.0.0", getVersionString(versions[1]), "Version 2.0.0 should be second")
|
||||
}
|
||||
|
||||
func TestSortRecipeVersionsSortsInvalidVersionStringToTheBottomInReverse(t *testing.T) {
|
||||
versions := RecipeVersions{
|
||||
{"invalid": {}}, // Index 0
|
||||
{"2.0.0": {}}, // Index 1
|
||||
}
|
||||
|
||||
sortRecipeVersions(versions)
|
||||
|
||||
assert.Equal(t, "invalid", getVersionString(versions[0]), "The invalid version should be first")
|
||||
assert.Equal(t, "2.0.0", getVersionString(versions[1]), "Version 2.0.0 should be second")
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ version: "3.8"
|
||||
|
||||
services:
|
||||
app:
|
||||
image: nginx:1.31.8
|
||||
image: nginx:1.31.4
|
||||
secrets:
|
||||
- test_pass_one
|
||||
- test_pass_two
|
||||
|
||||
@@ -122,39 +122,6 @@ teardown(){
|
||||
assert_failure
|
||||
}
|
||||
|
||||
@test "create new app with commit from another branch" {
|
||||
branchHash=$(_get_other_branch_hash)
|
||||
if [[ -z "$branchHash" ]]; then
|
||||
skip "$TEST_RECIPE has no branch besides main"
|
||||
fi
|
||||
|
||||
# re-clone the recipe the way an older abra did, with a single-branch refspec. --no-local
|
||||
# forces a real transfer, a local clone would hardlink the whole object database and leave
|
||||
# the commit reachable
|
||||
run rm -rf "$ABRA_DIR/recipes/$TEST_RECIPE"
|
||||
assert_success
|
||||
|
||||
run git clone -q --no-local --single-branch --branch main \
|
||||
"$ABRA_DIR/origin-recipes/$TEST_RECIPE.git" "$ABRA_DIR/recipes/$TEST_RECIPE"
|
||||
assert_success
|
||||
|
||||
# the commit has to be genuinely missing, otherwise this test passes for the wrong reason
|
||||
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" rev-parse --verify "$branchHash^{commit}"
|
||||
assert_failure
|
||||
|
||||
run $ABRA app new "$TEST_RECIPE" "$branchHash" \
|
||||
--no-input \
|
||||
--server "$TEST_SERVER" \
|
||||
--domain "$TEST_APP_DOMAIN"
|
||||
assert_success
|
||||
assert_exists "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
||||
|
||||
# the recipe names itself in TYPE, which differs per branch, so only the version is checked
|
||||
run grep -q "TYPE=.*:${branchHash}$" \
|
||||
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
||||
assert_success
|
||||
}
|
||||
|
||||
@test "does not overwrite existing env files" {
|
||||
run $ABRA app new "$TEST_RECIPE" \
|
||||
--no-input \
|
||||
|
||||
@@ -56,13 +56,6 @@ _get_tag_hash() {
|
||||
echo $(git -C "$ABRA_DIR/recipes/$TEST_RECIPE" rev-list -n 1 "$1")
|
||||
}
|
||||
|
||||
_get_other_branch_hash() {
|
||||
# asked from the origin mirror, not from the recipe checkout, so that the result does not
|
||||
# depend on what has been fetched. empty when the recipe only has a default branch
|
||||
echo $(git ls-remote "$ABRA_DIR/origin-recipes/$TEST_RECIPE.git" \
|
||||
| grep 'refs/heads/' | grep -v 'refs/heads/main$' | head -1 | cut -f1)
|
||||
}
|
||||
|
||||
_get_head_hash() {
|
||||
echo $(git -C "$ABRA_DIR/recipes/$TEST_RECIPE" show -s --format="%H" HEAD)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ version: "3.8"
|
||||
|
||||
services:
|
||||
app:
|
||||
image: nginx:1.31.8
|
||||
image: nginx:1.31.4
|
||||
networks:
|
||||
- proxy
|
||||
deploy:
|
||||
|
||||
-4
@@ -329,10 +329,6 @@ func (c *Config) unmarshalCore() {
|
||||
if parsed := parseConfigBool(s.Options.Get(protectHFSKey)); parsed.IsSet() {
|
||||
c.Core.ProtectHFS = parsed
|
||||
}
|
||||
|
||||
if s.Options.Get(repositoryFormatVersionKey) == string(format.Version_1) {
|
||||
c.Core.RepositoryFormatVersion = format.Version_1
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Config) unmarshalUser() {
|
||||
|
||||
+3
-25
@@ -139,12 +139,8 @@ func (dw *deltaSelector) fixAndBreakChains(objectsToPack []*ObjectToPack) error
|
||||
m[otp.Hash()] = otp
|
||||
}
|
||||
|
||||
// visiting holds the objects on the current resolution path, so that a
|
||||
// delta chain looping back on itself can be detected and broken.
|
||||
visiting := make(map[plumbing.Hash]bool)
|
||||
|
||||
for _, otp := range objectsToPack {
|
||||
if err := dw.fixAndBreakChainsOne(m, otp, visiting); err != nil {
|
||||
if err := dw.fixAndBreakChainsOne(m, otp); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -152,11 +148,7 @@ func (dw *deltaSelector) fixAndBreakChains(objectsToPack []*ObjectToPack) error
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dw *deltaSelector) fixAndBreakChainsOne(
|
||||
objectsToPack map[plumbing.Hash]*ObjectToPack,
|
||||
otp *ObjectToPack,
|
||||
visiting map[plumbing.Hash]bool,
|
||||
) error {
|
||||
func (dw *deltaSelector) fixAndBreakChainsOne(objectsToPack map[plumbing.Hash]*ObjectToPack, otp *ObjectToPack) error {
|
||||
if !otp.Object.Type().IsDelta() {
|
||||
return nil
|
||||
}
|
||||
@@ -182,21 +174,7 @@ func (dw *deltaSelector) fixAndBreakChainsOne(
|
||||
return dw.undeltify(otp)
|
||||
}
|
||||
|
||||
// Mark this object as being resolved before looking at its base, so that
|
||||
// a delta based on itself is caught by the check below.
|
||||
h := otp.Hash()
|
||||
visiting[h] = true
|
||||
defer delete(visiting, h)
|
||||
|
||||
// A delta chain that loops back onto an object we are already resolving
|
||||
// cannot be written: every delta needs its base written first. Break the
|
||||
// chain here instead of following the cycle, which would recurse until
|
||||
// the goroutine stack is exhausted.
|
||||
if visiting[do.BaseHash()] {
|
||||
return dw.undeltify(otp)
|
||||
}
|
||||
|
||||
if err := dw.fixAndBreakChainsOne(objectsToPack, base, visiting); err != nil {
|
||||
if err := dw.fixAndBreakChainsOne(objectsToPack, base); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
+2
-34
@@ -474,14 +474,6 @@ type TreeWalker struct {
|
||||
recursive bool
|
||||
seen map[plumbing.Hash]bool
|
||||
|
||||
// skipPathValidation disables the pathutil.ValidTreePath check in Next.
|
||||
// It is set by inspection-only callers (e.g. the revlist object walk)
|
||||
// that never funnel entry names into the filesystem and must enumerate
|
||||
// trees faithfully, including entries with names upstream Git accepts
|
||||
// but that are unsafe to materialise (control characters, `.git`-shaped
|
||||
// names).
|
||||
skipPathValidation bool
|
||||
|
||||
s storer.EncodedObjectStorer
|
||||
t *Tree
|
||||
}
|
||||
@@ -504,32 +496,10 @@ func NewTreeWalker(t *Tree, recursive bool, seen map[plumbing.Hash]bool) *TreeWa
|
||||
}
|
||||
}
|
||||
|
||||
// SkipPathValidation disables the pathutil.ValidTreePath check performed by
|
||||
// Next, and must be called before the first Next.
|
||||
//
|
||||
// It is for inspection-only walks that never funnel an entry name into the
|
||||
// filesystem — enumerating which objects exist, rather than materialising
|
||||
// them — and that must therefore see the tree faithfully, including entries
|
||||
// whose names upstream Git accepts but that are unsafe to check out. Callers
|
||||
// that hand the returned name to filesystem or archive output must not use
|
||||
// it; path safety for those is enforced at the materialisation boundaries
|
||||
// (FindEntry, TreeEntryFile, archive, FileIter).
|
||||
func (w *TreeWalker) SkipPathValidation() {
|
||||
w.skipPathValidation = true
|
||||
}
|
||||
|
||||
// Next returns the next object from the tree. Objects are returned in order
|
||||
// and subtrees are included. After the last object has been returned further
|
||||
// calls to Next() will return io.EOF.
|
||||
//
|
||||
// Each entry's name is validated against pathutil.ValidTreePath as it
|
||||
// surfaces, so callers that funnel the returned name into filesystem
|
||||
// or archive output can trust it is free of `.git`-shaped components,
|
||||
// HFS+/NTFS variants, Windows reserved names, and traversal sequences.
|
||||
// A malformed entry stops the walk with the validator's error;
|
||||
// inspection-only callers that need to enumerate raw, unvalidated
|
||||
// names can read Tree.Entries directly or call SkipPathValidation.
|
||||
//
|
||||
// In the current implementation any objects which cannot be found in the
|
||||
// underlying repository will be skipped automatically. It is possible that this
|
||||
// may change in future versions.
|
||||
@@ -566,10 +536,8 @@ func (w *TreeWalker) Next() (name string, entry TreeEntry, err error) {
|
||||
continue
|
||||
}
|
||||
|
||||
if !w.skipPathValidation {
|
||||
if err := pathutil.ValidTreePath(entry.Name); err != nil {
|
||||
return name, entry, err
|
||||
}
|
||||
if err := pathutil.ValidTreePath(entry.Name); err != nil {
|
||||
return name, entry, err
|
||||
}
|
||||
|
||||
if entry.Mode == filemode.Dir {
|
||||
|
||||
-6
@@ -106,12 +106,6 @@ func transformChildren(t *Tree) ([]noder.Noder, error) {
|
||||
ret := make([]noder.Noder, 0, len(t.Entries))
|
||||
|
||||
walker := NewTreeWalker(t, false, nil) // don't recurse
|
||||
// The diff walk is read-only and never materialises entry names into the
|
||||
// filesystem, so it must enumerate the tree faithfully — including entries
|
||||
// with names that are unsafe to check out but valid per upstream Git (e.g.
|
||||
// control characters). Path safety is enforced at materialisation
|
||||
// boundaries (FindEntry, TreeEntryFile, archive, FileIter), not here.
|
||||
walker.SkipPathValidation()
|
||||
// don't defer walker.Close() for efficiency reasons.
|
||||
for {
|
||||
_, e, err = walker.Next()
|
||||
|
||||
-7
@@ -187,13 +187,6 @@ func iterateCommitTrees(
|
||||
cb(tree.Hash)
|
||||
|
||||
treeWalker := object.NewTreeWalker(tree, true, seen)
|
||||
// This walk only enumerates which objects are reachable, to decide what
|
||||
// to send; it never materialises an entry name into the filesystem. It
|
||||
// must therefore enumerate the tree faithfully, including entries with
|
||||
// names that are unsafe to check out but valid per upstream Git (e.g.
|
||||
// control characters). Path safety is enforced at materialisation
|
||||
// boundaries (FindEntry, TreeEntryFile, archive, FileIter), not here.
|
||||
treeWalker.SkipPathValidation()
|
||||
|
||||
for {
|
||||
_, e, err := treeWalker.Next()
|
||||
|
||||
+22
-379
@@ -6,12 +6,8 @@ import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strconv"
|
||||
@@ -77,7 +73,7 @@ func advertisedReferences(ctx context.Context, s *session, serviceName string) (
|
||||
s.endpoint.String(), infoRefsPath, serviceName,
|
||||
)
|
||||
|
||||
req, err := newRequest(http.MethodGet, url, nil)
|
||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -181,24 +177,6 @@ func NewClient(c *http.Client) transport.Transport {
|
||||
// and other custom options specific to the client.
|
||||
// If the net/http client is nil or empty, it will use a net/http client configured
|
||||
// with http.DefaultTransport.
|
||||
//
|
||||
// Credentials this client adds where the transport cannot see them are not
|
||||
// subject to the redirect stripping described on AuthMethod: a RoundTripper
|
||||
// injects after the hop is decided, and Client.Jar is consulted after
|
||||
// CheckRedirect, so a domain cookie still follows a redirect to a subdomain
|
||||
// the transport counts as another origin. Apply them in an AuthMethod instead
|
||||
// if that is not wanted. A CheckRedirect hook set on this client runs
|
||||
// alongside the transport's own, but any header it adds when a redirect
|
||||
// leaves the repository's origin is discarded the same way.
|
||||
//
|
||||
// A RoundTripper is therefore also how to authenticate to a new origin a
|
||||
// redirect has moved the repository to: match on the request URL and inject
|
||||
// the credential only for that origin, so it is not sent anywhere else. The
|
||||
// transport keeps its own CheckRedirect on the copy it makes of this client,
|
||||
// so the policy and the origin checks still apply.
|
||||
//
|
||||
// None of this applies to a RoundTripper that follows redirects itself:
|
||||
// CheckRedirect is not consulted then, so no stripping happens at all.
|
||||
func NewClientWithOptions(c *http.Client, opts *ClientOptions) transport.Transport {
|
||||
if c == nil {
|
||||
c = &http.Client{
|
||||
@@ -392,20 +370,11 @@ func (s *session) ModifyEndpointIfRedirect(res *http.Response) error {
|
||||
if !strings.HasSuffix(r.URL.Path, infoRefsPath) {
|
||||
return fmt.Errorf("http redirect: target %q does not end with %s", r.URL.Path, infoRefsPath)
|
||||
}
|
||||
// A scheme is case-insensitive per RFC 3986, and checkRedirect folds case
|
||||
// when it reads the same hop, so fold here too rather than reject a
|
||||
// spelling that check let through. url.Parse and transport.NewEndpoint
|
||||
// both lowercase what they parse, so only a hand-built URL or Endpoint
|
||||
// arrives uppercased. The folded form is what gets stored below, so every
|
||||
// later request built from the endpoint carries the canonical spelling.
|
||||
scheme := strings.ToLower(r.URL.Scheme)
|
||||
if scheme != "http" && scheme != "https" {
|
||||
if r.URL.Scheme != "http" && r.URL.Scheme != "https" {
|
||||
return fmt.Errorf("http redirect: unsupported scheme %q", r.URL.Scheme)
|
||||
}
|
||||
// schemeUpgrade rather than an inline comparison, so the one cross-scheme
|
||||
// change go-git permits has a single definition shared with
|
||||
// credentialsMayFollow.
|
||||
if !strings.EqualFold(scheme, s.endpoint.Protocol) && !schemeUpgrade(s.endpoint.Protocol, scheme) {
|
||||
if r.URL.Scheme != s.endpoint.Protocol &&
|
||||
!(s.endpoint.Protocol == "http" && r.URL.Scheme == "https") {
|
||||
return fmt.Errorf("http redirect: changes scheme from %q to %q", s.endpoint.Protocol, r.URL.Scheme)
|
||||
}
|
||||
|
||||
@@ -415,20 +384,7 @@ func (s *session) ModifyEndpointIfRedirect(res *http.Response) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// The session stores the endpoint and re-applies its credentials on every
|
||||
// later request, so clear them once the redirect has left the origin they
|
||||
// were issued for. This uses the same predicate as stripCredentials, so
|
||||
// both halves share one definition of an origin.
|
||||
//
|
||||
// The two are deliberately asymmetric in one respect: stripCredentials is
|
||||
// sticky over the whole chain, so an origin -> evil -> origin redirect
|
||||
// leaves the discovery GET's later hops unauthenticated even though the
|
||||
// chain returned home. This compares the endpoint only against the final
|
||||
// URL, so the same round trip leaves the session authenticated. That is
|
||||
// not a leak - the final URL's origin is the original one - but it means
|
||||
// such a chain can make the discovery GET anonymous while the session's
|
||||
// POSTs are authenticated, which can surface as a confusing 401.
|
||||
if !credentialsMayFollow(endpointURL(s.endpoint), r.URL) {
|
||||
if host != s.endpoint.Host || effectivePort(r.URL.Scheme, port) != effectivePort(s.endpoint.Protocol, s.endpoint.Port) {
|
||||
s.endpoint.User = ""
|
||||
s.endpoint.Password = ""
|
||||
s.auth = nil
|
||||
@@ -437,7 +393,7 @@ func (s *session) ModifyEndpointIfRedirect(res *http.Response) error {
|
||||
s.endpoint.Host = host
|
||||
s.endpoint.Port = port
|
||||
|
||||
s.endpoint.Protocol = scheme
|
||||
s.endpoint.Protocol = r.URL.Scheme
|
||||
s.endpoint.Path = r.URL.Path[:len(r.URL.Path)-len(infoRefsPath)]
|
||||
return nil
|
||||
}
|
||||
@@ -463,132 +419,19 @@ func endpointPort(port string) (int, error) {
|
||||
return parsed, nil
|
||||
}
|
||||
|
||||
// schemeUpgrade reports whether the scheme transition from one URL to another
|
||||
// is the one cross-scheme change go-git permits: a plain-http origin upgrading
|
||||
// to https. It strictly improves confidentiality and is how servers steer
|
||||
// clients off cleartext.
|
||||
//
|
||||
// Permitting it at all is a deliberate deviation: curl, git and the Fetch
|
||||
// standard all count scheme as part of host identity and drop credentials on
|
||||
// the upgrade. Auth is sent pre-emptively here, so an http origin has already
|
||||
// spent its credential in cleartext on the first request and refusing the
|
||||
// upgrade would break the clone without unspending it. The host is unchanged,
|
||||
// where an on-path attacker needs a valid certificate to receive anything.
|
||||
func schemeUpgrade(from, to string) bool {
|
||||
return strings.EqualFold(from, "http") && strings.EqualFold(to, "https")
|
||||
}
|
||||
func effectivePort(scheme string, port int) int {
|
||||
if port != 0 {
|
||||
return port
|
||||
}
|
||||
|
||||
// canonicalHost returns u's hostname in the form origins are compared in.
|
||||
//
|
||||
// An address literal is normalised by netip, so the many spellings of one
|
||||
// address are one origin. Two literals are the same origin exactly when netip
|
||||
// parses them to the same Addr, which is also how the WHATWG URL Standard
|
||||
// compares hosts. An IPv4-mapped literal is deliberately not unmapped onto
|
||||
// the IPv4 it dials: reaching the same endpoint is not the same authority,
|
||||
// since net/http sends the literal as written in Host and a server may route
|
||||
// the two spellings to different virtual hosts.
|
||||
//
|
||||
// netip also keeps a scope zone verbatim, which is what origin comparison
|
||||
// needs: net resolves a zone to an interface by exact name, so folding %eth0
|
||||
// onto %ETH0 would call two hosts the same origin that net dials down
|
||||
// different interfaces.
|
||||
//
|
||||
// A registered name is ASCII-lowercased. That fold is the only liberty taken;
|
||||
// every other difference in spelling is a different origin.
|
||||
//
|
||||
// A trailing root dot is one such difference and is kept, for the same reason
|
||||
// as the IPv4-mapped literal: curl and the WHATWG URL Standard both hold
|
||||
// "example.com." and "example.com" to be distinct hosts, and although
|
||||
// crypto/tls and crypto/x509 fold the dot when they authenticate the peer,
|
||||
// net/http sends the name as written in Host.
|
||||
//
|
||||
// The fold is deliberately ASCII-only. strings.ToLower and strings.EqualFold
|
||||
// apply Unicode case mapping, which folds U+03C2 onto U+03C3 and so would
|
||||
// call two hosts the same origin when they resolve to different servers. An
|
||||
// ASCII-only fold cannot merge two names DNS keeps apart.
|
||||
//
|
||||
// No IDNA mapping is applied either, so a unicode hostname is a different
|
||||
// origin from the punycode encoding of it, and from another Unicode case of
|
||||
// itself, even though all three reach the same server. Mapping through
|
||||
// golang.org/x/net/idna would join them, but it can only widen this equality,
|
||||
// never narrow it, so leaving it out can cost a credential across such a
|
||||
// redirect and cannot forward one. Against that cost, go-git pins x/net while
|
||||
// net/http uses the copy vendored into the toolchain: the two are versioned
|
||||
// separately, so a release that moves the Unicode tables under one and not
|
||||
// the other would have this merge origins net/http still dials apart. That is
|
||||
// the failure this comparison exists to prevent, and comparing bytes has no
|
||||
// such mode.
|
||||
func canonicalHost(u *url.URL) string {
|
||||
host := u.Hostname()
|
||||
if addr, err := netip.ParseAddr(host); err == nil {
|
||||
return addr.String()
|
||||
switch strings.ToLower(scheme) {
|
||||
case "http":
|
||||
return 80
|
||||
case "https":
|
||||
return 443
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
b := []byte(host)
|
||||
for i := range b {
|
||||
if b[i] >= 'A' && b[i] <= 'Z' {
|
||||
b[i] += 'a' - 'A'
|
||||
}
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// effectivePort returns u's port as the connection will use it: the scheme's
|
||||
// well-known port when the URL does not spell one out, and without leading
|
||||
// zeroes, so "https://x", "https://x:443" and "https://x:0443" all agree.
|
||||
func effectivePort(u *url.URL) string {
|
||||
port := u.Port()
|
||||
if port == "" {
|
||||
switch strings.ToLower(u.Scheme) {
|
||||
case "http":
|
||||
return "80"
|
||||
case "https":
|
||||
return "443"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
if trimmed := strings.TrimLeft(port, "0"); trimmed != "" {
|
||||
return trimmed
|
||||
}
|
||||
return "0"
|
||||
}
|
||||
|
||||
// credentialsMayFollow reports whether credentials issued for one URL may be
|
||||
// sent to another.
|
||||
//
|
||||
// The relation is deliberately asymmetric: scheme, host and effective port
|
||||
// must all match, except that a plain http origin may upgrade to https on the
|
||||
// same host (see schemeUpgrade). That exception is confined to the two
|
||||
// default ports: 80 to 443 is the upgrade servers actually steer clients
|
||||
// through, whereas a non-default port carries no such convention, so
|
||||
// http://host:8080 to https://host:8443 is a move to another origin like any
|
||||
// other port change.
|
||||
//
|
||||
// Host matching is exact. Unlike Go's http.Client, which forwards credentials
|
||||
// from a host to any subdomain of it, a subdomain is a different origin here —
|
||||
// matching canonical git and libcurl.
|
||||
func credentialsMayFollow(from, to *url.URL) bool {
|
||||
if canonicalHost(from) != canonicalHost(to) {
|
||||
return false
|
||||
}
|
||||
if strings.EqualFold(from.Scheme, to.Scheme) {
|
||||
return effectivePort(from) == effectivePort(to)
|
||||
}
|
||||
return schemeUpgrade(from.Scheme, to.Scheme) &&
|
||||
effectivePort(from) == "80" && effectivePort(to) == "443"
|
||||
}
|
||||
|
||||
// endpointURL renders an Endpoint's origin as a URL, so that the session's
|
||||
// credential clearing and the per-hop stripping share one definition of an
|
||||
// origin and cannot drift apart.
|
||||
func endpointURL(ep *transport.Endpoint) *url.URL {
|
||||
host := strings.Trim(ep.Host, "[]")
|
||||
if ep.Port != 0 {
|
||||
host = net.JoinHostPort(host, strconv.Itoa(ep.Port))
|
||||
} else if strings.Contains(host, ":") {
|
||||
host = "[" + host + "]"
|
||||
}
|
||||
return &url.URL{Scheme: ep.Protocol, Host: host}
|
||||
}
|
||||
|
||||
func (c *client) cloneHTTPClient(transport http.RoundTripper) *http.Client {
|
||||
@@ -605,215 +448,26 @@ func wrapCheckRedirect(policy RedirectPolicy, next func(*http.Request, []*http.R
|
||||
if err := checkRedirect(req, via, policy); err != nil {
|
||||
return err
|
||||
}
|
||||
// Strip before the caller's hook so it observes what will actually
|
||||
// be sent, and again afterwards so a hook of the common "preserve
|
||||
// my headers across redirects" shape - which copies from via[0],
|
||||
// the original unsanitized request - cannot reinstate them.
|
||||
// Carrying credentials across an origin boundary is deliberately
|
||||
// unsupported.
|
||||
stripCredentials(req, via)
|
||||
if next != nil {
|
||||
if err := next(req, via); err != nil {
|
||||
return err
|
||||
}
|
||||
return next(req, via)
|
||||
}
|
||||
stripCredentials(req, via)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// safeHeaders lists the headers go-git sets itself, none of which can carry a
|
||||
// caller credential. stripCredentials keeps only these when a redirect leaves
|
||||
// the credential's origin. Adding a name here makes it forwardable across an
|
||||
// origin boundary - do not add anything a caller can put a secret in.
|
||||
//
|
||||
// This narrows rather than eliminates the exposure: an AuthMethod that writes
|
||||
// a credential into one of these names directly - for example
|
||||
// Header.Set("User-Agent", "token "+secret) - still survives a cross-origin
|
||||
// redirect. Such a value is also sent to the origin and to any proxy in path,
|
||||
// so it should not be placed there whether or not a redirect follows.
|
||||
var safeHeaders = map[string]struct{}{
|
||||
"User-Agent": {},
|
||||
"Host": {},
|
||||
"Accept": {},
|
||||
"Content-Type": {},
|
||||
"Content-Length": {},
|
||||
}
|
||||
|
||||
func filterHeaders(h http.Header) http.Header {
|
||||
filtered := make(http.Header)
|
||||
for key, values := range h {
|
||||
if _, ok := safeHeaders[http.CanonicalHeaderKey(key)]; ok {
|
||||
filtered[key] = values
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
// stripCredentials removes credentials from req once the redirect chain has
|
||||
// left the origin of the original, credential-bearing request.
|
||||
//
|
||||
// CheckRedirect is the only hook that runs while a redirected request's
|
||||
// headers are still mutable: http.Client.Do performs the entire chain
|
||||
// internally, so anything the transport does after Do returns - including
|
||||
// ModifyEndpointIfRedirect - is too late for the hops themselves.
|
||||
//
|
||||
// Two subtleties:
|
||||
//
|
||||
// - net/http rebuilds every redirect request from the original request's
|
||||
// headers before calling this, so a header removed at one hop reappears
|
||||
// at the next. The decision is therefore recomputed per hop.
|
||||
// - The decision is sticky: once the chain has left the origin, credentials
|
||||
// stay gone even if a later hop returns to it. Stickiness is derived from
|
||||
// via rather than stored, because this closure is shared across a
|
||||
// session's requests.
|
||||
//
|
||||
// Stripping keeps only the headers go-git sets itself (safeHeaders). An
|
||||
// allowlist is used rather than a list of credential header names because
|
||||
// caller credentials arrive under names that cannot be enumerated -
|
||||
// PRIVATE-TOKEN, X-Api-Key, gateway headers - which is exactly what
|
||||
// net/http's fixed list of sensitive header names gets wrong. It is also
|
||||
// immune to header-name canonicalisation: an AuthMethod that writes a raw map
|
||||
// key is still removed.
|
||||
func stripCredentials(req *http.Request, via []*http.Request) {
|
||||
if len(via) == 0 {
|
||||
return
|
||||
}
|
||||
// net/http sets a URL on every request it builds, and req.URL is non-nil
|
||||
// by construction: checkRedirect dereferences req.URL.Scheme on each path
|
||||
// that returns nil, so it runs first or not at all. This nil check and
|
||||
// the two in crossedOrigin are defensive, against a synthetic caller.
|
||||
// Each treats a URL it cannot read as an origin crossing; removing one
|
||||
// panics in canonicalHost rather than leaking.
|
||||
if origin := via[0].URL; origin != nil && !crossedOrigin(origin, req, via) {
|
||||
return
|
||||
}
|
||||
req.Header = filterHeaders(req.Header)
|
||||
if req.URL != nil {
|
||||
req.URL.User = nil
|
||||
}
|
||||
}
|
||||
|
||||
// crossedOrigin reports whether any hop so far, including the pending one, has
|
||||
// left origin.
|
||||
func crossedOrigin(origin *url.URL, req *http.Request, via []*http.Request) bool {
|
||||
if req.URL == nil || !credentialsMayFollow(origin, req.URL) {
|
||||
return true
|
||||
}
|
||||
for _, prev := range via[1:] {
|
||||
if prev.URL == nil || !credentialsMayFollow(origin, prev.URL) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// redactedURL returns the string form of u with the userinfo password
|
||||
// replaced, for use in error messages. (*url.URL).String() renders the
|
||||
// password verbatim, and request URLs are built from the endpoint, which
|
||||
// carries whatever credentials the caller put in the clone URL.
|
||||
func redactedURL(u *url.URL) string {
|
||||
if u == nil {
|
||||
return ""
|
||||
}
|
||||
if u.User == nil {
|
||||
return u.String()
|
||||
}
|
||||
if _, hasPassword := u.User.Password(); !hasPassword {
|
||||
return u.String()
|
||||
}
|
||||
redacted := *u
|
||||
redacted.User = url.UserPassword(u.User.Username(), "REDACTED")
|
||||
return redacted.String()
|
||||
}
|
||||
|
||||
// redactedRawURL is redactedURL for a string that may not parse. Request URLs
|
||||
// are assembled from Endpoint.String(), which re-emits Endpoint.Path raw, so a
|
||||
// path holding a stray percent produces a string url.Parse rejects. url.Parse
|
||||
// reports the input verbatim and applies no redaction of its own; only
|
||||
// http.Client strips a password, and only from errors it raises itself.
|
||||
func redactedRawURL(raw string) string {
|
||||
i := strings.Index(raw, "://")
|
||||
if i < 0 {
|
||||
return raw
|
||||
}
|
||||
authority := raw[i+3:]
|
||||
if end := strings.IndexByte(authority, '/'); end >= 0 {
|
||||
authority = authority[:end]
|
||||
}
|
||||
at := strings.LastIndexByte(authority, '@')
|
||||
if at < 0 {
|
||||
return raw
|
||||
}
|
||||
colon := strings.IndexByte(authority[:at], ':')
|
||||
if colon < 0 {
|
||||
// Username only, left alone, as redactedURL leaves it.
|
||||
return raw
|
||||
}
|
||||
return raw[:i+3+colon+1] + "REDACTED" + raw[i+3+at:]
|
||||
}
|
||||
|
||||
// newRequest wraps http.NewRequest so that a URL it cannot parse does not
|
||||
// reach the caller with the endpoint's credentials still in it.
|
||||
func newRequest(method, rawURL string, body io.Reader) (*http.Request, error) {
|
||||
req, err := http.NewRequest(method, rawURL, body)
|
||||
if err != nil {
|
||||
var uerr *url.Error
|
||||
if errors.As(err, &uerr) {
|
||||
uerr.URL = redactedRawURL(uerr.URL)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func checkRedirect(req *http.Request, via []*http.Request, policy RedirectPolicy) error {
|
||||
// CheckRedirect is the only hook that runs before the next hop leaves
|
||||
// the client. ModifyEndpointIfRedirect inspects the chain after
|
||||
// client.Do has followed all of it, so a hop rejected there has already
|
||||
// carried the request headers to its server.
|
||||
//
|
||||
// The wording matches the message ModifyEndpointIfRedirect produces for
|
||||
// the same hop, which this check reaches first.
|
||||
if len(via) != 0 {
|
||||
// A hop whose scheme cannot be read cannot be shown not to have been
|
||||
// https, so it is assumed to have been, and a cleartext target is
|
||||
// rejected. Skipping the comparison instead would let an
|
||||
// undeterminable hop turn the check off, which is the wrong default
|
||||
// for a credential control; crossedOrigin fails closed the same way.
|
||||
// An empty scheme is as unreadable as a nil URL, so both take the
|
||||
// assumed-https default.
|
||||
//
|
||||
// The comparisons fold case because a scheme is case-insensitive per
|
||||
// RFC 3986. net/url lowercases what it parses, so only a hand-built
|
||||
// URL reaches here uppercased - the same synthetic caller the nil
|
||||
// checks guard against - and for that caller "HTTPS" to "http" is
|
||||
// still a downgrade.
|
||||
prevScheme := "https"
|
||||
if prev := via[len(via)-1]; prev.URL != nil && prev.URL.Scheme != "" {
|
||||
prevScheme = prev.URL.Scheme
|
||||
}
|
||||
if strings.EqualFold(prevScheme, "https") && strings.EqualFold(req.URL.Scheme, "http") {
|
||||
return fmt.Errorf("http redirect: changes scheme from %q to %q: %s",
|
||||
prevScheme, req.URL.Scheme, redactedURL(req.URL))
|
||||
}
|
||||
}
|
||||
|
||||
switch policy {
|
||||
case FollowRedirects:
|
||||
case NoFollowRedirects:
|
||||
return fmt.Errorf("http redirect: redirects disabled to %s", redactedURL(req.URL))
|
||||
return fmt.Errorf("http redirect: redirects disabled to %s", req.URL)
|
||||
case "", FollowInitialRedirects:
|
||||
if !isInitialRequest(req) {
|
||||
return fmt.Errorf("http redirect: redirect on non-initial request to %s", redactedURL(req.URL))
|
||||
return fmt.Errorf("http redirect: redirect on non-initial request to %s", req.URL)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("http redirect: invalid redirect policy %q", policy)
|
||||
}
|
||||
// Folded for the same reason as the guard above: a scheme is
|
||||
// case-insensitive per RFC 3986, so a spelling the downgrade check read
|
||||
// as https must not be rejected here as a scheme go-git cannot speak.
|
||||
if !strings.EqualFold(req.URL.Scheme, "http") && !strings.EqualFold(req.URL.Scheme, "https") {
|
||||
if req.URL.Scheme != "http" && req.URL.Scheme != "https" {
|
||||
return fmt.Errorf("http redirect: unsupported scheme %q", req.URL.Scheme)
|
||||
}
|
||||
if len(via) >= 10 {
|
||||
@@ -827,17 +481,6 @@ func (*session) Close() error {
|
||||
}
|
||||
|
||||
// AuthMethod is concrete implementation of common.AuthMethod for HTTP services
|
||||
//
|
||||
// Headers SetAuth adds are dropped when a redirect leaves the repository's
|
||||
// origin: only the headers the transport sets itself survive that boundary.
|
||||
// This applies to non-credential headers too, so an implementation that adds a
|
||||
// trace or tenant header loses it on such a hop.
|
||||
//
|
||||
// That filter matches header names, not values. An implementation that writes
|
||||
// a credential into a name the transport also uses - User-Agent, Host, Accept,
|
||||
// Content-Type, Content-Length - has that value carried across the boundary
|
||||
// with the name. Such a credential is also sent to the origin and to any proxy
|
||||
// in path, so it should not be placed there whether or not a redirect follows.
|
||||
type AuthMethod interface {
|
||||
transport.AuthMethod
|
||||
SetAuth(r *http.Request)
|
||||
@@ -955,6 +598,6 @@ func (e *Err) StatusCode() int {
|
||||
|
||||
func (e *Err) Error() string {
|
||||
return fmt.Sprintf("unexpected requesting %q status code: %d",
|
||||
redactedURL(e.Response.Request.URL), e.Response.StatusCode,
|
||||
e.Response.Request.URL, e.Response.StatusCode,
|
||||
)
|
||||
}
|
||||
|
||||
+1
-1
@@ -88,7 +88,7 @@ func (s *rpSession) doRequest(
|
||||
body = content
|
||||
}
|
||||
|
||||
req, err := newRequest(method, url, body)
|
||||
req, err := http.NewRequest(method, url, body)
|
||||
if err != nil {
|
||||
return nil, plumbing.NewPermanentError(err)
|
||||
}
|
||||
|
||||
+1
-1
@@ -86,7 +86,7 @@ func (s *upSession) doRequest(
|
||||
body = content
|
||||
}
|
||||
|
||||
req, err := newRequest(method, url, body)
|
||||
req, err := http.NewRequest(method, url, body)
|
||||
if err != nil {
|
||||
return nil, plumbing.NewPermanentError(err)
|
||||
}
|
||||
|
||||
-1
@@ -433,7 +433,6 @@ func dotGitCommonDirectory(fs billy.Filesystem) (commonDir billy.Filesystem, err
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer ioutil.CheckClose(f, &err)
|
||||
|
||||
b, err := io.ReadAll(f)
|
||||
if err != nil {
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
* -text
|
||||
*.bin -text -diff
|
||||
*.md text eol=lf
|
||||
|
||||
+700
-700
File diff suppressed because it is too large
Load Diff
+78
-78
@@ -1,79 +1,79 @@
|
||||
# Finite State Entropy
|
||||
|
||||
This package provides Finite State Entropy encoding and decoding.
|
||||
|
||||
Finite State Entropy (also referenced as [tANS](https://en.wikipedia.org/wiki/Asymmetric_numeral_systems#tANS))
|
||||
encoding provides a fast near-optimal symbol encoding/decoding
|
||||
for byte blocks as implemented in [zstandard](https://github.com/facebook/zstd).
|
||||
|
||||
This can be used for compressing input with a lot of similar input values to the smallest number of bytes.
|
||||
This does not perform any multi-byte [dictionary coding](https://en.wikipedia.org/wiki/Dictionary_coder) as LZ coders,
|
||||
but it can be used as a secondary step to compressors (like Snappy) that does not do entropy encoding.
|
||||
|
||||
* [Godoc documentation](https://godoc.org/github.com/klauspost/compress/fse)
|
||||
|
||||
## News
|
||||
|
||||
* Feb 2018: First implementation released. Consider this beta software for now.
|
||||
|
||||
# Usage
|
||||
|
||||
This package provides a low level interface that allows to compress single independent blocks.
|
||||
|
||||
Each block is separate, and there is no built in integrity checks.
|
||||
This means that the caller should keep track of block sizes and also do checksums if needed.
|
||||
|
||||
Compressing a block is done via the [`Compress`](https://godoc.org/github.com/klauspost/compress/fse#Compress) function.
|
||||
You must provide input and will receive the output and maybe an error.
|
||||
|
||||
These error values can be returned:
|
||||
|
||||
| Error | Description |
|
||||
|---------------------|-----------------------------------------------------------------------------|
|
||||
| `<nil>` | Everything ok, output is returned |
|
||||
| `ErrIncompressible` | Returned when input is judged to be too hard to compress |
|
||||
| `ErrUseRLE` | Returned from the compressor when the input is a single byte value repeated |
|
||||
| `(error)` | An internal error occurred. |
|
||||
|
||||
As can be seen above there are errors that will be returned even under normal operation so it is important to handle these.
|
||||
|
||||
To reduce allocations you can provide a [`Scratch`](https://godoc.org/github.com/klauspost/compress/fse#Scratch) object
|
||||
that can be re-used for successive calls. Both compression and decompression accepts a `Scratch` object, and the same
|
||||
object can be used for both.
|
||||
|
||||
Be aware, that when re-using a `Scratch` object that the *output* buffer is also re-used, so if you are still using this
|
||||
you must set the `Out` field in the scratch to nil. The same buffer is used for compression and decompression output.
|
||||
|
||||
Decompressing is done by calling the [`Decompress`](https://godoc.org/github.com/klauspost/compress/fse#Decompress) function.
|
||||
You must provide the output from the compression stage, at exactly the size you got back. If you receive an error back
|
||||
your input was likely corrupted.
|
||||
|
||||
It is important to note that a successful decoding does *not* mean your output matches your original input.
|
||||
There are no integrity checks, so relying on errors from the decompressor does not assure your data is valid.
|
||||
|
||||
For more detailed usage, see examples in the [godoc documentation](https://godoc.org/github.com/klauspost/compress/fse#pkg-examples).
|
||||
|
||||
# Performance
|
||||
|
||||
A lot of factors are affecting speed. Block sizes and compressibility of the material are primary factors.
|
||||
All compression functions are currently only running on the calling goroutine so only one core will be used per block.
|
||||
|
||||
The compressor is significantly faster if symbols are kept as small as possible. The highest byte value of the input
|
||||
is used to reduce some of the processing, so if all your input is above byte value 64 for instance, it may be
|
||||
beneficial to transpose all your input values down by 64.
|
||||
|
||||
With moderate block sizes around 64k speed are typically 200MB/s per core for compression and
|
||||
around 300MB/s decompression speed.
|
||||
|
||||
The same hardware typically does Huffman (deflate) encoding at 125MB/s and decompression at 100MB/s.
|
||||
|
||||
# Plans
|
||||
|
||||
At one point, more internals will be exposed to facilitate more "expert" usage of the components.
|
||||
|
||||
A streaming interface is also likely to be implemented. Likely compatible with [FSE stream format](https://github.com/Cyan4973/FiniteStateEntropy/blob/dev/programs/fileio.c#L261).
|
||||
|
||||
# Contributing
|
||||
|
||||
Contributions are always welcome. Be aware that adding public functions will require good justification and breaking
|
||||
# Finite State Entropy
|
||||
|
||||
This package provides Finite State Entropy encoding and decoding.
|
||||
|
||||
Finite State Entropy (also referenced as [tANS](https://en.wikipedia.org/wiki/Asymmetric_numeral_systems#tANS))
|
||||
encoding provides a fast near-optimal symbol encoding/decoding
|
||||
for byte blocks as implemented in [zstandard](https://github.com/facebook/zstd).
|
||||
|
||||
This can be used for compressing input with a lot of similar input values to the smallest number of bytes.
|
||||
This does not perform any multi-byte [dictionary coding](https://en.wikipedia.org/wiki/Dictionary_coder) as LZ coders,
|
||||
but it can be used as a secondary step to compressors (like Snappy) that does not do entropy encoding.
|
||||
|
||||
* [Godoc documentation](https://godoc.org/github.com/klauspost/compress/fse)
|
||||
|
||||
## News
|
||||
|
||||
* Feb 2018: First implementation released. Consider this beta software for now.
|
||||
|
||||
# Usage
|
||||
|
||||
This package provides a low level interface that allows to compress single independent blocks.
|
||||
|
||||
Each block is separate, and there is no built in integrity checks.
|
||||
This means that the caller should keep track of block sizes and also do checksums if needed.
|
||||
|
||||
Compressing a block is done via the [`Compress`](https://godoc.org/github.com/klauspost/compress/fse#Compress) function.
|
||||
You must provide input and will receive the output and maybe an error.
|
||||
|
||||
These error values can be returned:
|
||||
|
||||
| Error | Description |
|
||||
|---------------------|-----------------------------------------------------------------------------|
|
||||
| `<nil>` | Everything ok, output is returned |
|
||||
| `ErrIncompressible` | Returned when input is judged to be too hard to compress |
|
||||
| `ErrUseRLE` | Returned from the compressor when the input is a single byte value repeated |
|
||||
| `(error)` | An internal error occurred. |
|
||||
|
||||
As can be seen above there are errors that will be returned even under normal operation so it is important to handle these.
|
||||
|
||||
To reduce allocations you can provide a [`Scratch`](https://godoc.org/github.com/klauspost/compress/fse#Scratch) object
|
||||
that can be re-used for successive calls. Both compression and decompression accepts a `Scratch` object, and the same
|
||||
object can be used for both.
|
||||
|
||||
Be aware, that when re-using a `Scratch` object that the *output* buffer is also re-used, so if you are still using this
|
||||
you must set the `Out` field in the scratch to nil. The same buffer is used for compression and decompression output.
|
||||
|
||||
Decompressing is done by calling the [`Decompress`](https://godoc.org/github.com/klauspost/compress/fse#Decompress) function.
|
||||
You must provide the output from the compression stage, at exactly the size you got back. If you receive an error back
|
||||
your input was likely corrupted.
|
||||
|
||||
It is important to note that a successful decoding does *not* mean your output matches your original input.
|
||||
There are no integrity checks, so relying on errors from the decompressor does not assure your data is valid.
|
||||
|
||||
For more detailed usage, see examples in the [godoc documentation](https://godoc.org/github.com/klauspost/compress/fse#pkg-examples).
|
||||
|
||||
# Performance
|
||||
|
||||
A lot of factors are affecting speed. Block sizes and compressibility of the material are primary factors.
|
||||
All compression functions are currently only running on the calling goroutine so only one core will be used per block.
|
||||
|
||||
The compressor is significantly faster if symbols are kept as small as possible. The highest byte value of the input
|
||||
is used to reduce some of the processing, so if all your input is above byte value 64 for instance, it may be
|
||||
beneficial to transpose all your input values down by 64.
|
||||
|
||||
With moderate block sizes around 64k speed are typically 200MB/s per core for compression and
|
||||
around 300MB/s decompression speed.
|
||||
|
||||
The same hardware typically does Huffman (deflate) encoding at 125MB/s and decompression at 100MB/s.
|
||||
|
||||
# Plans
|
||||
|
||||
At one point, more internals will be exposed to facilitate more "expert" usage of the components.
|
||||
|
||||
A streaming interface is also likely to be implemented. Likely compatible with [FSE stream format](https://github.com/Cyan4973/FiniteStateEntropy/blob/dev/programs/fileio.c#L261).
|
||||
|
||||
# Contributing
|
||||
|
||||
Contributions are always welcome. Be aware that adding public functions will require good justification and breaking
|
||||
changes will likely not be accepted. If in doubt open an issue before writing the PR.
|
||||
+89
-89
@@ -1,89 +1,89 @@
|
||||
# Huff0 entropy compression
|
||||
|
||||
This package provides Huff0 encoding and decoding as used in zstd.
|
||||
|
||||
[Huff0](https://github.com/Cyan4973/FiniteStateEntropy#new-generation-entropy-coders),
|
||||
a Huffman codec designed for modern CPU, featuring OoO (Out of Order) operations on multiple ALU
|
||||
(Arithmetic Logic Unit), achieving extremely fast compression and decompression speeds.
|
||||
|
||||
This can be used for compressing input with a lot of similar input values to the smallest number of bytes.
|
||||
This does not perform any multi-byte [dictionary coding](https://en.wikipedia.org/wiki/Dictionary_coder) as LZ coders,
|
||||
but it can be used as a secondary step to compressors (like Snappy) that does not do entropy encoding.
|
||||
|
||||
* [Godoc documentation](https://godoc.org/github.com/klauspost/compress/huff0)
|
||||
|
||||
## News
|
||||
|
||||
This is used as part of the [zstandard](https://github.com/klauspost/compress/tree/master/zstd#zstd) compression and decompression package.
|
||||
|
||||
This ensures that most functionality is well tested.
|
||||
|
||||
# Usage
|
||||
|
||||
This package provides a low level interface that allows to compress single independent blocks.
|
||||
|
||||
Each block is separate, and there is no built in integrity checks.
|
||||
This means that the caller should keep track of block sizes and also do checksums if needed.
|
||||
|
||||
Compressing a block is done via the [`Compress1X`](https://godoc.org/github.com/klauspost/compress/huff0#Compress1X) and
|
||||
[`Compress4X`](https://godoc.org/github.com/klauspost/compress/huff0#Compress4X) functions.
|
||||
You must provide input and will receive the output and maybe an error.
|
||||
|
||||
These error values can be returned:
|
||||
|
||||
| Error | Description |
|
||||
|---------------------|-----------------------------------------------------------------------------|
|
||||
| `<nil>` | Everything ok, output is returned |
|
||||
| `ErrIncompressible` | Returned when input is judged to be too hard to compress |
|
||||
| `ErrUseRLE` | Returned from the compressor when the input is a single byte value repeated |
|
||||
| `ErrTooBig` | Returned if the input block exceeds the maximum allowed size (128 Kib) |
|
||||
| `(error)` | An internal error occurred. |
|
||||
|
||||
|
||||
As can be seen above some of there are errors that will be returned even under normal operation so it is important to handle these.
|
||||
|
||||
To reduce allocations you can provide a [`Scratch`](https://godoc.org/github.com/klauspost/compress/huff0#Scratch) object
|
||||
that can be re-used for successive calls. Both compression and decompression accepts a `Scratch` object, and the same
|
||||
object can be used for both.
|
||||
|
||||
Be aware, that when re-using a `Scratch` object that the *output* buffer is also re-used, so if you are still using this
|
||||
you must set the `Out` field in the scratch to nil. The same buffer is used for compression and decompression output.
|
||||
|
||||
The `Scratch` object will retain state that allows to re-use previous tables for encoding and decoding.
|
||||
|
||||
## Tables and re-use
|
||||
|
||||
Huff0 allows for reusing tables from the previous block to save space if that is expected to give better/faster results.
|
||||
|
||||
The Scratch object allows you to set a [`ReusePolicy`](https://godoc.org/github.com/klauspost/compress/huff0#ReusePolicy)
|
||||
that controls this behaviour. See the documentation for details. This can be altered between each block.
|
||||
|
||||
Do however note that this information is *not* stored in the output block and it is up to the users of the package to
|
||||
record whether [`ReadTable`](https://godoc.org/github.com/klauspost/compress/huff0#ReadTable) should be called,
|
||||
based on the boolean reported back from the CompressXX call.
|
||||
|
||||
If you want to store the table separate from the data, you can access them as `OutData` and `OutTable` on the
|
||||
[`Scratch`](https://godoc.org/github.com/klauspost/compress/huff0#Scratch) object.
|
||||
|
||||
## Decompressing
|
||||
|
||||
The first part of decoding is to initialize the decoding table through [`ReadTable`](https://godoc.org/github.com/klauspost/compress/huff0#ReadTable).
|
||||
This will initialize the decoding tables.
|
||||
You can supply the complete block to `ReadTable` and it will return the data part of the block
|
||||
which can be given to the decompressor.
|
||||
|
||||
Decompressing is done by calling the [`Decompress1X`](https://godoc.org/github.com/klauspost/compress/huff0#Scratch.Decompress1X)
|
||||
or [`Decompress4X`](https://godoc.org/github.com/klauspost/compress/huff0#Scratch.Decompress4X) function.
|
||||
|
||||
For concurrently decompressing content with a fixed table a stateless [`Decoder`](https://godoc.org/github.com/klauspost/compress/huff0#Decoder) can be requested which will remain correct as long as the scratch is unchanged. The capacity of the provided slice indicates the expected output size.
|
||||
|
||||
You must provide the output from the compression stage, at exactly the size you got back. If you receive an error back
|
||||
your input was likely corrupted.
|
||||
|
||||
It is important to note that a successful decoding does *not* mean your output matches your original input.
|
||||
There are no integrity checks, so relying on errors from the decompressor does not assure your data is valid.
|
||||
|
||||
# Contributing
|
||||
|
||||
Contributions are always welcome. Be aware that adding public functions will require good justification and breaking
|
||||
changes will likely not be accepted. If in doubt open an issue before writing the PR.
|
||||
# Huff0 entropy compression
|
||||
|
||||
This package provides Huff0 encoding and decoding as used in zstd.
|
||||
|
||||
[Huff0](https://github.com/Cyan4973/FiniteStateEntropy#new-generation-entropy-coders),
|
||||
a Huffman codec designed for modern CPU, featuring OoO (Out of Order) operations on multiple ALU
|
||||
(Arithmetic Logic Unit), achieving extremely fast compression and decompression speeds.
|
||||
|
||||
This can be used for compressing input with a lot of similar input values to the smallest number of bytes.
|
||||
This does not perform any multi-byte [dictionary coding](https://en.wikipedia.org/wiki/Dictionary_coder) as LZ coders,
|
||||
but it can be used as a secondary step to compressors (like Snappy) that does not do entropy encoding.
|
||||
|
||||
* [Godoc documentation](https://godoc.org/github.com/klauspost/compress/huff0)
|
||||
|
||||
## News
|
||||
|
||||
This is used as part of the [zstandard](https://github.com/klauspost/compress/tree/master/zstd#zstd) compression and decompression package.
|
||||
|
||||
This ensures that most functionality is well tested.
|
||||
|
||||
# Usage
|
||||
|
||||
This package provides a low level interface that allows to compress single independent blocks.
|
||||
|
||||
Each block is separate, and there is no built in integrity checks.
|
||||
This means that the caller should keep track of block sizes and also do checksums if needed.
|
||||
|
||||
Compressing a block is done via the [`Compress1X`](https://godoc.org/github.com/klauspost/compress/huff0#Compress1X) and
|
||||
[`Compress4X`](https://godoc.org/github.com/klauspost/compress/huff0#Compress4X) functions.
|
||||
You must provide input and will receive the output and maybe an error.
|
||||
|
||||
These error values can be returned:
|
||||
|
||||
| Error | Description |
|
||||
|---------------------|-----------------------------------------------------------------------------|
|
||||
| `<nil>` | Everything ok, output is returned |
|
||||
| `ErrIncompressible` | Returned when input is judged to be too hard to compress |
|
||||
| `ErrUseRLE` | Returned from the compressor when the input is a single byte value repeated |
|
||||
| `ErrTooBig` | Returned if the input block exceeds the maximum allowed size (128 Kib) |
|
||||
| `(error)` | An internal error occurred. |
|
||||
|
||||
|
||||
As can be seen above some of there are errors that will be returned even under normal operation so it is important to handle these.
|
||||
|
||||
To reduce allocations you can provide a [`Scratch`](https://godoc.org/github.com/klauspost/compress/huff0#Scratch) object
|
||||
that can be re-used for successive calls. Both compression and decompression accepts a `Scratch` object, and the same
|
||||
object can be used for both.
|
||||
|
||||
Be aware, that when re-using a `Scratch` object that the *output* buffer is also re-used, so if you are still using this
|
||||
you must set the `Out` field in the scratch to nil. The same buffer is used for compression and decompression output.
|
||||
|
||||
The `Scratch` object will retain state that allows to re-use previous tables for encoding and decoding.
|
||||
|
||||
## Tables and re-use
|
||||
|
||||
Huff0 allows for reusing tables from the previous block to save space if that is expected to give better/faster results.
|
||||
|
||||
The Scratch object allows you to set a [`ReusePolicy`](https://godoc.org/github.com/klauspost/compress/huff0#ReusePolicy)
|
||||
that controls this behaviour. See the documentation for details. This can be altered between each block.
|
||||
|
||||
Do however note that this information is *not* stored in the output block and it is up to the users of the package to
|
||||
record whether [`ReadTable`](https://godoc.org/github.com/klauspost/compress/huff0#ReadTable) should be called,
|
||||
based on the boolean reported back from the CompressXX call.
|
||||
|
||||
If you want to store the table separate from the data, you can access them as `OutData` and `OutTable` on the
|
||||
[`Scratch`](https://godoc.org/github.com/klauspost/compress/huff0#Scratch) object.
|
||||
|
||||
## Decompressing
|
||||
|
||||
The first part of decoding is to initialize the decoding table through [`ReadTable`](https://godoc.org/github.com/klauspost/compress/huff0#ReadTable).
|
||||
This will initialize the decoding tables.
|
||||
You can supply the complete block to `ReadTable` and it will return the data part of the block
|
||||
which can be given to the decompressor.
|
||||
|
||||
Decompressing is done by calling the [`Decompress1X`](https://godoc.org/github.com/klauspost/compress/huff0#Scratch.Decompress1X)
|
||||
or [`Decompress4X`](https://godoc.org/github.com/klauspost/compress/huff0#Scratch.Decompress4X) function.
|
||||
|
||||
For concurrently decompressing content with a fixed table a stateless [`Decoder`](https://godoc.org/github.com/klauspost/compress/huff0#Decoder) can be requested which will remain correct as long as the scratch is unchanged. The capacity of the provided slice indicates the expected output size.
|
||||
|
||||
You must provide the output from the compression stage, at exactly the size you got back. If you receive an error back
|
||||
your input was likely corrupted.
|
||||
|
||||
It is important to note that a successful decoding does *not* mean your output matches your original input.
|
||||
There are no integrity checks, so relying on errors from the decompressor does not assure your data is valid.
|
||||
|
||||
# Contributing
|
||||
|
||||
Contributions are always welcome. Be aware that adding public functions will require good justification and breaking
|
||||
changes will likely not be accepted. If in doubt open an issue before writing the PR.
|
||||
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
version: 2
|
||||
|
||||
builds:
|
||||
-
|
||||
id: "cpuid"
|
||||
binary: cpuid
|
||||
main: ./cmd/cpuid/main.go
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
flags:
|
||||
- -ldflags=-s -w
|
||||
goos:
|
||||
- aix
|
||||
- linux
|
||||
- freebsd
|
||||
- netbsd
|
||||
- windows
|
||||
- darwin
|
||||
goarch:
|
||||
- 386
|
||||
- amd64
|
||||
- arm64
|
||||
goarm:
|
||||
- 7
|
||||
|
||||
archives:
|
||||
-
|
||||
id: cpuid
|
||||
name_template: "cpuid-{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}"
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
files:
|
||||
- LICENSE
|
||||
checksum:
|
||||
name_template: 'checksums.txt'
|
||||
changelog:
|
||||
sort: asc
|
||||
filters:
|
||||
exclude:
|
||||
- '^doc:'
|
||||
- '^docs:'
|
||||
- '^test:'
|
||||
- '^tests:'
|
||||
- '^Update\sREADME.md'
|
||||
|
||||
nfpms:
|
||||
-
|
||||
file_name_template: "cpuid_package_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}"
|
||||
vendor: Klaus Post
|
||||
homepage: https://github.com/klauspost/cpuid
|
||||
maintainer: Klaus Post <klauspost@gmail.com>
|
||||
description: CPUID Tool
|
||||
license: BSD 3-Clause
|
||||
formats:
|
||||
- deb
|
||||
- rpm
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
Developer Certificate of Origin
|
||||
Version 1.1
|
||||
|
||||
Copyright (C) 2015- Klaus Post & Contributors.
|
||||
Email: klauspost@gmail.com
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim copies of this
|
||||
license document, but changing it is not allowed.
|
||||
|
||||
|
||||
Developer's Certificate of Origin 1.1
|
||||
|
||||
By making a contribution to this project, I certify that:
|
||||
|
||||
(a) The contribution was created in whole or in part by me and I
|
||||
have the right to submit it under the open source license
|
||||
indicated in the file; or
|
||||
|
||||
(b) The contribution is based upon previous work that, to the best
|
||||
of my knowledge, is covered under an appropriate open source
|
||||
license and I have the right under that license to submit that
|
||||
work with modifications, whether created in whole or in part
|
||||
by me, under the same open source license (unless I am
|
||||
permitted to submit under a different license), as indicated
|
||||
in the file; or
|
||||
|
||||
(c) The contribution was provided directly to me by some other
|
||||
person who certified (a), (b) or (c) and I have not modified
|
||||
it.
|
||||
|
||||
(d) I understand and agree that this project and the contribution
|
||||
are public and that a record of the contribution (including all
|
||||
personal information I submit with it, including my sign-off) is
|
||||
maintained indefinitely and may be redistributed consistent with
|
||||
this project or the open source license(s) involved.
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Klaus Post
|
||||
|
||||
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.
|
||||
|
||||
+512
@@ -0,0 +1,512 @@
|
||||
# cpuid
|
||||
Package cpuid provides information about the CPU running the current program.
|
||||
|
||||
CPU features are detected on startup, and kept for fast access through the life of the application.
|
||||
Currently x86 / x64 (AMD64/i386) and ARM (ARM64) is supported, and no external C (cgo) code is used, which should make the library very easy to use.
|
||||
|
||||
You can access the CPU information by accessing the shared CPU variable of the cpuid library.
|
||||
|
||||
Package home: https://github.com/klauspost/cpuid
|
||||
|
||||
[](https://pkg.go.dev/github.com/klauspost/cpuid/v2)
|
||||
[](https://github.com/klauspost/cpuid/actions/workflows/go.yml)
|
||||
|
||||
## installing
|
||||
|
||||
`go get -u github.com/klauspost/cpuid/v2` using modules.
|
||||
Drop `v2` for others.
|
||||
|
||||
Installing binary:
|
||||
|
||||
`go install github.com/klauspost/cpuid/v2/cmd/cpuid@latest`
|
||||
|
||||
Or download binaries from release page: https://github.com/klauspost/cpuid/releases
|
||||
|
||||
### Homebrew
|
||||
|
||||
For macOS/Linux users, you can install via [brew](https://brew.sh/)
|
||||
|
||||
```sh
|
||||
$ brew install cpuid
|
||||
```
|
||||
|
||||
## example
|
||||
|
||||
```Go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
. "github.com/klauspost/cpuid/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Print basic CPU information:
|
||||
fmt.Println("Name:", CPU.BrandName)
|
||||
fmt.Println("PhysicalCores:", CPU.PhysicalCores)
|
||||
fmt.Println("ThreadsPerCore:", CPU.ThreadsPerCore)
|
||||
fmt.Println("LogicalCores:", CPU.LogicalCores)
|
||||
fmt.Println("Family", CPU.Family, "Model:", CPU.Model, "Vendor ID:", CPU.VendorID)
|
||||
fmt.Println("Features:", strings.Join(CPU.FeatureSet(), ","))
|
||||
fmt.Println("Cacheline bytes:", CPU.CacheLine)
|
||||
fmt.Println("L1 Data Cache:", CPU.Cache.L1D, "bytes")
|
||||
fmt.Println("L1 Instruction Cache:", CPU.Cache.L1I, "bytes")
|
||||
fmt.Println("L2 Cache:", CPU.Cache.L2, "bytes")
|
||||
fmt.Println("L3 Cache:", CPU.Cache.L3, "bytes")
|
||||
fmt.Println("Frequency", CPU.Hz, "hz")
|
||||
|
||||
// Test if we have these specific features:
|
||||
if CPU.Supports(SSE, SSE2) {
|
||||
fmt.Println("We have Streaming SIMD 2 Extensions")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Sample output:
|
||||
```
|
||||
>go run main.go
|
||||
Name: AMD Ryzen 9 3950X 16-Core Processor
|
||||
PhysicalCores: 16
|
||||
ThreadsPerCore: 2
|
||||
LogicalCores: 32
|
||||
Family 23 Model: 113 Vendor ID: AMD
|
||||
Features: ADX,AESNI,AVX,AVX2,BMI1,BMI2,CLMUL,CMOV,CX16,F16C,FMA3,HTT,HYPERVISOR,LZCNT,MMX,MMXEXT,NX,POPCNT,RDRAND,RDSEED,RDTSCP,SHA,SSE,SSE2,SSE3,SSE4,SSE42,SSE4A,SSSE3
|
||||
Cacheline bytes: 64
|
||||
L1 Data Cache: 32768 bytes
|
||||
L1 Instruction Cache: 32768 bytes
|
||||
L2 Cache: 524288 bytes
|
||||
L3 Cache: 16777216 bytes
|
||||
Frequency 0 hz
|
||||
We have Streaming SIMD 2 Extensions
|
||||
```
|
||||
|
||||
# usage
|
||||
|
||||
The `cpuid.CPU` provides access to CPU features. Use `cpuid.CPU.Supports()` to check for CPU features.
|
||||
A faster `cpuid.CPU.Has()` is provided which will usually be inlined by the gc compiler.
|
||||
|
||||
To test a larger number of features, they can be combined using `f := CombineFeatures(CMOV, CMPXCHG8, X87, FXSR, MMX, SYSCALL, SSE, SSE2)`, etc.
|
||||
This can be using with `cpuid.CPU.HasAll(f)` to quickly test if all features are supported.
|
||||
|
||||
Note that for some cpu/os combinations some features will not be detected.
|
||||
`amd64` has rather good support and should work reliably on all platforms.
|
||||
|
||||
Note that hypervisors may not pass through all CPU features through to the guest OS,
|
||||
so even if your host supports a feature it may not be visible on guests.
|
||||
|
||||
## arm64 feature detection
|
||||
|
||||
Not all operating systems provide ARM features directly
|
||||
and there is no safe way to do so for the rest.
|
||||
|
||||
Currently `arm64/linux` and `arm64/freebsd` should be quite reliable.
|
||||
`arm64/darwin` adds features expected from the M1 processor, but a lot remains undetected.
|
||||
|
||||
A `DetectARM()` can be used if you are able to control your deployment,
|
||||
it will detect CPU features, but may crash if the OS doesn't intercept the calls.
|
||||
A `-cpu.arm` flag for detecting unsafe ARM features can be added. See below.
|
||||
|
||||
Note that currently only features are detected on ARM,
|
||||
no additional information is currently available.
|
||||
|
||||
## flags
|
||||
|
||||
It is possible to add flags that affects cpu detection.
|
||||
|
||||
For this the `Flags()` command is provided.
|
||||
|
||||
This must be called *before* `flag.Parse()` AND after the flags have been parsed `Detect()` must be called.
|
||||
|
||||
This means that any detection used in `init()` functions will not contain these flags.
|
||||
|
||||
Example:
|
||||
|
||||
```Go
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/klauspost/cpuid/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cpuid.Flags()
|
||||
flag.Parse()
|
||||
cpuid.Detect()
|
||||
|
||||
// Test if we have these specific features:
|
||||
if cpuid.CPU.Supports(cpuid.SSE, cpuid.SSE2) {
|
||||
fmt.Println("We have Streaming SIMD 2 Extensions")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## commandline
|
||||
|
||||
Download as binary from: https://github.com/klauspost/cpuid/releases
|
||||
|
||||
Install from source:
|
||||
|
||||
`go install github.com/klauspost/cpuid/v2/cmd/cpuid@latest`
|
||||
|
||||
### Example
|
||||
|
||||
```
|
||||
λ cpuid
|
||||
Name: AMD Ryzen 9 3950X 16-Core Processor
|
||||
Vendor String: AuthenticAMD
|
||||
Vendor ID: AMD
|
||||
PhysicalCores: 16
|
||||
Threads Per Core: 2
|
||||
Logical Cores: 32
|
||||
CPU Family 23 Model: 113
|
||||
Features: ADX,AESNI,AVX,AVX2,BMI1,BMI2,CLMUL,CLZERO,CMOV,CMPXCHG8,CPBOOST,CX16,F16C,FMA3,FXSR,FXSROPT,HTT,HYPERVISOR,LAHF,LZCNT,MCAOVERFLOW,MMX,MMXEXT,MOVBE,NX,OSXSAVE,POPCNT,RDRAND,RDSEED,RDTSCP,SCE,SHA,SSE,SSE2,SSE3,SSE4,SSE42,SSE4A,SSSE3,SUCCOR,X87,XSAVE
|
||||
Microarchitecture level: 3
|
||||
Cacheline bytes: 64
|
||||
L1 Instruction Cache: 32768 bytes
|
||||
L1 Data Cache: 32768 bytes
|
||||
L2 Cache: 524288 bytes
|
||||
L3 Cache: 16777216 bytes
|
||||
|
||||
```
|
||||
### JSON Output:
|
||||
|
||||
```
|
||||
λ cpuid --json
|
||||
{
|
||||
"BrandName": "AMD Ryzen 9 3950X 16-Core Processor",
|
||||
"VendorID": 2,
|
||||
"VendorString": "AuthenticAMD",
|
||||
"PhysicalCores": 16,
|
||||
"ThreadsPerCore": 2,
|
||||
"LogicalCores": 32,
|
||||
"Family": 23,
|
||||
"Model": 113,
|
||||
"CacheLine": 64,
|
||||
"Hz": 0,
|
||||
"BoostFreq": 0,
|
||||
"Cache": {
|
||||
"L1I": 32768,
|
||||
"L1D": 32768,
|
||||
"L2": 524288,
|
||||
"L3": 16777216
|
||||
},
|
||||
"SGX": {
|
||||
"Available": false,
|
||||
"LaunchControl": false,
|
||||
"SGX1Supported": false,
|
||||
"SGX2Supported": false,
|
||||
"MaxEnclaveSizeNot64": 0,
|
||||
"MaxEnclaveSize64": 0,
|
||||
"EPCSections": null
|
||||
},
|
||||
"Features": [
|
||||
"ADX",
|
||||
"AESNI",
|
||||
"AVX",
|
||||
"AVX2",
|
||||
"BMI1",
|
||||
"BMI2",
|
||||
"CLMUL",
|
||||
"CLZERO",
|
||||
"CMOV",
|
||||
"CMPXCHG8",
|
||||
"CPBOOST",
|
||||
"CX16",
|
||||
"F16C",
|
||||
"FMA3",
|
||||
"FXSR",
|
||||
"FXSROPT",
|
||||
"HTT",
|
||||
"HYPERVISOR",
|
||||
"LAHF",
|
||||
"LZCNT",
|
||||
"MCAOVERFLOW",
|
||||
"MMX",
|
||||
"MMXEXT",
|
||||
"MOVBE",
|
||||
"NX",
|
||||
"OSXSAVE",
|
||||
"POPCNT",
|
||||
"RDRAND",
|
||||
"RDSEED",
|
||||
"RDTSCP",
|
||||
"SCE",
|
||||
"SHA",
|
||||
"SSE",
|
||||
"SSE2",
|
||||
"SSE3",
|
||||
"SSE4",
|
||||
"SSE42",
|
||||
"SSE4A",
|
||||
"SSSE3",
|
||||
"SUCCOR",
|
||||
"X87",
|
||||
"XSAVE"
|
||||
],
|
||||
"X64Level": 3
|
||||
}
|
||||
```
|
||||
|
||||
### Check CPU microarch level
|
||||
|
||||
```
|
||||
λ cpuid --check-level=3
|
||||
2022/03/18 17:04:40 AMD Ryzen 9 3950X 16-Core Processor
|
||||
2022/03/18 17:04:40 Microarchitecture level 3 is supported. Max level is 3.
|
||||
Exit Code 0
|
||||
|
||||
λ cpuid --check-level=4
|
||||
2022/03/18 17:06:18 AMD Ryzen 9 3950X 16-Core Processor
|
||||
2022/03/18 17:06:18 Microarchitecture level 4 not supported. Max level is 3.
|
||||
Exit Code 1
|
||||
```
|
||||
|
||||
|
||||
## Available flags
|
||||
|
||||
### x86 & amd64
|
||||
|
||||
| Feature Flag | Description |
|
||||
|--------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| ADX | Intel ADX (Multi-Precision Add-Carry Instruction Extensions) |
|
||||
| AESNI | Advanced Encryption Standard New Instructions |
|
||||
| AMD3DNOW | AMD 3DNOW |
|
||||
| AMD3DNOWEXT | AMD 3DNowExt |
|
||||
| AMXBF16 | Tile computational operations on BFLOAT16 numbers |
|
||||
| AMXINT8 | Tile computational operations on 8-bit integers |
|
||||
| AMXFP16 | Tile computational operations on FP16 numbers |
|
||||
| AMXFP8 | Tile computational operations on FP8 numbers |
|
||||
| AMXCOMPLEX | Tile computational operations on complex numbers |
|
||||
| AMXTILE | Tile architecture |
|
||||
| AMXTF32 | Matrix Multiplication of TF32 Tiles into Packed Single Precision Tile |
|
||||
| AMXTRANSPOSE | Tile multiply where the first operand is transposed |
|
||||
| APX_F | Intel APX |
|
||||
| AVX | AVX functions |
|
||||
| AVX10 | If set the Intel AVX10 Converged Vector ISA is supported |
|
||||
| AVX10_128 | If set indicates that AVX10 128-bit vector support is present |
|
||||
| AVX10_256 | If set indicates that AVX10 256-bit vector support is present |
|
||||
| AVX10_512 | If set indicates that AVX10 512-bit vector support is present |
|
||||
| AVX2 | AVX2 functions |
|
||||
| AVX512BF16 | AVX-512 BFLOAT16 Instructions |
|
||||
| AVX512BITALG | AVX-512 Bit Algorithms |
|
||||
| AVX512BW | AVX-512 Byte and Word Instructions |
|
||||
| AVX512CD | AVX-512 Conflict Detection Instructions |
|
||||
| AVX512DQ | AVX-512 Doubleword and Quadword Instructions |
|
||||
| AVX512ER | AVX-512 Exponential and Reciprocal Instructions |
|
||||
| AVX512F | AVX-512 Foundation |
|
||||
| AVX512FP16 | AVX-512 FP16 Instructions |
|
||||
| AVX512IFMA | AVX-512 Integer Fused Multiply-Add Instructions |
|
||||
| AVX512PF | AVX-512 Prefetch Instructions |
|
||||
| AVX512VBMI | AVX-512 Vector Bit Manipulation Instructions |
|
||||
| AVX512VBMI2 | AVX-512 Vector Bit Manipulation Instructions, Version 2 |
|
||||
| AVX512VL | AVX-512 Vector Length Extensions |
|
||||
| AVX512VNNI | AVX-512 Vector Neural Network Instructions |
|
||||
| AVX512VP2INTERSECT | AVX-512 Intersect for D/Q |
|
||||
| AVX512VPOPCNTDQ | AVX-512 Vector Population Count Doubleword and Quadword |
|
||||
| AVXIFMA | AVX-IFMA instructions |
|
||||
| AVXNECONVERT | AVX-NE-CONVERT instructions |
|
||||
| AVXSLOW | Indicates the CPU performs 2 128 bit operations instead of one |
|
||||
| AVXVNNI | AVX (VEX encoded) VNNI neural network instructions |
|
||||
| AVXVNNIINT8 | AVX-VNNI-INT8 instructions |
|
||||
| AVXVNNIINT16 | AVX-VNNI-INT16 instructions |
|
||||
| BHI_CTRL | Branch History Injection and Intra-mode Branch Target Injection / CVE-2022-0001, CVE-2022-0002 / INTEL-SA-00598 |
|
||||
| BMI1 | Bit Manipulation Instruction Set 1 |
|
||||
| BMI2 | Bit Manipulation Instruction Set 2 |
|
||||
| CETIBT | Intel CET Indirect Branch Tracking |
|
||||
| CETSS | Intel CET Shadow Stack |
|
||||
| CLDEMOTE | Cache Line Demote |
|
||||
| CLMUL | Carry-less Multiplication |
|
||||
| CLZERO | CLZERO instruction supported |
|
||||
| CMOV | i686 CMOV |
|
||||
| CMPCCXADD | CMPCCXADD instructions |
|
||||
| CMPSB_SCADBS_SHORT | Fast short CMPSB and SCASB |
|
||||
| CMPXCHG8 | CMPXCHG8 instruction |
|
||||
| CPBOOST | Core Performance Boost |
|
||||
| CPPC | AMD: Collaborative Processor Performance Control |
|
||||
| CX16 | CMPXCHG16B Instruction |
|
||||
| EFER_LMSLE_UNS | AMD: =Core::X86::Msr::EFER[LMSLE] is not supported, and MBZ |
|
||||
| ENQCMD | Enqueue Command |
|
||||
| ERMS | Enhanced REP MOVSB/STOSB |
|
||||
| F16C | Half-precision floating-point conversion |
|
||||
| FLUSH_L1D | Flush L1D cache |
|
||||
| FMA3 | Intel FMA 3. Does not imply AVX. |
|
||||
| FMA4 | Bulldozer FMA4 functions |
|
||||
| FP128 | AMD: When set, the internal FP/SIMD execution datapath is 128-bits wide |
|
||||
| FP256 | AMD: When set, the internal FP/SIMD execution datapath is 256-bits wide |
|
||||
| FSRM | Fast Short Rep Mov |
|
||||
| FXSR | FXSAVE, FXRESTOR instructions, CR4 bit 9 |
|
||||
| FXSROPT | FXSAVE/FXRSTOR optimizations |
|
||||
| GFNI | Galois Field New Instructions. May require other features (AVX, AVX512VL,AVX512F) based on usage. |
|
||||
| HLE | Hardware Lock Elision |
|
||||
| HRESET | If set CPU supports history reset and the IA32_HRESET_ENABLE MSR |
|
||||
| HTT | Hyperthreading (enabled) |
|
||||
| HWA | Hardware assert supported. Indicates support for MSRC001_10 |
|
||||
| HYBRID_CPU | This part has CPUs of more than one type. |
|
||||
| HYPERVISOR | This bit has been reserved by Intel & AMD for use by hypervisors |
|
||||
| IA32_ARCH_CAP | IA32_ARCH_CAPABILITIES MSR (Intel) |
|
||||
| IA32_CORE_CAP | IA32_CORE_CAPABILITIES MSR |
|
||||
| IBPB | Indirect Branch Restricted Speculation (IBRS) and Indirect Branch Predictor Barrier (IBPB) |
|
||||
| IBRS | AMD: Indirect Branch Restricted Speculation |
|
||||
| IBRS_PREFERRED | AMD: IBRS is preferred over software solution |
|
||||
| IBRS_PROVIDES_SMP | AMD: IBRS provides Same Mode Protection |
|
||||
| IBS | Instruction Based Sampling (AMD) |
|
||||
| IBSBRNTRGT | Instruction Based Sampling Feature (AMD) |
|
||||
| IBSFETCHSAM | Instruction Based Sampling Feature (AMD) |
|
||||
| IBSFFV | Instruction Based Sampling Feature (AMD) |
|
||||
| IBSOPCNT | Instruction Based Sampling Feature (AMD) |
|
||||
| IBSOPCNTEXT | Instruction Based Sampling Feature (AMD) |
|
||||
| IBSOPSAM | Instruction Based Sampling Feature (AMD) |
|
||||
| IBSRDWROPCNT | Instruction Based Sampling Feature (AMD) |
|
||||
| IBSRIPINVALIDCHK | Instruction Based Sampling Feature (AMD) |
|
||||
| IBS_FETCH_CTLX | AMD: IBS fetch control extended MSR supported |
|
||||
| IBS_OPDATA4 | AMD: IBS op data 4 MSR supported |
|
||||
| IBS_OPFUSE | AMD: Indicates support for IbsOpFuse |
|
||||
| IBS_PREVENTHOST | Disallowing IBS use by the host supported |
|
||||
| IBS_ZEN4 | Fetch and Op IBS support IBS extensions added with Zen4 |
|
||||
| IDPRED_CTRL | IPRED_DIS |
|
||||
| INT_WBINVD | WBINVD/WBNOINVD are interruptible. |
|
||||
| INVLPGB | NVLPGB and TLBSYNC instruction supported |
|
||||
| KEYLOCKER | Key locker |
|
||||
| KEYLOCKERW | Key locker wide |
|
||||
| LAHF | LAHF/SAHF in long mode |
|
||||
| LAM | If set, CPU supports Linear Address Masking |
|
||||
| LBRVIRT | LBR virtualization |
|
||||
| LZCNT | LZCNT instruction |
|
||||
| MCAOVERFLOW | MCA overflow recovery support. |
|
||||
| MCDT_NO | Processor do not exhibit MXCSR Configuration Dependent Timing behavior and do not need to mitigate it. |
|
||||
| MCOMMIT | MCOMMIT instruction supported |
|
||||
| MD_CLEAR | VERW clears CPU buffers |
|
||||
| MMX | standard MMX |
|
||||
| MMXEXT | SSE integer functions or AMD MMX ext |
|
||||
| MOVBE | MOVBE instruction (big-endian) |
|
||||
| MOVDIR64B | Move 64 Bytes as Direct Store |
|
||||
| MOVDIRI | Move Doubleword as Direct Store |
|
||||
| MOVSB_ZL | Fast Zero-Length MOVSB |
|
||||
| MPX | Intel MPX (Memory Protection Extensions) |
|
||||
| MOVU | MOVU SSE instructions are more efficient and should be preferred to SSE MOVL/MOVH. MOVUPS is more efficient than MOVLPS/MOVHPS. MOVUPD is more efficient than MOVLPD/MOVHPD |
|
||||
| MSRIRC | Instruction Retired Counter MSR available |
|
||||
| MSRLIST | Read/Write List of Model Specific Registers |
|
||||
| MSR_PAGEFLUSH | Page Flush MSR available |
|
||||
| NRIPS | Indicates support for NRIP save on VMEXIT |
|
||||
| NX | NX (No-Execute) bit |
|
||||
| OSXSAVE | XSAVE enabled by OS |
|
||||
| PCONFIG | PCONFIG for Intel Multi-Key Total Memory Encryption |
|
||||
| POPCNT | POPCNT instruction |
|
||||
| PPIN | AMD: Protected Processor Inventory Number support. Indicates that Protected Processor Inventory Number (PPIN) capability can be enabled |
|
||||
| PREFETCHI | PREFETCHIT0/1 instructions |
|
||||
| PSFD | Predictive Store Forward Disable |
|
||||
| RDPRU | RDPRU instruction supported |
|
||||
| RDRAND | RDRAND instruction is available |
|
||||
| RDSEED | RDSEED instruction is available |
|
||||
| RDTSCP | RDTSCP Instruction |
|
||||
| RRSBA_CTRL | Restricted RSB Alternate |
|
||||
| RTM | Restricted Transactional Memory |
|
||||
| RTM_ALWAYS_ABORT | Indicates that the loaded microcode is forcing RTM abort. |
|
||||
| SERIALIZE | Serialize Instruction Execution |
|
||||
| SEV | AMD Secure Encrypted Virtualization supported |
|
||||
| SEV_64BIT | AMD SEV guest execution only allowed from a 64-bit host |
|
||||
| SEV_ALTERNATIVE | AMD SEV Alternate Injection supported |
|
||||
| SEV_DEBUGSWAP | Full debug state swap supported for SEV-ES guests |
|
||||
| SEV_ES | AMD SEV Encrypted State supported |
|
||||
| SEV_RESTRICTED | AMD SEV Restricted Injection supported |
|
||||
| SEV_SNP | AMD SEV Secure Nested Paging supported |
|
||||
| SGX | Software Guard Extensions |
|
||||
| SGXLC | Software Guard Extensions Launch Control |
|
||||
| SGXPQC | Software Guard Extensions 256-bit Encryption |
|
||||
| SHA | Intel SHA Extensions |
|
||||
| SME | AMD Secure Memory Encryption supported |
|
||||
| SME_COHERENT | AMD Hardware cache coherency across encryption domains enforced |
|
||||
| SM3_X86 | SM3 instructions |
|
||||
| SM4_X86 | SM4 instructions |
|
||||
| SPEC_CTRL_SSBD | Speculative Store Bypass Disable |
|
||||
| SRBDS_CTRL | SRBDS mitigation MSR available |
|
||||
| SSE | SSE functions |
|
||||
| SSE2 | P4 SSE functions |
|
||||
| SSE3 | Prescott SSE3 functions |
|
||||
| SSE4 | Penryn SSE4.1 functions |
|
||||
| SSE42 | Nehalem SSE4.2 functions |
|
||||
| SSE4A | AMD Barcelona microarchitecture SSE4a instructions |
|
||||
| SSSE3 | Conroe SSSE3 functions |
|
||||
| STIBP | Single Thread Indirect Branch Predictors |
|
||||
| STIBP_ALWAYSON | AMD: Single Thread Indirect Branch Prediction Mode has Enhanced Performance and may be left Always On |
|
||||
| STOSB_SHORT | Fast short STOSB |
|
||||
| SUCCOR | Software uncorrectable error containment and recovery capability. |
|
||||
| SVM | AMD Secure Virtual Machine |
|
||||
| SVMDA | Indicates support for the SVM decode assists. |
|
||||
| SVMFBASID | SVM, Indicates that TLB flush events, including CR3 writes and CR4.PGE toggles, flush only the current ASID's TLB entries. Also indicates support for the extended VMCBTLB_Control |
|
||||
| SVML | AMD SVM lock. Indicates support for SVM-Lock. |
|
||||
| SVMNP | AMD SVM nested paging |
|
||||
| SVMPF | SVM pause intercept filter. Indicates support for the pause intercept filter |
|
||||
| SVMPFT | SVM PAUSE filter threshold. Indicates support for the PAUSE filter cycle count threshold |
|
||||
| SYSCALL | System-Call Extension (SCE): SYSCALL and SYSRET instructions. |
|
||||
| SYSEE | SYSENTER and SYSEXIT instructions |
|
||||
| TBM | AMD Trailing Bit Manipulation |
|
||||
| TDX_GUEST | Intel Trust Domain Extensions Guest |
|
||||
| TLB_FLUSH_NESTED | AMD: Flushing includes all the nested translations for guest translations |
|
||||
| TME | Intel Total Memory Encryption. The following MSRs are supported: IA32_TME_CAPABILITY, IA32_TME_ACTIVATE, IA32_TME_EXCLUDE_MASK, and IA32_TME_EXCLUDE_BASE. |
|
||||
| TOPEXT | TopologyExtensions: topology extensions support. Indicates support for CPUID Fn8000_001D_EAX_x[N:0]-CPUID Fn8000_001E_EDX. |
|
||||
| TSA_L1_NO | AMD only: Not vulnerable to TSA-L1 |
|
||||
| TSA_SQ_NO | AMD only: Not vulnerable to TSA-SQ |
|
||||
| TSA_VERW_CLEAR | AMD: If set, the memory form of the VERW instruction may be used to help mitigate TSA |
|
||||
| TSCRATEMSR | MSR based TSC rate control. Indicates support for MSR TSC ratio MSRC000_0104 |
|
||||
| TSXLDTRK | Intel TSX Suspend Load Address Tracking |
|
||||
| VAES | Vector AES. AVX(512) versions requires additional checks. |
|
||||
| VMCBCLEAN | VMCB clean bits. Indicates support for VMCB clean bits. |
|
||||
| VMPL | AMD VM Permission Levels supported |
|
||||
| VMSA_REGPROT | AMD VMSA Register Protection supported |
|
||||
| VMX | Virtual Machine Extensions |
|
||||
| VPCLMULQDQ | Carry-Less Multiplication Quadword. Requires AVX for 3 register versions. |
|
||||
| VTE | AMD Virtual Transparent Encryption supported |
|
||||
| WAITPKG | TPAUSE, UMONITOR, UMWAIT |
|
||||
| WBNOINVD | Write Back and Do Not Invalidate Cache |
|
||||
| WRMSRNS | Non-Serializing Write to Model Specific Register |
|
||||
| X87 | FPU |
|
||||
| XGETBV1 | Supports XGETBV with ECX = 1 |
|
||||
| XOP | Bulldozer XOP functions |
|
||||
| XSAVE | XSAVE, XRESTOR, XSETBV, XGETBV |
|
||||
| XSAVEC | Supports XSAVEC and the compacted form of XRSTOR. |
|
||||
| XSAVEOPT | XSAVEOPT available |
|
||||
| XSAVES | Supports XSAVES/XRSTORS and IA32_XSS |
|
||||
|
||||
# ARM features:
|
||||
|
||||
| Feature Flag | Description |
|
||||
|--------------|------------------------------------------------------------------|
|
||||
| AESARM | AES instructions |
|
||||
| ARMCPUID | Some CPU ID registers readable at user-level |
|
||||
| ASIMD | Advanced SIMD |
|
||||
| ASIMDDP | SIMD Dot Product |
|
||||
| ASIMDHP | Advanced SIMD half-precision floating point |
|
||||
| ASIMDRDM | Rounding Double Multiply Accumulate/Subtract (SQRDMLAH/SQRDMLSH) |
|
||||
| ATOMICS | Large System Extensions (LSE) |
|
||||
| CRC32 | CRC32/CRC32C instructions |
|
||||
| DCPOP | Data cache clean to Point of Persistence (DC CVAP) |
|
||||
| EVTSTRM | Generic timer |
|
||||
| FCMA | Floatin point complex number addition and multiplication |
|
||||
| FHM | FMLAL and FMLSL instructions |
|
||||
| FP | Single-precision and double-precision floating point |
|
||||
| FPHP | Half-precision floating point |
|
||||
| GPA | Generic Pointer Authentication |
|
||||
| JSCVT | Javascript-style double->int convert (FJCVTZS) |
|
||||
| LRCPC | Weaker release consistency (LDAPR, etc) |
|
||||
| PMULL | Polynomial Multiply instructions (PMULL/PMULL2) |
|
||||
| RNDR | Random Number instructions |
|
||||
| TLB | Outer Shareable and TLB range maintenance instructions |
|
||||
| TS | Flag manipulation instructions |
|
||||
| SHA1 | SHA-1 instructions (SHA1C, etc) |
|
||||
| SHA2 | SHA-2 instructions (SHA256H, etc) |
|
||||
| SHA3 | SHA-3 instructions (EOR3, RAXI, XAR, BCAX) |
|
||||
| SHA512 | SHA512 instructions |
|
||||
| SM3 | SM3 instructions |
|
||||
| SM4 | SM4 instructions |
|
||||
| SVE | Scalable Vector Extension |
|
||||
|
||||
# license
|
||||
|
||||
This code is published under an MIT license. See LICENSE file for more information.
|
||||
+1679
File diff suppressed because it is too large
Load Diff
+47
@@ -0,0 +1,47 @@
|
||||
// Copyright (c) 2015 Klaus Post, released under MIT License. See LICENSE file.
|
||||
|
||||
//+build 386,!gccgo,!noasm,!appengine
|
||||
|
||||
// func asmCpuid(op uint32) (eax, ebx, ecx, edx uint32)
|
||||
TEXT ·asmCpuid(SB), 7, $0
|
||||
XORL CX, CX
|
||||
MOVL op+0(FP), AX
|
||||
CPUID
|
||||
MOVL AX, eax+4(FP)
|
||||
MOVL BX, ebx+8(FP)
|
||||
MOVL CX, ecx+12(FP)
|
||||
MOVL DX, edx+16(FP)
|
||||
RET
|
||||
|
||||
// func asmCpuidex(op, op2 uint32) (eax, ebx, ecx, edx uint32)
|
||||
TEXT ·asmCpuidex(SB), 7, $0
|
||||
MOVL op+0(FP), AX
|
||||
MOVL op2+4(FP), CX
|
||||
CPUID
|
||||
MOVL AX, eax+8(FP)
|
||||
MOVL BX, ebx+12(FP)
|
||||
MOVL CX, ecx+16(FP)
|
||||
MOVL DX, edx+20(FP)
|
||||
RET
|
||||
|
||||
// func xgetbv(index uint32) (eax, edx uint32)
|
||||
TEXT ·asmXgetbv(SB), 7, $0
|
||||
MOVL index+0(FP), CX
|
||||
BYTE $0x0f; BYTE $0x01; BYTE $0xd0 // XGETBV
|
||||
MOVL AX, eax+4(FP)
|
||||
MOVL DX, edx+8(FP)
|
||||
RET
|
||||
|
||||
// func asmRdtscpAsm() (eax, ebx, ecx, edx uint32)
|
||||
TEXT ·asmRdtscpAsm(SB), 7, $0
|
||||
BYTE $0x0F; BYTE $0x01; BYTE $0xF9 // RDTSCP
|
||||
MOVL AX, eax+0(FP)
|
||||
MOVL BX, ebx+4(FP)
|
||||
MOVL CX, ecx+8(FP)
|
||||
MOVL DX, edx+12(FP)
|
||||
RET
|
||||
|
||||
// func asmDarwinHasAVX512() bool
|
||||
TEXT ·asmDarwinHasAVX512(SB), 7, $0
|
||||
MOVL $0, eax+0(FP)
|
||||
RET
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
// Copyright (c) 2015 Klaus Post, released under MIT License. See LICENSE file.
|
||||
|
||||
//+build amd64,!gccgo,!noasm,!appengine
|
||||
|
||||
// func asmCpuid(op uint32) (eax, ebx, ecx, edx uint32)
|
||||
TEXT ·asmCpuid(SB), 7, $0
|
||||
XORQ CX, CX
|
||||
MOVL op+0(FP), AX
|
||||
CPUID
|
||||
MOVL AX, eax+8(FP)
|
||||
MOVL BX, ebx+12(FP)
|
||||
MOVL CX, ecx+16(FP)
|
||||
MOVL DX, edx+20(FP)
|
||||
RET
|
||||
|
||||
// func asmCpuidex(op, op2 uint32) (eax, ebx, ecx, edx uint32)
|
||||
TEXT ·asmCpuidex(SB), 7, $0
|
||||
MOVL op+0(FP), AX
|
||||
MOVL op2+4(FP), CX
|
||||
CPUID
|
||||
MOVL AX, eax+8(FP)
|
||||
MOVL BX, ebx+12(FP)
|
||||
MOVL CX, ecx+16(FP)
|
||||
MOVL DX, edx+20(FP)
|
||||
RET
|
||||
|
||||
// func asmXgetbv(index uint32) (eax, edx uint32)
|
||||
TEXT ·asmXgetbv(SB), 7, $0
|
||||
MOVL index+0(FP), CX
|
||||
BYTE $0x0f; BYTE $0x01; BYTE $0xd0 // XGETBV
|
||||
MOVL AX, eax+8(FP)
|
||||
MOVL DX, edx+12(FP)
|
||||
RET
|
||||
|
||||
// func asmRdtscpAsm() (eax, ebx, ecx, edx uint32)
|
||||
TEXT ·asmRdtscpAsm(SB), 7, $0
|
||||
BYTE $0x0F; BYTE $0x01; BYTE $0xF9 // RDTSCP
|
||||
MOVL AX, eax+0(FP)
|
||||
MOVL BX, ebx+4(FP)
|
||||
MOVL CX, ecx+8(FP)
|
||||
MOVL DX, edx+12(FP)
|
||||
RET
|
||||
|
||||
// From https://go-review.googlesource.com/c/sys/+/285572/
|
||||
// func asmDarwinHasAVX512() bool
|
||||
TEXT ·asmDarwinHasAVX512(SB), 7, $0-1
|
||||
MOVB $0, ret+0(FP) // default to false
|
||||
|
||||
#ifdef GOOS_darwin // return if not darwin
|
||||
#ifdef GOARCH_amd64 // return if not amd64
|
||||
// These values from:
|
||||
// https://github.com/apple/darwin-xnu/blob/xnu-4570.1.46/osfmk/i386/cpu_capabilities.h
|
||||
#define commpage64_base_address 0x00007fffffe00000
|
||||
#define commpage64_cpu_capabilities64 (commpage64_base_address+0x010)
|
||||
#define commpage64_version (commpage64_base_address+0x01E)
|
||||
#define hasAVX512F 0x0000004000000000
|
||||
MOVQ $commpage64_version, BX
|
||||
MOVW (BX), AX
|
||||
CMPW AX, $13 // versions < 13 do not support AVX512
|
||||
JL no_avx512
|
||||
MOVQ $commpage64_cpu_capabilities64, BX
|
||||
MOVQ (BX), AX
|
||||
MOVQ $hasAVX512F, CX
|
||||
ANDQ CX, AX
|
||||
JZ no_avx512
|
||||
MOVB $1, ret+0(FP)
|
||||
|
||||
no_avx512:
|
||||
#endif
|
||||
#endif
|
||||
RET
|
||||
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
// Copyright (c) 2015 Klaus Post, released under MIT License. See LICENSE file.
|
||||
|
||||
//+build arm64,!gccgo,!noasm,!appengine
|
||||
|
||||
// See https://www.kernel.org/doc/Documentation/arm64/cpu-feature-registers.txt
|
||||
|
||||
// func getMidr
|
||||
TEXT ·getMidr(SB), 7, $0
|
||||
WORD $0xd5380000 // mrs x0, midr_el1 /* Main ID Register */
|
||||
MOVD R0, midr+0(FP)
|
||||
RET
|
||||
|
||||
// func getProcFeatures
|
||||
TEXT ·getProcFeatures(SB), 7, $0
|
||||
WORD $0xd5380400 // mrs x0, id_aa64pfr0_el1 /* Processor Feature Register 0 */
|
||||
MOVD R0, procFeatures+0(FP)
|
||||
RET
|
||||
|
||||
// func getInstAttributes
|
||||
TEXT ·getInstAttributes(SB), 7, $0
|
||||
WORD $0xd5380600 // mrs x0, id_aa64isar0_el1 /* Instruction Set Attribute Register 0 */
|
||||
WORD $0xd5380621 // mrs x1, id_aa64isar1_el1 /* Instruction Set Attribute Register 1 */
|
||||
MOVD R0, instAttrReg0+0(FP)
|
||||
MOVD R1, instAttrReg1+8(FP)
|
||||
RET
|
||||
|
||||
TEXT ·getVectorLength(SB), 7, $0
|
||||
WORD $0xd2800002 // mov x2, #0
|
||||
WORD $0x04225022 // addvl x2, x2, #1
|
||||
WORD $0xd37df042 // lsl x2, x2, #3
|
||||
WORD $0xd2800003 // mov x3, #0
|
||||
WORD $0x04635023 // addpl x3, x3, #1
|
||||
WORD $0xd37df063 // lsl x3, x3, #3
|
||||
MOVD R2, vl+0(FP)
|
||||
MOVD R3, pl+8(FP)
|
||||
RET
|
||||
+250
@@ -0,0 +1,250 @@
|
||||
// Copyright (c) 2015 Klaus Post, released under MIT License. See LICENSE file.
|
||||
|
||||
//go:build arm64 && !gccgo && !noasm && !appengine
|
||||
// +build arm64,!gccgo,!noasm,!appengine
|
||||
|
||||
package cpuid
|
||||
|
||||
import "runtime"
|
||||
|
||||
func getMidr() (midr uint64)
|
||||
func getProcFeatures() (procFeatures uint64)
|
||||
func getInstAttributes() (instAttrReg0, instAttrReg1 uint64)
|
||||
func getVectorLength() (vl, pl uint64)
|
||||
|
||||
func initCPU() {
|
||||
cpuid = func(uint32) (a, b, c, d uint32) { return 0, 0, 0, 0 }
|
||||
cpuidex = func(x, y uint32) (a, b, c, d uint32) { return 0, 0, 0, 0 }
|
||||
xgetbv = func(uint32) (a, b uint32) { return 0, 0 }
|
||||
rdtscpAsm = func() (a, b, c, d uint32) { return 0, 0, 0, 0 }
|
||||
}
|
||||
|
||||
func addInfo(c *CPUInfo, safe bool) {
|
||||
// Seems to be safe to assume on ARM64
|
||||
c.CacheLine = 64
|
||||
detectOS(c)
|
||||
|
||||
// ARM64 disabled since it may crash if interrupt is not intercepted by OS.
|
||||
if safe && !c.Has(ARMCPUID) && runtime.GOOS != "freebsd" {
|
||||
return
|
||||
}
|
||||
midr := getMidr()
|
||||
|
||||
// MIDR_EL1 - Main ID Register
|
||||
// https://developer.arm.com/docs/ddi0595/h/aarch64-system-registers/midr_el1
|
||||
// x--------------------------------------------------x
|
||||
// | Name | bits | visible |
|
||||
// |--------------------------------------------------|
|
||||
// | Implementer | [31-24] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | Variant | [23-20] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | Architecture | [19-16] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | PartNum | [15-4] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | Revision | [3-0] | y |
|
||||
// x--------------------------------------------------x
|
||||
|
||||
switch (midr >> 24) & 0xff {
|
||||
case 0xC0:
|
||||
c.VendorString = "Ampere Computing"
|
||||
c.VendorID = Ampere
|
||||
case 0x41:
|
||||
c.VendorString = "Arm Limited"
|
||||
c.VendorID = ARM
|
||||
case 0x42:
|
||||
c.VendorString = "Broadcom Corporation"
|
||||
c.VendorID = Broadcom
|
||||
case 0x43:
|
||||
c.VendorString = "Cavium Inc"
|
||||
c.VendorID = Cavium
|
||||
case 0x44:
|
||||
c.VendorString = "Digital Equipment Corporation"
|
||||
c.VendorID = DEC
|
||||
case 0x46:
|
||||
c.VendorString = "Fujitsu Ltd"
|
||||
c.VendorID = Fujitsu
|
||||
case 0x49:
|
||||
c.VendorString = "Infineon Technologies AG"
|
||||
c.VendorID = Infineon
|
||||
case 0x4D:
|
||||
c.VendorString = "Motorola or Freescale Semiconductor Inc"
|
||||
c.VendorID = Motorola
|
||||
case 0x4E:
|
||||
c.VendorString = "NVIDIA Corporation"
|
||||
c.VendorID = NVIDIA
|
||||
case 0x50:
|
||||
c.VendorString = "Applied Micro Circuits Corporation"
|
||||
c.VendorID = AMCC
|
||||
case 0x51:
|
||||
c.VendorString = "Qualcomm Inc"
|
||||
c.VendorID = Qualcomm
|
||||
case 0x56:
|
||||
c.VendorString = "Marvell International Ltd"
|
||||
c.VendorID = Marvell
|
||||
case 0x69:
|
||||
c.VendorString = "Intel Corporation"
|
||||
c.VendorID = Intel
|
||||
}
|
||||
|
||||
// Lower 4 bits: Architecture
|
||||
// Architecture Meaning
|
||||
// 0b0001 Armv4.
|
||||
// 0b0010 Armv4T.
|
||||
// 0b0011 Armv5 (obsolete).
|
||||
// 0b0100 Armv5T.
|
||||
// 0b0101 Armv5TE.
|
||||
// 0b0110 Armv5TEJ.
|
||||
// 0b0111 Armv6.
|
||||
// 0b1111 Architectural features are individually identified in the ID_* registers, see 'ID registers'.
|
||||
// Upper 4 bit: Variant
|
||||
// An IMPLEMENTATION DEFINED variant number.
|
||||
// Typically, this field is used to distinguish between different product variants, or major revisions of a product.
|
||||
c.Family = int(midr>>16) & 0xff
|
||||
|
||||
// PartNum, bits [15:4]
|
||||
// An IMPLEMENTATION DEFINED primary part number for the device.
|
||||
// On processors implemented by Arm, if the top four bits of the primary
|
||||
// part number are 0x0 or 0x7, the variant and architecture are encoded differently.
|
||||
// Revision, bits [3:0]
|
||||
// An IMPLEMENTATION DEFINED revision number for the device.
|
||||
c.Model = int(midr) & 0xffff
|
||||
|
||||
procFeatures := getProcFeatures()
|
||||
|
||||
// ID_AA64PFR0_EL1 - Processor Feature Register 0
|
||||
// x--------------------------------------------------x
|
||||
// | Name | bits | visible |
|
||||
// |--------------------------------------------------|
|
||||
// | DIT | [51-48] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | SVE | [35-32] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | GIC | [27-24] | n |
|
||||
// |--------------------------------------------------|
|
||||
// | AdvSIMD | [23-20] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | FP | [19-16] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | EL3 | [15-12] | n |
|
||||
// |--------------------------------------------------|
|
||||
// | EL2 | [11-8] | n |
|
||||
// |--------------------------------------------------|
|
||||
// | EL1 | [7-4] | n |
|
||||
// |--------------------------------------------------|
|
||||
// | EL0 | [3-0] | n |
|
||||
// x--------------------------------------------------x
|
||||
|
||||
var f flagSet
|
||||
// if procFeatures&(0xf<<48) != 0 {
|
||||
// fmt.Println("DIT")
|
||||
// }
|
||||
f.setIf(procFeatures&(0xf<<32) != 0, SVE)
|
||||
if procFeatures&(0xf<<20) != 15<<20 {
|
||||
f.set(ASIMD)
|
||||
// https://developer.arm.com/docs/ddi0595/b/aarch64-system-registers/id_aa64pfr0_el1
|
||||
// 0b0001 --> As for 0b0000, and also includes support for half-precision floating-point arithmetic.
|
||||
f.setIf(procFeatures&(0xf<<20) == 1<<20, FPHP, ASIMDHP)
|
||||
}
|
||||
f.setIf(procFeatures&(0xf<<16) != 0, FP)
|
||||
|
||||
instAttrReg0, instAttrReg1 := getInstAttributes()
|
||||
|
||||
// https://developer.arm.com/docs/ddi0595/b/aarch64-system-registers/id_aa64isar0_el1
|
||||
//
|
||||
// ID_AA64ISAR0_EL1 - Instruction Set Attribute Register 0
|
||||
// x--------------------------------------------------x
|
||||
// | Name | bits | visible |
|
||||
// |--------------------------------------------------|
|
||||
// | RNDR | [63-60] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | TLB | [59-56] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | TS | [55-52] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | FHM | [51-48] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | DP | [47-44] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | SM4 | [43-40] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | SM3 | [39-36] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | SHA3 | [35-32] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | RDM | [31-28] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | ATOMICS | [23-20] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | CRC32 | [19-16] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | SHA2 | [15-12] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | SHA1 | [11-8] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | AES | [7-4] | y |
|
||||
// x--------------------------------------------------x
|
||||
|
||||
f.setIf(instAttrReg0&(0xf<<60) != 0, RNDR)
|
||||
f.setIf(instAttrReg0&(0xf<<56) != 0, TLB)
|
||||
f.setIf(instAttrReg0&(0xf<<52) != 0, TS)
|
||||
f.setIf(instAttrReg0&(0xf<<48) != 0, FHM)
|
||||
f.setIf(instAttrReg0&(0xf<<44) != 0, ASIMDDP)
|
||||
f.setIf(instAttrReg0&(0xf<<40) != 0, SM4)
|
||||
f.setIf(instAttrReg0&(0xf<<36) != 0, SM3)
|
||||
f.setIf(instAttrReg0&(0xf<<32) != 0, SHA3)
|
||||
f.setIf(instAttrReg0&(0xf<<28) != 0, ASIMDRDM)
|
||||
f.setIf(instAttrReg0&(0xf<<20) != 0, ATOMICS)
|
||||
f.setIf(instAttrReg0&(0xf<<16) != 0, CRC32)
|
||||
f.setIf(instAttrReg0&(0xf<<12) != 0, SHA2)
|
||||
// https://developer.arm.com/docs/ddi0595/b/aarch64-system-registers/id_aa64isar0_el1
|
||||
// 0b0010 --> As 0b0001, plus SHA512H, SHA512H2, SHA512SU0, and SHA512SU1 instructions implemented.
|
||||
f.setIf(instAttrReg0&(0xf<<12) == 2<<12, SHA512)
|
||||
f.setIf(instAttrReg0&(0xf<<8) != 0, SHA1)
|
||||
f.setIf(instAttrReg0&(0xf<<4) != 0, AESARM)
|
||||
// https://developer.arm.com/docs/ddi0595/b/aarch64-system-registers/id_aa64isar0_el1
|
||||
// 0b0010 --> As for 0b0001, plus PMULL/PMULL2 instructions operating on 64-bit data quantities.
|
||||
f.setIf(instAttrReg0&(0xf<<4) == 2<<4, PMULL)
|
||||
|
||||
// https://developer.arm.com/docs/ddi0595/b/aarch64-system-registers/id_aa64isar1_el1
|
||||
//
|
||||
// ID_AA64ISAR1_EL1 - Instruction set attribute register 1
|
||||
// x--------------------------------------------------x
|
||||
// | Name | bits | visible |
|
||||
// |--------------------------------------------------|
|
||||
// | GPI | [31-28] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | GPA | [27-24] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | LRCPC | [23-20] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | FCMA | [19-16] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | JSCVT | [15-12] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | API | [11-8] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | APA | [7-4] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | DPB | [3-0] | y |
|
||||
// x--------------------------------------------------x
|
||||
|
||||
// if instAttrReg1&(0xf<<28) != 0 {
|
||||
// fmt.Println("GPI")
|
||||
// }
|
||||
f.setIf(instAttrReg1&(0xf<<28) != 24, GPA)
|
||||
f.setIf(instAttrReg1&(0xf<<20) != 0, LRCPC)
|
||||
f.setIf(instAttrReg1&(0xf<<16) != 0, FCMA)
|
||||
f.setIf(instAttrReg1&(0xf<<12) != 0, JSCVT)
|
||||
// if instAttrReg1&(0xf<<8) != 0 {
|
||||
// fmt.Println("API")
|
||||
// }
|
||||
// if instAttrReg1&(0xf<<4) != 0 {
|
||||
// fmt.Println("APA")
|
||||
// }
|
||||
f.setIf(instAttrReg1&(0xf<<0) != 0, DCPOP)
|
||||
|
||||
// Store
|
||||
c.featureSet.or(f)
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) 2015 Klaus Post, released under MIT License. See LICENSE file.
|
||||
|
||||
//go:build (!amd64 && !386 && !arm64) || gccgo || noasm || appengine
|
||||
// +build !amd64,!386,!arm64 gccgo noasm appengine
|
||||
|
||||
package cpuid
|
||||
|
||||
func initCPU() {
|
||||
cpuid = func(uint32) (a, b, c, d uint32) { return 0, 0, 0, 0 }
|
||||
cpuidex = func(x, y uint32) (a, b, c, d uint32) { return 0, 0, 0, 0 }
|
||||
xgetbv = func(uint32) (a, b uint32) { return 0, 0 }
|
||||
rdtscpAsm = func() (a, b, c, d uint32) { return 0, 0, 0, 0 }
|
||||
|
||||
}
|
||||
|
||||
func addInfo(info *CPUInfo, safe bool) {}
|
||||
func getVectorLength() (vl, pl uint64) { return 0, 0 }
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
// Copyright (c) 2015 Klaus Post, released under MIT License. See LICENSE file.
|
||||
|
||||
//go:build (386 && !gccgo && !noasm && !appengine) || (amd64 && !gccgo && !noasm && !appengine)
|
||||
// +build 386,!gccgo,!noasm,!appengine amd64,!gccgo,!noasm,!appengine
|
||||
|
||||
package cpuid
|
||||
|
||||
func asmCpuid(op uint32) (eax, ebx, ecx, edx uint32)
|
||||
func asmCpuidex(op, op2 uint32) (eax, ebx, ecx, edx uint32)
|
||||
func asmXgetbv(index uint32) (eax, edx uint32)
|
||||
func asmRdtscpAsm() (eax, ebx, ecx, edx uint32)
|
||||
func asmDarwinHasAVX512() bool
|
||||
|
||||
func initCPU() {
|
||||
cpuid = asmCpuid
|
||||
cpuidex = asmCpuidex
|
||||
xgetbv = asmXgetbv
|
||||
rdtscpAsm = asmRdtscpAsm
|
||||
darwinHasAVX512 = asmDarwinHasAVX512
|
||||
}
|
||||
|
||||
func addInfo(c *CPUInfo, safe bool) {
|
||||
c.maxFunc = maxFunctionID()
|
||||
c.maxExFunc = maxExtendedFunction()
|
||||
c.BrandName = brandName()
|
||||
c.CacheLine = cacheLine()
|
||||
c.Family, c.Model, c.Stepping = familyModel()
|
||||
c.featureSet = support()
|
||||
c.SGX = hasSGX(c.featureSet.inSet(SGX), c.featureSet.inSet(SGXLC))
|
||||
c.AMDMemEncryption = hasAMDMemEncryption(c.featureSet.inSet(SME) || c.featureSet.inSet(SEV))
|
||||
c.ThreadsPerCore = threadsPerCore()
|
||||
c.LogicalCores = logicalCores()
|
||||
c.PhysicalCores = physicalCores()
|
||||
c.VendorID, c.VendorString = vendorID()
|
||||
c.HypervisorVendorID, c.HypervisorVendorString = hypervisorVendorID()
|
||||
c.AVX10Level = c.supportAVX10()
|
||||
c.cacheSize()
|
||||
c.frequencies()
|
||||
if c.maxFunc >= 0x0A {
|
||||
eax, ebx, _, edx := cpuid(0x0A)
|
||||
c.PMU = parseLeaf0AH(c, eax, ebx, edx)
|
||||
}
|
||||
}
|
||||
|
||||
func getVectorLength() (vl, pl uint64) { return 0, 0 }
|
||||
+308
@@ -0,0 +1,308 @@
|
||||
// Code generated by "stringer -type=FeatureID,Vendor"; DO NOT EDIT.
|
||||
|
||||
package cpuid
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[ADX-1]
|
||||
_ = x[AESNI-2]
|
||||
_ = x[AMD3DNOW-3]
|
||||
_ = x[AMD3DNOWEXT-4]
|
||||
_ = x[AMXBF16-5]
|
||||
_ = x[AMXFP16-6]
|
||||
_ = x[AMXINT8-7]
|
||||
_ = x[AMXFP8-8]
|
||||
_ = x[AMXTILE-9]
|
||||
_ = x[AMXTF32-10]
|
||||
_ = x[AMXCOMPLEX-11]
|
||||
_ = x[AMXTRANSPOSE-12]
|
||||
_ = x[APX_F-13]
|
||||
_ = x[AVX-14]
|
||||
_ = x[AVX10-15]
|
||||
_ = x[AVX10_128-16]
|
||||
_ = x[AVX10_256-17]
|
||||
_ = x[AVX10_512-18]
|
||||
_ = x[AVX2-19]
|
||||
_ = x[AVX512BF16-20]
|
||||
_ = x[AVX512BITALG-21]
|
||||
_ = x[AVX512BW-22]
|
||||
_ = x[AVX512CD-23]
|
||||
_ = x[AVX512DQ-24]
|
||||
_ = x[AVX512ER-25]
|
||||
_ = x[AVX512F-26]
|
||||
_ = x[AVX512FP16-27]
|
||||
_ = x[AVX512IFMA-28]
|
||||
_ = x[AVX512PF-29]
|
||||
_ = x[AVX512VBMI-30]
|
||||
_ = x[AVX512VBMI2-31]
|
||||
_ = x[AVX512VL-32]
|
||||
_ = x[AVX512VNNI-33]
|
||||
_ = x[AVX512VP2INTERSECT-34]
|
||||
_ = x[AVX512VPOPCNTDQ-35]
|
||||
_ = x[AVXIFMA-36]
|
||||
_ = x[AVXNECONVERT-37]
|
||||
_ = x[AVXSLOW-38]
|
||||
_ = x[AVXVNNI-39]
|
||||
_ = x[AVXVNNIINT8-40]
|
||||
_ = x[AVXVNNIINT16-41]
|
||||
_ = x[BHI_CTRL-42]
|
||||
_ = x[BMI1-43]
|
||||
_ = x[BMI2-44]
|
||||
_ = x[CETIBT-45]
|
||||
_ = x[CETSS-46]
|
||||
_ = x[CLDEMOTE-47]
|
||||
_ = x[CLMUL-48]
|
||||
_ = x[CLZERO-49]
|
||||
_ = x[CMOV-50]
|
||||
_ = x[CMPCCXADD-51]
|
||||
_ = x[CMPSB_SCADBS_SHORT-52]
|
||||
_ = x[CMPXCHG8-53]
|
||||
_ = x[CPBOOST-54]
|
||||
_ = x[CPPC-55]
|
||||
_ = x[CX16-56]
|
||||
_ = x[EFER_LMSLE_UNS-57]
|
||||
_ = x[ENQCMD-58]
|
||||
_ = x[ERMS-59]
|
||||
_ = x[F16C-60]
|
||||
_ = x[FLUSH_L1D-61]
|
||||
_ = x[FMA3-62]
|
||||
_ = x[FMA4-63]
|
||||
_ = x[FP128-64]
|
||||
_ = x[FP256-65]
|
||||
_ = x[FSRM-66]
|
||||
_ = x[FXSR-67]
|
||||
_ = x[FXSROPT-68]
|
||||
_ = x[GFNI-69]
|
||||
_ = x[HLE-70]
|
||||
_ = x[HRESET-71]
|
||||
_ = x[HTT-72]
|
||||
_ = x[HWA-73]
|
||||
_ = x[HYBRID_CPU-74]
|
||||
_ = x[HYPERVISOR-75]
|
||||
_ = x[IA32_ARCH_CAP-76]
|
||||
_ = x[IA32_CORE_CAP-77]
|
||||
_ = x[IBPB-78]
|
||||
_ = x[IBPB_BRTYPE-79]
|
||||
_ = x[IBRS-80]
|
||||
_ = x[IBRS_PREFERRED-81]
|
||||
_ = x[IBRS_PROVIDES_SMP-82]
|
||||
_ = x[IBS-83]
|
||||
_ = x[IBSBRNTRGT-84]
|
||||
_ = x[IBSFETCHSAM-85]
|
||||
_ = x[IBSFFV-86]
|
||||
_ = x[IBSOPCNT-87]
|
||||
_ = x[IBSOPCNTEXT-88]
|
||||
_ = x[IBSOPSAM-89]
|
||||
_ = x[IBSRDWROPCNT-90]
|
||||
_ = x[IBSRIPINVALIDCHK-91]
|
||||
_ = x[IBS_FETCH_CTLX-92]
|
||||
_ = x[IBS_OPDATA4-93]
|
||||
_ = x[IBS_OPFUSE-94]
|
||||
_ = x[IBS_PREVENTHOST-95]
|
||||
_ = x[IBS_ZEN4-96]
|
||||
_ = x[IDPRED_CTRL-97]
|
||||
_ = x[INT_WBINVD-98]
|
||||
_ = x[INVLPGB-99]
|
||||
_ = x[KEYLOCKER-100]
|
||||
_ = x[KEYLOCKERW-101]
|
||||
_ = x[LAHF-102]
|
||||
_ = x[LAM-103]
|
||||
_ = x[LBRVIRT-104]
|
||||
_ = x[LZCNT-105]
|
||||
_ = x[MCAOVERFLOW-106]
|
||||
_ = x[MCDT_NO-107]
|
||||
_ = x[MCOMMIT-108]
|
||||
_ = x[MD_CLEAR-109]
|
||||
_ = x[MMX-110]
|
||||
_ = x[MMXEXT-111]
|
||||
_ = x[MOVBE-112]
|
||||
_ = x[MOVDIR64B-113]
|
||||
_ = x[MOVDIRI-114]
|
||||
_ = x[MOVSB_ZL-115]
|
||||
_ = x[MOVU-116]
|
||||
_ = x[MPX-117]
|
||||
_ = x[MSRIRC-118]
|
||||
_ = x[MSRLIST-119]
|
||||
_ = x[MSR_PAGEFLUSH-120]
|
||||
_ = x[NRIPS-121]
|
||||
_ = x[NX-122]
|
||||
_ = x[OSXSAVE-123]
|
||||
_ = x[PCONFIG-124]
|
||||
_ = x[POPCNT-125]
|
||||
_ = x[PPIN-126]
|
||||
_ = x[PREFETCHI-127]
|
||||
_ = x[PSFD-128]
|
||||
_ = x[RDPRU-129]
|
||||
_ = x[RDRAND-130]
|
||||
_ = x[RDSEED-131]
|
||||
_ = x[RDTSCP-132]
|
||||
_ = x[RRSBA_CTRL-133]
|
||||
_ = x[RTM-134]
|
||||
_ = x[RTM_ALWAYS_ABORT-135]
|
||||
_ = x[SBPB-136]
|
||||
_ = x[SERIALIZE-137]
|
||||
_ = x[SEV-138]
|
||||
_ = x[SEV_64BIT-139]
|
||||
_ = x[SEV_ALTERNATIVE-140]
|
||||
_ = x[SEV_DEBUGSWAP-141]
|
||||
_ = x[SEV_ES-142]
|
||||
_ = x[SEV_RESTRICTED-143]
|
||||
_ = x[SEV_SNP-144]
|
||||
_ = x[SGX-145]
|
||||
_ = x[SGXLC-146]
|
||||
_ = x[SGXPQC-147]
|
||||
_ = x[SHA-148]
|
||||
_ = x[SME-149]
|
||||
_ = x[SME_COHERENT-150]
|
||||
_ = x[SM3_X86-151]
|
||||
_ = x[SM4_X86-152]
|
||||
_ = x[SPEC_CTRL_SSBD-153]
|
||||
_ = x[SRBDS_CTRL-154]
|
||||
_ = x[SRSO_MSR_FIX-155]
|
||||
_ = x[SRSO_NO-156]
|
||||
_ = x[SRSO_USER_KERNEL_NO-157]
|
||||
_ = x[SSE-158]
|
||||
_ = x[SSE2-159]
|
||||
_ = x[SSE3-160]
|
||||
_ = x[SSE4-161]
|
||||
_ = x[SSE42-162]
|
||||
_ = x[SSE4A-163]
|
||||
_ = x[SSSE3-164]
|
||||
_ = x[STIBP-165]
|
||||
_ = x[STIBP_ALWAYSON-166]
|
||||
_ = x[STOSB_SHORT-167]
|
||||
_ = x[SUCCOR-168]
|
||||
_ = x[SVM-169]
|
||||
_ = x[SVMDA-170]
|
||||
_ = x[SVMFBASID-171]
|
||||
_ = x[SVML-172]
|
||||
_ = x[SVMNP-173]
|
||||
_ = x[SVMPF-174]
|
||||
_ = x[SVMPFT-175]
|
||||
_ = x[SYSCALL-176]
|
||||
_ = x[SYSEE-177]
|
||||
_ = x[TBM-178]
|
||||
_ = x[TDX_GUEST-179]
|
||||
_ = x[TLB_FLUSH_NESTED-180]
|
||||
_ = x[TME-181]
|
||||
_ = x[TOPEXT-182]
|
||||
_ = x[TSA_L1_NO-183]
|
||||
_ = x[TSA_SQ_NO-184]
|
||||
_ = x[TSA_VERW_CLEAR-185]
|
||||
_ = x[TSCRATEMSR-186]
|
||||
_ = x[TSXLDTRK-187]
|
||||
_ = x[VAES-188]
|
||||
_ = x[VMCBCLEAN-189]
|
||||
_ = x[VMPL-190]
|
||||
_ = x[VMSA_REGPROT-191]
|
||||
_ = x[VMX-192]
|
||||
_ = x[VPCLMULQDQ-193]
|
||||
_ = x[VTE-194]
|
||||
_ = x[WAITPKG-195]
|
||||
_ = x[WBNOINVD-196]
|
||||
_ = x[WRMSRNS-197]
|
||||
_ = x[X87-198]
|
||||
_ = x[XGETBV1-199]
|
||||
_ = x[XOP-200]
|
||||
_ = x[XSAVE-201]
|
||||
_ = x[XSAVEC-202]
|
||||
_ = x[XSAVEOPT-203]
|
||||
_ = x[XSAVES-204]
|
||||
_ = x[AESARM-205]
|
||||
_ = x[ARMCPUID-206]
|
||||
_ = x[ASIMD-207]
|
||||
_ = x[ASIMDDP-208]
|
||||
_ = x[ASIMDHP-209]
|
||||
_ = x[ASIMDRDM-210]
|
||||
_ = x[ATOMICS-211]
|
||||
_ = x[CRC32-212]
|
||||
_ = x[DCPOP-213]
|
||||
_ = x[EVTSTRM-214]
|
||||
_ = x[FCMA-215]
|
||||
_ = x[FHM-216]
|
||||
_ = x[FP-217]
|
||||
_ = x[FPHP-218]
|
||||
_ = x[GPA-219]
|
||||
_ = x[JSCVT-220]
|
||||
_ = x[LRCPC-221]
|
||||
_ = x[PMULL-222]
|
||||
_ = x[RNDR-223]
|
||||
_ = x[TLB-224]
|
||||
_ = x[TS-225]
|
||||
_ = x[SHA1-226]
|
||||
_ = x[SHA2-227]
|
||||
_ = x[SHA3-228]
|
||||
_ = x[SHA512-229]
|
||||
_ = x[SM3-230]
|
||||
_ = x[SM4-231]
|
||||
_ = x[SVE-232]
|
||||
_ = x[PMU_FIXEDCOUNTER_CYCLES-233]
|
||||
_ = x[PMU_FIXEDCOUNTER_REFCYCLES-234]
|
||||
_ = x[PMU_FIXEDCOUNTER_INSTRUCTIONS-235]
|
||||
_ = x[PMU_FIXEDCOUNTER_TOPDOWN_SLOTS-236]
|
||||
_ = x[lastID-237]
|
||||
_ = x[firstID-0]
|
||||
}
|
||||
|
||||
const _FeatureID_name = "firstIDADXAESNIAMD3DNOWAMD3DNOWEXTAMXBF16AMXFP16AMXINT8AMXFP8AMXTILEAMXTF32AMXCOMPLEXAMXTRANSPOSEAPX_FAVXAVX10AVX10_128AVX10_256AVX10_512AVX2AVX512BF16AVX512BITALGAVX512BWAVX512CDAVX512DQAVX512ERAVX512FAVX512FP16AVX512IFMAAVX512PFAVX512VBMIAVX512VBMI2AVX512VLAVX512VNNIAVX512VP2INTERSECTAVX512VPOPCNTDQAVXIFMAAVXNECONVERTAVXSLOWAVXVNNIAVXVNNIINT8AVXVNNIINT16BHI_CTRLBMI1BMI2CETIBTCETSSCLDEMOTECLMULCLZEROCMOVCMPCCXADDCMPSB_SCADBS_SHORTCMPXCHG8CPBOOSTCPPCCX16EFER_LMSLE_UNSENQCMDERMSF16CFLUSH_L1DFMA3FMA4FP128FP256FSRMFXSRFXSROPTGFNIHLEHRESETHTTHWAHYBRID_CPUHYPERVISORIA32_ARCH_CAPIA32_CORE_CAPIBPBIBPB_BRTYPEIBRSIBRS_PREFERREDIBRS_PROVIDES_SMPIBSIBSBRNTRGTIBSFETCHSAMIBSFFVIBSOPCNTIBSOPCNTEXTIBSOPSAMIBSRDWROPCNTIBSRIPINVALIDCHKIBS_FETCH_CTLXIBS_OPDATA4IBS_OPFUSEIBS_PREVENTHOSTIBS_ZEN4IDPRED_CTRLINT_WBINVDINVLPGBKEYLOCKERKEYLOCKERWLAHFLAMLBRVIRTLZCNTMCAOVERFLOWMCDT_NOMCOMMITMD_CLEARMMXMMXEXTMOVBEMOVDIR64BMOVDIRIMOVSB_ZLMOVUMPXMSRIRCMSRLISTMSR_PAGEFLUSHNRIPSNXOSXSAVEPCONFIGPOPCNTPPINPREFETCHIPSFDRDPRURDRANDRDSEEDRDTSCPRRSBA_CTRLRTMRTM_ALWAYS_ABORTSBPBSERIALIZESEVSEV_64BITSEV_ALTERNATIVESEV_DEBUGSWAPSEV_ESSEV_RESTRICTEDSEV_SNPSGXSGXLCSGXPQCSHASMESME_COHERENTSM3_X86SM4_X86SPEC_CTRL_SSBDSRBDS_CTRLSRSO_MSR_FIXSRSO_NOSRSO_USER_KERNEL_NOSSESSE2SSE3SSE4SSE42SSE4ASSSE3STIBPSTIBP_ALWAYSONSTOSB_SHORTSUCCORSVMSVMDASVMFBASIDSVMLSVMNPSVMPFSVMPFTSYSCALLSYSEETBMTDX_GUESTTLB_FLUSH_NESTEDTMETOPEXTTSA_L1_NOTSA_SQ_NOTSA_VERW_CLEARTSCRATEMSRTSXLDTRKVAESVMCBCLEANVMPLVMSA_REGPROTVMXVPCLMULQDQVTEWAITPKGWBNOINVDWRMSRNSX87XGETBV1XOPXSAVEXSAVECXSAVEOPTXSAVESAESARMARMCPUIDASIMDASIMDDPASIMDHPASIMDRDMATOMICSCRC32DCPOPEVTSTRMFCMAFHMFPFPHPGPAJSCVTLRCPCPMULLRNDRTLBTSSHA1SHA2SHA3SHA512SM3SM4SVEPMU_FIXEDCOUNTER_CYCLESPMU_FIXEDCOUNTER_REFCYCLESPMU_FIXEDCOUNTER_INSTRUCTIONSPMU_FIXEDCOUNTER_TOPDOWN_SLOTSlastID"
|
||||
|
||||
var _FeatureID_index = [...]uint16{0, 7, 10, 15, 23, 34, 41, 48, 55, 61, 68, 75, 85, 97, 102, 105, 110, 119, 128, 137, 141, 151, 163, 171, 179, 187, 195, 202, 212, 222, 230, 240, 251, 259, 269, 287, 302, 309, 321, 328, 335, 346, 358, 366, 370, 374, 380, 385, 393, 398, 404, 408, 417, 435, 443, 450, 454, 458, 472, 478, 482, 486, 495, 499, 503, 508, 513, 517, 521, 528, 532, 535, 541, 544, 547, 557, 567, 580, 593, 597, 608, 612, 626, 643, 646, 656, 667, 673, 681, 692, 700, 712, 728, 742, 753, 763, 778, 786, 797, 807, 814, 823, 833, 837, 840, 847, 852, 863, 870, 877, 885, 888, 894, 899, 908, 915, 923, 927, 930, 936, 943, 956, 961, 963, 970, 977, 983, 987, 996, 1000, 1005, 1011, 1017, 1023, 1033, 1036, 1052, 1056, 1065, 1068, 1077, 1092, 1105, 1111, 1125, 1132, 1135, 1140, 1146, 1149, 1152, 1164, 1171, 1178, 1192, 1202, 1214, 1221, 1240, 1243, 1247, 1251, 1255, 1260, 1265, 1270, 1275, 1289, 1300, 1306, 1309, 1314, 1323, 1327, 1332, 1337, 1343, 1350, 1355, 1358, 1367, 1383, 1386, 1392, 1401, 1410, 1424, 1434, 1442, 1446, 1455, 1459, 1471, 1474, 1484, 1487, 1494, 1502, 1509, 1512, 1519, 1522, 1527, 1533, 1541, 1547, 1553, 1561, 1566, 1573, 1580, 1588, 1595, 1600, 1605, 1612, 1616, 1619, 1621, 1625, 1628, 1633, 1638, 1643, 1647, 1650, 1652, 1656, 1660, 1664, 1670, 1673, 1676, 1679, 1702, 1728, 1757, 1787, 1793}
|
||||
|
||||
func (i FeatureID) String() string {
|
||||
if i < 0 || i >= FeatureID(len(_FeatureID_index)-1) {
|
||||
return "FeatureID(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _FeatureID_name[_FeatureID_index[i]:_FeatureID_index[i+1]]
|
||||
}
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[VendorUnknown-0]
|
||||
_ = x[Intel-1]
|
||||
_ = x[AMD-2]
|
||||
_ = x[VIA-3]
|
||||
_ = x[Transmeta-4]
|
||||
_ = x[NSC-5]
|
||||
_ = x[KVM-6]
|
||||
_ = x[MSVM-7]
|
||||
_ = x[VMware-8]
|
||||
_ = x[XenHVM-9]
|
||||
_ = x[Bhyve-10]
|
||||
_ = x[Hygon-11]
|
||||
_ = x[SiS-12]
|
||||
_ = x[RDC-13]
|
||||
_ = x[Ampere-14]
|
||||
_ = x[ARM-15]
|
||||
_ = x[Broadcom-16]
|
||||
_ = x[Cavium-17]
|
||||
_ = x[DEC-18]
|
||||
_ = x[Fujitsu-19]
|
||||
_ = x[Infineon-20]
|
||||
_ = x[Motorola-21]
|
||||
_ = x[NVIDIA-22]
|
||||
_ = x[AMCC-23]
|
||||
_ = x[Qualcomm-24]
|
||||
_ = x[Marvell-25]
|
||||
_ = x[QEMU-26]
|
||||
_ = x[QNX-27]
|
||||
_ = x[ACRN-28]
|
||||
_ = x[SRE-29]
|
||||
_ = x[Apple-30]
|
||||
_ = x[lastVendor-31]
|
||||
}
|
||||
|
||||
const _Vendor_name = "VendorUnknownIntelAMDVIATransmetaNSCKVMMSVMVMwareXenHVMBhyveHygonSiSRDCAmpereARMBroadcomCaviumDECFujitsuInfineonMotorolaNVIDIAAMCCQualcommMarvellQEMUQNXACRNSREApplelastVendor"
|
||||
|
||||
var _Vendor_index = [...]uint8{0, 13, 18, 21, 24, 33, 36, 39, 43, 49, 55, 60, 65, 68, 71, 77, 80, 88, 94, 97, 104, 112, 120, 126, 130, 138, 145, 149, 152, 156, 159, 164, 174}
|
||||
|
||||
func (i Vendor) String() string {
|
||||
if i < 0 || i >= Vendor(len(_Vendor_index)-1) {
|
||||
return "Vendor(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _Vendor_name[_Vendor_index[i]:_Vendor_index[i+1]]
|
||||
}
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
// Copyright (c) 2020 Klaus Post, released under MIT License. See LICENSE file.
|
||||
|
||||
package cpuid
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func detectOS(c *CPUInfo) bool {
|
||||
if runtime.GOOS != "ios" {
|
||||
tryToFillCPUInfoFomSysctl(c)
|
||||
}
|
||||
// There are no hw.optional sysctl values for the below features on Mac OS 11.0
|
||||
// to detect their supported state dynamically. Assume the CPU features that
|
||||
// Apple Silicon M1 supports to be available as a minimal set of features
|
||||
// to all Go programs running on darwin/arm64.
|
||||
// TODO: Add more if we know them.
|
||||
c.featureSet.setIf(runtime.GOOS != "ios", AESARM, PMULL, SHA1, SHA2)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func sysctlGetBool(name string) bool {
|
||||
value, err := unix.SysctlUint32(name)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return value != 0
|
||||
}
|
||||
|
||||
func sysctlGetString(name string) string {
|
||||
value, err := unix.Sysctl(name)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func sysctlGetInt(unknown int, names ...string) int {
|
||||
for _, name := range names {
|
||||
value, err := unix.SysctlUint32(name)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if value != 0 {
|
||||
return int(value)
|
||||
}
|
||||
}
|
||||
return unknown
|
||||
}
|
||||
|
||||
func sysctlGetInt64(unknown int, names ...string) int {
|
||||
for _, name := range names {
|
||||
value64, err := unix.SysctlUint64(name)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if int(value64) != unknown {
|
||||
return int(value64)
|
||||
}
|
||||
}
|
||||
return unknown
|
||||
}
|
||||
|
||||
func setFeature(c *CPUInfo, feature FeatureID, aliases ...string) {
|
||||
for _, alias := range aliases {
|
||||
set := sysctlGetBool(alias)
|
||||
c.featureSet.setIf(set, feature)
|
||||
if set {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func tryToFillCPUInfoFomSysctl(c *CPUInfo) {
|
||||
c.BrandName = sysctlGetString("machdep.cpu.brand_string")
|
||||
|
||||
if len(c.BrandName) != 0 {
|
||||
c.VendorString = strings.Fields(c.BrandName)[0]
|
||||
}
|
||||
|
||||
c.PhysicalCores = sysctlGetInt(runtime.NumCPU(), "hw.physicalcpu")
|
||||
c.ThreadsPerCore = sysctlGetInt(1, "machdep.cpu.thread_count", "kern.num_threads") /
|
||||
sysctlGetInt(1, "hw.physicalcpu")
|
||||
c.LogicalCores = sysctlGetInt(runtime.NumCPU(), "machdep.cpu.core_count")
|
||||
c.Family = sysctlGetInt(0, "machdep.cpu.family", "hw.cpufamily")
|
||||
c.Model = sysctlGetInt(0, "machdep.cpu.model")
|
||||
c.CacheLine = sysctlGetInt64(0, "hw.cachelinesize")
|
||||
c.Cache.L1I = sysctlGetInt64(-1, "hw.l1icachesize")
|
||||
c.Cache.L1D = sysctlGetInt64(-1, "hw.l1dcachesize")
|
||||
c.Cache.L2 = sysctlGetInt64(-1, "hw.l2cachesize")
|
||||
c.Cache.L3 = sysctlGetInt64(-1, "hw.l3cachesize")
|
||||
|
||||
// ARM features:
|
||||
//
|
||||
// Note: On some Apple Silicon system, some feats have aliases. See:
|
||||
// https://developer.apple.com/documentation/kernel/1387446-sysctlbyname/determining_instruction_set_characteristics
|
||||
// When so, we look at all aliases and consider a feature available when at least one identifier matches.
|
||||
setFeature(c, AESARM, "hw.optional.arm.FEAT_AES") // AES instructions
|
||||
setFeature(c, ASIMD, "hw.optional.arm.AdvSIMD", "hw.optional.neon") // Advanced SIMD
|
||||
setFeature(c, ASIMDDP, "hw.optional.arm.FEAT_DotProd") // SIMD Dot Product
|
||||
setFeature(c, ASIMDHP, "hw.optional.arm.AdvSIMD_HPFPCvt", "hw.optional.neon_hpfp") // Advanced SIMD half-precision floating point
|
||||
setFeature(c, ASIMDRDM, "hw.optional.arm.FEAT_RDM") // Rounding Double Multiply Accumulate/Subtract
|
||||
setFeature(c, ATOMICS, "hw.optional.arm.FEAT_LSE", "hw.optional.armv8_1_atomics") // Large System Extensions (LSE)
|
||||
setFeature(c, CRC32, "hw.optional.arm.FEAT_CRC32", "hw.optional.armv8_crc32") // CRC32/CRC32C instructions
|
||||
setFeature(c, DCPOP, "hw.optional.arm.FEAT_DPB") // Data cache clean to Point of Persistence (DC CVAP)
|
||||
setFeature(c, EVTSTRM, "hw.optional.arm.FEAT_ECV") // Generic timer
|
||||
setFeature(c, FCMA, "hw.optional.arm.FEAT_FCMA", "hw.optional.armv8_3_compnum") // Floating point complex number addition and multiplication
|
||||
setFeature(c, FHM, "hw.optional.armv8_2_fhm", "hw.optional.arm.FEAT_FHM") // FMLAL and FMLSL instructions
|
||||
setFeature(c, FP, "hw.optional.floatingpoint") // Single-precision and double-precision floating point
|
||||
setFeature(c, FPHP, "hw.optional.arm.FEAT_FP16", "hw.optional.neon_fp16") // Half-precision floating point
|
||||
setFeature(c, GPA, "hw.optional.arm.FEAT_PAuth") // Generic Pointer Authentication
|
||||
setFeature(c, JSCVT, "hw.optional.arm.FEAT_JSCVT") // Javascript-style double->int convert (FJCVTZS)
|
||||
setFeature(c, LRCPC, "hw.optional.arm.FEAT_LRCPC") // Weaker release consistency (LDAPR, etc)
|
||||
setFeature(c, PMULL, "hw.optional.arm.FEAT_PMULL") // Polynomial Multiply instructions (PMULL/PMULL2)
|
||||
setFeature(c, RNDR, "hw.optional.arm.FEAT_RNG") // Random Number instructions
|
||||
setFeature(c, TLB, "hw.optional.arm.FEAT_TLBIOS", "hw.optional.arm.FEAT_TLBIRANGE") // Outer Shareable and TLB range maintenance instructions
|
||||
setFeature(c, TS, "hw.optional.arm.FEAT_FlagM", "hw.optional.arm.FEAT_FlagM2") // Flag manipulation instructions
|
||||
setFeature(c, SHA1, "hw.optional.arm.FEAT_SHA1") // SHA-1 instructions (SHA1C, etc)
|
||||
setFeature(c, SHA2, "hw.optional.arm.FEAT_SHA256") // SHA-2 instructions (SHA256H, etc)
|
||||
setFeature(c, SHA3, "hw.optional.arm.FEAT_SHA3") // SHA-3 instructions (EOR3, RAXI, XAR, BCAX)
|
||||
setFeature(c, SHA512, "hw.optional.arm.FEAT_SHA512") // SHA512 instructions
|
||||
setFeature(c, SM3, "hw.optional.arm.FEAT_SM3") // SM3 instructions
|
||||
setFeature(c, SM4, "hw.optional.arm.FEAT_SM4") // SM4 instructions
|
||||
setFeature(c, SVE, "hw.optional.arm.FEAT_SVE") // Scalable Vector Extension
|
||||
}
|
||||
+208
@@ -0,0 +1,208 @@
|
||||
// Copyright (c) 2020 Klaus Post, released under MIT License. See LICENSE file.
|
||||
|
||||
// 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 located
|
||||
// here https://github.com/golang/sys/blob/master/LICENSE
|
||||
|
||||
package cpuid
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io/ioutil"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// HWCAP bits.
|
||||
const (
|
||||
hwcap_FP = 1 << 0
|
||||
hwcap_ASIMD = 1 << 1
|
||||
hwcap_EVTSTRM = 1 << 2
|
||||
hwcap_AES = 1 << 3
|
||||
hwcap_PMULL = 1 << 4
|
||||
hwcap_SHA1 = 1 << 5
|
||||
hwcap_SHA2 = 1 << 6
|
||||
hwcap_CRC32 = 1 << 7
|
||||
hwcap_ATOMICS = 1 << 8
|
||||
hwcap_FPHP = 1 << 9
|
||||
hwcap_ASIMDHP = 1 << 10
|
||||
hwcap_CPUID = 1 << 11
|
||||
hwcap_ASIMDRDM = 1 << 12
|
||||
hwcap_JSCVT = 1 << 13
|
||||
hwcap_FCMA = 1 << 14
|
||||
hwcap_LRCPC = 1 << 15
|
||||
hwcap_DCPOP = 1 << 16
|
||||
hwcap_SHA3 = 1 << 17
|
||||
hwcap_SM3 = 1 << 18
|
||||
hwcap_SM4 = 1 << 19
|
||||
hwcap_ASIMDDP = 1 << 20
|
||||
hwcap_SHA512 = 1 << 21
|
||||
hwcap_SVE = 1 << 22
|
||||
hwcap_ASIMDFHM = 1 << 23
|
||||
hwcap_DIT = 1 << 24
|
||||
hwcap_USCAT = 1 << 25
|
||||
hwcap_ILRCPC = 1 << 26
|
||||
hwcap_FLAGM = 1 << 27
|
||||
hwcap_SSBS = 1 << 28
|
||||
hwcap_SB = 1 << 29
|
||||
hwcap_PACA = 1 << 30
|
||||
hwcap_PACG = 1 << 31
|
||||
hwcap_GCS = 1 << 32
|
||||
|
||||
hwcap2_DCPODP = 1 << 0
|
||||
hwcap2_SVE2 = 1 << 1
|
||||
hwcap2_SVEAES = 1 << 2
|
||||
hwcap2_SVEPMULL = 1 << 3
|
||||
hwcap2_SVEBITPERM = 1 << 4
|
||||
hwcap2_SVESHA3 = 1 << 5
|
||||
hwcap2_SVESM4 = 1 << 6
|
||||
hwcap2_FLAGM2 = 1 << 7
|
||||
hwcap2_FRINT = 1 << 8
|
||||
hwcap2_SVEI8MM = 1 << 9
|
||||
hwcap2_SVEF32MM = 1 << 10
|
||||
hwcap2_SVEF64MM = 1 << 11
|
||||
hwcap2_SVEBF16 = 1 << 12
|
||||
hwcap2_I8MM = 1 << 13
|
||||
hwcap2_BF16 = 1 << 14
|
||||
hwcap2_DGH = 1 << 15
|
||||
hwcap2_RNG = 1 << 16
|
||||
hwcap2_BTI = 1 << 17
|
||||
hwcap2_MTE = 1 << 18
|
||||
hwcap2_ECV = 1 << 19
|
||||
hwcap2_AFP = 1 << 20
|
||||
hwcap2_RPRES = 1 << 21
|
||||
hwcap2_MTE3 = 1 << 22
|
||||
hwcap2_SME = 1 << 23
|
||||
hwcap2_SME_I16I64 = 1 << 24
|
||||
hwcap2_SME_F64F64 = 1 << 25
|
||||
hwcap2_SME_I8I32 = 1 << 26
|
||||
hwcap2_SME_F16F32 = 1 << 27
|
||||
hwcap2_SME_B16F32 = 1 << 28
|
||||
hwcap2_SME_F32F32 = 1 << 29
|
||||
hwcap2_SME_FA64 = 1 << 30
|
||||
hwcap2_WFXT = 1 << 31
|
||||
hwcap2_EBF16 = 1 << 32
|
||||
hwcap2_SVE_EBF16 = 1 << 33
|
||||
hwcap2_CSSC = 1 << 34
|
||||
hwcap2_RPRFM = 1 << 35
|
||||
hwcap2_SVE2P1 = 1 << 36
|
||||
hwcap2_SME2 = 1 << 37
|
||||
hwcap2_SME2P1 = 1 << 38
|
||||
hwcap2_SME_I16I32 = 1 << 39
|
||||
hwcap2_SME_BI32I32 = 1 << 40
|
||||
hwcap2_SME_B16B16 = 1 << 41
|
||||
hwcap2_SME_F16F16 = 1 << 42
|
||||
hwcap2_MOPS = 1 << 43
|
||||
hwcap2_HBC = 1 << 44
|
||||
hwcap2_SVE_B16B16 = 1 << 45
|
||||
hwcap2_LRCPC3 = 1 << 46
|
||||
hwcap2_LSE128 = 1 << 47
|
||||
hwcap2_FPMR = 1 << 48
|
||||
hwcap2_LUT = 1 << 49
|
||||
hwcap2_FAMINMAX = 1 << 50
|
||||
hwcap2_F8CVT = 1 << 51
|
||||
hwcap2_F8FMA = 1 << 52
|
||||
hwcap2_F8DP4 = 1 << 53
|
||||
hwcap2_F8DP2 = 1 << 54
|
||||
hwcap2_F8E4M3 = 1 << 55
|
||||
hwcap2_F8E5M2 = 1 << 56
|
||||
hwcap2_SME_LUTV2 = 1 << 57
|
||||
hwcap2_SME_F8F16 = 1 << 58
|
||||
hwcap2_SME_F8F32 = 1 << 59
|
||||
hwcap2_SME_SF8FMA = 1 << 60
|
||||
hwcap2_SME_SF8DP4 = 1 << 61
|
||||
hwcap2_SME_SF8DP2 = 1 << 62
|
||||
hwcap2_POE = 1 << 63
|
||||
)
|
||||
|
||||
func detectOS(c *CPUInfo) bool {
|
||||
// For now assuming no hyperthreading is reasonable.
|
||||
c.LogicalCores = runtime.NumCPU()
|
||||
c.PhysicalCores = c.LogicalCores
|
||||
c.ThreadsPerCore = 1
|
||||
if hwcap == 0 {
|
||||
// We did not get values from the runtime.
|
||||
// Try reading /proc/self/auxv
|
||||
|
||||
// From https://github.com/golang/sys
|
||||
const (
|
||||
_AT_HWCAP = 16
|
||||
_AT_HWCAP2 = 26
|
||||
|
||||
uintSize = int(32 << (^uint(0) >> 63))
|
||||
)
|
||||
|
||||
buf, err := ioutil.ReadFile("/proc/self/auxv")
|
||||
if err != nil {
|
||||
// e.g. on android /proc/self/auxv is not accessible, so silently
|
||||
// ignore the error and leave Initialized = false. On some
|
||||
// architectures (e.g. arm64) doinit() implements a fallback
|
||||
// readout and will set Initialized = true again.
|
||||
return false
|
||||
}
|
||||
bo := binary.LittleEndian
|
||||
for len(buf) >= 2*(uintSize/8) {
|
||||
var tag, val uint
|
||||
switch uintSize {
|
||||
case 32:
|
||||
tag = uint(bo.Uint32(buf[0:]))
|
||||
val = uint(bo.Uint32(buf[4:]))
|
||||
buf = buf[8:]
|
||||
case 64:
|
||||
tag = uint(bo.Uint64(buf[0:]))
|
||||
val = uint(bo.Uint64(buf[8:]))
|
||||
buf = buf[16:]
|
||||
}
|
||||
switch tag {
|
||||
case _AT_HWCAP:
|
||||
hwcap = val
|
||||
case _AT_HWCAP2:
|
||||
// Not used
|
||||
}
|
||||
}
|
||||
if hwcap == 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// HWCap was populated by the runtime from the auxiliary vector.
|
||||
// Use HWCap information since reading aarch64 system registers
|
||||
// is not supported in user space on older linux kernels.
|
||||
c.featureSet.setIf(isSet(hwcap, hwcap_AES), AESARM)
|
||||
c.featureSet.setIf(isSet(hwcap, hwcap_ASIMD), ASIMD)
|
||||
c.featureSet.setIf(isSet(hwcap, hwcap_ASIMDDP), ASIMDDP)
|
||||
c.featureSet.setIf(isSet(hwcap, hwcap_ASIMDHP), ASIMDHP)
|
||||
c.featureSet.setIf(isSet(hwcap, hwcap_ASIMDRDM), ASIMDRDM)
|
||||
c.featureSet.setIf(isSet(hwcap, hwcap_CPUID), ARMCPUID)
|
||||
c.featureSet.setIf(isSet(hwcap, hwcap_CRC32), CRC32)
|
||||
c.featureSet.setIf(isSet(hwcap, hwcap_DCPOP), DCPOP)
|
||||
c.featureSet.setIf(isSet(hwcap, hwcap_EVTSTRM), EVTSTRM)
|
||||
c.featureSet.setIf(isSet(hwcap, hwcap_FCMA), FCMA)
|
||||
c.featureSet.setIf(isSet(hwcap, hwcap_ASIMDFHM), FHM)
|
||||
c.featureSet.setIf(isSet(hwcap, hwcap_FP), FP)
|
||||
c.featureSet.setIf(isSet(hwcap, hwcap_FPHP), FPHP)
|
||||
c.featureSet.setIf(isSet(hwcap, hwcap_JSCVT), JSCVT)
|
||||
c.featureSet.setIf(isSet(hwcap, hwcap_LRCPC), LRCPC)
|
||||
c.featureSet.setIf(isSet(hwcap, hwcap_PMULL), PMULL)
|
||||
c.featureSet.setIf(isSet(hwcap, hwcap2_RNG), RNDR)
|
||||
// c.featureSet.setIf(isSet(hwcap, hwcap_), TLB)
|
||||
// c.featureSet.setIf(isSet(hwcap, hwcap_), TS)
|
||||
c.featureSet.setIf(isSet(hwcap, hwcap_SHA1), SHA1)
|
||||
c.featureSet.setIf(isSet(hwcap, hwcap_SHA2), SHA2)
|
||||
c.featureSet.setIf(isSet(hwcap, hwcap_SHA3), SHA3)
|
||||
c.featureSet.setIf(isSet(hwcap, hwcap_SHA512), SHA512)
|
||||
c.featureSet.setIf(isSet(hwcap, hwcap_SM3), SM3)
|
||||
c.featureSet.setIf(isSet(hwcap, hwcap_SM4), SM4)
|
||||
c.featureSet.setIf(isSet(hwcap, hwcap_SVE), SVE)
|
||||
|
||||
// The Samsung S9+ kernel reports support for atomics, but not all cores
|
||||
// actually support them, resulting in SIGILL. See issue #28431.
|
||||
// TODO(elias.naur): Only disable the optimization on bad chipsets on android.
|
||||
c.featureSet.setIf(isSet(hwcap, hwcap_ATOMICS) && runtime.GOOS != "android", ATOMICS)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func isSet(hwc uint, value uint) bool {
|
||||
return hwc&value != 0
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) 2020 Klaus Post, released under MIT License. See LICENSE file.
|
||||
|
||||
//go:build arm64 && !linux && !darwin
|
||||
// +build arm64,!linux,!darwin
|
||||
|
||||
package cpuid
|
||||
|
||||
import "runtime"
|
||||
|
||||
func detectOS(c *CPUInfo) bool {
|
||||
c.PhysicalCores = runtime.NumCPU()
|
||||
// For now assuming 1 thread per core...
|
||||
c.ThreadsPerCore = 1
|
||||
c.LogicalCores = c.PhysicalCores
|
||||
return false
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
// Copyright (c) 2021 Klaus Post, released under MIT License. See LICENSE file.
|
||||
|
||||
//go:build nounsafe
|
||||
// +build nounsafe
|
||||
|
||||
package cpuid
|
||||
|
||||
var hwcap uint
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// Copyright (c) 2021 Klaus Post, released under MIT License. See LICENSE file.
|
||||
|
||||
//go:build !nounsafe
|
||||
// +build !nounsafe
|
||||
|
||||
package cpuid
|
||||
|
||||
import _ "unsafe" // needed for go:linkname
|
||||
|
||||
//go:linkname hwcap internal/cpu.HWCap
|
||||
var hwcap uint
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
go tool dist list | while IFS=/ read os arch; do
|
||||
echo "Checking $os/$arch..."
|
||||
echo " normal"
|
||||
GOARCH=$arch GOOS=$os go build -o /dev/null .
|
||||
echo " noasm"
|
||||
GOARCH=$arch GOOS=$os go build -tags noasm -o /dev/null .
|
||||
echo " appengine"
|
||||
GOARCH=$arch GOOS=$os go build -tags appengine -o /dev/null .
|
||||
echo " noasm,appengine"
|
||||
GOARCH=$arch GOOS=$os go build -tags 'appengine noasm' -o /dev/null .
|
||||
done
|
||||
-64
@@ -1,64 +0,0 @@
|
||||
# AGENTS.md - gotext Project Guidelines
|
||||
|
||||
This document is the **source of truth** for the `gotext` project's architecture, conventions, and development workflows. All contributors — human or AI agent — must adhere to these guidelines.
|
||||
|
||||
Agent-specific configuration files (e.g., `GEMINI.md`, `CLAUDE.md`) should reference this document rather than duplicating its content.
|
||||
|
||||
## Project Overview
|
||||
|
||||
`gotext` is a GNU Gettext implementation for Go (Golang). It provides a thread-safe, hierarchical translation system that supports PO/MO files, plural forms, and context-aware translations.
|
||||
|
||||
## Core Architecture
|
||||
|
||||
The project follows a hierarchical structure for managing translations:
|
||||
|
||||
1. **Global Package API (`gotext.go`)**: Provides convenient, global functions (e.g., `Get`, `GetD`, `GetN`) that rely on a shared `globalConfig`.
|
||||
2. **Locales (`locale.go`)**: Represent a specific language (e.g., "en_US", "es_ES"). A `Locale` container manages multiple `Domains`.
|
||||
3. **Domains (`domain.go`)**: The core translation engine for a specific set of messages. It handles:
|
||||
- Message retrieval (with support for plural forms and `msgctxt`).
|
||||
- Header parsing (including plural-form rules).
|
||||
- Thread-safety via `sync.RWMutex`.
|
||||
4. **Translators (`translator.go`)**: An interface for translation sources.
|
||||
- **PO (`po.go`)**: Parsers for human-readable Gettext Portable Object files.
|
||||
- **MO (`mo.go`)**: Parsers for binary Gettext Machine Object files.
|
||||
5. **Plural Forms (`plurals/`)**: A dedicated package for compiling and evaluating GNU Gettext plural form expressions.
|
||||
|
||||
## CLI Tools
|
||||
|
||||
- **xgotext (`cli/xgotext/`)**: A tool to scan Go source files, extract translatable strings (matching `Get`, `GetD`, etc.), and generate/update PO files.
|
||||
|
||||
## Key Files & Symbols
|
||||
|
||||
- `gotext.go`: `Configure`, `Get`, `GetD`, `GetN`.
|
||||
- `locale.go`: `Locale`, `NewLocale`, `AddDomain`.
|
||||
- `domain.go`: `Domain`, `Get`, `GetN`, `parseHeaders`.
|
||||
- `translator.go`: `Translator` interface.
|
||||
- `plurals/compiler.go`: `Compile` function for plural expressions.
|
||||
|
||||
## Development Workflows
|
||||
|
||||
### Branching & PRs
|
||||
|
||||
- **Feature Branches**: All development MUST happen in dedicated feature branches (e.g., `feature/description-of-change`, `fix/bug-description`, `refactor/topic`).
|
||||
- **Target Branch**: All Pull Requests MUST be made against the `master` branch.
|
||||
- **Review Requirement**: No direct commits to `master` are allowed. All changes must be reviewed via a PR.
|
||||
- **GitHub Integration**: If the `gh` CLI tool is installed and authenticated, it should be used for managing Pull Requests, Issues, and Reviews. Otherwise, these tasks must be performed manually through the GitHub web interface.
|
||||
|
||||
### Coding Standards
|
||||
|
||||
- **Idiomatic Go**: Follow standard Go conventions (`gofmt`, `go vet`).
|
||||
- **Thread Safety**: Ensure all shared state (like in `Domain`) is protected by appropriate synchronization primitives.
|
||||
- **Testing**:
|
||||
- Every new feature or bug fix must include corresponding tests.
|
||||
- Use the `fixtures/` directory for sample PO/MO files.
|
||||
- Run `go test ./...` to ensure no regressions.
|
||||
|
||||
### Pre-Submit Checklist
|
||||
|
||||
Before submitting a Pull Request, verify:
|
||||
|
||||
1. All existing tests pass: `go test ./...`
|
||||
2. Code passes static analysis: `go vet ./...`
|
||||
3. Code is properly formatted: `gofmt`
|
||||
4. New features or bug fixes include corresponding tests.
|
||||
5. Commit messages are clear and descriptive.
|
||||
+45
-2
@@ -1,3 +1,46 @@
|
||||
# CODE OF CONDUCT
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
The canonical text of this project's Code of Conduct lives at [docs/CODE_OF_CONDUCT.md](docs/CODE_OF_CONDUCT.md).
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at leonel.quinteros@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
|
||||
+14
-4
@@ -1,9 +1,19 @@
|
||||
# CONTRIBUTING
|
||||
|
||||
Thank you for considering contributing to `gotext`! All kinds of contributions — features, bug fixes, tests, documentation, or discussions — are welcome.
|
||||
This open source project welcomes everybody that wants to contribute to it by implementing new features, fixing bugs, testing, creating documentation or simply talk about it.
|
||||
|
||||
The canonical contribution guidelines live at [docs/CONTRIBUTING.md](docs/CONTRIBUTING.md).
|
||||
Most contributions will start by creating a new Issue to discuss what is the contribution about and to agree on the steps to move forward.
|
||||
|
||||
Before contributing, please also read [AGENTS.md](AGENTS.md): it is the source of truth for project architecture, conventions, and development workflows (branching, PR process, coding standards, and the pre-submit checklist).
|
||||
## Issues
|
||||
|
||||
All issues reports are welcome. Open a new Issue whenever you want to report a bug, request a change or make a proposal.
|
||||
|
||||
This should be your start point of contribution.
|
||||
|
||||
|
||||
## Pull Requests
|
||||
|
||||
If you have any changes that can be merged, feel free to send a Pull Request.
|
||||
|
||||
Usually, you'd want to create a new Issue to discuss about the change you want to merge and why it's needed or what it solves.
|
||||
|
||||
Start point: open an [Issue](https://github.com/leonelquinteros/gotext/issues) to discuss your idea, then send a Pull Request against `master`.
|
||||
-9
@@ -1,9 +0,0 @@
|
||||
# GEMINI.md - Gemini CLI Instructions for gotext
|
||||
|
||||
> **Important**: Read and follow [`AGENTS.md`](./AGENTS.md) first. It is the source of truth for project architecture, conventions, and development workflows. The instructions below are specific to Gemini CLI and supplement — not replace — those guidelines.
|
||||
|
||||
## Gemini-Specific Instructions
|
||||
|
||||
- Always work in a feature branch. Never commit directly to `master`.
|
||||
- Before submitting, verify changes with existing and new tests (`go test ./...`).
|
||||
- Propose a PR to `master` after confirmation, using the `gh` CLI when available.
|
||||
+206
-99
@@ -9,151 +9,258 @@
|
||||
|
||||
[GNU gettext utilities](https://www.gnu.org/software/gettext) for Go.
|
||||
|
||||
`gotext` is a native Go implementation of the GNU Gettext utilities. It provides a thread-safe, flexible, and powerful way to handle internationalization (i18n) and localization (l10n) in your Go applications.
|
||||
|
||||
---
|
||||
# Features
|
||||
|
||||
**📖 [Read the Full Documentation](https://leonelquinteros.github.io/gotext/)**
|
||||
- Implements GNU gettext support in native Go.
|
||||
- Complete support for [PO files](https://www.gnu.org/software/gettext/manual/html_node/PO-Files.html) including:
|
||||
- Support for multiline strings and headers.
|
||||
- Support for variables inside translation strings using Go's [fmt syntax](https://golang.org/pkg/fmt/).
|
||||
- Support for [pluralization rules](https://www.gnu.org/software/gettext/manual/html_node/Translating-plural-forms.html).
|
||||
- Support for [message contexts](https://www.gnu.org/software/gettext/manual/html_node/Contexts.html).
|
||||
- Support for MO files.
|
||||
- Thread-safe: This package is safe for concurrent use across multiple goroutines.
|
||||
- It works with UTF-8 encoding as it's the default for Go language.
|
||||
- Unit tests available.
|
||||
- Language codes are automatically simplified from the form `en_UK` to `en` if the first isn't available.
|
||||
- Ready to use inside Go templates.
|
||||
- Objects are serializable to []byte to store them in cache.
|
||||
- Support for Go Modules.
|
||||
|
||||
---
|
||||
|
||||
### Table of Contents
|
||||
- [Features](#features)
|
||||
- [Installation](#installation)
|
||||
- [Getting Started](#getting-started)
|
||||
- [CLI Tool (xgotext)](#cli-tool-xgotext)
|
||||
- [Advanced Usage](#advanced-usage)
|
||||
- [Locale Object](#using-locale-object)
|
||||
- [Plural Forms](#use-plural-forms-of-translations)
|
||||
- [Dynamic Variables](#using-dynamic-variables-on-translations)
|
||||
- [Directory Structure](#locales-directories-structure)
|
||||
- [Contributing](#contributing)
|
||||
- [License](#license)
|
||||
# License
|
||||
|
||||
---
|
||||
[MIT license](LICENSE)
|
||||
|
||||
## Features
|
||||
- Native Go implementation of Gettext (the core library has no external dependencies).
|
||||
- Full support for **PO and MO files**.
|
||||
- **Pluralization rules** support via GNU Gettext plural-form expressions.
|
||||
- **Message context** support (`msgctxt`).
|
||||
- Thread-safe for concurrent use.
|
||||
- Works with UTF-8 by default.
|
||||
- Integrated CLI tool (`xgotext`) for string extraction.
|
||||
- Serializable objects for caching.
|
||||
- Plain-string results that plug directly into Go's `text/template` and `html/template` pipelines.
|
||||
|
||||
---
|
||||
# Documentation
|
||||
|
||||
## Installation
|
||||
```bash
|
||||
Refer to package documentation at (https://pkg.go.dev/github.com/leonelquinteros/gotext)
|
||||
|
||||
|
||||
# Installation
|
||||
|
||||
```
|
||||
go get github.com/leonelquinteros/gotext
|
||||
```
|
||||
|
||||
---
|
||||
- There are no requirements or dependencies to use this package.
|
||||
- No need to install GNU gettext utilities (unless specific needs of CLI tools).
|
||||
- No need for environment variables. Some naming conventions are applied but not needed.
|
||||
|
||||
## Getting Started
|
||||
For a quick start, use the package-level API:
|
||||
|
||||
# Usage examples
|
||||
|
||||
## Using package for single language/domain settings
|
||||
|
||||
For quick/simple translations you can use the package level functions directly.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Configure package: locales path, language, and domain
|
||||
gotext.Configure("/path/to/locales", "en_US", "default")
|
||||
// Configure package
|
||||
gotext.Configure("/path/to/locales/root/dir", "en_UK", "domain-name")
|
||||
|
||||
// Simple translation
|
||||
fmt.Println(gotext.Get("Hello, world!"))
|
||||
// Translate text from default domain
|
||||
fmt.Println(gotext.Get("My text on 'domain-name' domain"))
|
||||
|
||||
// Translation with variables
|
||||
fmt.Println(gotext.Get("Hello, %s!", "Gopher"))
|
||||
// Translate text from a different domain without reconfigure
|
||||
fmt.Println(gotext.GetD("domain2", "Another text on a different domain"))
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Using dynamic variables on translations
|
||||
|
||||
All translation strings support dynamic variables to be inserted without translate.
|
||||
Use the fmt.Printf syntax (from Go's "fmt" package) to specify how to print the non-translated variable inside the translation string.
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Configure package
|
||||
gotext.Configure("/path/to/locales/root/dir", "en_UK", "domain-name")
|
||||
|
||||
// Set variables
|
||||
name := "John"
|
||||
|
||||
// Translate text with variables
|
||||
fmt.Println(gotext.Get("Hi, my name is %s", name))
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Using Locale object
|
||||
|
||||
When having multiple languages/domains/libraries at the same time, you can create Locale objects for each variation
|
||||
so you can handle each settings on their own.
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create Locale with library path and language code
|
||||
l := gotext.NewLocale("/path/to/locales/root/dir", "es_UY")
|
||||
|
||||
// Load domain '/path/to/locales/root/dir/es_UY/default.po'
|
||||
l.AddDomain("default")
|
||||
|
||||
// Translate text from default domain
|
||||
fmt.Println(l.Get("Translate this"))
|
||||
|
||||
// Load different domain
|
||||
l.AddDomain("translations")
|
||||
|
||||
// Translate text from domain
|
||||
fmt.Println(l.GetD("translations", "Translate this"))
|
||||
}
|
||||
```
|
||||
|
||||
For more details, see the [Getting Started Guide](docs/GETTING_STARTED.md).
|
||||
This is also helpful for using inside templates (from the "text/template" package), where you can pass the Locale object to the template.
|
||||
If you set the Locale object as "Loc" in the template, then the template code would look like:
|
||||
|
||||
---
|
||||
|
||||
## CLI Tool (xgotext)
|
||||
`gotext` includes a command-line tool to extract translatable strings from your Go source code.
|
||||
|
||||
**Install xgotext:**
|
||||
```bash
|
||||
go install github.com/leonelquinteros/gotext/cli/xgotext@latest
|
||||
```
|
||||
{{ .Loc.Get "Translate this" }}
|
||||
```
|
||||
|
||||
**Extract strings:**
|
||||
```bash
|
||||
xgotext -in . -out locales/templates
|
||||
```
|
||||
The command generates `.pot` templates under `locales/templates`; merge those templates into language-specific `.po` files.
|
||||
|
||||
See the [xgotext Documentation](docs/xgotext.md) for full usage details.
|
||||
## Using the Po object to handle .po files and PO-formatted strings
|
||||
|
||||
---
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Using Locale object
|
||||
For managing multiple languages or domains independently:
|
||||
For when you need to work with PO files and strings,
|
||||
you can directly use the Po object to parse it and access the translations in there in the same way.
|
||||
|
||||
```go
|
||||
l := gotext.NewLocale("/path/to/locales", "es_UY")
|
||||
l.AddDomain("default")
|
||||
fmt.Println(l.Get("Translate this"))
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Set PO content
|
||||
str := `
|
||||
msgid "Translate this"
|
||||
msgstr "Translated text"
|
||||
|
||||
msgid "Another string"
|
||||
msgstr ""
|
||||
|
||||
msgid "One with var: %s"
|
||||
msgstr "This one sets the var: %s"
|
||||
`
|
||||
|
||||
// Create Po object
|
||||
po := gotext.NewPo()
|
||||
po.Parse(str)
|
||||
|
||||
fmt.Println(po.Get("Translate this"))
|
||||
}
|
||||
```
|
||||
|
||||
### Use plural forms of translations
|
||||
`gotext` handles complex pluralization rules defined in PO headers:
|
||||
|
||||
## Use plural forms of translations
|
||||
|
||||
PO format supports defining one or more plural forms for the same translation.
|
||||
Relying on the PO file headers, a Plural-Forms formula can be set on the translation file
|
||||
as defined in (https://www.gnu.org/savannah-checkouts/gnu/gettext/manual/html_node/Plural-forms.html)
|
||||
|
||||
```go
|
||||
// GetN(singular, plural, quantity, args...)
|
||||
fmt.Println(gotext.GetN("I have one apple.", "I have %d apples.", 5, 5))
|
||||
```
|
||||
See the [Plural Forms Guide](docs/PLURALS.md) for more examples.
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
)
|
||||
|
||||
### Using dynamic variables on translations
|
||||
Supports standard `fmt` package syntax:
|
||||
```go
|
||||
name := "John"
|
||||
fmt.Println(gotext.Get("Hi, my name is %s", name))
|
||||
func main() {
|
||||
// Set PO content
|
||||
str := `
|
||||
msgid ""
|
||||
msgstr ""
|
||||
|
||||
# Header below
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
msgid "Translate this"
|
||||
msgstr "Translated text"
|
||||
|
||||
msgid "Another string"
|
||||
msgstr ""
|
||||
|
||||
msgid "One with var: %s"
|
||||
msgid_plural "Several with vars: %s"
|
||||
msgstr[0] "This one is the singular: %s"
|
||||
msgstr[1] "This one is the plural: %s"
|
||||
`
|
||||
|
||||
// Create Po object
|
||||
po := new(gotext.Po)
|
||||
po.Parse(str)
|
||||
|
||||
fmt.Println(po.GetN("One with var: %s", "Several with vars: %s", 54, v))
|
||||
// "This one is the plural: Variable"
|
||||
}
|
||||
```
|
||||
|
||||
### More advanced scenarios
|
||||
- **Embedded translation files** (`embed.FS`) and named `%(name)s` formatting: see the [Advanced Usage Guide](docs/ADVANCED.md).
|
||||
- **Languages fallback**: pass multiple languages to `Configure` as a colon-separated list (`gotext.Configure("/path/to/locales", "es_UY:es:en_US", "default")`).
|
||||
- **Serialization/caching** of parsed locales: `Locale.MarshalBinary` / `UnmarshalBinary`.
|
||||
|
||||
---
|
||||
# Locales directories structure
|
||||
|
||||
The package will assume a directories structure starting with a base path that will be provided to the package configuration
|
||||
or to object constructors depending on the use, but either will use the same convention to lookup inside the base path.
|
||||
|
||||
Inside the base directory where will be the language directories named using the language and country 2-letter codes (en_US, es_AR, ...).
|
||||
All package functions can lookup after the simplified version for each language in case the full code isn't present but the more general language code exists.
|
||||
So if the language set is `en_UK`, but there is no directory named after that code and there is a directory named `en`,
|
||||
all package functions will be able to resolve this generalization and provide translations for the more general library.
|
||||
|
||||
The language codes are assumed to be [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) codes (2-letter codes).
|
||||
That said, most functions will work with any coding standard as long the directory name matches the language code set on the configuration.
|
||||
|
||||
Then, there can be a `LC_MESSAGES` containing all PO files or the PO files themselves.
|
||||
A library directory structure can look like:
|
||||
|
||||
## Locales directories structure
|
||||
The package expects a standard Gettext directory structure:
|
||||
```
|
||||
/path/to/locales
|
||||
/en_US
|
||||
/LC_MESSAGES
|
||||
default.po
|
||||
/es_ES
|
||||
/LC_MESSAGES
|
||||
default.po
|
||||
/path/to/locales/en_US
|
||||
/path/to/locales/en_US/LC_MESSAGES
|
||||
/path/to/locales/en_US/LC_MESSAGES/default.po
|
||||
/path/to/locales/en_US/LC_MESSAGES/extras.po
|
||||
/path/to/locales/en_UK
|
||||
/path/to/locales/en_UK/LC_MESSAGES
|
||||
/path/to/locales/en_UK/LC_MESSAGES/default.po
|
||||
/path/to/locales/en_UK/LC_MESSAGES/extras.po
|
||||
/path/to/locales/en_AU
|
||||
/path/to/locales/en_AU/LC_MESSAGES
|
||||
/path/to/locales/en_AU/LC_MESSAGES/default.po
|
||||
/path/to/locales/en_AU/LC_MESSAGES/extras.po
|
||||
/path/to/locales/es
|
||||
/path/to/locales/es/default.po
|
||||
/path/to/locales/es/extras.po
|
||||
/path/to/locales/es_ES
|
||||
/path/to/locales/es_ES/default.po
|
||||
/path/to/locales/es_ES/extras.po
|
||||
/path/to/locales/fr
|
||||
/path/to/locales/fr/default.po
|
||||
/path/to/locales/fr/extras.po
|
||||
```
|
||||
Translation files can live either inside an `LC_MESSAGES` subdirectory or directly under the language code (`/es_ES/default.po`); both layouts are supported.
|
||||
|
||||
It supports automatic language simplification (e.g., falling back from `en_UK` to `en`).
|
||||
And so on...
|
||||
|
||||
---
|
||||
|
||||
## Contributing
|
||||
We welcome contributions of all kinds!
|
||||
- [Contributing Guidelines](CONTRIBUTING.md)
|
||||
- [Code of Conduct](CODE_OF_CONDUCT.md)
|
||||
# Contribute
|
||||
|
||||
---
|
||||
- Please, contribute.
|
||||
- Use the package on your projects.
|
||||
- Report issues on Github.
|
||||
- Send pull requests for bugfixes and improvements.
|
||||
- Send proposals on Github issues.
|
||||
|
||||
## License
|
||||
`gotext` is released under the [MIT License](LICENSE).
|
||||
|
||||
+157
-316
@@ -4,7 +4,7 @@ import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"maps"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -64,6 +64,9 @@ func (m HeaderMap) Del(key string) {
|
||||
|
||||
// Get value for key from HeaderMap
|
||||
func (m HeaderMap) Get(key string) string {
|
||||
if m == nil {
|
||||
return ""
|
||||
}
|
||||
v := m[key]
|
||||
if len(v) == 0 {
|
||||
return ""
|
||||
@@ -78,6 +81,9 @@ func (m HeaderMap) Set(key, value string) {
|
||||
|
||||
// Values returns all values for a given key from HeaderMap
|
||||
func (m HeaderMap) Values(key string) []string {
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
return m[key]
|
||||
}
|
||||
|
||||
@@ -94,111 +100,29 @@ func NewDomain() *Domain {
|
||||
return domain
|
||||
}
|
||||
|
||||
func cloneRefs(refs []string) []string {
|
||||
if refs == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
owned := make([]string, len(refs))
|
||||
copy(owned, refs)
|
||||
return owned
|
||||
}
|
||||
|
||||
func cloneHeaderMap(headers HeaderMap) HeaderMap {
|
||||
if headers == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
owned := make(HeaderMap, len(headers))
|
||||
for key, values := range headers {
|
||||
owned[key] = cloneRefs(values)
|
||||
}
|
||||
return owned
|
||||
}
|
||||
|
||||
func cloneTranslation(trans *Translation) *Translation {
|
||||
if trans == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
owned := NewTranslation()
|
||||
owned.ID = trans.ID
|
||||
owned.PluralID = trans.PluralID
|
||||
owned.dirty = trans.dirty
|
||||
owned.Refs = cloneRefs(trans.Refs)
|
||||
|
||||
maps.Copy(owned.Trs, trans.Trs)
|
||||
|
||||
return owned
|
||||
}
|
||||
|
||||
func cloneTranslations(translations map[string]*Translation) map[string]*Translation {
|
||||
if translations == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
owned := make(map[string]*Translation, len(translations))
|
||||
for id, trans := range translations {
|
||||
owned[id] = cloneTranslation(trans)
|
||||
}
|
||||
return owned
|
||||
}
|
||||
|
||||
func cloneContextTranslations(contexts map[string]map[string]*Translation) map[string]map[string]*Translation {
|
||||
if contexts == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
owned := make(map[string]map[string]*Translation, len(contexts))
|
||||
for context, translations := range contexts {
|
||||
owned[context] = cloneTranslations(translations)
|
||||
}
|
||||
return owned
|
||||
}
|
||||
|
||||
func (do *Domain) hasTranslation(id string) bool {
|
||||
do.trMutex.RLock()
|
||||
defer do.trMutex.RUnlock()
|
||||
|
||||
return do.translations[id] != nil
|
||||
}
|
||||
|
||||
func (do *Domain) hasContextTranslation(id, context string) bool {
|
||||
do.trMutex.RLock()
|
||||
defer do.trMutex.RUnlock()
|
||||
|
||||
return do.contextTranslations[context][id] != nil
|
||||
}
|
||||
|
||||
// SetPluralResolver sets a custom plural resolver function
|
||||
func (do *Domain) SetPluralResolver(f func(int) int) {
|
||||
do.pluralMutex.Lock()
|
||||
defer do.pluralMutex.Unlock()
|
||||
|
||||
do.customPluralResolver = f
|
||||
}
|
||||
|
||||
func (do *Domain) pluralForm(n int) int {
|
||||
// Snapshot plural state before calling an expression or custom resolver.
|
||||
// do we really need locking here? not sure how this plurals.Expression works, so sticking with it for now
|
||||
do.pluralMutex.RLock()
|
||||
pluralforms := do.pluralforms
|
||||
customPluralResolver := do.customPluralResolver
|
||||
do.pluralMutex.RUnlock()
|
||||
|
||||
if pluralforms != nil {
|
||||
return pluralforms.Eval(uint32(n))
|
||||
}
|
||||
defer do.pluralMutex.RUnlock()
|
||||
|
||||
// Failure fallback
|
||||
if customPluralResolver != nil {
|
||||
return customPluralResolver(n)
|
||||
}
|
||||
if do.pluralforms == nil {
|
||||
if do.customPluralResolver != nil {
|
||||
return do.customPluralResolver(n)
|
||||
}
|
||||
|
||||
/* Use the Germanic plural rule. */
|
||||
if n == 1 {
|
||||
return 0
|
||||
/* Use the Germanic plural rule. */
|
||||
if n == 1 {
|
||||
return 0
|
||||
}
|
||||
return 1
|
||||
}
|
||||
return 1
|
||||
return do.pluralforms.Eval(uint32(n))
|
||||
}
|
||||
|
||||
// parseHeaders retrieves data from previously parsed headers. it's called by both Mo and Po when parsing
|
||||
@@ -212,16 +136,18 @@ func (do *Domain) parseHeaders() {
|
||||
languageKey := "Language"
|
||||
pluralFormsKey := "Plural-Forms"
|
||||
|
||||
for line := range strings.SplitSeq(raw, "\n") {
|
||||
rawLines := strings.Split(raw, "\n")
|
||||
for _, line := range rawLines {
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
key, value, ok := strings.Cut(line, ":")
|
||||
if !ok {
|
||||
colonIdx := strings.Index(line, ":")
|
||||
if colonIdx < 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
key := line[:colonIdx]
|
||||
lowerKey := strings.ToLower(key)
|
||||
if lowerKey == strings.ToLower(languageKey) {
|
||||
languageKey = key
|
||||
@@ -229,7 +155,7 @@ func (do *Domain) parseHeaders() {
|
||||
pluralFormsKey = key
|
||||
}
|
||||
|
||||
value = strings.TrimSpace(value)
|
||||
value := strings.TrimSpace(line[colonIdx+1:])
|
||||
do.Headers.Add(key, value)
|
||||
}
|
||||
|
||||
@@ -242,18 +168,22 @@ func (do *Domain) parseHeaders() {
|
||||
return
|
||||
}
|
||||
|
||||
for i := range strings.SplitSeq(do.PluralForms, ";") {
|
||||
key, value, ok := strings.Cut(i, "=")
|
||||
if !ok {
|
||||
// Split plural form header value
|
||||
pfs := strings.Split(do.PluralForms, ";")
|
||||
|
||||
// Parse values
|
||||
for _, i := range pfs {
|
||||
vs := strings.SplitN(i, "=", 2)
|
||||
if len(vs) != 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
switch strings.TrimSpace(key) {
|
||||
switch strings.TrimSpace(vs[0]) {
|
||||
case "nplurals":
|
||||
do.nplurals, _ = strconv.Atoi(value)
|
||||
do.nplurals, _ = strconv.Atoi(vs[1])
|
||||
|
||||
case "plural":
|
||||
do.plural = value
|
||||
do.plural = vs[1]
|
||||
|
||||
if expr, err := plurals.Compile(do.plural); err == nil {
|
||||
do.pluralforms = expr
|
||||
@@ -291,19 +221,17 @@ func (do *Domain) DropStaleTranslations() {
|
||||
|
||||
// SetRefs set source references for a given translation
|
||||
func (do *Domain) SetRefs(str string, refs []string) {
|
||||
ownedRefs := cloneRefs(refs)
|
||||
|
||||
do.trMutex.Lock()
|
||||
do.pluralMutex.Lock()
|
||||
defer do.trMutex.Unlock()
|
||||
defer do.pluralMutex.Unlock()
|
||||
|
||||
if trans, ok := do.translations[str]; ok && trans != nil {
|
||||
trans.SetRefs(ownedRefs)
|
||||
if trans, ok := do.translations[str]; ok {
|
||||
trans.Refs = refs
|
||||
} else {
|
||||
trans := NewTranslation()
|
||||
trans = NewTranslation()
|
||||
trans.ID = str
|
||||
trans.SetRefs(ownedRefs)
|
||||
trans.SetRefs(refs)
|
||||
do.translations[str] = trans
|
||||
}
|
||||
}
|
||||
@@ -315,8 +243,8 @@ func (do *Domain) GetRefs(str string) []string {
|
||||
defer do.trMutex.RUnlock()
|
||||
|
||||
if do.translations != nil {
|
||||
if trans, ok := do.translations[str]; ok && trans != nil {
|
||||
return cloneRefs(trans.Refs)
|
||||
if trans, ok := do.translations[str]; ok {
|
||||
return trans.Refs
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -340,7 +268,7 @@ func (do *Domain) Set(id, str string) {
|
||||
}
|
||||
|
||||
// Get retrieves the Translation for the given string.
|
||||
func (do *Domain) Get(str string, vars ...any) string {
|
||||
func (do *Domain) Get(str string, vars ...interface{}) string {
|
||||
// Sync read
|
||||
do.trMutex.RLock()
|
||||
defer do.trMutex.RUnlock()
|
||||
@@ -356,8 +284,8 @@ func (do *Domain) Get(str string, vars ...any) string {
|
||||
}
|
||||
|
||||
// Append retrieves the Translation for the given string.
|
||||
// Supports optional parameters (vars... any) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (do *Domain) Append(b []byte, str string, vars ...any) []byte {
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (do *Domain) Append(b []byte, str string, vars ...interface{}) []byte {
|
||||
// Sync read
|
||||
do.trMutex.RLock()
|
||||
defer do.trMutex.RUnlock()
|
||||
@@ -394,48 +322,40 @@ func (do *Domain) SetN(id, plural string, n int, str string) {
|
||||
}
|
||||
|
||||
// GetN retrieves the (N)th plural form of Translation for the given string.
|
||||
// Supports optional parameters (vars... any) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (do *Domain) GetN(str, plural string, n int, vars ...any) string {
|
||||
// Get plural form before acquiring the translation lock so a custom
|
||||
// resolver is never called while a project lock is held.
|
||||
pluralForm := do.pluralForm(n)
|
||||
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (do *Domain) GetN(str, plural string, n int, vars ...interface{}) string {
|
||||
// Sync read
|
||||
do.trMutex.RLock()
|
||||
defer do.trMutex.RUnlock()
|
||||
|
||||
if do.translations != nil {
|
||||
if _, ok := do.translations[str]; ok {
|
||||
return FormatString(do.translations[str].GetN(pluralForm), vars...)
|
||||
return FormatString(do.translations[str].GetN(do.pluralForm(n)), vars...)
|
||||
}
|
||||
}
|
||||
|
||||
// Parse plural forms to distinguish between plural and singular
|
||||
if pluralForm == 0 {
|
||||
if do.pluralForm(n) == 0 {
|
||||
return FormatString(str, vars...)
|
||||
}
|
||||
return FormatString(plural, vars...)
|
||||
}
|
||||
|
||||
// AppendN adds the (N)th plural form of Translation for the given string.
|
||||
// Supports optional parameters (vars... any) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (do *Domain) AppendN(b []byte, str, plural string, n int, vars ...any) []byte {
|
||||
// Get plural form before acquiring the translation lock so a custom
|
||||
// resolver is never called while a project lock is held.
|
||||
pluralForm := do.pluralForm(n)
|
||||
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (do *Domain) AppendN(b []byte, str, plural string, n int, vars ...interface{}) []byte {
|
||||
// Sync read
|
||||
do.trMutex.RLock()
|
||||
defer do.trMutex.RUnlock()
|
||||
|
||||
if do.translations != nil {
|
||||
if _, ok := do.translations[str]; ok {
|
||||
return Appendf(b, do.translations[str].GetN(pluralForm), vars...)
|
||||
return Appendf(b, do.translations[str].GetN(do.pluralForm(n)), vars...)
|
||||
}
|
||||
}
|
||||
|
||||
// Parse plural forms to distinguish between plural and singular
|
||||
if pluralForm == 0 {
|
||||
if do.pluralForm(n) == 0 {
|
||||
return Appendf(b, str, vars...)
|
||||
}
|
||||
return Appendf(b, plural, vars...)
|
||||
@@ -468,8 +388,8 @@ func (do *Domain) SetC(id, ctx, str string) {
|
||||
}
|
||||
|
||||
// GetC retrieves the corresponding Translation for a given string in the given context.
|
||||
// Supports optional parameters (vars... any) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (do *Domain) GetC(str, ctx string, vars ...any) string {
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (do *Domain) GetC(str, ctx string, vars ...interface{}) string {
|
||||
do.trMutex.RLock()
|
||||
defer do.trMutex.RUnlock()
|
||||
|
||||
@@ -488,8 +408,8 @@ func (do *Domain) GetC(str, ctx string, vars ...any) string {
|
||||
}
|
||||
|
||||
// AppendC retrieves the corresponding Translation for a given string in the given context.
|
||||
// Supports optional parameters (vars... any) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (do *Domain) AppendC(b []byte, str, ctx string, vars ...any) []byte {
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (do *Domain) AppendC(b []byte, str, ctx string, vars ...interface{}) []byte {
|
||||
do.trMutex.RLock()
|
||||
defer do.trMutex.RUnlock()
|
||||
|
||||
@@ -523,14 +443,12 @@ func (do *Domain) SetNC(id, plural, ctx string, n int, str string) {
|
||||
} else {
|
||||
trans = NewTranslation()
|
||||
trans.ID = id
|
||||
trans.PluralID = plural
|
||||
trans.SetN(pluralForm, str)
|
||||
context[id] = trans
|
||||
}
|
||||
} else {
|
||||
trans := NewTranslation()
|
||||
trans.ID = id
|
||||
trans.PluralID = plural
|
||||
trans.SetN(pluralForm, str)
|
||||
do.contextTranslations[ctx] = map[string]*Translation{
|
||||
id: trans,
|
||||
@@ -539,22 +457,19 @@ func (do *Domain) SetNC(id, plural, ctx string, n int, str string) {
|
||||
}
|
||||
|
||||
// GetNC retrieves the (N)th plural form of Translation for the given string in the given context.
|
||||
// Supports optional parameters (vars... any) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (do *Domain) GetNC(str, plural string, n int, ctx string, vars ...any) string {
|
||||
if do.hasContextTranslation(str, ctx) {
|
||||
// Get plural form before acquiring the translation lock so a custom
|
||||
// resolver is never called while a project lock is held.
|
||||
pluralForm := do.pluralForm(n)
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (do *Domain) GetNC(str, plural string, n int, ctx string, vars ...interface{}) string {
|
||||
do.trMutex.RLock()
|
||||
defer do.trMutex.RUnlock()
|
||||
|
||||
do.trMutex.RLock()
|
||||
if translations, ok := do.contextTranslations[ctx]; ok && translations != nil {
|
||||
if trans, ok := translations[str]; ok && trans != nil {
|
||||
translated := trans.GetN(pluralForm)
|
||||
do.trMutex.RUnlock()
|
||||
return FormatString(translated, vars...)
|
||||
if do.contextTranslations != nil {
|
||||
if _, ok := do.contextTranslations[ctx]; ok {
|
||||
if do.contextTranslations[ctx] != nil {
|
||||
if _, ok := do.contextTranslations[ctx][str]; ok {
|
||||
return FormatString(do.contextTranslations[ctx][str].GetN(do.pluralForm(n)), vars...)
|
||||
}
|
||||
}
|
||||
}
|
||||
do.trMutex.RUnlock()
|
||||
}
|
||||
|
||||
if n == 1 {
|
||||
@@ -564,22 +479,19 @@ func (do *Domain) GetNC(str, plural string, n int, ctx string, vars ...any) stri
|
||||
}
|
||||
|
||||
// AppendNC retrieves the (N)th plural form of Translation for the given string in the given context.
|
||||
// Supports optional parameters (vars... any) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (do *Domain) AppendNC(b []byte, str, plural string, n int, ctx string, vars ...any) []byte {
|
||||
if do.hasContextTranslation(str, ctx) {
|
||||
// Get plural form before acquiring the translation lock so a custom
|
||||
// resolver is never called while a project lock is held.
|
||||
pluralForm := do.pluralForm(n)
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (do *Domain) AppendNC(b []byte, str, plural string, n int, ctx string, vars ...interface{}) []byte {
|
||||
do.trMutex.RLock()
|
||||
defer do.trMutex.RUnlock()
|
||||
|
||||
do.trMutex.RLock()
|
||||
if translations, ok := do.contextTranslations[ctx]; ok && translations != nil {
|
||||
if trans, ok := translations[str]; ok && trans != nil {
|
||||
translated := trans.GetN(pluralForm)
|
||||
do.trMutex.RUnlock()
|
||||
return Appendf(b, translated, vars...)
|
||||
if do.contextTranslations != nil {
|
||||
if _, ok := do.contextTranslations[ctx]; ok {
|
||||
if do.contextTranslations[ctx] != nil {
|
||||
if _, ok := do.contextTranslations[ctx][str]; ok {
|
||||
return Appendf(b, do.contextTranslations[ctx][str].GetN(do.pluralForm(n)), vars...)
|
||||
}
|
||||
}
|
||||
}
|
||||
do.trMutex.RUnlock()
|
||||
}
|
||||
|
||||
if n == 1 {
|
||||
@@ -595,14 +507,6 @@ func (do *Domain) IsTranslated(str string) bool {
|
||||
|
||||
// IsTranslatedN reports whether a plural string is translated
|
||||
func (do *Domain) IsTranslatedN(str string, n int) bool {
|
||||
if !do.hasTranslation(str) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Get plural form before acquiring the translation lock so a custom
|
||||
// resolver is never called while a project lock is held.
|
||||
pluralForm := do.pluralForm(n)
|
||||
|
||||
do.trMutex.RLock()
|
||||
defer do.trMutex.RUnlock()
|
||||
|
||||
@@ -610,10 +514,10 @@ func (do *Domain) IsTranslatedN(str string, n int) bool {
|
||||
return false
|
||||
}
|
||||
tr, ok := do.translations[str]
|
||||
if !ok || tr == nil {
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return tr.IsTranslatedN(pluralForm)
|
||||
return tr.IsTranslatedN(do.pluralForm(n))
|
||||
}
|
||||
|
||||
// IsTranslatedC reports whether a context string is translated
|
||||
@@ -623,14 +527,6 @@ func (do *Domain) IsTranslatedC(str, ctx string) bool {
|
||||
|
||||
// IsTranslatedNC reports whether a plural context string is translated
|
||||
func (do *Domain) IsTranslatedNC(str string, n int, ctx string) bool {
|
||||
if !do.hasContextTranslation(str, ctx) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Get plural form before acquiring the translation lock so a custom
|
||||
// resolver is never called while a project lock is held.
|
||||
pluralForm := do.pluralForm(n)
|
||||
|
||||
do.trMutex.RLock()
|
||||
defer do.trMutex.RUnlock()
|
||||
|
||||
@@ -638,24 +534,36 @@ func (do *Domain) IsTranslatedNC(str string, n int, ctx string) bool {
|
||||
return false
|
||||
}
|
||||
translations, ok := do.contextTranslations[ctx]
|
||||
if !ok || translations == nil {
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
tr, ok := translations[str]
|
||||
if !ok || tr == nil {
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return tr.IsTranslatedN(pluralForm)
|
||||
return tr.IsTranslatedN(do.pluralForm(n))
|
||||
}
|
||||
|
||||
// GetTranslations returns a copy of every translation in the domain. It does not support contexts.
|
||||
func (do *Domain) GetTranslations() map[string]*Translation {
|
||||
all := make(map[string]*Translation, len(do.translations))
|
||||
|
||||
do.trMutex.RLock()
|
||||
defer do.trMutex.RUnlock()
|
||||
|
||||
all := make(map[string]*Translation, len(do.translations))
|
||||
for msgID, trans := range do.translations {
|
||||
all[msgID] = cloneTranslation(trans)
|
||||
newTrans := NewTranslation()
|
||||
newTrans.ID = trans.ID
|
||||
newTrans.PluralID = trans.PluralID
|
||||
newTrans.dirty = trans.dirty
|
||||
if len(trans.Refs) > 0 {
|
||||
newTrans.Refs = make([]string, len(trans.Refs))
|
||||
copy(newTrans.Refs, trans.Refs)
|
||||
}
|
||||
for k, v := range trans.Trs {
|
||||
newTrans.Trs[k] = v
|
||||
}
|
||||
all[msgID] = newTrans
|
||||
}
|
||||
|
||||
return all
|
||||
@@ -663,18 +571,32 @@ func (do *Domain) GetTranslations() map[string]*Translation {
|
||||
|
||||
// GetCtxTranslations returns a copy of every translation in the domain with context
|
||||
func (do *Domain) GetCtxTranslations() map[string]map[string]*Translation {
|
||||
all := make(map[string]map[string]*Translation, len(do.contextTranslations))
|
||||
|
||||
do.trMutex.RLock()
|
||||
defer do.trMutex.RUnlock()
|
||||
|
||||
all := make(map[string]map[string]*Translation, len(do.contextTranslations))
|
||||
for ctx, translations := range do.contextTranslations {
|
||||
for msgID, trans := range translations {
|
||||
newTrans := NewTranslation()
|
||||
newTrans.ID = trans.ID
|
||||
newTrans.PluralID = trans.PluralID
|
||||
newTrans.dirty = trans.dirty
|
||||
if len(trans.Refs) > 0 {
|
||||
newTrans.Refs = make([]string, len(trans.Refs))
|
||||
copy(newTrans.Refs, trans.Refs)
|
||||
}
|
||||
for k, v := range trans.Trs {
|
||||
newTrans.Trs[k] = v
|
||||
}
|
||||
|
||||
if all[ctx] == nil {
|
||||
all[ctx] = make(map[string]*Translation)
|
||||
}
|
||||
|
||||
all[ctx][msgID] = cloneTranslation(trans)
|
||||
all[ctx][msgID] = newTrans
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return all
|
||||
@@ -682,11 +604,10 @@ func (do *Domain) GetCtxTranslations() map[string]map[string]*Translation {
|
||||
|
||||
// SourceReference is a struct to hold source reference information
|
||||
type SourceReference struct {
|
||||
path string
|
||||
line int
|
||||
context string
|
||||
hasContext bool
|
||||
trans *Translation
|
||||
path string
|
||||
line int
|
||||
context string
|
||||
trans *Translation
|
||||
}
|
||||
|
||||
func extractPathAndLine(ref string) (string, int) {
|
||||
@@ -706,11 +627,6 @@ func extractPathAndLine(ref string) (string, int) {
|
||||
// MarshalText implements encoding.TextMarshaler interface
|
||||
// Assists round-trip of POT/PO content
|
||||
func (do *Domain) MarshalText() ([]byte, error) {
|
||||
do.trMutex.RLock()
|
||||
do.pluralMutex.RLock()
|
||||
defer do.trMutex.RUnlock()
|
||||
defer do.pluralMutex.RUnlock()
|
||||
|
||||
var buf bytes.Buffer
|
||||
if len(do.headerComments) > 0 {
|
||||
buf.WriteString(strings.Join(do.headerComments, "\n"))
|
||||
@@ -765,29 +681,31 @@ func (do *Domain) MarshalText() ([]byte, error) {
|
||||
v := do.Headers[k]
|
||||
|
||||
for _, value := range v {
|
||||
encoded := EscapeSpecialCharacters(k + ": " + value + "\n")
|
||||
buf.WriteString("\n\"" + encoded + "\"")
|
||||
buf.WriteString("\n\"" + k + ": " + value + "\\n\"")
|
||||
}
|
||||
}
|
||||
|
||||
// Just as with headers, output translations in consistent order (to minimise diffs between round-trips), with (first) source reference taking priority, followed by context and finally ID
|
||||
references := make([]SourceReference, 0)
|
||||
for name, ctx := range do.contextTranslations {
|
||||
for _, trans := range ctx {
|
||||
for id, trans := range ctx {
|
||||
if id == "" {
|
||||
continue
|
||||
}
|
||||
if len(trans.Refs) > 0 {
|
||||
path, line := extractPathAndLine(trans.Refs[0])
|
||||
references = append(references, SourceReference{
|
||||
path: path,
|
||||
line: line,
|
||||
context: name,
|
||||
hasContext: true,
|
||||
trans: trans,
|
||||
path,
|
||||
line,
|
||||
name,
|
||||
trans,
|
||||
})
|
||||
} else {
|
||||
references = append(references, SourceReference{
|
||||
context: name,
|
||||
hasContext: true,
|
||||
trans: trans,
|
||||
"",
|
||||
0,
|
||||
name,
|
||||
trans,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -801,13 +719,17 @@ func (do *Domain) MarshalText() ([]byte, error) {
|
||||
if len(trans.Refs) > 0 {
|
||||
path, line := extractPathAndLine(trans.Refs[0])
|
||||
references = append(references, SourceReference{
|
||||
path: path,
|
||||
line: line,
|
||||
trans: trans,
|
||||
path,
|
||||
line,
|
||||
"",
|
||||
trans,
|
||||
})
|
||||
} else {
|
||||
references = append(references, SourceReference{
|
||||
trans: trans,
|
||||
"",
|
||||
0,
|
||||
"",
|
||||
trans,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -832,9 +754,6 @@ func (do *Domain) MarshalText() ([]byte, error) {
|
||||
if references[i].context > references[j].context {
|
||||
return false
|
||||
}
|
||||
if references[i].hasContext != references[j].hasContext {
|
||||
return !references[i].hasContext
|
||||
}
|
||||
return references[i].trans.ID < references[j].trans.ID
|
||||
})
|
||||
|
||||
@@ -846,7 +765,7 @@ func (do *Domain) MarshalText() ([]byte, error) {
|
||||
buf.WriteByte(byte('\n'))
|
||||
}
|
||||
|
||||
if !ref.hasContext {
|
||||
if ref.context == "" {
|
||||
buf.WriteString("\nmsgid \"" + EscapeSpecialCharacters(trans.ID) + "\"")
|
||||
} else {
|
||||
buf.WriteString("\nmsgctxt \"" + EscapeSpecialCharacters(ref.context) + "\"\nmsgid \"" + EscapeSpecialCharacters(trans.ID) + "\"")
|
||||
@@ -855,16 +774,9 @@ func (do *Domain) MarshalText() ([]byte, error) {
|
||||
if trans.PluralID == "" {
|
||||
buf.WriteString("\nmsgstr \"" + EscapeSpecialCharacters(trans.Trs[0]) + "\"")
|
||||
} else {
|
||||
buf.WriteString("\nmsgid_plural \"" + EscapeSpecialCharacters(trans.PluralID) + "\"")
|
||||
|
||||
pluralIndexes := make([]int, 0, len(trans.Trs))
|
||||
for i := range trans.Trs {
|
||||
pluralIndexes = append(pluralIndexes, i)
|
||||
}
|
||||
sort.Ints(pluralIndexes)
|
||||
|
||||
for _, i := range pluralIndexes {
|
||||
buf.WriteString("\nmsgstr[" + strconv.Itoa(i) + "] \"" + EscapeSpecialCharacters(trans.Trs[i]) + "\"")
|
||||
buf.WriteString("\nmsgid_plural \"" + trans.PluralID + "\"")
|
||||
for i, tr := range trans.Trs {
|
||||
buf.WriteString("\nmsgstr[" + EscapeSpecialCharacters(strconv.Itoa(i)) + "] \"" + tr + "\"")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -874,23 +786,8 @@ func (do *Domain) MarshalText() ([]byte, error) {
|
||||
|
||||
// EscapeSpecialCharacters escapes special characters in a string
|
||||
func EscapeSpecialCharacters(s string) string {
|
||||
var escaped strings.Builder
|
||||
escaped.Grow(len(s))
|
||||
s = regexp.MustCompile(`([^\\])(")`).ReplaceAllString(s, "$1\\\"") // Escape non-escaped double quotation marks
|
||||
|
||||
for i := range len(s) {
|
||||
switch s[i] {
|
||||
case '\\':
|
||||
escaped.WriteByte('\\')
|
||||
escaped.WriteByte('\\')
|
||||
case '"':
|
||||
escaped.WriteByte('\\')
|
||||
escaped.WriteByte('"')
|
||||
default:
|
||||
escaped.WriteByte(s[i])
|
||||
}
|
||||
}
|
||||
|
||||
s = escaped.String()
|
||||
if strings.Count(s, "\n") == 0 {
|
||||
return s
|
||||
}
|
||||
@@ -924,26 +821,16 @@ func EscapeSpecialCharacters(s string) string {
|
||||
return strings.Join(data, "\n")
|
||||
}
|
||||
|
||||
func (do *Domain) translatorEncodingSnapshot() *TranslatorEncoding {
|
||||
do.trMutex.RLock()
|
||||
do.pluralMutex.RLock()
|
||||
defer do.trMutex.RUnlock()
|
||||
defer do.pluralMutex.RUnlock()
|
||||
|
||||
return &TranslatorEncoding{
|
||||
Headers: cloneHeaderMap(do.Headers),
|
||||
Language: do.Language,
|
||||
PluralForms: do.PluralForms,
|
||||
Nplurals: do.nplurals,
|
||||
Plural: do.plural,
|
||||
Translations: cloneTranslations(do.translations),
|
||||
Contexts: cloneContextTranslations(do.contextTranslations),
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalBinary implements encoding.BinaryMarshaler interface
|
||||
func (do *Domain) MarshalBinary() ([]byte, error) {
|
||||
obj := do.translatorEncodingSnapshot()
|
||||
obj := new(TranslatorEncoding)
|
||||
obj.Headers = do.Headers
|
||||
obj.Language = do.Language
|
||||
obj.PluralForms = do.PluralForms
|
||||
obj.Nplurals = do.nplurals
|
||||
obj.Plural = do.plural
|
||||
obj.Translations = do.translations
|
||||
obj.Contexts = do.contextTranslations
|
||||
|
||||
var buff bytes.Buffer
|
||||
encoder := gob.NewEncoder(&buff)
|
||||
@@ -958,68 +845,22 @@ func (do *Domain) UnmarshalBinary(data []byte) error {
|
||||
obj := new(TranslatorEncoding)
|
||||
|
||||
decoder := gob.NewDecoder(buff)
|
||||
if err := decoder.Decode(obj); err != nil {
|
||||
err := decoder.Decode(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
headers := cloneHeaderMap(obj.Headers)
|
||||
if headers == nil {
|
||||
headers = make(HeaderMap)
|
||||
}
|
||||
translations := cloneTranslations(obj.Translations)
|
||||
if translations == nil {
|
||||
translations = make(map[string]*Translation)
|
||||
}
|
||||
contextTranslations := cloneContextTranslations(obj.Contexts)
|
||||
if contextTranslations == nil {
|
||||
contextTranslations = make(map[string]map[string]*Translation)
|
||||
}
|
||||
|
||||
for id, trans := range translations {
|
||||
if trans == nil {
|
||||
trans = NewTranslation()
|
||||
trans.ID = id
|
||||
translations[id] = trans
|
||||
}
|
||||
}
|
||||
for context, translationsForContext := range contextTranslations {
|
||||
if translationsForContext == nil {
|
||||
contextTranslations[context] = make(map[string]*Translation)
|
||||
continue
|
||||
}
|
||||
for id, trans := range translationsForContext {
|
||||
if trans == nil {
|
||||
trans = NewTranslation()
|
||||
trans.ID = id
|
||||
translationsForContext[id] = trans
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var pluralforms plurals.Expression
|
||||
if expr, err := plurals.Compile(obj.Plural); err == nil {
|
||||
pluralforms = expr
|
||||
}
|
||||
|
||||
do.trMutex.Lock()
|
||||
do.pluralMutex.Lock()
|
||||
|
||||
do.Headers = headers
|
||||
do.Headers = obj.Headers
|
||||
do.Language = obj.Language
|
||||
do.PluralForms = obj.PluralForms
|
||||
do.nplurals = obj.Nplurals
|
||||
do.plural = obj.Plural
|
||||
do.pluralforms = pluralforms
|
||||
do.translations = translations
|
||||
do.contextTranslations = contextTranslations
|
||||
do.pluralTranslations = make(map[string]*Translation)
|
||||
do.headerComments = make([]string, 0)
|
||||
do.trBuffer = nil
|
||||
do.ctxBuffer = ""
|
||||
do.refBuffer = ""
|
||||
do.translations = obj.Translations
|
||||
do.contextTranslations = obj.Contexts
|
||||
|
||||
do.pluralMutex.Unlock()
|
||||
do.trMutex.Unlock()
|
||||
if expr, err := plurals.Compile(do.plural); err == nil {
|
||||
do.pluralforms = expr
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
+108
-170
@@ -55,59 +55,51 @@ func init() {
|
||||
domain: "default",
|
||||
languages: []string{FallbackLocale},
|
||||
library: "/usr/local/share/locale",
|
||||
locales: make([]*Locale, 0),
|
||||
locales: nil,
|
||||
}
|
||||
|
||||
// Register Translator types for gob encoding
|
||||
gob.Register(TranslatorEncoding{})
|
||||
}
|
||||
|
||||
// loadLocales ensures package-level locales and their configured domain are loaded.
|
||||
// loadLocales creates a new Locale object for every language (specified using Configure)
|
||||
// at package level based on the configuration of global configuration .
|
||||
// It is called when trying to use Get or GetD methods.
|
||||
func loadLocales(rebuildCache bool) {
|
||||
globalConfig.Lock()
|
||||
|
||||
if globalConfig.locales == nil || rebuildCache {
|
||||
locales := make([]*Locale, 0, len(globalConfig.languages))
|
||||
var locales []*Locale
|
||||
for _, language := range globalConfig.languages {
|
||||
locales = append(locales, NewLocale(globalConfig.library, language))
|
||||
}
|
||||
globalConfig.locales = locales
|
||||
}
|
||||
|
||||
locales := append([]*Locale(nil), globalConfig.locales...)
|
||||
domain := globalConfig.domain
|
||||
globalConfig.Unlock()
|
||||
|
||||
for _, locale := range locales {
|
||||
if locale == nil {
|
||||
continue
|
||||
for _, locale := range globalConfig.locales {
|
||||
if _, ok := locale.Domains[globalConfig.domain]; !ok || rebuildCache {
|
||||
locale.AddDomain(globalConfig.domain)
|
||||
}
|
||||
if rebuildCache || !locale.hasDomain(domain) {
|
||||
locale.AddDomain(domain)
|
||||
}
|
||||
locale.SetDomain(domain)
|
||||
locale.SetDomain(globalConfig.domain)
|
||||
}
|
||||
|
||||
globalConfig.Unlock()
|
||||
}
|
||||
|
||||
// GetDomain is the domain getter for the package configuration
|
||||
func GetDomain() string {
|
||||
var dom string
|
||||
globalConfig.RLock()
|
||||
domain := globalConfig.domain
|
||||
var locale *Locale
|
||||
for _, candidate := range globalConfig.locales {
|
||||
if candidate != nil {
|
||||
locale = candidate
|
||||
break
|
||||
}
|
||||
if globalConfig.locales != nil {
|
||||
// All locales have the same domain
|
||||
dom = globalConfig.locales[0].GetDomain()
|
||||
}
|
||||
if dom == "" {
|
||||
dom = globalConfig.domain
|
||||
}
|
||||
globalConfig.RUnlock()
|
||||
|
||||
if locale != nil {
|
||||
if localeDomain := locale.GetDomain(); localeDomain != "" {
|
||||
return localeDomain
|
||||
}
|
||||
}
|
||||
return domain
|
||||
return dom
|
||||
}
|
||||
|
||||
// SetDomain sets the name for the domain to be used at package level.
|
||||
@@ -115,8 +107,8 @@ func GetDomain() string {
|
||||
func SetDomain(dom string) {
|
||||
globalConfig.Lock()
|
||||
globalConfig.domain = dom
|
||||
for _, locale := range globalConfig.locales {
|
||||
if locale != nil {
|
||||
if globalConfig.locales != nil {
|
||||
for _, locale := range globalConfig.locales {
|
||||
locale.SetDomain(dom)
|
||||
}
|
||||
}
|
||||
@@ -139,10 +131,8 @@ func GetLanguage() string {
|
||||
// GetLanguages returns all languages that have been supplied.
|
||||
func GetLanguages() []string {
|
||||
globalConfig.RLock()
|
||||
languages := make([]string, len(globalConfig.languages))
|
||||
copy(languages, globalConfig.languages)
|
||||
globalConfig.RUnlock()
|
||||
return languages
|
||||
defer globalConfig.RUnlock()
|
||||
return globalConfig.languages
|
||||
}
|
||||
|
||||
// SetLanguage sets the language code (or colon separated language codes) to be used at package level.
|
||||
@@ -150,7 +140,7 @@ func GetLanguages() []string {
|
||||
func SetLanguage(lang string) {
|
||||
globalConfig.Lock()
|
||||
var languages []string
|
||||
for language := range strings.SplitSeq(lang, ":") {
|
||||
for _, language := range strings.Split(lang, ":") {
|
||||
languages = append(languages, SimplifiedLocale(language))
|
||||
}
|
||||
globalConfig.languages = languages
|
||||
@@ -181,20 +171,17 @@ func SetLibrary(lib string) {
|
||||
// GetLocales returns the locales that have been set for the package configuration.
|
||||
func GetLocales() []*Locale {
|
||||
globalConfig.RLock()
|
||||
locales := make([]*Locale, len(globalConfig.locales))
|
||||
copy(locales, globalConfig.locales)
|
||||
globalConfig.RUnlock()
|
||||
return locales
|
||||
defer globalConfig.RUnlock()
|
||||
return globalConfig.locales
|
||||
}
|
||||
|
||||
// GetStorage is the locale storage getter for the package configuration.
|
||||
//
|
||||
// Deprecated: Storage has been renamed to Locale for consistency, use GetLocales instead.
|
||||
func GetStorage() *Locale {
|
||||
for _, locale := range GetLocales() {
|
||||
if locale != nil {
|
||||
return locale
|
||||
}
|
||||
locales := GetLocales()
|
||||
if len(locales) > 0 {
|
||||
return locales[0]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -203,40 +190,24 @@ func GetStorage() *Locale {
|
||||
// NewLocale(). This makes it possible to attach custom Domain objects from in-memory po/mo.
|
||||
// The library, language and domain of the first Locale will set the default global configuration.
|
||||
func SetLocales(locales []*Locale) {
|
||||
ownedLocales := make([]*Locale, len(locales))
|
||||
copy(ownedLocales, locales)
|
||||
|
||||
globalConfig.Lock()
|
||||
globalConfig.locales = ownedLocales
|
||||
defer globalConfig.Unlock()
|
||||
|
||||
languages := make([]string, 0, len(ownedLocales))
|
||||
var first *Locale
|
||||
for _, locale := range ownedLocales {
|
||||
if locale == nil {
|
||||
continue
|
||||
}
|
||||
globalConfig.locales = locales
|
||||
globalConfig.library = locales[0].path
|
||||
globalConfig.domain = locales[0].defaultDomain
|
||||
|
||||
locale.RLock()
|
||||
if first == nil {
|
||||
first = locale
|
||||
globalConfig.library = locale.path
|
||||
globalConfig.domain = locale.defaultDomain
|
||||
}
|
||||
var languages []string
|
||||
for _, locale := range locales {
|
||||
languages = append(languages, locale.lang)
|
||||
locale.RUnlock()
|
||||
}
|
||||
globalConfig.languages = languages
|
||||
globalConfig.Unlock()
|
||||
}
|
||||
|
||||
// SetStorage allows overriding the global Locale object with one built manually with NewLocale().
|
||||
//
|
||||
// Deprecated: Storage has been renamed to Locale for consistency, use SetLocales instead.
|
||||
func SetStorage(locale *Locale) {
|
||||
if locale == nil {
|
||||
SetLocales(nil)
|
||||
return
|
||||
}
|
||||
SetLocales([]*Locale{locale})
|
||||
}
|
||||
|
||||
@@ -248,7 +219,7 @@ func Configure(lib, lang, dom string) {
|
||||
globalConfig.Lock()
|
||||
globalConfig.library = lib
|
||||
var languages []string
|
||||
for language := range strings.SplitSeq(lang, ":") {
|
||||
for _, language := range strings.Split(lang, ":") {
|
||||
languages = append(languages, SimplifiedLocale(language))
|
||||
}
|
||||
globalConfig.languages = languages
|
||||
@@ -259,137 +230,114 @@ func Configure(lib, lang, dom string) {
|
||||
}
|
||||
|
||||
// Get uses the default domain globally set to return the corresponding Translation of a given string.
|
||||
// Supports optional parameters (vars... any) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func Get(str string, vars ...any) string {
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func Get(str string, vars ...interface{}) string {
|
||||
return GetD(GetDomain(), str, vars...)
|
||||
}
|
||||
|
||||
// GetN retrieves the (N)th plural form of Translation for the given string in the default domain.
|
||||
// Supports optional parameters (vars... any) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func GetN(str, plural string, n int, vars ...any) string {
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func GetN(str, plural string, n int, vars ...interface{}) string {
|
||||
return GetND(GetDomain(), str, plural, n, vars...)
|
||||
}
|
||||
|
||||
// GetD returns the corresponding Translation in the given domain for a given string.
|
||||
// Supports optional parameters (vars... any) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func GetD(dom, str string, vars ...any) string {
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func GetD(dom, str string, vars ...interface{}) string {
|
||||
// Try to load default package Locales
|
||||
loadLocales(false)
|
||||
|
||||
locales := GetLocales()
|
||||
var fallback *Locale
|
||||
for _, locale := range locales {
|
||||
if locale == nil {
|
||||
continue
|
||||
}
|
||||
fallback = locale
|
||||
if !locale.hasDomain(dom) {
|
||||
globalConfig.RLock()
|
||||
defer globalConfig.RUnlock()
|
||||
|
||||
var tr string
|
||||
for i, locale := range globalConfig.locales {
|
||||
if _, ok := locale.Domains[dom]; !ok {
|
||||
locale.AddDomain(dom)
|
||||
}
|
||||
if locale.IsTranslatedD(dom, str) {
|
||||
return locale.GetD(dom, str, vars...)
|
||||
if !locale.IsTranslatedD(dom, str) && i < (len(globalConfig.locales)-1) {
|
||||
continue
|
||||
}
|
||||
tr = locale.GetD(dom, str, vars...)
|
||||
break
|
||||
}
|
||||
if fallback != nil {
|
||||
return fallback.GetD(dom, str, vars...)
|
||||
}
|
||||
return FormatString(str, vars...)
|
||||
return tr
|
||||
}
|
||||
|
||||
// GetND retrieves the (N)th plural form of Translation in the given domain for a given string.
|
||||
// Supports optional parameters (vars... any) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func GetND(dom, str, plural string, n int, vars ...any) string {
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func GetND(dom, str, plural string, n int, vars ...interface{}) string {
|
||||
// Try to load default package Locales
|
||||
loadLocales(false)
|
||||
|
||||
locales := GetLocales()
|
||||
var fallback *Locale
|
||||
for _, locale := range locales {
|
||||
if locale == nil {
|
||||
continue
|
||||
}
|
||||
fallback = locale
|
||||
if !locale.hasDomain(dom) {
|
||||
globalConfig.RLock()
|
||||
defer globalConfig.RUnlock()
|
||||
|
||||
var tr string
|
||||
for i, locale := range globalConfig.locales {
|
||||
if _, ok := locale.Domains[dom]; !ok {
|
||||
locale.AddDomain(dom)
|
||||
}
|
||||
if locale.IsTranslatedND(dom, str, n) {
|
||||
return locale.GetND(dom, str, plural, n, vars...)
|
||||
if !locale.IsTranslatedND(dom, str, n) && i < (len(globalConfig.locales)-1) {
|
||||
continue
|
||||
}
|
||||
tr = locale.GetND(dom, str, plural, n, vars...)
|
||||
break
|
||||
}
|
||||
if fallback != nil {
|
||||
return fallback.GetND(dom, str, plural, n, vars...)
|
||||
}
|
||||
if n == 1 {
|
||||
return FormatString(str, vars...)
|
||||
}
|
||||
return FormatString(plural, vars...)
|
||||
return tr
|
||||
}
|
||||
|
||||
// GetC uses the default domain globally set to return the corresponding Translation of the given string in the given context.
|
||||
// Supports optional parameters (vars... any) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func GetC(str, ctx string, vars ...any) string {
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func GetC(str, ctx string, vars ...interface{}) string {
|
||||
return GetDC(GetDomain(), str, ctx, vars...)
|
||||
}
|
||||
|
||||
// GetNC retrieves the (N)th plural form of Translation for the given string in the given context in the default domain.
|
||||
// Supports optional parameters (vars... any) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func GetNC(str, plural string, n int, ctx string, vars ...any) string {
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func GetNC(str, plural string, n int, ctx string, vars ...interface{}) string {
|
||||
return GetNDC(GetDomain(), str, plural, n, ctx, vars...)
|
||||
}
|
||||
|
||||
// GetDC returns the corresponding Translation in the given domain for the given string in the given context.
|
||||
// Supports optional parameters (vars... any) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func GetDC(dom, str, ctx string, vars ...any) string {
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func GetDC(dom, str, ctx string, vars ...interface{}) string {
|
||||
// Try to load default package Locales
|
||||
loadLocales(false)
|
||||
|
||||
locales := GetLocales()
|
||||
var fallback *Locale
|
||||
for _, locale := range locales {
|
||||
if locale == nil {
|
||||
globalConfig.RLock()
|
||||
defer globalConfig.RUnlock()
|
||||
|
||||
var tr string
|
||||
for i, locale := range globalConfig.locales {
|
||||
if !locale.IsTranslatedDC(dom, str, ctx) && i < (len(globalConfig.locales)-1) {
|
||||
continue
|
||||
}
|
||||
fallback = locale
|
||||
if !locale.hasDomain(dom) {
|
||||
locale.AddDomain(dom)
|
||||
}
|
||||
if locale.IsTranslatedDC(dom, str, ctx) {
|
||||
return locale.GetDC(dom, str, ctx, vars...)
|
||||
}
|
||||
tr = locale.GetDC(dom, str, ctx, vars...)
|
||||
break
|
||||
}
|
||||
if fallback != nil {
|
||||
return fallback.GetDC(dom, str, ctx, vars...)
|
||||
}
|
||||
return FormatString(str, vars...)
|
||||
return tr
|
||||
}
|
||||
|
||||
// GetNDC retrieves the (N)th plural form of Translation in the given domain for a given string.
|
||||
// Supports optional parameters (vars... any) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func GetNDC(dom, str, plural string, n int, ctx string, vars ...any) string {
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func GetNDC(dom, str, plural string, n int, ctx string, vars ...interface{}) string {
|
||||
// Try to load default package Locales
|
||||
loadLocales(false)
|
||||
|
||||
locales := GetLocales()
|
||||
var fallback *Locale
|
||||
for _, locale := range locales {
|
||||
if locale == nil {
|
||||
// Return Translation
|
||||
globalConfig.RLock()
|
||||
defer globalConfig.RUnlock()
|
||||
|
||||
var tr string
|
||||
for i, locale := range globalConfig.locales {
|
||||
if !locale.IsTranslatedNDC(dom, str, n, ctx) && i < (len(globalConfig.locales)-1) {
|
||||
continue
|
||||
}
|
||||
fallback = locale
|
||||
if !locale.hasDomain(dom) {
|
||||
locale.AddDomain(dom)
|
||||
}
|
||||
if locale.IsTranslatedNDC(dom, str, n, ctx) {
|
||||
return locale.GetNDC(dom, str, plural, n, ctx, vars...)
|
||||
}
|
||||
tr = locale.GetNDC(dom, str, plural, n, ctx, vars...)
|
||||
break
|
||||
}
|
||||
if fallback != nil {
|
||||
return fallback.GetNDC(dom, str, plural, n, ctx, vars...)
|
||||
}
|
||||
if n == 1 {
|
||||
return FormatString(str, vars...)
|
||||
}
|
||||
return FormatString(plural, vars...)
|
||||
return tr
|
||||
}
|
||||
|
||||
// IsTranslated reports whether a string is translated in given languages.
|
||||
@@ -418,23 +366,18 @@ func IsTranslatedND(dom, str string, n int, langs ...string) bool {
|
||||
}
|
||||
|
||||
loadLocales(false)
|
||||
locales := GetLocales()
|
||||
|
||||
globalConfig.RLock()
|
||||
defer globalConfig.RUnlock()
|
||||
|
||||
for _, lang := range langs {
|
||||
lang = SimplifiedLocale(lang)
|
||||
|
||||
for _, supportedLocale := range locales {
|
||||
if supportedLocale == nil {
|
||||
for _, supportedLocale := range globalConfig.locales {
|
||||
if lang != supportedLocale.GetActualLanguage(dom) {
|
||||
continue
|
||||
}
|
||||
if lang != supportedLocale.GetLanguage() && lang != supportedLocale.GetActualLanguage(dom) {
|
||||
continue
|
||||
}
|
||||
if !supportedLocale.hasDomain(dom) {
|
||||
supportedLocale.AddDomain(dom)
|
||||
}
|
||||
if supportedLocale.IsTranslatedND(dom, str, n) {
|
||||
return true
|
||||
}
|
||||
return supportedLocale.IsTranslatedND(dom, str, n)
|
||||
}
|
||||
}
|
||||
return false
|
||||
@@ -455,7 +398,7 @@ func IsTranslatedNC(str string, n int, ctx string, langs ...string) bool {
|
||||
// IsTranslatedDC reports whether a domain context string is translated in given languages.
|
||||
// When the langs argument is omitted, the output of GetLanguages is used.
|
||||
func IsTranslatedDC(dom, str, ctx string, langs ...string) bool {
|
||||
return IsTranslatedNDC(dom, str, 1, ctx, langs...)
|
||||
return IsTranslatedNDC(dom, str, 0, ctx, langs...)
|
||||
}
|
||||
|
||||
// IsTranslatedNDC reports whether a plural domain context string is translated in any of given languages.
|
||||
@@ -466,23 +409,18 @@ func IsTranslatedNDC(dom, str string, n int, ctx string, langs ...string) bool {
|
||||
}
|
||||
|
||||
loadLocales(false)
|
||||
locales := GetLocales()
|
||||
|
||||
globalConfig.RLock()
|
||||
defer globalConfig.RUnlock()
|
||||
|
||||
for _, lang := range langs {
|
||||
lang = SimplifiedLocale(lang)
|
||||
|
||||
for _, supportedLocale := range locales {
|
||||
if supportedLocale == nil {
|
||||
for _, locale := range globalConfig.locales {
|
||||
if lang != locale.GetActualLanguage(dom) {
|
||||
continue
|
||||
}
|
||||
if lang != supportedLocale.GetLanguage() && lang != supportedLocale.GetActualLanguage(dom) {
|
||||
continue
|
||||
}
|
||||
if !supportedLocale.hasDomain(dom) {
|
||||
supportedLocale.AddDomain(dom)
|
||||
}
|
||||
if supportedLocale.IsTranslatedNDC(dom, str, n, ctx) {
|
||||
return true
|
||||
}
|
||||
return locale.IsTranslatedNDC(dom, str, n, ctx)
|
||||
}
|
||||
}
|
||||
return false
|
||||
|
||||
+27
-31
@@ -29,21 +29,16 @@ func SimplifiedLocale(lang string) string {
|
||||
}
|
||||
|
||||
// FormatString applies text formatting only when needed to parse variables.
|
||||
func FormatString(str string, vars ...any) string {
|
||||
func FormatString(str string, vars ...interface{}) string {
|
||||
if len(vars) > 0 {
|
||||
return FormatStringWithArgs(str, vars)
|
||||
return fmt.Sprintf(str, vars...)
|
||||
}
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
// FormatStringWithArgs applies text formatting as a proxy for fmt.Sprintf.
|
||||
func FormatStringWithArgs(str string, vars []any) string {
|
||||
return fmt.Sprintf(str, vars...)
|
||||
}
|
||||
|
||||
// Appendf applies text formatting only when needed to parse variables.
|
||||
func Appendf(b []byte, str string, vars ...any) []byte {
|
||||
func Appendf(b []byte, str string, vars ...interface{}) []byte {
|
||||
if len(vars) > 0 {
|
||||
return fmt.Appendf(b, str, vars...)
|
||||
}
|
||||
@@ -52,49 +47,50 @@ func Appendf(b []byte, str string, vars ...any) []byte {
|
||||
}
|
||||
|
||||
// NPrintf support named format
|
||||
// NPrintf("%(name)s is Type %(type)s", map[string]any{"name": "Gotext", "type": "struct"})
|
||||
func NPrintf(format string, params map[string]any) {
|
||||
// NPrintf("%(name)s is Type %(type)s", map[string]interface{}{"name": "Gotext", "type": "struct"})
|
||||
func NPrintf(format string, params map[string]interface{}) {
|
||||
f, p := parseSprintf(format, params)
|
||||
fmt.Printf(f, p...)
|
||||
}
|
||||
|
||||
// Sprintf support named format
|
||||
//
|
||||
// Sprintf("%(name)s is Type %(type)s", map[string]any{"name": "Gotext", "type": "struct"})
|
||||
func Sprintf(format string, params map[string]any) string {
|
||||
// Sprintf("%(name)s is Type %(type)s", map[string]interface{}{"name": "Gotext", "type": "struct"})
|
||||
func Sprintf(format string, params map[string]interface{}) string {
|
||||
f, p := parseSprintf(format, params)
|
||||
return fmt.Sprintf(f, p...)
|
||||
}
|
||||
|
||||
func parseSprintf(format string, params map[string]any) (string, []any) {
|
||||
func parseSprintf(format string, params map[string]interface{}) (string, []interface{}) {
|
||||
f, n := reformatSprintf(format)
|
||||
var p []any
|
||||
if len(n) > 0 {
|
||||
p = make([]any, len(n))
|
||||
for i, v := range n {
|
||||
p[i] = params[v]
|
||||
}
|
||||
var p []interface{}
|
||||
for _, v := range n {
|
||||
p = append(p, params[v])
|
||||
}
|
||||
return f, p
|
||||
}
|
||||
|
||||
func reformatSprintf(f string) (string, []string) {
|
||||
matches := re.FindAllStringSubmatchIndex(f, -1)
|
||||
ord := make([]string, 0, len(matches))
|
||||
pair := make([]int, 1, 1+2*len(matches))
|
||||
pair[0] = 0
|
||||
m := re.FindAllStringSubmatch(f, -1)
|
||||
i := re.FindAllStringSubmatchIndex(f, -1)
|
||||
|
||||
for _, match := range matches {
|
||||
ord = append(ord, f[match[2]:match[3]])
|
||||
pair = append(pair, match[2]-1, match[3]+1)
|
||||
ord := []string{}
|
||||
for _, v := range m {
|
||||
ord = append(ord, v[1])
|
||||
}
|
||||
|
||||
pair := []int{0}
|
||||
for _, v := range i {
|
||||
pair = append(pair, v[2]-1)
|
||||
pair = append(pair, v[3]+1)
|
||||
}
|
||||
pair = append(pair, len(f))
|
||||
plen := len(pair)
|
||||
|
||||
var out strings.Builder
|
||||
out.Grow(len(f))
|
||||
for n := 0; n < len(pair); n += 2 {
|
||||
out.WriteString(f[pair[n]:pair[n+1]])
|
||||
out := ""
|
||||
for n := 0; n < plen; n += 2 {
|
||||
out += f[pair[n]:pair[n+1]]
|
||||
}
|
||||
|
||||
return out.String(), ord
|
||||
return out, ord
|
||||
}
|
||||
|
||||
+142
-193
@@ -9,7 +9,6 @@ import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"io/fs"
|
||||
"maps"
|
||||
"os"
|
||||
"path"
|
||||
"sync"
|
||||
@@ -23,6 +22,8 @@ multiple languages at the same time by working with this object.
|
||||
Example:
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
)
|
||||
@@ -89,114 +90,95 @@ func NewLocaleFSWithPath(l string, filesystem fs.FS, p string) *Locale {
|
||||
return loc
|
||||
}
|
||||
|
||||
func (l *Locale) snapshotPathLanguageFS() (string, string, fs.FS) {
|
||||
if l == nil {
|
||||
return "", "", nil
|
||||
func (l *Locale) findExt(dom, ext string) string {
|
||||
filename := path.Join(l.path, l.lang, "LC_MESSAGES", dom+"."+ext)
|
||||
if l.fileExists(filename) {
|
||||
return filename
|
||||
}
|
||||
|
||||
l.RLock()
|
||||
p := l.path
|
||||
lang := l.lang
|
||||
filesystem := l.fs
|
||||
l.RUnlock()
|
||||
return p, lang, filesystem
|
||||
}
|
||||
|
||||
func fileExistsIn(filesystem fs.FS, filename string) bool {
|
||||
if filesystem != nil {
|
||||
_, err := fs.Stat(filesystem, filename)
|
||||
return err == nil
|
||||
}
|
||||
_, err := os.Stat(filename)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (l *Locale) findExtWithFS(dom, ext string) (fs.FS, string) {
|
||||
p, lang, filesystem := l.snapshotPathLanguageFS()
|
||||
|
||||
filename := path.Join(p, lang, "LC_MESSAGES", dom+"."+ext)
|
||||
if fileExistsIn(filesystem, filename) {
|
||||
return filesystem, filename
|
||||
}
|
||||
|
||||
if len(lang) > 2 {
|
||||
filename = path.Join(p, lang[:2], "LC_MESSAGES", dom+"."+ext)
|
||||
if fileExistsIn(filesystem, filename) {
|
||||
return filesystem, filename
|
||||
if len(l.lang) > 2 {
|
||||
filename = path.Join(l.path, l.lang[:2], "LC_MESSAGES", dom+"."+ext)
|
||||
if l.fileExists(filename) {
|
||||
return filename
|
||||
}
|
||||
}
|
||||
|
||||
filename = path.Join(p, lang, dom+"."+ext)
|
||||
if fileExistsIn(filesystem, filename) {
|
||||
return filesystem, filename
|
||||
filename = path.Join(l.path, l.lang, dom+"."+ext)
|
||||
if l.fileExists(filename) {
|
||||
return filename
|
||||
}
|
||||
|
||||
if len(lang) > 2 {
|
||||
filename = path.Join(p, lang[:2], dom+"."+ext)
|
||||
if fileExistsIn(filesystem, filename) {
|
||||
return filesystem, filename
|
||||
if len(l.lang) > 2 {
|
||||
filename = path.Join(l.path, l.lang[:2], dom+"."+ext)
|
||||
if l.fileExists(filename) {
|
||||
return filename
|
||||
}
|
||||
}
|
||||
|
||||
return filesystem, ""
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetActualLanguage inspects the filesystem and decides whether to strip
|
||||
// a CC part of the ll_CC locale string.
|
||||
func (l *Locale) GetActualLanguage(dom string) string {
|
||||
p, lang, filesystem := l.snapshotPathLanguageFS()
|
||||
extensions := []string{"po", "mo"}
|
||||
extensions := []string{"mo", "po"}
|
||||
var fp string
|
||||
for _, ext := range extensions {
|
||||
// 'll' (or 'll_CC') exists, and it was specified as-is
|
||||
fp = path.Join(p, lang, "LC_MESSAGES", dom+"."+ext)
|
||||
if fileExistsIn(filesystem, fp) {
|
||||
return lang
|
||||
fp = path.Join(l.path, l.lang, "LC_MESSAGES", dom+"."+ext)
|
||||
if l.fileExists(fp) {
|
||||
return l.lang
|
||||
}
|
||||
// 'll' exists, but 'll_CC' was specified
|
||||
if len(lang) > 2 {
|
||||
fp = path.Join(p, lang[:2], "LC_MESSAGES", dom+"."+ext)
|
||||
if fileExistsIn(filesystem, fp) {
|
||||
return lang[:2]
|
||||
if len(l.lang) > 2 {
|
||||
fp = path.Join(l.path, l.lang[:2], "LC_MESSAGES", dom+"."+ext)
|
||||
if l.fileExists(fp) {
|
||||
return l.lang[:2]
|
||||
}
|
||||
}
|
||||
// 'll' (or 'll_CC') exists outside of LC_category, and it was specified as-is
|
||||
fp = path.Join(p, lang, dom+"."+ext)
|
||||
if fileExistsIn(filesystem, fp) {
|
||||
return lang
|
||||
fp = path.Join(l.path, l.lang, dom+"."+ext)
|
||||
if l.fileExists(fp) {
|
||||
return l.lang
|
||||
}
|
||||
// 'll' exists outside of LC_category, but 'll_CC' was specified
|
||||
if len(lang) > 2 {
|
||||
fp = path.Join(p, lang[:2], dom+"."+ext)
|
||||
if fileExistsIn(filesystem, fp) {
|
||||
return lang[:2]
|
||||
if len(l.lang) > 2 {
|
||||
fp = path.Join(l.path, l.lang[:2], dom+"."+ext)
|
||||
if l.fileExists(fp) {
|
||||
return l.lang[:2]
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (l *Locale) fileExists(filename string) bool {
|
||||
if l.fs != nil {
|
||||
_, err := fs.Stat(l.fs, filename)
|
||||
return err == nil
|
||||
}
|
||||
_, err := os.Stat(filename)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// AddDomain creates a new domain for a given locale object and initializes the Po object.
|
||||
// If the domain exists, it gets reloaded.
|
||||
func (l *Locale) AddDomain(dom string) {
|
||||
if l == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var poObj Translator
|
||||
|
||||
filesystem, file := l.findExtWithFS(dom, "po")
|
||||
file := l.findExt(dom, "po")
|
||||
if file != "" {
|
||||
poObj = NewPoFS(filesystem)
|
||||
poObj = NewPoFS(l.fs)
|
||||
// Parse file.
|
||||
poObj.ParseFile(file)
|
||||
} else {
|
||||
filesystem, file = l.findExtWithFS(dom, "mo")
|
||||
file = l.findExt(dom, "mo")
|
||||
if file != "" {
|
||||
poObj = NewMoFS(filesystem)
|
||||
poObj = NewMoFS(l.fs)
|
||||
// Parse file.
|
||||
poObj.ParseFile(file)
|
||||
} else {
|
||||
// fallback return if no file found with
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -230,34 +212,9 @@ func (l *Locale) AddTranslator(dom string, tr Translator) {
|
||||
|
||||
l.Unlock()
|
||||
}
|
||||
func (l *Locale) translatorFor(dom string) Translator {
|
||||
if l == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
l.RLock()
|
||||
translator := l.Domains[dom]
|
||||
l.RUnlock()
|
||||
return translator
|
||||
}
|
||||
|
||||
func (l *Locale) hasDomain(dom string) bool {
|
||||
if l == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
l.RLock()
|
||||
_, ok := l.Domains[dom]
|
||||
l.RUnlock()
|
||||
return ok
|
||||
}
|
||||
|
||||
// GetDomain is the domain getter for Locale configuration
|
||||
func (l *Locale) GetDomain() string {
|
||||
if l == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
l.RLock()
|
||||
dom := l.defaultDomain
|
||||
l.RUnlock()
|
||||
@@ -266,10 +223,6 @@ func (l *Locale) GetDomain() string {
|
||||
|
||||
// SetDomain sets the name for the domain to be used.
|
||||
func (l *Locale) SetDomain(dom string) {
|
||||
if l == nil {
|
||||
return
|
||||
}
|
||||
|
||||
l.Lock()
|
||||
l.defaultDomain = dom
|
||||
l.Unlock()
|
||||
@@ -277,10 +230,6 @@ func (l *Locale) SetDomain(dom string) {
|
||||
|
||||
// GetLanguage is the lang getter for Locale configuration
|
||||
func (l *Locale) GetLanguage() string {
|
||||
if l == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
l.RLock()
|
||||
lang := l.lang
|
||||
l.RUnlock()
|
||||
@@ -288,37 +237,51 @@ func (l *Locale) GetLanguage() string {
|
||||
}
|
||||
|
||||
// Get uses a domain "default" to return the corresponding Translation of a given string.
|
||||
// Supports optional parameters (vars... any) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (l *Locale) Get(str string, vars ...any) string {
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (l *Locale) Get(str string, vars ...interface{}) string {
|
||||
return l.GetD(l.GetDomain(), str, vars...)
|
||||
}
|
||||
|
||||
// GetN retrieves the (N)th plural form of Translation for the given string in the "default" domain.
|
||||
// Supports optional parameters (vars... any) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (l *Locale) GetN(str, plural string, n int, vars ...any) string {
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (l *Locale) GetN(str, plural string, n int, vars ...interface{}) string {
|
||||
return l.GetND(l.GetDomain(), str, plural, n, vars...)
|
||||
}
|
||||
|
||||
// GetD returns the corresponding Translation in the given domain for the given string.
|
||||
// Supports optional parameters (vars... any) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (l *Locale) GetD(dom, str string, vars ...any) string {
|
||||
translator := l.translatorFor(dom)
|
||||
if translator != nil {
|
||||
return translator.Get(str, vars...)
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (l *Locale) GetD(dom, str string, vars ...interface{}) string {
|
||||
// Sync read
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
|
||||
if l.Domains != nil {
|
||||
if _, ok := l.Domains[dom]; ok {
|
||||
if l.Domains[dom] != nil {
|
||||
return l.Domains[dom].Get(str, vars...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return FormatString(str, vars...)
|
||||
}
|
||||
|
||||
// GetND retrieves the (N)th plural form of Translation in the given domain for the given string.
|
||||
// Supports optional parameters (vars... any) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (l *Locale) GetND(dom, str, plural string, n int, vars ...any) string {
|
||||
translator := l.translatorFor(dom)
|
||||
if translator != nil {
|
||||
return translator.GetN(str, plural, n, vars...)
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (l *Locale) GetND(dom, str, plural string, n int, vars ...interface{}) string {
|
||||
// Sync read
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
|
||||
if l.Domains != nil {
|
||||
if _, ok := l.Domains[dom]; ok {
|
||||
if l.Domains[dom] != nil {
|
||||
return l.Domains[dom].GetN(str, plural, n, vars...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use western default rule (n != 1) to handle missing domain default result.
|
||||
// Use western default rule (plural > 1) to handle missing domain default result.
|
||||
if n == 1 {
|
||||
return FormatString(str, vars...)
|
||||
}
|
||||
@@ -326,37 +289,51 @@ func (l *Locale) GetND(dom, str, plural string, n int, vars ...any) string {
|
||||
}
|
||||
|
||||
// GetC uses a domain "default" to return the corresponding Translation of the given string in the given context.
|
||||
// Supports optional parameters (vars... any) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (l *Locale) GetC(str, ctx string, vars ...any) string {
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (l *Locale) GetC(str, ctx string, vars ...interface{}) string {
|
||||
return l.GetDC(l.GetDomain(), str, ctx, vars...)
|
||||
}
|
||||
|
||||
// GetNC retrieves the (N)th plural form of Translation for the given string in the given context in the "default" domain.
|
||||
// Supports optional parameters (vars... any) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (l *Locale) GetNC(str, plural string, n int, ctx string, vars ...any) string {
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (l *Locale) GetNC(str, plural string, n int, ctx string, vars ...interface{}) string {
|
||||
return l.GetNDC(l.GetDomain(), str, plural, n, ctx, vars...)
|
||||
}
|
||||
|
||||
// GetDC returns the corresponding Translation in the given domain for the given string in the given context.
|
||||
// Supports optional parameters (vars... any) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (l *Locale) GetDC(dom, str, ctx string, vars ...any) string {
|
||||
translator := l.translatorFor(dom)
|
||||
if translator != nil {
|
||||
return translator.GetC(str, ctx, vars...)
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (l *Locale) GetDC(dom, str, ctx string, vars ...interface{}) string {
|
||||
// Sync read
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
|
||||
if l.Domains != nil {
|
||||
if _, ok := l.Domains[dom]; ok {
|
||||
if l.Domains[dom] != nil {
|
||||
return l.Domains[dom].GetC(str, ctx, vars...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return FormatString(str, vars...)
|
||||
}
|
||||
|
||||
// GetNDC retrieves the (N)th plural form of Translation in the given domain for the given string in the given context.
|
||||
// Supports optional parameters (vars... any) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (l *Locale) GetNDC(dom, str, plural string, n int, ctx string, vars ...any) string {
|
||||
translator := l.translatorFor(dom)
|
||||
if translator != nil {
|
||||
return translator.GetNC(str, plural, n, ctx, vars...)
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (l *Locale) GetNDC(dom, str, plural string, n int, ctx string, vars ...interface{}) string {
|
||||
// Sync read
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
|
||||
if l.Domains != nil {
|
||||
if _, ok := l.Domains[dom]; ok {
|
||||
if l.Domains[dom] != nil {
|
||||
return l.Domains[dom].GetNC(str, plural, n, ctx, vars...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use western default rule (n != 1) to handle missing domain default result.
|
||||
// Use western default rule (plural > 1) to handle missing domain default result.
|
||||
if n == 1 {
|
||||
return FormatString(str, vars...)
|
||||
}
|
||||
@@ -366,26 +343,13 @@ func (l *Locale) GetNDC(dom, str, plural string, n int, ctx string, vars ...any)
|
||||
// GetTranslations returns a copy of all translations in all domains of this locale. It does not support contexts.
|
||||
func (l *Locale) GetTranslations() map[string]*Translation {
|
||||
all := make(map[string]*Translation)
|
||||
if l == nil {
|
||||
return all
|
||||
}
|
||||
|
||||
l.RLock()
|
||||
translators := make([]Translator, 0, len(l.Domains))
|
||||
defer l.RUnlock()
|
||||
for _, translator := range l.Domains {
|
||||
translators = append(translators, translator)
|
||||
}
|
||||
l.RUnlock()
|
||||
|
||||
for _, translator := range translators {
|
||||
if translator == nil {
|
||||
continue
|
||||
for msgID, trans := range translator.GetDomain().GetTranslations() {
|
||||
all[msgID] = trans
|
||||
}
|
||||
domain := translator.GetDomain()
|
||||
if domain == nil {
|
||||
continue
|
||||
}
|
||||
maps.Copy(all, domain.GetTranslations())
|
||||
}
|
||||
|
||||
return all
|
||||
@@ -393,7 +357,7 @@ func (l *Locale) GetTranslations() map[string]*Translation {
|
||||
|
||||
// IsTranslated reports whether a string is translated
|
||||
func (l *Locale) IsTranslated(str string) bool {
|
||||
return l.IsTranslatedND(l.GetDomain(), str, 1)
|
||||
return l.IsTranslatedND(l.GetDomain(), str, 0)
|
||||
}
|
||||
|
||||
// IsTranslatedN reports whether a plural string is translated
|
||||
@@ -403,25 +367,27 @@ func (l *Locale) IsTranslatedN(str string, n int) bool {
|
||||
|
||||
// IsTranslatedD reports whether a domain string is translated
|
||||
func (l *Locale) IsTranslatedD(dom, str string) bool {
|
||||
return l.IsTranslatedND(dom, str, 1)
|
||||
return l.IsTranslatedND(dom, str, 0)
|
||||
}
|
||||
|
||||
// IsTranslatedND reports whether a plural domain string is translated
|
||||
func (l *Locale) IsTranslatedND(dom, str string, n int) bool {
|
||||
translator := l.translatorFor(dom)
|
||||
if translator == nil {
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
|
||||
if l.Domains == nil {
|
||||
return false
|
||||
}
|
||||
domain := translator.GetDomain()
|
||||
if domain == nil {
|
||||
translator, ok := l.Domains[dom]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return domain.IsTranslatedN(str, n)
|
||||
return translator.GetDomain().IsTranslatedN(str, n)
|
||||
}
|
||||
|
||||
// IsTranslatedC reports whether a context string is translated
|
||||
func (l *Locale) IsTranslatedC(str, ctx string) bool {
|
||||
return l.IsTranslatedNDC(l.GetDomain(), str, 1, ctx)
|
||||
return l.IsTranslatedNDC(l.GetDomain(), str, 0, ctx)
|
||||
}
|
||||
|
||||
// IsTranslatedNC reports whether a plural context string is translated
|
||||
@@ -431,20 +397,22 @@ func (l *Locale) IsTranslatedNC(str string, n int, ctx string) bool {
|
||||
|
||||
// IsTranslatedDC reports whether a domain context string is translated
|
||||
func (l *Locale) IsTranslatedDC(dom, str, ctx string) bool {
|
||||
return l.IsTranslatedNDC(dom, str, 1, ctx)
|
||||
return l.IsTranslatedNDC(dom, str, 0, ctx)
|
||||
}
|
||||
|
||||
// IsTranslatedNDC reports whether a plural domain context string is translated
|
||||
func (l *Locale) IsTranslatedNDC(dom string, str string, n int, ctx string) bool {
|
||||
translator := l.translatorFor(dom)
|
||||
if translator == nil {
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
|
||||
if l.Domains == nil {
|
||||
return false
|
||||
}
|
||||
domain := translator.GetDomain()
|
||||
if domain == nil {
|
||||
translator, ok := l.Domains[dom]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return domain.IsTranslatedNC(str, n, ctx)
|
||||
return translator.GetDomain().IsTranslatedNC(str, n, ctx)
|
||||
}
|
||||
|
||||
// LocaleEncoding is used as intermediary storage to encode Locale objects to Gob.
|
||||
@@ -457,35 +425,18 @@ type LocaleEncoding struct {
|
||||
|
||||
// MarshalBinary implements encoding BinaryMarshaler interface
|
||||
func (l *Locale) MarshalBinary() ([]byte, error) {
|
||||
var localePath, lang, defaultDomain string
|
||||
domains := make(map[string]Translator)
|
||||
if l != nil {
|
||||
l.RLock()
|
||||
localePath = l.path
|
||||
lang = l.lang
|
||||
defaultDomain = l.defaultDomain
|
||||
domains = make(map[string]Translator, len(l.Domains))
|
||||
for name, translator := range l.Domains {
|
||||
if translator != nil {
|
||||
domains[name] = translator
|
||||
}
|
||||
}
|
||||
l.RUnlock()
|
||||
}
|
||||
|
||||
obj := &LocaleEncoding{
|
||||
Path: localePath,
|
||||
Lang: lang,
|
||||
Domains: make(map[string][]byte, len(domains)),
|
||||
DefaultDomain: defaultDomain,
|
||||
}
|
||||
for name, translator := range domains {
|
||||
data, err := translator.MarshalBinary()
|
||||
obj := new(LocaleEncoding)
|
||||
obj.DefaultDomain = l.defaultDomain
|
||||
obj.Domains = make(map[string][]byte)
|
||||
for k, v := range l.Domains {
|
||||
var err error
|
||||
obj.Domains[k], err = v.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
obj.Domains[name] = data
|
||||
}
|
||||
obj.Lang = l.lang
|
||||
obj.Path = l.path
|
||||
|
||||
var buff bytes.Buffer
|
||||
encoder := gob.NewEncoder(&buff)
|
||||
@@ -494,7 +445,7 @@ func (l *Locale) MarshalBinary() ([]byte, error) {
|
||||
return buff.Bytes(), err
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements encoding.BinaryUnmarshaler interface
|
||||
// UnmarshalBinary implements encoding BinaryUnmarshaler interface
|
||||
func (l *Locale) UnmarshalBinary(data []byte) error {
|
||||
buff := bytes.NewBuffer(data)
|
||||
obj := new(LocaleEncoding)
|
||||
@@ -505,7 +456,12 @@ func (l *Locale) UnmarshalBinary(data []byte) error {
|
||||
return err
|
||||
}
|
||||
|
||||
domains := make(map[string]Translator, len(obj.Domains))
|
||||
l.defaultDomain = obj.DefaultDomain
|
||||
l.lang = obj.Lang
|
||||
l.path = obj.Path
|
||||
|
||||
// Decode Domains
|
||||
l.Domains = make(map[string]Translator)
|
||||
for k, v := range obj.Domains {
|
||||
var tr TranslatorEncoding
|
||||
buff := bytes.NewBuffer(v)
|
||||
@@ -515,15 +471,8 @@ func (l *Locale) UnmarshalBinary(data []byte) error {
|
||||
return err
|
||||
}
|
||||
|
||||
domains[k] = tr.GetTranslator()
|
||||
l.Domains[k] = tr.GetTranslator()
|
||||
}
|
||||
|
||||
l.Lock()
|
||||
l.defaultDomain = obj.DefaultDomain
|
||||
l.lang = obj.Lang
|
||||
l.path = obj.Path
|
||||
l.Domains = domains
|
||||
l.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
-40
@@ -1,40 +0,0 @@
|
||||
site_name: gotext
|
||||
site_description: GNU gettext utilities for Go
|
||||
site_author: Leonel Quinteros
|
||||
repo_url: https://github.com/leonelquinteros/gotext
|
||||
edit_uri: edit/master/docs/
|
||||
|
||||
theme:
|
||||
name: material
|
||||
palette:
|
||||
primary: indigo
|
||||
accent: indigo
|
||||
features:
|
||||
- navigation.tabs
|
||||
- navigation.sections
|
||||
- navigation.top
|
||||
- search.suggest
|
||||
- search.highlight
|
||||
|
||||
nav:
|
||||
- Home: index.md
|
||||
- Getting Started: GETTING_STARTED.md
|
||||
- User Guides:
|
||||
- Plural Forms: PLURALS.md
|
||||
- xgotext CLI: xgotext.md
|
||||
- Character Encoding: ENCODING.md
|
||||
- Advanced Usage: ADVANCED.md
|
||||
- Best Practices: BEST_PRACTICES.md
|
||||
- API Reference: https://pkg.go.dev/github.com/leonelquinteros/gotext
|
||||
- Contributing:
|
||||
- Guidelines: CONTRIBUTING.md
|
||||
- Code of Conduct: CODE_OF_CONDUCT.md
|
||||
- License: LICENSE.md
|
||||
|
||||
markdown_extensions:
|
||||
- admonition
|
||||
- pymdownx.highlight:
|
||||
anchor_linenums: true
|
||||
- pymdownx.inlinehilite
|
||||
- pymdownx.snippets
|
||||
- pymdownx.superfences
|
||||
+94
-62
@@ -76,42 +76,42 @@ func (mo *Mo) GetDomain() *Domain {
|
||||
}
|
||||
|
||||
// Get returns the translation for the given string
|
||||
func (mo *Mo) Get(str string, vars ...any) string {
|
||||
func (mo *Mo) Get(str string, vars ...interface{}) string {
|
||||
return mo.domain.Get(str, vars...)
|
||||
}
|
||||
|
||||
// Append a translation string into the domain
|
||||
func (mo *Mo) Append(b []byte, str string, vars ...any) []byte {
|
||||
func (mo *Mo) Append(b []byte, str string, vars ...interface{}) []byte {
|
||||
return mo.domain.Append(b, str, vars...)
|
||||
}
|
||||
|
||||
// GetN returns the translation for the given string and plural form
|
||||
func (mo *Mo) GetN(str, plural string, n int, vars ...any) string {
|
||||
func (mo *Mo) GetN(str, plural string, n int, vars ...interface{}) string {
|
||||
return mo.domain.GetN(str, plural, n, vars...)
|
||||
}
|
||||
|
||||
// AppendN appends a translation string for the given plural form into the domain
|
||||
func (mo *Mo) AppendN(b []byte, str, plural string, n int, vars ...any) []byte {
|
||||
func (mo *Mo) AppendN(b []byte, str, plural string, n int, vars ...interface{}) []byte {
|
||||
return mo.domain.AppendN(b, str, plural, n, vars...)
|
||||
}
|
||||
|
||||
// GetC returns the translation for the given string and context
|
||||
func (mo *Mo) GetC(str, ctx string, vars ...any) string {
|
||||
func (mo *Mo) GetC(str, ctx string, vars ...interface{}) string {
|
||||
return mo.domain.GetC(str, ctx, vars...)
|
||||
}
|
||||
|
||||
// AppendC appends a translation string for the given context into the domain
|
||||
func (mo *Mo) AppendC(b []byte, str, ctx string, vars ...any) []byte {
|
||||
func (mo *Mo) AppendC(b []byte, str, ctx string, vars ...interface{}) []byte {
|
||||
return mo.domain.AppendC(b, str, ctx, vars...)
|
||||
}
|
||||
|
||||
// GetNC returns the translation for the given string, plural form and context
|
||||
func (mo *Mo) GetNC(str, plural string, n int, ctx string, vars ...any) string {
|
||||
func (mo *Mo) GetNC(str, plural string, n int, ctx string, vars ...interface{}) string {
|
||||
return mo.domain.GetNC(str, plural, n, ctx, vars...)
|
||||
}
|
||||
|
||||
// AppendNC appends a translation string for the given plural form and context into the domain
|
||||
func (mo *Mo) AppendNC(b []byte, str, plural string, n int, ctx string, vars ...any) []byte {
|
||||
func (mo *Mo) AppendNC(b []byte, str, plural string, n int, ctx string, vars ...interface{}) []byte {
|
||||
return mo.domain.AppendNC(b, str, plural, n, ctx, vars...)
|
||||
}
|
||||
|
||||
@@ -163,18 +163,22 @@ func (mo *Mo) Parse(buf []byte) {
|
||||
defer mo.domain.trMutex.Unlock()
|
||||
defer mo.domain.pluralMutex.Unlock()
|
||||
|
||||
if len(buf) < 28 {
|
||||
return
|
||||
}
|
||||
r := bytes.NewReader(buf)
|
||||
|
||||
var magicNumber uint32
|
||||
if err := binary.Read(r, binary.LittleEndian, &magicNumber); err != nil {
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
var bo binary.ByteOrder
|
||||
switch binary.LittleEndian.Uint32(buf[:4]) {
|
||||
switch magicNumber {
|
||||
case MoMagicLittleEndian:
|
||||
bo = binary.LittleEndian
|
||||
case MoMagicBigEndian:
|
||||
bo = binary.BigEndian
|
||||
default:
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", "invalid magic number")
|
||||
}
|
||||
|
||||
var header struct {
|
||||
@@ -183,58 +187,82 @@ func (mo *Mo) Parse(buf []byte) {
|
||||
MsgIDCount uint32
|
||||
MsgIDOffset uint32
|
||||
MsgStrOffset uint32
|
||||
HashSize uint32
|
||||
HashOffset uint32
|
||||
}
|
||||
|
||||
bufLen := uint64(len(buf))
|
||||
header.MajorVersion = bo.Uint16(buf[4:6])
|
||||
header.MinorVersion = bo.Uint16(buf[6:8])
|
||||
header.MsgIDCount = bo.Uint32(buf[8:12])
|
||||
header.MsgIDOffset = bo.Uint32(buf[12:16])
|
||||
header.MsgStrOffset = bo.Uint32(buf[16:20])
|
||||
|
||||
if header.MajorVersion != 0 && header.MajorVersion != 1 {
|
||||
if err := binary.Read(r, bo, &header); err != nil {
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
if header.MinorVersion != 0 && header.MinorVersion != 1 {
|
||||
if v := header.MajorVersion; v != 0 && v != 1 {
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", "invalid version number")
|
||||
}
|
||||
if v := header.MinorVersion; v != 0 && v != 1 {
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", "invalid version number")
|
||||
}
|
||||
|
||||
directorySize := uint64(header.MsgIDCount) * 8
|
||||
if uint64(header.MsgIDOffset)+directorySize > bufLen ||
|
||||
uint64(header.MsgStrOffset)+directorySize > bufLen {
|
||||
msgIDStart := make([]uint32, header.MsgIDCount)
|
||||
msgIDLen := make([]uint32, header.MsgIDCount)
|
||||
if _, err := r.Seek(int64(header.MsgIDOffset), 0); err != nil {
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
|
||||
for i := uint32(0); i < header.MsgIDCount; i++ {
|
||||
msgIDEntry := int(uint64(header.MsgIDOffset) + uint64(i)*8)
|
||||
msgIDLen := bo.Uint32(buf[msgIDEntry:])
|
||||
msgIDStart := bo.Uint32(buf[msgIDEntry+4:])
|
||||
if uint64(msgIDStart) > bufLen ||
|
||||
uint64(msgIDLen) > bufLen-uint64(msgIDStart) {
|
||||
for i := 0; i < int(header.MsgIDCount); i++ {
|
||||
if err := binary.Read(r, bo, &msgIDLen[i]); err != nil {
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
|
||||
msgStrEntry := int(uint64(header.MsgStrOffset) + uint64(i)*8)
|
||||
msgStrLen := bo.Uint32(buf[msgStrEntry:])
|
||||
msgStrStart := bo.Uint32(buf[msgStrEntry+4:])
|
||||
if uint64(msgStrStart) > bufLen ||
|
||||
uint64(msgStrLen) > bufLen-uint64(msgStrStart) {
|
||||
if err := binary.Read(r, bo, &msgIDStart[i]); err != nil {
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
for i := uint32(0); i < header.MsgIDCount; i++ {
|
||||
msgIDEntry := int(uint64(header.MsgIDOffset) + uint64(i)*8)
|
||||
msgIDLen := bo.Uint32(buf[msgIDEntry:])
|
||||
msgIDStart := bo.Uint32(buf[msgIDEntry+4:])
|
||||
msgIDEnd := int(uint64(msgIDStart) + uint64(msgIDLen))
|
||||
msgStrStart := make([]int32, header.MsgIDCount)
|
||||
msgStrLen := make([]int32, header.MsgIDCount)
|
||||
if _, err := r.Seek(int64(header.MsgStrOffset), 0); err != nil {
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
for i := 0; i < int(header.MsgIDCount); i++ {
|
||||
if err := binary.Read(r, bo, &msgStrLen[i]); err != nil {
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
if err := binary.Read(r, bo, &msgStrStart[i]); err != nil {
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
msgStrEntry := int(uint64(header.MsgStrOffset) + uint64(i)*8)
|
||||
msgStrLen := bo.Uint32(buf[msgStrEntry:])
|
||||
msgStrStart := bo.Uint32(buf[msgStrEntry+4:])
|
||||
msgStrEnd := int(uint64(msgStrStart) + uint64(msgStrLen))
|
||||
for i := 0; i < int(header.MsgIDCount); i++ {
|
||||
if _, err := r.Seek(int64(msgIDStart[i]), 0); err != nil {
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
msgIDData := make([]byte, msgIDLen[i])
|
||||
if _, err := r.Read(msgIDData); err != nil {
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
|
||||
mo.addTranslation(buf[int(msgIDStart):msgIDEnd], buf[int(msgStrStart):msgStrEnd])
|
||||
if _, err := r.Seek(int64(msgStrStart[i]), 0); err != nil {
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
msgStrData := make([]byte, msgStrLen[i])
|
||||
if _, err := r.Read(msgStrData); err != nil {
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
|
||||
if len(msgIDData) == 0 {
|
||||
mo.addTranslation(msgIDData, msgStrData)
|
||||
} else {
|
||||
mo.addTranslation(msgIDData, msgStrData)
|
||||
}
|
||||
}
|
||||
|
||||
// Parse headers
|
||||
@@ -251,32 +279,36 @@ func (mo *Mo) addTranslation(msgid, msgstr []byte) {
|
||||
translation := NewTranslation()
|
||||
var msgctxt []byte
|
||||
var msgidPlural []byte
|
||||
hasContext := false
|
||||
|
||||
if eotIndex := bytes.IndexByte(msgid, EotSeparator[0]); eotIndex >= 0 {
|
||||
hasContext = true
|
||||
msgctxt = msgid[:eotIndex]
|
||||
msgid = msgid[eotIndex+len(EotSeparator):]
|
||||
d := bytes.Split(msgid, []byte(EotSeparator))
|
||||
if len(d) == 1 {
|
||||
msgid = d[0]
|
||||
} else {
|
||||
msgid, msgctxt = d[1], d[0]
|
||||
}
|
||||
|
||||
if nulIndex := bytes.IndexByte(msgid, NulSeparator[0]); nulIndex >= 0 {
|
||||
msgidPlural = msgid[nulIndex+1:]
|
||||
msgid = msgid[:nulIndex]
|
||||
dd := bytes.Split(msgid, []byte(NulSeparator))
|
||||
if len(dd) > 1 {
|
||||
msgid = dd[0]
|
||||
dd = dd[1:]
|
||||
}
|
||||
|
||||
translation.ID = string(msgid)
|
||||
|
||||
msgidPlural = bytes.Join(dd, []byte(NulSeparator))
|
||||
if len(msgidPlural) > 0 {
|
||||
translation.PluralID = string(msgidPlural)
|
||||
}
|
||||
|
||||
i := 0
|
||||
for s := range bytes.SplitSeq(msgstr, []byte(NulSeparator)) {
|
||||
translation.Trs[i] = string(s)
|
||||
i++
|
||||
ddd := bytes.Split(msgstr, []byte(NulSeparator))
|
||||
if len(ddd) > 0 {
|
||||
for i, s := range ddd {
|
||||
translation.Trs[i] = string(s)
|
||||
}
|
||||
}
|
||||
|
||||
if hasContext {
|
||||
// With context, including an explicitly empty context...
|
||||
if len(msgctxt) > 0 {
|
||||
// With context...
|
||||
if _, ok := mo.domain.contextTranslations[string(msgctxt)]; !ok {
|
||||
mo.domain.contextTranslations[string(msgctxt)] = make(map[string]*Translation)
|
||||
}
|
||||
|
||||
+182
-366
@@ -12,12 +12,18 @@ package plurals
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
type match struct {
|
||||
openPos int
|
||||
closePos int
|
||||
}
|
||||
|
||||
var pat = regexp.MustCompile(`(\?|:|\|\||&&|==|!=|>=|>|<=|<|%|\d+|n)`)
|
||||
|
||||
type testToken interface {
|
||||
compile(tokens []string) (test test, err error)
|
||||
}
|
||||
@@ -25,30 +31,30 @@ type testToken interface {
|
||||
type cmpTestBuilder func(val uint32, flipped bool) test
|
||||
type logicTestBuild func(left test, right test) test
|
||||
|
||||
func compileTernary(tokens []string, depth int) (expr Expression, err error) {
|
||||
if depth > maxParseDepth {
|
||||
return nil, errors.New("expression nesting is too deep")
|
||||
}
|
||||
var ternaryToken ternaryStruct
|
||||
|
||||
type ternaryStruct struct{}
|
||||
|
||||
func (ternaryStruct) compile(tokens []string) (expr Expression, err error) {
|
||||
main, err := splitTokens(tokens, "?")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return expr, err
|
||||
}
|
||||
test, err := compileTestDepth(strings.Join(main.Left, ""), depth+1)
|
||||
test, err := compileTest(strings.Join(main.Left, ""))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return expr, err
|
||||
}
|
||||
actions, err := splitTernaryTokens(main.Right)
|
||||
actions, err := splitTokens(main.Right, ":")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return expr, err
|
||||
}
|
||||
trueAction, err := compileExpressionDepth(strings.Join(actions.Left, ""), depth+1)
|
||||
trueAction, err := compileExpression(strings.Join(actions.Left, ""))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return expr, err
|
||||
}
|
||||
falseAction, err := compileExpressionDepth(strings.Join(actions.Right, ""), depth+1)
|
||||
falseAction, err := compileExpression(strings.Join(actions.Right, ""))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return expr, nil
|
||||
}
|
||||
return ternary{
|
||||
test: test,
|
||||
@@ -57,145 +63,93 @@ func compileTernary(tokens []string, depth int) (expr Expression, err error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
const maxParseDepth = 1024
|
||||
|
||||
func splitTernaryTokens(tokens []string) (s splitted, err error) {
|
||||
depth := 0
|
||||
for index, token := range tokens {
|
||||
switch token {
|
||||
case "?":
|
||||
depth++
|
||||
case ":":
|
||||
if depth == 0 {
|
||||
return splitted{
|
||||
Left: tokens[:index],
|
||||
Right: tokens[index+1:],
|
||||
}, nil
|
||||
}
|
||||
depth--
|
||||
}
|
||||
}
|
||||
return s, errors.New("':' not found for ternary expression")
|
||||
}
|
||||
|
||||
var constToken constValStruct
|
||||
|
||||
type constValStruct struct{}
|
||||
|
||||
func (constValStruct) compile(tokens []string) (expr Expression, err error) {
|
||||
if len(tokens) == 0 {
|
||||
return nil, errors.New("got nothing instead of constant")
|
||||
return expr, errors.New("got nothing instead of constant")
|
||||
}
|
||||
if len(tokens) != 1 {
|
||||
return nil, fmt.Errorf("invalid constant: %s", strings.Join(tokens, ""))
|
||||
return expr, fmt.Errorf("invalid constant: %s", strings.Join(tokens, ""))
|
||||
}
|
||||
i, err := strconv.Atoi(tokens[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return expr, err
|
||||
}
|
||||
return constValue{value: i}, nil
|
||||
}
|
||||
|
||||
func compileLogicTestDepth(tokens []string, sep string, builder logicTestBuild, depth int) (test test, err error) {
|
||||
if depth > maxParseDepth {
|
||||
return nil, errors.New("expression nesting is too deep")
|
||||
}
|
||||
func compileLogicTest(tokens []string, sep string, builder logicTestBuild) (test test, err error) {
|
||||
split, err := splitTokens(tokens, sep)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return test, err
|
||||
}
|
||||
left, err := compileTestDepth(strings.Join(split.Left, ""), depth+1)
|
||||
left, err := compileTest(strings.Join(split.Left, ""))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return test, err
|
||||
}
|
||||
right, err := compileTestDepth(strings.Join(split.Right, ""), depth+1)
|
||||
right, err := compileTest(strings.Join(split.Right, ""))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return test, err
|
||||
}
|
||||
return builder(left, right), nil
|
||||
}
|
||||
|
||||
var orToken orStruct
|
||||
|
||||
type orStruct struct{}
|
||||
|
||||
func (orStruct) compile(tokens []string) (test test, err error) {
|
||||
return compileLogicTest(tokens, "||", buildOr)
|
||||
}
|
||||
func buildOr(left test, right test) test {
|
||||
return or{left: left, right: right}
|
||||
}
|
||||
|
||||
var andToken andStruct
|
||||
|
||||
type andStruct struct{}
|
||||
|
||||
func (andStruct) compile(tokens []string) (test test, err error) {
|
||||
return compileLogicTest(tokens, "&&", buildAnd)
|
||||
}
|
||||
func buildAnd(left test, right test) test {
|
||||
return and{left: left, right: right}
|
||||
}
|
||||
|
||||
func unwrapSingleGroup(tokens []string) ([]string, error) {
|
||||
for len(tokens) == 1 && strings.HasPrefix(tokens[0], "(") {
|
||||
inner, err := tokenize(tokens[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(inner) == 1 && inner[0] == tokens[0] {
|
||||
break
|
||||
}
|
||||
tokens = inner
|
||||
}
|
||||
return tokens, nil
|
||||
}
|
||||
|
||||
func isSimpleN(tokens []string) bool {
|
||||
tokens, err := unwrapSingleGroup(tokens)
|
||||
if err != nil || len(tokens) != 1 {
|
||||
return false
|
||||
}
|
||||
return tokens[0] == "n"
|
||||
}
|
||||
|
||||
func parseLiteralTokens(tokens []string) (uint32, error) {
|
||||
tokens, err := unwrapSingleGroup(tokens)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len(tokens) != 1 {
|
||||
return 0, errors.New("expected a single integer")
|
||||
}
|
||||
return parseUint32(tokens[0])
|
||||
}
|
||||
|
||||
func containsSimpleN(tokens []string) bool {
|
||||
for _, token := range tokens {
|
||||
if isSimpleN([]string{token}) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func compileMod(tokens []string) (math math, err error) {
|
||||
split, err := splitTokens(tokens, "%")
|
||||
if err != nil {
|
||||
return math, err
|
||||
}
|
||||
if !isSimpleN(split.Left) {
|
||||
if len(split.Left) != 1 || split.Left[0] != "n" {
|
||||
return math, errors.New("modulus operation requires 'n' as left operand")
|
||||
}
|
||||
i, err := parseLiteralTokens(split.Right)
|
||||
if len(split.Right) != 1 {
|
||||
return math, errors.New("modulus operation requires simple integer as right operand")
|
||||
}
|
||||
i, err := parseUint32(split.Right[0])
|
||||
if err != nil {
|
||||
return math, err
|
||||
}
|
||||
if i == 0 {
|
||||
return math, errors.New("modulus operation requires a non-zero integer as right operand")
|
||||
}
|
||||
return mod{value: i}, nil
|
||||
return mod{value: uint32(i)}, nil
|
||||
}
|
||||
|
||||
func subPipe(modTokens []string, actionTokens []string, builder cmpTestBuilder, flipped bool) (test test, err error) {
|
||||
modifier, err := compileMod(modTokens)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return test, err
|
||||
}
|
||||
if len(actionTokens) == 0 {
|
||||
return nil, errors.New("can only get modulus of integer")
|
||||
if len(actionTokens) != 1 {
|
||||
return test, errors.New("can only get modulus of integer")
|
||||
}
|
||||
i, err := parseLiteralTokens(actionTokens)
|
||||
i, err := parseUint32(actionTokens[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return test, err
|
||||
}
|
||||
action := builder(i, flipped)
|
||||
action := builder(uint32(i), flipped)
|
||||
return pipe{
|
||||
modifier: modifier,
|
||||
action: action,
|
||||
@@ -205,37 +159,31 @@ func subPipe(modTokens []string, actionTokens []string, builder cmpTestBuilder,
|
||||
func compileEquality(tokens []string, sep string, builder cmpTestBuilder) (test test, err error) {
|
||||
split, err := splitTokens(tokens, sep)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return test, err
|
||||
}
|
||||
left, err := unwrapSingleGroup(split.Left)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
right, err := unwrapSingleGroup(split.Right)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if isSimpleN(left) {
|
||||
i, err := parseLiteralTokens(right)
|
||||
if len(split.Left) == 1 && split.Left[0] == "n" {
|
||||
if len(split.Right) != 1 {
|
||||
return test, errors.New("test can only compare n to integers")
|
||||
}
|
||||
i, err := parseUint32(split.Right[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return test, err
|
||||
}
|
||||
return builder(i, false), nil
|
||||
}
|
||||
if isSimpleN(right) {
|
||||
i, err := parseLiteralTokens(left)
|
||||
} else if len(split.Right) == 1 && split.Right[0] == "n" {
|
||||
if len(split.Left) != 1 {
|
||||
return test, errors.New("test can only compare n to integers")
|
||||
}
|
||||
i, err := parseUint32(split.Left[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return test, err
|
||||
}
|
||||
return builder(i, true), nil
|
||||
} else if contains(split.Left, "n") && contains(split.Left, "%") {
|
||||
return subPipe(split.Left, split.Right, builder, false)
|
||||
}
|
||||
if containsSimpleN(left) && slices.Contains(left, "%") {
|
||||
return subPipe(left, right, builder, false)
|
||||
}
|
||||
if containsSimpleN(right) && slices.Contains(right, "%") {
|
||||
return subPipe(right, left, builder, true)
|
||||
}
|
||||
return nil, errors.New("equality test must have 'n' as one of the two tests")
|
||||
return test, errors.New("equality test must have 'n' as one of the two tests")
|
||||
|
||||
}
|
||||
|
||||
var eqToken eqStruct
|
||||
@@ -310,8 +258,8 @@ type testTokenDef struct {
|
||||
}
|
||||
|
||||
var precedence = []testTokenDef{
|
||||
{op: "||"},
|
||||
{op: "&&"},
|
||||
{op: "||", token: orToken},
|
||||
{op: "&&", token: andToken},
|
||||
{op: "==", token: eqToken},
|
||||
{op: "!=", token: neqToken},
|
||||
{op: ">=", token: gteToken},
|
||||
@@ -325,10 +273,20 @@ type splitted struct {
|
||||
Right []string
|
||||
}
|
||||
|
||||
// Find index of token in list of tokens
|
||||
func index(tokens []string, sep string) int {
|
||||
for index, token := range tokens {
|
||||
if token == sep {
|
||||
return index
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// Split a list of tokens by a token into a splitted struct holding the tokens
|
||||
// before and after the token to be split by.
|
||||
func splitTokens(tokens []string, sep string) (s splitted, err error) {
|
||||
index := slices.Index(tokens, sep)
|
||||
index := index(tokens, sep)
|
||||
if index == -1 {
|
||||
return s, fmt.Errorf("'%s' not found in ['%s']", sep, strings.Join(tokens, "','"))
|
||||
}
|
||||
@@ -338,277 +296,135 @@ func splitTokens(tokens []string, sep string) (s splitted, err error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func trimLexicalWhitespace(s string) string {
|
||||
return strings.Map(func(r rune) rune {
|
||||
if unicode.IsSpace(r) {
|
||||
return -1
|
||||
// Scan a string for parenthesis
|
||||
func scan(s string) <-chan match {
|
||||
ch := make(chan match)
|
||||
go func() {
|
||||
depth := 0
|
||||
opener := 0
|
||||
for index, char := range s {
|
||||
switch char {
|
||||
case '(':
|
||||
if depth == 0 {
|
||||
opener = index
|
||||
}
|
||||
depth++
|
||||
case ')':
|
||||
depth--
|
||||
if depth == 0 {
|
||||
ch <- match{
|
||||
openPos: opener,
|
||||
closePos: index + 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return r
|
||||
}, s)
|
||||
close(ch)
|
||||
}()
|
||||
return ch
|
||||
}
|
||||
|
||||
func lexicalToken(s string, index int) (token string, next int, err error) {
|
||||
if index >= len(s) {
|
||||
return "", index, errors.New("unexpected end of expression")
|
||||
}
|
||||
|
||||
switch s[index] {
|
||||
case '?', ':', '%', '(', ')':
|
||||
return s[index : index+1], index + 1, nil
|
||||
case '|':
|
||||
if index+1 >= len(s) || s[index+1] != '|' {
|
||||
return "", index, errors.New("logical OR must use '||'")
|
||||
}
|
||||
return "||", index + 2, nil
|
||||
case '&':
|
||||
if index+1 >= len(s) || s[index+1] != '&' {
|
||||
return "", index, errors.New("logical AND must use '&&'")
|
||||
}
|
||||
return "&&", index + 2, nil
|
||||
case '=':
|
||||
if index+1 >= len(s) || s[index+1] != '=' {
|
||||
return "", index, errors.New("equality must use '=='")
|
||||
}
|
||||
return "==", index + 2, nil
|
||||
case '!':
|
||||
if index+1 >= len(s) || s[index+1] != '=' {
|
||||
return "", index, errors.New("inequality must use '!='")
|
||||
}
|
||||
return "!=", index + 2, nil
|
||||
case '>':
|
||||
if index+1 < len(s) && s[index+1] == '=' {
|
||||
return ">=", index + 2, nil
|
||||
}
|
||||
return ">", index + 1, nil
|
||||
case '<':
|
||||
if index+1 < len(s) && s[index+1] == '=' {
|
||||
return "<=", index + 2, nil
|
||||
}
|
||||
return "<", index + 1, nil
|
||||
case 'n':
|
||||
return "n", index + 1, nil
|
||||
default:
|
||||
if s[index] >= '0' && s[index] <= '9' {
|
||||
next = index + 1
|
||||
for next < len(s) && s[next] >= '0' && s[next] <= '9' {
|
||||
next++
|
||||
// Split the string into tokens
|
||||
func split(s string) <-chan string {
|
||||
ch := make(chan string)
|
||||
go func() {
|
||||
s = strings.ReplaceAll(s, " ", "")
|
||||
if !strings.Contains(s, "(") {
|
||||
ch <- s
|
||||
} else {
|
||||
last := 0
|
||||
end := len(s)
|
||||
for info := range scan(s) {
|
||||
if last != info.openPos {
|
||||
ch <- s[last:info.openPos]
|
||||
}
|
||||
ch <- s[info.openPos:info.closePos]
|
||||
last = info.closePos
|
||||
}
|
||||
return s[index:next], next, nil
|
||||
}
|
||||
return "", index, fmt.Errorf("invalid character %q in expression", s[index])
|
||||
}
|
||||
}
|
||||
|
||||
func validateParentheses(s string) error {
|
||||
depth := 0
|
||||
for index := 0; index < len(s); {
|
||||
token, next, err := lexicalToken(s, index)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch token {
|
||||
case "(":
|
||||
depth++
|
||||
case ")":
|
||||
depth--
|
||||
if depth < 0 {
|
||||
return errors.New("unmatched closing parenthesis")
|
||||
if last != end {
|
||||
ch <- s[last:]
|
||||
}
|
||||
}
|
||||
index = next
|
||||
}
|
||||
if depth != 0 {
|
||||
return errors.New("unmatched opening parenthesis")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func stripRedundantOuterParens(s string) string {
|
||||
if len(s) < 2 || s[0] != '(' {
|
||||
return s
|
||||
}
|
||||
|
||||
leading := 0
|
||||
for leading < len(s) && s[leading] == '(' {
|
||||
leading++
|
||||
}
|
||||
|
||||
openers := make([]int, 0, leading)
|
||||
outer := leading
|
||||
for index := range s {
|
||||
switch s[index] {
|
||||
case '(':
|
||||
openers = append(openers, index)
|
||||
case ')':
|
||||
if len(openers) == 0 {
|
||||
return s
|
||||
}
|
||||
opener := openers[len(openers)-1]
|
||||
openers = openers[:len(openers)-1]
|
||||
if opener < outer && index != len(s)-1-opener {
|
||||
outer = opener
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(openers) != 0 {
|
||||
return s
|
||||
}
|
||||
return s[outer : len(s)-outer]
|
||||
close(ch)
|
||||
}()
|
||||
return ch
|
||||
}
|
||||
|
||||
// Tokenizes a string into a list of strings, tokens grouped by parenthesis are
|
||||
// not split! If the string starts with ( and ends in ), those are stripped.
|
||||
func tokenize(s string) ([]string, error) {
|
||||
s = trimLexicalWhitespace(s)
|
||||
if s == "" {
|
||||
return []string{}, nil
|
||||
}
|
||||
if err := validateParentheses(s); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s = stripRedundantOuterParens(s)
|
||||
if s == "" {
|
||||
return []string{}, nil
|
||||
}
|
||||
func tokenize(s string) []string {
|
||||
/*
|
||||
TODO: Properly detect if the string starts with a ( and ends with a )
|
||||
and that those two form a matching pair.
|
||||
|
||||
ret := []string{}
|
||||
for index := 0; index < len(s); {
|
||||
if s[index] == '(' {
|
||||
start := index
|
||||
depth := 0
|
||||
groupEnd := false
|
||||
for index < len(s) {
|
||||
switch s[index] {
|
||||
case '(':
|
||||
depth++
|
||||
case ')':
|
||||
depth--
|
||||
if depth == 0 {
|
||||
index++
|
||||
ret = append(ret, s[start:index])
|
||||
groupEnd = true
|
||||
}
|
||||
}
|
||||
if groupEnd {
|
||||
break
|
||||
}
|
||||
index++
|
||||
}
|
||||
if depth != 0 {
|
||||
return nil, errors.New("unmatched opening parenthesis")
|
||||
}
|
||||
continue
|
||||
}
|
||||
token, next, err := lexicalToken(s, index)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if token == ")" {
|
||||
return nil, errors.New("unmatched closing parenthesis")
|
||||
}
|
||||
ret = append(ret, token)
|
||||
index = next
|
||||
Eg: (foo) -> true; (foo)(bar) -> false;
|
||||
*/
|
||||
if len(s) == 0 {
|
||||
return []string{}
|
||||
}
|
||||
return ret, nil
|
||||
if s[0] == '(' && s[len(s)-1] == ')' {
|
||||
s = s[1 : len(s)-1]
|
||||
}
|
||||
ret := []string{}
|
||||
for chunk := range split(s) {
|
||||
if len(chunk) != 0 {
|
||||
if chunk[0] == '(' && chunk[len(chunk)-1] == ')' {
|
||||
ret = append(ret, chunk)
|
||||
} else {
|
||||
for _, token := range pat.FindAllStringSubmatch(chunk, -1) {
|
||||
ret = append(ret, token[0])
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("Empty chunk in string '%s'\n", s)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// Compile a string containing a plural form expression to a Expression object.
|
||||
func Compile(s string) (expr Expression, err error) {
|
||||
s = trimLexicalWhitespace(s)
|
||||
if s == "" {
|
||||
return nil, errors.New("empty expression")
|
||||
if s == "0" {
|
||||
return constValue{value: 0}, nil
|
||||
}
|
||||
return compileExpressionDepth(s, 0)
|
||||
if !strings.Contains(s, "?") {
|
||||
s += "?1:0"
|
||||
}
|
||||
return compileExpression(s)
|
||||
}
|
||||
|
||||
// Compiles an expression (ternary, numeric leaf, or test).
|
||||
func compileExpressionDepth(s string, depth int) (expr Expression, err error) {
|
||||
if depth > maxParseDepth {
|
||||
return nil, errors.New("expression nesting is too deep")
|
||||
}
|
||||
tokens, err := tokenize(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if slices.Contains(tokens, "?") {
|
||||
return compileTernary(tokens, depth)
|
||||
}
|
||||
for _, tokenDef := range precedence {
|
||||
if !slices.Contains(tokens, tokenDef.op) {
|
||||
continue
|
||||
// Check if a token is in a slice of strings
|
||||
func contains(haystack []string, needle string) bool {
|
||||
for _, s := range haystack {
|
||||
if s == needle {
|
||||
return true
|
||||
}
|
||||
condition, err := compileTestDepth(s, depth+1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return testValue{condition: condition}, nil
|
||||
}
|
||||
if isSimpleN(tokens) {
|
||||
return variableValue{}, nil
|
||||
}
|
||||
if slices.Contains(tokens, "%") {
|
||||
modifier, err := compileMod(tokens)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return mathValue{value: modifier}, nil
|
||||
return false
|
||||
}
|
||||
|
||||
// Compiles an expression (ternary or constant)
|
||||
func compileExpression(s string) (expr Expression, err error) {
|
||||
tokens := tokenize(s)
|
||||
if contains(tokens, "?") {
|
||||
return ternaryToken.compile(tokens)
|
||||
}
|
||||
return constToken.compile(tokens)
|
||||
}
|
||||
|
||||
// Compiles a test (comparison)
|
||||
func compileTestDepth(s string, depth int) (test test, err error) {
|
||||
if depth > maxParseDepth {
|
||||
return nil, errors.New("expression nesting is too deep")
|
||||
}
|
||||
tokens, err := tokenize(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
func compileTest(s string) (test test, err error) {
|
||||
tokens := tokenize(s)
|
||||
for _, tokenDef := range precedence {
|
||||
if !slices.Contains(tokens, tokenDef.op) {
|
||||
continue
|
||||
}
|
||||
switch tokenDef.op {
|
||||
case "||":
|
||||
return compileLogicTestDepth(tokens, "||", buildOr, depth)
|
||||
case "&&":
|
||||
return compileLogicTestDepth(tokens, "&&", buildAnd, depth)
|
||||
default:
|
||||
if contains(tokens, tokenDef.op) {
|
||||
return tokenDef.token.compile(tokens)
|
||||
}
|
||||
}
|
||||
if slices.Contains(tokens, "%") {
|
||||
m, err := compileMod(tokens)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pipe{
|
||||
modifier: m,
|
||||
action: notequal{value: 0},
|
||||
}, nil
|
||||
}
|
||||
if len(tokens) == 1 && strings.HasPrefix(tokens[0], "(") {
|
||||
inner, err := tokenize(tokens[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(inner) == 1 && inner[0] == tokens[0] {
|
||||
return nil, errors.New("cannot compile parenthesized test")
|
||||
}
|
||||
return compileTestDepth(tokens[0], depth+1)
|
||||
}
|
||||
if len(tokens) == 1 && tokens[0] == "n" {
|
||||
return notequal{value: 0}, nil
|
||||
}
|
||||
if value, err := parseLiteralTokens(tokens); err == nil {
|
||||
return literalTest{value: value}, nil
|
||||
}
|
||||
return nil, errors.New("cannot compile")
|
||||
return test, errors.New("cannot compile")
|
||||
}
|
||||
|
||||
func parseUint32(s string) (ui uint32, err error) {
|
||||
s = trimLexicalWhitespace(s)
|
||||
i, err := strconv.ParseUint(s, 10, 32)
|
||||
if err != nil {
|
||||
return ui, err
|
||||
|
||||
-34
@@ -20,37 +20,6 @@ func (c constValue) Eval(n uint32) int {
|
||||
return c.value
|
||||
}
|
||||
|
||||
type variableValue struct{}
|
||||
|
||||
func (variableValue) Eval(n uint32) int {
|
||||
return int(n)
|
||||
}
|
||||
|
||||
type mathValue struct {
|
||||
value math
|
||||
}
|
||||
|
||||
func (m mathValue) Eval(n uint32) int {
|
||||
if m.value == nil {
|
||||
return -1
|
||||
}
|
||||
return int(m.value.calc(n))
|
||||
}
|
||||
|
||||
type testValue struct {
|
||||
condition test
|
||||
}
|
||||
|
||||
func (t testValue) Eval(n uint32) int {
|
||||
if t.condition == nil {
|
||||
return -1
|
||||
}
|
||||
if t.condition.test(n) {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type test interface {
|
||||
test(n uint32) bool
|
||||
}
|
||||
@@ -62,9 +31,6 @@ type ternary struct {
|
||||
}
|
||||
|
||||
func (t ternary) Eval(n uint32) int {
|
||||
if t.test == nil {
|
||||
return -1
|
||||
}
|
||||
if t.test.test(n) {
|
||||
if t.trueExpr == nil {
|
||||
return -1
|
||||
|
||||
-3
@@ -15,8 +15,5 @@ type mod struct {
|
||||
}
|
||||
|
||||
func (m mod) calc(n uint32) uint32 {
|
||||
if m.value == 0 {
|
||||
return 0
|
||||
}
|
||||
return n % m.value
|
||||
}
|
||||
|
||||
-17
@@ -22,14 +22,6 @@ func (e notequal) test(n uint32) bool {
|
||||
return n != e.value
|
||||
}
|
||||
|
||||
type literalTest struct {
|
||||
value uint32
|
||||
}
|
||||
|
||||
func (l literalTest) test(n uint32) bool {
|
||||
return l.value != 0
|
||||
}
|
||||
|
||||
type gt struct {
|
||||
value uint32
|
||||
flipped bool
|
||||
@@ -85,9 +77,6 @@ type and struct {
|
||||
}
|
||||
|
||||
func (e and) test(n uint32) bool {
|
||||
if e.left == nil || e.right == nil {
|
||||
return false
|
||||
}
|
||||
if !e.left.test(n) {
|
||||
return false
|
||||
}
|
||||
@@ -100,9 +89,6 @@ type or struct {
|
||||
}
|
||||
|
||||
func (e or) test(n uint32) bool {
|
||||
if e.left == nil || e.right == nil {
|
||||
return false
|
||||
}
|
||||
if e.left.test(n) {
|
||||
return true
|
||||
}
|
||||
@@ -115,8 +101,5 @@ type pipe struct {
|
||||
}
|
||||
|
||||
func (e pipe) test(n uint32) bool {
|
||||
if e.modifier == nil || e.action == nil {
|
||||
return false
|
||||
}
|
||||
return e.action.test(e.modifier.calc(n))
|
||||
}
|
||||
|
||||
+80
-307
@@ -42,10 +42,6 @@ type Po struct {
|
||||
|
||||
domain *Domain
|
||||
fs fs.FS
|
||||
|
||||
parseBufferHasID bool
|
||||
parseBufferHasPlural bool
|
||||
parseBufferHasContext bool
|
||||
}
|
||||
|
||||
type parseState int
|
||||
@@ -56,7 +52,6 @@ const (
|
||||
msgID
|
||||
msgIDPlural
|
||||
msgStr
|
||||
msgStrInvalid
|
||||
)
|
||||
|
||||
// NewPo should always be used to instantiate a new Po object
|
||||
@@ -99,7 +94,7 @@ func (po *Po) GetRefs(str string) []string {
|
||||
|
||||
// SetPluralResolver sets the plural resolver function
|
||||
func (po *Po) SetPluralResolver(f func(int) int) {
|
||||
po.domain.SetPluralResolver(f)
|
||||
po.domain.customPluralResolver = f
|
||||
}
|
||||
|
||||
// Set translation
|
||||
@@ -108,12 +103,12 @@ func (po *Po) Set(id, str string) {
|
||||
}
|
||||
|
||||
// Get translation
|
||||
func (po *Po) Get(str string, vars ...any) string {
|
||||
func (po *Po) Get(str string, vars ...interface{}) string {
|
||||
return po.domain.Get(str, vars...)
|
||||
}
|
||||
|
||||
// Append translation
|
||||
func (po *Po) Append(b []byte, str string, vars ...any) []byte {
|
||||
func (po *Po) Append(b []byte, str string, vars ...interface{}) []byte {
|
||||
return po.domain.Append(b, str, vars...)
|
||||
}
|
||||
|
||||
@@ -123,12 +118,12 @@ func (po *Po) SetN(id, plural string, n int, str string) {
|
||||
}
|
||||
|
||||
// GetN gets the plural translation
|
||||
func (po *Po) GetN(str, plural string, n int, vars ...any) string {
|
||||
func (po *Po) GetN(str, plural string, n int, vars ...interface{}) string {
|
||||
return po.domain.GetN(str, plural, n, vars...)
|
||||
}
|
||||
|
||||
// AppendN appends the plural translation
|
||||
func (po *Po) AppendN(b []byte, str, plural string, n int, vars ...any) []byte {
|
||||
func (po *Po) AppendN(b []byte, str, plural string, n int, vars ...interface{}) []byte {
|
||||
return po.domain.AppendN(b, str, plural, n, vars...)
|
||||
}
|
||||
|
||||
@@ -138,12 +133,12 @@ func (po *Po) SetC(id, ctx, str string) {
|
||||
}
|
||||
|
||||
// GetC gets the translation for a given context
|
||||
func (po *Po) GetC(str, ctx string, vars ...any) string {
|
||||
func (po *Po) GetC(str, ctx string, vars ...interface{}) string {
|
||||
return po.domain.GetC(str, ctx, vars...)
|
||||
}
|
||||
|
||||
// AppendC appends the translation for a given context
|
||||
func (po *Po) AppendC(b []byte, str, ctx string, vars ...any) []byte {
|
||||
func (po *Po) AppendC(b []byte, str, ctx string, vars ...interface{}) []byte {
|
||||
return po.domain.AppendC(b, str, ctx, vars...)
|
||||
}
|
||||
|
||||
@@ -153,12 +148,12 @@ func (po *Po) SetNC(id, plural, ctx string, n int, str string) {
|
||||
}
|
||||
|
||||
// GetNC gets the plural translation for a given context
|
||||
func (po *Po) GetNC(str, plural string, n int, ctx string, vars ...any) string {
|
||||
func (po *Po) GetNC(str, plural string, n int, ctx string, vars ...interface{}) string {
|
||||
return po.domain.GetNC(str, plural, n, ctx, vars...)
|
||||
}
|
||||
|
||||
// AppendNC appends the plural translation for a given context
|
||||
func (po *Po) AppendNC(b []byte, str, plural string, n int, ctx string, vars ...any) []byte {
|
||||
func (po *Po) AppendNC(b []byte, str, plural string, n int, ctx string, vars ...interface{}) []byte {
|
||||
return po.domain.AppendNC(b, str, plural, n, ctx, vars...)
|
||||
}
|
||||
|
||||
@@ -219,17 +214,16 @@ func (po *Po) Parse(buf []byte) {
|
||||
defer po.domain.trMutex.Unlock()
|
||||
defer po.domain.pluralMutex.Unlock()
|
||||
|
||||
// Get lines
|
||||
lines := strings.Split(string(buf), "\n")
|
||||
|
||||
// Init buffer
|
||||
po.domain.trBuffer = NewTranslation()
|
||||
po.domain.ctxBuffer = ""
|
||||
po.domain.refBuffer = ""
|
||||
po.parseBufferHasID = false
|
||||
po.parseBufferHasPlural = false
|
||||
po.parseBufferHasContext = false
|
||||
|
||||
state := head
|
||||
activeMsgStrIndex := 0
|
||||
for l := range strings.Lines(string(buf)) {
|
||||
for _, l := range lines {
|
||||
// Trim spaces
|
||||
l = strings.TrimSpace(l)
|
||||
|
||||
@@ -239,71 +233,44 @@ func (po *Po) Parse(buf []byte) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Multi line strings and headers
|
||||
if strings.HasPrefix(l, "\"") {
|
||||
if !po.parseString(l, state, activeMsgStrIndex) && state != head {
|
||||
state = msgStrInvalid
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Buffer context and continue
|
||||
if po.hasKeywordBoundary(l, "msgctxt") {
|
||||
if po.parseContext(l) {
|
||||
state = msgCtxt
|
||||
} else {
|
||||
state = msgStrInvalid
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Check for plural form
|
||||
if po.hasKeywordBoundary(l, "msgid_plural") {
|
||||
if state != msgID || !po.parsePluralID(l) {
|
||||
state = msgStrInvalid
|
||||
} else {
|
||||
state = msgIDPlural
|
||||
}
|
||||
if strings.HasPrefix(l, "msgctxt") {
|
||||
po.parseContext(l)
|
||||
state = msgCtxt
|
||||
continue
|
||||
}
|
||||
|
||||
// Buffer msgid and continue
|
||||
if po.hasKeywordBoundary(l, "msgid") {
|
||||
if po.parseID(l) {
|
||||
state = msgID
|
||||
} else {
|
||||
state = msgStrInvalid
|
||||
}
|
||||
if strings.HasPrefix(l, "msgid") && !strings.HasPrefix(l, "msgid_plural") {
|
||||
po.parseID(l)
|
||||
state = msgID
|
||||
continue
|
||||
}
|
||||
|
||||
// Check for plural form
|
||||
if strings.HasPrefix(l, "msgid_plural") {
|
||||
po.parsePluralID(l)
|
||||
po.domain.pluralTranslations[po.domain.trBuffer.PluralID] = po.domain.trBuffer
|
||||
state = msgIDPlural
|
||||
continue
|
||||
}
|
||||
|
||||
// Save Translation
|
||||
if po.hasKeywordBoundary(l, "msgstr") {
|
||||
if state != msgID && state != msgIDPlural && state != msgStr {
|
||||
activeMsgStrIndex = 0
|
||||
state = msgStrInvalid
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(l, "msgstr") {
|
||||
po.parseMessage(l)
|
||||
state = msgStr
|
||||
continue
|
||||
}
|
||||
|
||||
var ok bool
|
||||
activeMsgStrIndex, ok = po.parseMessage(l)
|
||||
if ok {
|
||||
state = msgStr
|
||||
} else {
|
||||
activeMsgStrIndex = 0
|
||||
state = msgStrInvalid
|
||||
}
|
||||
// Multi line strings and headers
|
||||
if strings.HasPrefix(l, "\"") && strings.HasSuffix(l, "\"") {
|
||||
po.parseString(l, state)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Save last Translation buffer, but do not synthesize an entry after an
|
||||
// invalid directive. Preserve the historical empty-input entry.
|
||||
if po.parseBufferHasID {
|
||||
po.saveBuffer()
|
||||
} else if len(buf) == 0 {
|
||||
po.domain.translations[""] = po.domain.trBuffer
|
||||
}
|
||||
// Save last Translation buffer.
|
||||
po.saveBuffer()
|
||||
|
||||
// Parse headers
|
||||
po.domain.parseHeaders()
|
||||
@@ -318,30 +285,22 @@ func (po *Po) Parse(buf []byte) {
|
||||
// saveBuffer takes the context and Translation buffers
|
||||
// and saves it on the translations collection
|
||||
func (po *Po) saveBuffer() {
|
||||
if !po.parseBufferHasID {
|
||||
return
|
||||
}
|
||||
|
||||
// Store a completed plural ID after all of its continuation lines have
|
||||
// been decoded.
|
||||
if po.parseBufferHasPlural {
|
||||
po.domain.pluralTranslations[po.domain.trBuffer.PluralID] = po.domain.trBuffer
|
||||
}
|
||||
|
||||
if po.parseBufferHasContext {
|
||||
// With no context...
|
||||
if po.domain.ctxBuffer == "" {
|
||||
po.domain.translations[po.domain.trBuffer.ID] = po.domain.trBuffer
|
||||
} else {
|
||||
// With context...
|
||||
if _, ok := po.domain.contextTranslations[po.domain.ctxBuffer]; !ok {
|
||||
po.domain.contextTranslations[po.domain.ctxBuffer] = make(map[string]*Translation)
|
||||
}
|
||||
po.domain.contextTranslations[po.domain.ctxBuffer][po.domain.trBuffer.ID] = po.domain.trBuffer
|
||||
// Context applies only to the record it prefixes, even when its ID is empty.
|
||||
po.domain.ctxBuffer = ""
|
||||
po.parseBufferHasContext = false
|
||||
} else {
|
||||
po.domain.translations[po.domain.trBuffer.ID] = po.domain.trBuffer
|
||||
|
||||
// Cleanup current context buffer if needed
|
||||
if po.domain.trBuffer.ID != "" {
|
||||
po.domain.ctxBuffer = ""
|
||||
}
|
||||
}
|
||||
|
||||
po.parseBufferHasID = false
|
||||
po.parseBufferHasPlural = false
|
||||
// Flush Translation buffer
|
||||
if po.domain.refBuffer == "" {
|
||||
po.domain.trBuffer = NewTranslation()
|
||||
@@ -369,106 +328,68 @@ func (po *Po) parseComment(l string, state parseState) {
|
||||
|
||||
// parseContext takes a line starting with "msgctxt",
|
||||
// saves the current Translation buffer and creates a new context.
|
||||
func (po *Po) parseContext(l string) bool {
|
||||
value, ok := po.parseQuotedDirective(l, "msgctxt")
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
func (po *Po) parseContext(l string) {
|
||||
// Save current Translation buffer.
|
||||
po.saveBuffer()
|
||||
|
||||
// Buffer context, including an explicitly empty context.
|
||||
po.domain.ctxBuffer = value
|
||||
po.parseBufferHasContext = true
|
||||
return true
|
||||
// Buffer context
|
||||
po.domain.ctxBuffer, _ = strconv.Unquote(strings.TrimSpace(strings.TrimPrefix(l, "msgctxt")))
|
||||
}
|
||||
|
||||
// parseID takes a line starting with "msgid",
|
||||
// saves the current Translation and creates a new msgid buffer.
|
||||
func (po *Po) parseID(l string) bool {
|
||||
value, ok := po.parseQuotedDirective(l, "msgid")
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
func (po *Po) parseID(l string) {
|
||||
// Save current Translation buffer.
|
||||
po.saveBuffer()
|
||||
|
||||
// Set id
|
||||
po.domain.trBuffer.ID = value
|
||||
po.parseBufferHasID = true
|
||||
return true
|
||||
po.domain.trBuffer.ID, _ = strconv.Unquote(strings.TrimSpace(strings.TrimPrefix(l, "msgid")))
|
||||
}
|
||||
|
||||
// parsePluralID saves the plural id buffer from a line starting with "msgid_plural"
|
||||
func (po *Po) parsePluralID(l string) bool {
|
||||
value, ok := po.parseQuotedDirective(l, "msgid_plural")
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
po.domain.trBuffer.PluralID = value
|
||||
po.parseBufferHasPlural = true
|
||||
return true
|
||||
func (po *Po) parsePluralID(l string) {
|
||||
po.domain.trBuffer.PluralID, _ = strconv.Unquote(strings.TrimSpace(strings.TrimPrefix(l, "msgid_plural")))
|
||||
}
|
||||
|
||||
// parseMessage takes a line starting with "msgstr" and saves it into the current buffer.
|
||||
func (po *Po) parseMessage(l string) (int, bool) {
|
||||
if !po.hasKeywordBoundary(l, "msgstr") {
|
||||
return 0, false
|
||||
}
|
||||
l = strings.TrimSpace(l[len("msgstr"):])
|
||||
func (po *Po) parseMessage(l string) {
|
||||
l = strings.TrimSpace(strings.TrimPrefix(l, "msgstr"))
|
||||
|
||||
// Check for indexed Translation forms
|
||||
if strings.HasPrefix(l, "[") {
|
||||
idx := strings.IndexByte(l, ']')
|
||||
if idx <= 1 || !isASCIIPluralIndex(l[1:idx]) {
|
||||
idx := strings.Index(l, "]")
|
||||
if idx == -1 {
|
||||
// Skip wrong index formatting
|
||||
return 0, false
|
||||
return
|
||||
}
|
||||
|
||||
// Parse index
|
||||
i, err := strconv.Atoi(l[1:idx])
|
||||
if err != nil {
|
||||
// Skip wrong index formatting
|
||||
return 0, false
|
||||
return
|
||||
}
|
||||
|
||||
// Parse Translation string
|
||||
clean, ok := decodePOQuotedString(strings.TrimSpace(l[idx+1:]))
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
po.domain.trBuffer.Trs[i] = clean
|
||||
po.domain.trBuffer.Trs[i], _ = strconv.Unquote(strings.TrimSpace(l[idx+1:]))
|
||||
|
||||
return i, true
|
||||
// Loop
|
||||
return
|
||||
}
|
||||
|
||||
// Save single Translation form under 0 index
|
||||
clean, ok := decodePOQuotedString(l)
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
po.domain.trBuffer.Trs[0] = clean
|
||||
return 0, true
|
||||
po.domain.trBuffer.Trs[0], _ = strconv.Unquote(l)
|
||||
}
|
||||
|
||||
// parseString takes a well formatted string without prefix
|
||||
// and creates headers or attach multi-line strings when corresponding
|
||||
func (po *Po) parseString(l string, state parseState, activeMsgStrIndex int) bool {
|
||||
if state == msgStrInvalid {
|
||||
return false
|
||||
}
|
||||
|
||||
clean, ok := decodePOQuotedString(l)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
func (po *Po) parseString(l string, state parseState) {
|
||||
clean, _ := strconv.Unquote(l)
|
||||
|
||||
switch state {
|
||||
case msgStr:
|
||||
// Append to active Translation form
|
||||
po.domain.trBuffer.Trs[activeMsgStrIndex] += clean
|
||||
// Append to last Translation found
|
||||
po.domain.trBuffer.Trs[len(po.domain.trBuffer.Trs)-1] += clean
|
||||
|
||||
case msgID:
|
||||
// Multiline msgid - Append to current id
|
||||
@@ -481,174 +402,26 @@ func (po *Po) parseString(l string, state parseState, activeMsgStrIndex int) boo
|
||||
case msgCtxt:
|
||||
// Multiline context - Append to current context
|
||||
po.domain.ctxBuffer += clean
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// isValidLine checks for line prefixes to detect valid syntax.
|
||||
func (po *Po) isValidLine(l string) bool {
|
||||
if strings.HasPrefix(l, "\"") {
|
||||
return true
|
||||
// Check prefix
|
||||
valid := []string{
|
||||
"\"",
|
||||
"msgctxt",
|
||||
"msgid",
|
||||
"msgid_plural",
|
||||
"msgstr",
|
||||
}
|
||||
|
||||
for _, keyword := range []string{
|
||||
"msgctxt",
|
||||
"msgid_plural",
|
||||
"msgid",
|
||||
"msgstr",
|
||||
} {
|
||||
if po.hasKeywordBoundary(l, keyword) {
|
||||
for _, v := range valid {
|
||||
if strings.HasPrefix(l, v) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (po *Po) hasKeywordBoundary(l, keyword string) bool {
|
||||
if !strings.HasPrefix(l, keyword) {
|
||||
return false
|
||||
}
|
||||
if len(l) == len(keyword) {
|
||||
return true
|
||||
}
|
||||
|
||||
next := l[len(keyword)]
|
||||
if keyword == "msgstr" && next == '[' {
|
||||
return true
|
||||
}
|
||||
return next == ' ' || next == '\t'
|
||||
}
|
||||
|
||||
func (po *Po) parseQuotedDirective(l, keyword string) (string, bool) {
|
||||
if !po.hasKeywordBoundary(l, keyword) {
|
||||
return "", false
|
||||
}
|
||||
|
||||
value, ok := decodePOQuotedString(strings.TrimSpace(l[len(keyword):]))
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
return value, true
|
||||
}
|
||||
|
||||
// decodePOQuotedString decodes a GNU PO double-quoted string.
|
||||
func decodePOQuotedString(source string) (string, bool) {
|
||||
if len(source) < 2 || source[0] != '"' {
|
||||
return "", false
|
||||
}
|
||||
|
||||
var decoded []byte
|
||||
for i := 1; i < len(source); {
|
||||
switch source[i] {
|
||||
case '"':
|
||||
if i != len(source)-1 {
|
||||
return "", false
|
||||
}
|
||||
if decoded == nil {
|
||||
return source[1:i], true
|
||||
}
|
||||
return string(decoded), true
|
||||
|
||||
case '\\':
|
||||
if decoded == nil {
|
||||
decoded = make([]byte, 0, len(source)-2)
|
||||
decoded = append(decoded, source[1:i]...)
|
||||
}
|
||||
i++
|
||||
if i >= len(source) {
|
||||
return "", false
|
||||
}
|
||||
|
||||
switch source[i] {
|
||||
case 'a':
|
||||
decoded = append(decoded, '\a')
|
||||
i++
|
||||
case 'b':
|
||||
decoded = append(decoded, '\b')
|
||||
i++
|
||||
case 'f':
|
||||
decoded = append(decoded, '\f')
|
||||
i++
|
||||
case 'n':
|
||||
decoded = append(decoded, '\n')
|
||||
i++
|
||||
case 'r':
|
||||
decoded = append(decoded, '\r')
|
||||
i++
|
||||
case 't':
|
||||
decoded = append(decoded, '\t')
|
||||
i++
|
||||
case 'v':
|
||||
decoded = append(decoded, '\v')
|
||||
i++
|
||||
case '\\':
|
||||
decoded = append(decoded, '\\')
|
||||
i++
|
||||
case '"':
|
||||
decoded = append(decoded, '"')
|
||||
i++
|
||||
case '0', '1', '2', '3', '4', '5', '6', '7':
|
||||
var value byte
|
||||
for digits := 0; i < len(source) && digits < 3; digits++ {
|
||||
if source[i] < '0' || source[i] > '7' {
|
||||
break
|
||||
}
|
||||
value = value<<3 | (source[i] - '0')
|
||||
i++
|
||||
}
|
||||
decoded = append(decoded, value)
|
||||
case 'x':
|
||||
i++
|
||||
if i >= len(source) || !isPOHexDigit(source[i]) {
|
||||
return "", false
|
||||
}
|
||||
var value byte
|
||||
for i < len(source) && isPOHexDigit(source[i]) {
|
||||
value = value<<4 | poHexValue(source[i])
|
||||
i++
|
||||
}
|
||||
decoded = append(decoded, value)
|
||||
default:
|
||||
return "", false
|
||||
}
|
||||
|
||||
default:
|
||||
if decoded != nil {
|
||||
decoded = append(decoded, source[i])
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
func isPOHexDigit(c byte) bool {
|
||||
return c >= '0' && c <= '9' ||
|
||||
c >= 'a' && c <= 'f' ||
|
||||
c >= 'A' && c <= 'F'
|
||||
}
|
||||
|
||||
func poHexValue(c byte) byte {
|
||||
switch {
|
||||
case c >= '0' && c <= '9':
|
||||
return c - '0'
|
||||
case c >= 'a' && c <= 'f':
|
||||
return c - 'a' + 10
|
||||
default:
|
||||
return c - 'A' + 10
|
||||
}
|
||||
}
|
||||
|
||||
func isASCIIPluralIndex(index string) bool {
|
||||
if index == "" {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < len(index); i++ {
|
||||
if index[i] < '0' || index[i] > '9' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
+14
-6
@@ -49,8 +49,11 @@ func (t *Translation) Set(str string) {
|
||||
|
||||
// Get returns the string of the translation
|
||||
func (t *Translation) Get() string {
|
||||
if tr := t.Trs[0]; tr != "" {
|
||||
return tr
|
||||
// Look for Translation index 0
|
||||
if _, ok := t.Trs[0]; ok {
|
||||
if t.Trs[0] != "" {
|
||||
return t.Trs[0]
|
||||
}
|
||||
}
|
||||
|
||||
// Return untranslated id by default
|
||||
@@ -65,8 +68,11 @@ func (t *Translation) SetN(n int, str string) {
|
||||
|
||||
// GetN returns the string of the plural translation
|
||||
func (t *Translation) GetN(n int) string {
|
||||
if tr := t.Trs[n]; tr != "" {
|
||||
return tr
|
||||
// Look for Translation index
|
||||
if _, ok := t.Trs[n]; ok {
|
||||
if t.Trs[n] != "" {
|
||||
return t.Trs[n]
|
||||
}
|
||||
}
|
||||
|
||||
// Return untranslated singular if corresponding
|
||||
@@ -80,10 +86,12 @@ func (t *Translation) GetN(n int) string {
|
||||
|
||||
// IsTranslated reports whether a string is translated
|
||||
func (t *Translation) IsTranslated() bool {
|
||||
return t.Trs[0] != ""
|
||||
tr, ok := t.Trs[0]
|
||||
return tr != "" && ok
|
||||
}
|
||||
|
||||
// IsTranslatedN reports whether a plural string is translated
|
||||
func (t *Translation) IsTranslatedN(n int) bool {
|
||||
return t.Trs[n] != ""
|
||||
tr, ok := t.Trs[n]
|
||||
return tr != "" && ok
|
||||
}
|
||||
|
||||
+14
-60
@@ -9,20 +9,18 @@ import (
|
||||
"errors"
|
||||
"io/fs"
|
||||
"os"
|
||||
|
||||
"github.com/leonelquinteros/gotext/plurals"
|
||||
)
|
||||
|
||||
// Translator interface is used by Locale and Po objects.
|
||||
// Translator interface is used by Locale and Po objects.Translator
|
||||
// It contains all methods needed to parse translation sources and obtain corresponding translations.
|
||||
// It includes encoding.BinaryMarshaler and encoding.BinaryUnmarshaler methods for Locale serialization.
|
||||
// Also implements gob.GobEncoder/gob.DobDecoder interfaces to allow serialization of Locale objects.
|
||||
type Translator interface {
|
||||
ParseFile(f string)
|
||||
Parse(buf []byte)
|
||||
Get(str string, vars ...any) string
|
||||
GetN(str, plural string, n int, vars ...any) string
|
||||
GetC(str, ctx string, vars ...any) string
|
||||
GetNC(str, plural string, n int, ctx string, vars ...any) string
|
||||
Get(str string, vars ...interface{}) string
|
||||
GetN(str, plural string, n int, vars ...interface{}) string
|
||||
GetC(str, ctx string, vars ...interface{}) string
|
||||
GetNC(str, plural string, n int, ctx string, vars ...interface{}) string
|
||||
|
||||
MarshalBinary() ([]byte, error)
|
||||
UnmarshalBinary([]byte) error
|
||||
@@ -33,10 +31,10 @@ type Translator interface {
|
||||
// It contains all methods needed to parse translation sources and to append entries to the object.
|
||||
type AppendTranslator interface {
|
||||
Translator
|
||||
Append(b []byte, str string, vars ...any) []byte
|
||||
AppendN(b []byte, str, plural string, n int, vars ...any) []byte
|
||||
AppendC(b []byte, str, ctx string, vars ...any) []byte
|
||||
AppendNC(b []byte, str, plural string, n int, ctx string, vars ...any) []byte
|
||||
Append(b []byte, str string, vars ...interface{}) []byte
|
||||
AppendN(b []byte, str, plural string, n int, vars ...interface{}) []byte
|
||||
AppendC(b []byte, str, ctx string, vars ...interface{}) []byte
|
||||
AppendNC(b []byte, str, plural string, n int, ctx string, vars ...interface{}) []byte
|
||||
}
|
||||
|
||||
// TranslatorEncoding is used as intermediary storage to encode Translator objects to Gob.
|
||||
@@ -65,58 +63,14 @@ type TranslatorEncoding struct {
|
||||
// deserialize into a Po-compatible object.
|
||||
func (te *TranslatorEncoding) GetTranslator() Translator {
|
||||
po := NewPo()
|
||||
if te == nil {
|
||||
return po
|
||||
}
|
||||
|
||||
headers := te.Headers
|
||||
if headers == nil {
|
||||
headers = make(HeaderMap)
|
||||
}
|
||||
translations := te.Translations
|
||||
if translations == nil {
|
||||
translations = make(map[string]*Translation)
|
||||
}
|
||||
for id, translation := range translations {
|
||||
if translation == nil {
|
||||
translation = NewTranslation()
|
||||
translation.ID = id
|
||||
translations[id] = translation
|
||||
}
|
||||
}
|
||||
contexts := te.Contexts
|
||||
if contexts == nil {
|
||||
contexts = make(map[string]map[string]*Translation)
|
||||
}
|
||||
for context, translationsForContext := range contexts {
|
||||
if translationsForContext == nil {
|
||||
contexts[context] = make(map[string]*Translation)
|
||||
continue
|
||||
}
|
||||
for id, translation := range translationsForContext {
|
||||
if translation == nil {
|
||||
translation = NewTranslation()
|
||||
translation.ID = id
|
||||
translationsForContext[id] = translation
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
po.domain.Headers = headers
|
||||
po.domain = NewDomain()
|
||||
po.domain.Headers = te.Headers
|
||||
po.domain.Language = te.Language
|
||||
po.domain.PluralForms = te.PluralForms
|
||||
po.domain.nplurals = te.Nplurals
|
||||
po.domain.plural = te.Plural
|
||||
po.domain.translations = translations
|
||||
po.domain.contextTranslations = contexts
|
||||
|
||||
if expr, err := plurals.Compile(te.Plural); err == nil {
|
||||
po.domain.pluralforms = expr
|
||||
}
|
||||
|
||||
po.Headers = po.domain.Headers
|
||||
po.Language = po.domain.Language
|
||||
po.PluralForms = po.domain.PluralForms
|
||||
po.domain.translations = te.Translations
|
||||
po.domain.contextTranslations = te.Contexts
|
||||
|
||||
return po
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.27@sha256:820153096fda4de6ac1b7194d12e416b7dd8f4e9a33d17e0345b111b945162d8
|
||||
FROM golang:1.27@sha256:eb37f58646a901dc7727cf448cae36daaefaba79de33b5058dab79aa4c04aefb
|
||||
|
||||
ENV GOOS=linux
|
||||
ENV GOARCH=arm
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.27@sha256:820153096fda4de6ac1b7194d12e416b7dd8f4e9a33d17e0345b111b945162d8
|
||||
FROM golang:1.27@sha256:eb37f58646a901dc7727cf448cae36daaefaba79de33b5058dab79aa4c04aefb
|
||||
|
||||
ENV GOOS=linux
|
||||
ENV GOARCH=arm64
|
||||
|
||||
+4
-23
@@ -1,35 +1,18 @@
|
||||
FUZZ_TIME ?= 1m
|
||||
FUZZ_FLAGS ?=
|
||||
|
||||
# The fuzz targets of each package, along with the tags needed to build them.
|
||||
FUZZ_TARGETS = ./test/:gofuzz .:sha1cd_asmtest
|
||||
|
||||
export CGO_ENABLED := 1
|
||||
|
||||
# The hashing tests run a second time with SHA-NI off, so that CPUs with it
|
||||
# also cover the AVX2 fallback that CPUs without it use.
|
||||
.PHONY: test
|
||||
test:
|
||||
go test -race -timeout 15s -tags sha1cd_asmtest ./...
|
||||
SHA1CD_TEST_NOSHANI=1 go test -race -timeout 15s -tags sha1cd_asmtest ./test/
|
||||
go test -race -timeout 15s ./...
|
||||
|
||||
.PHONY: bench
|
||||
bench:
|
||||
go test -benchmem -run=^$$ -bench ^Benchmark ./...
|
||||
|
||||
# go test only fuzzes a single target per invocation, so each one is run in
|
||||
# turn for FUZZ_TIME.
|
||||
.PHONY: fuzz
|
||||
fuzz:
|
||||
@set -e; for entry in $(FUZZ_TARGETS); do \
|
||||
pkg="$${entry%%:*}"; tags="$${entry##*:}"; \
|
||||
listed="$$(go test -tags "$$tags" -list '^Fuzz' "$$pkg")"; \
|
||||
for target in $$(echo "$$listed" | grep '^Fuzz'); do \
|
||||
echo "fuzzing $$target in $$pkg for $(FUZZ_TIME)"; \
|
||||
go test $(FUZZ_FLAGS) -tags "$$tags" -run '^$$' -fuzz "^$$target"'$$' \
|
||||
-fuzztime=$(FUZZ_TIME) "$$pkg"; \
|
||||
done; \
|
||||
done
|
||||
go test -tags gofuzz -fuzz=. -fuzztime=$(FUZZ_TIME) ./test/
|
||||
|
||||
# Cross build project in arm/v7.
|
||||
build-arm:
|
||||
@@ -41,11 +24,9 @@ build-arm64:
|
||||
docker build -t sha1cd-arm64 -f Dockerfile.arm64 .
|
||||
docker run --rm sha1cd-arm64
|
||||
|
||||
# Build with cgo disabled. Vetting every package (not just ./cgo) is what
|
||||
# catches the cgo and non-cgo builds drifting apart in their exported API.
|
||||
# Build with cgo disabled.
|
||||
build-nocgo:
|
||||
CGO_ENABLED=0 go build ./...
|
||||
CGO_ENABLED=0 go vet ./...
|
||||
CGO_ENABLED=0 go build ./cgo
|
||||
|
||||
# Run cross-compilation to assure supported architectures.
|
||||
cross-build: build-arm build-arm64 build-nocgo
|
||||
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
// Package cpu detects the CPU features that sha1cd dispatches on.
|
||||
//
|
||||
// It covers only what the assembly implementations need, which keeps
|
||||
// start up cheap and avoids an external dependency. Every flag is false
|
||||
// where a feature cannot be detected safely, which selects the generic
|
||||
// implementation.
|
||||
package cpu
|
||||
|
||||
// X86 holds the features of the current amd64 CPU. All flags are false on
|
||||
// other architectures.
|
||||
var X86 struct {
|
||||
// HasAVX and HasAVX2 are set only when the OS also preserves the YMM
|
||||
// state, and HasAVX512F only when it preserves the ZMM and opmask state.
|
||||
HasAVX bool
|
||||
HasAVX2 bool
|
||||
HasAVX512F bool
|
||||
HasBMI1 bool
|
||||
HasBMI2 bool
|
||||
HasSHA bool
|
||||
HasSSSE3 bool
|
||||
HasSSE41 bool
|
||||
}
|
||||
|
||||
// ARM64 holds the features of the current arm64 CPU. All flags are false on
|
||||
// other architectures.
|
||||
var ARM64 struct {
|
||||
HasSHA1 bool
|
||||
}
|
||||
-63
@@ -1,63 +0,0 @@
|
||||
//go:build !noasm && gc && amd64
|
||||
|
||||
package cpu
|
||||
|
||||
// cpuid and xgetbv are implemented in cpu_amd64.s.
|
||||
func cpuid(eaxArg, ecxArg uint32) (eax, ebx, ecx, edx uint32)
|
||||
func xgetbv() (eax, edx uint32)
|
||||
|
||||
func init() {
|
||||
const (
|
||||
// CPUID EAX=1: ECX
|
||||
ssse3 = 1 << 9
|
||||
sse41 = 1 << 19
|
||||
osxsave = 1 << 27
|
||||
avx = 1 << 28
|
||||
|
||||
// CPUID EAX=7, ECX=0: EBX
|
||||
bmi1 = 1 << 3
|
||||
avx2 = 1 << 5
|
||||
bmi2 = 1 << 8
|
||||
avx512f = 1 << 16
|
||||
sha = 1 << 29
|
||||
|
||||
// XCR0
|
||||
xmmState = 1 << 1
|
||||
ymmState = 1 << 2
|
||||
opmaskState = 1 << 5
|
||||
zmmHi256State = 1 << 6
|
||||
hi16ZMMState = 1 << 7
|
||||
zmmState = opmaskState | zmmHi256State | hi16ZMMState
|
||||
)
|
||||
|
||||
maxID, _, _, _ := cpuid(0, 0)
|
||||
if maxID < 1 {
|
||||
return
|
||||
}
|
||||
|
||||
_, _, ecx1, _ := cpuid(1, 0)
|
||||
X86.HasSSSE3 = ecx1&ssse3 != 0
|
||||
X86.HasSSE41 = ecx1&sse41 != 0
|
||||
|
||||
// VEX encoded instructions also need the OS to preserve the YMM state,
|
||||
// which XGETBV reports once OSXSAVE says it is available.
|
||||
// macOS enables the ZMM state lazily, so XCR0 may not report it and
|
||||
// AVX-512 is then left unused, which is safe.
|
||||
var osYMM, osZMM bool
|
||||
if ecx1&(osxsave|avx) == osxsave|avx {
|
||||
xcr0, _ := xgetbv()
|
||||
osYMM = xcr0&(xmmState|ymmState) == xmmState|ymmState
|
||||
osZMM = osYMM && xcr0&zmmState == zmmState
|
||||
}
|
||||
X86.HasAVX = osYMM
|
||||
|
||||
if maxID < 7 {
|
||||
return
|
||||
}
|
||||
_, ebx7, _, _ := cpuid(7, 0)
|
||||
X86.HasAVX2 = osYMM && ebx7&avx2 != 0
|
||||
X86.HasAVX512F = osZMM && ebx7&avx512f != 0
|
||||
X86.HasBMI1 = ebx7&bmi1 != 0
|
||||
X86.HasBMI2 = ebx7&bmi2 != 0
|
||||
X86.HasSHA = ebx7&sha != 0
|
||||
}
|
||||
-9
@@ -1,9 +0,0 @@
|
||||
//go:build !noasm && gc && arm64 && darwin
|
||||
|
||||
package cpu
|
||||
|
||||
// Every Apple arm64 chip implements the ARMv8 Cryptographic Extension. The Go
|
||||
// runtime makes the same assumption for crypto/sha1 on darwin/arm64.
|
||||
func init() {
|
||||
ARM64.HasSHA1 = true
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
//go:build !noasm && gc && arm64 && freebsd
|
||||
|
||||
package cpu
|
||||
|
||||
// getisar0 is implemented in cpu_arm64_freebsd.s.
|
||||
func getisar0() uint64
|
||||
|
||||
// FreeBSD emulates user space reads of ID_AA64ISAR0_EL1. Its SHA1 field, bits
|
||||
// [11:8], is non zero when the SHA1 instructions are implemented.
|
||||
func init() {
|
||||
ARM64.HasSHA1 = (getisar0()>>8)&0xf != 0
|
||||
}
|
||||
-9
@@ -1,9 +0,0 @@
|
||||
//go:build !noasm && gc && arm64 && freebsd
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
// func getisar0() uint64
|
||||
TEXT ·getisar0(SB), NOSPLIT, $0-8
|
||||
MRS ID_AA64ISAR0_EL1, R0
|
||||
MOVD R0, ret+0(FP)
|
||||
RET
|
||||
-29
@@ -1,29 +0,0 @@
|
||||
//go:build !noasm && gc && arm64 && linux
|
||||
|
||||
package cpu
|
||||
|
||||
import (
|
||||
"os"
|
||||
_ "unsafe" // for go:linkname
|
||||
)
|
||||
|
||||
// runtime_getAuxv returns the auxiliary vector the kernel passed to the
|
||||
// process. The runtime keeps it reachable for golang.org/x/sys/cpu and
|
||||
// others, see go.dev/issue/57336 and go.dev/issue/67401.
|
||||
//
|
||||
//go:linkname runtime_getAuxv runtime.getAuxv
|
||||
func runtime_getAuxv() []uintptr
|
||||
|
||||
// Linux, and so Android, reports the SHA1 instructions through HWCAP, as
|
||||
// not every kernel lets user space read the ID registers.
|
||||
func init() {
|
||||
hwcap, ok := hwcapFromAuxv(runtime_getAuxv())
|
||||
if !ok {
|
||||
// The procfs copy may not be readable in restricted environments,
|
||||
// in which case the generic implementation is used.
|
||||
if buf, err := os.ReadFile("/proc/self/auxv"); err == nil {
|
||||
hwcap, _ = hwcapFromProcAuxv(buf)
|
||||
}
|
||||
}
|
||||
ARM64.HasSHA1 = hwcap&hwcapSHA1 != 0
|
||||
}
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
//go:build !noasm && gc && arm64 && windows
|
||||
|
||||
package cpu
|
||||
|
||||
import "syscall"
|
||||
|
||||
// Windows reports the ARMv8 Cryptographic Extension, which includes the SHA1
|
||||
// instructions, through IsProcessorFeaturePresent.
|
||||
func init() {
|
||||
const _PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE = 30
|
||||
|
||||
proc := syscall.NewLazyDLL("kernel32.dll").NewProc("IsProcessorFeaturePresent")
|
||||
if proc.Find() != nil {
|
||||
return
|
||||
}
|
||||
ret, _, _ := proc.Call(_PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE)
|
||||
ARM64.HasSHA1 = ret != 0
|
||||
}
|
||||
-32
@@ -1,32 +0,0 @@
|
||||
package cpu
|
||||
|
||||
import "encoding/binary"
|
||||
|
||||
const (
|
||||
_AT_HWCAP = 16
|
||||
|
||||
// hwcapSHA1 is HWCAP_SHA1 on linux/arm64.
|
||||
hwcapSHA1 = 1 << 5
|
||||
)
|
||||
|
||||
// hwcapFromAuxv returns AT_HWCAP from an auxiliary vector of tag and value
|
||||
// pairs, and whether it was present.
|
||||
func hwcapFromAuxv(auxv []uintptr) (uint64, bool) {
|
||||
for i := 0; i+1 < len(auxv); i += 2 {
|
||||
if auxv[i] == _AT_HWCAP {
|
||||
return uint64(auxv[i+1]), true
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// hwcapFromProcAuxv does the same for the contents of /proc/self/auxv on a
|
||||
// 64-bit little endian system.
|
||||
func hwcapFromProcAuxv(buf []byte) (uint64, bool) {
|
||||
for ; len(buf) >= 16; buf = buf[16:] {
|
||||
if binary.LittleEndian.Uint64(buf) == _AT_HWCAP {
|
||||
return binary.LittleEndian.Uint64(buf[8:]), true
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
+18
-28
@@ -4,39 +4,30 @@
|
||||
package sha1cd
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
|
||||
"github.com/klauspost/cpuid/v2"
|
||||
shared "github.com/pjbgf/sha1cd/internal"
|
||||
"github.com/pjbgf/sha1cd/internal/cpu"
|
||||
"github.com/pjbgf/sha1cd/ubc"
|
||||
)
|
||||
|
||||
// hasSHANI reports whether blockAMD64 can run. It uses legacy SSE encodings
|
||||
// only, so it does not need AVX and also runs on the Goldmont, Goldmont Plus
|
||||
// and Tremont Atoms, which implement SHA-NI without AVX.
|
||||
var hasSHANI = cpu.X86.HasSHA && cpu.X86.HasSSSE3 && cpu.X86.HasSSE41
|
||||
var hasSHANI = (runtime.GOARCH == "amd64" &&
|
||||
cpuid.CPU.Supports(cpuid.AVX) &&
|
||||
cpuid.CPU.Supports(cpuid.SHA) &&
|
||||
cpuid.CPU.Supports(cpuid.SSE3) &&
|
||||
cpuid.CPU.Supports(cpuid.SSE4))
|
||||
|
||||
// blockAMD64 hashes a single chunk of p into the current state in h.
|
||||
// p must hold at least one whole chunk. Anything beyond the first chunk is
|
||||
// ignored, as the collision detection the caller runs afterwards inspects m1
|
||||
// and cs for one chunk only.
|
||||
// blockAMD64 hashes the message p into the current state in h.
|
||||
// Both m1 and cs are used to store intermediate results which are used by the collision detection logic.
|
||||
//
|
||||
//go:noescape
|
||||
func blockAMD64(h []uint32, p []byte, m1 []uint32, cs [][5]uint32)
|
||||
|
||||
func block(dig *digest, p []byte) {
|
||||
switch {
|
||||
case forceGeneric:
|
||||
blockGeneric(dig, p)
|
||||
case hasSHANI:
|
||||
blockSHANI(dig, p)
|
||||
case hasAVX2:
|
||||
blockAVX2(dig, p)
|
||||
default:
|
||||
if forceGeneric || !hasSHANI {
|
||||
blockGeneric(dig, p)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func blockSHANI(dig *digest, p []byte) {
|
||||
m1 := [shared.Rounds]uint32{}
|
||||
cs := [shared.PreStepState][shared.WordBuffers]uint32{}
|
||||
|
||||
@@ -46,15 +37,14 @@ func blockSHANI(dig *digest, p []byte) {
|
||||
chunk := p[:shared.Chunk]
|
||||
|
||||
blockAMD64(dig.h[:], chunk, m1[:], cs[:])
|
||||
// Assembly states need repair only when a disturbance vector survives.
|
||||
if mask := ubc.CalculateDvMask(&m1); mask != 0 {
|
||||
rectifyCompressionState(&m1, &cs)
|
||||
if checkCollision(&m1, &cs, &dig.h, mask) {
|
||||
dig.col = true
|
||||
rectifyCompressionState(&m1, &cs)
|
||||
|
||||
blockAMD64(dig.h[:], chunk, m1[:], cs[:])
|
||||
blockAMD64(dig.h[:], chunk, m1[:], cs[:])
|
||||
}
|
||||
col := checkCollision(&m1, &cs, &dig.h)
|
||||
if col {
|
||||
dig.col = true
|
||||
|
||||
blockAMD64(dig.h[:], chunk, m1[:], cs[:])
|
||||
blockAMD64(dig.h[:], chunk, m1[:], cs[:])
|
||||
}
|
||||
|
||||
p = p[shared.Chunk:]
|
||||
|
||||
+49
-44
@@ -12,30 +12,27 @@
|
||||
// - https://github.com/golang/go/blob/master/src/crypto/sha1/sha1block_amd64.s
|
||||
|
||||
// Reverse the dword order in abcd via PSHUFD then store the 16 bytes in one
|
||||
// move, instead of issuing four PEXTRD's that each go through the store port.
|
||||
// move, instead of issuing four VPEXTRD's that each go through the store port.
|
||||
#define LOADCS(abcd, e, index, target) \
|
||||
PSHUFD $0x1B, abcd, X8; \
|
||||
MOVOU X8, ((index*20)+0)(target); \
|
||||
VPSHUFD $0x1B, abcd, X8; \
|
||||
VMOVDQU X8, ((index*20)+0)(target); \
|
||||
MOVL e, ((index*20)+16)(target);
|
||||
|
||||
#define LOADM1(m1, index, target) \
|
||||
PSHUFD $0x1B, m1, X8; \
|
||||
MOVOU X8, ((index*16)+0)(target);
|
||||
VPSHUFD $0x1B, m1, X8; \
|
||||
VMOVDQU X8, ((index*16)+0)(target);
|
||||
|
||||
// func blockAMD64(h []uint32, p []byte, m1 []uint32, cs [][5]uint32)
|
||||
// Requires: SHA, SSE2, SSE4.1, SSSE3
|
||||
// Requires: AVX, SHA, SSE2, SSE4.1, SSSE3
|
||||
TEXT ·blockAMD64(SB), NOSPLIT, $80-96
|
||||
MOVQ h_base+0(FP), DI
|
||||
MOVQ p_base+24(FP), SI
|
||||
MOVQ p_len+32(FP), DX
|
||||
MOVQ m1_base+48(FP), R13
|
||||
MOVQ cs_base+72(FP), R15
|
||||
|
||||
// Truncate the length to whole chunks and skip the block if none is left.
|
||||
// The caller compresses exactly one chunk per call, so that the collision
|
||||
// detection it runs afterwards sees this chunk's m1 and cs.
|
||||
ANDQ $-64, DX
|
||||
JZ done
|
||||
CMPQ DX, $0x00
|
||||
JEQ done
|
||||
ADDQ SI, DX
|
||||
|
||||
// Allocate space on the stack for saving ABCD and E0, and align it to 16 bytes
|
||||
LEAQ 15(SP), AX
|
||||
@@ -45,51 +42,52 @@ TEXT ·blockAMD64(SB), NOSPLIT, $80-96
|
||||
|
||||
// Load initial hash state
|
||||
PINSRD $0x03, 16(DI), X5
|
||||
MOVOU (DI), X0
|
||||
VMOVDQU (DI), X0
|
||||
PAND upper_mask<>+0(SB), X5
|
||||
PSHUFD $0x1b, X0, X0
|
||||
MOVO shuffle_mask<>+0(SB), X7
|
||||
VMOVDQA shuffle_mask<>+0(SB), X7
|
||||
|
||||
loop:
|
||||
// Save ABCD and E working values
|
||||
MOVO X5, (AX)
|
||||
MOVO X0, 16(AX)
|
||||
VMOVDQA X5, (AX)
|
||||
VMOVDQA X0, 16(AX)
|
||||
|
||||
// LOAD CS 0
|
||||
PEXTRD $3, X5, R12
|
||||
VPEXTRD $3, X5, R12
|
||||
LOADCS(X0, R12, 0, R15)
|
||||
|
||||
// Rounds 0-3
|
||||
MOVOU (SI), X1
|
||||
VMOVDQU (SI), X1
|
||||
PSHUFB X7, X1
|
||||
PADDD X1, X5
|
||||
MOVO X0, X6
|
||||
VMOVDQA X0, X6
|
||||
SHA1RNDS4 $0x00, X5, X0
|
||||
LOADM1(X1, 0, R13)
|
||||
|
||||
// Rounds 4-7
|
||||
MOVOU 16(SI), X2
|
||||
VMOVDQU 16(SI), X2
|
||||
PSHUFB X7, X2
|
||||
SHA1NEXTE X2, X6
|
||||
MOVO X0, X5
|
||||
VMOVDQA X0, X5
|
||||
SHA1RNDS4 $0x00, X6, X0
|
||||
SHA1MSG1 X2, X1
|
||||
LOADM1(X2, 1, R13)
|
||||
|
||||
// Rounds 8-11
|
||||
MOVOU 32(SI), X3
|
||||
VMOVDQU 32(SI), X3
|
||||
PSHUFB X7, X3
|
||||
SHA1NEXTE X3, X5
|
||||
MOVO X0, X6
|
||||
VMOVDQA X0, X6
|
||||
SHA1RNDS4 $0x00, X5, X0
|
||||
SHA1MSG1 X3, X2
|
||||
PXOR X3, X1
|
||||
LOADM1(X3, 2, R13)
|
||||
|
||||
// Rounds 12-15
|
||||
MOVOU 48(SI), X4
|
||||
VMOVDQU 48(SI), X4
|
||||
PSHUFB X7, X4
|
||||
SHA1NEXTE X4, X6
|
||||
MOVO X0, X5
|
||||
VMOVDQA X0, X5
|
||||
SHA1MSG2 X4, X1
|
||||
SHA1RNDS4 $0x00, X6, X0
|
||||
SHA1MSG1 X4, X3
|
||||
@@ -98,7 +96,7 @@ TEXT ·blockAMD64(SB), NOSPLIT, $80-96
|
||||
|
||||
// Rounds 16-19
|
||||
SHA1NEXTE X1, X5
|
||||
MOVO X0, X6
|
||||
VMOVDQA X0, X6
|
||||
SHA1MSG2 X1, X2
|
||||
SHA1RNDS4 $0x00, X5, X0
|
||||
SHA1MSG1 X1, X4
|
||||
@@ -107,7 +105,7 @@ TEXT ·blockAMD64(SB), NOSPLIT, $80-96
|
||||
|
||||
// Rounds 20-23
|
||||
SHA1NEXTE X2, X6
|
||||
MOVO X0, X5
|
||||
VMOVDQA X0, X5
|
||||
SHA1MSG2 X2, X3
|
||||
SHA1RNDS4 $0x01, X6, X0
|
||||
SHA1MSG1 X2, X1
|
||||
@@ -116,7 +114,7 @@ TEXT ·blockAMD64(SB), NOSPLIT, $80-96
|
||||
|
||||
// Rounds 24-27
|
||||
SHA1NEXTE X3, X5
|
||||
MOVO X0, X6
|
||||
VMOVDQA X0, X6
|
||||
SHA1MSG2 X3, X4
|
||||
SHA1RNDS4 $0x01, X5, X0
|
||||
SHA1MSG1 X3, X2
|
||||
@@ -125,7 +123,7 @@ TEXT ·blockAMD64(SB), NOSPLIT, $80-96
|
||||
|
||||
// Rounds 28-31
|
||||
SHA1NEXTE X4, X6
|
||||
MOVO X0, X5
|
||||
VMOVDQA X0, X5
|
||||
SHA1MSG2 X4, X1
|
||||
SHA1RNDS4 $0x01, X6, X0
|
||||
SHA1MSG1 X4, X3
|
||||
@@ -134,7 +132,7 @@ TEXT ·blockAMD64(SB), NOSPLIT, $80-96
|
||||
|
||||
// Rounds 32-35
|
||||
SHA1NEXTE X1, X5
|
||||
MOVO X0, X6
|
||||
VMOVDQA X0, X6
|
||||
SHA1MSG2 X1, X2
|
||||
SHA1RNDS4 $0x01, X5, X0
|
||||
SHA1MSG1 X1, X4
|
||||
@@ -143,7 +141,7 @@ TEXT ·blockAMD64(SB), NOSPLIT, $80-96
|
||||
|
||||
// Rounds 36-39
|
||||
SHA1NEXTE X2, X6
|
||||
MOVO X0, X5
|
||||
VMOVDQA X0, X5
|
||||
SHA1MSG2 X2, X3
|
||||
SHA1RNDS4 $0x01, X6, X0
|
||||
SHA1MSG1 X2, X1
|
||||
@@ -152,7 +150,7 @@ TEXT ·blockAMD64(SB), NOSPLIT, $80-96
|
||||
|
||||
// Rounds 40-43
|
||||
SHA1NEXTE X3, X5
|
||||
MOVO X0, X6
|
||||
VMOVDQA X0, X6
|
||||
SHA1MSG2 X3, X4
|
||||
SHA1RNDS4 $0x02, X5, X0
|
||||
SHA1MSG1 X3, X2
|
||||
@@ -161,7 +159,7 @@ TEXT ·blockAMD64(SB), NOSPLIT, $80-96
|
||||
|
||||
// Rounds 44-47
|
||||
SHA1NEXTE X4, X6
|
||||
MOVO X0, X5
|
||||
VMOVDQA X0, X5
|
||||
SHA1MSG2 X4, X1
|
||||
SHA1RNDS4 $0x02, X6, X0
|
||||
SHA1MSG1 X4, X3
|
||||
@@ -170,20 +168,21 @@ TEXT ·blockAMD64(SB), NOSPLIT, $80-96
|
||||
|
||||
// Rounds 48-51
|
||||
SHA1NEXTE X1, X5
|
||||
MOVO X0, X6
|
||||
VMOVDQA X0, X6
|
||||
SHA1MSG2 X1, X2
|
||||
SHA1RNDS4 $0x02, X5, X0
|
||||
VPEXTRD $0, X5, R12
|
||||
SHA1MSG1 X1, X4
|
||||
PXOR X1, X3
|
||||
LOADM1(X1, 12, R13)
|
||||
|
||||
// derive pre-round 56's E out of round 51's A.
|
||||
PEXTRD $3, X0, R12
|
||||
VPEXTRD $3, X0, R12
|
||||
ROLL $30, R12
|
||||
|
||||
// Rounds 52-55
|
||||
SHA1NEXTE X2, X6
|
||||
MOVO X0, X5
|
||||
VMOVDQA X0, X5
|
||||
SHA1MSG2 X2, X3
|
||||
SHA1RNDS4 $0x02, X6, X0
|
||||
SHA1MSG1 X2, X1
|
||||
@@ -195,20 +194,21 @@ TEXT ·blockAMD64(SB), NOSPLIT, $80-96
|
||||
|
||||
// Rounds 56-59
|
||||
SHA1NEXTE X3, X5
|
||||
MOVO X0, X6
|
||||
VMOVDQA X0, X6
|
||||
SHA1MSG2 X3, X4
|
||||
SHA1RNDS4 $0x02, X5, X0
|
||||
VPEXTRD $0, X5, R12
|
||||
SHA1MSG1 X3, X2
|
||||
PXOR X3, X1
|
||||
LOADM1(X3, 14, R13)
|
||||
|
||||
// derive pre-round 64's E out of round 59's A.
|
||||
PEXTRD $3, X0, R12
|
||||
VPEXTRD $3, X0, R12
|
||||
ROLL $30, R12
|
||||
|
||||
// Rounds 60-63
|
||||
SHA1NEXTE X4, X6
|
||||
MOVO X0, X5
|
||||
VMOVDQA X0, X5
|
||||
SHA1MSG2 X4, X1
|
||||
SHA1RNDS4 $0x03, X6, X0
|
||||
SHA1MSG1 X4, X3
|
||||
@@ -220,7 +220,7 @@ TEXT ·blockAMD64(SB), NOSPLIT, $80-96
|
||||
|
||||
// Rounds 64-67
|
||||
SHA1NEXTE X1, X5
|
||||
MOVO X0, X6
|
||||
VMOVDQA X0, X6
|
||||
SHA1MSG2 X1, X2
|
||||
SHA1RNDS4 $0x03, X5, X0
|
||||
SHA1MSG1 X1, X4
|
||||
@@ -229,7 +229,7 @@ TEXT ·blockAMD64(SB), NOSPLIT, $80-96
|
||||
|
||||
// Rounds 68-71
|
||||
SHA1NEXTE X2, X6
|
||||
MOVO X0, X5
|
||||
VMOVDQA X0, X5
|
||||
SHA1MSG2 X2, X3
|
||||
SHA1RNDS4 $0x03, X6, X0
|
||||
PXOR X2, X4
|
||||
@@ -237,14 +237,14 @@ TEXT ·blockAMD64(SB), NOSPLIT, $80-96
|
||||
|
||||
// Rounds 72-75
|
||||
SHA1NEXTE X3, X5
|
||||
MOVO X0, X6
|
||||
VMOVDQA X0, X6
|
||||
SHA1MSG2 X3, X4
|
||||
SHA1RNDS4 $0x03, X5, X0
|
||||
LOADM1(X3, 18, R13)
|
||||
|
||||
// Rounds 76-79
|
||||
SHA1NEXTE X4, X6
|
||||
MOVO X0, X5
|
||||
VMOVDQA X0, X5
|
||||
SHA1RNDS4 $0x03, X6, X0
|
||||
LOADM1(X4, 19, R13)
|
||||
|
||||
@@ -252,9 +252,14 @@ TEXT ·blockAMD64(SB), NOSPLIT, $80-96
|
||||
SHA1NEXTE (AX), X5
|
||||
PADDD 16(AX), X0
|
||||
|
||||
// Check if we are done, if not return to the loop
|
||||
ADDQ $0x40, SI
|
||||
CMPQ SI, DX
|
||||
JNE loop
|
||||
|
||||
// Write the hash state back to digest
|
||||
PSHUFD $0x1b, X0, X0
|
||||
MOVOU X0, (DI)
|
||||
VMOVDQU X0, (DI)
|
||||
PEXTRD $0x03, X5, 16(DI)
|
||||
|
||||
done:
|
||||
|
||||
+11
-15
@@ -4,17 +4,15 @@
|
||||
package sha1cd
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
|
||||
"github.com/klauspost/cpuid/v2"
|
||||
shared "github.com/pjbgf/sha1cd/internal"
|
||||
"github.com/pjbgf/sha1cd/internal/cpu"
|
||||
"github.com/pjbgf/sha1cd/ubc"
|
||||
)
|
||||
|
||||
var hasSHA1 = cpu.ARM64.HasSHA1
|
||||
var hasSHA1 = (runtime.GOARCH == "arm64" && cpuid.CPU.Supports(cpuid.SHA1))
|
||||
|
||||
// blockARM64 hashes a single chunk of p into the current state in h.
|
||||
// p must hold at least one whole chunk. Anything beyond the first chunk is
|
||||
// ignored, as the collision detection the caller runs afterwards inspects m1
|
||||
// and cs for one chunk only.
|
||||
// blockARM64 hashes the message p into the current state in h.
|
||||
// Both m1 and cs are used to store intermediate results which are used by the collision detection logic.
|
||||
//
|
||||
//go:noescape
|
||||
@@ -36,15 +34,13 @@ func block(dig *digest, p []byte) {
|
||||
|
||||
blockARM64(dig.h[:], chunk, m1[:], cs[:])
|
||||
|
||||
// Assembly states need repair only when a disturbance vector survives.
|
||||
if mask := ubc.CalculateDvMask(&m1); mask != 0 {
|
||||
rectifyCompressionState(&m1, &cs)
|
||||
if checkCollision(&m1, &cs, &dig.h, mask) {
|
||||
dig.col = true
|
||||
rectifyCompressionState(&m1, &cs)
|
||||
col := checkCollision(&m1, &cs, &dig.h)
|
||||
if col {
|
||||
dig.col = true
|
||||
|
||||
blockARM64(dig.h[:], chunk, m1[:], cs[:])
|
||||
blockARM64(dig.h[:], chunk, m1[:], cs[:])
|
||||
}
|
||||
blockARM64(dig.h[:], chunk, m1[:], cs[:])
|
||||
blockARM64(dig.h[:], chunk, m1[:], cs[:])
|
||||
}
|
||||
|
||||
p = p[shared.Chunk:]
|
||||
|
||||
+4
-2
@@ -37,13 +37,15 @@ TEXT ·blockARM64(SB), NOSPLIT, $80-96
|
||||
|
||||
LSR $6, R2, R2
|
||||
LSL $6, R2, R2
|
||||
ADD R16, R2, R21
|
||||
|
||||
VLD1.P 16(R0), [V0.S4]
|
||||
FMOVS (R0), F20
|
||||
SUB $16, R0, R0
|
||||
|
||||
// The caller passes exactly one block; skip hashing only if p is shorter.
|
||||
CBZ R2, end
|
||||
loop:
|
||||
CMP R16, R21
|
||||
BLS end
|
||||
|
||||
// Load block (p) into 16-bytes vectors.
|
||||
VLD1.P 16(R1), [V4.B16]
|
||||
|
||||
-100
@@ -1,100 +0,0 @@
|
||||
//go:build !noasm && gc && arm64 && !amd64 && sha1cd_asmtest
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
// callBlockARM64DirtyRegs calls blockARM64 with every general-purpose and
|
||||
// vector register the caller does not own set to all ones. It is only built
|
||||
// with the sha1cd_asmtest tag, to catch the assembly reading a register before
|
||||
// writing it.
|
||||
//
|
||||
// func callBlockARM64DirtyRegs(h []uint32, p []byte, m1 []uint32, cs [][5]uint32)
|
||||
TEXT ·callBlockARM64DirtyRegs(SB), NOSPLIT, $104-96
|
||||
MOVD h_base+0(FP), R0
|
||||
MOVD R0, 8(RSP)
|
||||
MOVD h_len+8(FP), R0
|
||||
MOVD R0, 16(RSP)
|
||||
MOVD h_cap+16(FP), R0
|
||||
MOVD R0, 24(RSP)
|
||||
MOVD p_base+24(FP), R0
|
||||
MOVD R0, 32(RSP)
|
||||
MOVD p_len+32(FP), R0
|
||||
MOVD R0, 40(RSP)
|
||||
MOVD p_cap+40(FP), R0
|
||||
MOVD R0, 48(RSP)
|
||||
MOVD m1_base+48(FP), R0
|
||||
MOVD R0, 56(RSP)
|
||||
MOVD m1_len+56(FP), R0
|
||||
MOVD R0, 64(RSP)
|
||||
MOVD m1_cap+64(FP), R0
|
||||
MOVD R0, 72(RSP)
|
||||
MOVD cs_base+72(FP), R0
|
||||
MOVD R0, 80(RSP)
|
||||
MOVD cs_len+80(FP), R0
|
||||
MOVD R0, 88(RSP)
|
||||
MOVD cs_cap+88(FP), R0
|
||||
MOVD R0, 96(RSP)
|
||||
|
||||
// R18 is reserved by the platform, R27 by the assembler, R28 holds g,
|
||||
// R29 is the frame pointer and R30 the link register.
|
||||
MOVD $-1, R0
|
||||
MOVD R0, R1
|
||||
MOVD R0, R2
|
||||
MOVD R0, R3
|
||||
MOVD R0, R4
|
||||
MOVD R0, R5
|
||||
MOVD R0, R6
|
||||
MOVD R0, R7
|
||||
MOVD R0, R8
|
||||
MOVD R0, R9
|
||||
MOVD R0, R10
|
||||
MOVD R0, R11
|
||||
MOVD R0, R12
|
||||
MOVD R0, R13
|
||||
MOVD R0, R14
|
||||
MOVD R0, R15
|
||||
MOVD R0, R16
|
||||
MOVD R0, R17
|
||||
MOVD R0, R19
|
||||
MOVD R0, R20
|
||||
MOVD R0, R21
|
||||
MOVD R0, R22
|
||||
MOVD R0, R23
|
||||
MOVD R0, R24
|
||||
MOVD R0, R25
|
||||
MOVD R0, R26
|
||||
|
||||
VMOVI $0xff, V0.B16
|
||||
VMOVI $0xff, V1.B16
|
||||
VMOVI $0xff, V2.B16
|
||||
VMOVI $0xff, V3.B16
|
||||
VMOVI $0xff, V4.B16
|
||||
VMOVI $0xff, V5.B16
|
||||
VMOVI $0xff, V6.B16
|
||||
VMOVI $0xff, V7.B16
|
||||
VMOVI $0xff, V8.B16
|
||||
VMOVI $0xff, V9.B16
|
||||
VMOVI $0xff, V10.B16
|
||||
VMOVI $0xff, V11.B16
|
||||
VMOVI $0xff, V12.B16
|
||||
VMOVI $0xff, V13.B16
|
||||
VMOVI $0xff, V14.B16
|
||||
VMOVI $0xff, V15.B16
|
||||
VMOVI $0xff, V16.B16
|
||||
VMOVI $0xff, V17.B16
|
||||
VMOVI $0xff, V18.B16
|
||||
VMOVI $0xff, V19.B16
|
||||
VMOVI $0xff, V20.B16
|
||||
VMOVI $0xff, V21.B16
|
||||
VMOVI $0xff, V22.B16
|
||||
VMOVI $0xff, V23.B16
|
||||
VMOVI $0xff, V24.B16
|
||||
VMOVI $0xff, V25.B16
|
||||
VMOVI $0xff, V26.B16
|
||||
VMOVI $0xff, V27.B16
|
||||
VMOVI $0xff, V28.B16
|
||||
VMOVI $0xff, V29.B16
|
||||
VMOVI $0xff, V30.B16
|
||||
VMOVI $0xff, V31.B16
|
||||
|
||||
BL ·blockARM64(SB)
|
||||
RET
|
||||
-55
@@ -1,55 +0,0 @@
|
||||
//go:build !noasm && gc && amd64
|
||||
|
||||
package sha1cd
|
||||
|
||||
import (
|
||||
shared "github.com/pjbgf/sha1cd/internal"
|
||||
"github.com/pjbgf/sha1cd/internal/cpu"
|
||||
"github.com/pjbgf/sha1cd/ubc"
|
||||
)
|
||||
|
||||
// hasAVX2 reports whether blockAVX2 can run. It is the fallback for CPUs
|
||||
// without SHA-NI, such as Intel's big cores before Ice Lake.
|
||||
var hasAVX2 = cpu.X86.HasAVX2 && cpu.X86.HasBMI1 && cpu.X86.HasBMI2
|
||||
|
||||
// scheduleAVX2 expands the message schedules of the blocks at pa and pb, which
|
||||
// may be the same, into m1a and m1b.
|
||||
//
|
||||
//go:noescape
|
||||
func scheduleAVX2(pa, pb *byte, m1a, m1b *[shared.Rounds]uint32)
|
||||
|
||||
// roundsBMI2 compresses the block whose schedule is in m1 into h, and stores
|
||||
// the states before steps 0, 58 and 65 into cs.
|
||||
//
|
||||
//go:noescape
|
||||
func roundsBMI2(h *[shared.WordBuffers]uint32, m1 *[shared.Rounds]uint32,
|
||||
cs *[shared.PreStepState][shared.WordBuffers]uint32)
|
||||
|
||||
func blockAVX2(dig *digest, p []byte) {
|
||||
var m1 [2][shared.Rounds]uint32
|
||||
cs := [shared.PreStepState][shared.WordBuffers]uint32{}
|
||||
|
||||
for len(p) >= shared.Chunk {
|
||||
// The schedules do not depend on the chaining state, so expand two
|
||||
// blocks at once. The rounds must still run one block at a time, as a
|
||||
// detected collision changes the state the next block starts from.
|
||||
n, pb := 1, p
|
||||
if len(p) >= 2*shared.Chunk {
|
||||
n, pb = 2, p[shared.Chunk:]
|
||||
}
|
||||
scheduleAVX2(&p[0], &pb[0], &m1[0], &m1[1])
|
||||
|
||||
for j := 0; j < n; j++ {
|
||||
w := &m1[j]
|
||||
roundsBMI2(&dig.h, w, &cs)
|
||||
if mask := ubc.CalculateDvMask(w); mask != 0 && checkCollision(w, &cs, &dig.h, mask) {
|
||||
dig.col = true
|
||||
|
||||
roundsBMI2(&dig.h, w, &cs)
|
||||
roundsBMI2(&dig.h, w, &cs)
|
||||
}
|
||||
}
|
||||
|
||||
p = p[n*shared.Chunk:]
|
||||
}
|
||||
}
|
||||
-415
@@ -1,415 +0,0 @@
|
||||
//go:build !noasm && gc && amd64
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
// The fallback for CPUs without SHA-NI. The message schedule is expanded with
|
||||
// AVX2 for two blocks at a time, one per 128-bit lane, as it does not depend
|
||||
// on the chaining state. The rounds then run one block at a time with scalar
|
||||
// BMI instructions, so that the collision detection can run between blocks
|
||||
// and gets the exact states before steps 58 and 65, with no repair needed.
|
||||
|
||||
// func scheduleAVX2(pa, pb *byte, m1a, m1b *[80]uint32)
|
||||
// Requires: AVX, AVX2
|
||||
//
|
||||
// Expands the blocks at pa and pb, which may be the same, into m1a and m1b.
|
||||
// Y0-Y7 hold the last eight groups of four words, group g in Y(g%8).
|
||||
TEXT ·scheduleAVX2(SB), NOSPLIT, $0-32
|
||||
MOVQ pa+0(FP), AX
|
||||
MOVQ pb+8(FP), BX
|
||||
MOVQ m1a+16(FP), CX
|
||||
MOVQ m1b+24(FP), DX
|
||||
VBROADCASTI128 bswap_mask<>(SB), Y13
|
||||
|
||||
// W[0..3] loaded from the blocks
|
||||
VMOVDQU 0(AX), X0
|
||||
VINSERTI128 $1, 0(BX), Y0, Y0
|
||||
VPSHUFB Y13, Y0, Y0
|
||||
VMOVDQU X0, 0(CX)
|
||||
VEXTRACTI128 $1, Y0, 0(DX)
|
||||
|
||||
// W[4..7] loaded from the blocks
|
||||
VMOVDQU 16(AX), X1
|
||||
VINSERTI128 $1, 16(BX), Y1, Y1
|
||||
VPSHUFB Y13, Y1, Y1
|
||||
VMOVDQU X1, 16(CX)
|
||||
VEXTRACTI128 $1, Y1, 16(DX)
|
||||
|
||||
// W[8..11] loaded from the blocks
|
||||
VMOVDQU 32(AX), X2
|
||||
VINSERTI128 $1, 32(BX), Y2, Y2
|
||||
VPSHUFB Y13, Y2, Y2
|
||||
VMOVDQU X2, 32(CX)
|
||||
VEXTRACTI128 $1, Y2, 32(DX)
|
||||
|
||||
// W[12..15] loaded from the blocks
|
||||
VMOVDQU 48(AX), X3
|
||||
VINSERTI128 $1, 48(BX), Y3, Y3
|
||||
VPSHUFB Y13, Y3, Y3
|
||||
VMOVDQU X3, 48(CX)
|
||||
VEXTRACTI128 $1, Y3, 48(DX)
|
||||
|
||||
// W[16..19] = rol1(W[i-3] ^ W[i-8] ^ W[i-14] ^ W[i-16]). W[i+3] needs
|
||||
// W[i] from this group, which is added in afterwards.
|
||||
VPALIGNR $8, Y0, Y1, Y8 // W[i-14..i-11]
|
||||
VPXOR Y0, Y8, Y8 // W[i-16..i-13]
|
||||
VPXOR Y2, Y8, Y8 // W[i-8..i-5]
|
||||
VPSRLDQ $4, Y3, Y9 // W[i-3..i-1], 0
|
||||
VPXOR Y9, Y8, Y8
|
||||
VPSRLD $31, Y8, Y9
|
||||
VPSLLD $1, Y8, Y4
|
||||
VPOR Y9, Y4, Y4
|
||||
VPSLLDQ $12, Y8, Y10 // 0, 0, 0, the input to W[i]
|
||||
VPSRLD $30, Y10, Y9
|
||||
VPSLLD $2, Y10, Y10
|
||||
VPOR Y9, Y10, Y10 // rol1(W[i]) in the lane of W[i+3]
|
||||
VPXOR Y10, Y4, Y4
|
||||
VMOVDQU X4, 64(CX)
|
||||
VEXTRACTI128 $1, Y4, 64(DX)
|
||||
|
||||
// W[20..23] = rol1(W[i-3] ^ W[i-8] ^ W[i-14] ^ W[i-16]). W[i+3] needs
|
||||
// W[i] from this group, which is added in afterwards.
|
||||
VPALIGNR $8, Y1, Y2, Y8 // W[i-14..i-11]
|
||||
VPXOR Y1, Y8, Y8 // W[i-16..i-13]
|
||||
VPXOR Y3, Y8, Y8 // W[i-8..i-5]
|
||||
VPSRLDQ $4, Y4, Y9 // W[i-3..i-1], 0
|
||||
VPXOR Y9, Y8, Y8
|
||||
VPSRLD $31, Y8, Y9
|
||||
VPSLLD $1, Y8, Y5
|
||||
VPOR Y9, Y5, Y5
|
||||
VPSLLDQ $12, Y8, Y10 // 0, 0, 0, the input to W[i]
|
||||
VPSRLD $30, Y10, Y9
|
||||
VPSLLD $2, Y10, Y10
|
||||
VPOR Y9, Y10, Y10 // rol1(W[i]) in the lane of W[i+3]
|
||||
VPXOR Y10, Y5, Y5
|
||||
VMOVDQU X5, 80(CX)
|
||||
VEXTRACTI128 $1, Y5, 80(DX)
|
||||
|
||||
// W[24..27] = rol1(W[i-3] ^ W[i-8] ^ W[i-14] ^ W[i-16]). W[i+3] needs
|
||||
// W[i] from this group, which is added in afterwards.
|
||||
VPALIGNR $8, Y2, Y3, Y8 // W[i-14..i-11]
|
||||
VPXOR Y2, Y8, Y8 // W[i-16..i-13]
|
||||
VPXOR Y4, Y8, Y8 // W[i-8..i-5]
|
||||
VPSRLDQ $4, Y5, Y9 // W[i-3..i-1], 0
|
||||
VPXOR Y9, Y8, Y8
|
||||
VPSRLD $31, Y8, Y9
|
||||
VPSLLD $1, Y8, Y6
|
||||
VPOR Y9, Y6, Y6
|
||||
VPSLLDQ $12, Y8, Y10 // 0, 0, 0, the input to W[i]
|
||||
VPSRLD $30, Y10, Y9
|
||||
VPSLLD $2, Y10, Y10
|
||||
VPOR Y9, Y10, Y10 // rol1(W[i]) in the lane of W[i+3]
|
||||
VPXOR Y10, Y6, Y6
|
||||
VMOVDQU X6, 96(CX)
|
||||
VEXTRACTI128 $1, Y6, 96(DX)
|
||||
|
||||
// W[28..31] = rol1(W[i-3] ^ W[i-8] ^ W[i-14] ^ W[i-16]). W[i+3] needs
|
||||
// W[i] from this group, which is added in afterwards.
|
||||
VPALIGNR $8, Y3, Y4, Y8 // W[i-14..i-11]
|
||||
VPXOR Y3, Y8, Y8 // W[i-16..i-13]
|
||||
VPXOR Y5, Y8, Y8 // W[i-8..i-5]
|
||||
VPSRLDQ $4, Y6, Y9 // W[i-3..i-1], 0
|
||||
VPXOR Y9, Y8, Y8
|
||||
VPSRLD $31, Y8, Y9
|
||||
VPSLLD $1, Y8, Y7
|
||||
VPOR Y9, Y7, Y7
|
||||
VPSLLDQ $12, Y8, Y10 // 0, 0, 0, the input to W[i]
|
||||
VPSRLD $30, Y10, Y9
|
||||
VPSLLD $2, Y10, Y10
|
||||
VPOR Y9, Y10, Y10 // rol1(W[i]) in the lane of W[i+3]
|
||||
VPXOR Y10, Y7, Y7
|
||||
VMOVDQU X7, 112(CX)
|
||||
VEXTRACTI128 $1, Y7, 112(DX)
|
||||
|
||||
// W[32..35] = rol2(W[i-6] ^ W[i-16] ^ W[i-28] ^ W[i-32])
|
||||
VPALIGNR $8, Y6, Y7, Y8 // W[i-6..i-3]
|
||||
VPXOR Y4, Y8, Y8 // W[i-16..i-13]
|
||||
VPXOR Y1, Y8, Y8 // W[i-28..i-25]
|
||||
VPXOR Y0, Y8, Y8 // W[i-32..i-29]
|
||||
VPSRLD $30, Y8, Y9
|
||||
VPSLLD $2, Y8, Y0
|
||||
VPOR Y9, Y0, Y0
|
||||
VMOVDQU X0, 128(CX)
|
||||
VEXTRACTI128 $1, Y0, 128(DX)
|
||||
|
||||
// W[36..39] = rol2(W[i-6] ^ W[i-16] ^ W[i-28] ^ W[i-32])
|
||||
VPALIGNR $8, Y7, Y0, Y8 // W[i-6..i-3]
|
||||
VPXOR Y5, Y8, Y8 // W[i-16..i-13]
|
||||
VPXOR Y2, Y8, Y8 // W[i-28..i-25]
|
||||
VPXOR Y1, Y8, Y8 // W[i-32..i-29]
|
||||
VPSRLD $30, Y8, Y9
|
||||
VPSLLD $2, Y8, Y1
|
||||
VPOR Y9, Y1, Y1
|
||||
VMOVDQU X1, 144(CX)
|
||||
VEXTRACTI128 $1, Y1, 144(DX)
|
||||
|
||||
// W[40..43] = rol2(W[i-6] ^ W[i-16] ^ W[i-28] ^ W[i-32])
|
||||
VPALIGNR $8, Y0, Y1, Y8 // W[i-6..i-3]
|
||||
VPXOR Y6, Y8, Y8 // W[i-16..i-13]
|
||||
VPXOR Y3, Y8, Y8 // W[i-28..i-25]
|
||||
VPXOR Y2, Y8, Y8 // W[i-32..i-29]
|
||||
VPSRLD $30, Y8, Y9
|
||||
VPSLLD $2, Y8, Y2
|
||||
VPOR Y9, Y2, Y2
|
||||
VMOVDQU X2, 160(CX)
|
||||
VEXTRACTI128 $1, Y2, 160(DX)
|
||||
|
||||
// W[44..47] = rol2(W[i-6] ^ W[i-16] ^ W[i-28] ^ W[i-32])
|
||||
VPALIGNR $8, Y1, Y2, Y8 // W[i-6..i-3]
|
||||
VPXOR Y7, Y8, Y8 // W[i-16..i-13]
|
||||
VPXOR Y4, Y8, Y8 // W[i-28..i-25]
|
||||
VPXOR Y3, Y8, Y8 // W[i-32..i-29]
|
||||
VPSRLD $30, Y8, Y9
|
||||
VPSLLD $2, Y8, Y3
|
||||
VPOR Y9, Y3, Y3
|
||||
VMOVDQU X3, 176(CX)
|
||||
VEXTRACTI128 $1, Y3, 176(DX)
|
||||
|
||||
// W[48..51] = rol2(W[i-6] ^ W[i-16] ^ W[i-28] ^ W[i-32])
|
||||
VPALIGNR $8, Y2, Y3, Y8 // W[i-6..i-3]
|
||||
VPXOR Y0, Y8, Y8 // W[i-16..i-13]
|
||||
VPXOR Y5, Y8, Y8 // W[i-28..i-25]
|
||||
VPXOR Y4, Y8, Y8 // W[i-32..i-29]
|
||||
VPSRLD $30, Y8, Y9
|
||||
VPSLLD $2, Y8, Y4
|
||||
VPOR Y9, Y4, Y4
|
||||
VMOVDQU X4, 192(CX)
|
||||
VEXTRACTI128 $1, Y4, 192(DX)
|
||||
|
||||
// W[52..55] = rol2(W[i-6] ^ W[i-16] ^ W[i-28] ^ W[i-32])
|
||||
VPALIGNR $8, Y3, Y4, Y8 // W[i-6..i-3]
|
||||
VPXOR Y1, Y8, Y8 // W[i-16..i-13]
|
||||
VPXOR Y6, Y8, Y8 // W[i-28..i-25]
|
||||
VPXOR Y5, Y8, Y8 // W[i-32..i-29]
|
||||
VPSRLD $30, Y8, Y9
|
||||
VPSLLD $2, Y8, Y5
|
||||
VPOR Y9, Y5, Y5
|
||||
VMOVDQU X5, 208(CX)
|
||||
VEXTRACTI128 $1, Y5, 208(DX)
|
||||
|
||||
// W[56..59] = rol2(W[i-6] ^ W[i-16] ^ W[i-28] ^ W[i-32])
|
||||
VPALIGNR $8, Y4, Y5, Y8 // W[i-6..i-3]
|
||||
VPXOR Y2, Y8, Y8 // W[i-16..i-13]
|
||||
VPXOR Y7, Y8, Y8 // W[i-28..i-25]
|
||||
VPXOR Y6, Y8, Y8 // W[i-32..i-29]
|
||||
VPSRLD $30, Y8, Y9
|
||||
VPSLLD $2, Y8, Y6
|
||||
VPOR Y9, Y6, Y6
|
||||
VMOVDQU X6, 224(CX)
|
||||
VEXTRACTI128 $1, Y6, 224(DX)
|
||||
|
||||
// W[60..63] = rol2(W[i-6] ^ W[i-16] ^ W[i-28] ^ W[i-32])
|
||||
VPALIGNR $8, Y5, Y6, Y8 // W[i-6..i-3]
|
||||
VPXOR Y3, Y8, Y8 // W[i-16..i-13]
|
||||
VPXOR Y0, Y8, Y8 // W[i-28..i-25]
|
||||
VPXOR Y7, Y8, Y8 // W[i-32..i-29]
|
||||
VPSRLD $30, Y8, Y9
|
||||
VPSLLD $2, Y8, Y7
|
||||
VPOR Y9, Y7, Y7
|
||||
VMOVDQU X7, 240(CX)
|
||||
VEXTRACTI128 $1, Y7, 240(DX)
|
||||
|
||||
// W[64..67] = rol2(W[i-6] ^ W[i-16] ^ W[i-28] ^ W[i-32])
|
||||
VPALIGNR $8, Y6, Y7, Y8 // W[i-6..i-3]
|
||||
VPXOR Y4, Y8, Y8 // W[i-16..i-13]
|
||||
VPXOR Y1, Y8, Y8 // W[i-28..i-25]
|
||||
VPXOR Y0, Y8, Y8 // W[i-32..i-29]
|
||||
VPSRLD $30, Y8, Y9
|
||||
VPSLLD $2, Y8, Y0
|
||||
VPOR Y9, Y0, Y0
|
||||
VMOVDQU X0, 256(CX)
|
||||
VEXTRACTI128 $1, Y0, 256(DX)
|
||||
|
||||
// W[68..71] = rol2(W[i-6] ^ W[i-16] ^ W[i-28] ^ W[i-32])
|
||||
VPALIGNR $8, Y7, Y0, Y8 // W[i-6..i-3]
|
||||
VPXOR Y5, Y8, Y8 // W[i-16..i-13]
|
||||
VPXOR Y2, Y8, Y8 // W[i-28..i-25]
|
||||
VPXOR Y1, Y8, Y8 // W[i-32..i-29]
|
||||
VPSRLD $30, Y8, Y9
|
||||
VPSLLD $2, Y8, Y1
|
||||
VPOR Y9, Y1, Y1
|
||||
VMOVDQU X1, 272(CX)
|
||||
VEXTRACTI128 $1, Y1, 272(DX)
|
||||
|
||||
// W[72..75] = rol2(W[i-6] ^ W[i-16] ^ W[i-28] ^ W[i-32])
|
||||
VPALIGNR $8, Y0, Y1, Y8 // W[i-6..i-3]
|
||||
VPXOR Y6, Y8, Y8 // W[i-16..i-13]
|
||||
VPXOR Y3, Y8, Y8 // W[i-28..i-25]
|
||||
VPXOR Y2, Y8, Y8 // W[i-32..i-29]
|
||||
VPSRLD $30, Y8, Y9
|
||||
VPSLLD $2, Y8, Y2
|
||||
VPOR Y9, Y2, Y2
|
||||
VMOVDQU X2, 288(CX)
|
||||
VEXTRACTI128 $1, Y2, 288(DX)
|
||||
|
||||
// W[76..79] = rol2(W[i-6] ^ W[i-16] ^ W[i-28] ^ W[i-32])
|
||||
VPALIGNR $8, Y1, Y2, Y8 // W[i-6..i-3]
|
||||
VPXOR Y7, Y8, Y8 // W[i-16..i-13]
|
||||
VPXOR Y4, Y8, Y8 // W[i-28..i-25]
|
||||
VPXOR Y3, Y8, Y8 // W[i-32..i-29]
|
||||
VPSRLD $30, Y8, Y9
|
||||
VPSLLD $2, Y8, Y3
|
||||
VPOR Y9, Y3, Y3
|
||||
VMOVDQU X3, 304(CX)
|
||||
VEXTRACTI128 $1, Y3, 304(DX)
|
||||
|
||||
VZEROUPPER
|
||||
RET
|
||||
|
||||
// Each round adds W[i], K, f(b, c, d) and rol5(a) into e and rotates b by 30.
|
||||
// The callers rotate the register names instead of moving values around, and
|
||||
// rol5(a) goes in last, as a is the only input the previous round produced.
|
||||
#define ROUND_CH(a, b, c, d, e, i) \
|
||||
ADDL ((i)*4)(SI), e; \
|
||||
ADDL $0x5a827999, e; \
|
||||
ANDNL d, b, AX; \
|
||||
MOVL c, BX; \
|
||||
ANDL b, BX; \
|
||||
ADDL AX, e; \
|
||||
ADDL BX, e; \
|
||||
RORXL $27, a, CX; \
|
||||
RORXL $2, b, b; \
|
||||
ADDL CX, e
|
||||
|
||||
#define ROUND_PARITY(a, b, c, d, e, i, k) \
|
||||
ADDL ((i)*4)(SI), e; \
|
||||
ADDL k, e; \
|
||||
MOVL b, AX; \
|
||||
XORL c, AX; \
|
||||
XORL d, AX; \
|
||||
ADDL AX, e; \
|
||||
RORXL $27, a, CX; \
|
||||
RORXL $2, b, b; \
|
||||
ADDL CX, e
|
||||
|
||||
// maj(b, c, d) = (b & c) + ((b ^ c) & d), as the two never share a bit.
|
||||
#define ROUND_MAJ(a, b, c, d, e, i) \
|
||||
ADDL ((i)*4)(SI), e; \
|
||||
ADDL $0x8f1bbcdc, e; \
|
||||
MOVL b, AX; \
|
||||
XORL c, AX; \
|
||||
ANDL d, AX; \
|
||||
MOVL b, BX; \
|
||||
ANDL c, BX; \
|
||||
ADDL AX, e; \
|
||||
ADDL BX, e; \
|
||||
RORXL $27, a, CX; \
|
||||
RORXL $2, b, b; \
|
||||
ADDL CX, e
|
||||
|
||||
#define SAVECS(a, b, c, d, e, index) \
|
||||
MOVL a, ((index)*20+0)(DX); \
|
||||
MOVL b, ((index)*20+4)(DX); \
|
||||
MOVL c, ((index)*20+8)(DX); \
|
||||
MOVL d, ((index)*20+12)(DX); \
|
||||
MOVL e, ((index)*20+16)(DX)
|
||||
|
||||
// func roundsBMI2(h *[5]uint32, m1 *[80]uint32, cs *[3][5]uint32)
|
||||
// Requires: BMI1, BMI2
|
||||
//
|
||||
// Compresses the block whose schedule is in m1 into h, storing the states
|
||||
// before steps 0, 58 and 65 into cs.
|
||||
TEXT ·roundsBMI2(SB), NOSPLIT, $0-24
|
||||
MOVQ h+0(FP), DI
|
||||
MOVQ m1+8(FP), SI
|
||||
MOVQ cs+16(FP), DX
|
||||
MOVL 0(DI), R8
|
||||
MOVL 4(DI), R9
|
||||
MOVL 8(DI), R10
|
||||
MOVL 12(DI), R11
|
||||
MOVL 16(DI), R12
|
||||
|
||||
SAVECS(R8, R9, R10, R11, R12, 0)
|
||||
ROUND_CH(R8, R9, R10, R11, R12, 0)
|
||||
ROUND_CH(R12, R8, R9, R10, R11, 1)
|
||||
ROUND_CH(R11, R12, R8, R9, R10, 2)
|
||||
ROUND_CH(R10, R11, R12, R8, R9, 3)
|
||||
ROUND_CH(R9, R10, R11, R12, R8, 4)
|
||||
ROUND_CH(R8, R9, R10, R11, R12, 5)
|
||||
ROUND_CH(R12, R8, R9, R10, R11, 6)
|
||||
ROUND_CH(R11, R12, R8, R9, R10, 7)
|
||||
ROUND_CH(R10, R11, R12, R8, R9, 8)
|
||||
ROUND_CH(R9, R10, R11, R12, R8, 9)
|
||||
ROUND_CH(R8, R9, R10, R11, R12, 10)
|
||||
ROUND_CH(R12, R8, R9, R10, R11, 11)
|
||||
ROUND_CH(R11, R12, R8, R9, R10, 12)
|
||||
ROUND_CH(R10, R11, R12, R8, R9, 13)
|
||||
ROUND_CH(R9, R10, R11, R12, R8, 14)
|
||||
ROUND_CH(R8, R9, R10, R11, R12, 15)
|
||||
ROUND_CH(R12, R8, R9, R10, R11, 16)
|
||||
ROUND_CH(R11, R12, R8, R9, R10, 17)
|
||||
ROUND_CH(R10, R11, R12, R8, R9, 18)
|
||||
ROUND_CH(R9, R10, R11, R12, R8, 19)
|
||||
ROUND_PARITY(R8, R9, R10, R11, R12, 20, $0x6ed9eba1)
|
||||
ROUND_PARITY(R12, R8, R9, R10, R11, 21, $0x6ed9eba1)
|
||||
ROUND_PARITY(R11, R12, R8, R9, R10, 22, $0x6ed9eba1)
|
||||
ROUND_PARITY(R10, R11, R12, R8, R9, 23, $0x6ed9eba1)
|
||||
ROUND_PARITY(R9, R10, R11, R12, R8, 24, $0x6ed9eba1)
|
||||
ROUND_PARITY(R8, R9, R10, R11, R12, 25, $0x6ed9eba1)
|
||||
ROUND_PARITY(R12, R8, R9, R10, R11, 26, $0x6ed9eba1)
|
||||
ROUND_PARITY(R11, R12, R8, R9, R10, 27, $0x6ed9eba1)
|
||||
ROUND_PARITY(R10, R11, R12, R8, R9, 28, $0x6ed9eba1)
|
||||
ROUND_PARITY(R9, R10, R11, R12, R8, 29, $0x6ed9eba1)
|
||||
ROUND_PARITY(R8, R9, R10, R11, R12, 30, $0x6ed9eba1)
|
||||
ROUND_PARITY(R12, R8, R9, R10, R11, 31, $0x6ed9eba1)
|
||||
ROUND_PARITY(R11, R12, R8, R9, R10, 32, $0x6ed9eba1)
|
||||
ROUND_PARITY(R10, R11, R12, R8, R9, 33, $0x6ed9eba1)
|
||||
ROUND_PARITY(R9, R10, R11, R12, R8, 34, $0x6ed9eba1)
|
||||
ROUND_PARITY(R8, R9, R10, R11, R12, 35, $0x6ed9eba1)
|
||||
ROUND_PARITY(R12, R8, R9, R10, R11, 36, $0x6ed9eba1)
|
||||
ROUND_PARITY(R11, R12, R8, R9, R10, 37, $0x6ed9eba1)
|
||||
ROUND_PARITY(R10, R11, R12, R8, R9, 38, $0x6ed9eba1)
|
||||
ROUND_PARITY(R9, R10, R11, R12, R8, 39, $0x6ed9eba1)
|
||||
ROUND_MAJ(R8, R9, R10, R11, R12, 40)
|
||||
ROUND_MAJ(R12, R8, R9, R10, R11, 41)
|
||||
ROUND_MAJ(R11, R12, R8, R9, R10, 42)
|
||||
ROUND_MAJ(R10, R11, R12, R8, R9, 43)
|
||||
ROUND_MAJ(R9, R10, R11, R12, R8, 44)
|
||||
ROUND_MAJ(R8, R9, R10, R11, R12, 45)
|
||||
ROUND_MAJ(R12, R8, R9, R10, R11, 46)
|
||||
ROUND_MAJ(R11, R12, R8, R9, R10, 47)
|
||||
ROUND_MAJ(R10, R11, R12, R8, R9, 48)
|
||||
ROUND_MAJ(R9, R10, R11, R12, R8, 49)
|
||||
ROUND_MAJ(R8, R9, R10, R11, R12, 50)
|
||||
ROUND_MAJ(R12, R8, R9, R10, R11, 51)
|
||||
ROUND_MAJ(R11, R12, R8, R9, R10, 52)
|
||||
ROUND_MAJ(R10, R11, R12, R8, R9, 53)
|
||||
ROUND_MAJ(R9, R10, R11, R12, R8, 54)
|
||||
ROUND_MAJ(R8, R9, R10, R11, R12, 55)
|
||||
ROUND_MAJ(R12, R8, R9, R10, R11, 56)
|
||||
ROUND_MAJ(R11, R12, R8, R9, R10, 57)
|
||||
SAVECS(R10, R11, R12, R8, R9, 1)
|
||||
ROUND_MAJ(R10, R11, R12, R8, R9, 58)
|
||||
ROUND_MAJ(R9, R10, R11, R12, R8, 59)
|
||||
ROUND_PARITY(R8, R9, R10, R11, R12, 60, $0xca62c1d6)
|
||||
ROUND_PARITY(R12, R8, R9, R10, R11, 61, $0xca62c1d6)
|
||||
ROUND_PARITY(R11, R12, R8, R9, R10, 62, $0xca62c1d6)
|
||||
ROUND_PARITY(R10, R11, R12, R8, R9, 63, $0xca62c1d6)
|
||||
ROUND_PARITY(R9, R10, R11, R12, R8, 64, $0xca62c1d6)
|
||||
SAVECS(R8, R9, R10, R11, R12, 2)
|
||||
ROUND_PARITY(R8, R9, R10, R11, R12, 65, $0xca62c1d6)
|
||||
ROUND_PARITY(R12, R8, R9, R10, R11, 66, $0xca62c1d6)
|
||||
ROUND_PARITY(R11, R12, R8, R9, R10, 67, $0xca62c1d6)
|
||||
ROUND_PARITY(R10, R11, R12, R8, R9, 68, $0xca62c1d6)
|
||||
ROUND_PARITY(R9, R10, R11, R12, R8, 69, $0xca62c1d6)
|
||||
ROUND_PARITY(R8, R9, R10, R11, R12, 70, $0xca62c1d6)
|
||||
ROUND_PARITY(R12, R8, R9, R10, R11, 71, $0xca62c1d6)
|
||||
ROUND_PARITY(R11, R12, R8, R9, R10, 72, $0xca62c1d6)
|
||||
ROUND_PARITY(R10, R11, R12, R8, R9, 73, $0xca62c1d6)
|
||||
ROUND_PARITY(R9, R10, R11, R12, R8, 74, $0xca62c1d6)
|
||||
ROUND_PARITY(R8, R9, R10, R11, R12, 75, $0xca62c1d6)
|
||||
ROUND_PARITY(R12, R8, R9, R10, R11, 76, $0xca62c1d6)
|
||||
ROUND_PARITY(R11, R12, R8, R9, R10, 77, $0xca62c1d6)
|
||||
ROUND_PARITY(R10, R11, R12, R8, R9, 78, $0xca62c1d6)
|
||||
ROUND_PARITY(R9, R10, R11, R12, R8, 79, $0xca62c1d6)
|
||||
|
||||
ADDL R8, 0(DI)
|
||||
ADDL R9, 4(DI)
|
||||
ADDL R10, 8(DI)
|
||||
ADDL R11, 12(DI)
|
||||
ADDL R12, 16(DI)
|
||||
RET
|
||||
|
||||
// Swaps the bytes of each word, as SHA-1 reads the block big endian.
|
||||
DATA bswap_mask<>+0(SB)/8, $0x0405060700010203
|
||||
DATA bswap_mask<>+8(SB)/8, $0x0c0d0e0f08090a0b
|
||||
GLOBL bswap_mask<>(SB), RODATA|NOPTR, $16
|
||||
+46
-32
@@ -20,9 +20,7 @@ var forceGeneric bool
|
||||
// blockGeneric is a portable, pure Go version of the SHA-1 block step.
|
||||
// It's used by sha1block_generic.go and tests.
|
||||
func blockGeneric(dig *digest, p []byte) {
|
||||
// Expand directly into the schedule retained for collision detection.
|
||||
// Every word is overwritten for each block, including rehashes.
|
||||
var m1 [shared.Rounds]uint32
|
||||
var w [16]uint32
|
||||
|
||||
// cs stores the pre-step compression state for only the steps required for the
|
||||
// collision detection, which are 0, 58 and 65.
|
||||
@@ -31,6 +29,7 @@ func blockGeneric(dig *digest, p []byte) {
|
||||
|
||||
h0, h1, h2, h3, h4 := dig.h[0], dig.h[1], dig.h[2], dig.h[3], dig.h[4]
|
||||
for len(p) >= shared.Chunk {
|
||||
m1 := [shared.Rounds]uint32{}
|
||||
hi := 1
|
||||
|
||||
// Collision attacks are thwarted by hashing a detected near-collision block 3 times.
|
||||
@@ -52,27 +51,36 @@ func blockGeneric(dig *digest, p []byte) {
|
||||
for ; i < 16; i++ {
|
||||
// load step
|
||||
j := i * 4
|
||||
m1[i] = uint32(p[j])<<24 | uint32(p[j+1])<<16 | uint32(p[j+2])<<8 | uint32(p[j+3])
|
||||
w[i] = uint32(p[j])<<24 | uint32(p[j+1])<<16 | uint32(p[j+2])<<8 | uint32(p[j+3])
|
||||
|
||||
f := b&c | (^b)&d
|
||||
t := bits.RotateLeft32(a, 5) + f + e + m1[i] + shared.K0
|
||||
t := bits.RotateLeft32(a, 5) + f + e + w[i&0xf] + shared.K0
|
||||
a, b, c, d, e = t, a, bits.RotateLeft32(b, 30), c, d
|
||||
|
||||
// Store compression state for the collision detection.
|
||||
m1[i] = w[i&0xf]
|
||||
}
|
||||
for ; i < 20; i++ {
|
||||
tmp := m1[i-3] ^ m1[i-8] ^ m1[i-14] ^ m1[i-16]
|
||||
m1[i] = tmp<<1 | tmp>>(32-1)
|
||||
tmp := w[(i-3)&0xf] ^ w[(i-8)&0xf] ^ w[(i-14)&0xf] ^ w[(i)&0xf]
|
||||
w[i&0xf] = tmp<<1 | tmp>>(32-1)
|
||||
|
||||
f := b&c | (^b)&d
|
||||
t := bits.RotateLeft32(a, 5) + f + e + m1[i] + shared.K0
|
||||
t := bits.RotateLeft32(a, 5) + f + e + w[i&0xf] + shared.K0
|
||||
a, b, c, d, e = t, a, bits.RotateLeft32(b, 30), c, d
|
||||
|
||||
// Store compression state for the collision detection.
|
||||
m1[i] = w[i&0xf]
|
||||
}
|
||||
for ; i < 40; i++ {
|
||||
tmp := m1[i-3] ^ m1[i-8] ^ m1[i-14] ^ m1[i-16]
|
||||
m1[i] = tmp<<1 | tmp>>(32-1)
|
||||
tmp := w[(i-3)&0xf] ^ w[(i-8)&0xf] ^ w[(i-14)&0xf] ^ w[(i)&0xf]
|
||||
w[i&0xf] = tmp<<1 | tmp>>(32-1)
|
||||
|
||||
f := b ^ c ^ d
|
||||
t := bits.RotateLeft32(a, 5) + f + e + m1[i] + shared.K1
|
||||
t := bits.RotateLeft32(a, 5) + f + e + w[i&0xf] + shared.K1
|
||||
a, b, c, d, e = t, a, bits.RotateLeft32(b, 30), c, d
|
||||
|
||||
// Store compression state for the collision detection.
|
||||
m1[i] = w[i&0xf]
|
||||
}
|
||||
for ; i < 60; i++ {
|
||||
if i == 58 {
|
||||
@@ -80,12 +88,15 @@ func blockGeneric(dig *digest, p []byte) {
|
||||
cs[1] = [shared.WordBuffers]uint32{a, b, c, d, e}
|
||||
}
|
||||
|
||||
tmp := m1[i-3] ^ m1[i-8] ^ m1[i-14] ^ m1[i-16]
|
||||
m1[i] = tmp<<1 | tmp>>(32-1)
|
||||
tmp := w[(i-3)&0xf] ^ w[(i-8)&0xf] ^ w[(i-14)&0xf] ^ w[(i)&0xf]
|
||||
w[i&0xf] = tmp<<1 | tmp>>(32-1)
|
||||
|
||||
f := ((b | c) & d) | (b & c)
|
||||
t := bits.RotateLeft32(a, 5) + f + e + m1[i] + shared.K2
|
||||
t := bits.RotateLeft32(a, 5) + f + e + w[i&0xf] + shared.K2
|
||||
a, b, c, d, e = t, a, bits.RotateLeft32(b, 30), c, d
|
||||
|
||||
// Store compression state for the collision detection.
|
||||
m1[i] = w[i&0xf]
|
||||
}
|
||||
for ; i < 80; i++ {
|
||||
if i == 65 {
|
||||
@@ -93,12 +104,15 @@ func blockGeneric(dig *digest, p []byte) {
|
||||
cs[2] = [shared.WordBuffers]uint32{a, b, c, d, e}
|
||||
}
|
||||
|
||||
tmp := m1[i-3] ^ m1[i-8] ^ m1[i-14] ^ m1[i-16]
|
||||
m1[i] = tmp<<1 | tmp>>(32-1)
|
||||
tmp := w[(i-3)&0xf] ^ w[(i-8)&0xf] ^ w[(i-14)&0xf] ^ w[(i)&0xf]
|
||||
w[i&0xf] = tmp<<1 | tmp>>(32-1)
|
||||
|
||||
f := b ^ c ^ d
|
||||
t := bits.RotateLeft32(a, 5) + f + e + m1[i] + shared.K3
|
||||
t := bits.RotateLeft32(a, 5) + f + e + w[i&0xf] + shared.K3
|
||||
a, b, c, d, e = t, a, bits.RotateLeft32(b, 30), c, d
|
||||
|
||||
// Store compression state for the collision detection.
|
||||
m1[i] = w[i&0xf]
|
||||
}
|
||||
|
||||
h0 += a
|
||||
@@ -114,7 +128,7 @@ func blockGeneric(dig *digest, p []byte) {
|
||||
|
||||
if hi == 1 {
|
||||
h := [shared.WordBuffers]uint32{h0, h1, h2, h3, h4}
|
||||
col := checkCollision(&m1, &cs, &h, ubc.CalculateDvMask(&m1))
|
||||
col := checkCollision(&m1, &cs, &h)
|
||||
if col {
|
||||
dig.col = true
|
||||
hi++
|
||||
@@ -133,9 +147,8 @@ func checkCollision(
|
||||
m1 *[shared.Rounds]uint32,
|
||||
cs *[shared.PreStepState][shared.WordBuffers]uint32,
|
||||
h *[shared.WordBuffers]uint32,
|
||||
mask uint32,
|
||||
) bool {
|
||||
if mask != 0 {
|
||||
if mask := ubc.CalculateDvMask(m1); mask != 0 {
|
||||
dvs := ubc.SHA1_dvs()
|
||||
|
||||
for i := 0; dvs[i].DvType != 0; i++ {
|
||||
@@ -277,22 +290,23 @@ func rectifyCompressionState(
|
||||
return
|
||||
}
|
||||
|
||||
// The words are loaded and stored one at a time. Passing [5]uint32 values
|
||||
// around made the compiler reload them with wider moves than they were
|
||||
// stored with, which stalled on store forwarding each time.
|
||||
func3 := func(state [shared.WordBuffers]uint32, i int) [shared.WordBuffers]uint32 {
|
||||
a, b, c, d, e := state[0], state[1], state[2], state[3], state[4]
|
||||
|
||||
// Advance cs[1] from the state before step 56 to the one before step 58.
|
||||
a, b, c, d, e := cs[1][0], cs[1][1], cs[1][2], cs[1][3], cs[1][4]
|
||||
for i := 56; i < 58; i++ {
|
||||
f := ((b | c) & d) | (b & c)
|
||||
t := bits.RotateLeft32(a, 5) + f + e + m1[i] + shared.K2
|
||||
a, b, c, d, e = t, a, bits.RotateLeft32(b, 30), c, d
|
||||
return [shared.WordBuffers]uint32{a, b, c, d, e}
|
||||
}
|
||||
func4 := func(state [shared.WordBuffers]uint32, i int) [shared.WordBuffers]uint32 {
|
||||
a, b, c, d, e := state[0], state[1], state[2], state[3], state[4]
|
||||
f := b ^ c ^ d
|
||||
t := bits.RotateLeft32(a, 5) + f + e + m1[i] + shared.K3
|
||||
a, b, c, d, e = t, a, bits.RotateLeft32(b, 30), c, d
|
||||
return [shared.WordBuffers]uint32{a, b, c, d, e}
|
||||
}
|
||||
cs[1][0], cs[1][1], cs[1][2], cs[1][3], cs[1][4] = a, b, c, d, e
|
||||
|
||||
// Advance cs[2] from the state before step 64 to the one before step 65.
|
||||
a, b, c, d, e = cs[2][0], cs[2][1], cs[2][2], cs[2][3], cs[2][4]
|
||||
f := b ^ c ^ d
|
||||
t := bits.RotateLeft32(a, 5) + f + e + m1[64] + shared.K3
|
||||
cs[2][0], cs[2][1], cs[2][2], cs[2][3], cs[2][4] = t, a, bits.RotateLeft32(b, 30), c, d
|
||||
cs57 := func3(cs[1], 56)
|
||||
cs[1] = func3(cs57, 57)
|
||||
cs[2] = func4(cs[2], 64)
|
||||
}
|
||||
|
||||
-51
@@ -1,51 +0,0 @@
|
||||
//go:build !noasm && gc && amd64
|
||||
|
||||
package ubc
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/pjbgf/sha1cd/internal/cpu"
|
||||
)
|
||||
|
||||
// useAVX512 reports whether calculateDvMaskAVX512 can run. Its only
|
||||
// instructions outside AVX-512F are VMOVD and VZEROUPPER, which need AVX.
|
||||
// HasAVX512F is set only when the CPU reports AVX as well.
|
||||
var useAVX512 = cpu.X86.HasAVX512F
|
||||
|
||||
//go:noescape
|
||||
func calculateDvMaskAVX512(W *[80]uint32, terms *uint32, groups int) uint32
|
||||
|
||||
// avx512Lanes is the number of terms a group of avx512Terms describes, and
|
||||
// avx512Fields the vectors it holds for them.
|
||||
const (
|
||||
avx512Lanes = 16
|
||||
avx512Fields = 6
|
||||
)
|
||||
|
||||
// avx512Groups is how many passes of the kernel loop the table needs.
|
||||
const avx512Groups = len(avx512Terms) / (avx512Fields * avx512Lanes)
|
||||
|
||||
// alignedTerms is a copy of avx512Terms that starts on a cache line. The
|
||||
// linker aligns data to 32 bytes at most, and a 64 byte load that straddles
|
||||
// two cache lines costs about twice as much.
|
||||
var alignedTerms = func() *uint32 {
|
||||
buf := make([]uint32, len(avx512Terms)+16)
|
||||
off := (64 - uintptr(unsafe.Pointer(&buf[0]))%64) % 64 / 4
|
||||
copy(buf[off:], avx512Terms[:])
|
||||
return &buf[off]
|
||||
}()
|
||||
|
||||
// CalculateDvMask takes as input an expanded message block and
|
||||
// verifies the unavoidable bitconditions for all listed DVs. It returns
|
||||
// a dvmask where each bit belonging to a DV is set if all unavoidable
|
||||
// bitconditions for that DV have been met.
|
||||
func CalculateDvMask(W *[80]uint32) uint32 {
|
||||
if W == nil {
|
||||
return 0
|
||||
}
|
||||
if useAVX512 {
|
||||
return calculateDvMaskAVX512(W, alignedTerms, avx512Groups)
|
||||
}
|
||||
return calculateDvMaskGeneric(W)
|
||||
}
|
||||
-52
@@ -1,52 +0,0 @@
|
||||
//go:build !noasm && gc && amd64
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
// func calculateDvMaskAVX512(W *[80]uint32, terms *uint32, groups int) uint32
|
||||
// Requires: AVX, AVX512F
|
||||
//
|
||||
// Each group of avx512Terms describes 16 of the bit tests of CalculateDvMask,
|
||||
// as vectors of word indices, shift counts, expected parities and the masks
|
||||
// that clear the DVs of a failing test. One pass of the loop runs a group.
|
||||
//
|
||||
// terms only has to be 64 byte aligned for speed, as otherwise every load of
|
||||
// it straddles two cache lines. The word indices are biased by 34, matching
|
||||
// the two vectors of W loaded below.
|
||||
TEXT ·calculateDvMaskAVX512(SB), NOSPLIT, $0-28
|
||||
MOVQ W+0(FP), AX
|
||||
MOVQ terms+8(FP), BX
|
||||
MOVQ groups+16(FP), CX
|
||||
VMOVDQU32 136(AX), Z0 // W[34..49]
|
||||
VMOVDQU32 200(AX), Z1 // W[50..65]
|
||||
VPTERNLOGD $0xff, Z2, Z2, Z2 // mask = all ones
|
||||
MOVL $1, DX
|
||||
VPBROADCASTD DX, Z3
|
||||
|
||||
loop:
|
||||
VMOVDQU32 0(BX), Z4
|
||||
VMOVDQU32 64(BX), Z5
|
||||
VPERMI2D Z1, Z0, Z4 // W[a]
|
||||
VPERMI2D Z1, Z0, Z5 // W[b]
|
||||
VPSRLVD 128(BX), Z4, Z4 // >> ka
|
||||
VPSRLVD 192(BX), Z5, Z5 // >> kb
|
||||
VPTERNLOGD $0x96, 256(BX), Z5, Z4 // ^ e
|
||||
VPTESTMD Z3, Z4, K1 // the terms whose bit test fails
|
||||
VPANDD 320(BX), Z2, K1, Z2 // clear their DVs
|
||||
ADDQ $384, BX
|
||||
DECQ CX
|
||||
JNZ loop
|
||||
|
||||
// AND the 16 lanes together, first the 128 bit lanes and then the
|
||||
// words within them.
|
||||
VSHUFI64X2 $0x4e, Z2, Z2, Z4
|
||||
VPANDD Z4, Z2, Z2
|
||||
VSHUFI64X2 $0xb1, Z2, Z2, Z4
|
||||
VPANDD Z4, Z2, Z2
|
||||
VPSHUFD $0x4e, Z2, Z4
|
||||
VPANDD Z4, Z2, Z2
|
||||
VPSHUFD $0xb1, Z2, Z4
|
||||
VPANDD Z4, Z2, Z2
|
||||
VMOVD X2, AX
|
||||
VZEROUPPER
|
||||
MOVL AX, ret+24(FP)
|
||||
RET
|
||||
-111
@@ -1,111 +0,0 @@
|
||||
//go:build !noasm && gc && amd64
|
||||
|
||||
package ubc
|
||||
|
||||
// avx512Terms holds the bit tests of CalculateDvMask in the layout the
|
||||
// AVX-512 kernel consumes, 16 terms per group. Each term says that a set of
|
||||
// DVs survives only if bit ka of W[a] XOR bit kb of W[b] equals e, and names
|
||||
// the mask that clears those DVs when it does not.
|
||||
//
|
||||
// The six vectors of a group are, in order: a-34, b-34, ka, kb, e, and the
|
||||
// clear mask. Word indices are biased by 34 because the kernel keeps W[34..65]
|
||||
// in two registers. A group is 384 bytes, and the kernel runs one per pass.
|
||||
//
|
||||
// The terms are those of calculateDvMaskGeneric, which is a port of
|
||||
// ubc_check.c and has not changed since the collision detection was published
|
||||
// in 2017. TestAVX512MatchesGeneric holds the two implementations to the same
|
||||
// results over inputs that reach every DV, so a mistake here is a test
|
||||
// failure rather than a missed collision. The comment above each group names
|
||||
// its terms as a.ka^b.kb=e.
|
||||
var avx512Terms = [960]uint32{
|
||||
// terms 0-15
|
||||
// 44.29^45.29=0 49.29^50.29=0 48.29^49.29=0 47.4^50.29=0 47.29^48.29=0 46.4^49.29=0 46.29^47.29=0 45.4^48.29=0
|
||||
// 45.29^46.29=0 44.4^47.29=0 43.4^46.29=0 43.29^44.29=0 42.4^45.29=0 41.4^44.29=0 40.29^41.29=0 54.29^55.29=0
|
||||
0x0000000a, 0x0000000f, 0x0000000e, 0x0000000d, 0x0000000d, 0x0000000c, 0x0000000c, 0x0000000b, 0x0000000b, 0x0000000a, 0x00000009, 0x00000009, 0x00000008, 0x00000007, 0x00000006, 0x00000014,
|
||||
0x0000000b, 0x00000010, 0x0000000f, 0x00000010, 0x0000000e, 0x0000000f, 0x0000000d, 0x0000000e, 0x0000000c, 0x0000000d, 0x0000000c, 0x0000000a, 0x0000000b, 0x0000000a, 0x00000007, 0x00000015,
|
||||
0x0000001d, 0x0000001d, 0x0000001d, 0x00000004, 0x0000001d, 0x00000004, 0x0000001d, 0x00000004, 0x0000001d, 0x00000004, 0x00000004, 0x0000001d, 0x00000004, 0x00000004, 0x0000001d, 0x0000001d,
|
||||
0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d,
|
||||
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
|
||||
0xfd7c5f7f, 0x3d7efff7, 0x9f5f7ffb, 0x7dfedddf, 0xcfcfdffd, 0xbf7f7777, 0xe7e7f7fe, 0xdfdfdddb, 0xf5f57dff, 0xefeff775, 0xf7f7fdda, 0xff5ed7df, 0xfdfd7f75, 0xff7edfda, 0x7ff5ff5d, 0x3f77dfff,
|
||||
// terms 16-31
|
||||
// 53.29^54.29=0 52.29^53.29=0 50.4^53.29=0 50.29^51.29=0 49.4^52.29=0 48.4^51.29=0 42.29^43.29=0 41.29^42.29=0
|
||||
// 40.4^43.29=0 39.4^42.29=0 38.4^41.29=0 37.4^40.29=0 55.29^56.29=0 52.4^55.29=0 51.4^54.29=0 51.29^52.29=0
|
||||
0x00000013, 0x00000012, 0x00000010, 0x00000010, 0x0000000f, 0x0000000e, 0x00000008, 0x00000007, 0x00000006, 0x00000005, 0x00000004, 0x00000003, 0x00000015, 0x00000012, 0x00000011, 0x00000011,
|
||||
0x00000014, 0x00000013, 0x00000013, 0x00000011, 0x00000012, 0x00000011, 0x00000009, 0x00000008, 0x00000009, 0x00000008, 0x00000007, 0x00000006, 0x00000016, 0x00000015, 0x00000014, 0x00000012,
|
||||
0x0000001d, 0x0000001d, 0x00000004, 0x0000001d, 0x00000004, 0x00000004, 0x0000001d, 0x0000001d, 0x00000004, 0x00000004, 0x00000004, 0x00000004, 0x0000001d, 0x00000004, 0x00000004, 0x0000001d,
|
||||
0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d,
|
||||
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
|
||||
0x9fddf7ff, 0xcfeefdff, 0xdfed77ff, 0x75fdffdf, 0xeff6ddff, 0xf7fd777f, 0xffcff5f7, 0xffe7fd7b, 0x7fdff7f5, 0xbfeffdfa, 0x5ff7ff7d, 0xaffdffde, 0x7def7fff, 0x7f6f7fff, 0xbfd7dfff, 0xe7f7ff7f,
|
||||
// terms 32-47
|
||||
// 36.4^40.29=0 53.29^56.29=1 51.29^54.29=1 50.29^52.29=1 49.29^51.29=1 48.29^50.29=1 47.29^49.29=1 46.29^48.29=1
|
||||
// 45.6^47.6=0 45.29^47.29=1 44.6^46.6=0 44.29^46.29=1 41.1^42.6=1 40.1^41.6=1 40.4^42.4=1 39.1^40.6=1
|
||||
0x00000002, 0x00000013, 0x00000011, 0x00000010, 0x0000000f, 0x0000000e, 0x0000000d, 0x0000000c, 0x0000000b, 0x0000000b, 0x0000000a, 0x0000000a, 0x00000007, 0x00000006, 0x00000006, 0x00000005,
|
||||
0x00000006, 0x00000016, 0x00000014, 0x00000012, 0x00000011, 0x00000010, 0x0000000f, 0x0000000e, 0x0000000d, 0x0000000d, 0x0000000c, 0x0000000c, 0x00000008, 0x00000007, 0x00000008, 0x00000006,
|
||||
0x00000004, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x00000006, 0x0000001d, 0x00000006, 0x0000001d, 0x00000001, 0x00000001, 0x00000004, 0x00000001,
|
||||
0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x00000006, 0x0000001d, 0x00000006, 0x0000001d, 0x00000006, 0x00000006, 0x00000004, 0x00000006,
|
||||
0x00000000, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000000, 0x00000001, 0x00000000, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001,
|
||||
0xffeefdf7, 0xffcf7fff, 0xfff5f7ff, 0xfffeddff, 0xffff777f, 0xffffdddf, 0xfffff777, 0xfffffddb, 0xffffbbbf, 0xffffff75, 0xffffeeef, 0xffffffda, 0xfbfbfeff, 0xfeffbfbf, 0x7ffffff5, 0xffbfefef,
|
||||
// terms 48-63
|
||||
// 39.4^41.4=1 38.4^40.4=1 37.4^39.4=1 36.1^37.6=1 35.4^39.29=0 63.0^64.5=1 63.1^64.6=1 62.0^63.5=1
|
||||
// 61.0^62.5=1 61.2^62.7=1 60.0^61.5=1 58.29^59.29=0 57.29^58.29=0 56.4^59.29=0 56.29^59.29=1 56.29^57.29=0
|
||||
0x00000005, 0x00000004, 0x00000003, 0x00000002, 0x00000001, 0x0000001d, 0x0000001d, 0x0000001c, 0x0000001b, 0x0000001b, 0x0000001a, 0x00000018, 0x00000017, 0x00000016, 0x00000016, 0x00000016,
|
||||
0x00000007, 0x00000006, 0x00000005, 0x00000003, 0x00000005, 0x0000001e, 0x0000001e, 0x0000001d, 0x0000001c, 0x0000001c, 0x0000001b, 0x00000019, 0x00000018, 0x00000019, 0x00000019, 0x00000017,
|
||||
0x00000004, 0x00000004, 0x00000004, 0x00000001, 0x00000004, 0x00000000, 0x00000001, 0x00000000, 0x00000000, 0x00000002, 0x00000000, 0x0000001d, 0x0000001d, 0x00000004, 0x0000001d, 0x0000001d,
|
||||
0x00000004, 0x00000004, 0x00000004, 0x00000006, 0x0000001d, 0x00000005, 0x00000006, 0x00000005, 0x00000005, 0x00000007, 0x00000005, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d,
|
||||
0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000000, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00000000,
|
||||
0xbffffffa, 0x5ffffffd, 0xaffffffe, 0xfffbefbf, 0xfff7ff7b, 0xffefff7f, 0xfffefffb, 0xfff7ffdf, 0xfffdfff7, 0xfffbffef, 0xfffefffb, 0xddffffff, 0xef7fffff, 0xd7ffffff, 0xf5ffffff, 0xf7dfffff,
|
||||
// terms 64-79
|
||||
// 55.4^58.29=0 54.4^57.29=0 53.4^56.29=0 50.6^51.1=0 48.6^50.6=0 48.29^55.29=1 47.6^49.6=0 47.6^48.1=0
|
||||
// 46.6^48.6=0 46.6^47.1=0 44.1^45.6=1 43.6^45.6=0 42.6^44.6=0 42.6^43.1=0 41.6^42.1=0 40.6^41.1=0
|
||||
0x00000015, 0x00000014, 0x00000013, 0x00000010, 0x0000000e, 0x0000000e, 0x0000000d, 0x0000000d, 0x0000000c, 0x0000000c, 0x0000000a, 0x00000009, 0x00000008, 0x00000008, 0x00000007, 0x00000006,
|
||||
0x00000018, 0x00000017, 0x00000016, 0x00000011, 0x00000010, 0x00000015, 0x0000000f, 0x0000000e, 0x0000000e, 0x0000000d, 0x0000000b, 0x0000000b, 0x0000000a, 0x00000009, 0x00000008, 0x00000007,
|
||||
0x00000004, 0x00000004, 0x00000004, 0x00000006, 0x00000006, 0x0000001d, 0x00000006, 0x00000006, 0x00000006, 0x00000006, 0x00000001, 0x00000006, 0x00000006, 0x00000006, 0x00000006, 0x00000006,
|
||||
0x0000001d, 0x0000001d, 0x0000001d, 0x00000001, 0x00000006, 0x0000001d, 0x00000006, 0x00000001, 0x00000006, 0x00000001, 0x00000006, 0x00000006, 0x00000006, 0x00000001, 0x00000001, 0x00000001,
|
||||
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
|
||||
0xedffffff, 0xf77fffff, 0xfddfffff, 0xfffbefff, 0xfffbefff, 0xffff5fff, 0xffffbbff, 0xfbffffbf, 0xffffeeff, 0xfeffffef, 0xffbfbfff, 0xfffffbbf, 0xfffffeef, 0xfbfbffff, 0xfeffbfff, 0xffbfefff,
|
||||
// terms 80-95
|
||||
// 39.4^43.29=0 38.4^42.29=0 37.1^38.6=1 37.4^41.29=0 36.4^38.4=1 35.1^36.6=1 35.3^39.28=0 61.1^62.6=1
|
||||
// 59.5^63.30=0 58.0^63.30=1 62.1^63.6=1 60.5^64.30=0 59.0^64.30=1 40.6^42.6=0 62.2^63.7=1 41.6^43.6=0
|
||||
0x00000005, 0x00000004, 0x00000003, 0x00000003, 0x00000002, 0x00000001, 0x00000001, 0x0000001b, 0x00000019, 0x00000018, 0x0000001c, 0x0000001a, 0x00000019, 0x00000006, 0x0000001c, 0x00000007,
|
||||
0x00000009, 0x00000008, 0x00000004, 0x00000007, 0x00000004, 0x00000002, 0x00000005, 0x0000001c, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001e, 0x0000001e, 0x00000008, 0x0000001d, 0x00000009,
|
||||
0x00000004, 0x00000004, 0x00000001, 0x00000004, 0x00000004, 0x00000001, 0x00000003, 0x00000001, 0x00000005, 0x00000000, 0x00000001, 0x00000005, 0x00000000, 0x00000006, 0x00000002, 0x00000006,
|
||||
0x0000001d, 0x0000001d, 0x00000006, 0x0000001d, 0x00000004, 0x00000006, 0x0000001c, 0x00000006, 0x0000001e, 0x0000001e, 0x00000006, 0x0000001e, 0x0000001e, 0x00000006, 0x00000007, 0x00000006,
|
||||
0x00000000, 0x00000000, 0x00000001, 0x00000000, 0x00000001, 0x00000001, 0x00000000, 0x00000001, 0x00000000, 0x00000001, 0x00000001, 0x00000000, 0x00000001, 0x00000000, 0x00000001, 0x00000000,
|
||||
0xfdff7fff, 0xff7fdfff, 0xffffbeff, 0xffdff7ff, 0xd7ffffff, 0xfffffbef, 0xfff7dfff, 0xfffffffe, 0xfffffffe, 0xfffffffe, 0xfffffffd, 0xfffffffd, 0xfffffffd, 0xffffffef, 0xffffffbf, 0xffffffbf,
|
||||
// terms 96-111
|
||||
// 63.2^64.7=1 48.6^49.1=0 49.6^50.1=0 42.1^50.1=1 39.6^40.1=0 38.1^40.1=1 36.4^37.4=1 43.1^51.1=1
|
||||
// 37.4^38.4=1 51.6^52.1=0 49.6^51.6=0 37.1^37.6=0 35.5^39.30=0 38.4^39.4=1 47.1^51.1=1 36.3^40.28=0
|
||||
0x0000001d, 0x0000000e, 0x0000000f, 0x00000008, 0x00000005, 0x00000004, 0x00000002, 0x00000009, 0x00000003, 0x00000011, 0x0000000f, 0x00000003, 0x00000001, 0x00000004, 0x0000000d, 0x00000002,
|
||||
0x0000001e, 0x0000000f, 0x00000010, 0x00000010, 0x00000006, 0x00000006, 0x00000003, 0x00000011, 0x00000004, 0x00000012, 0x00000011, 0x00000003, 0x00000005, 0x00000005, 0x00000011, 0x00000006,
|
||||
0x00000002, 0x00000006, 0x00000006, 0x00000001, 0x00000006, 0x00000001, 0x00000004, 0x00000001, 0x00000004, 0x00000006, 0x00000006, 0x00000001, 0x00000005, 0x00000004, 0x00000001, 0x00000003,
|
||||
0x00000007, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000004, 0x00000001, 0x00000004, 0x00000001, 0x00000006, 0x00000006, 0x0000001e, 0x00000004, 0x00000001, 0x0000001c,
|
||||
0x00000001, 0x00000000, 0x00000000, 0x00000001, 0x00000000, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00000001, 0x00000000,
|
||||
0xfffffeff, 0xfffffeff, 0xfffffbff, 0xfffffbff, 0xfffffbff, 0xfffffbff, 0xfffff7ff, 0xffffefff, 0xffffdfff, 0xffffbfff, 0xffffbfff, 0xffffbfff, 0xffffbfff, 0xffff7fff, 0xfffbffff, 0xffefffff,
|
||||
// terms 112-127
|
||||
// 35.30^40.28=1 37.3^41.28=0 36.30^41.28=1 53.6^54.1=0 51.6^53.6=0 50.1^54.1=1 45.6^46.1=0 37.5^41.30=0
|
||||
// 36.0^41.30=1 55.29^58.29=1 38.3^42.28=0 37.30^42.28=1 54.6^55.1=0 52.6^54.6=0 51.1^55.1=1 45.1^47.1=1
|
||||
0x00000001, 0x00000003, 0x00000002, 0x00000013, 0x00000011, 0x00000010, 0x0000000b, 0x00000003, 0x00000002, 0x00000015, 0x00000004, 0x00000003, 0x00000014, 0x00000012, 0x00000011, 0x0000000b,
|
||||
0x00000006, 0x00000007, 0x00000007, 0x00000014, 0x00000013, 0x00000014, 0x0000000c, 0x00000007, 0x00000007, 0x00000018, 0x00000008, 0x00000008, 0x00000015, 0x00000014, 0x00000015, 0x0000000d,
|
||||
0x0000001e, 0x00000003, 0x0000001e, 0x00000006, 0x00000006, 0x00000001, 0x00000006, 0x00000005, 0x00000000, 0x0000001d, 0x00000003, 0x0000001e, 0x00000006, 0x00000006, 0x00000001, 0x00000001,
|
||||
0x0000001c, 0x0000001c, 0x0000001c, 0x00000001, 0x00000006, 0x00000001, 0x00000001, 0x0000001e, 0x0000001e, 0x0000001d, 0x0000001c, 0x0000001c, 0x00000001, 0x00000006, 0x00000001, 0x00000001,
|
||||
0x00000001, 0x00000000, 0x00000001, 0x00000000, 0x00000000, 0x00000001, 0x00000000, 0x00000000, 0x00000001, 0x00000001, 0x00000000, 0x00000001, 0x00000000, 0x00000000, 0x00000001, 0x00000001,
|
||||
0xffefffff, 0xffdfffff, 0xffdfffff, 0xffbfffff, 0xffbfffff, 0xffbfffff, 0xffbfffff, 0xffbfffff, 0xffbfffff, 0xff7fffff, 0xff7fffff, 0xff7fffff, 0xfeffffff, 0xfeffffff, 0xfeffffff, 0xfeffffff,
|
||||
// terms 128-143
|
||||
// 38.5^42.30=0 37.0^42.30=1 39.3^43.28=0 38.30^43.28=1 55.6^56.1=0 53.6^55.6=0 52.1^56.1=1 46.1^48.1=1
|
||||
// 39.5^43.30=0 38.0^43.30=1 59.29^60.29=0 40.3^44.28=0 40.4^44.29=0 39.30^44.28=1 58.29^61.29=1 57.4^61.29=0
|
||||
0x00000004, 0x00000003, 0x00000005, 0x00000004, 0x00000015, 0x00000013, 0x00000012, 0x0000000c, 0x00000005, 0x00000004, 0x00000019, 0x00000006, 0x00000006, 0x00000005, 0x00000018, 0x00000017,
|
||||
0x00000008, 0x00000008, 0x00000009, 0x00000009, 0x00000016, 0x00000015, 0x00000016, 0x0000000e, 0x00000009, 0x00000009, 0x0000001a, 0x0000000a, 0x0000000a, 0x0000000a, 0x0000001b, 0x0000001b,
|
||||
0x00000005, 0x00000000, 0x00000003, 0x0000001e, 0x00000006, 0x00000006, 0x00000001, 0x00000001, 0x00000005, 0x00000000, 0x0000001d, 0x00000003, 0x00000004, 0x0000001e, 0x0000001d, 0x00000004,
|
||||
0x0000001e, 0x0000001e, 0x0000001c, 0x0000001c, 0x00000001, 0x00000006, 0x00000001, 0x00000001, 0x0000001e, 0x0000001e, 0x0000001d, 0x0000001c, 0x0000001d, 0x0000001c, 0x0000001d, 0x0000001d,
|
||||
0x00000000, 0x00000001, 0x00000000, 0x00000001, 0x00000000, 0x00000000, 0x00000001, 0x00000001, 0x00000000, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00000001, 0x00000000,
|
||||
0xfeffffff, 0xfeffffff, 0xfdffffff, 0xfdffffff, 0xfbffffff, 0xfbffffff, 0xfbffffff, 0xfbffffff, 0xfbffffff, 0xfbffffff, 0xf7ffffff, 0xf7ffffff, 0xf7ffffff, 0xf7ffffff, 0xefffffff, 0xefffffff,
|
||||
// terms 144-159
|
||||
// 41.3^45.28=0 41.4^45.29=0 58.4^62.29=0 42.3^46.28=0 42.4^46.29=0 59.4^63.29=0 57.4^59.29=0 43.3^47.28=0
|
||||
// 43.4^47.29=0 60.4^64.29=0 44.3^48.28=0 44.4^48.29=0 35.0^35.0=0 35.0^35.0=0 35.0^35.0=0 35.0^35.0=0
|
||||
0x00000007, 0x00000007, 0x00000018, 0x00000008, 0x00000008, 0x00000019, 0x00000017, 0x00000009, 0x00000009, 0x0000001a, 0x0000000a, 0x0000000a, 0x00000001, 0x00000001, 0x00000001, 0x00000001,
|
||||
0x0000000b, 0x0000000b, 0x0000001c, 0x0000000c, 0x0000000c, 0x0000001d, 0x00000019, 0x0000000d, 0x0000000d, 0x0000001e, 0x0000000e, 0x0000000e, 0x00000001, 0x00000001, 0x00000001, 0x00000001,
|
||||
0x00000003, 0x00000004, 0x00000004, 0x00000003, 0x00000004, 0x00000004, 0x00000004, 0x00000003, 0x00000004, 0x00000004, 0x00000003, 0x00000004, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
|
||||
0x0000001c, 0x0000001d, 0x0000001d, 0x0000001c, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001c, 0x0000001d, 0x0000001d, 0x0000001c, 0x0000001d, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
|
||||
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
|
||||
0xefffffff, 0xefffffff, 0xdfffffff, 0xdfffffff, 0xdfffffff, 0xbfffffff, 0xbfffffff, 0xbfffffff, 0xbfffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
|
||||
}
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
//go:build noasm || !gc || !amd64
|
||||
|
||||
package ubc
|
||||
|
||||
// CalculateDvMask takes as input an expanded message block and
|
||||
// verifies the unavoidable bitconditions for all listed DVs. It returns
|
||||
// a dvmask where each bit belonging to a DV is set if all unavoidable
|
||||
// bitconditions for that DV have been met.
|
||||
func CalculateDvMask(W *[80]uint32) uint32 {
|
||||
if W == nil {
|
||||
return 0
|
||||
}
|
||||
return calculateDvMaskGeneric(W)
|
||||
}
|
||||
+8
-4
@@ -23,12 +23,16 @@ type DvInfo struct {
|
||||
Dm [80]uint32
|
||||
}
|
||||
|
||||
// calculateDvMaskGeneric is the portable implementation of CalculateDvMask.
|
||||
// The AVX-512 kernel runs the same bit tests from avx512Terms, and
|
||||
// TestAVX512MatchesGeneric holds the two to the same results.
|
||||
// CalculateDvMask takes as input an expanded message block and
|
||||
// verifies the unavoidable bitconditions for all listed DVs. It returns
|
||||
// a dvmask where each bit belonging to a DV is set if all unavoidable
|
||||
// bitconditions for that DV have been met.
|
||||
//
|
||||
//go:nosplit
|
||||
func calculateDvMaskGeneric(W *[80]uint32) uint32 {
|
||||
func CalculateDvMask(W *[80]uint32) uint32 {
|
||||
if W == nil {
|
||||
return 0
|
||||
}
|
||||
mask := uint32(0xFFFFFFFF)
|
||||
mask &= (((((W[44] ^ W[45]) >> 29) & 1) - 1) | ^(DV_I_48_0_bit | DV_I_51_0_bit | DV_I_52_0_bit | DV_II_45_0_bit | DV_II_46_0_bit | DV_II_50_0_bit | DV_II_51_0_bit))
|
||||
mask &= (((((W[49] ^ W[50]) >> 29) & 1) - 1) | ^(DV_I_46_0_bit | DV_II_45_0_bit | DV_II_50_0_bit | DV_II_51_0_bit | DV_II_55_0_bit | DV_II_56_0_bit))
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
*
|
||||
!.git/
|
||||
!build/root.tgz
|
||||
!cmd/
|
||||
!config/
|
||||
!internal/
|
||||
!mod/
|
||||
!pkg/
|
||||
!regclient/
|
||||
!scheme/
|
||||
!types/
|
||||
!vendor/
|
||||
!go.*
|
||||
!*.go
|
||||
!Makefile
|
||||
@@ -0,0 +1,5 @@
|
||||
artifacts/
|
||||
bin/
|
||||
output/
|
||||
vendor/
|
||||
.regctl_conf_ci.json
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
# all lists use a `-`
|
||||
MD004:
|
||||
style: dash
|
||||
|
||||
# allow tabs in code blocks (for Go)
|
||||
MD010:
|
||||
code_blocks: false
|
||||
|
||||
# disable line length, prefer one sentence per line for PRs
|
||||
MD013: false
|
||||
|
||||
# emphasis with underscore (`_emphasis_`)
|
||||
MD049:
|
||||
style: "underscore"
|
||||
|
||||
# bold with asterisk (`**bold**`)
|
||||
MD050:
|
||||
style: "asterisk"
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
GoVersionOverride = "1.26.3"
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
{"name":"docker-arg-alpine-digest","key":"docker.io/library/alpine:3.23.4","version":"sha256:5b10f432ef3da1b8d4c7eb6c487f2f5a8f096bc91145e68878dd4a5019afde11"}
|
||||
{"name":"docker-arg-alpine-tag","key":"docker.io/library/alpine","version":"3.23.4"}
|
||||
{"name":"docker-arg-ecr","key":"https://github.com/awslabs/amazon-ecr-credential-helper.git","version":"v0.12.0"}
|
||||
{"name":"docker-arg-gcr","key":"https://github.com/GoogleCloudPlatform/docker-credential-gcr.git","version":"v2.1.32"}
|
||||
{"name":"docker-arg-go-digest","key":"docker.io/library/golang:1.26.3-alpine","version":"sha256:91eda9776261207ea25fd06b5b7fed8d397dd2c0a283e77f2ab6e91bfa71079d"}
|
||||
{"name":"docker-arg-go-tag","key":"docker.io/library/golang","version":"1.26.3"}
|
||||
{"name":"docker-arg-lunajson","key":"https://github.com/grafi-tt/lunajson.git:master","version":"e3a9666eb1275741e887e29926b144f8daee3bef"}
|
||||
{"name":"docker-arg-semver","key":"https://github.com/kikito/semver.lua.git:master","version":"a4b708ba243208d46e575da870af969dca46a94d"}
|
||||
{"name":"gha-alpine-digest","key":"docker.io/library/alpine:3.23.4","version":"sha256:5b10f432ef3da1b8d4c7eb6c487f2f5a8f096bc91145e68878dd4a5019afde11"}
|
||||
{"name":"gha-alpine-tag-base","key":"docker.io/library/alpine","version":"3"}
|
||||
{"name":"gha-alpine-tag-comment","key":"docker.io/library/alpine","version":"3.23.4"}
|
||||
{"name":"gha-cosign-version","key":"https://github.com/sigstore/cosign.git","version":"v3.0.6"}
|
||||
{"name":"gha-golang-matrix","key":"golang-matrix","version":"[\"1.25\", \"1.26\"]"}
|
||||
{"name":"gha-golang-release","key":"golang-latest","version":"1.26"}
|
||||
{"name":"gha-syft-version","key":"docker.io/anchore/syft","version":"v1.44.0"}
|
||||
{"name":"gha-uses-commit","key":"https://github.com/actions/checkout.git:v6.0.2","version":"de0fac2e4500dabe0009e67214ff5f5447ce83dd"}
|
||||
{"name":"gha-uses-commit","key":"https://github.com/actions/setup-go.git:v6.4.0","version":"4a3601121dd01d1626a1e23e37211e3254c1c06c"}
|
||||
{"name":"gha-uses-commit","key":"https://github.com/actions/stale.git:v10.3.0","version":"eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899"}
|
||||
{"name":"gha-uses-commit","key":"https://github.com/actions/upload-artifact.git:v7.0.1","version":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a"}
|
||||
{"name":"gha-uses-commit","key":"https://github.com/anchore/sbom-action.git:v0.24.0","version":"e22c389904149dbc22b58101806040fa8d37a610"}
|
||||
{"name":"gha-uses-commit","key":"https://github.com/docker/build-push-action.git:v7.2.0","version":"f9f3042f7e2789586610d6e8b85c8f03e5195baf"}
|
||||
{"name":"gha-uses-commit","key":"https://github.com/docker/login-action.git:v4.2.0","version":"650006c6eb7dba73a995cc03b0b2d7f5ca915bee"}
|
||||
{"name":"gha-uses-commit","key":"https://github.com/docker/setup-buildx-action.git:v4.1.0","version":"d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5"}
|
||||
{"name":"gha-uses-commit","key":"https://github.com/regclient/actions.git:main","version":"c70ad64367908075211b10dcd2ab9fad4bfa1816"}
|
||||
{"name":"gha-uses-commit","key":"https://github.com/sigstore/cosign-installer.git:v4.1.2","version":"6f9f17788090df1f26f669e9d70d6ae9567deba6"}
|
||||
{"name":"gha-uses-commit","key":"https://github.com/softprops/action-gh-release.git:v3.0.0","version":"b4309332981a82ec1c5618f44dd2e27cc8bfbfda"}
|
||||
{"name":"gha-uses-semver","key":"https://github.com/actions/checkout.git","version":"v6.0.2"}
|
||||
{"name":"gha-uses-semver","key":"https://github.com/actions/setup-go.git","version":"v6.4.0"}
|
||||
{"name":"gha-uses-semver","key":"https://github.com/actions/stale.git","version":"v10.3.0"}
|
||||
{"name":"gha-uses-semver","key":"https://github.com/actions/upload-artifact.git","version":"v7.0.1"}
|
||||
{"name":"gha-uses-semver","key":"https://github.com/anchore/sbom-action.git","version":"v0.24.0"}
|
||||
{"name":"gha-uses-semver","key":"https://github.com/docker/build-push-action.git","version":"v7.2.0"}
|
||||
{"name":"gha-uses-semver","key":"https://github.com/docker/login-action.git","version":"v4.2.0"}
|
||||
{"name":"gha-uses-semver","key":"https://github.com/docker/setup-buildx-action.git","version":"v4.1.0"}
|
||||
{"name":"gha-uses-semver","key":"https://github.com/sigstore/cosign-installer.git","version":"v4.1.2"}
|
||||
{"name":"gha-uses-semver","key":"https://github.com/softprops/action-gh-release.git","version":"v3.0.0"}
|
||||
{"name":"go-mod-golang-release","key":"golang-oldest","version":"1.25.0"}
|
||||
{"name":"makefile-ci-distribution","key":"docker.io/library/registry","version":"3.1.1"}
|
||||
{"name":"makefile-ci-zot","key":"ghcr.io/project-zot/zot-linux-amd64","version":"v2.1.17"}
|
||||
{"name":"makefile-go-vulncheck","key":"https://go.googlesource.com/vuln.git","version":"v1.3.0"}
|
||||
{"name":"makefile-gofumpt","key":"https://github.com/mvdan/gofumpt.git","version":"v0.10.0"}
|
||||
{"name":"makefile-gomajor","key":"https://github.com/icholy/gomajor.git","version":"v0.15.0"}
|
||||
{"name":"makefile-gosec","key":"https://github.com/securego/gosec.git","version":"v2.26.1"}
|
||||
{"name":"makefile-markdown-lint","key":"docker.io/davidanson/markdownlint-cli2","version":"v0.22.1"}
|
||||
{"name":"makefile-osv-scanner","key":"https://github.com/google/osv-scanner.git","version":"v2.3.8"}
|
||||
{"name":"makefile-staticcheck","key":"https://github.com/dominikh/go-tools.git","version":"v0.7.0"}
|
||||
{"name":"makefile-syft-container-digest","key":"anchore/syft:v1.44.0","version":"sha256:86fde6445b483d902fe011dd9f68c4987dd94e07da1e9edc004e3c2422650de6"}
|
||||
{"name":"makefile-syft-container-tag","key":"anchore/syft","version":"v1.44.0"}
|
||||
{"name":"makefile-syft-version","key":"docker.io/anchore/syft","version":"v1.44.0"}
|
||||
{"name":"osv-golang-release","key":"docker.io/library/golang","version":"1.26.3"}
|
||||
{"name":"shell-alpine-digest","key":"docker.io/library/alpine:3.23.4","version":"sha256:5b10f432ef3da1b8d4c7eb6c487f2f5a8f096bc91145e68878dd4a5019afde11"}
|
||||
{"name":"shell-alpine-tag-base","key":"docker.io/library/alpine","version":"3"}
|
||||
{"name":"shell-alpine-tag-comment","key":"docker.io/library/alpine","version":"3.23.4"}
|
||||
+346
@@ -0,0 +1,346 @@
|
||||
files:
|
||||
"build/Dockerfile*":
|
||||
processors:
|
||||
- docker-arg-alpine-tag
|
||||
- docker-arg-alpine-digest
|
||||
- docker-arg-go-tag
|
||||
- docker-arg-go-digest
|
||||
- docker-arg-ecr
|
||||
- docker-arg-gcr
|
||||
- docker-arg-lunajson
|
||||
- docker-arg-semver
|
||||
"build/oci-image.sh":
|
||||
processors:
|
||||
- shell-alpine-tag-base
|
||||
- shell-alpine-tag-comment
|
||||
- shell-alpine-digest
|
||||
".github/workflows/*.yml":
|
||||
processors:
|
||||
- gha-golang-matrix
|
||||
- gha-golang-release
|
||||
- gha-uses-vx
|
||||
- gha-uses-semver
|
||||
- gha-uses-commit
|
||||
- gha-syft-version
|
||||
- gha-cosign-version
|
||||
- gha-alpine-tag-base
|
||||
- gha-alpine-tag-comment
|
||||
- gha-alpine-digest
|
||||
"Makefile":
|
||||
processors:
|
||||
- makefile-gofumpt
|
||||
- makefile-gomajor
|
||||
- makefile-go-vulncheck
|
||||
- makefile-markdown-lint
|
||||
- makefile-gosec
|
||||
- makefile-osv-scanner
|
||||
- makefile-staticcheck
|
||||
- makefile-syft-version
|
||||
- makefile-syft-container-tag
|
||||
- makefile-syft-container-digest
|
||||
- makefile-ci-distribution
|
||||
- makefile-ci-zot
|
||||
"go.mod":
|
||||
processors:
|
||||
- go-mod-golang-release
|
||||
".osv-scanner.toml":
|
||||
processors:
|
||||
- osv-golang-release
|
||||
|
||||
x-processor-tmpl:
|
||||
git-commit: &git-commit
|
||||
key: "{{ .SourceArgs.url }}:{{ .SourceArgs.ref }}"
|
||||
scan: "regexp"
|
||||
source: "git-commit"
|
||||
filter:
|
||||
expr: "^{{ .SourceArgs.ref }}$"
|
||||
git-tag-semver: &git-tag-semver
|
||||
key: "{{ .SourceArgs.url }}"
|
||||
scan: "regexp"
|
||||
source: "git-tag"
|
||||
filter:
|
||||
expr: '^v?\d+\.\d+\.\d+$'
|
||||
sort:
|
||||
method: "semver"
|
||||
registry-digest: ®istry-digest
|
||||
key: "{{ .SourceArgs.image }}"
|
||||
scan: "regexp"
|
||||
source: "registry-digest"
|
||||
registry-tag-semver: ®istry-tag-semver
|
||||
key: "{{ .SourceArgs.repo }}"
|
||||
scan: "regexp"
|
||||
source: "registry-tag"
|
||||
filter:
|
||||
expr: '^v?\d+\.\d+\.\d+$'
|
||||
sort:
|
||||
method: "semver"
|
||||
|
||||
processors:
|
||||
docker-arg-alpine-tag:
|
||||
<<: *registry-tag-semver
|
||||
scanArgs:
|
||||
regexp: '^ARG ALPINE_VER=(?P<Version>v?\d+\.\d+\.\d+)@(?P<SHA>sha256:[0-9a-f]+)\s*$'
|
||||
sourceArgs:
|
||||
repo: "docker.io/library/alpine"
|
||||
docker-arg-alpine-digest:
|
||||
<<: *registry-digest
|
||||
scanArgs:
|
||||
regexp: '^ARG ALPINE_VER=(?P<Tag>v?\d+\.\d+\.\d+)@(?P<Version>sha256:[0-9a-f]+)\s*$'
|
||||
sourceArgs:
|
||||
image: "docker.io/library/alpine:{{.ScanMatch.Tag}}"
|
||||
docker-arg-go-tag:
|
||||
<<: *registry-tag-semver
|
||||
scanArgs:
|
||||
regexp: '^ARG GO_VER=(?P<Version>[a-z0-9\-\.]+)-alpine@(?P<SHA>sha256:[0-9a-f]+)\s*$'
|
||||
sourceArgs:
|
||||
repo: "docker.io/library/golang"
|
||||
docker-arg-go-digest:
|
||||
<<: *registry-digest
|
||||
scanArgs:
|
||||
regexp: '^ARG GO_VER=(?P<Tag>[a-z0-9\-\.]+)@(?P<Version>sha256:[0-9a-f]+)\s*$'
|
||||
sourceArgs:
|
||||
image: "docker.io/library/golang:{{.ScanMatch.Tag}}"
|
||||
docker-arg-ecr:
|
||||
<<: *git-tag-semver
|
||||
scanArgs:
|
||||
regexp: '^ARG ECR_HELPER_VER=(?P<Version>v?\d+\.\d+\.\d+)\s*$'
|
||||
sourceArgs:
|
||||
url: "https://github.com/awslabs/amazon-ecr-credential-helper.git"
|
||||
# get the version for the ecr-login nested package in the repo
|
||||
filter:
|
||||
expr: '^ecr-login/v?\d+\.\d+\.\d+$'
|
||||
# sort and output only the version number without the package name prefix
|
||||
sort:
|
||||
method: "semver"
|
||||
template: '{{ index (split . "/") 1 }}'
|
||||
template: '{{ index (split .Version "/") 1 }}'
|
||||
docker-arg-gcr:
|
||||
<<: *git-tag-semver
|
||||
scanArgs:
|
||||
regexp: '^ARG GCR_HELPER_VER=(?P<Version>v?\d+\.\d+\.\d+)\s*$'
|
||||
sourceArgs:
|
||||
url: "https://github.com/GoogleCloudPlatform/docker-credential-gcr.git"
|
||||
docker-arg-lunajson:
|
||||
<<: *git-commit
|
||||
scanArgs:
|
||||
regexp: '^ARG LUNAJSON_COMMIT=(?P<Version>[0-9a-f]+)\s*$'
|
||||
sourceArgs:
|
||||
url: "https://github.com/grafi-tt/lunajson.git"
|
||||
ref: master
|
||||
docker-arg-semver:
|
||||
<<: *git-commit
|
||||
scanArgs:
|
||||
regexp: '^ARG SEMVER_COMMIT=(?P<Version>[0-9a-f]+)\s*$'
|
||||
sourceArgs:
|
||||
url: "https://github.com/kikito/semver.lua.git"
|
||||
ref: master
|
||||
|
||||
gha-alpine-digest:
|
||||
<<: *registry-digest
|
||||
scanArgs:
|
||||
regexp: '^\s*ALPINE_DIGEST: "(?P<Version>sha256:[0-9a-f]+)"\s*#\s*(?P<Tag>\d+\.\d+\.\d+)\s*$'
|
||||
sourceArgs:
|
||||
image: "docker.io/library/alpine:{{ .ScanMatch.Tag }}"
|
||||
gha-alpine-tag-base:
|
||||
<<: *registry-tag-semver
|
||||
scanArgs:
|
||||
regexp: '^\s*ALPINE_NAME: "alpine:(?P<Version>v?\d+)"\s*$'
|
||||
sourceArgs:
|
||||
repo: "docker.io/library/alpine"
|
||||
# only return the major version number in the tag to support detecting a change in the base image
|
||||
template: '{{ index ( split .Version "." ) 0 }}'
|
||||
gha-alpine-tag-comment:
|
||||
<<: *registry-tag-semver
|
||||
scanArgs:
|
||||
regexp: '^\s*ALPINE_DIGEST: "(?P<Digest>sha256:[0-9a-f]+)"\s*#\s*(?P<Version>v?\d+\.\d+\.\d+)\s*$'
|
||||
sourceArgs:
|
||||
repo: "docker.io/library/alpine"
|
||||
gha-cosign-version:
|
||||
<<: *git-tag-semver
|
||||
scanArgs:
|
||||
regexp: '^\s*cosign-release: "(?P<Version>v?[0-9\.]+)"\s*$'
|
||||
sourceArgs:
|
||||
url: "https://github.com/sigstore/cosign.git"
|
||||
filter:
|
||||
expr: '^v?3\.\d+\.\d+$' # pin to v3, v4 will remove support for older clients
|
||||
gha-golang-matrix:
|
||||
<<: *registry-tag-semver
|
||||
key: "golang-matrix"
|
||||
scanArgs:
|
||||
regexp: '^\s*gover: (?P<Version>\[["0-9, \.]+\])\s*$'
|
||||
sourceArgs:
|
||||
repo: "docker.io/library/golang"
|
||||
filter:
|
||||
expr: '^v?\d+\.\d+$'
|
||||
template: '["{{ index .VerMap ( index .VerList 1 ) }}", "{{ index .VerMap ( index .VerList 0 ) }}"]'
|
||||
gha-golang-release:
|
||||
<<: *registry-tag-semver
|
||||
key: "golang-latest"
|
||||
scanArgs:
|
||||
regexp: '^\s*RELEASE_GO_VER: "(?P<Version>v?[0-9\.]+)"\s*$'
|
||||
sourceArgs:
|
||||
repo: "docker.io/library/golang"
|
||||
filter:
|
||||
expr: '^v?\d+\.\d+$'
|
||||
gha-syft-version:
|
||||
<<: *registry-tag-semver
|
||||
scanArgs:
|
||||
regexp: '^\s*syft-version: "(?P<Version>v?[0-9\.]+)"\s*$'
|
||||
sourceArgs:
|
||||
repo: "docker.io/anchore/syft"
|
||||
gha-uses-vx:
|
||||
<<: *git-tag-semver
|
||||
scanArgs:
|
||||
regexp: '^\s+-?\s+uses: (?P<Repo>[^@/]+/[^@/]+)[^@]*@(?P<Commit>[0-9a-f]+)\s+#\s+(?P<Version>v?\d+)\s*$'
|
||||
sourceArgs:
|
||||
url: "https://github.com/{{ .ScanMatch.Repo }}.git"
|
||||
filter:
|
||||
expr: '^v?\d+$'
|
||||
gha-uses-semver:
|
||||
<<: *git-tag-semver
|
||||
scanArgs:
|
||||
regexp: '^\s+-?\s+uses: (?P<Repo>[^@/]+/[^@/]+)[^@]*@(?P<Commit>[0-9a-f]+)\s+#\s+(?P<Version>v?\d+\.\d+\.\d+)\s*$'
|
||||
sourceArgs:
|
||||
url: "https://github.com/{{ .ScanMatch.Repo }}.git"
|
||||
gha-uses-commit:
|
||||
<<: *git-commit
|
||||
scanArgs:
|
||||
regexp: '^\s+-?\s+uses: (?P<Repo>[^@/]+/[^@/]+)[^@]*@(?P<Version>[0-9a-f]+)\s+#\s+(?P<Ref>[\w\d\.]+)\s*$'
|
||||
sourceArgs:
|
||||
url: "https://github.com/{{ .ScanMatch.Repo }}.git"
|
||||
ref: "{{ .ScanMatch.Ref }}"
|
||||
|
||||
go-mod-golang-release:
|
||||
<<: *registry-tag-semver
|
||||
key: "golang-oldest"
|
||||
scanArgs:
|
||||
regexp: '^go (?P<Version>[0-9\.]+)\s*$'
|
||||
sourceArgs:
|
||||
repo: "docker.io/library/golang"
|
||||
filter:
|
||||
expr: '^\d+\.\d+$'
|
||||
template: '{{ index .VerMap ( index .VerList 1 ) }}.0'
|
||||
|
||||
makefile-ci-distribution:
|
||||
<<: *registry-tag-semver
|
||||
scanArgs:
|
||||
regexp: '^CI_DISTRIBUTION_VER\?=(?P<Version>v?[0-9\.]+)\s*$'
|
||||
sourceArgs:
|
||||
repo: "docker.io/library/registry"
|
||||
makefile-ci-zot:
|
||||
<<: *registry-tag-semver
|
||||
scanArgs:
|
||||
regexp: '^CI_ZOT_VER\?=(?P<Version>v?[0-9\.]+)\s*$'
|
||||
sourceArgs:
|
||||
repo: "ghcr.io/project-zot/zot-linux-amd64"
|
||||
makefile-gofumpt:
|
||||
<<: *git-tag-semver
|
||||
scanArgs:
|
||||
regexp: '^GOFUMPT_VER\?=(?P<Version>v?[0-9\.]+)\s*$'
|
||||
sourceArgs:
|
||||
url: "https://github.com/mvdan/gofumpt.git"
|
||||
makefile-gomajor:
|
||||
<<: *git-tag-semver
|
||||
scanArgs:
|
||||
regexp: '^GOMAJOR_VER\?=(?P<Version>v?[0-9\.]+)\s*$'
|
||||
sourceArgs:
|
||||
url: "https://github.com/icholy/gomajor.git"
|
||||
makefile-gosec:
|
||||
<<: *git-tag-semver
|
||||
scanArgs:
|
||||
regexp: '^GOSEC_VER\?=(?P<Version>v?[0-9\.]+)\s*$'
|
||||
sourceArgs:
|
||||
url: "https://github.com/securego/gosec.git"
|
||||
makefile-go-vulncheck:
|
||||
<<: *git-tag-semver
|
||||
scanArgs:
|
||||
regexp: '^GO_VULNCHECK_VER\?=(?P<Version>v?[0-9\.]+)\s*$'
|
||||
sourceArgs:
|
||||
url: "https://go.googlesource.com/vuln.git"
|
||||
makefile-markdown-lint:
|
||||
<<: *registry-tag-semver
|
||||
scanArgs:
|
||||
regexp: '^MARKDOWN_LINT_VER\?=(?P<Version>v?[0-9\.]+)\s*$'
|
||||
sourceArgs:
|
||||
repo: "docker.io/davidanson/markdownlint-cli2"
|
||||
makefile-osv-scanner:
|
||||
<<: *git-tag-semver
|
||||
scanArgs:
|
||||
regexp: '^OSV_SCANNER_VER\?=(?P<Version>v?[0-9\.]+)\s*$'
|
||||
sourceArgs:
|
||||
url: "https://github.com/google/osv-scanner.git"
|
||||
makefile-staticcheck:
|
||||
<<: *git-tag-semver
|
||||
scanArgs:
|
||||
regexp: '^STATICCHECK_VER\?=(?P<Version>v?[0-9\.]+)\s*$'
|
||||
sourceArgs:
|
||||
url: "https://github.com/dominikh/go-tools.git"
|
||||
filter:
|
||||
# repo also has dated tags, ignore versions without a preceding "v"
|
||||
expr: '^v\d+\.\d+\.\d+$'
|
||||
makefile-syft-container-tag:
|
||||
<<: *registry-tag-semver
|
||||
scanArgs:
|
||||
regexp: '^SYFT_CONTAINER\?=(?P<Repo>[^:]*):(?P<Version>v?[0-9\.]+)@(?P<Digest>sha256:[0-9a-f]+)\s*$'
|
||||
sourceArgs:
|
||||
repo: "{{ .ScanMatch.Repo }}"
|
||||
makefile-syft-container-digest:
|
||||
<<: *registry-digest
|
||||
scanArgs:
|
||||
regexp: '^SYFT_CONTAINER\?=(?P<Image>[^:]*):(?P<Tag>v?[0-9\.]+)@(?P<Version>sha256:[0-9a-f]+)\s*$'
|
||||
sourceArgs:
|
||||
image: "{{ .ScanMatch.Image }}:{{.ScanMatch.Tag}}"
|
||||
makefile-syft-version:
|
||||
<<: *registry-tag-semver
|
||||
scanArgs:
|
||||
regexp: '^SYFT_VERSION\?=(?P<Version>v[0-9\.]+)\s*$'
|
||||
sourceArgs:
|
||||
repo: "docker.io/anchore/syft"
|
||||
|
||||
osv-golang-release:
|
||||
<<: *registry-tag-semver
|
||||
scanArgs:
|
||||
regexp: '^GoVersionOverride = "(?P<Version>v?[0-9\.]+)"\s*$'
|
||||
sourceArgs:
|
||||
repo: "docker.io/library/golang"
|
||||
|
||||
shell-alpine-tag-base:
|
||||
<<: *registry-tag-semver
|
||||
scanArgs:
|
||||
regexp: '^\s*ALPINE_NAME="alpine:(?P<Version>v?\d+)"\s*$'
|
||||
sourceArgs:
|
||||
repo: "docker.io/library/alpine"
|
||||
# only return the major version number in the tag to support detecting a change in the base image
|
||||
template: '{{ index ( split .Version "." ) 0 }}'
|
||||
shell-alpine-tag-comment:
|
||||
<<: *registry-tag-semver
|
||||
scanArgs:
|
||||
regexp: '^\s*ALPINE_DIGEST="(?P<Digest>sha256:[0-9a-f]+)"\s*#\s*(?P<Version>v?\d+\.\d+\.\d+)\s*$'
|
||||
sourceArgs:
|
||||
repo: "docker.io/library/alpine"
|
||||
shell-alpine-digest:
|
||||
<<: *registry-digest
|
||||
scanArgs:
|
||||
regexp: '^\s*ALPINE_DIGEST="(?P<Version>sha256:[0-9a-f]+)"\s*#\s*(?P<Tag>\d+\.\d+\.\d+)\s*$'
|
||||
sourceArgs:
|
||||
image: "docker.io/library/alpine:{{ .ScanMatch.Tag }}"
|
||||
|
||||
scans:
|
||||
regexp:
|
||||
type: "regexp"
|
||||
|
||||
sources:
|
||||
git-commit:
|
||||
type: "git"
|
||||
args:
|
||||
type: "commit"
|
||||
git-tag:
|
||||
type: "git"
|
||||
args:
|
||||
type: "tag"
|
||||
registry-digest:
|
||||
type: "registry"
|
||||
registry-tag:
|
||||
type: "registry"
|
||||
args:
|
||||
type: "tag"
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user