mirror of
https://github.com/mikefarah/yq.git
synced 2026-08-24 16:39:58 +08:00
Compare commits
42
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
00efd3cc0e | ||
|
|
4ee76205b3 | ||
|
|
7620a727b5 | ||
|
|
6f9201a0ca | ||
|
|
5b19ddbcd0 | ||
|
|
1ac2a3d96a | ||
|
|
4a12c3f908 | ||
|
|
41377138ef | ||
|
|
ae5cf9ff04 | ||
|
|
fe449b956a | ||
|
|
8f3291d316 | ||
|
|
2861815f71 | ||
|
|
fcb79822dd | ||
|
|
e9acb9b734 | ||
|
|
83b282c413 | ||
|
|
54fa4324ea | ||
|
|
ee6c30dac2 | ||
|
|
722c9aa16c | ||
|
|
702dd16048 | ||
|
|
d1dff4661b | ||
|
|
cb97935554 | ||
|
|
cfe2eee7e6 | ||
|
|
1a433d1035 | ||
|
|
1c0d8b9da9 | ||
|
|
0110a3cea8 | ||
|
|
54482d44b3 | ||
|
|
33f3351c01 | ||
|
|
6cb656ced0 | ||
|
|
ecc43d7c9e | ||
|
|
1deec5e450 | ||
|
|
ff45fad14c | ||
|
|
6679d3c02b | ||
|
|
54a7fc8f0c | ||
|
|
0d3ab07928 | ||
|
|
d93987a93a | ||
|
|
751d8ad57b | ||
|
|
6dd681a7c0 | ||
|
|
fc7c337d8f | ||
|
|
e969dd789f | ||
|
|
dc4b4ea1df | ||
|
|
602586d8fd | ||
|
|
9a0335abb2 |
@@ -20,6 +20,8 @@ on:
|
||||
schedule:
|
||||
- cron: '24 3 * * 1'
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
@@ -38,11 +40,11 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
|
||||
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@@ -53,7 +55,7 @@ jobs:
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
|
||||
uses: github/codeql-action/autobuild@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
@@ -67,4 +69,4 @@ jobs:
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
|
||||
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
|
||||
|
||||
@@ -7,23 +7,28 @@ on:
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
publishDocker:
|
||||
environment: dockerhub
|
||||
env:
|
||||
IMAGE_NAME: mikefarah/yq
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
|
||||
uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0
|
||||
with:
|
||||
platforms: all
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
|
||||
with:
|
||||
version: latest
|
||||
|
||||
@@ -31,13 +36,13 @@ jobs:
|
||||
run: echo ${{ steps.buildx.outputs.platforms }} && docker version
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
- name: Get dependencies
|
||||
run: |
|
||||
|
||||
@@ -5,6 +5,8 @@ on:
|
||||
- 'v4.*'
|
||||
- 'draft-*'
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
publishGitRelease:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -12,7 +14,7 @@ jobs:
|
||||
contents: write
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||
with:
|
||||
go-version: '^1.20'
|
||||
@@ -51,11 +53,11 @@ jobs:
|
||||
|
||||
- name: Sign checksums
|
||||
run: |
|
||||
cosign sign-blob --yes --output-bundle build/checksums.bundle build/checksums
|
||||
cosign sign-blob --yes --output-bundle build/checksums-bsd.bundle build/checksums-bsd
|
||||
cosign sign-blob --yes --bundle build/checksums.bundle build/checksums
|
||||
cosign sign-blob --yes --bundle build/checksums-bsd.bundle build/checksums-bsd
|
||||
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2.6.2
|
||||
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
|
||||
with:
|
||||
files: build/*
|
||||
draft: true
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
# This workflow uses actions that are not certified by GitHub. They are provided
|
||||
# by a third-party and are governed by separate terms of service, privacy
|
||||
# policy, and support documentation.
|
||||
|
||||
name: Scorecard supply-chain security
|
||||
on:
|
||||
# For Branch-Protection check. Only the default branch is supported. See
|
||||
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection
|
||||
branch_protection_rule:
|
||||
# To guarantee Maintained check is occasionally updated. See
|
||||
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained
|
||||
schedule:
|
||||
- cron: '39 7 * * 2'
|
||||
push:
|
||||
branches: [ "master" ]
|
||||
|
||||
# Declare default permissions as read only.
|
||||
permissions: read-all
|
||||
|
||||
jobs:
|
||||
analysis:
|
||||
name: Scorecard analysis
|
||||
runs-on: ubuntu-latest
|
||||
# `publish_results: true` only works when run from the default branch. conditional can be removed if disabled.
|
||||
if: github.event.repository.default_branch == github.ref_name || github.event_name == 'pull_request'
|
||||
permissions:
|
||||
# Needed to upload the results to code-scanning dashboard.
|
||||
security-events: write
|
||||
# Needed to publish results and get a badge (see publish_results below).
|
||||
id-token: write
|
||||
# Uncomment the permissions below if installing in a private repository.
|
||||
# contents: read
|
||||
# actions: read
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: "Run analysis"
|
||||
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
|
||||
with:
|
||||
results_file: results.sarif
|
||||
results_format: sarif
|
||||
# (Optional) "write" PAT token. Uncomment the `repo_token` line below if:
|
||||
# - you want to enable the Branch-Protection check on a *public* repository, or
|
||||
# - you are installing Scorecard on a *private* repository
|
||||
# To create the PAT, follow the steps in https://github.com/ossf/scorecard-action?tab=readme-ov-file#authentication-with-fine-grained-pat-optional.
|
||||
# repo_token: ${{ secrets.SCORECARD_TOKEN }}
|
||||
|
||||
# Public repositories:
|
||||
# - Publish results to OpenSSF REST API for easy access by consumers
|
||||
# - Allows the repository to include the Scorecard badge.
|
||||
# - See https://github.com/ossf/scorecard-action#publishing-results.
|
||||
# For private repositories:
|
||||
# - `publish_results` will always be set to `false`, regardless
|
||||
# of the value entered here.
|
||||
publish_results: true
|
||||
|
||||
# (Optional) Uncomment file_mode if you have a .gitattributes with files marked export-ignore
|
||||
# file_mode: git
|
||||
|
||||
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
||||
# format to the repository Actions tab.
|
||||
- name: "Upload artifact"
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: SARIF file
|
||||
path: results.sarif
|
||||
retention-days: 5
|
||||
|
||||
# Upload the results to GitHub's code scanning dashboard (optional).
|
||||
# Commenting out will disable upload of results to your repo's Code Scanning dashboard
|
||||
- name: "Upload to code-scanning"
|
||||
uses: github/codeql-action/upload-sarif@v4
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
@@ -7,12 +7,16 @@ on:
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
buildSnap:
|
||||
environment: snap
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: snapcore/action-build@3bdaa03e1ba6bf59a65f84a751d943d549a54e79 # v1.3.0
|
||||
id: build
|
||||
env:
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- name: Get test
|
||||
id: get_value
|
||||
uses: mikefarah/yq@master
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
# yq — agent instructions
|
||||
|
||||
See [agents.md](./agents.md) for contributor workflow (formatting, testing patterns, adding encoders/decoders).
|
||||
|
||||
Always run the spellcheck before raising a PR:
|
||||
|
||||
```bash
|
||||
bash scripts/spelling.sh
|
||||
```
|
||||
|
||||
This is also included in the full CI pipeline via `make local test`.
|
||||
|
||||
## Cursor Cloud specific instructions
|
||||
|
||||
### Overview
|
||||
|
||||
**yq** is a Go CLI for querying and transforming YAML, JSON, XML, INI, and other structured formats. There are no long-running services — development is build-and-test against a local `./yq` binary.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- **Go ≥ 1.25** (see `go.mod`)
|
||||
- **Node.js** (for `npx cspell` spelling checks in the full `make test` pipeline)
|
||||
- **Bash** (acceptance tests)
|
||||
- **Docker/Podman** is optional; use `make local <target>` to run natively when containers are unavailable
|
||||
|
||||
### PATH
|
||||
|
||||
After `scripts/devtools.sh`, add Go tool binaries to PATH:
|
||||
|
||||
```bash
|
||||
export PATH="$HOME/go/bin:$PATH"
|
||||
```
|
||||
|
||||
`golangci-lint` installs to `$HOME/go/bin`; `gosec` installs to `./bin/gosec` in the repo root.
|
||||
|
||||
### Common commands (local, no Docker)
|
||||
|
||||
| Task | Command |
|
||||
|------|---------|
|
||||
| Install dev tools | `bash scripts/devtools.sh` |
|
||||
| Vendor dependencies | `make local vendor` |
|
||||
| Build binary | `go build -o yq .` or `make local build` |
|
||||
| Format | `make local format` |
|
||||
| Lint | `make local check` |
|
||||
| Unit tests | `make local test` or `bash scripts/test.sh` |
|
||||
| Acceptance (E2E) | `bash scripts/acceptance.sh` (requires `./yq` built first) |
|
||||
|
||||
`make local build` runs the full CI chain (format → spelling → gosec → lint → unit tests → build → acceptance). For a faster loop, build with `go build -o yq .` and run `bash scripts/acceptance.sh`.
|
||||
|
||||
### Caveats
|
||||
|
||||
- **`make` without `local`** tries Docker/Podman (`Dockerfile.dev`). In Cloud Agent VMs without Docker, always prefix with `make local`.
|
||||
- **Spelling step** uses `npx cspell` and may download cspell on first run (network required).
|
||||
- **`make local test` / `scripts/check.sh`** require `golangci-lint` on PATH (`devtools.sh`).
|
||||
- As of the current tree, `pkg/yqlib/ini_test.go` calls `NewINIDecoder()` without required `INIPreferences`, which breaks unit-test compilation in `make local check` / `make local test`. The **binary still builds** (`go build -o yq .`) and **all acceptance tests pass** (`bash scripts/acceptance.sh`).
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.26.2@sha256:2a2b4b5791cea8ae09caecba7bad0bd9631def96e5fe362e4a5e67009fe4ae61 AS builder
|
||||
FROM golang:1.26.4@sha256:68cb6d68bed024785b69195b89af7ac7a444f27791435f98647edff595aa0479 AS builder
|
||||
|
||||
WORKDIR /go/src/mikefarah/yq
|
||||
|
||||
@@ -10,7 +10,7 @@ RUN ./scripts/acceptance.sh
|
||||
|
||||
# Choose alpine as a base image to make this useful for CI, as many
|
||||
# CI tools expect an interactive shell inside the container
|
||||
FROM alpine:3@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659 AS production
|
||||
FROM alpine:3@sha256:5b10f432ef3da1b8d4c7eb6c487f2f5a8f096bc91145e68878dd4a5019afde11 AS production
|
||||
LABEL maintainer="Mike Farah <mikefarah@users.noreply.github.com>"
|
||||
|
||||
COPY --from=builder /go/src/mikefarah/yq/yq /usr/bin/yq
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.26.2@sha256:2a2b4b5791cea8ae09caecba7bad0bd9631def96e5fe362e4a5e67009fe4ae61
|
||||
FROM golang:1.26.4@sha256:68cb6d68bed024785b69195b89af7ac7a444f27791435f98647edff595aa0479
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y npm && \
|
||||
|
||||
@@ -156,6 +156,8 @@ yq -P -oy sample.json
|
||||
rootCmd.PersistentFlags().BoolVar(&yqlib.ConfiguredLuaPreferences.UnquotedKeys, "lua-unquoted", yqlib.ConfiguredLuaPreferences.UnquotedKeys, "output unquoted string keys (e.g. {foo=\"bar\"})")
|
||||
rootCmd.PersistentFlags().BoolVar(&yqlib.ConfiguredLuaPreferences.Globals, "lua-globals", yqlib.ConfiguredLuaPreferences.Globals, "output keys as top-level global variables")
|
||||
|
||||
rootCmd.PersistentFlags().BoolVar(&yqlib.ConfiguredINIPreferences.PreserveSurroundedQuote, "ini-preserve-quotes", yqlib.ConfiguredINIPreferences.PreserveSurroundedQuote, "preserve surrounding quotes on INI values during round-trip")
|
||||
|
||||
rootCmd.PersistentFlags().StringVar(&yqlib.ConfiguredPropertiesPreferences.KeyValueSeparator, "properties-separator", yqlib.ConfiguredPropertiesPreferences.KeyValueSeparator, "separator to use between keys and values")
|
||||
rootCmd.PersistentFlags().BoolVar(&yqlib.ConfiguredPropertiesPreferences.UseArrayBrackets, "properties-array-brackets", yqlib.ConfiguredPropertiesPreferences.UseArrayBrackets, "use [x] in array paths (e.g. for SpringBoot)")
|
||||
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ var (
|
||||
GitDescribe string
|
||||
|
||||
// Version is main version number that is being run at the moment.
|
||||
Version = "v4.52.5"
|
||||
Version = "v4.53.2"
|
||||
|
||||
// VersionPrerelease is a pre-release marker for the version. If this is "" (empty string)
|
||||
// then it means that it is a final release. Otherwise, this is a pre-release
|
||||
|
||||
@@ -13,16 +13,16 @@ require (
|
||||
github.com/hashicorp/hcl/v2 v2.24.0
|
||||
github.com/jinzhu/copier v0.4.0
|
||||
github.com/magiconair/properties v1.8.10
|
||||
github.com/pelletier/go-toml/v2 v2.3.0
|
||||
github.com/pelletier/go-toml/v2 v2.3.1
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e
|
||||
github.com/spf13/cobra v1.10.2
|
||||
github.com/spf13/pflag v1.0.10
|
||||
github.com/yuin/gopher-lua v1.1.2
|
||||
github.com/zclconf/go-cty v1.18.0
|
||||
github.com/zclconf/go-cty v1.18.1
|
||||
go.yaml.in/yaml/v4 v4.0.0-rc.4
|
||||
golang.org/x/mod v0.34.0
|
||||
golang.org/x/net v0.52.0
|
||||
golang.org/x/text v0.35.0
|
||||
golang.org/x/mod v0.36.0
|
||||
golang.org/x/net v0.55.0
|
||||
golang.org/x/text v0.37.0
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -34,8 +34,8 @@ require (
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
|
||||
golang.org/x/sync v0.20.0 // indirect
|
||||
golang.org/x/sys v0.42.0 // indirect
|
||||
golang.org/x/tools v0.42.0 // indirect
|
||||
golang.org/x/sys v0.45.0 // indirect
|
||||
golang.org/x/tools v0.44.0 // indirect
|
||||
)
|
||||
|
||||
go 1.25.0
|
||||
|
||||
@@ -46,8 +46,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
|
||||
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
|
||||
github.com/pelletier/go-toml/v2 v2.3.0 h1:k59bC/lIZREW0/iVaQR8nDHxVq8OVlIzYCOJf421CaM=
|
||||
github.com/pelletier/go-toml/v2 v2.3.0/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pelletier/go-toml/v2 v2.3.1 h1:MYEvvGnQjeNkRF1qUuGolNtNExTDwct51yp7olPtrEc=
|
||||
github.com/pelletier/go-toml/v2 v2.3.1/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e h1:aoZm08cpOy4WuID//EZDgcC4zIxODThtZNPirFr42+A=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
@@ -63,26 +63,26 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/yuin/gopher-lua v1.1.2 h1:yF/FjE3hD65tBbt0VXLE13HWS9h34fdzJmrWRXwobGA=
|
||||
github.com/yuin/gopher-lua v1.1.2/go.mod h1:7aRmXIWl37SqRf0koeyylBEzJ+aPt8A+mmkQ4f1ntR8=
|
||||
github.com/zclconf/go-cty v1.18.0 h1:pJ8+HNI4gFoyRNqVE37wWbJWVw43BZczFo7KUoRczaA=
|
||||
github.com/zclconf/go-cty v1.18.0/go.mod h1:qpnV6EDNgC1sns/AleL1fvatHw72j+S+nS+MJ+T2CSg=
|
||||
github.com/zclconf/go-cty v1.18.1 h1:yEGE8M4iIZlyKQURZNb2SnEyZlZHUcBCnx6KF81KuwM=
|
||||
github.com/zclconf/go-cty v1.18.1/go.mod h1:qpnV6EDNgC1sns/AleL1fvatHw72j+S+nS+MJ+T2CSg=
|
||||
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo=
|
||||
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
go.yaml.in/yaml/v4 v4.0.0-rc.4 h1:UP4+v6fFrBIb1l934bDl//mmnoIZEDK0idg1+AIvX5U=
|
||||
go.yaml.in/yaml/v4 v4.0.0-rc.4/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
|
||||
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
|
||||
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
|
||||
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
||||
golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=
|
||||
golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=
|
||||
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
|
||||
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
|
||||
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
|
||||
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
|
||||
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
|
||||
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
|
||||
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
||||
golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
|
||||
golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
@@ -7,6 +7,9 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
)
|
||||
@@ -140,6 +143,12 @@ func (o *CandidateNode) MarshalJSON() ([]byte, error) {
|
||||
return buf.Bytes(), err
|
||||
case ScalarNode:
|
||||
log.Debugf("MarshalJSON ScalarNode")
|
||||
if o.guessTagFromCustomType() == "!!float" {
|
||||
if raw, ok := jsonFloatLiteral(o.Value); ok {
|
||||
buf.WriteString(raw)
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
}
|
||||
value, err := o.GetValueRep()
|
||||
if err != nil {
|
||||
return buf.Bytes(), err
|
||||
@@ -177,3 +186,85 @@ func (o *CandidateNode) MarshalJSON() ([]byte, error) {
|
||||
return buf.Bytes(), err
|
||||
}
|
||||
}
|
||||
|
||||
// jsonFloatLiteral returns a JSON-shaped representation of a YAML !!float scalar
|
||||
// value, preserving the original textual form (e.g. "50.0" stays "50.0") whenever
|
||||
// possible. The second return value is false when the value cannot be safely
|
||||
// rendered as a JSON number (e.g. ".inf", ".nan", or anything that parses to a
|
||||
// non-finite float); callers should fall back to the normal encoding path in
|
||||
// that case, which preserves the existing behaviour for those inputs.
|
||||
func jsonFloatLiteral(raw string) (string, bool) {
|
||||
if raw == "" {
|
||||
return "", false
|
||||
}
|
||||
f, err := strconv.ParseFloat(raw, 64)
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
if math.IsInf(f, 0) || math.IsNaN(f) {
|
||||
return "", false
|
||||
}
|
||||
if isJSONNumberLiteral(raw) {
|
||||
return raw, true
|
||||
}
|
||||
formatted := strconv.FormatFloat(f, 'f', -1, 64)
|
||||
if !strings.ContainsAny(formatted, ".eE") {
|
||||
formatted += ".0"
|
||||
}
|
||||
return formatted, true
|
||||
}
|
||||
|
||||
// isJSONNumberLiteral reports whether s is already a valid JSON number literal
|
||||
// representing a fractional value (i.e. contains a "." or an exponent), so it
|
||||
// can be emitted verbatim without round-tripping through a float64.
|
||||
func isJSONNumberLiteral(s string) bool {
|
||||
if s == "" {
|
||||
return false
|
||||
}
|
||||
i := 0
|
||||
if s[i] == '-' {
|
||||
i++
|
||||
if i == len(s) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
// integer part: 0 or [1-9][0-9]*
|
||||
if s[i] == '0' {
|
||||
i++
|
||||
} else if s[i] >= '1' && s[i] <= '9' {
|
||||
for i < len(s) && s[i] >= '0' && s[i] <= '9' {
|
||||
i++
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
hasFraction := false
|
||||
if i < len(s) && s[i] == '.' {
|
||||
hasFraction = true
|
||||
i++
|
||||
if i == len(s) || s[i] < '0' || s[i] > '9' {
|
||||
return false
|
||||
}
|
||||
for i < len(s) && s[i] >= '0' && s[i] <= '9' {
|
||||
i++
|
||||
}
|
||||
}
|
||||
hasExponent := false
|
||||
if i < len(s) && (s[i] == 'e' || s[i] == 'E') {
|
||||
hasExponent = true
|
||||
i++
|
||||
if i < len(s) && (s[i] == '+' || s[i] == '-') {
|
||||
i++
|
||||
}
|
||||
if i == len(s) || s[i] < '0' || s[i] > '9' {
|
||||
return false
|
||||
}
|
||||
for i < len(s) && s[i] >= '0' && s[i] <= '9' {
|
||||
i++
|
||||
}
|
||||
}
|
||||
if i != len(s) {
|
||||
return false
|
||||
}
|
||||
return hasFraction || hasExponent
|
||||
}
|
||||
|
||||
@@ -12,17 +12,20 @@ import (
|
||||
type iniDecoder struct {
|
||||
reader io.Reader
|
||||
finished bool // Flag to signal completion of processing
|
||||
prefs INIPreferences
|
||||
}
|
||||
|
||||
func NewINIDecoder() Decoder {
|
||||
func NewINIDecoder(prefs INIPreferences) Decoder {
|
||||
return &iniDecoder{
|
||||
finished: false, // Initialise the flag as false
|
||||
prefs: prefs,
|
||||
}
|
||||
}
|
||||
|
||||
func (dec *iniDecoder) Init(reader io.Reader) error {
|
||||
// Store the reader for use in Decode
|
||||
dec.reader = reader
|
||||
dec.finished = false
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -39,7 +42,10 @@ func (dec *iniDecoder) Decode() (*CandidateNode, error) {
|
||||
}
|
||||
|
||||
// Parse the INI content
|
||||
cfg, err := ini.Load(content)
|
||||
loadOpts := ini.LoadOptions{
|
||||
PreserveSurroundedQuote: dec.prefs.PreserveSurroundedQuote,
|
||||
}
|
||||
cfg, err := ini.LoadSources(loadOpts, content)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse INI content: %w", err)
|
||||
}
|
||||
|
||||
@@ -16,10 +16,11 @@ type propertiesDecoder struct {
|
||||
reader io.Reader
|
||||
finished bool
|
||||
d DataTreeNavigator
|
||||
prefs PropertiesPreferences
|
||||
}
|
||||
|
||||
func NewPropertiesDecoder() Decoder {
|
||||
return &propertiesDecoder{d: NewDataTreeNavigator(), finished: false}
|
||||
return &propertiesDecoder{d: NewDataTreeNavigator(), finished: false, prefs: ConfiguredPropertiesPreferences.Copy()}
|
||||
}
|
||||
|
||||
func (dec *propertiesDecoder) Init(reader io.Reader) error {
|
||||
@@ -28,20 +29,56 @@ func (dec *propertiesDecoder) Init(reader io.Reader) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func parsePropKey(key string) []interface{} {
|
||||
func parsePropKey(key string, prefs PropertiesPreferences) []interface{} {
|
||||
pathStrArray := strings.Split(key, ".")
|
||||
path := make([]interface{}, len(pathStrArray))
|
||||
for i, pathStr := range pathStrArray {
|
||||
num, err := strconv.ParseInt(pathStr, 10, 32)
|
||||
if err == nil {
|
||||
path[i] = num
|
||||
} else {
|
||||
path[i] = pathStr
|
||||
}
|
||||
path := make([]interface{}, 0, len(pathStrArray))
|
||||
for _, pathStr := range pathStrArray {
|
||||
path = appendPropKeySegment(path, pathStr, prefs.UseArrayBrackets)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func appendPropKeySegment(path []interface{}, segment string, useArrayBrackets bool) []interface{} {
|
||||
if useArrayBrackets && strings.Contains(segment, "[") {
|
||||
bracketPath, ok := parsePropKeyArrayBracketSegment(segment)
|
||||
if ok {
|
||||
return append(path, bracketPath...)
|
||||
}
|
||||
}
|
||||
|
||||
num, err := strconv.ParseInt(segment, 10, 32)
|
||||
if err == nil {
|
||||
return append(path, num)
|
||||
}
|
||||
return append(path, segment)
|
||||
}
|
||||
|
||||
func parsePropKeyArrayBracketSegment(segment string) ([]interface{}, bool) {
|
||||
path := []interface{}{}
|
||||
bracketIndex := strings.Index(segment, "[")
|
||||
if bracketIndex > 0 {
|
||||
path = append(path, segment[:bracketIndex])
|
||||
}
|
||||
|
||||
remaining := segment[bracketIndex:]
|
||||
for remaining != "" {
|
||||
if !strings.HasPrefix(remaining, "[") {
|
||||
return nil, false
|
||||
}
|
||||
closingBracket := strings.Index(remaining, "]")
|
||||
if closingBracket < 0 {
|
||||
return nil, false
|
||||
}
|
||||
arrayIndex, err := strconv.ParseInt(remaining[1:closingBracket], 10, 32)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
path = append(path, arrayIndex)
|
||||
remaining = remaining[closingBracket+1:]
|
||||
}
|
||||
return path, true
|
||||
}
|
||||
|
||||
func (dec *propertiesDecoder) processComment(c string) string {
|
||||
if c == "" {
|
||||
return ""
|
||||
@@ -75,7 +112,7 @@ func (dec *propertiesDecoder) applyPropertyComments(context Context, path []inte
|
||||
|
||||
func (dec *propertiesDecoder) applyProperty(context Context, properties *properties.Properties, key string) error {
|
||||
value, _ := properties.Get(key)
|
||||
path := parsePropKey(key)
|
||||
path := parsePropKey(key, dec.prefs)
|
||||
|
||||
propertyComments := properties.GetComments(key)
|
||||
if len(propertyComments) > 0 {
|
||||
|
||||
@@ -125,6 +125,22 @@ will output
|
||||
{"whatever":"cat"}
|
||||
```
|
||||
|
||||
## Encode json: preserve floats with trailing zero
|
||||
Whole-number floats keep their decimal point so downstream consumers see a JSON number with a fractional part (matches Go's encoding/json, Python's json, and jq).
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
percentiles: [50.0, 95.0, 99.0, 99.9]
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq -o=json -I=0 '.' sample.yml
|
||||
```
|
||||
will output
|
||||
```json
|
||||
{"percentiles":[50.0,95.0,99.0,99.9]}
|
||||
```
|
||||
|
||||
## Roundtrip JSON Lines / NDJSON
|
||||
Given a sample.json file of:
|
||||
```json
|
||||
|
||||
+75
-53
@@ -132,12 +132,23 @@ func (te *tomlEncoder) encodeRootMapping(w io.Writer, node *CandidateNode) error
|
||||
}
|
||||
}
|
||||
|
||||
// Preserve existing order by iterating Content
|
||||
for i := 0; i < len(node.Content); i += 2 {
|
||||
keyNode := node.Content[i]
|
||||
valNode := node.Content[i+1]
|
||||
if err := te.encodeTopLevelEntry(w, []string{keyNode.Value}, valNode); err != nil {
|
||||
return err
|
||||
if isTomlAttribute(valNode) {
|
||||
if err := te.encodeTopLevelEntry(w, []string{keyNode.Value}, valNode); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < len(node.Content); i += 2 {
|
||||
keyNode := node.Content[i]
|
||||
valNode := node.Content[i+1]
|
||||
if !isTomlAttribute(valNode) {
|
||||
if err := te.encodeTopLevelEntry(w, []string{keyNode.Value}, valNode); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -170,8 +181,13 @@ func (te *tomlEncoder) encodeTopLevelEntry(w io.Writer, path []string, node *Can
|
||||
if allMaps {
|
||||
key := path[len(path)-1]
|
||||
quotedKey := tomlKey(key)
|
||||
if te.wroteRootAttr {
|
||||
if _, err := w.Write([]byte("\n")); err != nil {
|
||||
return err
|
||||
}
|
||||
te.wroteRootAttr = false
|
||||
}
|
||||
for _, it := range node.Content {
|
||||
// [[key]] then body
|
||||
if _, err := w.Write([]byte("[[" + quotedKey + "]]\n")); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -184,9 +200,12 @@ func (te *tomlEncoder) encodeTopLevelEntry(w io.Writer, path []string, node *Can
|
||||
// Regular array attribute
|
||||
return te.writeArrayAttribute(w, path[len(path)-1], node)
|
||||
case MappingNode:
|
||||
// Use inline table syntax for nodes explicitly marked as TOML inline tables
|
||||
// or YAML flow mappings. All other mappings become readable TOML table sections.
|
||||
if node.EncodeHint == EncodeHintInline || node.Style&FlowStyle != 0 {
|
||||
// Use inline table syntax only for nodes explicitly marked as TOML inline tables.
|
||||
// YAML flow-style mappings are not treated as inline tables; the FlowStyle attribute
|
||||
// is a YAML-specific rendering hint and should not affect TOML output. This ensures
|
||||
// that auto-detected JSON input (parsed as YAML flow style) produces readable table
|
||||
// sections, consistent with explicitly parsed JSON input.
|
||||
if node.EncodeHint == EncodeHintInline {
|
||||
return te.writeInlineTableAttribute(w, path[len(path)-1], node)
|
||||
}
|
||||
return te.encodeSeparateMapping(w, path, node)
|
||||
@@ -195,7 +214,30 @@ func (te *tomlEncoder) encodeTopLevelEntry(w io.Writer, path []string, node *Can
|
||||
}
|
||||
}
|
||||
|
||||
func isTomlArrayOfTables(seq *CandidateNode) bool {
|
||||
if len(seq.Content) == 0 {
|
||||
return false
|
||||
}
|
||||
for _, it := range seq.Content {
|
||||
if it.Kind != MappingNode || it.EncodeHint == EncodeHintInline {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func isTomlAttribute(node *CandidateNode) bool {
|
||||
if node.Kind == ScalarNode {
|
||||
return true
|
||||
}
|
||||
return node.Kind == SequenceNode && !isTomlArrayOfTables(node)
|
||||
}
|
||||
|
||||
func (te *tomlEncoder) writeAttribute(w io.Writer, key string, value *CandidateNode) error {
|
||||
if value.Tag == "!!null" {
|
||||
return nil
|
||||
}
|
||||
|
||||
te.wroteRootAttr = true // Mark that we wrote a root attribute
|
||||
|
||||
// Write head comment before the attribute
|
||||
@@ -391,6 +433,9 @@ func (te *tomlEncoder) mappingToInlineTable(m *CandidateNode) (string, error) {
|
||||
v := m.Content[i+1]
|
||||
switch v.Kind {
|
||||
case ScalarNode:
|
||||
if v.Tag == "!!null" {
|
||||
continue
|
||||
}
|
||||
parts = append(parts, fmt.Sprintf("%s = %s", tomlKey(k), te.formatScalar(v)))
|
||||
case SequenceNode:
|
||||
// inline array in inline table
|
||||
@@ -453,24 +498,16 @@ func (te *tomlEncoder) encodeSeparateMapping(w io.Writer, path []string, m *Cand
|
||||
hasAttrs := false
|
||||
for i := 0; i < len(m.Content); i += 2 {
|
||||
v := m.Content[i+1]
|
||||
if v.Kind == ScalarNode {
|
||||
if v.Kind == ScalarNode && v.Tag != "!!null" {
|
||||
hasAttrs = true
|
||||
break
|
||||
}
|
||||
if v.Kind == MappingNode && (v.EncodeHint == EncodeHintInline || v.Style&FlowStyle != 0) {
|
||||
if v.Kind == MappingNode && v.EncodeHint == EncodeHintInline {
|
||||
hasAttrs = true
|
||||
break
|
||||
}
|
||||
if v.Kind == SequenceNode {
|
||||
// Check if it's NOT an array of tables
|
||||
allMaps := true
|
||||
for _, it := range v.Content {
|
||||
if it.Kind != MappingNode {
|
||||
allMaps = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if !allMaps {
|
||||
if !isTomlArrayOfTables(v) {
|
||||
hasAttrs = true
|
||||
break
|
||||
}
|
||||
@@ -500,15 +537,14 @@ func (te *tomlEncoder) encodeSeparateMapping(w io.Writer, path []string, m *Cand
|
||||
}
|
||||
case SequenceNode:
|
||||
// If sequence of maps, emit [[path.k]] per element
|
||||
allMaps := true
|
||||
for _, it := range v.Content {
|
||||
if it.Kind != MappingNode {
|
||||
allMaps = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if allMaps {
|
||||
if isTomlArrayOfTables(v) {
|
||||
key := tomlDottedKey(append(append([]string{}, path...), k))
|
||||
if te.wroteRootAttr {
|
||||
if _, err := w.Write([]byte("\n")); err != nil {
|
||||
return err
|
||||
}
|
||||
te.wroteRootAttr = false
|
||||
}
|
||||
for _, it := range v.Content {
|
||||
if _, err := w.Write([]byte("[[" + key + "]]\n")); err != nil {
|
||||
return err
|
||||
@@ -535,7 +571,7 @@ func (te *tomlEncoder) encodeSeparateMapping(w io.Writer, path []string, m *Cand
|
||||
|
||||
// encodeMappingBodyWithPath encodes attributes and nested arrays of tables using full dotted path context
|
||||
func (te *tomlEncoder) encodeMappingBodyWithPath(w io.Writer, path []string, m *CandidateNode) error {
|
||||
// First, attributes (scalars and non-map arrays)
|
||||
// First, attributes (scalars, inline mappings, and non-map arrays)
|
||||
for i := 0; i < len(m.Content); i += 2 {
|
||||
k := m.Content[i].Value
|
||||
v := m.Content[i+1]
|
||||
@@ -544,15 +580,14 @@ func (te *tomlEncoder) encodeMappingBodyWithPath(w io.Writer, path []string, m *
|
||||
if err := te.writeAttribute(w, k, v); err != nil {
|
||||
return err
|
||||
}
|
||||
case SequenceNode:
|
||||
allMaps := true
|
||||
for _, it := range v.Content {
|
||||
if it.Kind != MappingNode {
|
||||
allMaps = false
|
||||
break
|
||||
case MappingNode:
|
||||
if v.EncodeHint == EncodeHintInline {
|
||||
if err := te.writeInlineTableAttribute(w, k, v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if !allMaps {
|
||||
case SequenceNode:
|
||||
if !isTomlArrayOfTables(v) {
|
||||
if err := te.writeArrayAttribute(w, k, v); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -565,14 +600,7 @@ func (te *tomlEncoder) encodeMappingBodyWithPath(w io.Writer, path []string, m *
|
||||
k := m.Content[i].Value
|
||||
v := m.Content[i+1]
|
||||
if v.Kind == SequenceNode {
|
||||
allMaps := true
|
||||
for _, it := range v.Content {
|
||||
if it.Kind != MappingNode {
|
||||
allMaps = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if allMaps {
|
||||
if isTomlArrayOfTables(v) {
|
||||
dotted := tomlDottedKey(append(append([]string{}, path...), k))
|
||||
for _, it := range v.Content {
|
||||
if _, err := w.Write([]byte("[[" + dotted + "]]\n")); err != nil {
|
||||
@@ -586,21 +614,15 @@ func (te *tomlEncoder) encodeMappingBodyWithPath(w io.Writer, path []string, m *
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, child mappings: inline-hint or flow-style ones become inline table attributes,
|
||||
// Finally, child mappings: inline-hint ones were emitted above as attributes,
|
||||
// while all others are emitted as separate sub-table sections.
|
||||
for i := 0; i < len(m.Content); i += 2 {
|
||||
k := m.Content[i].Value
|
||||
v := m.Content[i+1]
|
||||
if v.Kind == MappingNode {
|
||||
if v.EncodeHint == EncodeHintInline || v.Style&FlowStyle != 0 {
|
||||
if err := te.writeInlineTableAttribute(w, k, v); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
subPath := append(append([]string{}, path...), k)
|
||||
if err := te.encodeSeparateMapping(w, subPath, v); err != nil {
|
||||
return err
|
||||
}
|
||||
if v.Kind == MappingNode && v.EncodeHint != EncodeHintInline {
|
||||
subPath := append(append([]string{}, path...), k)
|
||||
if err := te.encodeSeparateMapping(w, subPath, v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -90,7 +90,7 @@ var LuaFormat = &Format{"lua", []string{"l"},
|
||||
|
||||
var INIFormat = &Format{"ini", []string{"i"},
|
||||
func() Encoder { return NewINIEncoder() },
|
||||
func() Decoder { return NewINIDecoder() },
|
||||
func() Decoder { return NewINIDecoder(ConfiguredINIPreferences) },
|
||||
}
|
||||
|
||||
var Formats = []*Format{
|
||||
|
||||
+6
-3
@@ -1,18 +1,21 @@
|
||||
package yqlib
|
||||
|
||||
type INIPreferences struct {
|
||||
ColorsEnabled bool
|
||||
ColorsEnabled bool
|
||||
PreserveSurroundedQuote bool
|
||||
}
|
||||
|
||||
func NewDefaultINIPreferences() INIPreferences {
|
||||
return INIPreferences{
|
||||
ColorsEnabled: false,
|
||||
ColorsEnabled: false,
|
||||
PreserveSurroundedQuote: false,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *INIPreferences) Copy() INIPreferences {
|
||||
return INIPreferences{
|
||||
ColorsEnabled: p.ColorsEnabled,
|
||||
ColorsEnabled: p.ColorsEnabled,
|
||||
PreserveSurroundedQuote: p.PreserveSurroundedQuote,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+63
-5
@@ -5,6 +5,7 @@ package yqlib
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/mikefarah/yq/v4/test"
|
||||
@@ -22,6 +23,16 @@ const expectedSimpleINIYaml = `section:
|
||||
key: value
|
||||
`
|
||||
|
||||
const quotedINIInput = `[section]
|
||||
color_theme = "Default"
|
||||
theme_background = "False"
|
||||
`
|
||||
|
||||
const expectedQuotedINIOutput = `[section]
|
||||
color_theme = "Default"
|
||||
theme_background = "False"
|
||||
`
|
||||
|
||||
var iniScenarios = []formatScenario{
|
||||
{
|
||||
description: "Parse INI: simple",
|
||||
@@ -49,6 +60,22 @@ var iniScenarios = []formatScenario{
|
||||
},
|
||||
}
|
||||
|
||||
// iniPreserveQuotesPrefs returns INIPreferences with PreserveSurroundedQuote enabled.
|
||||
func iniPreserveQuotesPrefs() INIPreferences {
|
||||
prefs := NewDefaultINIPreferences()
|
||||
prefs.PreserveSurroundedQuote = true
|
||||
return prefs
|
||||
}
|
||||
|
||||
var iniPreserveQuotesScenarios = []formatScenario{
|
||||
{
|
||||
description: "Roundtrip INI: preserve quotes",
|
||||
input: quotedINIInput,
|
||||
expected: expectedQuotedINIOutput,
|
||||
scenarioType: "roundtrip",
|
||||
},
|
||||
}
|
||||
|
||||
func documentRoundtripINIScenario(w *bufio.Writer, s formatScenario) {
|
||||
writeOrPanic(w, fmt.Sprintf("## %v\n", s.description))
|
||||
|
||||
@@ -70,7 +97,7 @@ func documentRoundtripINIScenario(w *bufio.Writer, s formatScenario) {
|
||||
}
|
||||
|
||||
writeOrPanic(w, "will output\n")
|
||||
writeOrPanic(w, fmt.Sprintf("```ini\n%v```\n\n", mustProcessFormatScenario(s, NewINIDecoder(), NewINIEncoder())))
|
||||
writeOrPanic(w, fmt.Sprintf("```ini\n%v```\n\n", mustProcessFormatScenario(s, NewINIDecoder(NewDefaultINIPreferences()), NewINIEncoder())))
|
||||
}
|
||||
|
||||
func documentDecodeINIScenario(w *bufio.Writer, s formatScenario) {
|
||||
@@ -94,7 +121,7 @@ func documentDecodeINIScenario(w *bufio.Writer, s formatScenario) {
|
||||
}
|
||||
|
||||
writeOrPanic(w, "will output\n")
|
||||
writeOrPanic(w, fmt.Sprintf("```yaml\n%v```\n\n", mustProcessFormatScenario(s, NewINIDecoder(), NewYamlEncoder(ConfiguredYamlPreferences))))
|
||||
writeOrPanic(w, fmt.Sprintf("```yaml\n%v```\n\n", mustProcessFormatScenario(s, NewINIDecoder(NewDefaultINIPreferences()), NewYamlEncoder(ConfiguredYamlPreferences))))
|
||||
}
|
||||
|
||||
func testINIScenario(t *testing.T, s formatScenario) {
|
||||
@@ -102,11 +129,11 @@ func testINIScenario(t *testing.T, s formatScenario) {
|
||||
case "encode":
|
||||
test.AssertResultWithContext(t, s.expected, mustProcessFormatScenario(s, NewYamlDecoder(ConfiguredYamlPreferences), NewINIEncoder()), s.description)
|
||||
case "decode":
|
||||
test.AssertResultWithContext(t, s.expected, mustProcessFormatScenario(s, NewINIDecoder(), NewYamlEncoder(ConfiguredYamlPreferences)), s.description)
|
||||
test.AssertResultWithContext(t, s.expected, mustProcessFormatScenario(s, NewINIDecoder(NewDefaultINIPreferences()), NewYamlEncoder(ConfiguredYamlPreferences)), s.description)
|
||||
case "roundtrip":
|
||||
test.AssertResultWithContext(t, s.expected, mustProcessFormatScenario(s, NewINIDecoder(), NewINIEncoder()), s.description)
|
||||
test.AssertResultWithContext(t, s.expected, mustProcessFormatScenario(s, NewINIDecoder(NewDefaultINIPreferences()), NewINIEncoder()), s.description)
|
||||
case "decode-error":
|
||||
result, err := processFormatScenario(s, NewINIDecoder(), NewINIEncoder())
|
||||
result, err := processFormatScenario(s, NewINIDecoder(NewDefaultINIPreferences()), NewINIEncoder())
|
||||
if err == nil {
|
||||
t.Errorf("Expected error '%v' but it worked: %v", s.expectedError, result)
|
||||
} else {
|
||||
@@ -175,6 +202,21 @@ func documentDecodeErrorINIScenario(w *bufio.Writer, s formatScenario) {
|
||||
writeOrPanic(w, fmt.Sprintf("```\n%v\n```\n\n", s.expectedError))
|
||||
}
|
||||
|
||||
func TestINIDecoderInitResetsFinished(t *testing.T) {
|
||||
decoder := NewINIDecoder()
|
||||
firstDocuments, err := readDocuments(strings.NewReader("[first]\nkey = value\n"), "first.ini", 0, decoder)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
test.AssertResult(t, 1, firstDocuments.Len())
|
||||
|
||||
secondDocuments, err := readDocuments(strings.NewReader("[second]\nkey = value\n"), "second.ini", 1, decoder)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
test.AssertResult(t, 1, secondDocuments.Len())
|
||||
}
|
||||
|
||||
func TestINIScenarios(t *testing.T) {
|
||||
for _, tt := range iniScenarios {
|
||||
testINIScenario(t, tt)
|
||||
@@ -185,3 +227,19 @@ func TestINIScenarios(t *testing.T) {
|
||||
}
|
||||
documentScenarios(t, "usage", "convert", genericScenarios, documentINIScenario)
|
||||
}
|
||||
|
||||
func testINIPreserveQuotesScenario(t *testing.T, s formatScenario) {
|
||||
prefs := iniPreserveQuotesPrefs()
|
||||
switch s.scenarioType {
|
||||
case "roundtrip":
|
||||
test.AssertResultWithContext(t, s.expected, mustProcessFormatScenario(s, NewINIDecoder(prefs), NewINIEncoder()), s.description)
|
||||
default:
|
||||
panic(fmt.Sprintf("unhandled scenario type %q", s.scenarioType))
|
||||
}
|
||||
}
|
||||
|
||||
func TestINIPreserveQuotesScenarios(t *testing.T) {
|
||||
for _, tt := range iniPreserveQuotesScenarios {
|
||||
testINIPreserveQuotesScenario(t, tt)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,6 +220,54 @@ var jsonScenarios = []formatScenario{
|
||||
expected: "{\"stuff\":\"cool\"}\n{\"whatever\":\"cat\"}\n",
|
||||
scenarioType: "encode",
|
||||
},
|
||||
{
|
||||
description: "Encode json: preserve floats with trailing zero",
|
||||
subdescription: "Whole-number floats keep their decimal point so downstream consumers see a JSON number with a fractional part (matches Go's encoding/json, Python's json, and jq).",
|
||||
input: `percentiles: [50.0, 95.0, 99.0, 99.9]`,
|
||||
indent: 0,
|
||||
expected: "{\"percentiles\":[50.0,95.0,99.0,99.9]}\n",
|
||||
scenarioType: "encode",
|
||||
},
|
||||
{
|
||||
description: "Encode json: ints stay ints",
|
||||
skipDoc: true,
|
||||
input: `a: 50`,
|
||||
indent: 0,
|
||||
expected: "{\"a\":50}\n",
|
||||
scenarioType: "encode",
|
||||
},
|
||||
{
|
||||
description: "Encode json: !!float tagged whole number gets .0",
|
||||
skipDoc: true,
|
||||
input: `a: !!float 5`,
|
||||
indent: 0,
|
||||
expected: "{\"a\":5.0}\n",
|
||||
scenarioType: "encode",
|
||||
},
|
||||
{
|
||||
description: "Encode json: scientific notation float preserved",
|
||||
skipDoc: true,
|
||||
input: `a: 1.5e-3`,
|
||||
indent: 0,
|
||||
expected: "{\"a\":1.5e-3}\n",
|
||||
scenarioType: "encode",
|
||||
},
|
||||
{
|
||||
description: "Encode json: negative float preserved",
|
||||
skipDoc: true,
|
||||
input: `a: -7.0`,
|
||||
indent: 0,
|
||||
expected: "{\"a\":-7.0}\n",
|
||||
scenarioType: "encode",
|
||||
},
|
||||
{
|
||||
description: "Encode json: mixed int and float array",
|
||||
skipDoc: true,
|
||||
input: `a: [1, 2.0, 3, 4.5]`,
|
||||
indent: 0,
|
||||
expected: "{\"a\":[1,2.0,3,4.5]}\n",
|
||||
scenarioType: "encode",
|
||||
},
|
||||
{
|
||||
description: "Roundtrip JSON Lines / NDJSON",
|
||||
input: sampleNdJson,
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
package yqlib
|
||||
|
||||
func NewINIDecoder() Decoder {
|
||||
func NewINIDecoder(prefs INIPreferences) Decoder {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -170,6 +170,10 @@ func fixedReconstructAliasedMap(node *CandidateNode) error {
|
||||
if mergeNodeSeq.Kind == AliasNode {
|
||||
mergeNodeSeq = mergeNodeSeq.Alias
|
||||
}
|
||||
mergeNodeSeq = mergeNodeSeq.Copy()
|
||||
if err := explodeNode(mergeNodeSeq, Context{}); err != nil {
|
||||
return err
|
||||
}
|
||||
if mergeNodeSeq.Kind != MappingNode {
|
||||
return fmt.Errorf("can only use merge anchors with maps (!!map) or sequences (!!seq) of maps, but got sequence containing %v", mergeNodeSeq.Tag)
|
||||
}
|
||||
@@ -179,12 +183,7 @@ func fixedReconstructAliasedMap(node *CandidateNode) error {
|
||||
})
|
||||
|
||||
for _, item := range itemsToAdd {
|
||||
// copy to ensure exploding doesn't modify the original node
|
||||
itemCopy := item.Copy()
|
||||
if err := explodeNode(itemCopy, Context{}); err != nil {
|
||||
return err
|
||||
}
|
||||
newContent = append(newContent, itemCopy)
|
||||
newContent = append(newContent, item.Copy())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,6 +198,15 @@ var fixedAnchorOperatorScenarios = []expressionScenario{
|
||||
"D0, P[], (!!map)::{a: 42}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
description: "Nested merge anchor with inline map",
|
||||
document: `{<<: {<<: {a: 42}}}`,
|
||||
expression: `explode(.)`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::{a: 42}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
description: "Merge anchor with sequence with inline map",
|
||||
|
||||
@@ -2,6 +2,7 @@ package yqlib
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/bits"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
@@ -707,11 +708,13 @@ var multiplyOperatorScenarios = []expressionScenario{
|
||||
expectedError: "result of repeating string (1 bytes) by 99999999 would exceed 10485760 bytes",
|
||||
},
|
||||
{
|
||||
// The size guard must not overflow: len * count can wrap to
|
||||
// a negative or small value on 64-bit, bypassing the check.
|
||||
// Pick a count whose product with len("ab") overflows int on
|
||||
// any architecture: 2^30 on 32-bit, 2^62 on 64-bit. Doubling
|
||||
// either yields MaxInt+1, which wraps to MinInt and bypasses
|
||||
// a naive len*count guard.
|
||||
skipDoc: true,
|
||||
expression: `"ab" * 4611686018427387904`,
|
||||
expectedError: "result of repeating string (2 bytes) by 4611686018427387904 would exceed 10485760 bytes",
|
||||
expression: fmt.Sprintf(`"ab" * %d`, 1<<(bits.UintSize-2)),
|
||||
expectedError: fmt.Sprintf("result of repeating string (2 bytes) by %d would exceed 10485760 bytes", 1<<(bits.UintSize-2)),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -202,6 +202,37 @@ var propertyScenarios = []formatScenario{
|
||||
expected: expectedDecodedYaml,
|
||||
scenarioType: "decode",
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
description: "Decode properties with array brackets",
|
||||
input: `user.credentials[0].username=user1
|
||||
user.credentials[0].password=$2b$08$...
|
||||
user.credentials[1].username=user2
|
||||
user.credentials[1].password=$2b$08$...
|
||||
user.credentials[2].username=user3
|
||||
user.credentials[2].password=$2b$10$...`,
|
||||
expected: `user:
|
||||
credentials:
|
||||
- username: user1
|
||||
password: $2b$08$...
|
||||
- username: user2
|
||||
password: $2b$08$...
|
||||
- username: user3
|
||||
password: $2b$10$...
|
||||
`,
|
||||
scenarioType: "decode-array-brackets",
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
description: "Decode properties with nested array brackets",
|
||||
input: `user.clowns[0][1] = "cool"`,
|
||||
expected: `user:
|
||||
clowns:
|
||||
- - null
|
||||
- '"cool"'
|
||||
`,
|
||||
scenarioType: "decode-array-brackets",
|
||||
},
|
||||
|
||||
{
|
||||
skipDoc: true,
|
||||
@@ -442,6 +473,12 @@ func TestPropertyScenarios(t *testing.T) {
|
||||
test.AssertResultWithContext(t, s.expected, mustProcessFormatScenario(s, NewYamlDecoder(ConfiguredYamlPreferences), NewPropertiesEncoder(ConfiguredPropertiesPreferences)), s.description)
|
||||
case "decode":
|
||||
test.AssertResultWithContext(t, s.expected, mustProcessFormatScenario(s, NewPropertiesDecoder(), NewYamlEncoder(ConfiguredYamlPreferences)), s.description)
|
||||
case "decode-array-brackets":
|
||||
previousPreferences := ConfiguredPropertiesPreferences.Copy()
|
||||
ConfiguredPropertiesPreferences.UseArrayBrackets = true
|
||||
actual := mustProcessFormatScenario(s, NewPropertiesDecoder(), NewYamlEncoder(ConfiguredYamlPreferences))
|
||||
ConfiguredPropertiesPreferences = previousPreferences
|
||||
test.AssertResultWithContext(t, s.expected, actual, s.description)
|
||||
case "encode-wrapped":
|
||||
prefs := ConfiguredPropertiesPreferences.Copy()
|
||||
prefs.UnwrapScalar = false
|
||||
|
||||
+155
-3
@@ -209,6 +209,50 @@ address = "12 cat st"
|
||||
var rtEmptyArray = `A = []
|
||||
`
|
||||
|
||||
var rtEmptyArrayInTable = `[features]
|
||||
my-feature = []
|
||||
`
|
||||
|
||||
var rtMixedEmptyArraysInTable = `[features]
|
||||
my-other-feature = []
|
||||
my-feature = ["my-other-feature"]
|
||||
`
|
||||
|
||||
var yamlEmptyArrayInTable = `features:
|
||||
my-feature: []
|
||||
`
|
||||
|
||||
var expectedTomlEmptyArrayInTable = `[features]
|
||||
my-feature = []
|
||||
`
|
||||
|
||||
var yamlMixedEmptyArraysInTable = `features:
|
||||
my-other-feature: []
|
||||
my-feature:
|
||||
- my-other-feature
|
||||
`
|
||||
|
||||
var expectedTomlMixedEmptyArraysInTable = `[features]
|
||||
my-other-feature = []
|
||||
my-feature = ["my-other-feature"]
|
||||
`
|
||||
|
||||
var issue2688SampleToml = `[project]
|
||||
name = "some-project"
|
||||
version = "0.5.1"
|
||||
authors = [{name = "Author", email = "author@example.com"}]
|
||||
license = { file = "LICENSE" }
|
||||
readme = "README.md"
|
||||
`
|
||||
|
||||
var issue2688SampleExpected = `[project]
|
||||
name = "some-project"
|
||||
version = "0.5.2"
|
||||
authors = [{ name = "Author", email = "author@example.com" }]
|
||||
license = { file = "LICENSE" }
|
||||
readme = "README.md"
|
||||
`
|
||||
|
||||
var rtSampleTable = `var = "x"
|
||||
|
||||
[owner.contact]
|
||||
@@ -563,6 +607,36 @@ var tomlScenarios = []formatScenario{
|
||||
expected: rtEmptyArray,
|
||||
scenarioType: "roundtrip",
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
description: "Issue #2674: roundtrip empty array in table",
|
||||
input: rtEmptyArrayInTable,
|
||||
expression: ".",
|
||||
expected: rtEmptyArrayInTable,
|
||||
scenarioType: "roundtrip",
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
description: "Issue #2674: roundtrip mixed empty and non-empty arrays in table",
|
||||
input: rtMixedEmptyArraysInTable,
|
||||
expression: ".",
|
||||
expected: rtMixedEmptyArraysInTable,
|
||||
scenarioType: "roundtrip",
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
description: "Issue #2674: encode empty array in table",
|
||||
input: yamlEmptyArrayInTable,
|
||||
expected: expectedTomlEmptyArrayInTable,
|
||||
scenarioType: "encode",
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
description: "Issue #2674: encode mixed empty and non-empty arrays in table",
|
||||
input: yamlMixedEmptyArraysInTable,
|
||||
expected: expectedTomlMixedEmptyArraysInTable,
|
||||
scenarioType: "encode",
|
||||
},
|
||||
{
|
||||
description: "Roundtrip: sample table",
|
||||
input: rtSampleTable,
|
||||
@@ -649,11 +723,33 @@ var tomlScenarios = []formatScenario{
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
description: "Encode: YAML flow mapping stays inline",
|
||||
description: "Encode: YAML flow mapping produces table section (same as block mapping)",
|
||||
input: "arg: {hello: foo}\n",
|
||||
expected: "arg = { hello = \"foo\" }\n",
|
||||
expected: "[arg]\nhello = \"foo\"\n",
|
||||
scenarioType: "encode",
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
description: "Issue: JSON auto-detected via YAML decoder produces table sections",
|
||||
input: `{"arg":{"hello": "foo"}}`,
|
||||
expected: "[arg]\nhello = \"foo\"\n",
|
||||
scenarioType: "encode",
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
description: "Issue: JSON via JSON decoder produces table sections",
|
||||
input: `{"arg":{"hello": "foo"}}`,
|
||||
expected: "[arg]\nhello = \"foo\"\n",
|
||||
scenarioType: "encode-json",
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
description: "Issue 2688: inline table arrays do not change following table scope",
|
||||
input: issue2688SampleToml,
|
||||
expression: `.project.version = "0.5.2"`,
|
||||
expected: issue2688SampleExpected,
|
||||
scenarioType: "roundtrip",
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
description: "Roundtrip: key with special characters in inline table",
|
||||
@@ -695,6 +791,8 @@ func testTomlScenario(t *testing.T, s formatScenario) {
|
||||
test.AssertResultWithContext(t, s.expected, mustProcessFormatScenario(s, NewTomlDecoder(), NewTomlEncoder()), s.description)
|
||||
case "encode":
|
||||
test.AssertResultWithContext(t, s.expected, mustProcessFormatScenario(s, NewYamlDecoder(ConfiguredYamlPreferences), NewTomlEncoder()), s.description)
|
||||
case "encode-json":
|
||||
test.AssertResultWithContext(t, s.expected, mustProcessFormatScenario(s, NewJSONDecoder(), NewTomlEncoder()), s.description)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -775,7 +873,7 @@ func documentTomlScenario(_ *testing.T, w *bufio.Writer, i interface{}) {
|
||||
documentTomlDecodeScenario(w, s)
|
||||
case "roundtrip":
|
||||
documentTomlRoundtripScenario(w, s)
|
||||
case "encode":
|
||||
case "encode", "encode-json":
|
||||
documentTomlEncodeScenario(w, s)
|
||||
|
||||
default:
|
||||
@@ -794,6 +892,60 @@ func TestTomlScenarios(t *testing.T) {
|
||||
documentScenarios(t, "usage", "toml", genericScenarios, documentTomlScenario)
|
||||
}
|
||||
|
||||
func TestTomlEncodeJsonKeepsRootArrayBeforeTables(t *testing.T) {
|
||||
scenario := formatScenario{
|
||||
description: "Encode: JSON root array stays outside later tables",
|
||||
input: `{
|
||||
"_source": {
|
||||
"cookie": [
|
||||
{
|
||||
"Domain": "",
|
||||
"Expires": "0001-01-01T00:00:00Z",
|
||||
"HttpOnly": false,
|
||||
"MaxAge": 0,
|
||||
"Name": "name",
|
||||
"Path": "",
|
||||
"Raw": "",
|
||||
"RawExpires": "",
|
||||
"SameSite": 0,
|
||||
"Secure": false,
|
||||
"Unparsed": null,
|
||||
"Value": "value"
|
||||
}
|
||||
]
|
||||
},
|
||||
"highlight": {
|
||||
"did": [
|
||||
"did"
|
||||
]
|
||||
},
|
||||
"sort": [
|
||||
1
|
||||
]
|
||||
}`,
|
||||
expected: `sort = [1]
|
||||
|
||||
[[_source.cookie]]
|
||||
Domain = ""
|
||||
Expires = "0001-01-01T00:00:00Z"
|
||||
HttpOnly = false
|
||||
MaxAge = 0
|
||||
Name = "name"
|
||||
Path = ""
|
||||
Raw = ""
|
||||
RawExpires = ""
|
||||
SameSite = 0
|
||||
Secure = false
|
||||
Value = "value"
|
||||
|
||||
[highlight]
|
||||
did = ["did"]
|
||||
`,
|
||||
}
|
||||
|
||||
test.AssertResultWithContext(t, scenario.expected, mustProcessFormatScenario(scenario, NewJSONDecoder(), NewTomlEncoder()), scenario.description)
|
||||
}
|
||||
|
||||
// TestTomlColourization tests that colourization correctly distinguishes
|
||||
// between table section headers and inline arrays
|
||||
func TestTomlColourization(t *testing.T) {
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
4.53.2:
|
||||
- Fixing release process
|
||||
|
||||
4.53.1:
|
||||
- Releases and tags now signed and immutable!
|
||||
- Add system(command; args) operator (disabled by default) (#2640)
|
||||
- TOML encoder: prefer readable table sections over inline tables (#2649)
|
||||
- Fix TOML encoder to quote keys containing special characters (#2648)
|
||||
- Add string slicing support (#2639)
|
||||
- Fix findInArray misuse on MappingNodes in equality and contains (#2645) Thanks @jandubois!
|
||||
- Fix panic on negative slice indices that underflow after adjustment (#2646) Thanks @jandubois!
|
||||
- Fix stack overflow from circular alias in traverse (#2647) Thanks @jandubois!
|
||||
- Fix panic and OOM in repeatString for large repeat counts (#2644) Thanks @jandubois!
|
||||
- Bumped dependencies
|
||||
|
||||
|
||||
4.52.5:
|
||||
- Fix: reset TOML decoder state between files (#2634) thanks @terminalchai
|
||||
- Fix: preserve original filename when using --front-matter (#2613) thanks @cobyfrombrooklyn-bot
|
||||
|
||||
@@ -49,5 +49,5 @@ fi
|
||||
|
||||
git add cmd/version.go snap/snapcraft.yaml
|
||||
git commit -m 'Bumping version'
|
||||
git tag $version
|
||||
git tag -f v4
|
||||
git tag $version -m "releasing"
|
||||
git tag -f v4 -m "releasing $version"
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
name: yq
|
||||
version: 'v4.52.5'
|
||||
version: 'v4.53.2'
|
||||
summary: A lightweight and portable command-line data file processor
|
||||
description: |
|
||||
`yq` uses [jq](https://github.com/stedolan/jq) like syntax but works with yaml, json, xml, csv, properties and TOML files.
|
||||
@@ -32,6 +32,6 @@ parts:
|
||||
build-environment:
|
||||
- CGO_ENABLED: 0
|
||||
source: https://github.com/mikefarah/yq.git
|
||||
source-tag: v4.52.5
|
||||
source-tag: v4.53.2
|
||||
build-snaps:
|
||||
- go/latest/stable
|
||||
|
||||
Reference in New Issue
Block a user