Merge pull request #28106 from Luap99/vendor

vendor: update go.podman.io/... and buildah to latest
This commit is contained in:
Miloslav Trmač
2026-02-18 20:27:18 +01:00
committed by GitHub
64 changed files with 3354 additions and 1960 deletions

View File

@@ -608,10 +608,12 @@ func buildFlagsWrapperToOptions(c *cobra.Command, contextDir string, flags *Buil
SBOMScanOptions: sbomScanOptions,
SignBy: flags.SignBy,
SignaturePolicyPath: flags.SignaturePolicy,
SourcePolicyFile: flags.SourcePolicyFile,
Squash: flags.Squash,
SystemContext: systemContext,
Target: flags.Target,
TransientMounts: flags.Volumes,
TransientRunMounts: flags.TransientRunMounts,
UnsetEnvs: flags.UnsetEnvs,
UnsetLabels: flags.UnsetLabels,
UnsetAnnotations: flags.UnsetAnnotations,

View File

@@ -0,0 +1,64 @@
####> This option file is used in:
####> podman build, farm build
####> If file is edited, make sure the changes
####> are applicable to all of those.
#### **--source-policy-file**=*pathname*
Specifies the path to a BuildKit-compatible source policy JSON file. When
specified, source references (e.g., base images in FROM instructions) are
evaluated against the policy rules before being used.
Source policies allow controlling which images can be used as base images and
optionally converting image references (e.g., pinning tags to specific digests)
without modifying Containerfiles. This is useful for enforcing organizational
policies and ensuring build reproducibility.
The policy file is a JSON document containing an array of rules. Each rule has:
- **action**: The action to take when the rule matches. Valid actions are:
- **ALLOW**: Explicitly allow the source (no transformation).
- **DENY**: Block the source and fail the build.
- **CONVERT**: Transform the source to a different reference specified in `updates`.
- **selector**: Specifies which sources the rule applies to.
- **identifier**: The source identifier to match (e.g., `docker-image://docker.io/library/alpine:latest`).
- **matchType**: How to match the identifier. Valid types are `EXACT` and `WILDCARD` (supports `*` and `?` glob patterns). Defaults to `WILDCARD` if not specified.
- **updates**: For `CONVERT` actions, specifies the replacement identifier.
Rules are evaluated in order; the first matching rule wins. If no rule matches,
the source is allowed by default.
Note: Source policy CONVERT rules are processed after **--build-context** substitutions
but before any substitutions specified in **containers-registries.conf(5)**. This provides
multiple ways to override which base image is used for a particular stage, in order of
precedence: `--build-context`, then source policy, then registries.conf.
Example policy file that pins alpine:latest to a specific digest:
```json
{
"rules": [
{
"action": "CONVERT",
"selector": {
"identifier": "docker-image://docker.io/library/alpine:latest"
},
"updates": {
"identifier": "docker-image://docker.io/library/alpine@sha256:..."
}
}
]
}
```
Example policy file that denies all ubuntu images:
```json
{
"rules": [
{
"action": "DENY",
"selector": {
"identifier": "docker-image://docker.io/library/ubuntu:*",
"matchType": "WILDCARD"
}
}
]
}
```

View File

@@ -174,7 +174,6 @@ from the base image's image ID.
This option is not supported on the remote client, including Mac and Windows
(excluding WSL2) machines.
@@option decryption-key
@@option device
@@ -259,6 +258,20 @@ This option is not supported on the remote client, including Mac and Windows
@@option metadata-file
#### **--mount**=*mount-specification*
Adds this mount to each `RUN` command in a Containerfile before executing. For example:
`podman build --mount=type=secret,id=mysecret …`
and a Containerfile entry of:
`RUN cat /run/secrets/mysecret`
Has the same effect as:
`RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret`
@@option network.image
@@option no-cache
@@ -445,6 +458,8 @@ Sign the image using a GPG key with the specified FINGERPRINT. (This option is n
@@option source-date-epoch
@@option source-policy-file
@@option squash
@@option squash-all

View File

@@ -153,6 +153,20 @@ Build image on local machine as well as on farm nodes.
@@option memory-swap
#### **--mount**=*mount-specification*
Adds this mount to each `RUN` command in a Containerfile before executing. For example:
`podman build --mount=type=secret,id=mysecret …`
and a Containerfile entry of:
`RUN cat /run/secrets/mysecret`
Has the same effect as:
`RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret`
@@option network.image
@@option no-cache
@@ -217,6 +231,8 @@ Build only on farm nodes that match the given platforms.
@@option source-date-epoch
@@option source-policy-file
@@option squash
@@option squash-all

19
go.mod
View File

@@ -11,7 +11,7 @@ require (
github.com/checkpoint-restore/checkpointctl v1.5.0
github.com/checkpoint-restore/go-criu/v7 v7.2.0
github.com/containernetworking/plugins v1.9.0
github.com/containers/buildah v1.42.1-0.20260126144005-964d45f717ce
github.com/containers/buildah v1.42.1-0.20260216192603-e473f9d26ec6
github.com/containers/gvisor-tap-vsock v0.8.8
github.com/containers/libhvee v0.10.1-0.20250829163521-178d10e67860
github.com/containers/ocicrypt v1.2.1
@@ -61,11 +61,11 @@ require (
github.com/spf13/cobra v1.10.2
github.com/spf13/pflag v1.0.10
github.com/stretchr/testify v1.11.1
github.com/vbauerster/mpb/v8 v8.11.3
github.com/vbauerster/mpb/v8 v8.12.0
github.com/vishvananda/netlink v1.3.1
go.podman.io/common v0.66.2-0.20260204175822-4e7127fdc31f
go.podman.io/image/v5 v5.38.1-0.20260204175822-4e7127fdc31f
go.podman.io/storage v1.61.1-0.20260204175822-4e7127fdc31f
go.podman.io/common v0.67.1-0.20260217150212-026c3538f3d1
go.podman.io/image/v5 v5.39.2-0.20260217150212-026c3538f3d1
go.podman.io/storage v1.62.1-0.20260217150212-026c3538f3d1
golang.org/x/crypto v0.48.0
golang.org/x/net v0.50.0
golang.org/x/sync v0.19.0
@@ -89,8 +89,7 @@ require (
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect
github.com/aead/serpent v0.0.0-20160714141033-fba169763ea6 // indirect
github.com/chzyer/readline v1.5.1 // indirect
github.com/clipperhouse/stringish v0.1.1 // indirect
github.com/clipperhouse/uax29/v2 v2.3.0 // indirect
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
github.com/containerd/errdefs v1.0.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/containerd/log v0.1.0 // indirect
@@ -129,11 +128,11 @@ require (
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jinzhu/copier v0.4.0 // indirect
github.com/klauspost/compress v1.18.3 // indirect
github.com/klauspost/compress v1.18.4 // indirect
github.com/kr/fs v0.1.0 // indirect
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect
github.com/manifoldco/promptui v0.9.0 // indirect
github.com/mattn/go-runewidth v0.0.19 // indirect
github.com/mattn/go-runewidth v0.0.20 // indirect
github.com/mdlayher/socket v0.5.1 // indirect
github.com/miekg/pkcs11 v1.1.1 // indirect
github.com/mistifyio/go-zfs/v4 v4.0.0 // indirect
@@ -182,7 +181,7 @@ require (
go.yaml.in/yaml/v2 v2.4.3 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/mod v0.32.0 // indirect
golang.org/x/oauth2 v0.34.0 // indirect
golang.org/x/oauth2 v0.35.0 // indirect
golang.org/x/text v0.34.0 // indirect
golang.org/x/time v0.14.0 // indirect
golang.org/x/tools v0.41.0 // indirect

42
go.sum
View File

@@ -37,10 +37,8 @@ github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObk
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4=
github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
@@ -57,8 +55,8 @@ github.com/containernetworking/cni v1.3.0 h1:v6EpN8RznAZj9765HhXQrtXgX+ECGebEYEm
github.com/containernetworking/cni v1.3.0/go.mod h1:Bs8glZjjFfGPHMw6hQu82RUgEPNGEaBb9KS5KtNMnJ4=
github.com/containernetworking/plugins v1.9.0 h1:Mg3SXBdRGkdXyFC4lcwr6u2ZB2SDeL6LC3U+QrEANuQ=
github.com/containernetworking/plugins v1.9.0/go.mod h1:JG3BxoJifxxHBhG3hFyxyhid7JgRVBu/wtooGEvWf1c=
github.com/containers/buildah v1.42.1-0.20260126144005-964d45f717ce h1:JNPN3qlLtAZRzggCPK1ffZ4MWuDogG+NtIvQv2bINOY=
github.com/containers/buildah v1.42.1-0.20260126144005-964d45f717ce/go.mod h1:F10eTynOMnjfEzsX8pmZRIJEb5/3+mTEzmRHXB/6+hk=
github.com/containers/buildah v1.42.1-0.20260216192603-e473f9d26ec6 h1:j8qnAjmrLf6x47qChpQZla1JzdMZ2BEmhTFlE0e9EUU=
github.com/containers/buildah v1.42.1-0.20260216192603-e473f9d26ec6/go.mod h1:rnvz1cLM9/wTR4BYODd62GHIwTrWzQsn5J1+qEbjgJ8=
github.com/containers/common v0.64.2 h1:1xepE7QwQggUXxmyQ1Dbh6Cn0yd7ktk14sN3McSWf5I=
github.com/containers/common v0.64.2/go.mod h1:o29GfYy4tefUuShm8mOn2AiL5Mpzdio+viHI7n24KJ4=
github.com/containers/gvisor-tap-vsock v0.8.8 h1:5FznbOYMIuaCv8B6zQ7M6wjqP63Lasy0A6GpViEnjTg=
@@ -102,8 +100,8 @@ github.com/disiqueira/gotree/v3 v3.0.2 h1:ik5iuLQQoufZBNPY518dXhiO5056hyNBIK9lWh
github.com/disiqueira/gotree/v3 v3.0.2/go.mod h1:ZuyjE4+mUQZlbpkI24AmruZKhg3VHEgPLDY8Qk+uUu8=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/cli v29.1.5+incompatible h1:GckbANUt3j+lsnQ6eCcQd70mNSOismSHWt8vk2AX8ao=
github.com/docker/cli v29.1.5+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli v29.2.1+incompatible h1:n3Jt0QVCN65eiVBoUTZQM9mcQICCJt3akW4pKAbKdJg=
github.com/docker/cli v29.2.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM=
@@ -215,8 +213,8 @@ github.com/kevinburke/ssh_config v1.5.0 h1:3cPZmE54xb5j3G5xQCjSvokqNwU2uW+3ry1+P
github.com/kevinburke/ssh_config v1.5.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw=
github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
@@ -237,8 +235,8 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/mattn/go-runewidth v0.0.20 h1:WcT52H91ZUAwy8+HUkdM3THM6gXqXuLJi9O3rjcQQaQ=
github.com/mattn/go-runewidth v0.0.20/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
github.com/mattn/go-sqlite3 v1.14.34 h1:3NtcvcUnFBPsuRcno8pUtupspG/GM+9nZ88zgJcp6Zk=
@@ -395,8 +393,8 @@ 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/vbatts/tar-split v0.12.2 h1:w/Y6tjxpeiFMR47yzZPlPj/FcPLpXbTUi/9H7d3CPa4=
github.com/vbatts/tar-split v0.12.2/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA=
github.com/vbauerster/mpb/v8 v8.11.3 h1:iniBmO4ySXCl4gVdmJpgrtormH5uvjpxcx/dMyVU9Jw=
github.com/vbauerster/mpb/v8 v8.11.3/go.mod h1:n9M7WbP0NFjpgKS5XdEC3tMRgZTNM/xtC8zWGkiMuy0=
github.com/vbauerster/mpb/v8 v8.12.0 h1:+gneY3ifzc88tKDzOtfG8k8gfngCx615S2ZmFM4liWg=
github.com/vbauerster/mpb/v8 v8.12.0/go.mod h1:V02YIuMVo301Y1VE9VtZlD8s84OMsk+EKN6mwvf/588=
github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0=
github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4=
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
@@ -445,12 +443,12 @@ go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJr
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4=
go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE=
go.podman.io/common v0.66.2-0.20260204175822-4e7127fdc31f h1:+0VKagql2xFs+5ugEk/v9t5oSdpWtYqNf9xqQGmHO0I=
go.podman.io/common v0.66.2-0.20260204175822-4e7127fdc31f/go.mod h1:VmtRqeaeFq7DrgWmx9TeUcwGh72jtyyBHWjHEENgZfU=
go.podman.io/image/v5 v5.38.1-0.20260204175822-4e7127fdc31f h1:+kQbbmMs0MakSrmYK6/XDE+g7c/MKaqqSYa6H722F6c=
go.podman.io/image/v5 v5.38.1-0.20260204175822-4e7127fdc31f/go.mod h1:d3wkDEdmkuEkXEMP6qqmc85P7fvhlT1BsdMqkfubuTU=
go.podman.io/storage v1.61.1-0.20260204175822-4e7127fdc31f h1:z/zp+wPHdiQ2EeY3Wr99TeJwc9ZZ4o3zPTBNixSgcR4=
go.podman.io/storage v1.61.1-0.20260204175822-4e7127fdc31f/go.mod h1:yuLB1ikwsdGrGqSGBWv7fMbOeHupCaMn5iJ1biqxrpI=
go.podman.io/common v0.67.1-0.20260217150212-026c3538f3d1 h1:MhQdvsUa8hef3qN+hRtu6+O/QsF6Cpsd0XP2gZUW3bU=
go.podman.io/common v0.67.1-0.20260217150212-026c3538f3d1/go.mod h1:KKYaKM+ZTve0ZW+3yvd05PpWawetNGNzb05dDUw+LG0=
go.podman.io/image/v5 v5.39.2-0.20260217150212-026c3538f3d1 h1:41n9FLO6l3xQ7yVAE9oxKNwmJgXbFRTeS1BHTAQjt80=
go.podman.io/image/v5 v5.39.2-0.20260217150212-026c3538f3d1/go.mod h1:ZcbaWguhrYsWLx6kbX+gcVq9yzIrvzu21JF2bgQIlMc=
go.podman.io/storage v1.62.1-0.20260217150212-026c3538f3d1 h1:JdxkufArDqjwqWWA+45NsqdAZ9zTSqQmonncbuRzQxo=
go.podman.io/storage v1.62.1-0.20260217150212-026c3538f3d1/go.mod h1:B83Ad8mtO0GZs7rEwb66f0Ed5G57NyKI/iJZHoJrpUE=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
@@ -491,8 +489,8 @@ golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=
golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=

View File

@@ -116,11 +116,13 @@ type BuildQuery struct {
ShmSize int `schema:"shmsize"`
SkipUnusedStages bool `schema:"skipunusedstages"`
SourceDateEpoch int64 `schema:"sourcedateepoch"`
SourcePolicy string `schema:"sourcePolicy"`
Squash bool `schema:"squash"`
TLSVerify bool `schema:"tlsVerify"`
Tags []string `schema:"t"`
Target string `schema:"target"`
Timestamp int64 `schema:"timestamp"`
TransientRunMounts []string `schema:"transientRunMounts"`
Ulimits string `schema:"ulimits"`
UnsetEnvs []string `schema:"unsetenv"`
UnsetLabels []string `schema:"unsetlabel"`
@@ -587,8 +589,29 @@ func createBuildOptions(query *BuildQuery, buildCtx *BuildContext, queryValues u
return nil, nil, utils.GetGenericBadRequestError(err)
}
var temporaryFiles []string
cleanup := func() {
auth.RemoveAuthfile(authfile)
for _, temporaryFile := range temporaryFiles {
os.Remove(temporaryFile)
}
}
makeTemporaryFileWithContent := func(data []byte, pattern string) (string, error) {
if pattern == "" {
pattern = "podman-build-"
}
f, err := os.CreateTemp(parse.GetTempDir(), pattern)
if err != nil {
return "", err
}
filename := f.Name()
temporaryFiles = append(temporaryFiles, filename)
_, err = f.Write(data)
err = errors.Join(err, f.Close())
if err != nil {
return "", err
}
return filename, nil
}
// Process from image
@@ -744,6 +767,7 @@ func createBuildOptions(query *BuildQuery, buildCtx *BuildContext, queryValues u
Squash: query.Squash,
SystemContext: systemContext,
Target: query.Target,
TransientRunMounts: query.TransientRunMounts,
UnsetEnvs: query.UnsetEnvs,
UnsetLabels: query.UnsetLabels,
UnsetAnnotations: query.UnsetAnnotations,
@@ -768,6 +792,15 @@ func createBuildOptions(query *BuildQuery, buildCtx *BuildContext, queryValues u
})
}
// Process source policy
if _, found := queryValues["sourcePolicy"]; found {
filename, err := makeTemporaryFileWithContent([]byte(query.SourcePolicy), "podman-source-policy-")
if err != nil {
return nil, cleanup, utils.GetBadRequestError("sourcePolicy", query.SourcePolicy, err)
}
buildOptions.SourcePolicyFile = filename
}
// Process timestamps
if _, found := queryValues["sourcedateepoch"]; found {
ts := time.Unix(query.SourceDateEpoch, 0)

View File

@@ -2,6 +2,7 @@ package images
import (
"archive/tar"
"bytes"
"context"
"encoding/json"
"errors"
@@ -430,6 +431,9 @@ func prepareParams(options types.BuildOptions) (url.Values, error) {
for _, volume := range options.CommonBuildOpts.Volumes {
params.Add("volume", convertVolumeSrcPath(volume))
}
for _, mount := range options.TransientRunMounts {
params.Add("transientRunMounts", mount)
}
for _, group := range options.GroupAdd {
params.Add("groupadd", group)
@@ -482,6 +486,13 @@ func prepareParams(options types.BuildOptions) (url.Values, error) {
t := options.SourceDateEpoch
params.Set("sourcedateepoch", strconv.FormatInt(t.Unix(), 10))
}
if options.SourcePolicyFile != "" {
rawSourcePolicy, err := os.ReadFile(options.SourcePolicyFile)
if err != nil {
return nil, fmt.Errorf("loading source policy: %w", err)
}
params.Set("sourcePolicy", string(bytes.TrimSpace(rawSourcePolicy)))
}
if options.RewriteTimestamp {
params.Set("rewritetimestamp", "1")
} else {

View File

@@ -337,7 +337,8 @@ skip_if_remote "FIXME: 2025-04-01 git related errors returning wrong exit code"
# FIXME: Don't use process substitution for Containerfile in buildah tests
skip "process substitution with overlay context not supported" \
"build-with-timestamp-applies-to-oci-archive" \
"build-with-timestamp-applies-to-oci-archive-with-base"
"build-with-timestamp-applies-to-oci-archive-with-base" \
"build-with-run-mount"
# END temporary workarounds that must be reevaluated periodically
###############################################################################

View File

@@ -1,2 +0,0 @@
.DS_Store
*.test

View File

@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2025 Matt Sherman
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.

View File

@@ -1,64 +0,0 @@
# stringish
A small Go module that provides a generic type constraint for “string-like”
data, and a utf8 package that works with both strings and byte slices
without conversions.
```go
type Interface interface {
~[]byte | ~string
}
```
[![Go Reference](https://pkg.go.dev/badge/github.com/clipperhouse/stringish/utf8.svg)](https://pkg.go.dev/github.com/clipperhouse/stringish/utf8)
[![Test Status](https://github.com/clipperhouse/stringish/actions/workflows/gotest.yml/badge.svg)](https://github.com/clipperhouse/stringish/actions/workflows/gotest.yml)
## Install
```
go get github.com/clipperhouse/stringish
```
## Examples
```go
import (
"github.com/clipperhouse/stringish"
"github.com/clipperhouse/stringish/utf8"
)
s := "Hello, 世界"
r, size := utf8.DecodeRune(s) // not DecodeRuneInString 🎉
b := []byte("Hello, 世界")
r, size = utf8.DecodeRune(b) // same API!
func MyFoo[T stringish.Interface](s T) T {
// pass a string or a []byte
// iterate, slice, transform, whatever
}
```
## Motivation
Sometimes we want APIs to accept `string` or `[]byte` without having to convert
between those types. That conversion usually allocates!
By implementing with `stringish.Interface`, we can have a single API, and
single implementation for both types: one `Foo` instead of `Foo` and
`FooString`.
We have converted the
[`unicode/utf8` package](https://github.com/clipperhouse/stringish/blob/main/utf8/utf8.go)
as an example -- note the absence of`*InString` funcs. We might look at `x/text`
next.
## Used by
- clipperhouse/uax29: [stringish trie](https://github.com/clipperhouse/uax29/blob/master/graphemes/trie.go#L27), [stringish iterator](https://github.com/clipperhouse/uax29/blob/master/internal/iterators/iterator.go#L9), [stringish SplitFunc](https://github.com/clipperhouse/uax29/blob/master/graphemes/splitfunc.go#L21)
- [clipperhouse/displaywidth](https://github.com/clipperhouse/displaywidth)
## Prior discussion
- [Consideration of similar by the Go team](https://github.com/golang/go/issues/48643)

View File

@@ -1,5 +0,0 @@
package stringish
type Interface interface {
~[]byte | ~string
}

View File

@@ -1,4 +1,4 @@
An implementation of grapheme cluster boundaries from [Unicode text segmentation](https://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries) (UAX 29), for Unicode version 15.0.0.
An implementation of grapheme cluster boundaries from [Unicode text segmentation](https://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries) (UAX 29), for Unicode 17.
[![Documentation](https://pkg.go.dev/badge/github.com/clipperhouse/uax29/v2/graphemes.svg)](https://pkg.go.dev/github.com/clipperhouse/uax29/v2/graphemes)
![Tests](https://github.com/clipperhouse/uax29/actions/workflows/gotest.yml/badge.svg)
@@ -7,18 +7,17 @@ An implementation of grapheme cluster boundaries from [Unicode text segmentation
## Quick start
```
go get "github.com/clipperhouse/uax29/v2/graphemes"
go get github.com/clipperhouse/uax29/v2/graphemes
```
```go
import "github.com/clipperhouse/uax29/v2/graphemes"
text := "Hello, 世界. Nice dog! 👍🐶"
g := graphemes.FromString(text)
tokens := graphemes.FromString(text)
for tokens.Next() { // Next() returns true until end of data
fmt.Println(tokens.Value()) // Do something with the current grapheme
for g.Next() { // Next() returns true until end of data
fmt.Println(g.Value()) // Do something with the current grapheme
}
```
@@ -26,7 +25,7 @@ _A grapheme is a “single visible character”, which might be a simple as a si
## Conformance
We use the Unicode [test suite](https://unicode.org/reports/tr41/tr41-26.html#Tests29).
We use the Unicode [test suite](https://unicode.org/reports/tr41/tr41-36.html#Tests29).
![Tests](https://github.com/clipperhouse/uax29/actions/workflows/gotest.yml/badge.svg)
![Fuzz](https://github.com/clipperhouse/uax29/actions/workflows/gofuzz.yml/badge.svg)
@@ -37,11 +36,10 @@ We use the Unicode [test suite](https://unicode.org/reports/tr41/tr41-26.html#Te
```go
text := "Hello, 世界. Nice dog! 👍🐶"
g := graphemes.FromString(text)
tokens := graphemes.FromString(text)
for tokens.Next() { // Next() returns true until end of data
fmt.Println(tokens.Value()) // Do something with the current grapheme
for g.Next() { // Next() returns true until end of data
fmt.Println(g.Value()) // Do something with the current grapheme
}
```
@@ -50,15 +48,15 @@ for tokens.Next() { // Next() returns true until end of data
`FromReader` embeds a [`bufio.Scanner`](https://pkg.go.dev/bufio#Scanner), so just use those methods.
```go
r := getYourReader() // from a file or network maybe
tokens := graphemes.FromReader(r)
r := getYourReader() // from a file or network maybe
g := graphemes.FromReader(r)
for tokens.Scan() { // Scan() returns true until error or EOF
fmt.Println(tokens.Text()) // Do something with the current grapheme
for g.Scan() { // Scan() returns true until error or EOF
fmt.Println(g.Text()) // Do something with the current grapheme
}
if tokens.Err() != nil { // Check the error
log.Fatal(tokens.Err())
if g.Err() != nil { // Check the error
log.Fatal(g.Err())
}
```
@@ -67,24 +65,52 @@ if tokens.Err() != nil { // Check the error
```go
b := []byte("Hello, 世界. Nice dog! 👍🐶")
tokens := graphemes.FromBytes(b)
g := graphemes.FromBytes(b)
for tokens.Next() { // Next() returns true until end of data
fmt.Println(tokens.Value()) // Do something with the current grapheme
for g.Next() { // Next() returns true until end of data
fmt.Println(g.Value()) // Do something with the current grapheme
}
```
### Benchmarks
### ANSI escape sequences
On a Mac M2 laptop, we see around 200MB/s, or around 100 million graphemes per second, and no allocations.
By the UAX 29 specification, ANSI escape sequences are not grapheme clusters. To treat 7-bit ANSI escape sequences as a single cluster, set `AnsiEscapeSequences` to true.
```go
text := "Hello, \x1b[31mworld\x1b[0m!"
g := graphemes.FromString(text)
g.AnsiEscapeSequences = true
for g.Next() {
fmt.Println(g.Value())
}
```
To also parse 8-bit C1 controls (non-UTF-8 bytes), set `AnsiEscapeSequences8Bit` to true.
```go
g.AnsiEscapeSequences = true // 7-bit forms (ESC ...)
g.AnsiEscapeSequences8Bit = true // 8-bit C1 forms (0x80-0x9F), not valid UTF-8
```
For ESC-initiated (7-bit) control strings, only 7-bit terminators are recognized.
For C1-initiated (8-bit) control strings, only C1 ST (`0x9C`) is recognized as ST.
We implement [ECMA-48](https://ecma-international.org/publications-and-standards/standards/ecma-48/) control codes in both 7-bit and 8-bit representations. 8-bit control codes are not UTF-8 encoded and are not valid UTF-8, caveat emptor.
### Benchmarks
```
goos: darwin
goarch: arm64
pkg: github.com/clipperhouse/uax29/graphemes/comparative
cpu: Apple M2
BenchmarkGraphemes/clipperhouse/uax29-8 173805 ns/op 201.16 MB/s 0 B/op 0 allocs/op
BenchmarkGraphemes/rivo/uniseg-8 2045128 ns/op 17.10 MB/s 0 B/op 0 allocs/op
BenchmarkGraphemesMixed/clipperhouse/uax29-8 142635 ns/op 245.12 MB/s 0 B/op 0 allocs/op
BenchmarkGraphemesMixed/rivo/uniseg-8 2018284 ns/op 17.32 MB/s 0 B/op 0 allocs/op
BenchmarkGraphemesASCII/clipperhouse/uax29-8 8846 ns/op 508.73 MB/s 0 B/op 0 allocs/op
BenchmarkGraphemesASCII/rivo/uniseg-8 366760 ns/op 12.27 MB/s 0 B/op 0 allocs/op
```
### Invalid inputs

View File

@@ -0,0 +1,138 @@
package graphemes
// ansiEscapeLength returns the byte length of a valid 7-bit ANSI escape
// sequence at the start of data, or 0 if none.
//
// Recognized forms (ECMA-48 / ISO 6429):
// - CSI: ESC [ then parameter bytes (0x30-0x3F), intermediate (0x20-0x2F), final (0x40-0x7E)
// - OSC: ESC ] then payload until BEL (0x07), 7-bit ST (ESC \), CAN (0x18), or SUB (0x1A)
// - DCS, SOS, PM, APC: ESC P/X/^/_ then payload until 7-bit ST (ESC \), CAN, or SUB
// - Two-byte: ESC + Fe/Fs (0x40-0x7E excluding above), or Fp (0x30-0x3F), or nF (0x20-0x2F then final)
func ansiEscapeLength[T ~string | ~[]byte](data T) int {
n := len(data)
if n < 2 || data[0] != esc {
return 0
}
b1 := data[1]
switch b1 {
case '[': // CSI
body := csiBodyLength(data[2:])
if body == 0 {
return 0
}
return 2 + body
case ']': // OSC - allows BEL or 7-bit ST terminator
body := oscLength(data[2:])
if body < 0 {
return 0
}
return 2 + body
case 'P', 'X', '^', '_': // DCS, SOS, PM, APC
body := stSequenceLength(data[2:])
if body < 0 {
return 0
}
return 2 + body
}
if b1 >= 0x40 && b1 <= 0x7E {
// Fe/Fs two-byte; [ ] P X ^ _ handled above
return 2
}
if b1 >= 0x30 && b1 <= 0x3F {
// Fp (private) two-byte
return 2
}
if b1 >= 0x20 && b1 <= 0x2F {
// nF: intermediates then one final (0x30-0x7E)
i := 2
for i < n && data[i] >= 0x20 && data[i] <= 0x2F {
i++
}
if i < n && data[i] >= 0x30 && data[i] <= 0x7E {
return i + 1
}
return 0
}
return 0
}
// csiBodyLength returns the length of the CSI body (param/intermediate/final bytes).
// data is the slice after "ESC [".
// Per ECMA-48, the CSI body has the form:
//
// parameters (0x300x3F)*, intermediates (0x200x2F)*, final (0x400x7E)
//
// Once an intermediate byte is seen, subsequent parameter bytes are invalid.
func csiBodyLength[T ~string | ~[]byte](data T) int {
seenIntermediate := false
for i := 0; i < len(data); i++ {
b := data[i]
if b >= 0x30 && b <= 0x3F {
if seenIntermediate {
return 0
}
continue
}
if b >= 0x20 && b <= 0x2F {
seenIntermediate = true
continue
}
if b >= 0x40 && b <= 0x7E {
return i + 1
}
return 0
}
return 0
}
// oscLength returns the length of the OSC body.
// data is the slice after "ESC ]".
//
// Returns:
// - n >= 0: consumed body length (includes BEL/ST terminator when present)
// - -1: not terminated in the provided data
//
// OSC accepts BEL (0x07) or 7-bit ST (ESC \) as terminators by widespread convention.
// Per ECMA-48, CAN (0x18) and SUB (0x1A) cancel the control string; in that
// case they are not part of the OSC sequence length.
func oscLength[T ~string | ~[]byte](data T) int {
for i := 0; i < len(data); i++ {
b := data[i]
if b == bel {
return i + 1
}
if b == can || b == sub {
return i
}
if b == esc && i+1 < len(data) && data[i+1] == '\\' {
return i + 2
}
}
return -1
}
// stSequenceLength returns the length of a control-string body.
// data is the slice after "ESC x".
//
// Returns:
// - n >= 0: consumed body length (includes ST terminator when present)
// - -1: not terminated in the provided data
//
// Used for DCS, SOS, PM, and APC, which per ECMA-48 terminate with ST.
// ST here is the 7-bit form (ESC \).
// CAN (0x18) and SUB (0x1A) cancel the control string; in that case they are
// not part of the sequence length.
func stSequenceLength[T ~string | ~[]byte](data T) int {
for i := 0; i < len(data); i++ {
if data[i] == can || data[i] == sub {
return i
}
if data[i] == esc && i+1 < len(data) && data[i+1] == '\\' {
return i + 2
}
}
return -1
}

View File

@@ -0,0 +1,79 @@
package graphemes
// ansiEscapeLength8Bit returns the byte length of a valid 8-bit C1 ANSI
// sequence at the start of data, or 0 if none.
//
// Recognized forms (ECMA-48 / ISO 6429):
// - C1 CSI (0x9B) body as parameter/intermediate/final bytes
// - C1 OSC (0x9D) body terminated by BEL, C1 ST, CAN, or SUB
// - C1 DCS/SOS/PM/APC (0x90/0x98/0x9E/0x9F) body terminated by C1 ST, CAN, or SUB
// - Standalone C1 controls (0x80..0x9F not listed above): single byte
func ansiEscapeLength8Bit[T ~string | ~[]byte](data T) int {
if len(data) == 0 {
return 0
}
switch data[0] {
case 0x9B: // C1 CSI
body := csiBodyLength(data[1:])
if body == 0 {
return 0
}
return 1 + body
case 0x9D: // C1 OSC
body := oscLengthC1(data[1:])
if body < 0 {
return 0
}
return 1 + body
case 0x90, 0x98, 0x9E, 0x9F: // C1 DCS, SOS, PM, APC
body := stSequenceLengthC1(data[1:])
if body < 0 {
return 0
}
return 1 + body
default:
if data[0] >= 0x80 && data[0] <= 0x9F {
return 1
}
}
return 0
}
// oscLengthC1 returns the length of a C1 OSC body.
// data is the slice after the C1 OSC initiator (0x9D).
//
// Returns:
// - n >= 0: consumed body length (includes BEL/ST terminator when present)
// - -1: not terminated in the provided data
//
// Terminators: BEL (0x07) or C1 ST (0x9C).
// CAN (0x18) and SUB (0x1A) cancel the control string.
func oscLengthC1[T ~string | ~[]byte](data T) int {
for i := 0; i < len(data); i++ {
b := data[i]
if b == bel || b == st {
return i + 1
}
if b == can || b == sub {
return i
}
}
return -1
}
// stSequenceLengthC1 parses DCS/SOS/PM/APC bodies that terminate with C1 ST
// (0x9C), or are canceled by CAN/SUB.
func stSequenceLengthC1[T ~string | ~[]byte](data T) int {
for i := 0; i < len(data); i++ {
b := data[i]
if b == can || b == sub {
return i
}
if b == st {
return i + 1
}
}
return -1
}

View File

@@ -1,12 +1,44 @@
package graphemes
import (
"github.com/clipperhouse/stringish"
"github.com/clipperhouse/uax29/v2/internal/iterators"
)
import "unicode/utf8"
type Iterator[T stringish.Interface] struct {
*iterators.Iterator[T]
// FromString returns an iterator for the grapheme clusters in the input string.
// Iterate while Next() is true, and access the grapheme via Value().
func FromString(s string) *Iterator[string] {
return &Iterator[string]{
split: splitFuncString,
data: s,
}
}
// FromBytes returns an iterator for the grapheme clusters in the input bytes.
// Iterate while Next() is true, and access the grapheme via Value().
func FromBytes(b []byte) *Iterator[[]byte] {
return &Iterator[[]byte]{
split: splitFuncBytes,
data: b,
}
}
// Iterator is a generic iterator for grapheme clusters in strings or byte slices,
// with an ASCII hot path optimization.
type Iterator[T ~string | ~[]byte] struct {
split func(T, bool) (int, T, error)
data T
pos int
start int
// AnsiEscapeSequences treats 7-bit ANSI escape sequences (ECMA-48) as
// single grapheme clusters when true. The default is false.
//
// 8-bit controls are not enabled by this option. See [AnsiEscapeSequences8Bit].
AnsiEscapeSequences bool
// AnsiEscapeSequences8Bit treats 8-bit C1 ANSI escape sequences (ECMA-48) as single
// grapheme clusters when true. The default is false.
//
// 8-bit control bytes are not UTF-8 encoded, i.e. not valid UTF-8. If you
// choose this option, you are choosing to interpret non-UTF-8 data, caveat
// emptor.
AnsiEscapeSequences8Bit bool
}
var (
@@ -14,18 +46,99 @@ var (
splitFuncBytes = splitFunc[[]byte]
)
// FromString returns an iterator for the grapheme clusters in the input string.
// Iterate while Next() is true, and access the grapheme via Value().
func FromString(s string) Iterator[string] {
return Iterator[string]{
iterators.New(splitFuncString, s),
const (
esc = 0x1B
cr = 0x0D
bel = 0x07
can = 0x18
sub = 0x1A
st = 0x9C
)
// Next advances the iterator to the next grapheme cluster.
// Returns false when there are no more grapheme clusters.
func (iter *Iterator[T]) Next() bool {
if iter.pos >= len(iter.data) {
return false
}
iter.start = iter.pos
b := iter.data[iter.pos]
if iter.AnsiEscapeSequences && b == esc {
if a := ansiEscapeLength(iter.data[iter.pos:]); a > 0 {
iter.pos += a
return true
}
}
if iter.AnsiEscapeSequences8Bit && b >= 0x80 && b <= 0x9F {
if a := ansiEscapeLength8Bit(iter.data[iter.pos:]); a > 0 {
iter.pos += a
return true
}
}
// ASCII hot path: any ASCII is one grapheme when next byte is ASCII or end.
if b < utf8.RuneSelf && b != cr {
if iter.pos+1 >= len(iter.data) || iter.data[iter.pos+1] < utf8.RuneSelf {
iter.pos++
return true
}
}
// Fall back to UAX29 grapheme parsing
remaining := iter.data[iter.pos:]
advance, _, err := iter.split(remaining, true)
if err != nil {
panic(err)
}
if advance <= 0 {
panic("splitFunc returned a zero or negative advance")
}
iter.pos += advance
if iter.pos > len(iter.data) {
panic("splitFunc advanced beyond end of data")
}
return true
}
// FromBytes returns an iterator for the grapheme clusters in the input bytes.
// Iterate while Next() is true, and access the grapheme via Value().
func FromBytes(b []byte) Iterator[[]byte] {
return Iterator[[]byte]{
iterators.New(splitFuncBytes, b),
}
// Value returns the current grapheme cluster.
func (iter *Iterator[T]) Value() T {
return iter.data[iter.start:iter.pos]
}
// Start returns the byte position of the current grapheme in the original data.
func (iter *Iterator[T]) Start() int {
return iter.start
}
// End returns the byte position after the current grapheme in the original data.
func (iter *Iterator[T]) End() int {
return iter.pos
}
// Reset resets the iterator to the beginning of the data.
func (iter *Iterator[T]) Reset() {
iter.start = 0
iter.pos = 0
}
// SetText sets the data for the iterator to operate on, and resets all state.
func (iter *Iterator[T]) SetText(data T) {
iter.data = data
iter.start = 0
iter.pos = 0
}
// First returns the first grapheme cluster without advancing the iterator.
func (iter *Iterator[T]) First() T {
if len(iter.data) == 0 {
return iter.data
}
// Use a copy to leverage Next()'s ASCII optimization
cp := *iter
cp.pos = 0
cp.start = 0
cp.Next()
return cp.Value()
}

View File

@@ -2,8 +2,6 @@ package graphemes
import (
"bufio"
"github.com/clipperhouse/stringish"
)
// is determines if lookup intersects propert(ies)
@@ -13,12 +11,22 @@ func (lookup property) is(properties property) bool {
const _Ignore = _Extend
// incbState tracks state for GB9c rule (Indic conjunct clusters)
// Pattern: Consonant (Extend|Linker)* Linker (Extend|Linker)* × Consonant
type incbState int
const (
incbNone incbState = iota // initial/reset
incbConsonant // seen Consonant, awaiting Linker
incbLinker // seen Consonant and Linker (conjunct ready)
)
// SplitFunc is a bufio.SplitFunc implementation of Unicode grapheme cluster segmentation, for use with bufio.Scanner.
//
// See https://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries.
var SplitFunc bufio.SplitFunc = splitFunc[[]byte]
func splitFunc[T stringish.Interface](data T, atEOF bool) (advance int, token T, err error) {
func splitFunc[T ~string | ~[]byte](data T, atEOF bool) (advance int, token T, err error) {
var empty T
if len(data) == 0 {
return 0, empty, nil
@@ -30,6 +38,9 @@ func splitFunc[T stringish.Interface](data T, atEOF bool) (advance int, token T,
var lastLastExIgnore property = 0 // "last one before that"
var regionalIndicatorCount int
// GB9c state: tracking Indic conjunct clusters
var incb incbState
// Rules are usually of the form Cat1 × Cat2; "current" refers to the first property
// to the right of the ×, from which we look back or forward
@@ -76,6 +87,23 @@ func splitFunc[T stringish.Interface](data T, atEOF bool) (advance int, token T,
lastExIgnore = last
}
// Update GB9c state based on what we just advanced past
if last.is(_InCBConsonant | _InCBLinker | _InCBExtend) {
switch {
case last.is(_InCBConsonant):
if incb != incbLinker {
incb = incbConsonant
}
case last.is(_InCBLinker):
if incb >= incbConsonant {
incb = incbLinker
}
// case last.is(_InCBExtend): stay in current state
}
} else {
incb = incbNone
}
current, w = lookup(data[pos:])
if w == 0 {
if atEOF {
@@ -141,11 +169,14 @@ func splitFunc[T stringish.Interface](data T, atEOF bool) (advance int, token T,
}
// https://unicode.org/reports/tr29/#GB9c
// TODO(clipperhouse):
// It appears to be added in Unicode 15.1.0:
// https://unicode.org/versions/Unicode15.1.0/#Migration
// This package currently supports Unicode 15.0.0, so
// out of scope for now
// Do not break within certain combinations with Indic_Conjunct_Break (InCB)=Linker.
if incb == incbLinker && current.is(_InCBConsonant) {
// After matching the pattern, reset state to start tracking a new pattern
// The current Consonant becomes the start of the new pattern
incb = incbConsonant
pos += w
continue
}
// https://unicode.org/reports/tr29/#GB11
if current.is(_ExtendedPictographic) && last.is(_ZWJ) && lastLastExIgnore.is(_ExtendedPictographic) {

View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,100 +0,0 @@
package iterators
import "github.com/clipperhouse/stringish"
type SplitFunc[T stringish.Interface] func(T, bool) (int, T, error)
// Iterator is a generic iterator for words that are either []byte or string.
// Iterate while Next() is true, and access the word via Value().
type Iterator[T stringish.Interface] struct {
split SplitFunc[T]
data T
start int
pos int
}
// New creates a new Iterator for the given data and SplitFunc.
func New[T stringish.Interface](split SplitFunc[T], data T) *Iterator[T] {
return &Iterator[T]{
split: split,
data: data,
}
}
// SetText sets the text for the iterator to operate on, and resets all state.
func (iter *Iterator[T]) SetText(data T) {
iter.data = data
iter.start = 0
iter.pos = 0
}
// Split sets the SplitFunc for the Iterator.
func (iter *Iterator[T]) Split(split SplitFunc[T]) {
iter.split = split
}
// Next advances the iterator to the next token. It returns false when there
// are no remaining tokens or an error occurred.
func (iter *Iterator[T]) Next() bool {
if iter.pos == len(iter.data) {
return false
}
if iter.pos > len(iter.data) {
panic("SplitFunc advanced beyond the end of the data")
}
iter.start = iter.pos
advance, _, err := iter.split(iter.data[iter.pos:], true)
if err != nil {
panic(err)
}
if advance <= 0 {
panic("SplitFunc returned a zero or negative advance")
}
iter.pos += advance
if iter.pos > len(iter.data) {
panic("SplitFunc advanced beyond the end of the data")
}
return true
}
// Value returns the current token.
func (iter *Iterator[T]) Value() T {
return iter.data[iter.start:iter.pos]
}
// Start returns the byte position of the current token in the original data.
func (iter *Iterator[T]) Start() int {
return iter.start
}
// End returns the byte position after the current token in the original data.
func (iter *Iterator[T]) End() int {
return iter.pos
}
// Reset resets the iterator to the beginning of the data.
func (iter *Iterator[T]) Reset() {
iter.start = 0
iter.pos = 0
}
func (iter *Iterator[T]) First() T {
if len(iter.data) == 0 {
return iter.data
}
advance, _, err := iter.split(iter.data, true)
if err != nil {
panic(err)
}
if advance <= 0 {
panic("SplitFunc returned a zero or negative advance")
}
if advance > len(iter.data) {
panic("SplitFunc advanced beyond the end of the data")
}
return iter.data[:advance]
}

View File

@@ -54,7 +54,7 @@ ifeq ($(BUILDDEBUG), 1)
endif
# Managed by renovate.
export GOLANGCI_LINT_VERSION := 2.8.0
export GOLANGCI_LINT_VERSION := 2.9.0
# make all BUILDDEBUG=1
# Note: Uses the -N -l go compiler options to disable compiler optimizations

View File

@@ -2181,6 +2181,15 @@ func copierHandlerPut(bulkReader io.Reader, req request, idMappings *idtools.IDM
if err != io.EOF {
return fmt.Errorf("reading tar stream: expected EOF: %w", err)
}
// Drain any remaining data from bulkReader to prevent broken pipe errors.
// tar.Reader returns EOF after reading the standard tar EOF marker
// (two 512-byte blocks of nulls), but the tar file may have additional
// trailing null bytes. If we don't read them, the subprocess exits before
// the sender finishes writing to the pipe, causing EPIPE/broken pipe.
// See: https://github.com/containers/buildah/issues/6573
if _, err := io.Copy(io.Discard, bulkReader); err != nil {
logrus.Debugf("error draining remaining data from tar stream: %v", err)
}
return nil
}
return &response{Error: "", Put: putResponse{}}, cb, nil
@@ -2329,7 +2338,7 @@ func copierHandlerEnsure(req request, idMappings *idtools.IDMappings) *response
for _, item := range req.EnsureOptions.Paths {
uid, gid := 0, 0
if item.Chown != nil {
uid, gid = item.Chown.UID, item.Chown.UID
uid, gid = item.Chown.UID, item.Chown.GID
}
var mode os.FileMode
switch item.Typeflag {
@@ -2405,7 +2414,7 @@ func copierHandlerEnsure(req request, idMappings *idtools.IDMappings) *response
createdLeaf = strings.TrimPrefix(createdLeaf, string(os.PathSeparator))
}
created = append(created, createdLeaf)
if err = chown(filepath.Join(req.Root, leaf), uid, uid); err != nil {
if err = chown(filepath.Join(req.Root, leaf), uid, gid); err != nil {
return errorResponse("copier: ensure: error setting owner of %q to %d:%d: %v", leaf, uid, gid, err)
}
if err = chmod(filepath.Join(req.Root, leaf), mode); err != nil {

View File

@@ -145,9 +145,12 @@ type BuildOptions struct {
Runtime string
// RuntimeArgs adds global arguments for the runtime.
RuntimeArgs []string
// TransientMounts is a list of unparsed mounts that will be provided to
// TransientMounts is a list of unparsed src:dest volume instructions that will be provided to
// RUN instructions.
TransientMounts []string
// TransientRunMounts is a list of unparsed mounts (e.g. type=secret etc) that will be provided to
// RUN instructions.
TransientRunMounts []string
// CacheFrom specifies any remote repository which can be treated as
// potential cache source.
CacheFrom []reference.Named
@@ -204,6 +207,12 @@ type BuildOptions struct {
// specified, indicating that the shared, system-wide default policy
// should be used.
SignaturePolicyPath string
// SourcePolicyFile specifies the path to a BuildKit-compatible source
// policy JSON file. When specified, source references (e.g., base images
// in FROM instructions) are evaluated against the policy rules. Rules
// can DENY specific sources or CONVERT them to different references
// (e.g., pinning tags to digests).
SourcePolicyFile string
// SkipUnusedStages allows users to skip stages in a multi-stage builds
// which do not contribute anything to the target stage. Expected default
// value is true.

View File

@@ -110,6 +110,7 @@ type Secret struct {
}
// BuildOutputOptions contains the the outcome of parsing the value of a build --output flag
// Deprecated: This structure is now internal
type BuildOutputOption struct {
Path string // Only valid if !IsStdout
IsDir bool

View File

@@ -19,6 +19,7 @@ import (
"github.com/containers/buildah/internal/metadata"
internalUtil "github.com/containers/buildah/internal/util"
"github.com/containers/buildah/pkg/parse"
"github.com/containers/buildah/pkg/sourcepolicy"
"github.com/containers/buildah/pkg/sshagent"
"github.com/containers/buildah/util"
encconfig "github.com/containers/ocicrypt/config"
@@ -81,6 +82,7 @@ type executor struct {
runtime string
runtimeArgs []string
transientMounts []Mount
transientRunMounts []string
compression archive.Compression
output string
outputFormat string
@@ -92,6 +94,7 @@ type executor struct {
out io.Writer
err io.Writer
signaturePolicyPath string
sourcePolicy *sourcepolicy.Policy
skipUnusedStages types.OptionalBool
systemContext *types.SystemContext
reportWriter io.Writer
@@ -121,6 +124,7 @@ type executor struct {
containerMap map[string]*buildah.Builder // Used to map from image names to only-created-for-the-rootfs containers.
baseMap map[string]struct{} // Holds the names of every base image, as given.
rootfsMap map[string]struct{} // Holds the names of every stage whose rootfs is referenced in a COPY or ADD instruction.
afterDependency map[string]string // Maps stage names to their --after dependency.
blobDirectory string
excludes []string
groupAdd []string
@@ -226,6 +230,15 @@ func newExecutor(logger *logrus.Logger, logPrefix string, store storage.Store, o
return nil, err
}
// Load source policy if specified
var srcPolicy *sourcepolicy.Policy
if options.SourcePolicyFile != "" {
srcPolicy, err = sourcepolicy.LoadFromFile(options.SourcePolicyFile)
if err != nil {
return nil, fmt.Errorf("loading source policy: %w", err)
}
}
writer := options.ReportWriter
if options.Quiet {
writer = io.Discard
@@ -270,11 +283,13 @@ func newExecutor(logger *logrus.Logger, logPrefix string, store storage.Store, o
runtime: options.Runtime,
runtimeArgs: options.RuntimeArgs,
transientMounts: transientMounts,
transientRunMounts: options.TransientRunMounts,
compression: options.Compression,
output: options.Output,
outputFormat: options.OutputFormat,
additionalTags: options.AdditionalTags,
signaturePolicyPath: options.SignaturePolicyPath,
sourcePolicy: srcPolicy,
skipUnusedStages: options.SkipUnusedStages,
systemContext: options.SystemContext,
log: options.Log,
@@ -533,6 +548,16 @@ func (b *executor) buildStage(ctx context.Context, cleanupStages map[int]*stageE
stage := stages[stageIndex]
ib := stage.Builder
node := stage.Node
// Wait for any --after deps before ib.From(node) which may try to access images
// via local transports (like oci-archive:) populated by those deps.
if afterDep, ok := b.afterDependency[stage.Name]; ok {
logrus.Debugf("stage %d (%s): waiting for --after dependency %q", stageIndex, stage.Name, afterDep)
if isStage, err := b.waitForStage(ctx, afterDep, stages[:stageIndex]); isStage && err != nil {
return "", nil, false, fmt.Errorf("waiting for --after=%s: %w", afterDep, err)
}
}
base, err := ib.From(node)
if err != nil {
logrus.Debugf("buildStage(node.Children=%#v)", node.Children)
@@ -794,6 +819,8 @@ func (b *executor) Build(ctx context.Context, stages imagebuilder.Stages) (image
// dependencyInfo is used later to mark if a particular
// stage is needed by target or not.
dependencyMap := make(map[string]*stageDependencyInfo)
// Initialize afterDependency map to track --after= dependency per stage
b.afterDependency = make(map[string]string)
// Build maps of every named base image and every referenced stage root
// filesystem. Individual stages can use them to determine whether or
// not they can skip certain steps near the end of their stages.
@@ -825,7 +852,7 @@ func (b *executor) Build(ctx context.Context, stages imagebuilder.Stages) (image
// append heading args so if --build-arg key=value is not
// specified but default value is set in Containerfile
// via `ARG key=value` so default value can be used.
userArgs = append(builtinArgs, append(userArgs, headingArgs...)...)
userArgs = slices.Concat(builtinArgs, userArgs, headingArgs)
baseWithArg, err := imagebuilder.ProcessWord(base, userArgs)
if err != nil {
return "", nil, fmt.Errorf("while replacing arg variables with values for format %q: %w", base, err)
@@ -844,6 +871,40 @@ func (b *executor) Build(ctx context.Context, stages imagebuilder.Stages) (image
}
}
}
// Parse any --after= flag for explicit stage dependency
for _, flag := range child.Flags {
if after, ok := strings.CutPrefix(flag, "--after="); ok {
// only allow one --after flag per FROM for now; nothing necessarily
// semantically wrong with multiple --after, but keeping it conservative until a
// use case shows up
if _, exists := b.afterDependency[stage.Name]; exists {
return "", nil, fmt.Errorf("FROM --after=%s: only one --after flag is allowed per FROM instruction", after)
}
builtinArgs := argsMapToSlice(stage.Builder.BuiltinArgDefaults)
headingArgs := argsMapToSlice(stage.Builder.HeadingArgs)
userArgs := argsMapToSlice(stage.Builder.Args)
userArgs = slices.Concat(builtinArgs, userArgs, headingArgs)
afterResolved, err := imagebuilder.ProcessWord(after, userArgs)
if err != nil {
return "", nil, fmt.Errorf("while replacing arg variables with values for --after=%q: %w", after, err)
}
// If --after=<index> convert index to name
if index, err := strconv.Atoi(afterResolved); err == nil && index >= 0 && index < stageIndex {
afterResolved = stages[index].Name
}
if depInfo, ok := dependencyMap[afterResolved]; !ok {
return "", nil, fmt.Errorf("FROM --after=%s: stage %q not found", after, afterResolved)
} else if depInfo.Position >= stageIndex {
return "", nil, fmt.Errorf("FROM --after=%s: cannot depend on later stage %q", after, afterResolved)
}
// Mark the stage as a dep so we actually build it
currentStageInfo := dependencyMap[stage.Name]
currentStageInfo.Needs = append(currentStageInfo.Needs, afterResolved)
// And mark it on the stage executor itself so it knows to wait before even pulling
b.afterDependency[stage.Name] = afterResolved
logrus.Debugf("stage %d: explicit dependency on %q via --after", stageIndex, afterResolved)
}
}
case "ADD", "COPY":
for _, flag := range child.Flags { // flags for this instruction
if after, ok := strings.CutPrefix(flag, "--from="); ok {
@@ -864,7 +925,7 @@ func (b *executor) Build(ctx context.Context, stages imagebuilder.Stages) (image
// append heading args so if --build-arg key=value is not
// specified but default value is set in Containerfile
// via `ARG key=value` so default value can be used.
userArgs = append(builtinArgs, append(userArgs, headingArgs...)...)
userArgs = slices.Concat(builtinArgs, userArgs, headingArgs)
baseWithArg, err := imagebuilder.ProcessWord(stageName, userArgs)
if err != nil {
return "", nil, fmt.Errorf("while replacing arg variables with values for format %q: %w", stageName, err)

View File

@@ -20,11 +20,13 @@ import (
buildahdocker "github.com/containers/buildah/docker"
"github.com/containers/buildah/internal"
"github.com/containers/buildah/internal/metadata"
"github.com/containers/buildah/internal/output"
"github.com/containers/buildah/internal/sanitize"
"github.com/containers/buildah/internal/tmpdir"
internalUtil "github.com/containers/buildah/internal/util"
"github.com/containers/buildah/pkg/parse"
"github.com/containers/buildah/pkg/rusage"
"github.com/containers/buildah/pkg/sourcepolicy"
"github.com/containers/buildah/util"
docker "github.com/fsouza/go-dockerclient"
buildkitparser "github.com/moby/buildkit/frontend/dockerfile/parser"
@@ -842,7 +844,7 @@ func (s *stageExecutor) Run(run imagebuilder.Run, config docker.Config) error {
NoPivot: os.Getenv("BUILDAH_NOPIVOT") != "" || s.executor.noPivotRoot,
Quiet: s.executor.quiet,
CompatBuiltinVolumes: types.OptionalBoolFalse,
RunMounts: run.Mounts,
RunMounts: slices.Concat(run.Mounts, s.executor.transientRunMounts),
Runtime: s.executor.runtime,
Secrets: s.executor.secrets,
SSHSources: s.executor.sshsources,
@@ -978,6 +980,55 @@ func (s *stageExecutor) prepare(ctx context.Context, from string, initializeIBCo
}
from = base
}
// Apply source policy if one is configured and this is not "scratch" or a stage reference.
// Stage references are handled separately and don't need policy evaluation since they
// refer to images built within this same build.
if s.executor.sourcePolicy != nil && from != "scratch" {
// Check if 'from' references a previous stage by name, index, or image ID
isStageRef := false
for i, st := range s.stages[:s.index] {
if st.Name == from || strconv.Itoa(i) == from {
isStageRef = true
break
}
}
// Also check if 'from' is an image ID that was created by a previous stage
// (this happens when execute() resolves stage names to image IDs before calling prepare)
if !isStageRef {
s.executor.stagesLock.Lock()
for _, imgID := range s.executor.imageMap {
if imgID == from {
isStageRef = true
break
}
}
s.executor.stagesLock.Unlock()
}
if !isStageRef {
sourceID := sourcepolicy.ImageSourceIdentifier(from)
decision, matched, err := s.executor.sourcePolicy.Evaluate(sourceID)
if err != nil {
return nil, fmt.Errorf("evaluating source policy for %q: %w", from, err)
}
if matched {
switch decision.Action {
case sourcepolicy.ActionDeny:
return nil, fmt.Errorf("source %q denied by source policy: %s", from, decision.Reason)
case sourcepolicy.ActionConvert:
// Extract the new image reference from the policy decision
newFrom := sourcepolicy.ExtractImageRef(decision.TargetRef)
logrus.Debugf("source policy: converting %q to %q (%s)", from, newFrom, decision.Reason)
from = newFrom
case sourcepolicy.ActionAllow:
logrus.Debugf("source policy: allowing %q (%s)", from, decision.Reason)
}
}
}
}
sanitizedFrom, err := s.sanitizeFrom(from, tmpdir.GetTempDir())
if err != nil {
return nil, fmt.Errorf("invalid base image specification %q: %w", from, err)
@@ -1310,11 +1361,11 @@ func (s *stageExecutor) execute(ctx context.Context, base string) (imgID string,
}
// Parse and populate buildOutputOption if needed
var buildOutputOptions []define.BuildOutputOption
var buildOutputOptions []output.BuildOutputOption
if lastStage && len(s.executor.buildOutputs) > 0 {
for _, buildOutput := range s.executor.buildOutputs {
logrus.Debugf("generating custom build output with options %q", buildOutput)
buildOutputOption, err := parse.GetBuildOutput(buildOutput)
buildOutputOption, err := output.GetBuildOutput(buildOutput)
if err != nil {
return "", nil, false, fmt.Errorf("failed to parse build output %q: %w", buildOutput, err)
}
@@ -2387,6 +2438,9 @@ func (s *stageExecutor) intermediateImageExists(ctx context.Context, currNode *p
if image.TopLayer != "" {
imageTopLayer, err = s.executor.store.Layer(image.TopLayer)
if err != nil {
if errors.Is(err, storage.ErrLayerUnknown) {
continue
}
return "", fmt.Errorf("getting top layer info: %w", err)
}
// Figure out which layer from this image we should
@@ -2621,7 +2675,7 @@ func (s *stageExecutor) commit(ctx context.Context, createdBy string, emptyLayer
return results.ImageID, results, nil
}
func (s *stageExecutor) generateBuildOutput(buildOutputOpts define.BuildOutputOption) error {
func (s *stageExecutor) generateBuildOutput(buildOutputOpts output.BuildOutputOption) error {
forceTimestamp := s.executor.timestamp
if s.executor.sourceDateEpoch != nil {
forceTimestamp = s.executor.sourceDateEpoch

View File

@@ -0,0 +1,103 @@
package output
import (
"fmt"
"strings"
)
type BuildOutputType int
const (
BuildOutputInvalid BuildOutputType = 0
BuildOutputStdout BuildOutputType = 1 // stream tar to stdout
BuildOutputLocalDir BuildOutputType = 2
BuildOutputTar BuildOutputType = 3
)
// BuildOutputOptions contains the the outcome of parsing the value of a build --output flag
type BuildOutputOption struct {
Type BuildOutputType
Path string // Only valid if Type is local dir or tar
}
// GetBuildOutput is responsible for parsing custom build output argument i.e `build --output` flag.
// Takes `buildOutput` as string and returns BuildOutputOption
func GetBuildOutput(buildOutput string) (BuildOutputOption, error) {
// Support simple values, in the form --output ./mydir
if !strings.Contains(buildOutput, ",") && !strings.Contains(buildOutput, "=") {
if buildOutput == "-" {
// Feature parity with buildkit, output tar to stdout
// Read more here: https://docs.docker.com/engine/reference/commandline/build/#custom-build-outputs
return BuildOutputOption{
Type: BuildOutputStdout,
Path: "",
}, nil
}
return BuildOutputOption{
Type: BuildOutputLocalDir,
Path: buildOutput,
}, nil
}
// Support complex values, in the form --output type=local,dest=./mydir
typeSelected := BuildOutputInvalid
pathSelected := ""
for option := range strings.SplitSeq(buildOutput, ",") {
key, value, found := strings.Cut(option, "=")
if !found {
return BuildOutputOption{}, fmt.Errorf("invalid build output options %q, expected format key=value", buildOutput)
}
switch key {
case "type":
if typeSelected != BuildOutputInvalid {
return BuildOutputOption{}, fmt.Errorf("duplicate %q not supported", key)
}
switch value {
case "local":
typeSelected = BuildOutputLocalDir
case "tar":
typeSelected = BuildOutputTar
default:
return BuildOutputOption{}, fmt.Errorf("invalid type %q selected for build output options %q", value, buildOutput)
}
case "dest":
if pathSelected != "" {
return BuildOutputOption{}, fmt.Errorf("duplicate %q not supported", key)
}
pathSelected = value
default:
return BuildOutputOption{}, fmt.Errorf("unrecognized key %q in build output option: %q", key, buildOutput)
}
}
// Validate there is a type
if typeSelected == BuildOutputInvalid {
return BuildOutputOption{}, fmt.Errorf("missing required key %q in build output option: %q", "type", buildOutput)
}
// Validate path
if typeSelected == BuildOutputLocalDir || typeSelected == BuildOutputTar {
if pathSelected == "" {
return BuildOutputOption{}, fmt.Errorf("missing required key %q in build output option: %q", "dest", buildOutput)
}
} else {
// Clear path when not needed by type
pathSelected = ""
}
// Handle redirecting stdout for tar output
if pathSelected == "-" {
if typeSelected == BuildOutputTar {
typeSelected = BuildOutputStdout
pathSelected = ""
} else {
return BuildOutputOption{}, fmt.Errorf(`invalid build output option %q, only "type=tar" can be used with "dest=-"`, buildOutput)
}
}
return BuildOutputOption{
Type: typeSelected,
Path: pathSelected,
}, nil
}

View File

@@ -1,4 +1,4 @@
package parse
package parsevolume
import (
"fmt"
@@ -21,8 +21,8 @@ func ValidateVolumeMountHostDir(hostDir string) error {
return nil
}
// RevertEscapedColon converts "\:" to ":"
func RevertEscapedColon(source string) string {
// revertEscapedColon converts "\:" to ":"
func revertEscapedColon(source string) string {
return strings.ReplaceAll(source, "\\:", ":")
}
@@ -37,7 +37,7 @@ func SplitStringWithColonEscape(str string) []string {
sb.WriteRune(r)
} else {
// os.Stat will fail if path contains escaped colon
result = append(result, RevertEscapedColon(sb.String()))
result = append(result, revertEscapedColon(sb.String()))
sb.Reset()
}
} else {
@@ -45,7 +45,7 @@ func SplitStringWithColonEscape(str string) []string {
}
}
if sb.Len() > 0 {
result = append(result, RevertEscapedColon(sb.String()))
result = append(result, revertEscapedColon(sb.String()))
}
return result
}

View File

@@ -6,7 +6,7 @@ import (
"os"
"path/filepath"
"github.com/containers/buildah/define"
"github.com/containers/buildah/internal/output"
v1 "github.com/opencontainers/image-spec/specs-go/v1"
"go.podman.io/common/libimage"
lplatform "go.podman.io/common/libimage/platform"
@@ -51,14 +51,21 @@ func NormalizePlatform(platform v1.Platform) v1.Platform {
}
// ExportFromReader reads bytes from given reader and exports to external tar, directory or stdout.
func ExportFromReader(input io.Reader, opts define.BuildOutputOption) error {
func ExportFromReader(input io.Reader, opts output.BuildOutputOption) error {
var err error
if !filepath.IsAbs(opts.Path) {
if opts.Path, err = filepath.Abs(opts.Path); err != nil {
return err
// Only process path for types that require it.
if opts.Type == output.BuildOutputLocalDir || opts.Type == output.BuildOutputTar {
if !filepath.IsAbs(opts.Path) {
if opts.Path, err = filepath.Abs(opts.Path); err != nil {
return err
}
}
}
if opts.IsDir {
// Process output type
switch opts.Type {
case output.BuildOutputLocalDir:
// In order to keep this feature as close as possible to
// buildkit it was decided to preserve ownership when
// invoked as root since caller already has access to artifacts
@@ -78,17 +85,22 @@ func ExportFromReader(input io.Reader, opts define.BuildOutputOption) error {
if err = chrootarchive.Untar(input, opts.Path, &archive.TarOptions{NoLchown: noLChown}); err != nil {
return fmt.Errorf("failed while performing untar at %q: %w", opts.Path, err)
}
} else {
outFile := os.Stdout
if !opts.IsStdout {
if outFile, err = os.Create(opts.Path); err != nil {
return fmt.Errorf("failed while creating destination tar at %q: %w", opts.Path, err)
}
defer outFile.Close()
case output.BuildOutputStdout:
if _, err = io.Copy(os.Stdout, input); err != nil {
return fmt.Errorf("failed while writing to stdout: %w", err)
}
case output.BuildOutputTar:
outFile, err := os.Create(opts.Path)
if err != nil {
return fmt.Errorf("failed while creating destination tar at %q: %w", opts.Path, err)
}
defer outFile.Close()
if _, err = io.Copy(outFile, input); err != nil {
return fmt.Errorf("failed while performing copy to %q: %w", opts.Path, err)
}
default:
return fmt.Errorf("unsupported output type %q", opts.Type)
}
return nil
}

View File

@@ -14,7 +14,7 @@ import (
"github.com/containers/buildah/copier"
"github.com/containers/buildah/define"
"github.com/containers/buildah/internal"
internalParse "github.com/containers/buildah/internal/parse"
internalParse "github.com/containers/buildah/internal/parsevolume"
"github.com/containers/buildah/internal/tmpdir"
internalUtil "github.com/containers/buildah/internal/util"
"github.com/containers/buildah/pkg/overlay"

View File

@@ -18,6 +18,7 @@ import (
"time"
"github.com/containers/buildah/define"
"github.com/containers/buildah/internal/output"
"github.com/containers/buildah/pkg/parse"
"github.com/containers/buildah/pkg/util"
"github.com/opencontainers/runtime-spec/specs-go"
@@ -48,7 +49,7 @@ func GenBuildOptions(c *cobra.Command, inputArgs []string, iopts BuildOptions) (
var removeAll []string
output := ""
outputSpec := ""
cleanTmpFile := false
tags := []string{}
if iopts.Network == "none" {
@@ -65,7 +66,7 @@ func GenBuildOptions(c *cobra.Command, inputArgs []string, iopts BuildOptions) (
if c.Flag("tag").Changed {
tags = iopts.Tag
if len(tags) > 0 {
output = tags[0]
outputSpec = tags[0]
tags = tags[1:]
}
if c.Flag("manifest").Changed {
@@ -278,11 +279,11 @@ func GenBuildOptions(c *cobra.Command, inputArgs []string, iopts BuildOptions) (
for _, buildOutput := range iopts.BuildOutputs {
// if any of these go to stdout, we need to avoid
// interspersing our random output in with it
buildOption, err := parse.GetBuildOutput(buildOutput)
buildOption, err := output.GetBuildOutput(buildOutput)
if err != nil {
return options, nil, nil, err
}
if buildOption.IsStdout {
if buildOption.Type == output.BuildOutputStdout {
iopts.Quiet = true
}
}
@@ -431,7 +432,7 @@ func GenBuildOptions(c *cobra.Command, inputArgs []string, iopts BuildOptions) (
OSVersion: iopts.OSVersion,
OciDecryptConfig: decryptConfig,
Out: stdout,
Output: output,
Output: outputSpec,
OutputFormat: format,
Platforms: platforms,
PullPolicy: pullPolicy,
@@ -445,6 +446,7 @@ func GenBuildOptions(c *cobra.Command, inputArgs []string, iopts BuildOptions) (
SBOMScanOptions: sbomScanOptions,
SignBy: iopts.SignBy,
SignaturePolicyPath: iopts.SignaturePolicy,
SourcePolicyFile: iopts.SourcePolicyFile,
SkipUnusedStages: skipUnusedStages,
SourceDateEpoch: sourceDateEpoch,
Squash: iopts.Squash,
@@ -452,6 +454,7 @@ func GenBuildOptions(c *cobra.Command, inputArgs []string, iopts BuildOptions) (
Target: iopts.Target,
Timestamp: timestamp,
TransientMounts: iopts.Volumes,
TransientRunMounts: iopts.TransientRunMounts,
UnsetEnvs: iopts.UnsetEnvs,
UnsetLabels: iopts.UnsetLabels,
UnsetAnnotations: iopts.UnsetAnnotations,

View File

@@ -130,6 +130,8 @@ type BudResults struct {
SourceDateEpoch string
RewriteTimestamp bool
CreatedAnnotation bool
SourcePolicyFile string
TransientRunMounts []string
}
// FromAndBugResults represents the results for common flags
@@ -300,6 +302,7 @@ newer: only pull base and SBOM scanner images when newer images exist on the r
fs.BoolVar(&flags.Rm, "rm", true, "remove intermediate containers after a successful build")
// "runtime" definition moved to avoid name collision in podman build. Defined in cmd/buildah/build.go.
fs.StringSliceVar(&flags.RuntimeFlags, "runtime-flag", []string{}, "add global flags for the container runtime")
fs.StringArrayVar(&flags.TransientRunMounts, "mount", []string{}, "set transient mounts for each RUN instruction, e.g. type=secret,id=mysecret")
fs.StringVar(&flags.SbomPreset, "sbom", "", "scan working container using `preset` configuration")
fs.StringVar(&flags.SbomScannerImage, "sbom-scanner-image", "", "scan working container using scanner command from `image`")
fs.StringArrayVar(&flags.SbomScannerCommand, "sbom-scanner-command", nil, "scan working container using `command` in scanner image")
@@ -314,6 +317,7 @@ newer: only pull base and SBOM scanner images when newer images exist on the r
if err := fs.MarkHidden("signature-policy"); err != nil {
panic(fmt.Sprintf("error marking the signature-policy flag as hidden: %v", err))
}
fs.StringVar(&flags.SourcePolicyFile, "source-policy-file", "", "`pathname` of source policy file for controlling source references during build")
fs.BoolVar(&flags.SkipUnusedStages, "skip-unused-stages", true, "skips stages in multi-stage builds which do not affect the final target")
sourceDateEpochUsageDefault := ", defaults to current time"
if v := os.Getenv(internal.SourceDateEpochName); v != "" {
@@ -366,6 +370,7 @@ func GetBudFlagsCompletions() commonComp.FlagCompletions {
flagCompletion["logfile"] = commonComp.AutocompleteDefault
flagCompletion["manifest"] = commonComp.AutocompleteDefault
flagCompletion["metadata-file"] = commonComp.AutocompleteDefault
flagCompletion["mount"] = commonComp.AutocompleteNone
flagCompletion["os"] = commonComp.AutocompleteNone
flagCompletion["os-feature"] = commonComp.AutocompleteNone
flagCompletion["os-version"] = commonComp.AutocompleteNone
@@ -383,6 +388,7 @@ func GetBudFlagsCompletions() commonComp.FlagCompletions {
flagCompletion["secret"] = commonComp.AutocompleteNone
flagCompletion["sign-by"] = commonComp.AutocompleteNone
flagCompletion["signature-policy"] = commonComp.AutocompleteNone
flagCompletion["source-policy-file"] = commonComp.AutocompleteDefault
flagCompletion["ssh"] = commonComp.AutocompleteNone
flagCompletion["source-date-epoch"] = commonComp.AutocompleteNone
flagCompletion["tag"] = commonComp.AutocompleteNone

View File

@@ -1,4 +1,4 @@
package parse
package parse //nolint:revive,nolintlint
// this package should contain functions that parse and validate
// user input and is shared either amongst buildah subcommands or
@@ -19,7 +19,7 @@ import (
"github.com/containerd/platforms"
"github.com/containers/buildah/define"
mkcwtypes "github.com/containers/buildah/internal/mkcw/types"
internalParse "github.com/containers/buildah/internal/parse"
internalParse "github.com/containers/buildah/internal/parsevolume"
"github.com/containers/buildah/internal/sbom"
"github.com/containers/buildah/internal/tmpdir"
"github.com/containers/buildah/pkg/sshagent"
@@ -734,6 +734,9 @@ func AuthConfig(creds string) (*types.DockerAuthConfig, error) {
// GetBuildOutput is responsible for parsing custom build output argument i.e `build --output` flag.
// Takes `buildOutput` as string and returns BuildOutputOption
// This function will read an argument of `type=tar` as "output in a local folder names type=tar"
//
// Deprecated: This function is now internal
func GetBuildOutput(buildOutput string) (define.BuildOutputOption, error) {
if buildOutput == "-" {
// Feature parity with buildkit, output tar to stdout

View File

@@ -1,6 +1,6 @@
//go:build linux || darwin
package parse
package parse //nolint:revive,nolintlint
import (
"fmt"

View File

@@ -1,6 +1,6 @@
//go:build !linux && !darwin
package parse
package parse //nolint:revive,nolintlint
import (
"errors"

View File

@@ -0,0 +1,305 @@
// Package sourcepolicy implements BuildKit-compatible source policy evaluation
// for controlling and transforming source references during builds.
//
// Source policies allow users to:
// - Pin base image tags to specific digests at build time
// - Deny specific sources from being used
// - Transform source references without modifying Containerfiles or Dockerfiles
//
// The policy file format is compatible with BuildKit's source policy JSON schema.
package sourcepolicy
import (
"encoding/json"
"fmt"
"os"
"strings"
"go.podman.io/image/v5/docker/reference"
)
// Action represents the action to take when a rule matches.
type Action string
const (
// ActionAllow explicitly allows a source (no transformation).
ActionAllow Action = "ALLOW"
// ActionDeny blocks the source and fails the build.
ActionDeny Action = "DENY"
// ActionConvert transforms the source to a different reference.
ActionConvert Action = "CONVERT"
)
// MatchType represents how the selector identifier should be matched.
type MatchType string
const (
// MatchTypeExact requires an exact string match.
MatchTypeExact MatchType = "EXACT"
// MatchTypeWildcard allows * and ? glob patterns.
MatchTypeWildcard MatchType = "WILDCARD"
// MatchTypeRegex allows regular expression patterns (not implemented in MVP).
MatchTypeRegex MatchType = "REGEX"
)
// Selector specifies which sources a rule applies to.
type Selector struct {
// Identifier is the source identifier to match.
// For docker images, this is typically "docker-image://registry/repo:tag".
Identifier string `json:"identifier"`
// MatchType specifies how the identifier should be matched.
// Defaults to EXACT if not specified.
MatchType MatchType `json:"matchType,omitempty"`
}
// Updates specifies how to transform a matched source.
type Updates struct {
// Identifier is the new source identifier to use.
// For CONVERT actions, this replaces the original identifier.
Identifier string `json:"identifier,omitempty"`
// Attrs contains additional attributes (e.g., http.checksum).
// Reserved for future use with HTTP sources.
Attrs map[string]string `json:"attrs,omitempty"`
}
// Rule represents a single policy rule.
type Rule struct {
// Action specifies what to do when this rule matches.
Action Action `json:"action"`
// Selector specifies which sources this rule applies to.
Selector Selector `json:"selector"`
// Updates specifies how to transform the source (for CONVERT action).
Updates *Updates `json:"updates,omitempty"`
}
// Policy represents a source policy containing multiple rules.
type Policy struct {
// Rules is the list of policy rules, evaluated in order.
// First matching rule wins.
Rules []Rule `json:"rules"`
}
// Decision represents the result of evaluating a source against a policy.
type Decision struct {
// Action is the action to take (ALLOW, DENY, or CONVERT).
Action Action
// TargetRef is the new reference to use (for CONVERT actions).
TargetRef string
// Reason provides context for the decision (e.g., which rule matched).
Reason string
}
// LoadFromFile loads a source policy from a JSON file.
func LoadFromFile(path string) (*Policy, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("reading source policy file %q: %w", path, err)
}
return Parse(data)
}
// Parse parses a source policy from JSON data.
func Parse(data []byte) (*Policy, error) {
var policy Policy
if err := json.Unmarshal(data, &policy); err != nil {
return nil, fmt.Errorf("parsing source policy JSON: %w", err)
}
if err := policy.Validate(); err != nil {
return nil, fmt.Errorf("validating source policy: %w", err)
}
return &policy, nil
}
// Validate checks that the policy is well-formed.
func (p *Policy) Validate() error {
if len(p.Rules) == 0 {
// Empty policy is valid - it just means no rules apply
return nil
}
for i, rule := range p.Rules {
if err := rule.Validate(); err != nil {
return fmt.Errorf("rule %d: %w", i, err)
}
}
return nil
}
// Validate checks that a rule is well-formed.
func (r *Rule) Validate() error {
// Validate action
switch r.Action {
case ActionAllow, ActionDeny, ActionConvert:
// Valid actions
case "":
return fmt.Errorf("action is required")
default:
return fmt.Errorf("unknown action %q (valid: ALLOW, DENY, CONVERT)", r.Action)
}
// Validate selector
if r.Selector.Identifier == "" {
return fmt.Errorf("selector.identifier is required")
}
// Validate match type
switch r.Selector.MatchType {
case MatchTypeExact, MatchTypeWildcard, "":
// Valid match types (empty defaults to EXACT)
case MatchTypeRegex:
return fmt.Errorf("REGEX match type is not supported in this version")
default:
return fmt.Errorf("unknown matchType %q (valid: EXACT, WILDCARD)", r.Selector.MatchType)
}
// Validate updates for CONVERT action
if r.Action == ActionConvert {
if r.Updates == nil || r.Updates.Identifier == "" {
return fmt.Errorf("updates.identifier is required for CONVERT action")
}
}
return nil
}
// Evaluate checks a source identifier against the policy and returns a decision.
// The first matching rule wins. If no rule matches, returns (Decision{}, false, nil).
func (p *Policy) Evaluate(sourceIdentifier string) (Decision, bool, error) {
if p == nil || len(p.Rules) == 0 {
return Decision{}, false, nil
}
for i, rule := range p.Rules {
matched, err := rule.Matches(sourceIdentifier)
if err != nil {
return Decision{}, false, fmt.Errorf("evaluating rule %d: %w", i, err)
}
if matched {
decision := Decision{
Action: rule.Action,
Reason: fmt.Sprintf("matched rule %d (selector: %q)", i, rule.Selector.Identifier),
}
if rule.Action == ActionConvert && rule.Updates != nil {
decision.TargetRef = rule.Updates.Identifier
}
return decision, true, nil
}
}
return Decision{}, false, nil
}
// Matches checks if a source identifier matches this rule's selector.
func (r *Rule) Matches(sourceIdentifier string) (bool, error) {
matchType := r.Selector.MatchType
if matchType == "" {
matchType = MatchTypeWildcard
}
switch matchType {
case MatchTypeExact:
return r.Selector.Identifier == sourceIdentifier, nil
case MatchTypeWildcard:
return matchWildcard(r.Selector.Identifier, sourceIdentifier), nil
default:
return false, fmt.Errorf("unsupported match type: %s", matchType)
}
}
// matchWildcard performs glob-style pattern matching.
// Supports * (matches any sequence of characters) and ? (matches any single character).
func matchWildcard(pattern, str string) bool {
// Use a simple recursive approach for wildcard matching
return wildcardMatch(pattern, str)
}
// wildcardMatch implements recursive wildcard matching.
func wildcardMatch(pattern, str string) bool {
for len(pattern) > 0 {
switch pattern[0] {
case '*':
// * matches zero or more characters
// Try matching the rest of the pattern against all possible suffixes
pattern = pattern[1:]
if len(pattern) == 0 {
// Trailing * matches everything
return true
}
// Try matching at each position
for i := 0; i <= len(str); i++ {
if wildcardMatch(pattern, str[i:]) {
return true
}
}
return false
case '?':
// ? matches exactly one character
if len(str) == 0 {
return false
}
pattern = pattern[1:]
str = str[1:]
default:
// Regular character must match exactly
if len(str) == 0 || pattern[0] != str[0] {
return false
}
pattern = pattern[1:]
str = str[1:]
}
}
return len(str) == 0
}
// ImageSourceIdentifier creates a BuildKit-style source identifier for a docker image.
// This normalizes image references to the format "docker-image://registry/repo:tag".
func ImageSourceIdentifier(imageRef string) string {
// If already in docker-image:// format, return as-is
if strings.HasPrefix(imageRef, "docker-image://") {
return imageRef
}
// Normalize the image reference
normalized := normalizeImageRef(imageRef)
return "docker-image://" + normalized
}
// normalizeImageRef normalizes an image reference to include registry and library prefix.
func normalizeImageRef(ref string) string {
// Handle scratch specially
if ref == "scratch" {
return ref
}
// Use go.podman.io/image/v5/docker/reference for proper normalization
named, err := reference.ParseNormalizedNamed(ref)
if err != nil {
// If parsing fails, return the original reference
return ref
}
return named.String()
}
// ExtractImageRef extracts the image reference from a BuildKit-style source identifier.
// It returns the original identifier if it's not a docker-image:// reference.
func ExtractImageRef(sourceIdentifier string) string {
const prefix = "docker-image://"
if strings.HasPrefix(sourceIdentifier, prefix) {
return sourceIdentifier[len(prefix):]
}
return sourceIdentifier
}

View File

@@ -1421,7 +1421,7 @@ func (b *Builder) setupMounts(mountPoint string, spec *specs.Spec, bundlePath st
}
mountArtifacts.RunOverlayDirs = append(mountArtifacts.RunOverlayDirs, overlayDirs...)
allMounts := util.SortMounts(append(append(append(append(append(volumes, builtins...), runMounts...), subscriptionMounts...), bindFileMounts...), specMounts...))
allMounts := util.SortMounts(slices.Concat(volumes, builtins, runMounts, subscriptionMounts, bindFileMounts, specMounts))
// Add them all, in the preferred order, except where they conflict with something that was previously added.
for _, mount := range allMounts {

View File

@@ -285,7 +285,7 @@ func (b *Builder) Run(command []string, options RunOptions) error {
}
devices = append(devices, device...)
}
devices = append(append(devices, options.Devices...), b.Devices...)
devices = slices.Concat(devices, options.Devices, b.Devices)
// Mount devices, if any, and if we're rootless attempt to work around not
// being able to create device nodes by bind-mounting them from the host, like podman does.

View File

@@ -7,7 +7,7 @@ This package provides various compression algorithms.
* Optimized [deflate](https://godoc.org/github.com/klauspost/compress/flate) packages which can be used as a dropin replacement for [gzip](https://godoc.org/github.com/klauspost/compress/gzip), [zip](https://godoc.org/github.com/klauspost/compress/zip) and [zlib](https://godoc.org/github.com/klauspost/compress/zlib).
* [snappy](https://github.com/klauspost/compress/tree/master/snappy) is a drop-in replacement for `github.com/golang/snappy` offering better compression and concurrent streams.
* [huff0](https://github.com/klauspost/compress/tree/master/huff0) and [FSE](https://github.com/klauspost/compress/tree/master/fse) implementations for raw entropy encoding.
* [gzhttp](https://github.com/klauspost/compress/tree/master/gzhttp) Provides client and server wrappers for handling gzipped requests efficiently.
* [gzhttp](https://github.com/klauspost/compress/tree/master/gzhttp) Provides client and server wrappers for handling gzipped/zstd HTTP requests efficiently.
* [pgzip](https://github.com/klauspost/pgzip) is a separate package that provides a very fast parallel gzip implementation.
[![Go Reference](https://pkg.go.dev/badge/klauspost/compress.svg)](https://pkg.go.dev/github.com/klauspost/compress?tab=subdirectories)
@@ -26,8 +26,14 @@ This package will support the current Go version and 2 versions back.
Use the links above for more information on each.
# changelog
* Jan 16th, 2026 [1.18.3](https://github.com/klauspost/compress/releases/tag/v1.18.3)
* Downstream CVE-2025-61728. See [golang/go#77102](https://github.com/golang/go/issues/77102).
* Oct 20, 2025 - [1.18.1](https://github.com/klauspost/compress/releases/tag/v1.18.1)
* Dec 1st, 2025 - [1.18.2](https://github.com/klauspost/compress/releases/tag/v1.18.2)
* flate: Fix invalid encoding on level 9 with single value input in https://github.com/klauspost/compress/pull/1115
* flate: reduce stateless allocations by @RXamzin in https://github.com/klauspost/compress/pull/1106
* Oct 20, 2025 - [1.18.1](https://github.com/klauspost/compress/releases/tag/v1.18.1) - RETRACTED
* zstd: Add simple zstd EncodeTo/DecodeTo functions https://github.com/klauspost/compress/pull/1079
* zstd: Fix incorrect buffer size in dictionary encodes https://github.com/klauspost/compress/pull/1059
* s2: check for cap, not len of buffer in EncodeBetter/Best by @vdarulis in https://github.com/klauspost/compress/pull/1080
@@ -603,7 +609,7 @@ While the release has been extensively tested, it is recommended to testing when
# deflate usage
The packages are drop-in replacements for standard libraries. Simply replace the import path to use them:
The packages are drop-in replacements for standard library [deflate](https://godoc.org/github.com/klauspost/compress/flate), [gzip](https://godoc.org/github.com/klauspost/compress/gzip), [zip](https://godoc.org/github.com/klauspost/compress/zip), and [zlib](https://godoc.org/github.com/klauspost/compress/zlib). Simply replace the import path to use them:
Typical speed is about 2x of the standard library packages.
@@ -614,17 +620,15 @@ Typical speed is about 2x of the standard library packages.
| `archive/zip` | `github.com/klauspost/compress/zip` | [zip](https://pkg.go.dev/github.com/klauspost/compress/zip?tab=doc) |
| `compress/flate` | `github.com/klauspost/compress/flate` | [flate](https://pkg.go.dev/github.com/klauspost/compress/flate?tab=doc) |
* Optimized [deflate](https://godoc.org/github.com/klauspost/compress/flate) packages which can be used as a dropin replacement for [gzip](https://godoc.org/github.com/klauspost/compress/gzip), [zip](https://godoc.org/github.com/klauspost/compress/zip) and [zlib](https://godoc.org/github.com/klauspost/compress/zlib).
You may also be interested in [pgzip](https://github.com/klauspost/pgzip), which is a drop-in replacement for gzip, which support multithreaded compression on big files and the optimized [crc32](https://github.com/klauspost/crc32) package used by these packages.
You may also be interested in [pgzip](https://github.com/klauspost/pgzip), which is a drop in replacement for gzip, which support multithreaded compression on big files and the optimized [crc32](https://github.com/klauspost/crc32) package used by these packages.
The packages contains the same as the standard library, so you can use the godoc for that: [gzip](http://golang.org/pkg/compress/gzip/), [zip](http://golang.org/pkg/archive/zip/), [zlib](http://golang.org/pkg/compress/zlib/), [flate](http://golang.org/pkg/compress/flate/).
The packages implement the same API as the standard library, so you can use the original godoc documentation: [gzip](http://golang.org/pkg/compress/gzip/), [zip](http://golang.org/pkg/archive/zip/), [zlib](http://golang.org/pkg/compress/zlib/), [flate](http://golang.org/pkg/compress/flate/).
Currently there is only minor speedup on decompression (mostly CRC32 calculation).
Memory usage is typically 1MB for a Writer. stdlib is in the same range.
If you expect to have a lot of concurrently allocated Writers consider using
the stateless compress described below.
the stateless compression described below.
For compression performance, see: [this spreadsheet](https://docs.google.com/spreadsheets/d/1nuNE2nPfuINCZJRMt6wFWhKpToF95I47XjSsc-1rbPQ/edit?usp=sharing).
@@ -684,3 +688,6 @@ Here are other packages of good quality and pure Go (no cgo wrappers or autoconv
This code is licensed under the same conditions as the original Go code. See LICENSE file.

View File

@@ -39,9 +39,6 @@ type Decoder struct {
frame *frameDec
// Custom dictionaries.
dicts map[uint32]*dict
// streamWg is the waitgroup for all streams
streamWg sync.WaitGroup
}
@@ -101,12 +98,10 @@ func NewReader(r io.Reader, opts ...DOption) (*Decoder, error) {
d.current.err = ErrDecoderNilInput
}
// Transfer option dicts.
d.dicts = make(map[uint32]*dict, len(d.o.dicts))
for _, dc := range d.o.dicts {
d.dicts[dc.id] = dc
// Initialize dict map if needed.
if d.o.dicts == nil {
d.o.dicts = make(map[uint32]*dict)
}
d.o.dicts = nil
// Create decoders
d.decoders = make(chan *blockDec, d.o.concurrent)
@@ -238,6 +233,21 @@ func (d *Decoder) Reset(r io.Reader) error {
return nil
}
// ResetWithOptions will reset the decoder and apply the given options
// for the next stream or DecodeAll operation.
// Options are applied on top of the existing options.
// Some options cannot be changed on reset and will return an error.
func (d *Decoder) ResetWithOptions(r io.Reader, opts ...DOption) error {
d.o.resetOpt = true
defer func() { d.o.resetOpt = false }()
for _, o := range opts {
if err := o(&d.o); err != nil {
return err
}
}
return d.Reset(r)
}
// drainOutput will drain the output until errEndOfStream is sent.
func (d *Decoder) drainOutput() {
if d.current.cancel != nil {
@@ -930,7 +940,7 @@ decodeStream:
}
func (d *Decoder) setDict(frame *frameDec) (err error) {
dict, ok := d.dicts[frame.DictionaryID]
dict, ok := d.o.dicts[frame.DictionaryID]
if ok {
if debugDecoder {
println("setting dict", frame.DictionaryID)

View File

@@ -20,10 +20,11 @@ type decoderOptions struct {
concurrent int
maxDecodedSize uint64
maxWindowSize uint64
dicts []*dict
dicts map[uint32]*dict
ignoreChecksum bool
limitToCap bool
decodeBufsBelow int
resetOpt bool
}
func (o *decoderOptions) setDefault() {
@@ -42,8 +43,15 @@ func (o *decoderOptions) setDefault() {
// WithDecoderLowmem will set whether to use a lower amount of memory,
// but possibly have to allocate more while running.
// Cannot be changed with ResetWithOptions.
func WithDecoderLowmem(b bool) DOption {
return func(o *decoderOptions) error { o.lowMem = b; return nil }
return func(o *decoderOptions) error {
if o.resetOpt && b != o.lowMem {
return errors.New("WithDecoderLowmem cannot be changed on Reset")
}
o.lowMem = b
return nil
}
}
// WithDecoderConcurrency sets the number of created decoders.
@@ -53,18 +61,23 @@ func WithDecoderLowmem(b bool) DOption {
// inflight blocks.
// When decoding streams and setting maximum to 1,
// no async decoding will be done.
// The value supplied must be at least 0.
// When a value of 0 is provided GOMAXPROCS will be used.
// By default this will be set to 4 or GOMAXPROCS, whatever is lower.
// Cannot be changed with ResetWithOptions.
func WithDecoderConcurrency(n int) DOption {
return func(o *decoderOptions) error {
if n < 0 {
return errors.New("concurrency must be at least 1")
return errors.New("concurrency must be at least 0")
}
newVal := n
if n == 0 {
o.concurrent = runtime.GOMAXPROCS(0)
} else {
o.concurrent = n
newVal = runtime.GOMAXPROCS(0)
}
if o.resetOpt && newVal != o.concurrent {
return errors.New("WithDecoderConcurrency cannot be changed on Reset")
}
o.concurrent = newVal
return nil
}
}
@@ -73,6 +86,7 @@ func WithDecoderConcurrency(n int) DOption {
// non-streaming operations or maximum window size for streaming operations.
// This can be used to control memory usage of potentially hostile content.
// Maximum is 1 << 63 bytes. Default is 64GiB.
// Can be changed with ResetWithOptions.
func WithDecoderMaxMemory(n uint64) DOption {
return func(o *decoderOptions) error {
if n == 0 {
@@ -92,16 +106,20 @@ func WithDecoderMaxMemory(n uint64) DOption {
// "zstd --train" from the Zstandard reference implementation.
//
// If several dictionaries with the same ID are provided, the last one will be used.
// Can be changed with ResetWithOptions.
//
// [dictionary format]: https://github.com/facebook/zstd/blob/dev/doc/zstd_compression_format.md#dictionary-format
func WithDecoderDicts(dicts ...[]byte) DOption {
return func(o *decoderOptions) error {
if o.dicts == nil {
o.dicts = make(map[uint32]*dict)
}
for _, b := range dicts {
d, err := loadDict(b)
if err != nil {
return err
}
o.dicts = append(o.dicts, d)
o.dicts[d.id] = d
}
return nil
}
@@ -109,12 +127,16 @@ func WithDecoderDicts(dicts ...[]byte) DOption {
// WithDecoderDictRaw registers a dictionary that may be used by the decoder.
// The slice content can be arbitrary data.
// Can be changed with ResetWithOptions.
func WithDecoderDictRaw(id uint32, content []byte) DOption {
return func(o *decoderOptions) error {
if bits.UintSize > 32 && uint(len(content)) > dictMaxLength {
return fmt.Errorf("dictionary of size %d > 2GiB too large", len(content))
}
o.dicts = append(o.dicts, &dict{id: id, content: content, offsets: [3]int{1, 4, 8}})
if o.dicts == nil {
o.dicts = make(map[uint32]*dict)
}
o.dicts[id] = &dict{id: id, content: content, offsets: [3]int{1, 4, 8}}
return nil
}
}
@@ -124,6 +146,7 @@ func WithDecoderDictRaw(id uint32, content []byte) DOption {
// The Decoder will likely allocate more memory based on the WithDecoderLowmem setting.
// If WithDecoderMaxMemory is set to a lower value, that will be used.
// Default is 512MB, Maximum is ~3.75 TB as per zstandard spec.
// Can be changed with ResetWithOptions.
func WithDecoderMaxWindow(size uint64) DOption {
return func(o *decoderOptions) error {
if size < MinWindowSize {
@@ -141,6 +164,7 @@ func WithDecoderMaxWindow(size uint64) DOption {
// or any size set in WithDecoderMaxMemory.
// This can be used to limit decoding to a specific maximum output size.
// Disabled by default.
// Can be changed with ResetWithOptions.
func WithDecodeAllCapLimit(b bool) DOption {
return func(o *decoderOptions) error {
o.limitToCap = b
@@ -153,17 +177,37 @@ func WithDecodeAllCapLimit(b bool) DOption {
// This typically uses less allocations but will have the full decompressed object in memory.
// Note that DecodeAllCapLimit will disable this, as well as giving a size of 0 or less.
// Default is 128KiB.
// Cannot be changed with ResetWithOptions.
func WithDecodeBuffersBelow(size int) DOption {
return func(o *decoderOptions) error {
if o.resetOpt && size != o.decodeBufsBelow {
return errors.New("WithDecodeBuffersBelow cannot be changed on Reset")
}
o.decodeBufsBelow = size
return nil
}
}
// IgnoreChecksum allows to forcibly ignore checksum checking.
// Can be changed with ResetWithOptions.
func IgnoreChecksum(b bool) DOption {
return func(o *decoderOptions) error {
o.ignoreChecksum = b
return nil
}
}
// WithDecoderDictDelete removes dictionaries by ID.
// If no ids are passed, all dictionaries are deleted.
// Should be used with ResetWithOptions.
func WithDecoderDictDelete(ids ...uint32) DOption {
return func(o *decoderOptions) error {
if len(ids) == 0 {
clear(o.dicts)
}
for _, id := range ids {
delete(o.dicts, id)
}
return nil
}
}

View File

@@ -131,6 +131,22 @@ func (e *Encoder) Reset(w io.Writer) {
s.frameContentSize = 0
}
// ResetWithOptions will re-initialize the writer and apply the given options
// as a new, independent stream.
// Options are applied on top of the existing options.
// Some options cannot be changed on reset and will return an error.
func (e *Encoder) ResetWithOptions(w io.Writer, opts ...EOption) error {
e.o.resetOpt = true
defer func() { e.o.resetOpt = false }()
for _, o := range opts {
if err := o(&e.o); err != nil {
return err
}
}
e.Reset(w)
return nil
}
// ResetContentSize will reset and set a content size for the next stream.
// If the bytes written does not match the size given an error will be returned
// when calling Close().

View File

@@ -14,6 +14,7 @@ type EOption func(*encoderOptions) error
// options retains accumulated state of multiple options.
type encoderOptions struct {
resetOpt bool
concurrent int
level EncoderLevel
single *bool
@@ -71,19 +72,28 @@ func (o encoderOptions) encoder() encoder {
// WithEncoderCRC will add CRC value to output.
// Output will be 4 bytes larger.
// Can be changed with ResetWithOptions.
func WithEncoderCRC(b bool) EOption {
return func(o *encoderOptions) error { o.crc = b; return nil }
}
// WithEncoderConcurrency will set the concurrency,
// meaning the maximum number of encoders to run concurrently.
// The value supplied must be at least 1.
// The value supplied must be at least 0.
// When a value of 0 is provided GOMAXPROCS will be used.
// For streams, setting a value of 1 will disable async compression.
// By default this will be set to GOMAXPROCS.
// Cannot be changed with ResetWithOptions.
func WithEncoderConcurrency(n int) EOption {
return func(o *encoderOptions) error {
if n <= 0 {
return fmt.Errorf("concurrency must be at least 1")
if n < 0 {
return errors.New("concurrency must at least 0")
}
if n == 0 {
n = runtime.GOMAXPROCS(0)
}
if o.resetOpt && n != o.concurrent {
return errors.New("WithEncoderConcurrency cannot be changed on Reset")
}
o.concurrent = n
return nil
@@ -95,6 +105,7 @@ func WithEncoderConcurrency(n int) EOption {
// A larger value will enable better compression but allocate more memory and,
// for above-default values, take considerably longer.
// The default value is determined by the compression level and max 8MB.
// Cannot be changed with ResetWithOptions.
func WithWindowSize(n int) EOption {
return func(o *encoderOptions) error {
switch {
@@ -105,6 +116,9 @@ func WithWindowSize(n int) EOption {
case (n & (n - 1)) != 0:
return errors.New("window size must be a power of 2")
}
if o.resetOpt && n != o.windowSize {
return errors.New("WithWindowSize cannot be changed on Reset")
}
o.windowSize = n
o.customWindow = true
@@ -122,6 +136,7 @@ func WithWindowSize(n int) EOption {
// n must be > 0 and <= 1GB, 1<<30 bytes.
// The padded area will be filled with data from crypto/rand.Reader.
// If `EncodeAll` is used with data already in the destination, the total size will be multiple of this.
// Can be changed with ResetWithOptions.
func WithEncoderPadding(n int) EOption {
return func(o *encoderOptions) error {
if n <= 0 {
@@ -215,12 +230,16 @@ func (e EncoderLevel) String() string {
}
// WithEncoderLevel specifies a predefined compression level.
// Cannot be changed with ResetWithOptions.
func WithEncoderLevel(l EncoderLevel) EOption {
return func(o *encoderOptions) error {
switch {
case l <= speedNotSet || l >= speedLast:
return fmt.Errorf("unknown encoder level")
}
if o.resetOpt && l != o.level {
return errors.New("WithEncoderLevel cannot be changed on Reset")
}
o.level = l
if !o.customWindow {
switch o.level {
@@ -248,6 +267,7 @@ func WithEncoderLevel(l EncoderLevel) EOption {
// WithZeroFrames will encode 0 length input as full frames.
// This can be needed for compatibility with zstandard usage,
// but is not needed for this package.
// Can be changed with ResetWithOptions.
func WithZeroFrames(b bool) EOption {
return func(o *encoderOptions) error {
o.fullZero = b
@@ -259,6 +279,7 @@ func WithZeroFrames(b bool) EOption {
// Disabling this will skip incompressible data faster, but in cases with no matches but
// skewed character distribution compression is lost.
// Default value depends on the compression level selected.
// Can be changed with ResetWithOptions.
func WithAllLitEntropyCompression(b bool) EOption {
return func(o *encoderOptions) error {
o.customALEntropy = true
@@ -270,6 +291,7 @@ func WithAllLitEntropyCompression(b bool) EOption {
// WithNoEntropyCompression will always skip entropy compression of literals.
// This can be useful if content has matches, but unlikely to benefit from entropy
// compression. Usually the slight speed improvement is not worth enabling this.
// Can be changed with ResetWithOptions.
func WithNoEntropyCompression(b bool) EOption {
return func(o *encoderOptions) error {
o.noEntropy = b
@@ -287,6 +309,7 @@ func WithNoEntropyCompression(b bool) EOption {
// This is only a recommendation, each decoder is free to support higher or lower limits, depending on local limitations.
// If this is not specified, block encodes will automatically choose this based on the input size and the window size.
// This setting has no effect on streamed encodes.
// Can be changed with ResetWithOptions.
func WithSingleSegment(b bool) EOption {
return func(o *encoderOptions) error {
o.single = &b
@@ -298,8 +321,12 @@ func WithSingleSegment(b bool) EOption {
// slower encoding speed.
// This will not change the window size which is the primary function for reducing
// memory usage. See WithWindowSize.
// Cannot be changed with ResetWithOptions.
func WithLowerEncoderMem(b bool) EOption {
return func(o *encoderOptions) error {
if o.resetOpt && b != o.lowMem {
return errors.New("WithLowerEncoderMem cannot be changed on Reset")
}
o.lowMem = b
return nil
}
@@ -311,6 +338,7 @@ func WithLowerEncoderMem(b bool) EOption {
// "zstd --train" from the Zstandard reference implementation.
//
// The encoder *may* choose to use no dictionary instead for certain payloads.
// Can be changed with ResetWithOptions.
//
// [dictionary format]: https://github.com/facebook/zstd/blob/dev/doc/zstd_compression_format.md#dictionary-format
func WithEncoderDict(dict []byte) EOption {
@@ -328,6 +356,7 @@ func WithEncoderDict(dict []byte) EOption {
//
// The slice content may contain arbitrary data. It will be used as an initial
// history.
// Can be changed with ResetWithOptions.
func WithEncoderDictRaw(id uint32, content []byte) EOption {
return func(o *encoderOptions) error {
if bits.UintSize > 32 && uint(len(content)) > dictMaxLength {
@@ -337,3 +366,12 @@ func WithEncoderDictRaw(id uint32, content []byte) EOption {
return nil
}
}
// WithEncoderDictDelete clears the dictionary, so no dictionary will be used.
// Should be used with ResetWithOptions.
func WithEncoderDictDelete() EOption {
return func(o *encoderOptions) error {
o.dict = nil
return nil
}
}

View File

@@ -6,46 +6,47 @@ var combining = table{
{0x0300, 0x036F}, {0x0483, 0x0489}, {0x07EB, 0x07F3},
{0x0C00, 0x0C00}, {0x0C04, 0x0C04}, {0x0CF3, 0x0CF3},
{0x0D00, 0x0D01}, {0x135D, 0x135F}, {0x1A7F, 0x1A7F},
{0x1AB0, 0x1ACE}, {0x1B6B, 0x1B73}, {0x1DC0, 0x1DFF},
{0x20D0, 0x20F0}, {0x2CEF, 0x2CF1}, {0x2DE0, 0x2DFF},
{0x3099, 0x309A}, {0xA66F, 0xA672}, {0xA674, 0xA67D},
{0xA69E, 0xA69F}, {0xA6F0, 0xA6F1}, {0xA8E0, 0xA8F1},
{0xFE20, 0xFE2F}, {0x101FD, 0x101FD}, {0x10376, 0x1037A},
{0x10EAB, 0x10EAC}, {0x10F46, 0x10F50}, {0x10F82, 0x10F85},
{0x11300, 0x11301}, {0x1133B, 0x1133C}, {0x11366, 0x1136C},
{0x11370, 0x11374}, {0x16AF0, 0x16AF4}, {0x1CF00, 0x1CF2D},
{0x1CF30, 0x1CF46}, {0x1D165, 0x1D169}, {0x1D16D, 0x1D172},
{0x1D17B, 0x1D182}, {0x1D185, 0x1D18B}, {0x1D1AA, 0x1D1AD},
{0x1D242, 0x1D244}, {0x1E000, 0x1E006}, {0x1E008, 0x1E018},
{0x1E01B, 0x1E021}, {0x1E023, 0x1E024}, {0x1E026, 0x1E02A},
{0x1E08F, 0x1E08F}, {0x1E8D0, 0x1E8D6},
{0x1AB0, 0x1ADD}, {0x1AE0, 0x1AEB}, {0x1B6B, 0x1B73},
{0x1DC0, 0x1DFF}, {0x20D0, 0x20F0}, {0x2CEF, 0x2CF1},
{0x2DE0, 0x2DFF}, {0x3099, 0x309A}, {0xA66F, 0xA672},
{0xA674, 0xA67D}, {0xA69E, 0xA69F}, {0xA6F0, 0xA6F1},
{0xA8E0, 0xA8F1}, {0xFE20, 0xFE2F}, {0x101FD, 0x101FD},
{0x10376, 0x1037A}, {0x10EAB, 0x10EAC}, {0x10F46, 0x10F50},
{0x10F82, 0x10F85}, {0x11300, 0x11301}, {0x1133B, 0x1133C},
{0x11366, 0x1136C}, {0x11370, 0x11374}, {0x16AF0, 0x16AF4},
{0x1CF00, 0x1CF2D}, {0x1CF30, 0x1CF46}, {0x1D165, 0x1D169},
{0x1D16D, 0x1D172}, {0x1D17B, 0x1D182}, {0x1D185, 0x1D18B},
{0x1D1AA, 0x1D1AD}, {0x1D242, 0x1D244}, {0x1E000, 0x1E006},
{0x1E008, 0x1E018}, {0x1E01B, 0x1E021}, {0x1E023, 0x1E024},
{0x1E026, 0x1E02A}, {0x1E08F, 0x1E08F}, {0x1E8D0, 0x1E8D6},
}
var doublewidth = table{
{0x1100, 0x115F}, {0x231A, 0x231B}, {0x2329, 0x232A},
{0x23E9, 0x23EC}, {0x23F0, 0x23F0}, {0x23F3, 0x23F3},
{0x25FD, 0x25FE}, {0x2614, 0x2615}, {0x2648, 0x2653},
{0x267F, 0x267F}, {0x2693, 0x2693}, {0x26A1, 0x26A1},
{0x26AA, 0x26AB}, {0x26BD, 0x26BE}, {0x26C4, 0x26C5},
{0x26CE, 0x26CE}, {0x26D4, 0x26D4}, {0x26EA, 0x26EA},
{0x26F2, 0x26F3}, {0x26F5, 0x26F5}, {0x26FA, 0x26FA},
{0x26FD, 0x26FD}, {0x2705, 0x2705}, {0x270A, 0x270B},
{0x2728, 0x2728}, {0x274C, 0x274C}, {0x274E, 0x274E},
{0x2753, 0x2755}, {0x2757, 0x2757}, {0x2795, 0x2797},
{0x27B0, 0x27B0}, {0x27BF, 0x27BF}, {0x2B1B, 0x2B1C},
{0x2B50, 0x2B50}, {0x2B55, 0x2B55}, {0x2E80, 0x2E99},
{0x2E9B, 0x2EF3}, {0x2F00, 0x2FD5}, {0x2FF0, 0x303E},
{0x3041, 0x3096}, {0x3099, 0x30FF}, {0x3105, 0x312F},
{0x3131, 0x318E}, {0x3190, 0x31E3}, {0x31EF, 0x321E},
{0x3220, 0x3247}, {0x3250, 0x4DBF}, {0x4E00, 0xA48C},
{0xA490, 0xA4C6}, {0xA960, 0xA97C}, {0xAC00, 0xD7A3},
{0xF900, 0xFAFF}, {0xFE10, 0xFE19}, {0xFE30, 0xFE52},
{0xFE54, 0xFE66}, {0xFE68, 0xFE6B}, {0xFF01, 0xFF60},
{0xFFE0, 0xFFE6}, {0x16FE0, 0x16FE4}, {0x16FF0, 0x16FF1},
{0x17000, 0x187F7}, {0x18800, 0x18CD5}, {0x18D00, 0x18D08},
{0x1AFF0, 0x1AFF3}, {0x1AFF5, 0x1AFFB}, {0x1AFFD, 0x1AFFE},
{0x1B000, 0x1B122}, {0x1B132, 0x1B132}, {0x1B150, 0x1B152},
{0x1B155, 0x1B155}, {0x1B164, 0x1B167}, {0x1B170, 0x1B2FB},
{0x25FD, 0x25FE}, {0x2614, 0x2615}, {0x2630, 0x2637},
{0x2648, 0x2653}, {0x267F, 0x267F}, {0x268A, 0x268F},
{0x2693, 0x2693}, {0x26A1, 0x26A1}, {0x26AA, 0x26AB},
{0x26BD, 0x26BE}, {0x26C4, 0x26C5}, {0x26CE, 0x26CE},
{0x26D4, 0x26D4}, {0x26EA, 0x26EA}, {0x26F2, 0x26F3},
{0x26F5, 0x26F5}, {0x26FA, 0x26FA}, {0x26FD, 0x26FD},
{0x2705, 0x2705}, {0x270A, 0x270B}, {0x2728, 0x2728},
{0x274C, 0x274C}, {0x274E, 0x274E}, {0x2753, 0x2755},
{0x2757, 0x2757}, {0x2795, 0x2797}, {0x27B0, 0x27B0},
{0x27BF, 0x27BF}, {0x2B1B, 0x2B1C}, {0x2B50, 0x2B50},
{0x2B55, 0x2B55}, {0x2E80, 0x2E99}, {0x2E9B, 0x2EF3},
{0x2F00, 0x2FD5}, {0x2FF0, 0x303E}, {0x3041, 0x3096},
{0x3099, 0x30FF}, {0x3105, 0x312F}, {0x3131, 0x318E},
{0x3190, 0x31E5}, {0x31EF, 0x321E}, {0x3220, 0x3247},
{0x3250, 0xA48C}, {0xA490, 0xA4C6}, {0xA960, 0xA97C},
{0xAC00, 0xD7A3}, {0xF900, 0xFAFF}, {0xFE10, 0xFE19},
{0xFE30, 0xFE52}, {0xFE54, 0xFE66}, {0xFE68, 0xFE6B},
{0xFF01, 0xFF60}, {0xFFE0, 0xFFE6}, {0x16FE0, 0x16FE4},
{0x16FF0, 0x16FF6}, {0x17000, 0x18CD5}, {0x18CFF, 0x18D1E},
{0x18D80, 0x18DF2}, {0x1AFF0, 0x1AFF3}, {0x1AFF5, 0x1AFFB},
{0x1AFFD, 0x1AFFE}, {0x1B000, 0x1B122}, {0x1B132, 0x1B132},
{0x1B150, 0x1B152}, {0x1B155, 0x1B155}, {0x1B164, 0x1B167},
{0x1B170, 0x1B2FB}, {0x1D300, 0x1D356}, {0x1D360, 0x1D376},
{0x1F004, 0x1F004}, {0x1F0CF, 0x1F0CF}, {0x1F18E, 0x1F18E},
{0x1F191, 0x1F19A}, {0x1F200, 0x1F202}, {0x1F210, 0x1F23B},
{0x1F240, 0x1F248}, {0x1F250, 0x1F251}, {0x1F260, 0x1F265},
@@ -56,12 +57,12 @@ var doublewidth = table{
{0x1F54B, 0x1F54E}, {0x1F550, 0x1F567}, {0x1F57A, 0x1F57A},
{0x1F595, 0x1F596}, {0x1F5A4, 0x1F5A4}, {0x1F5FB, 0x1F64F},
{0x1F680, 0x1F6C5}, {0x1F6CC, 0x1F6CC}, {0x1F6D0, 0x1F6D2},
{0x1F6D5, 0x1F6D7}, {0x1F6DC, 0x1F6DF}, {0x1F6EB, 0x1F6EC},
{0x1F6D5, 0x1F6D8}, {0x1F6DC, 0x1F6DF}, {0x1F6EB, 0x1F6EC},
{0x1F6F4, 0x1F6FC}, {0x1F7E0, 0x1F7EB}, {0x1F7F0, 0x1F7F0},
{0x1F90C, 0x1F93A}, {0x1F93C, 0x1F945}, {0x1F947, 0x1F9FF},
{0x1FA70, 0x1FA7C}, {0x1FA80, 0x1FA88}, {0x1FA90, 0x1FABD},
{0x1FABF, 0x1FAC5}, {0x1FACE, 0x1FADB}, {0x1FAE0, 0x1FAE8},
{0x1FAF0, 0x1FAF8}, {0x20000, 0x2FFFD}, {0x30000, 0x3FFFD},
{0x1FA70, 0x1FA7C}, {0x1FA80, 0x1FA8A}, {0x1FA8E, 0x1FAC6},
{0x1FAC8, 0x1FAC8}, {0x1FACD, 0x1FADC}, {0x1FADF, 0x1FAEA},
{0x1FAEF, 0x1FAF8}, {0x20000, 0x2FFFD}, {0x30000, 0x3FFFD},
}
var ambiguous = table{
@@ -159,164 +160,162 @@ var neutral = table{
{0x0600, 0x070D}, {0x070F, 0x074A}, {0x074D, 0x07B1},
{0x07C0, 0x07FA}, {0x07FD, 0x082D}, {0x0830, 0x083E},
{0x0840, 0x085B}, {0x085E, 0x085E}, {0x0860, 0x086A},
{0x0870, 0x088E}, {0x0890, 0x0891}, {0x0898, 0x0983},
{0x0985, 0x098C}, {0x098F, 0x0990}, {0x0993, 0x09A8},
{0x09AA, 0x09B0}, {0x09B2, 0x09B2}, {0x09B6, 0x09B9},
{0x09BC, 0x09C4}, {0x09C7, 0x09C8}, {0x09CB, 0x09CE},
{0x09D7, 0x09D7}, {0x09DC, 0x09DD}, {0x09DF, 0x09E3},
{0x09E6, 0x09FE}, {0x0A01, 0x0A03}, {0x0A05, 0x0A0A},
{0x0A0F, 0x0A10}, {0x0A13, 0x0A28}, {0x0A2A, 0x0A30},
{0x0A32, 0x0A33}, {0x0A35, 0x0A36}, {0x0A38, 0x0A39},
{0x0A3C, 0x0A3C}, {0x0A3E, 0x0A42}, {0x0A47, 0x0A48},
{0x0A4B, 0x0A4D}, {0x0A51, 0x0A51}, {0x0A59, 0x0A5C},
{0x0A5E, 0x0A5E}, {0x0A66, 0x0A76}, {0x0A81, 0x0A83},
{0x0A85, 0x0A8D}, {0x0A8F, 0x0A91}, {0x0A93, 0x0AA8},
{0x0AAA, 0x0AB0}, {0x0AB2, 0x0AB3}, {0x0AB5, 0x0AB9},
{0x0ABC, 0x0AC5}, {0x0AC7, 0x0AC9}, {0x0ACB, 0x0ACD},
{0x0AD0, 0x0AD0}, {0x0AE0, 0x0AE3}, {0x0AE6, 0x0AF1},
{0x0AF9, 0x0AFF}, {0x0B01, 0x0B03}, {0x0B05, 0x0B0C},
{0x0B0F, 0x0B10}, {0x0B13, 0x0B28}, {0x0B2A, 0x0B30},
{0x0B32, 0x0B33}, {0x0B35, 0x0B39}, {0x0B3C, 0x0B44},
{0x0B47, 0x0B48}, {0x0B4B, 0x0B4D}, {0x0B55, 0x0B57},
{0x0B5C, 0x0B5D}, {0x0B5F, 0x0B63}, {0x0B66, 0x0B77},
{0x0B82, 0x0B83}, {0x0B85, 0x0B8A}, {0x0B8E, 0x0B90},
{0x0B92, 0x0B95}, {0x0B99, 0x0B9A}, {0x0B9C, 0x0B9C},
{0x0B9E, 0x0B9F}, {0x0BA3, 0x0BA4}, {0x0BA8, 0x0BAA},
{0x0BAE, 0x0BB9}, {0x0BBE, 0x0BC2}, {0x0BC6, 0x0BC8},
{0x0BCA, 0x0BCD}, {0x0BD0, 0x0BD0}, {0x0BD7, 0x0BD7},
{0x0BE6, 0x0BFA}, {0x0C00, 0x0C0C}, {0x0C0E, 0x0C10},
{0x0C12, 0x0C28}, {0x0C2A, 0x0C39}, {0x0C3C, 0x0C44},
{0x0C46, 0x0C48}, {0x0C4A, 0x0C4D}, {0x0C55, 0x0C56},
{0x0C58, 0x0C5A}, {0x0C5D, 0x0C5D}, {0x0C60, 0x0C63},
{0x0C66, 0x0C6F}, {0x0C77, 0x0C8C}, {0x0C8E, 0x0C90},
{0x0C92, 0x0CA8}, {0x0CAA, 0x0CB3}, {0x0CB5, 0x0CB9},
{0x0CBC, 0x0CC4}, {0x0CC6, 0x0CC8}, {0x0CCA, 0x0CCD},
{0x0CD5, 0x0CD6}, {0x0CDD, 0x0CDE}, {0x0CE0, 0x0CE3},
{0x0CE6, 0x0CEF}, {0x0CF1, 0x0CF3}, {0x0D00, 0x0D0C},
{0x0D0E, 0x0D10}, {0x0D12, 0x0D44}, {0x0D46, 0x0D48},
{0x0D4A, 0x0D4F}, {0x0D54, 0x0D63}, {0x0D66, 0x0D7F},
{0x0D81, 0x0D83}, {0x0D85, 0x0D96}, {0x0D9A, 0x0DB1},
{0x0DB3, 0x0DBB}, {0x0DBD, 0x0DBD}, {0x0DC0, 0x0DC6},
{0x0DCA, 0x0DCA}, {0x0DCF, 0x0DD4}, {0x0DD6, 0x0DD6},
{0x0DD8, 0x0DDF}, {0x0DE6, 0x0DEF}, {0x0DF2, 0x0DF4},
{0x0E01, 0x0E3A}, {0x0E3F, 0x0E5B}, {0x0E81, 0x0E82},
{0x0E84, 0x0E84}, {0x0E86, 0x0E8A}, {0x0E8C, 0x0EA3},
{0x0EA5, 0x0EA5}, {0x0EA7, 0x0EBD}, {0x0EC0, 0x0EC4},
{0x0EC6, 0x0EC6}, {0x0EC8, 0x0ECE}, {0x0ED0, 0x0ED9},
{0x0EDC, 0x0EDF}, {0x0F00, 0x0F47}, {0x0F49, 0x0F6C},
{0x0F71, 0x0F97}, {0x0F99, 0x0FBC}, {0x0FBE, 0x0FCC},
{0x0FCE, 0x0FDA}, {0x1000, 0x10C5}, {0x10C7, 0x10C7},
{0x10CD, 0x10CD}, {0x10D0, 0x10FF}, {0x1160, 0x1248},
{0x124A, 0x124D}, {0x1250, 0x1256}, {0x1258, 0x1258},
{0x125A, 0x125D}, {0x1260, 0x1288}, {0x128A, 0x128D},
{0x1290, 0x12B0}, {0x12B2, 0x12B5}, {0x12B8, 0x12BE},
{0x12C0, 0x12C0}, {0x12C2, 0x12C5}, {0x12C8, 0x12D6},
{0x12D8, 0x1310}, {0x1312, 0x1315}, {0x1318, 0x135A},
{0x135D, 0x137C}, {0x1380, 0x1399}, {0x13A0, 0x13F5},
{0x13F8, 0x13FD}, {0x1400, 0x169C}, {0x16A0, 0x16F8},
{0x1700, 0x1715}, {0x171F, 0x1736}, {0x1740, 0x1753},
{0x1760, 0x176C}, {0x176E, 0x1770}, {0x1772, 0x1773},
{0x1780, 0x17DD}, {0x17E0, 0x17E9}, {0x17F0, 0x17F9},
{0x1800, 0x1819}, {0x1820, 0x1878}, {0x1880, 0x18AA},
{0x18B0, 0x18F5}, {0x1900, 0x191E}, {0x1920, 0x192B},
{0x1930, 0x193B}, {0x1940, 0x1940}, {0x1944, 0x196D},
{0x1970, 0x1974}, {0x1980, 0x19AB}, {0x19B0, 0x19C9},
{0x19D0, 0x19DA}, {0x19DE, 0x1A1B}, {0x1A1E, 0x1A5E},
{0x1A60, 0x1A7C}, {0x1A7F, 0x1A89}, {0x1A90, 0x1A99},
{0x1AA0, 0x1AAD}, {0x1AB0, 0x1ACE}, {0x1B00, 0x1B4C},
{0x1B50, 0x1B7E}, {0x1B80, 0x1BF3}, {0x1BFC, 0x1C37},
{0x1C3B, 0x1C49}, {0x1C4D, 0x1C88}, {0x1C90, 0x1CBA},
{0x1CBD, 0x1CC7}, {0x1CD0, 0x1CFA}, {0x1D00, 0x1F15},
{0x1F18, 0x1F1D}, {0x1F20, 0x1F45}, {0x1F48, 0x1F4D},
{0x1F50, 0x1F57}, {0x1F59, 0x1F59}, {0x1F5B, 0x1F5B},
{0x1F5D, 0x1F5D}, {0x1F5F, 0x1F7D}, {0x1F80, 0x1FB4},
{0x1FB6, 0x1FC4}, {0x1FC6, 0x1FD3}, {0x1FD6, 0x1FDB},
{0x1FDD, 0x1FEF}, {0x1FF2, 0x1FF4}, {0x1FF6, 0x1FFE},
{0x2000, 0x200F}, {0x2011, 0x2012}, {0x2017, 0x2017},
{0x201A, 0x201B}, {0x201E, 0x201F}, {0x2023, 0x2023},
{0x2028, 0x202F}, {0x2031, 0x2031}, {0x2034, 0x2034},
{0x2036, 0x203A}, {0x203C, 0x203D}, {0x203F, 0x2064},
{0x2066, 0x2071}, {0x2075, 0x207E}, {0x2080, 0x2080},
{0x2085, 0x208E}, {0x2090, 0x209C}, {0x20A0, 0x20A8},
{0x20AA, 0x20AB}, {0x20AD, 0x20C0}, {0x20D0, 0x20F0},
{0x2100, 0x2102}, {0x2104, 0x2104}, {0x2106, 0x2108},
{0x210A, 0x2112}, {0x2114, 0x2115}, {0x2117, 0x2120},
{0x2123, 0x2125}, {0x2127, 0x212A}, {0x212C, 0x2152},
{0x2155, 0x215A}, {0x215F, 0x215F}, {0x216C, 0x216F},
{0x217A, 0x2188}, {0x218A, 0x218B}, {0x219A, 0x21B7},
{0x21BA, 0x21D1}, {0x21D3, 0x21D3}, {0x21D5, 0x21E6},
{0x21E8, 0x21FF}, {0x2201, 0x2201}, {0x2204, 0x2206},
{0x2209, 0x220A}, {0x220C, 0x220E}, {0x2210, 0x2210},
{0x2212, 0x2214}, {0x2216, 0x2219}, {0x221B, 0x221C},
{0x2221, 0x2222}, {0x2224, 0x2224}, {0x2226, 0x2226},
{0x222D, 0x222D}, {0x222F, 0x2233}, {0x2238, 0x223B},
{0x223E, 0x2247}, {0x2249, 0x224B}, {0x224D, 0x2251},
{0x2253, 0x225F}, {0x2262, 0x2263}, {0x2268, 0x2269},
{0x226C, 0x226D}, {0x2270, 0x2281}, {0x2284, 0x2285},
{0x2288, 0x2294}, {0x2296, 0x2298}, {0x229A, 0x22A4},
{0x22A6, 0x22BE}, {0x22C0, 0x2311}, {0x2313, 0x2319},
{0x231C, 0x2328}, {0x232B, 0x23E8}, {0x23ED, 0x23EF},
{0x23F1, 0x23F2}, {0x23F4, 0x2426}, {0x2440, 0x244A},
{0x24EA, 0x24EA}, {0x254C, 0x254F}, {0x2574, 0x257F},
{0x2590, 0x2591}, {0x2596, 0x259F}, {0x25A2, 0x25A2},
{0x25AA, 0x25B1}, {0x25B4, 0x25B5}, {0x25B8, 0x25BB},
{0x25BE, 0x25BF}, {0x25C2, 0x25C5}, {0x25C9, 0x25CA},
{0x25CC, 0x25CD}, {0x25D2, 0x25E1}, {0x25E6, 0x25EE},
{0x25F0, 0x25FC}, {0x25FF, 0x2604}, {0x2607, 0x2608},
{0x260A, 0x260D}, {0x2610, 0x2613}, {0x2616, 0x261B},
{0x261D, 0x261D}, {0x261F, 0x263F}, {0x2641, 0x2641},
{0x0870, 0x0891}, {0x0897, 0x0983}, {0x0985, 0x098C},
{0x098F, 0x0990}, {0x0993, 0x09A8}, {0x09AA, 0x09B0},
{0x09B2, 0x09B2}, {0x09B6, 0x09B9}, {0x09BC, 0x09C4},
{0x09C7, 0x09C8}, {0x09CB, 0x09CE}, {0x09D7, 0x09D7},
{0x09DC, 0x09DD}, {0x09DF, 0x09E3}, {0x09E6, 0x09FE},
{0x0A01, 0x0A03}, {0x0A05, 0x0A0A}, {0x0A0F, 0x0A10},
{0x0A13, 0x0A28}, {0x0A2A, 0x0A30}, {0x0A32, 0x0A33},
{0x0A35, 0x0A36}, {0x0A38, 0x0A39}, {0x0A3C, 0x0A3C},
{0x0A3E, 0x0A42}, {0x0A47, 0x0A48}, {0x0A4B, 0x0A4D},
{0x0A51, 0x0A51}, {0x0A59, 0x0A5C}, {0x0A5E, 0x0A5E},
{0x0A66, 0x0A76}, {0x0A81, 0x0A83}, {0x0A85, 0x0A8D},
{0x0A8F, 0x0A91}, {0x0A93, 0x0AA8}, {0x0AAA, 0x0AB0},
{0x0AB2, 0x0AB3}, {0x0AB5, 0x0AB9}, {0x0ABC, 0x0AC5},
{0x0AC7, 0x0AC9}, {0x0ACB, 0x0ACD}, {0x0AD0, 0x0AD0},
{0x0AE0, 0x0AE3}, {0x0AE6, 0x0AF1}, {0x0AF9, 0x0AFF},
{0x0B01, 0x0B03}, {0x0B05, 0x0B0C}, {0x0B0F, 0x0B10},
{0x0B13, 0x0B28}, {0x0B2A, 0x0B30}, {0x0B32, 0x0B33},
{0x0B35, 0x0B39}, {0x0B3C, 0x0B44}, {0x0B47, 0x0B48},
{0x0B4B, 0x0B4D}, {0x0B55, 0x0B57}, {0x0B5C, 0x0B5D},
{0x0B5F, 0x0B63}, {0x0B66, 0x0B77}, {0x0B82, 0x0B83},
{0x0B85, 0x0B8A}, {0x0B8E, 0x0B90}, {0x0B92, 0x0B95},
{0x0B99, 0x0B9A}, {0x0B9C, 0x0B9C}, {0x0B9E, 0x0B9F},
{0x0BA3, 0x0BA4}, {0x0BA8, 0x0BAA}, {0x0BAE, 0x0BB9},
{0x0BBE, 0x0BC2}, {0x0BC6, 0x0BC8}, {0x0BCA, 0x0BCD},
{0x0BD0, 0x0BD0}, {0x0BD7, 0x0BD7}, {0x0BE6, 0x0BFA},
{0x0C00, 0x0C0C}, {0x0C0E, 0x0C10}, {0x0C12, 0x0C28},
{0x0C2A, 0x0C39}, {0x0C3C, 0x0C44}, {0x0C46, 0x0C48},
{0x0C4A, 0x0C4D}, {0x0C55, 0x0C56}, {0x0C58, 0x0C5A},
{0x0C5C, 0x0C5D}, {0x0C60, 0x0C63}, {0x0C66, 0x0C6F},
{0x0C77, 0x0C8C}, {0x0C8E, 0x0C90}, {0x0C92, 0x0CA8},
{0x0CAA, 0x0CB3}, {0x0CB5, 0x0CB9}, {0x0CBC, 0x0CC4},
{0x0CC6, 0x0CC8}, {0x0CCA, 0x0CCD}, {0x0CD5, 0x0CD6},
{0x0CDC, 0x0CDE}, {0x0CE0, 0x0CE3}, {0x0CE6, 0x0CEF},
{0x0CF1, 0x0CF3}, {0x0D00, 0x0D0C}, {0x0D0E, 0x0D10},
{0x0D12, 0x0D44}, {0x0D46, 0x0D48}, {0x0D4A, 0x0D4F},
{0x0D54, 0x0D63}, {0x0D66, 0x0D7F}, {0x0D81, 0x0D83},
{0x0D85, 0x0D96}, {0x0D9A, 0x0DB1}, {0x0DB3, 0x0DBB},
{0x0DBD, 0x0DBD}, {0x0DC0, 0x0DC6}, {0x0DCA, 0x0DCA},
{0x0DCF, 0x0DD4}, {0x0DD6, 0x0DD6}, {0x0DD8, 0x0DDF},
{0x0DE6, 0x0DEF}, {0x0DF2, 0x0DF4}, {0x0E01, 0x0E3A},
{0x0E3F, 0x0E5B}, {0x0E81, 0x0E82}, {0x0E84, 0x0E84},
{0x0E86, 0x0E8A}, {0x0E8C, 0x0EA3}, {0x0EA5, 0x0EA5},
{0x0EA7, 0x0EBD}, {0x0EC0, 0x0EC4}, {0x0EC6, 0x0EC6},
{0x0EC8, 0x0ECE}, {0x0ED0, 0x0ED9}, {0x0EDC, 0x0EDF},
{0x0F00, 0x0F47}, {0x0F49, 0x0F6C}, {0x0F71, 0x0F97},
{0x0F99, 0x0FBC}, {0x0FBE, 0x0FCC}, {0x0FCE, 0x0FDA},
{0x1000, 0x10C5}, {0x10C7, 0x10C7}, {0x10CD, 0x10CD},
{0x10D0, 0x10FF}, {0x1160, 0x1248}, {0x124A, 0x124D},
{0x1250, 0x1256}, {0x1258, 0x1258}, {0x125A, 0x125D},
{0x1260, 0x1288}, {0x128A, 0x128D}, {0x1290, 0x12B0},
{0x12B2, 0x12B5}, {0x12B8, 0x12BE}, {0x12C0, 0x12C0},
{0x12C2, 0x12C5}, {0x12C8, 0x12D6}, {0x12D8, 0x1310},
{0x1312, 0x1315}, {0x1318, 0x135A}, {0x135D, 0x137C},
{0x1380, 0x1399}, {0x13A0, 0x13F5}, {0x13F8, 0x13FD},
{0x1400, 0x169C}, {0x16A0, 0x16F8}, {0x1700, 0x1715},
{0x171F, 0x1736}, {0x1740, 0x1753}, {0x1760, 0x176C},
{0x176E, 0x1770}, {0x1772, 0x1773}, {0x1780, 0x17DD},
{0x17E0, 0x17E9}, {0x17F0, 0x17F9}, {0x1800, 0x1819},
{0x1820, 0x1878}, {0x1880, 0x18AA}, {0x18B0, 0x18F5},
{0x1900, 0x191E}, {0x1920, 0x192B}, {0x1930, 0x193B},
{0x1940, 0x1940}, {0x1944, 0x196D}, {0x1970, 0x1974},
{0x1980, 0x19AB}, {0x19B0, 0x19C9}, {0x19D0, 0x19DA},
{0x19DE, 0x1A1B}, {0x1A1E, 0x1A5E}, {0x1A60, 0x1A7C},
{0x1A7F, 0x1A89}, {0x1A90, 0x1A99}, {0x1AA0, 0x1AAD},
{0x1AB0, 0x1ADD}, {0x1AE0, 0x1AEB}, {0x1B00, 0x1B4C},
{0x1B4E, 0x1BF3}, {0x1BFC, 0x1C37}, {0x1C3B, 0x1C49},
{0x1C4D, 0x1C8A}, {0x1C90, 0x1CBA}, {0x1CBD, 0x1CC7},
{0x1CD0, 0x1CFA}, {0x1D00, 0x1F15}, {0x1F18, 0x1F1D},
{0x1F20, 0x1F45}, {0x1F48, 0x1F4D}, {0x1F50, 0x1F57},
{0x1F59, 0x1F59}, {0x1F5B, 0x1F5B}, {0x1F5D, 0x1F5D},
{0x1F5F, 0x1F7D}, {0x1F80, 0x1FB4}, {0x1FB6, 0x1FC4},
{0x1FC6, 0x1FD3}, {0x1FD6, 0x1FDB}, {0x1FDD, 0x1FEF},
{0x1FF2, 0x1FF4}, {0x1FF6, 0x1FFE}, {0x2000, 0x200F},
{0x2011, 0x2012}, {0x2017, 0x2017}, {0x201A, 0x201B},
{0x201E, 0x201F}, {0x2023, 0x2023}, {0x2028, 0x202F},
{0x2031, 0x2031}, {0x2034, 0x2034}, {0x2036, 0x203A},
{0x203C, 0x203D}, {0x203F, 0x2064}, {0x2066, 0x2071},
{0x2075, 0x207E}, {0x2080, 0x2080}, {0x2085, 0x208E},
{0x2090, 0x209C}, {0x20A0, 0x20A8}, {0x20AA, 0x20AB},
{0x20AD, 0x20C1}, {0x20D0, 0x20F0}, {0x2100, 0x2102},
{0x2104, 0x2104}, {0x2106, 0x2108}, {0x210A, 0x2112},
{0x2114, 0x2115}, {0x2117, 0x2120}, {0x2123, 0x2125},
{0x2127, 0x212A}, {0x212C, 0x2152}, {0x2155, 0x215A},
{0x215F, 0x215F}, {0x216C, 0x216F}, {0x217A, 0x2188},
{0x218A, 0x218B}, {0x219A, 0x21B7}, {0x21BA, 0x21D1},
{0x21D3, 0x21D3}, {0x21D5, 0x21E6}, {0x21E8, 0x21FF},
{0x2201, 0x2201}, {0x2204, 0x2206}, {0x2209, 0x220A},
{0x220C, 0x220E}, {0x2210, 0x2210}, {0x2212, 0x2214},
{0x2216, 0x2219}, {0x221B, 0x221C}, {0x2221, 0x2222},
{0x2224, 0x2224}, {0x2226, 0x2226}, {0x222D, 0x222D},
{0x222F, 0x2233}, {0x2238, 0x223B}, {0x223E, 0x2247},
{0x2249, 0x224B}, {0x224D, 0x2251}, {0x2253, 0x225F},
{0x2262, 0x2263}, {0x2268, 0x2269}, {0x226C, 0x226D},
{0x2270, 0x2281}, {0x2284, 0x2285}, {0x2288, 0x2294},
{0x2296, 0x2298}, {0x229A, 0x22A4}, {0x22A6, 0x22BE},
{0x22C0, 0x2311}, {0x2313, 0x2319}, {0x231C, 0x2328},
{0x232B, 0x23E8}, {0x23ED, 0x23EF}, {0x23F1, 0x23F2},
{0x23F4, 0x2429}, {0x2440, 0x244A}, {0x24EA, 0x24EA},
{0x254C, 0x254F}, {0x2574, 0x257F}, {0x2590, 0x2591},
{0x2596, 0x259F}, {0x25A2, 0x25A2}, {0x25AA, 0x25B1},
{0x25B4, 0x25B5}, {0x25B8, 0x25BB}, {0x25BE, 0x25BF},
{0x25C2, 0x25C5}, {0x25C9, 0x25CA}, {0x25CC, 0x25CD},
{0x25D2, 0x25E1}, {0x25E6, 0x25EE}, {0x25F0, 0x25FC},
{0x25FF, 0x2604}, {0x2607, 0x2608}, {0x260A, 0x260D},
{0x2610, 0x2613}, {0x2616, 0x261B}, {0x261D, 0x261D},
{0x261F, 0x262F}, {0x2638, 0x263F}, {0x2641, 0x2641},
{0x2643, 0x2647}, {0x2654, 0x265F}, {0x2662, 0x2662},
{0x2666, 0x2666}, {0x266B, 0x266B}, {0x266E, 0x266E},
{0x2670, 0x267E}, {0x2680, 0x2692}, {0x2694, 0x269D},
{0x26A0, 0x26A0}, {0x26A2, 0x26A9}, {0x26AC, 0x26BC},
{0x26C0, 0x26C3}, {0x26E2, 0x26E2}, {0x26E4, 0x26E7},
{0x2700, 0x2704}, {0x2706, 0x2709}, {0x270C, 0x2727},
{0x2729, 0x273C}, {0x273E, 0x274B}, {0x274D, 0x274D},
{0x274F, 0x2752}, {0x2756, 0x2756}, {0x2758, 0x2775},
{0x2780, 0x2794}, {0x2798, 0x27AF}, {0x27B1, 0x27BE},
{0x27C0, 0x27E5}, {0x27EE, 0x2984}, {0x2987, 0x2B1A},
{0x2B1D, 0x2B4F}, {0x2B51, 0x2B54}, {0x2B5A, 0x2B73},
{0x2B76, 0x2B95}, {0x2B97, 0x2CF3}, {0x2CF9, 0x2D25},
{0x2670, 0x267E}, {0x2680, 0x2689}, {0x2690, 0x2692},
{0x2694, 0x269D}, {0x26A0, 0x26A0}, {0x26A2, 0x26A9},
{0x26AC, 0x26BC}, {0x26C0, 0x26C3}, {0x26E2, 0x26E2},
{0x26E4, 0x26E7}, {0x2700, 0x2704}, {0x2706, 0x2709},
{0x270C, 0x2727}, {0x2729, 0x273C}, {0x273E, 0x274B},
{0x274D, 0x274D}, {0x274F, 0x2752}, {0x2756, 0x2756},
{0x2758, 0x2775}, {0x2780, 0x2794}, {0x2798, 0x27AF},
{0x27B1, 0x27BE}, {0x27C0, 0x27E5}, {0x27EE, 0x2984},
{0x2987, 0x2B1A}, {0x2B1D, 0x2B4F}, {0x2B51, 0x2B54},
{0x2B5A, 0x2B73}, {0x2B76, 0x2CF3}, {0x2CF9, 0x2D25},
{0x2D27, 0x2D27}, {0x2D2D, 0x2D2D}, {0x2D30, 0x2D67},
{0x2D6F, 0x2D70}, {0x2D7F, 0x2D96}, {0x2DA0, 0x2DA6},
{0x2DA8, 0x2DAE}, {0x2DB0, 0x2DB6}, {0x2DB8, 0x2DBE},
{0x2DC0, 0x2DC6}, {0x2DC8, 0x2DCE}, {0x2DD0, 0x2DD6},
{0x2DD8, 0x2DDE}, {0x2DE0, 0x2E5D}, {0x303F, 0x303F},
{0x4DC0, 0x4DFF}, {0xA4D0, 0xA62B}, {0xA640, 0xA6F7},
{0xA700, 0xA7CA}, {0xA7D0, 0xA7D1}, {0xA7D3, 0xA7D3},
{0xA7D5, 0xA7D9}, {0xA7F2, 0xA82C}, {0xA830, 0xA839},
{0xA840, 0xA877}, {0xA880, 0xA8C5}, {0xA8CE, 0xA8D9},
{0xA8E0, 0xA953}, {0xA95F, 0xA95F}, {0xA980, 0xA9CD},
{0xA9CF, 0xA9D9}, {0xA9DE, 0xA9FE}, {0xAA00, 0xAA36},
{0xAA40, 0xAA4D}, {0xAA50, 0xAA59}, {0xAA5C, 0xAAC2},
{0xAADB, 0xAAF6}, {0xAB01, 0xAB06}, {0xAB09, 0xAB0E},
{0xAB11, 0xAB16}, {0xAB20, 0xAB26}, {0xAB28, 0xAB2E},
{0xAB30, 0xAB6B}, {0xAB70, 0xABED}, {0xABF0, 0xABF9},
{0xD7B0, 0xD7C6}, {0xD7CB, 0xD7FB}, {0xD800, 0xDFFF},
{0xFB00, 0xFB06}, {0xFB13, 0xFB17}, {0xFB1D, 0xFB36},
{0xFB38, 0xFB3C}, {0xFB3E, 0xFB3E}, {0xFB40, 0xFB41},
{0xFB43, 0xFB44}, {0xFB46, 0xFBC2}, {0xFBD3, 0xFD8F},
{0xFD92, 0xFDC7}, {0xFDCF, 0xFDCF}, {0xFDF0, 0xFDFF},
{0xFE20, 0xFE2F}, {0xFE70, 0xFE74}, {0xFE76, 0xFEFC},
{0xFEFF, 0xFEFF}, {0xFFF9, 0xFFFC}, {0x10000, 0x1000B},
{0x1000D, 0x10026}, {0x10028, 0x1003A}, {0x1003C, 0x1003D},
{0x1003F, 0x1004D}, {0x10050, 0x1005D}, {0x10080, 0x100FA},
{0x10100, 0x10102}, {0x10107, 0x10133}, {0x10137, 0x1018E},
{0x10190, 0x1019C}, {0x101A0, 0x101A0}, {0x101D0, 0x101FD},
{0x10280, 0x1029C}, {0x102A0, 0x102D0}, {0x102E0, 0x102FB},
{0x10300, 0x10323}, {0x1032D, 0x1034A}, {0x10350, 0x1037A},
{0x10380, 0x1039D}, {0x1039F, 0x103C3}, {0x103C8, 0x103D5},
{0x10400, 0x1049D}, {0x104A0, 0x104A9}, {0x104B0, 0x104D3},
{0x104D8, 0x104FB}, {0x10500, 0x10527}, {0x10530, 0x10563},
{0x1056F, 0x1057A}, {0x1057C, 0x1058A}, {0x1058C, 0x10592},
{0x10594, 0x10595}, {0x10597, 0x105A1}, {0x105A3, 0x105B1},
{0x105B3, 0x105B9}, {0x105BB, 0x105BC}, {0x10600, 0x10736},
{0xA4D0, 0xA62B}, {0xA640, 0xA6F7}, {0xA700, 0xA7DC},
{0xA7F1, 0xA82C}, {0xA830, 0xA839}, {0xA840, 0xA877},
{0xA880, 0xA8C5}, {0xA8CE, 0xA8D9}, {0xA8E0, 0xA953},
{0xA95F, 0xA95F}, {0xA980, 0xA9CD}, {0xA9CF, 0xA9D9},
{0xA9DE, 0xA9FE}, {0xAA00, 0xAA36}, {0xAA40, 0xAA4D},
{0xAA50, 0xAA59}, {0xAA5C, 0xAAC2}, {0xAADB, 0xAAF6},
{0xAB01, 0xAB06}, {0xAB09, 0xAB0E}, {0xAB11, 0xAB16},
{0xAB20, 0xAB26}, {0xAB28, 0xAB2E}, {0xAB30, 0xAB6B},
{0xAB70, 0xABED}, {0xABF0, 0xABF9}, {0xD7B0, 0xD7C6},
{0xD7CB, 0xD7FB}, {0xD800, 0xDFFF}, {0xFB00, 0xFB06},
{0xFB13, 0xFB17}, {0xFB1D, 0xFB36}, {0xFB38, 0xFB3C},
{0xFB3E, 0xFB3E}, {0xFB40, 0xFB41}, {0xFB43, 0xFB44},
{0xFB46, 0xFDCF}, {0xFDF0, 0xFDFF}, {0xFE20, 0xFE2F},
{0xFE70, 0xFE74}, {0xFE76, 0xFEFC}, {0xFEFF, 0xFEFF},
{0xFFF9, 0xFFFC}, {0x10000, 0x1000B}, {0x1000D, 0x10026},
{0x10028, 0x1003A}, {0x1003C, 0x1003D}, {0x1003F, 0x1004D},
{0x10050, 0x1005D}, {0x10080, 0x100FA}, {0x10100, 0x10102},
{0x10107, 0x10133}, {0x10137, 0x1018E}, {0x10190, 0x1019C},
{0x101A0, 0x101A0}, {0x101D0, 0x101FD}, {0x10280, 0x1029C},
{0x102A0, 0x102D0}, {0x102E0, 0x102FB}, {0x10300, 0x10323},
{0x1032D, 0x1034A}, {0x10350, 0x1037A}, {0x10380, 0x1039D},
{0x1039F, 0x103C3}, {0x103C8, 0x103D5}, {0x10400, 0x1049D},
{0x104A0, 0x104A9}, {0x104B0, 0x104D3}, {0x104D8, 0x104FB},
{0x10500, 0x10527}, {0x10530, 0x10563}, {0x1056F, 0x1057A},
{0x1057C, 0x1058A}, {0x1058C, 0x10592}, {0x10594, 0x10595},
{0x10597, 0x105A1}, {0x105A3, 0x105B1}, {0x105B3, 0x105B9},
{0x105BB, 0x105BC}, {0x105C0, 0x105F3}, {0x10600, 0x10736},
{0x10740, 0x10755}, {0x10760, 0x10767}, {0x10780, 0x10785},
{0x10787, 0x107B0}, {0x107B2, 0x107BA}, {0x10800, 0x10805},
{0x10808, 0x10808}, {0x1080A, 0x10835}, {0x10837, 0x10838},
{0x1083C, 0x1083C}, {0x1083F, 0x10855}, {0x10857, 0x1089E},
{0x108A7, 0x108AF}, {0x108E0, 0x108F2}, {0x108F4, 0x108F5},
{0x108FB, 0x1091B}, {0x1091F, 0x10939}, {0x1093F, 0x1093F},
{0x108FB, 0x1091B}, {0x1091F, 0x10939}, {0x1093F, 0x10959},
{0x10980, 0x109B7}, {0x109BC, 0x109CF}, {0x109D2, 0x10A03},
{0x10A05, 0x10A06}, {0x10A0C, 0x10A13}, {0x10A15, 0x10A17},
{0x10A19, 0x10A35}, {0x10A38, 0x10A3A}, {0x10A3F, 0x10A48},
@@ -325,53 +324,63 @@ var neutral = table{
{0x10B58, 0x10B72}, {0x10B78, 0x10B91}, {0x10B99, 0x10B9C},
{0x10BA9, 0x10BAF}, {0x10C00, 0x10C48}, {0x10C80, 0x10CB2},
{0x10CC0, 0x10CF2}, {0x10CFA, 0x10D27}, {0x10D30, 0x10D39},
{0x10D40, 0x10D65}, {0x10D69, 0x10D85}, {0x10D8E, 0x10D8F},
{0x10E60, 0x10E7E}, {0x10E80, 0x10EA9}, {0x10EAB, 0x10EAD},
{0x10EB0, 0x10EB1}, {0x10EFD, 0x10F27}, {0x10F30, 0x10F59},
{0x10F70, 0x10F89}, {0x10FB0, 0x10FCB}, {0x10FE0, 0x10FF6},
{0x11000, 0x1104D}, {0x11052, 0x11075}, {0x1107F, 0x110C2},
{0x110CD, 0x110CD}, {0x110D0, 0x110E8}, {0x110F0, 0x110F9},
{0x11100, 0x11134}, {0x11136, 0x11147}, {0x11150, 0x11176},
{0x11180, 0x111DF}, {0x111E1, 0x111F4}, {0x11200, 0x11211},
{0x11213, 0x11241}, {0x11280, 0x11286}, {0x11288, 0x11288},
{0x1128A, 0x1128D}, {0x1128F, 0x1129D}, {0x1129F, 0x112A9},
{0x112B0, 0x112EA}, {0x112F0, 0x112F9}, {0x11300, 0x11303},
{0x11305, 0x1130C}, {0x1130F, 0x11310}, {0x11313, 0x11328},
{0x1132A, 0x11330}, {0x11332, 0x11333}, {0x11335, 0x11339},
{0x1133B, 0x11344}, {0x11347, 0x11348}, {0x1134B, 0x1134D},
{0x11350, 0x11350}, {0x11357, 0x11357}, {0x1135D, 0x11363},
{0x11366, 0x1136C}, {0x11370, 0x11374}, {0x11400, 0x1145B},
{0x1145D, 0x11461}, {0x11480, 0x114C7}, {0x114D0, 0x114D9},
{0x11580, 0x115B5}, {0x115B8, 0x115DD}, {0x11600, 0x11644},
{0x11650, 0x11659}, {0x11660, 0x1166C}, {0x11680, 0x116B9},
{0x116C0, 0x116C9}, {0x11700, 0x1171A}, {0x1171D, 0x1172B},
{0x11730, 0x11746}, {0x11800, 0x1183B}, {0x118A0, 0x118F2},
{0x118FF, 0x11906}, {0x11909, 0x11909}, {0x1190C, 0x11913},
{0x11915, 0x11916}, {0x11918, 0x11935}, {0x11937, 0x11938},
{0x1193B, 0x11946}, {0x11950, 0x11959}, {0x119A0, 0x119A7},
{0x119AA, 0x119D7}, {0x119DA, 0x119E4}, {0x11A00, 0x11A47},
{0x11A50, 0x11AA2}, {0x11AB0, 0x11AF8}, {0x11B00, 0x11B09},
{0x11C00, 0x11C08}, {0x11C0A, 0x11C36}, {0x11C38, 0x11C45},
{0x11C50, 0x11C6C}, {0x11C70, 0x11C8F}, {0x11C92, 0x11CA7},
{0x11CA9, 0x11CB6}, {0x11D00, 0x11D06}, {0x11D08, 0x11D09},
{0x11D0B, 0x11D36}, {0x11D3A, 0x11D3A}, {0x11D3C, 0x11D3D},
{0x11D3F, 0x11D47}, {0x11D50, 0x11D59}, {0x11D60, 0x11D65},
{0x11D67, 0x11D68}, {0x11D6A, 0x11D8E}, {0x11D90, 0x11D91},
{0x11D93, 0x11D98}, {0x11DA0, 0x11DA9}, {0x11EE0, 0x11EF8},
{0x11F00, 0x11F10}, {0x11F12, 0x11F3A}, {0x11F3E, 0x11F59},
{0x11FB0, 0x11FB0}, {0x11FC0, 0x11FF1}, {0x11FFF, 0x12399},
{0x12400, 0x1246E}, {0x12470, 0x12474}, {0x12480, 0x12543},
{0x12F90, 0x12FF2}, {0x13000, 0x13455}, {0x14400, 0x14646},
{0x10EB0, 0x10EB1}, {0x10EC2, 0x10EC7}, {0x10ED0, 0x10ED8},
{0x10EFA, 0x10F27}, {0x10F30, 0x10F59}, {0x10F70, 0x10F89},
{0x10FB0, 0x10FCB}, {0x10FE0, 0x10FF6}, {0x11000, 0x1104D},
{0x11052, 0x11075}, {0x1107F, 0x110C2}, {0x110CD, 0x110CD},
{0x110D0, 0x110E8}, {0x110F0, 0x110F9}, {0x11100, 0x11134},
{0x11136, 0x11147}, {0x11150, 0x11176}, {0x11180, 0x111DF},
{0x111E1, 0x111F4}, {0x11200, 0x11211}, {0x11213, 0x11241},
{0x11280, 0x11286}, {0x11288, 0x11288}, {0x1128A, 0x1128D},
{0x1128F, 0x1129D}, {0x1129F, 0x112A9}, {0x112B0, 0x112EA},
{0x112F0, 0x112F9}, {0x11300, 0x11303}, {0x11305, 0x1130C},
{0x1130F, 0x11310}, {0x11313, 0x11328}, {0x1132A, 0x11330},
{0x11332, 0x11333}, {0x11335, 0x11339}, {0x1133B, 0x11344},
{0x11347, 0x11348}, {0x1134B, 0x1134D}, {0x11350, 0x11350},
{0x11357, 0x11357}, {0x1135D, 0x11363}, {0x11366, 0x1136C},
{0x11370, 0x11374}, {0x11380, 0x11389}, {0x1138B, 0x1138B},
{0x1138E, 0x1138E}, {0x11390, 0x113B5}, {0x113B7, 0x113C0},
{0x113C2, 0x113C2}, {0x113C5, 0x113C5}, {0x113C7, 0x113CA},
{0x113CC, 0x113D5}, {0x113D7, 0x113D8}, {0x113E1, 0x113E2},
{0x11400, 0x1145B}, {0x1145D, 0x11461}, {0x11480, 0x114C7},
{0x114D0, 0x114D9}, {0x11580, 0x115B5}, {0x115B8, 0x115DD},
{0x11600, 0x11644}, {0x11650, 0x11659}, {0x11660, 0x1166C},
{0x11680, 0x116B9}, {0x116C0, 0x116C9}, {0x116D0, 0x116E3},
{0x11700, 0x1171A}, {0x1171D, 0x1172B}, {0x11730, 0x11746},
{0x11800, 0x1183B}, {0x118A0, 0x118F2}, {0x118FF, 0x11906},
{0x11909, 0x11909}, {0x1190C, 0x11913}, {0x11915, 0x11916},
{0x11918, 0x11935}, {0x11937, 0x11938}, {0x1193B, 0x11946},
{0x11950, 0x11959}, {0x119A0, 0x119A7}, {0x119AA, 0x119D7},
{0x119DA, 0x119E4}, {0x11A00, 0x11A47}, {0x11A50, 0x11AA2},
{0x11AB0, 0x11AF8}, {0x11B00, 0x11B09}, {0x11B60, 0x11B67},
{0x11BC0, 0x11BE1}, {0x11BF0, 0x11BF9}, {0x11C00, 0x11C08},
{0x11C0A, 0x11C36}, {0x11C38, 0x11C45}, {0x11C50, 0x11C6C},
{0x11C70, 0x11C8F}, {0x11C92, 0x11CA7}, {0x11CA9, 0x11CB6},
{0x11D00, 0x11D06}, {0x11D08, 0x11D09}, {0x11D0B, 0x11D36},
{0x11D3A, 0x11D3A}, {0x11D3C, 0x11D3D}, {0x11D3F, 0x11D47},
{0x11D50, 0x11D59}, {0x11D60, 0x11D65}, {0x11D67, 0x11D68},
{0x11D6A, 0x11D8E}, {0x11D90, 0x11D91}, {0x11D93, 0x11D98},
{0x11DA0, 0x11DA9}, {0x11DB0, 0x11DDB}, {0x11DE0, 0x11DE9},
{0x11EE0, 0x11EF8}, {0x11F00, 0x11F10}, {0x11F12, 0x11F3A},
{0x11F3E, 0x11F5A}, {0x11FB0, 0x11FB0}, {0x11FC0, 0x11FF1},
{0x11FFF, 0x12399}, {0x12400, 0x1246E}, {0x12470, 0x12474},
{0x12480, 0x12543}, {0x12F90, 0x12FF2}, {0x13000, 0x13455},
{0x13460, 0x143FA}, {0x14400, 0x14646}, {0x16100, 0x16139},
{0x16800, 0x16A38}, {0x16A40, 0x16A5E}, {0x16A60, 0x16A69},
{0x16A6E, 0x16ABE}, {0x16AC0, 0x16AC9}, {0x16AD0, 0x16AED},
{0x16AF0, 0x16AF5}, {0x16B00, 0x16B45}, {0x16B50, 0x16B59},
{0x16B5B, 0x16B61}, {0x16B63, 0x16B77}, {0x16B7D, 0x16B8F},
{0x16E40, 0x16E9A}, {0x16F00, 0x16F4A}, {0x16F4F, 0x16F87},
{0x16D40, 0x16D79}, {0x16E40, 0x16E9A}, {0x16EA0, 0x16EB8},
{0x16EBB, 0x16ED3}, {0x16F00, 0x16F4A}, {0x16F4F, 0x16F87},
{0x16F8F, 0x16F9F}, {0x1BC00, 0x1BC6A}, {0x1BC70, 0x1BC7C},
{0x1BC80, 0x1BC88}, {0x1BC90, 0x1BC99}, {0x1BC9C, 0x1BCA3},
{0x1CF00, 0x1CF2D}, {0x1CF30, 0x1CF46}, {0x1CF50, 0x1CFC3},
{0x1D000, 0x1D0F5}, {0x1D100, 0x1D126}, {0x1D129, 0x1D1EA},
{0x1D200, 0x1D245}, {0x1D2C0, 0x1D2D3}, {0x1D2E0, 0x1D2F3},
{0x1D300, 0x1D356}, {0x1D360, 0x1D378}, {0x1D400, 0x1D454},
{0x1CC00, 0x1CCFC}, {0x1CD00, 0x1CEB3}, {0x1CEBA, 0x1CED0},
{0x1CEE0, 0x1CEF0}, {0x1CF00, 0x1CF2D}, {0x1CF30, 0x1CF46},
{0x1CF50, 0x1CFC3}, {0x1D000, 0x1D0F5}, {0x1D100, 0x1D126},
{0x1D129, 0x1D1EA}, {0x1D200, 0x1D245}, {0x1D2C0, 0x1D2D3},
{0x1D2E0, 0x1D2F3}, {0x1D377, 0x1D378}, {0x1D400, 0x1D454},
{0x1D456, 0x1D49C}, {0x1D49E, 0x1D49F}, {0x1D4A2, 0x1D4A2},
{0x1D4A5, 0x1D4A6}, {0x1D4A9, 0x1D4AC}, {0x1D4AE, 0x1D4B9},
{0x1D4BB, 0x1D4BB}, {0x1D4BD, 0x1D4C3}, {0x1D4C5, 0x1D505},
@@ -385,66 +394,94 @@ var neutral = table{
{0x1E08F, 0x1E08F}, {0x1E100, 0x1E12C}, {0x1E130, 0x1E13D},
{0x1E140, 0x1E149}, {0x1E14E, 0x1E14F}, {0x1E290, 0x1E2AE},
{0x1E2C0, 0x1E2F9}, {0x1E2FF, 0x1E2FF}, {0x1E4D0, 0x1E4F9},
{0x1E7E0, 0x1E7E6}, {0x1E7E8, 0x1E7EB}, {0x1E7ED, 0x1E7EE},
{0x1E7F0, 0x1E7FE}, {0x1E800, 0x1E8C4}, {0x1E8C7, 0x1E8D6},
{0x1E900, 0x1E94B}, {0x1E950, 0x1E959}, {0x1E95E, 0x1E95F},
{0x1EC71, 0x1ECB4}, {0x1ED01, 0x1ED3D}, {0x1EE00, 0x1EE03},
{0x1EE05, 0x1EE1F}, {0x1EE21, 0x1EE22}, {0x1EE24, 0x1EE24},
{0x1EE27, 0x1EE27}, {0x1EE29, 0x1EE32}, {0x1EE34, 0x1EE37},
{0x1EE39, 0x1EE39}, {0x1EE3B, 0x1EE3B}, {0x1EE42, 0x1EE42},
{0x1EE47, 0x1EE47}, {0x1EE49, 0x1EE49}, {0x1EE4B, 0x1EE4B},
{0x1EE4D, 0x1EE4F}, {0x1EE51, 0x1EE52}, {0x1EE54, 0x1EE54},
{0x1EE57, 0x1EE57}, {0x1EE59, 0x1EE59}, {0x1EE5B, 0x1EE5B},
{0x1EE5D, 0x1EE5D}, {0x1EE5F, 0x1EE5F}, {0x1EE61, 0x1EE62},
{0x1EE64, 0x1EE64}, {0x1EE67, 0x1EE6A}, {0x1EE6C, 0x1EE72},
{0x1EE74, 0x1EE77}, {0x1EE79, 0x1EE7C}, {0x1EE7E, 0x1EE7E},
{0x1EE80, 0x1EE89}, {0x1EE8B, 0x1EE9B}, {0x1EEA1, 0x1EEA3},
{0x1EEA5, 0x1EEA9}, {0x1EEAB, 0x1EEBB}, {0x1EEF0, 0x1EEF1},
{0x1F000, 0x1F003}, {0x1F005, 0x1F02B}, {0x1F030, 0x1F093},
{0x1F0A0, 0x1F0AE}, {0x1F0B1, 0x1F0BF}, {0x1F0C1, 0x1F0CE},
{0x1F0D1, 0x1F0F5}, {0x1F10B, 0x1F10F}, {0x1F12E, 0x1F12F},
{0x1F16A, 0x1F16F}, {0x1F1AD, 0x1F1AD}, {0x1F1E6, 0x1F1FF},
{0x1F321, 0x1F32C}, {0x1F336, 0x1F336}, {0x1F37D, 0x1F37D},
{0x1F394, 0x1F39F}, {0x1F3CB, 0x1F3CE}, {0x1F3D4, 0x1F3DF},
{0x1F3F1, 0x1F3F3}, {0x1F3F5, 0x1F3F7}, {0x1F43F, 0x1F43F},
{0x1F441, 0x1F441}, {0x1F4FD, 0x1F4FE}, {0x1F53E, 0x1F54A},
{0x1F54F, 0x1F54F}, {0x1F568, 0x1F579}, {0x1F57B, 0x1F594},
{0x1F597, 0x1F5A3}, {0x1F5A5, 0x1F5FA}, {0x1F650, 0x1F67F},
{0x1F6C6, 0x1F6CB}, {0x1F6CD, 0x1F6CF}, {0x1F6D3, 0x1F6D4},
{0x1F6E0, 0x1F6EA}, {0x1F6F0, 0x1F6F3}, {0x1F700, 0x1F776},
{0x1F77B, 0x1F7D9}, {0x1F800, 0x1F80B}, {0x1F810, 0x1F847},
{0x1F850, 0x1F859}, {0x1F860, 0x1F887}, {0x1F890, 0x1F8AD},
{0x1F8B0, 0x1F8B1}, {0x1F900, 0x1F90B}, {0x1F93B, 0x1F93B},
{0x1F946, 0x1F946}, {0x1FA00, 0x1FA53}, {0x1FA60, 0x1FA6D},
{0x1FB00, 0x1FB92}, {0x1FB94, 0x1FBCA}, {0x1FBF0, 0x1FBF9},
{0xE0001, 0xE0001}, {0xE0020, 0xE007F},
{0x1E5D0, 0x1E5FA}, {0x1E5FF, 0x1E5FF}, {0x1E6C0, 0x1E6DE},
{0x1E6E0, 0x1E6F5}, {0x1E6FE, 0x1E6FF}, {0x1E7E0, 0x1E7E6},
{0x1E7E8, 0x1E7EB}, {0x1E7ED, 0x1E7EE}, {0x1E7F0, 0x1E7FE},
{0x1E800, 0x1E8C4}, {0x1E8C7, 0x1E8D6}, {0x1E900, 0x1E94B},
{0x1E950, 0x1E959}, {0x1E95E, 0x1E95F}, {0x1EC71, 0x1ECB4},
{0x1ED01, 0x1ED3D}, {0x1EE00, 0x1EE03}, {0x1EE05, 0x1EE1F},
{0x1EE21, 0x1EE22}, {0x1EE24, 0x1EE24}, {0x1EE27, 0x1EE27},
{0x1EE29, 0x1EE32}, {0x1EE34, 0x1EE37}, {0x1EE39, 0x1EE39},
{0x1EE3B, 0x1EE3B}, {0x1EE42, 0x1EE42}, {0x1EE47, 0x1EE47},
{0x1EE49, 0x1EE49}, {0x1EE4B, 0x1EE4B}, {0x1EE4D, 0x1EE4F},
{0x1EE51, 0x1EE52}, {0x1EE54, 0x1EE54}, {0x1EE57, 0x1EE57},
{0x1EE59, 0x1EE59}, {0x1EE5B, 0x1EE5B}, {0x1EE5D, 0x1EE5D},
{0x1EE5F, 0x1EE5F}, {0x1EE61, 0x1EE62}, {0x1EE64, 0x1EE64},
{0x1EE67, 0x1EE6A}, {0x1EE6C, 0x1EE72}, {0x1EE74, 0x1EE77},
{0x1EE79, 0x1EE7C}, {0x1EE7E, 0x1EE7E}, {0x1EE80, 0x1EE89},
{0x1EE8B, 0x1EE9B}, {0x1EEA1, 0x1EEA3}, {0x1EEA5, 0x1EEA9},
{0x1EEAB, 0x1EEBB}, {0x1EEF0, 0x1EEF1}, {0x1F000, 0x1F003},
{0x1F005, 0x1F02B}, {0x1F030, 0x1F093}, {0x1F0A0, 0x1F0AE},
{0x1F0B1, 0x1F0BF}, {0x1F0C1, 0x1F0CE}, {0x1F0D1, 0x1F0F5},
{0x1F10B, 0x1F10F}, {0x1F12E, 0x1F12F}, {0x1F16A, 0x1F16F},
{0x1F1AD, 0x1F1AD}, {0x1F1E6, 0x1F1FF}, {0x1F321, 0x1F32C},
{0x1F336, 0x1F336}, {0x1F37D, 0x1F37D}, {0x1F394, 0x1F39F},
{0x1F3CB, 0x1F3CE}, {0x1F3D4, 0x1F3DF}, {0x1F3F1, 0x1F3F3},
{0x1F3F5, 0x1F3F7}, {0x1F43F, 0x1F43F}, {0x1F441, 0x1F441},
{0x1F4FD, 0x1F4FE}, {0x1F53E, 0x1F54A}, {0x1F54F, 0x1F54F},
{0x1F568, 0x1F579}, {0x1F57B, 0x1F594}, {0x1F597, 0x1F5A3},
{0x1F5A5, 0x1F5FA}, {0x1F650, 0x1F67F}, {0x1F6C6, 0x1F6CB},
{0x1F6CD, 0x1F6CF}, {0x1F6D3, 0x1F6D4}, {0x1F6E0, 0x1F6EA},
{0x1F6F0, 0x1F6F3}, {0x1F700, 0x1F7D9}, {0x1F800, 0x1F80B},
{0x1F810, 0x1F847}, {0x1F850, 0x1F859}, {0x1F860, 0x1F887},
{0x1F890, 0x1F8AD}, {0x1F8B0, 0x1F8BB}, {0x1F8C0, 0x1F8C1},
{0x1F8D0, 0x1F8D8}, {0x1F900, 0x1F90B}, {0x1F93B, 0x1F93B},
{0x1F946, 0x1F946}, {0x1FA00, 0x1FA57}, {0x1FA60, 0x1FA6D},
{0x1FB00, 0x1FB92}, {0x1FB94, 0x1FBFA}, {0xE0001, 0xE0001},
{0xE0020, 0xE007F},
}
var emoji = table{
{0x203C, 0x203C}, {0x2049, 0x2049}, {0x2122, 0x2122},
{0x2139, 0x2139}, {0x2194, 0x2199}, {0x21A9, 0x21AA},
{0x231A, 0x231B}, {0x2328, 0x2328}, {0x2388, 0x2388},
{0x23CF, 0x23CF}, {0x23E9, 0x23F3}, {0x23F8, 0x23FA},
{0x24C2, 0x24C2}, {0x25AA, 0x25AB}, {0x25B6, 0x25B6},
{0x25C0, 0x25C0}, {0x25FB, 0x25FE}, {0x2600, 0x2605},
{0x2607, 0x2612}, {0x2614, 0x2685}, {0x2690, 0x2705},
{0x2708, 0x2712}, {0x2714, 0x2714}, {0x2716, 0x2716},
{0x271D, 0x271D}, {0x2721, 0x2721}, {0x2728, 0x2728},
{0x2733, 0x2734}, {0x2744, 0x2744}, {0x2747, 0x2747},
{0x274C, 0x274C}, {0x274E, 0x274E}, {0x2753, 0x2755},
{0x2757, 0x2757}, {0x2763, 0x2767}, {0x2795, 0x2797},
{0x27A1, 0x27A1}, {0x27B0, 0x27B0}, {0x27BF, 0x27BF},
{0x2934, 0x2935}, {0x2B05, 0x2B07}, {0x2B1B, 0x2B1C},
{0x2B50, 0x2B50}, {0x2B55, 0x2B55}, {0x3030, 0x3030},
{0x303D, 0x303D}, {0x3297, 0x3297}, {0x3299, 0x3299},
{0x1F000, 0x1F0FF}, {0x1F10D, 0x1F10F}, {0x1F12F, 0x1F12F},
{0x1F16C, 0x1F171}, {0x1F17E, 0x1F17F}, {0x1F18E, 0x1F18E},
{0x1F191, 0x1F19A}, {0x1F1AD, 0x1F1E5}, {0x1F201, 0x1F20F},
{0x1F21A, 0x1F21A}, {0x1F22F, 0x1F22F}, {0x1F232, 0x1F23A},
{0x1F23C, 0x1F23F}, {0x1F249, 0x1F3FA}, {0x1F400, 0x1F53D},
{0x1F546, 0x1F64F}, {0x1F680, 0x1F6FF}, {0x1F774, 0x1F77F},
{0x1F7D5, 0x1F7FF}, {0x1F80C, 0x1F80F}, {0x1F848, 0x1F84F},
{0x1F85A, 0x1F85F}, {0x1F888, 0x1F88F}, {0x1F8AE, 0x1F8FF},
{0x1F90C, 0x1F93A}, {0x1F93C, 0x1F945}, {0x1F947, 0x1FAFF},
{0x231A, 0x231B}, {0x2328, 0x2328}, {0x23CF, 0x23CF},
{0x23E9, 0x23F3}, {0x23F8, 0x23FA}, {0x24C2, 0x24C2},
{0x25AA, 0x25AB}, {0x25B6, 0x25B6}, {0x25C0, 0x25C0},
{0x25FB, 0x25FE}, {0x2600, 0x2604}, {0x260E, 0x260E},
{0x2611, 0x2611}, {0x2614, 0x2615}, {0x2618, 0x2618},
{0x261D, 0x261D}, {0x2620, 0x2620}, {0x2622, 0x2623},
{0x2626, 0x2626}, {0x262A, 0x262A}, {0x262E, 0x262F},
{0x2638, 0x263A}, {0x2640, 0x2640}, {0x2642, 0x2642},
{0x2648, 0x2653}, {0x265F, 0x2660}, {0x2663, 0x2663},
{0x2665, 0x2666}, {0x2668, 0x2668}, {0x267B, 0x267B},
{0x267E, 0x267F}, {0x2692, 0x2697}, {0x2699, 0x2699},
{0x269B, 0x269C}, {0x26A0, 0x26A1}, {0x26A7, 0x26A7},
{0x26AA, 0x26AB}, {0x26B0, 0x26B1}, {0x26BD, 0x26BE},
{0x26C4, 0x26C5}, {0x26C8, 0x26C8}, {0x26CE, 0x26CF},
{0x26D1, 0x26D1}, {0x26D3, 0x26D4}, {0x26E9, 0x26EA},
{0x26F0, 0x26F5}, {0x26F7, 0x26FA}, {0x26FD, 0x26FD},
{0x2702, 0x2702}, {0x2705, 0x2705}, {0x2708, 0x270D},
{0x270F, 0x270F}, {0x2712, 0x2712}, {0x2714, 0x2714},
{0x2716, 0x2716}, {0x271D, 0x271D}, {0x2721, 0x2721},
{0x2728, 0x2728}, {0x2733, 0x2734}, {0x2744, 0x2744},
{0x2747, 0x2747}, {0x274C, 0x274C}, {0x274E, 0x274E},
{0x2753, 0x2755}, {0x2757, 0x2757}, {0x2763, 0x2764},
{0x2795, 0x2797}, {0x27A1, 0x27A1}, {0x27B0, 0x27B0},
{0x27BF, 0x27BF}, {0x2934, 0x2935}, {0x2B05, 0x2B07},
{0x2B1B, 0x2B1C}, {0x2B50, 0x2B50}, {0x2B55, 0x2B55},
{0x3030, 0x3030}, {0x303D, 0x303D}, {0x3297, 0x3297},
{0x3299, 0x3299}, {0x1F004, 0x1F004}, {0x1F02C, 0x1F02F},
{0x1F094, 0x1F09F}, {0x1F0AF, 0x1F0B0}, {0x1F0C0, 0x1F0C0},
{0x1F0CF, 0x1F0D0}, {0x1F0F6, 0x1F0FF}, {0x1F170, 0x1F171},
{0x1F17E, 0x1F17F}, {0x1F18E, 0x1F18E}, {0x1F191, 0x1F19A},
{0x1F1AE, 0x1F1E5}, {0x1F201, 0x1F20F}, {0x1F21A, 0x1F21A},
{0x1F22F, 0x1F22F}, {0x1F232, 0x1F23A}, {0x1F23C, 0x1F23F},
{0x1F249, 0x1F25F}, {0x1F266, 0x1F321}, {0x1F324, 0x1F393},
{0x1F396, 0x1F397}, {0x1F399, 0x1F39B}, {0x1F39E, 0x1F3F0},
{0x1F3F3, 0x1F3F5}, {0x1F3F7, 0x1F3FA}, {0x1F400, 0x1F4FD},
{0x1F4FF, 0x1F53D}, {0x1F549, 0x1F54E}, {0x1F550, 0x1F567},
{0x1F56F, 0x1F570}, {0x1F573, 0x1F57A}, {0x1F587, 0x1F587},
{0x1F58A, 0x1F58D}, {0x1F590, 0x1F590}, {0x1F595, 0x1F596},
{0x1F5A4, 0x1F5A5}, {0x1F5A8, 0x1F5A8}, {0x1F5B1, 0x1F5B2},
{0x1F5BC, 0x1F5BC}, {0x1F5C2, 0x1F5C4}, {0x1F5D1, 0x1F5D3},
{0x1F5DC, 0x1F5DE}, {0x1F5E1, 0x1F5E1}, {0x1F5E3, 0x1F5E3},
{0x1F5E8, 0x1F5E8}, {0x1F5EF, 0x1F5EF}, {0x1F5F3, 0x1F5F3},
{0x1F5FA, 0x1F64F}, {0x1F680, 0x1F6C5}, {0x1F6CB, 0x1F6D2},
{0x1F6D5, 0x1F6E5}, {0x1F6E9, 0x1F6E9}, {0x1F6EB, 0x1F6F0},
{0x1F6F3, 0x1F6FF}, {0x1F7DA, 0x1F7FF}, {0x1F80C, 0x1F80F},
{0x1F848, 0x1F84F}, {0x1F85A, 0x1F85F}, {0x1F888, 0x1F88F},
{0x1F8AE, 0x1F8AF}, {0x1F8BC, 0x1F8BF}, {0x1F8C2, 0x1F8CF},
{0x1F8D9, 0x1F8FF}, {0x1F90C, 0x1F93A}, {0x1F93C, 0x1F945},
{0x1F947, 0x1F9FF}, {0x1FA58, 0x1FA5F}, {0x1FA6E, 0x1FAFF},
{0x1FC00, 0x1FFFD},
}

View File

@@ -108,9 +108,9 @@ func main() {
![dynTotal](_svg/godEMrCZmJkHYH1X9dN4Nm0U7.svg)
#### [complex example](_examples/complex/main.go)
#### [queueBar example](_examples/queueBar/main.go)
![complex](_svg/wHzf1M7sd7B3zVa2scBMnjqRf.svg)
![queueBar](_svg/wHzf1M7sd7B3zVa2scBMnjqRf.svg)
#### [io example](_examples/io/main.go)

View File

@@ -38,12 +38,6 @@ type bState struct {
total int64
current int64
refill int64
trimSpace bool
aborted bool
triggerComplete bool
rmOnComplete bool
noPop bool
autoRefresh bool
buffers [3]*bytes.Buffer
decorGroups [2][]decor.Decorator
ewmaDecorators []decor.EwmaDecorator
@@ -51,6 +45,12 @@ type bState struct {
extender extenderFunc
renderReq chan<- time.Time
waitBar *Bar // key for (*pState).queueBars
trimSpace bool
aborted bool
triggerComplete bool
rmOnComplete bool
noPop bool
autoRefresh bool
}
type renderFrame struct {
@@ -61,24 +61,6 @@ type renderFrame struct {
err error
}
func newBar(ctx context.Context, container *Progress, bs *bState) *Bar {
ctx, cancel := context.WithCancel(ctx)
bar := &Bar{
priority: bs.priority,
frameCh: make(chan *renderFrame, 1),
operateState: make(chan func(*bState)),
bsOk: make(chan struct{}),
container: container,
ctx: ctx,
cancel: cancel,
}
container.bwg.Add(1)
go bar.serve(bs)
return bar
}
// ProxyReader wraps io.Reader with metrics required for progress
// tracking. If `r` is 'unknown total/size' reader it's mandatory
// to call `(*Bar).SetTotal(-1, true)` after the wrapper returns
@@ -88,7 +70,7 @@ func (b *Bar) ProxyReader(r io.Reader) io.ReadCloser {
if r == nil {
panic("expected non nil io.Reader")
}
result := make(chan io.ReadCloser)
result := make(chan io.ReadCloser, 1)
select {
case b.operateState <- func(s *bState) {
result <- newProxyReader(r, b, len(s.ewmaDecorators) != 0)
@@ -106,7 +88,7 @@ func (b *Bar) ProxyWriter(w io.Writer) io.WriteCloser {
if w == nil {
panic("expected non nil io.Writer")
}
result := make(chan io.WriteCloser)
result := make(chan io.WriteCloser, 1)
select {
case b.operateState <- func(s *bState) {
result <- newProxyWriter(w, b, len(s.ewmaDecorators) != 0)
@@ -119,7 +101,7 @@ func (b *Bar) ProxyWriter(w io.Writer) io.WriteCloser {
// ID returns id of the bar.
func (b *Bar) ID() int {
result := make(chan int)
result := make(chan int, 1)
select {
case b.operateState <- func(s *bState) { result <- s.id }:
return <-result
@@ -130,7 +112,7 @@ func (b *Bar) ID() int {
// Current returns bar's current value, in other words sum of all increments.
func (b *Bar) Current() int64 {
result := make(chan int64)
result := make(chan int64, 1)
select {
case b.operateState <- func(s *bState) { result <- s.current }:
return <-result
@@ -157,7 +139,7 @@ func (b *Bar) SetRefillCurrent() {
// TraverseDecorators traverses available decorators and calls `cb`
// on each unwrapped one.
func (b *Bar) TraverseDecorators(cb func(decor.Decorator)) {
func (b *Bar) TraverseDecorators(cb func(decor.Decorator)) (ok bool) {
select {
case b.operateState <- func(s *bState) {
for _, group := range s.decorGroups {
@@ -166,7 +148,9 @@ func (b *Bar) TraverseDecorators(cb func(decor.Decorator)) {
}
}
}:
return true
case <-b.ctx.Done():
return false
}
}
@@ -179,11 +163,10 @@ func (b *Bar) EnableTriggerComplete() {
if s.triggerComplete {
return
}
s.triggerComplete = true
if s.current >= s.total {
s.current = s.total
s.triggerCompletion(b)
} else {
s.triggerComplete = true
b.done(s.renderReq, s.autoRefresh)
}
}:
case <-b.ctx.Done():
@@ -208,7 +191,8 @@ func (b *Bar) SetTotal(total int64, complete bool) {
}
if complete {
s.current = s.total
s.triggerCompletion(b)
s.triggerComplete = true
b.done(s.renderReq, s.autoRefresh)
}
}:
case <-b.ctx.Done():
@@ -225,7 +209,7 @@ func (b *Bar) SetCurrent(current int64) {
s.current = current
if s.triggerComplete && s.current >= s.total {
s.current = s.total
s.triggerCompletion(b)
b.done(s.renderReq, s.autoRefresh)
}
}:
case <-b.ctx.Done():
@@ -249,7 +233,7 @@ func (b *Bar) IncrInt64(n int64) {
s.current += n
if s.triggerComplete && s.current >= s.total {
s.current = s.total
s.triggerCompletion(b)
b.done(s.renderReq, s.autoRefresh)
}
}:
case <-b.ctx.Done():
@@ -277,7 +261,7 @@ func (b *Bar) EwmaIncrInt64(n int64, iterDur time.Duration) {
s.current += n
if s.triggerComplete && s.current >= s.total {
s.current = s.total
s.triggerCompletion(b)
b.done(s.renderReq, s.autoRefresh)
}
}:
case <-b.ctx.Done():
@@ -299,7 +283,7 @@ func (b *Bar) EwmaSetCurrent(current int64, iterDur time.Duration) {
s.current = current
if s.triggerComplete && s.current >= s.total {
s.current = s.total
s.triggerCompletion(b)
b.done(s.renderReq, s.autoRefresh)
}
}:
case <-b.ctx.Done():
@@ -335,7 +319,8 @@ func (b *Bar) Abort(drop bool) {
}
s.aborted = true
s.rmOnComplete = drop
s.triggerCompletion(b)
s.triggerComplete = true
b.done(s.renderReq, s.autoRefresh)
}:
case <-b.ctx.Done():
}
@@ -343,7 +328,7 @@ func (b *Bar) Abort(drop bool) {
// Aborted reports whether the bar is in aborted state.
func (b *Bar) Aborted() bool {
result := make(chan bool)
result := make(chan bool, 1)
select {
case b.operateState <- func(s *bState) { result <- s.aborted }:
return <-result
@@ -354,7 +339,7 @@ func (b *Bar) Aborted() bool {
// Completed reports whether the bar is in completed state.
func (b *Bar) Completed() bool {
result := make(chan bool)
result := make(chan bool, 1)
select {
case b.operateState <- func(s *bState) { result <- s.completed() }:
return <-result
@@ -363,13 +348,17 @@ func (b *Bar) Completed() bool {
}
}
// IsRunning reports whether the bar is in running state.
func (b *Bar) IsRunning() bool {
// AbortedOrCompleted reports whether a bar is in aborted or completed state.
// Faster and atomic version of `(*Bar).Aborted() || (*Bar).Completed()`.
func (b *Bar) AbortedOrCompleted() bool {
result := make(chan bool, 1)
select {
case <-b.ctx.Done():
return false
default:
return true
case b.operateState <- func(s *bState) {
result <- s.aborted || s.completed()
}:
return <-result
case <-b.bsOk:
return b.bs.aborted || b.bs.completed()
}
}
@@ -379,29 +368,27 @@ func (b *Bar) Wait() {
}
func (b *Bar) serve(bs *bState) {
defer b.container.bwg.Done()
decoratorsOnShutdown := func(group []decor.Decorator) {
for _, d := range group {
if d, ok := unwrap(d).(decor.ShutdownListener); ok {
b.container.bwg.Add(1)
go func() {
defer b.container.bwg.Done()
d.OnShutdown()
}()
d.OnShutdown()
}
}
}
defer func() {
decoratorsOnShutdown(bs.decorGroups[0])
decoratorsOnShutdown(bs.decorGroups[1])
b.bs = bs
close(b.bsOk)
b.container.bwg.Done()
}()
for {
select {
case op := <-b.operateState:
op(bs)
case <-b.ctx.Done():
decoratorsOnShutdown(bs.decorGroups[0])
decoratorsOnShutdown(bs.decorGroups[1])
// bar can be aborted by canceling parent ctx without calling b.Abort
bs.aborted = !bs.completed()
b.bs = bs
close(b.bsOk)
bs.aborted = bs.aborted || !bs.completed()
return
}
}
@@ -437,28 +424,8 @@ func (b *Bar) render(tw int) {
}
}
func (b *Bar) tryEarlyRefresh(renderReq chan<- time.Time) {
var otherRunning int
b.container.traverseBars(func(bar *Bar) bool {
if b != bar && bar.IsRunning() {
otherRunning++
return false // stop traverse
}
return true // continue traverse
})
if otherRunning == 0 {
for {
select {
case renderReq <- time.Now():
case <-b.ctx.Done():
return
}
}
}
}
func (b *Bar) wSyncTable() decorSyncTable {
result := make(chan decorSyncTable)
result := make(chan decorSyncTable, 1)
select {
case b.operateState <- func(s *bState) { result <- s.wSyncTable() }:
return <-result
@@ -467,12 +434,12 @@ func (b *Bar) wSyncTable() decorSyncTable {
}
}
func (s *bState) draw(stat decor.Statistics) (_ io.Reader, err error) {
func (s *bState) draw(stat decor.Statistics) (io.Reader, error) {
decorFiller := func(buf *bytes.Buffer, group []decor.Decorator) (err error) {
for _, d := range group {
for i, d := range group {
// need to call Decor in any case because of width synchronization
str, width := d.Decor(stat)
if err != nil {
if i != 0 && err != nil {
continue
}
if w := stat.AvailableWidth - width; w >= 0 {
@@ -487,38 +454,33 @@ func (s *bState) draw(stat decor.Statistics) (_ io.Reader, err error) {
return err
}
for i, buf := range s.buffers[:2] {
err = decorFiller(buf, s.decorGroups[i])
for i, buf := range s.buffers[1:] {
err := decorFiller(buf, s.decorGroups[i])
if err != nil {
return nil, err
}
}
spaces := []io.Reader{
strings.NewReader(" "),
strings.NewReader(" "),
}
if s.trimSpace || stat.AvailableWidth < 2 {
for _, r := range spaces {
_, _ = io.Copy(io.Discard, r)
}
} else {
stat.AvailableWidth -= 2
}
err = s.filler.Fill(s.buffers[2], stat)
if err != nil {
return nil, err
err := s.filler.Fill(s.buffers[0], stat)
return io.MultiReader(
s.buffers[1],
s.buffers[0],
s.buffers[2],
strings.NewReader("\n"),
), err
}
stat.AvailableWidth -= 2
err := s.filler.Fill(s.buffers[0], stat)
return io.MultiReader(
s.buffers[0],
spaces[0],
s.buffers[2],
spaces[1],
s.buffers[1],
strings.NewReader(" "),
s.buffers[0],
strings.NewReader(" "),
s.buffers[2],
strings.NewReader("\n"),
), nil
), err
}
func (s *bState) wSyncTable() (table decorSyncTable) {
@@ -536,23 +498,55 @@ func (s *bState) wSyncTable() (table decorSyncTable) {
return table
}
func (s *bState) triggerCompletion(b *Bar) {
s.triggerComplete = true
if s.autoRefresh {
func (b *Bar) done(renderReq chan<- time.Time, autoRefresh bool) {
if autoRefresh {
// Technically this call isn't required, but if refresh rate is set to
// one hour for example and bar completes within a few minutes p.Wait()
// will wait for one hour. This call helps to avoid unnecessary waiting.
go b.tryEarlyRefresh(s.renderReq)
go b.tryEarlyRefresh(renderReq)
} else {
b.cancel()
}
}
func (s bState) completed() bool {
func (b *Bar) tryEarlyRefresh(renderReq chan<- time.Time) {
otherRunning := make(chan struct{})
ok := b.container.iterateBars(func(bar *Bar) bool {
if b != bar && bar.isRunning() {
close(otherRunning)
return false // stop traverse
}
return true // continue traverse
})
if ok {
select {
case <-otherRunning:
default:
for {
select {
case renderReq <- time.Now():
case <-b.ctx.Done():
return
}
}
}
}
}
func (b *Bar) isRunning() bool {
select {
case <-b.ctx.Done():
return false
default:
return true
}
}
func (s *bState) completed() bool {
return s.triggerComplete && s.current == s.total
}
func (s bState) newStatistics(tw int) decor.Statistics {
func (s *bState) newStatistics(tw int) decor.Statistics {
return decor.Statistics{
AvailableWidth: tw,
RequestedWidth: s.reqWidth,

View File

@@ -201,7 +201,7 @@ func (s *barFiller) Fill(w io.Writer, stat decor.Statistics) error {
var tip component
var refilling, filling, padding []byte
var fillCount int
curWidth := int(internal.PercentageRound(stat.Total, stat.Current, uint(width)))
curWidth := int(internal.PercentageRound(stat.Total, stat.Current, int64(width)))
if curWidth != 0 {
if !stat.Completed || s.tip.onComplete {
@@ -211,7 +211,7 @@ func (s *barFiller) Fill(w io.Writer, stat decor.Statistics) error {
}
switch refWidth := 0; {
case stat.Refill != 0:
refWidth = int(internal.PercentageRound(stat.Total, stat.Refill, uint(width)))
refWidth = int(internal.PercentageRound(stat.Total, stat.Refill, int64(width)))
curWidth -= refWidth
refWidth += curWidth
fallthrough

View File

@@ -21,18 +21,17 @@ func (s barHeap) Swap(i, j int) {
}
func (h *barHeap) Push(x interface{}) {
s := *h
b := x.(*Bar)
b.index = len(s)
*h = append(s, b)
b.index = h.Len()
*h = append(*h, b)
}
func (h *barHeap) Pop() interface{} {
var b *Bar
s := *h
i := s.Len() - 1
b, s[i] = s[i], nil // nil to avoid memory leak
b.index = -1 // for safety
b := s[i]
b.index = -1 // for safety
s[i] = nil // nil to avoid memory leak
*h = s[:i]
return b
}

View File

@@ -33,7 +33,7 @@ func WithWidth(width int) ContainerOption {
// WithQueueLen sets buffer size of heap manager channel. Ideally it must be
// kept at MAX value, where MAX is number of bars to be rendered at the same
// time. Default queue len is 128.
// time. Default queue len is 64.
func WithQueueLen(len int) ContainerOption {
return func(s *pState) {
s.hmQueueLen = len
@@ -65,9 +65,9 @@ func WithRenderDelay(ch <-chan struct{}) ContainerOption {
}
}
// WithShutdownNotifier value of type `[]*mpb.Bar` will be send to provided channel
// on shutdown event, i.e. after `(*Progress) Wait()` or `(*Progress) Shutdown()` call.
func WithShutdownNotifier(ch chan<- interface{}) ContainerOption {
// WithShutdownNotifier closes provided channel on shutdown event,
// i.e. after `(*Progress) Wait()` or `(*Progress) Shutdown()` call.
func WithShutdownNotifier(ch chan interface{}) ContainerOption {
return func(s *pState) {
s.shutdownNotifier = ch
}
@@ -137,3 +137,10 @@ func ContainerFuncOptOn(option func() ContainerOption, predicate func() bool) Co
}
return nil
}
// withHandOverBarHeap for test purposes only
func withHandOverBarHeap(ch chan<- []*Bar) ContainerOption {
return func(s *pState) {
s.handOverBarHeap = ch
}
}

View File

@@ -30,9 +30,7 @@ func New(out io.Writer) *Writer {
w.fd = int(f.Fd())
if IsTerminal(w.fd) {
w.terminal = true
w.termSize = func(fd int) (int, int, error) {
return GetSize(fd)
}
w.termSize = GetSize
}
}
bb := make([]byte, 16)

View File

@@ -0,0 +1,21 @@
package decor
// OnAbortOrOnComplete wrap decorator.
// Displays provided message on abort or on complete event.
//
// `decorator` Decorator to wrap
// `message` message to display
func OnAbortOrOnComplete(decorator Decorator, message string) Decorator {
return OnAbort(OnComplete(decorator, message), message)
}
// OnAbortMetaOrOnCompleteMeta wrap decorator.
// Provided fn is supposed to wrap output of given decorator
// with meta information like ANSI escape codes for example.
// Primary usage intention is to set SGR display attributes.
//
// `decorator` Decorator to wrap
// `fn` func to apply meta information
func OnAbortMetaOrOnCompleteMeta(decorator Decorator, fn func(string) string) Decorator {
return OnAbortMeta(OnCompleteMeta(decorator, fn), fn)
}

View File

@@ -61,7 +61,7 @@ func NewPercentage(format string, wcc ...WC) Decorator {
format = "% d"
}
f := func(s Statistics) string {
p := internal.Percentage(uint(s.Total), uint(s.Current), 100)
p := internal.Percentage(s.Total, s.Current, 100)
return fmt.Sprintf(format, percentageType(p))
}
return Any(f, wcc...)

View File

@@ -2,7 +2,9 @@ package mpb
import (
"container/heap"
"time"
"iter"
"slices"
"sync"
"github.com/vbauerster/mpb/v8/decor"
)
@@ -14,9 +16,9 @@ type heapCmd int
const (
h_sync heapCmd = iota
h_push
h_render
h_iter
h_fix
h_state
)
type heapRequest struct {
@@ -24,37 +26,40 @@ type heapRequest struct {
data interface{}
}
type iterRequest chan (<-chan *Bar)
type pushData struct {
bar *Bar
sync bool
}
type renderData struct {
width int
seqCh chan<- iter.Seq[*Bar]
}
type fixData struct {
bar *Bar
priority int
lazy bool
}
func (m heapManager) run(shutdownNotifier chan<- interface{}) {
func (m heapManager) run(pwg *sync.WaitGroup, shutdown <-chan interface{}, handOverBarHeap chan<- []*Bar) {
var bHeap barHeap
done := make(chan struct{})
defer func() {
close(done)
if shutdownNotifier != nil {
select {
case shutdownNotifier <- []*Bar(bHeap):
case <-time.After(time.Second):
}
}
}()
var sync bool
var prevLen int
var pMatrix map[int][]*decor.Sync
var aMatrix map[int][]*decor.Sync
defer func() {
if handOverBarHeap != nil {
ordered := make([]*Bar, 0, bHeap.Len())
for bHeap.Len() != 0 {
ordered = append(ordered, heap.Pop(&bHeap).(*Bar))
}
handOverBarHeap <- ordered
}
pwg.Done()
}()
for req := range m {
switch req.cmd {
case h_sync:
@@ -72,23 +77,34 @@ func (m heapManager) run(shutdownNotifier chan<- interface{}) {
}
sync, prevLen = false, bHeap.Len()
}
syncWidth(pMatrix, done)
syncWidth(aMatrix, done)
syncWidth(pMatrix, shutdown)
syncWidth(aMatrix, shutdown)
case h_push:
data := req.data.(pushData)
heap.Push(&bHeap, data.bar)
sync = sync || data.sync
case h_render:
data := req.data.(renderData)
for _, b := range bHeap {
go b.render(data.width)
}
ordered := make([]*Bar, 0, bHeap.Len())
for bHeap.Len() != 0 {
ordered = append(ordered, heap.Pop(&bHeap).(*Bar))
}
data.seqCh <- slices.Values(ordered)
case h_iter:
for i, req := range req.data.([]iterRequest) {
ch := make(chan *Bar, bHeap.Len())
req <- ch
switch i {
case 0:
rangeOverSlice(bHeap, ch)
case 1:
popOverHeap(&bHeap, ch)
seqCh := req.data.(chan<- iter.Seq[*Bar])
done := make(chan struct{})
seqCh <- func(yield func(*Bar) bool) {
defer close(done)
for _, b := range bHeap {
if !yield(b) {
break
}
}
}
<-done
case h_fix:
data := req.data.(fixData)
if data.bar.index < 0 {
@@ -98,9 +114,6 @@ func (m heapManager) run(shutdownNotifier chan<- interface{}) {
if !data.lazy {
heap.Fix(&bHeap, data.bar.index)
}
case h_state:
ch := req.data.(chan<- bool)
ch <- sync || prevLen != bHeap.Len()
}
}
}
@@ -114,8 +127,17 @@ func (m heapManager) push(b *Bar, sync bool) {
m <- heapRequest{cmd: h_push, data: data}
}
func (m heapManager) iter(req ...iterRequest) {
m <- heapRequest{cmd: h_iter, data: req}
func (m heapManager) render(width int) iter.Seq[*Bar] {
seqCh := make(chan iter.Seq[*Bar], 1)
m <- heapRequest{cmd: h_render, data: renderData{
width: width,
seqCh: seqCh,
}}
return <-seqCh
}
func (m heapManager) iter(seqCh chan<- iter.Seq[*Bar]) {
m <- heapRequest{cmd: h_iter, data: seqCh}
}
func (m heapManager) fix(b *Bar, priority int, lazy bool) {
@@ -123,17 +145,13 @@ func (m heapManager) fix(b *Bar, priority int, lazy bool) {
m <- heapRequest{cmd: h_fix, data: data}
}
func (m heapManager) state(ch chan<- bool) {
m <- heapRequest{cmd: h_state, data: ch}
}
func syncWidth(matrix map[int][]*decor.Sync, done <-chan struct{}) {
func syncWidth(matrix map[int][]*decor.Sync, done <-chan interface{}) {
for _, column := range matrix {
go maxWidthDistributor(column, done)
}
}
func maxWidthDistributor(column []*decor.Sync, done <-chan struct{}) {
func maxWidthDistributor(column []*decor.Sync, done <-chan interface{}) {
var maxWidth int
for _, s := range column {
select {
@@ -149,19 +167,3 @@ func maxWidthDistributor(column []*decor.Sync, done <-chan struct{}) {
s.Rx <- maxWidth
}
}
// unordered iteration
func rangeOverSlice(s barHeap, dst chan<- *Bar) {
defer close(dst)
for _, b := range s {
dst <- b
}
}
// ordered iteration
func popOverHeap(h heap.Interface, dst chan<- *Bar) {
defer close(dst)
for h.Len() != 0 {
dst <- heap.Pop(h).(*Bar)
}
}

View File

@@ -3,7 +3,7 @@ package internal
import "math"
// Percentage is a helper function, to calculate percentage.
func Percentage(total, current, width uint) float64 {
func Percentage(total, current, width int64) float64 {
if total == 0 {
return 0
}
@@ -14,9 +14,9 @@ func Percentage(total, current, width uint) float64 {
}
// PercentageRound same as Percentage but with math.Round.
func PercentageRound(total, current int64, width uint) float64 {
func PercentageRound(total, current, width int64) float64 {
if total < 0 || current < 0 {
return 0
}
return math.Round(Percentage(uint(total), uint(current), width))
return math.Round(Percentage(total, current, width))
}

View File

@@ -6,6 +6,7 @@ import (
"context"
"fmt"
"io"
"iter"
"math"
"os"
"sync"
@@ -16,24 +17,28 @@ import (
)
const defaultRefreshRate = 150 * time.Millisecond
const defaultHmQueueLength = 128
const defaultHmQueueLength = 64
// ErrDone represents use after `(*Progress).Wait()` error.
var ErrDone = fmt.Errorf("%T instance can't be reused after %[1]T.Wait()", (*Progress)(nil))
// Progress represents a container that renders one or more progress bars.
type Progress struct {
uwg *sync.WaitGroup
pwg, bwg sync.WaitGroup
pwg, bwg *sync.WaitGroup
operateState chan func(*pState)
interceptIO chan func(io.Writer)
done <-chan struct{}
ctx context.Context
cancel func()
}
type queueBar struct {
state *bState
bar *Bar
}
// pState holds bars in its priorityQueue, it gets passed to (*Progress).serve monitor goroutine.
type pState struct {
ctx context.Context
hm heapManager
renderReq chan time.Time
idCount int
@@ -43,15 +48,17 @@ type pState struct {
hmQueueLen int
reqWidth int
refreshRate time.Duration
popCompleted bool
autoRefresh bool
delayRC <-chan struct{}
manualRC <-chan interface{}
shutdownNotifier chan<- interface{}
queueBars map[*Bar]*Bar
shutdownNotifier chan interface{}
handOverBarHeap chan<- []*Bar
queueBars map[*Bar]*queueBar
output io.Writer
debugOut io.Writer
uwg *sync.WaitGroup
popCompleted bool
autoRefresh bool
rmOnComplete bool
}
// New creates new Progress container instance. It's not possible to
@@ -68,13 +75,13 @@ func NewWithContext(ctx context.Context, options ...ContainerOption) *Progress {
ctx = context.Background()
}
ctx, cancel := context.WithCancel(ctx)
s := &pState{
ctx: ctx,
hmQueueLen: defaultHmQueueLength,
renderReq: make(chan time.Time),
popPriority: math.MinInt32,
refreshRate: defaultRefreshRate,
queueBars: make(map[*Bar]*Bar),
queueBars: make(map[*Bar]*queueBar),
output: os.Stdout,
debugOut: io.Discard,
}
@@ -85,33 +92,40 @@ func NewWithContext(ctx context.Context, options ...ContainerOption) *Progress {
}
}
if s.shutdownNotifier == nil {
s.shutdownNotifier = make(chan interface{})
}
s.hm = make(heapManager, s.hmQueueLen)
p := &Progress{
uwg: s.uwg,
pwg: new(sync.WaitGroup),
bwg: new(sync.WaitGroup),
operateState: make(chan func(*pState)),
interceptIO: make(chan func(io.Writer)),
ctx: ctx,
cancel: cancel,
}
cw := cwriter.New(s.output)
if s.manualRC != nil {
switch {
case s.manualRC != nil:
done := make(chan struct{})
p.done = done
s.autoRefresh = false
go s.manualRefreshListener(done)
} else if cw.IsTerminal() || s.autoRefresh {
go s.manualRefreshListener(ctx, done)
case s.autoRefresh || cw.IsTerminal():
done := make(chan struct{})
p.done = done
s.autoRefresh = true
go s.autoRefreshListener(done)
} else {
go s.autoRefreshListener(ctx, done)
default:
p.done = ctx.Done()
s.autoRefresh = false
}
p.pwg.Add(1)
go s.hm.run(s.shutdownNotifier)
p.pwg.Add(2)
go s.hm.run(p.pwg, s.shutdownNotifier, s.handOverBarHeap)
go p.serve(s, cw)
return p
}
@@ -155,17 +169,18 @@ func (p *Progress) Add(total int64, filler BarFiller, options ...BarOption) (*Ba
} else if f, ok := filler.(BarFillerFunc); ok && f == nil {
filler = NopStyle().Build()
}
ch := make(chan *Bar)
ch := make(chan *Bar, 1)
select {
case p.operateState <- func(ps *pState) {
p.bwg.Add(1)
bs := ps.makeBarState(total, filler, options...)
bar := newBar(ps.ctx, p, bs)
bar := p.makeBar(bs.priority)
if bs.waitBar != nil {
ps.queueBars[bs.waitBar] = bar
ps.queueBars[bs.waitBar] = &queueBar{bs, bar}
} else {
go bar.serve(bs)
ps.hm.push(bar, true)
}
ps.idCount++
ch <- bar
}:
return <-ch, nil
@@ -174,18 +189,35 @@ func (p *Progress) Add(total int64, filler BarFiller, options ...BarOption) (*Ba
}
}
func (p *Progress) traverseBars(cb func(b *Bar) bool) {
req := make(iterRequest, 1)
func (p *Progress) makeBar(priority int) *Bar {
ctx, cancel := context.WithCancel(p.ctx)
bar := &Bar{
priority: priority,
frameCh: make(chan *renderFrame, 1),
operateState: make(chan func(*bState)),
bsOk: make(chan struct{}),
container: p,
ctx: ctx,
cancel: cancel,
}
return bar
}
// blocks until iteration is done
func (p *Progress) iterateBars(yield func(*Bar) bool) (ok bool) {
seqCh := make(chan iter.Seq[*Bar], 1)
select {
case p.operateState <- func(s *pState) {
s.hm.iter(req)
}:
for b := range <-req {
if !cb(b) {
case p.operateState <- func(s *pState) { s.hm.iter(seqCh) }:
for b := range <-seqCh {
if !yield(b) {
break
}
}
return true
case <-p.done:
return false
}
}
@@ -211,7 +243,7 @@ func (p *Progress) Write(b []byte) (int, error) {
n int
err error
}
ch := make(chan result)
ch := make(chan result, 1)
select {
case p.interceptIO <- func(w io.Writer) {
n, err := w.Write(b)
@@ -227,10 +259,6 @@ func (p *Progress) Write(b []byte) (int, error) {
// Wait waits for all bars to complete and finally shutdowns container. After
// this method has been called, there is no way to reuse `*Progress` instance.
func (p *Progress) Wait() {
// wait for user wg, if any
if p.uwg != nil {
p.uwg.Wait()
}
p.bwg.Wait()
p.Shutdown()
}
@@ -245,60 +273,51 @@ func (p *Progress) Shutdown() {
func (p *Progress) serve(s *pState, cw *cwriter.Writer) {
defer func() {
if s.uwg != nil {
s.uwg.Wait() // wait for user wg
}
p.bwg.Wait()
close(s.hm)
close(s.shutdownNotifier)
p.pwg.Done()
}()
var err error
var w *cwriter.Writer
renderReq := s.renderReq
operateState := p.operateState
interceptIO := p.interceptIO
var dw *cwriter.Writer
if s.delayRC != nil {
w = cwriter.New(io.Discard)
dw = cwriter.New(io.Discard)
} else {
w, cw = cw, nil
dw = cw
}
for {
select {
case <-s.delayRC:
w, cw = cw, nil
dw = cw
s.delayRC = nil
case op := <-operateState:
case op := <-p.operateState:
op(s)
case fn := <-interceptIO:
fn(w)
case <-renderReq:
err = s.render(w)
case fn := <-p.interceptIO:
fn(cw)
case <-s.renderReq:
err := s.render(dw)
if err != nil {
p.cancel()
// (*pState).(autoRefreshListener|manualRefreshListener) may block
// if not launching following short lived goroutine
go func() {
for {
select {
case <-s.renderReq:
case <-p.done:
return
}
// if not depleting s.renderReq
for {
select {
case <-s.renderReq:
case <-p.done:
_, _ = fmt.Fprintln(s.debugOut, err.Error())
return
}
}()
p.cancel() // cancel all bars
renderReq = nil
operateState = nil
interceptIO = nil
}
}
case <-p.done:
if err != nil {
_, _ = fmt.Fprintln(s.debugOut, err.Error())
} else if s.autoRefresh {
update := make(chan bool)
for i := 0; i == 0 || <-update; i++ {
if err := s.render(w); err != nil {
_, _ = fmt.Fprintln(s.debugOut, err.Error())
break
}
s.hm.state(update)
if s.autoRefresh && s.rmOnComplete {
if err := s.render(cw); err != nil {
_, _ = fmt.Fprintln(s.debugOut, err.Error())
return
}
}
return
@@ -306,21 +325,21 @@ func (p *Progress) serve(s *pState, cw *cwriter.Writer) {
}
}
func (s *pState) autoRefreshListener(done chan struct{}) {
func (s *pState) autoRefreshListener(ctx context.Context, done chan struct{}) {
ticker := time.NewTicker(s.refreshRate)
defer ticker.Stop()
for {
select {
case t := <-ticker.C:
s.renderReq <- t
case <-s.ctx.Done():
case <-ctx.Done():
close(done)
return
}
}
}
func (s *pState) manualRefreshListener(done chan struct{}) {
func (s *pState) manualRefreshListener(ctx context.Context, done chan struct{}) {
for {
select {
case x := <-s.manualRC:
@@ -329,7 +348,7 @@ func (s *pState) manualRefreshListener(done chan struct{}) {
} else {
s.renderReq <- time.Now()
}
case <-s.ctx.Done():
case <-ctx.Done():
close(done)
return
}
@@ -337,9 +356,7 @@ func (s *pState) manualRefreshListener(done chan struct{}) {
}
func (s *pState) render(cw *cwriter.Writer) (err error) {
req := make(iterRequest, 2)
s.hm.sync()
s.hm.iter(req, req)
var width, height int
if cw.IsTerminal() {
@@ -352,18 +369,16 @@ func (s *pState) render(cw *cwriter.Writer) (err error) {
height = width
}
for b := range <-req {
go b.render(width)
}
return s.flush(cw, height, <-req)
return s.flush(cw, height, s.hm.render(width))
}
func (s *pState) flush(cw *cwriter.Writer, height int, iter <-chan *Bar) error {
func (s *pState) flush(cw *cwriter.Writer, height int, seq iter.Seq[*Bar]) error {
var total, popCount int
rows := make([][]io.Reader, 0, cap(iter))
var rows [][]io.Reader
for b := range iter {
s.rmOnComplete = false
for b := range seq {
frame := <-b.frameCh
if frame.err != nil {
b.cancel()
@@ -383,18 +398,23 @@ func (s *pState) flush(cw *cwriter.Writer, height int, iter <-chan *Bar) error {
switch frame.shutdown {
case 1:
b.cancel()
if qb, ok := s.queueBars[b]; ok {
delete(s.queueBars, b)
qb.priority = b.priority
s.hm.push(qb, true)
} else if s.popCompleted && !frame.noPop {
b.priority = s.popPriority
s.popPriority++
s.hm.push(b, false)
} else if !frame.rmOnComplete {
s.hm.push(b, false)
qb.bar.priority = b.priority
go qb.bar.serve(qb.state)
s.hm.push(qb.bar, true)
} else {
switch {
case s.popCompleted && !frame.noPop:
b.priority = s.popPriority
s.popPriority++
fallthrough
case !frame.rmOnComplete:
s.hm.push(b, false)
}
s.rmOnComplete = s.rmOnComplete || frame.rmOnComplete
}
b.cancel()
case 2:
if s.popCompleted && !frame.noPop {
popCount += len(frame.rows) - discarded
@@ -418,22 +438,20 @@ func (s *pState) flush(cw *cwriter.Writer, height int, iter <-chan *Bar) error {
return cw.Flush(total - popCount)
}
func (s pState) makeBarState(total int64, filler BarFiller, options ...BarOption) *bState {
func (s *pState) makeBarState(total int64, filler BarFiller, options ...BarOption) *bState {
bs := &bState{
id: s.idCount,
priority: s.idCount,
reqWidth: s.reqWidth,
total: total,
filler: filler,
renderReq: s.renderReq,
autoRefresh: s.autoRefresh,
extender: func(_ decor.Statistics, rows ...io.Reader) ([]io.Reader, error) {
return rows, nil
},
id: s.idCount,
priority: s.idCount,
reqWidth: s.reqWidth,
total: total,
filler: filler,
renderReq: s.renderReq,
triggerComplete: total > 0,
autoRefresh: s.autoRefresh,
}
if total > 0 {
bs.triggerComplete = true
bs.extender = func(_ decor.Statistics, rows ...io.Reader) ([]io.Reader, error) {
return rows, nil
}
for _, opt := range options {
@@ -450,9 +468,10 @@ func (s pState) makeBarState(total int64, filler BarFiller, options ...BarOption
}
}
bs.buffers[0] = bytes.NewBuffer(make([]byte, 0, 128)) // prepend
bs.buffers[1] = bytes.NewBuffer(make([]byte, 0, 128)) // append
bs.buffers[2] = bytes.NewBuffer(make([]byte, 0, 256)) // filler
bs.buffers[0] = bytes.NewBuffer(make([]byte, 0, 256)) // filler
bs.buffers[1] = bytes.NewBuffer(make([]byte, 0, 128)) // prepend
bs.buffers[2] = bytes.NewBuffer(make([]byte, 0, 128)) // append
s.idCount++
return bs
}

View File

@@ -9,7 +9,6 @@ import (
"net"
"os"
"path/filepath"
"reflect"
"slices"
"strconv"
"time"
@@ -81,7 +80,7 @@ func (n *netavarkNetwork) NetworkUpdate(name string, options types.NetworkUpdate
networkDNSServersAfter = append(networkDNSServersAfter, options.AddDNSServers...)
networkDNSServersAfter = sliceRemoveDuplicates(networkDNSServersAfter)
network.NetworkDNSServers = networkDNSServersAfter
if reflect.DeepEqual(networkDNSServersBefore, networkDNSServersAfter) {
if slices.Equal(networkDNSServersBefore, networkDNSServersAfter) {
return nil
}
err = n.commitNetwork(network)

View File

@@ -1,4 +1,4 @@
package version
// Version is the version of the build.
const Version = "0.67.0-dev"
const Version = "0.68.0-dev"

View File

@@ -6,7 +6,7 @@ const (
// VersionMajor is for an API incompatible changes
VersionMajor = 5
// VersionMinor is for functionality in a backwards-compatible manner
VersionMinor = 39
VersionMinor = 40
// VersionPatch is for backwards-compatible bug fixes
VersionPatch = 0

View File

@@ -1 +1 @@
1.62.0-dev
1.63.0-dev

View File

@@ -8,7 +8,6 @@ import (
"maps"
"os"
"path/filepath"
"reflect"
"sort"
"strings"
"syscall"
@@ -337,7 +336,7 @@ func (info *FileInfo) addChanges(oldInfo *FileInfo, changes *[]Change) {
if statDifferent(oldStat, oldInfo, newStat, info) ||
!bytes.Equal(oldChild.capability, newChild.capability) ||
oldChild.target != newChild.target ||
!reflect.DeepEqual(oldChild.xattrs, newChild.xattrs) {
!maps.Equal(oldChild.xattrs, newChild.xattrs) {
change := Change{
Path: newChild.path(),
Kind: ChangeModify,

View File

@@ -9,9 +9,11 @@ import (
"hash"
"io"
"io/fs"
"maps"
"os"
"path/filepath"
"reflect"
"slices"
"sort"
"strings"
"sync"
@@ -606,11 +608,7 @@ func maybeDoIDRemap(manifest []fileMetadata, options *archive.TarOptions) error
}
func mapToSlice(inputMap map[uint32]struct{}) []uint32 {
var out []uint32
for value := range inputMap {
out = append(out, value)
}
return out
return slices.Collect(maps.Keys(inputMap))
}
func collectIDs(entries []fileMetadata) ([]uint32, []uint32) {

View File

@@ -399,9 +399,9 @@ func (l *LockFile) lock(lType rawfilelock.LockType) {
if err := rawfilelock.LockFile(l.fd, lType); err != nil {
panic(err)
}
l.lockType = lType
l.locked = true
}
l.lockType = lType
l.locked = true
l.counter++
}
@@ -442,9 +442,9 @@ func (l *LockFile) tryLock(lType rawfilelock.LockType) error {
rwMutexUnlocker()
return err
}
l.lockType = lType
l.locked = true
}
l.lockType = lType
l.locked = true
l.counter++
return nil
}

26
vendor/modules.txt vendored
View File

@@ -49,13 +49,9 @@ github.com/checkpoint-restore/go-criu/v7/stats
# github.com/chzyer/readline v1.5.1
## explicit; go 1.15
github.com/chzyer/readline
# github.com/clipperhouse/stringish v0.1.1
## explicit; go 1.18
github.com/clipperhouse/stringish
# github.com/clipperhouse/uax29/v2 v2.3.0
# github.com/clipperhouse/uax29/v2 v2.7.0
## explicit; go 1.18
github.com/clipperhouse/uax29/v2/graphemes
github.com/clipperhouse/uax29/v2/internal/iterators
# github.com/containerd/errdefs v1.0.0
## explicit; go 1.20
github.com/containerd/errdefs
@@ -91,7 +87,7 @@ github.com/containernetworking/cni/pkg/version
# github.com/containernetworking/plugins v1.9.0
## explicit; go 1.24.2
github.com/containernetworking/plugins/pkg/ns
# github.com/containers/buildah v1.42.1-0.20260126144005-964d45f717ce
# github.com/containers/buildah v1.42.1-0.20260216192603-e473f9d26ec6
## explicit; go 1.24.6
github.com/containers/buildah
github.com/containers/buildah/bind
@@ -106,7 +102,8 @@ github.com/containers/buildah/internal/metadata
github.com/containers/buildah/internal/mkcw
github.com/containers/buildah/internal/mkcw/types
github.com/containers/buildah/internal/open
github.com/containers/buildah/internal/parse
github.com/containers/buildah/internal/output
github.com/containers/buildah/internal/parsevolume
github.com/containers/buildah/internal/pty
github.com/containers/buildah/internal/sanitize
github.com/containers/buildah/internal/sbom
@@ -122,6 +119,7 @@ github.com/containers/buildah/pkg/jail
github.com/containers/buildah/pkg/overlay
github.com/containers/buildah/pkg/parse
github.com/containers/buildah/pkg/rusage
github.com/containers/buildah/pkg/sourcepolicy
github.com/containers/buildah/pkg/sshagent
github.com/containers/buildah/pkg/util
github.com/containers/buildah/pkg/volumes
@@ -392,7 +390,7 @@ github.com/json-iterator/go
# github.com/kevinburke/ssh_config v1.5.0
## explicit; go 1.18
github.com/kevinburke/ssh_config
# github.com/klauspost/compress v1.18.3
# github.com/klauspost/compress v1.18.4
## explicit; go 1.23
github.com/klauspost/compress
github.com/klauspost/compress/flate
@@ -420,7 +418,7 @@ github.com/lufia/plan9stats
github.com/manifoldco/promptui
github.com/manifoldco/promptui/list
github.com/manifoldco/promptui/screenbuf
# github.com/mattn/go-runewidth v0.0.19
# github.com/mattn/go-runewidth v0.0.20
## explicit; go 1.20
github.com/mattn/go-runewidth
# github.com/mattn/go-shellwords v1.0.12
@@ -717,7 +715,7 @@ github.com/ulikunitz/xz/lzma
github.com/vbatts/tar-split/archive/tar
github.com/vbatts/tar-split/tar/asm
github.com/vbatts/tar-split/tar/storage
# github.com/vbauerster/mpb/v8 v8.11.3
# github.com/vbauerster/mpb/v8 v8.12.0
## explicit; go 1.24.0
github.com/vbauerster/mpb/v8
github.com/vbauerster/mpb/v8/cwriter
@@ -771,7 +769,7 @@ go.opentelemetry.io/otel/trace
go.opentelemetry.io/otel/trace/embedded
go.opentelemetry.io/otel/trace/internal/telemetry
go.opentelemetry.io/otel/trace/noop
# go.podman.io/common v0.66.2-0.20260204175822-4e7127fdc31f
# go.podman.io/common v0.67.1-0.20260217150212-026c3538f3d1
## explicit; go 1.24.6
go.podman.io/common/internal
go.podman.io/common/internal/attributedstring
@@ -839,7 +837,7 @@ go.podman.io/common/pkg/umask
go.podman.io/common/pkg/util
go.podman.io/common/pkg/version
go.podman.io/common/version
# go.podman.io/image/v5 v5.38.1-0.20260204175822-4e7127fdc31f
# go.podman.io/image/v5 v5.39.2-0.20260217150212-026c3538f3d1
## explicit; go 1.24.6
go.podman.io/image/v5/copy
go.podman.io/image/v5/directory
@@ -914,7 +912,7 @@ go.podman.io/image/v5/transports
go.podman.io/image/v5/transports/alltransports
go.podman.io/image/v5/types
go.podman.io/image/v5/version
# go.podman.io/storage v1.61.1-0.20260204175822-4e7127fdc31f
# go.podman.io/storage v1.62.1-0.20260217150212-026c3538f3d1
## explicit; go 1.24.0
go.podman.io/storage
go.podman.io/storage/drivers
@@ -1019,7 +1017,7 @@ golang.org/x/net/internal/socks
golang.org/x/net/internal/timeseries
golang.org/x/net/proxy
golang.org/x/net/trace
# golang.org/x/oauth2 v0.34.0
# golang.org/x/oauth2 v0.35.0
## explicit; go 1.24.0
golang.org/x/oauth2
golang.org/x/oauth2/internal