mirror of
https://github.com/mikefarah/yq.git
synced 2026-07-01 18:01:40 +00:00
Compare commits
No commits in common. "master" and "v4.50.1" have entirely different histories.
4
.github/ISSUE_TEMPLATE/bug_report_v4.md
vendored
4
.github/ISSUE_TEMPLATE/bug_report_v4.md
vendored
@ -34,13 +34,13 @@ The command you ran:
|
|||||||
yq eval-all 'select(fileIndex==0) | .a.b.c' data1.yml data2.yml
|
yq eval-all 'select(fileIndex==0) | .a.b.c' data1.yml data2.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
**Actual behaviour**
|
**Actual behavior**
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
cat: meow
|
cat: meow
|
||||||
```
|
```
|
||||||
|
|
||||||
**Expected behaviour**
|
**Expected behavior**
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
this: should really work
|
this: should really work
|
||||||
|
|||||||
1
.github/instructions/instructions.md
vendored
1
.github/instructions/instructions.md
vendored
@ -1 +0,0 @@
|
|||||||
When you find a bug - make sure to include a new test that exposes the bug, as well as the fix for the bug itself.
|
|
||||||
10
.github/workflows/codeql.yml
vendored
10
.github/workflows/codeql.yml
vendored
@ -20,8 +20,6 @@ on:
|
|||||||
schedule:
|
schedule:
|
||||||
- cron: '24 3 * * 1'
|
- cron: '24 3 * * 1'
|
||||||
|
|
||||||
permissions: {}
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
analyze:
|
analyze:
|
||||||
name: Analyze
|
name: Analyze
|
||||||
@ -40,11 +38,11 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
|
uses: github/codeql-action/init@v4
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
@ -55,7 +53,7 @@ jobs:
|
|||||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
# 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)
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
|
uses: github/codeql-action/autobuild@v4
|
||||||
|
|
||||||
# ℹ️ Command-line programs to run using the OS shell.
|
# ℹ️ 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
|
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||||
@ -69,4 +67,4 @@ jobs:
|
|||||||
# make release
|
# make release
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
|
uses: github/codeql-action/analyze@v4
|
||||||
|
|||||||
107
.github/workflows/docker-githubaction.yml
vendored
107
.github/workflows/docker-githubaction.yml
vendored
@ -1,107 +0,0 @@
|
|||||||
name: Release Docker GitHub Action
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
permissions: {}
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
publishGithubActionDocker:
|
|
||||||
environment: dockerhub
|
|
||||||
env:
|
|
||||||
IMAGE_NAME: mikefarah/yq
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
packages: write
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0
|
|
||||||
with:
|
|
||||||
platforms: all
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
id: buildx
|
|
||||||
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
|
|
||||||
with:
|
|
||||||
version: latest
|
|
||||||
|
|
||||||
- name: Verify Dockerfile base image digest matches yq:4
|
|
||||||
run: |
|
|
||||||
PINNED_DIGEST=$(grep -oE 'sha256:[a-f0-9]{64}' github-action/Dockerfile | head -1)
|
|
||||||
if [ -z "${PINNED_DIGEST}" ]; then
|
|
||||||
echo "::error::Could not find a sha256 digest in github-action/Dockerfile"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
LATEST_DIGEST=$(docker buildx imagetools inspect "${IMAGE_NAME}:4" --format '{{printf "%s" .Manifest.Digest}}')
|
|
||||||
|
|
||||||
echo "Dockerfile pins: ${PINNED_DIGEST}"
|
|
||||||
echo "mikefarah/yq:4 is: ${LATEST_DIGEST}"
|
|
||||||
|
|
||||||
if [ "${PINNED_DIGEST}" != "${LATEST_DIGEST}" ]; then
|
|
||||||
echo "::error::github-action/Dockerfile digest does not match the current mikefarah/yq:4 image"
|
|
||||||
echo "Update the FROM line in github-action/Dockerfile to:"
|
|
||||||
echo " FROM mikefarah/yq:4@${LATEST_DIGEST}"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Resolve version from yq:4
|
|
||||||
run: |
|
|
||||||
IMAGE_VERSION=$(docker run --rm "${IMAGE_NAME}:4" --version | awk '{print $NF}' | sed 's/^v//')
|
|
||||||
if [ -z "${IMAGE_VERSION}" ]; then
|
|
||||||
echo "::error::Could not determine yq version from ${IMAGE_NAME}:4"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "Resolved yq version: ${IMAGE_VERSION}"
|
|
||||||
echo "IMAGE_VERSION=${IMAGE_VERSION}" >> "${GITHUB_ENV}"
|
|
||||||
|
|
||||||
- name: Login to Docker Hub
|
|
||||||
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@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Build and push github-action image
|
|
||||||
working-directory: github-action
|
|
||||||
run: |
|
|
||||||
PLATFORMS="linux/amd64,linux/ppc64le,linux/arm64,linux/arm/v7,linux/s390x"
|
|
||||||
|
|
||||||
echo "Building and pushing github-action image for version ${IMAGE_VERSION}"
|
|
||||||
docker buildx build \
|
|
||||||
--label "org.opencontainers.image.authors=https://github.com/mikefarah/yq/graphs/contributors" \
|
|
||||||
--label "org.opencontainers.image.created=$(date --rfc-3339=seconds)" \
|
|
||||||
--label "org.opencontainers.image.description=yq is a portable command-line data file processor" \
|
|
||||||
--label "org.opencontainers.image.documentation=https://mikefarah.gitbook.io/yq/" \
|
|
||||||
--label "org.opencontainers.image.licenses=MIT" \
|
|
||||||
--label "org.opencontainers.image.revision=$(git rev-parse HEAD)" \
|
|
||||||
--label "org.opencontainers.image.source=https://github.com/mikefarah/yq" \
|
|
||||||
--label "org.opencontainers.image.title=yq" \
|
|
||||||
--label "org.opencontainers.image.url=https://mikefarah.gitbook.io/yq/" \
|
|
||||||
--label "org.opencontainers.image.version=${IMAGE_VERSION}" \
|
|
||||||
--platform "${PLATFORMS}" \
|
|
||||||
--pull \
|
|
||||||
--push \
|
|
||||||
-t "${IMAGE_NAME}:${IMAGE_VERSION}-githubaction" \
|
|
||||||
-t "${IMAGE_NAME}:4-githubaction" \
|
|
||||||
-t "${IMAGE_NAME}:latest-githubaction" \
|
|
||||||
-t "ghcr.io/${IMAGE_NAME}:${IMAGE_VERSION}-githubaction" \
|
|
||||||
-t "ghcr.io/${IMAGE_NAME}:4-githubaction" \
|
|
||||||
-t "ghcr.io/${IMAGE_NAME}:latest-githubaction" \
|
|
||||||
.
|
|
||||||
|
|
||||||
- name: Report action.yml digest to pin
|
|
||||||
run: |
|
|
||||||
GITHUBACTION_DIGEST=$(docker buildx imagetools inspect "${IMAGE_NAME}:4-githubaction" --format '{{printf "%s" .Manifest.Digest}}')
|
|
||||||
echo "Published ${IMAGE_NAME}:4-githubaction at ${GITHUBACTION_DIGEST}"
|
|
||||||
echo "Update action.yml image to:"
|
|
||||||
echo " docker://${IMAGE_NAME}:4-githubaction@${GITHUBACTION_DIGEST}"
|
|
||||||
38
.github/workflows/docker-release.yml
vendored
38
.github/workflows/docker-release.yml
vendored
@ -7,28 +7,23 @@ on:
|
|||||||
# Allows you to run this workflow manually from the Actions tab
|
# Allows you to run this workflow manually from the Actions tab
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions: {}
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
publishDocker:
|
publishDocker:
|
||||||
environment: dockerhub
|
environment: dockerhub
|
||||||
env:
|
env:
|
||||||
IMAGE_NAME: mikefarah/yq
|
IMAGE_NAME: mikefarah/yq
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
packages: write
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0
|
uses: docker/setup-qemu-action@v3
|
||||||
with:
|
with:
|
||||||
platforms: all
|
platforms: all
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
id: buildx
|
id: buildx
|
||||||
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
|
uses: docker/setup-buildx-action@v3
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
|
|
||||||
@ -36,13 +31,13 @@ jobs:
|
|||||||
run: echo ${{ steps.buildx.outputs.platforms }} && docker version
|
run: echo ${{ steps.buildx.outputs.platforms }} && docker version
|
||||||
|
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
@ -80,3 +75,26 @@ jobs:
|
|||||||
-t "ghcr.io/${IMAGE_NAME}:4" \
|
-t "ghcr.io/${IMAGE_NAME}:4" \
|
||||||
-t "ghcr.io/${IMAGE_NAME}:latest" \
|
-t "ghcr.io/${IMAGE_NAME}:latest" \
|
||||||
.
|
.
|
||||||
|
|
||||||
|
cd github-action
|
||||||
|
docker buildx build \
|
||||||
|
--label "org.opencontainers.image.authors=https://github.com/mikefarah/yq/graphs/contributors" \
|
||||||
|
--label "org.opencontainers.image.created=$(date --rfc-3339=seconds)" \
|
||||||
|
--label "org.opencontainers.image.description=yq is a portable command-line data file processor" \
|
||||||
|
--label "org.opencontainers.image.documentation=https://mikefarah.gitbook.io/yq/" \
|
||||||
|
--label "org.opencontainers.image.licenses=MIT" \
|
||||||
|
--label "org.opencontainers.image.revision=$(git rev-parse HEAD)" \
|
||||||
|
--label "org.opencontainers.image.source=https://github.com/mikefarah/yq" \
|
||||||
|
--label "org.opencontainers.image.title=yq" \
|
||||||
|
--label "org.opencontainers.image.url=https://mikefarah.gitbook.io/yq/" \
|
||||||
|
--label "org.opencontainers.image.version=${IMAGE_VERSION}" \
|
||||||
|
--platform "${PLATFORMS}" \
|
||||||
|
--pull \
|
||||||
|
--push \
|
||||||
|
-t "${IMAGE_NAME}:${IMAGE_VERSION}-githubaction" \
|
||||||
|
-t "${IMAGE_NAME}:4-githubaction" \
|
||||||
|
-t "${IMAGE_NAME}:latest-githubaction" \
|
||||||
|
-t "ghcr.io/${IMAGE_NAME}:${IMAGE_VERSION}-githubaction" \
|
||||||
|
-t "ghcr.io/${IMAGE_NAME}:4-githubaction" \
|
||||||
|
-t "ghcr.io/${IMAGE_NAME}:latest-githubaction" \
|
||||||
|
.
|
||||||
|
|||||||
32
.github/workflows/go.yml
vendored
32
.github/workflows/go.yml
vendored
@ -5,51 +5,25 @@ permissions:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
verify-action-digest:
|
|
||||||
name: Verify action.yml image digest
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
||||||
|
|
||||||
- name: Verify action.yml digest matches published image
|
|
||||||
run: |
|
|
||||||
PINNED_DIGEST=$(grep -oE 'sha256:[a-f0-9]{64}' action.yml | head -1)
|
|
||||||
if [ -z "${PINNED_DIGEST}" ]; then
|
|
||||||
echo "::error::action.yml does not pin the runtime image by digest"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
LATEST_DIGEST=$(docker buildx imagetools inspect docker.io/mikefarah/yq:4-githubaction --format '{{printf "%s" .Manifest.Digest}}')
|
|
||||||
|
|
||||||
echo "action.yml pins: ${PINNED_DIGEST}"
|
|
||||||
echo "mikefarah/yq:4-githubaction: ${LATEST_DIGEST}"
|
|
||||||
|
|
||||||
if [ "${PINNED_DIGEST}" != "${LATEST_DIGEST}" ]; then
|
|
||||||
echo "::error::action.yml digest does not match the current mikefarah/yq:4-githubaction image"
|
|
||||||
echo "Update the image line in action.yml to:"
|
|
||||||
echo " docker://mikefarah/yq:4-githubaction@${LATEST_DIGEST}"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
build:
|
build:
|
||||||
name: Build
|
name: Build
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: '^1.20'
|
go-version: '^1.20'
|
||||||
id: go
|
id: go
|
||||||
|
|
||||||
- name: Check out code into the Go module directory
|
- name: Check out code into the Go module directory
|
||||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Get dependencies
|
- name: Get dependencies
|
||||||
run: |
|
run: |
|
||||||
go get -v -t -d ./...
|
go get -v -t -d ./...
|
||||||
if [ -f Gopkg.toml ]; then
|
if [ -f Gopkg.toml ]; then
|
||||||
curl -sSfL https://raw.githubusercontent.com/golang/dep/1f7c19e5f52f49ffb9f956f64c010be14683468b/install.sh | env DEP_RELEASE_TAG=v0.5.4 sh
|
curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
|
||||||
dep ensure
|
dep ensure
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
23
.github/workflows/release.yml
vendored
23
.github/workflows/release.yml
vendored
@ -5,17 +5,12 @@ on:
|
|||||||
- 'v4.*'
|
- 'v4.*'
|
||||||
- 'draft-*'
|
- 'draft-*'
|
||||||
|
|
||||||
permissions: {}
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
publishGitRelease:
|
publishGitRelease:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
id-token: write
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
- uses: actions/checkout@v6
|
||||||
- uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: '^1.20'
|
go-version: '^1.20'
|
||||||
check-latest: true
|
check-latest: true
|
||||||
@ -29,7 +24,7 @@ jobs:
|
|||||||
run: echo "VERSION=${GITHUB_REF##*/}" >> "${GITHUB_OUTPUT}"
|
run: echo "VERSION=${GITHUB_REF##*/}" >> "${GITHUB_OUTPUT}"
|
||||||
|
|
||||||
- name: Generate man page
|
- name: Generate man page
|
||||||
uses: docker://pandoc/core:2.14.2@sha256:04e127c6642a2b9d447c26fe0ac6a5932efa8f508eda9f07da51b6e621dd7c19
|
uses: docker://pandoc/core:2.14.2
|
||||||
id: gen-man-page
|
id: gen-man-page
|
||||||
with:
|
with:
|
||||||
args: >-
|
args: >-
|
||||||
@ -42,22 +37,14 @@ jobs:
|
|||||||
--output=yq.1
|
--output=yq.1
|
||||||
man.md
|
man.md
|
||||||
|
|
||||||
- name: Install cosign
|
|
||||||
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
|
|
||||||
|
|
||||||
- name: Cross compile
|
- name: Cross compile
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get install rhash -y
|
sudo apt-get install rhash -y
|
||||||
go install github.com/goreleaser/goreleaser/v2@v2.16.0
|
go install github.com/goreleaser/goreleaser/v2@latest
|
||||||
./scripts/xcompile.sh
|
./scripts/xcompile.sh
|
||||||
|
|
||||||
- name: Sign checksums
|
|
||||||
run: |
|
|
||||||
cosign sign-blob --yes --bundle build/checksums.bundle build/checksums
|
|
||||||
cosign sign-blob --yes --bundle build/checksums-bsd.bundle build/checksums-bsd
|
|
||||||
|
|
||||||
- name: Release
|
- name: Release
|
||||||
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
|
uses: softprops/action-gh-release@v1
|
||||||
with:
|
with:
|
||||||
files: build/*
|
files: build/*
|
||||||
draft: true
|
draft: true
|
||||||
|
|||||||
78
.github/workflows/scorecard.yml
vendored
78
.github/workflows/scorecard.yml
vendored
@ -1,78 +0,0 @@
|
|||||||
# 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@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
|
|
||||||
with:
|
|
||||||
sarif_file: results.sarif
|
|
||||||
10
.github/workflows/snap-release.yml
vendored
10
.github/workflows/snap-release.yml
vendored
@ -7,23 +7,19 @@ on:
|
|||||||
# Allows you to run this workflow manually from the Actions tab
|
# Allows you to run this workflow manually from the Actions tab
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions: {}
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
buildSnap:
|
buildSnap:
|
||||||
environment: snap
|
environment: snap
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
- uses: actions/checkout@v6
|
||||||
- uses: snapcore/action-build@3bdaa03e1ba6bf59a65f84a751d943d549a54e79 # v1.3.0
|
- uses: snapcore/action-build@v1
|
||||||
id: build
|
id: build
|
||||||
env:
|
env:
|
||||||
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.STORE_LOGIN }}
|
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.STORE_LOGIN }}
|
||||||
with:
|
with:
|
||||||
snapcraft-args: "remote-build --launchpad-accept-public-upload"
|
snapcraft-args: "remote-build --launchpad-accept-public-upload"
|
||||||
- uses: snapcore/action-publish@214b86e5ca036ead1668c79afb81e550e6c54d40 # v1.2.0
|
- uses: snapcore/action-publish@v1
|
||||||
env:
|
env:
|
||||||
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.STORE_LOGIN }}
|
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.STORE_LOGIN }}
|
||||||
with:
|
with:
|
||||||
|
|||||||
2
.github/workflows/test-yq.yml
vendored
2
.github/workflows/test-yq.yml
vendored
@ -13,7 +13,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
- uses: actions/checkout@v6
|
||||||
- name: Get test
|
- name: Get test
|
||||||
id: get_value
|
id: get_value
|
||||||
uses: mikefarah/yq@master
|
uses: mikefarah/yq@master
|
||||||
|
|||||||
6
.gitignore
vendored
6
.gitignore
vendored
@ -43,11 +43,9 @@ yq*.snap
|
|||||||
|
|
||||||
test.yml
|
test.yml
|
||||||
test*.yml
|
test*.yml
|
||||||
test*.tf
|
|
||||||
test*.xml
|
test*.xml
|
||||||
test*.toml
|
test*.toml
|
||||||
test*.yaml
|
test*.yaml
|
||||||
*.kyaml
|
|
||||||
test_dir1/
|
test_dir1/
|
||||||
test_dir2/
|
test_dir2/
|
||||||
0.yml
|
0.yml
|
||||||
@ -70,7 +68,3 @@ debian/files
|
|||||||
.vscode
|
.vscode
|
||||||
|
|
||||||
yq3
|
yq3
|
||||||
|
|
||||||
# Golang
|
|
||||||
.gomodcache/
|
|
||||||
.gocache/
|
|
||||||
|
|||||||
@ -14,11 +14,6 @@ linters:
|
|||||||
- unconvert
|
- unconvert
|
||||||
- unparam
|
- unparam
|
||||||
settings:
|
settings:
|
||||||
misspell:
|
|
||||||
locale: UK
|
|
||||||
ignore-rules:
|
|
||||||
- color
|
|
||||||
- colors
|
|
||||||
depguard:
|
depguard:
|
||||||
rules:
|
rules:
|
||||||
prevent_unmaintained_packages:
|
prevent_unmaintained_packages:
|
||||||
|
|||||||
@ -39,6 +39,7 @@ builds:
|
|||||||
- openbsd_amd64
|
- openbsd_amd64
|
||||||
- windows_386
|
- windows_386
|
||||||
- windows_amd64
|
- windows_amd64
|
||||||
|
- windows_arm
|
||||||
- windows_arm64
|
- windows_arm64
|
||||||
|
|
||||||
no_unique_dist_dir: true
|
no_unique_dist_dir: true
|
||||||
|
|||||||
@ -11,7 +11,7 @@ appearance, race, religion, or sexual identity and orientation.
|
|||||||
|
|
||||||
## Our Standards
|
## Our Standards
|
||||||
|
|
||||||
Examples of behaviour that contributes to creating a positive environment
|
Examples of behavior that contributes to creating a positive environment
|
||||||
include:
|
include:
|
||||||
|
|
||||||
* Using welcoming and inclusive language
|
* Using welcoming and inclusive language
|
||||||
@ -20,7 +20,7 @@ include:
|
|||||||
* Focusing on what is best for the community
|
* Focusing on what is best for the community
|
||||||
* Showing empathy towards other community members
|
* Showing empathy towards other community members
|
||||||
|
|
||||||
Examples of unacceptable behaviour by participants include:
|
Examples of unacceptable behavior by participants include:
|
||||||
|
|
||||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||||
advances
|
advances
|
||||||
@ -34,13 +34,13 @@ Examples of unacceptable behaviour by participants include:
|
|||||||
## Our Responsibilities
|
## Our Responsibilities
|
||||||
|
|
||||||
Project maintainers are responsible for clarifying the standards of acceptable
|
Project maintainers are responsible for clarifying the standards of acceptable
|
||||||
behaviour and are expected to take appropriate and fair corrective action in
|
behavior and are expected to take appropriate and fair corrective action in
|
||||||
response to any instances of unacceptable behaviour.
|
response to any instances of unacceptable behavior.
|
||||||
|
|
||||||
Project maintainers have the right and responsibility to remove, edit, or
|
Project maintainers have the right and responsibility to remove, edit, or
|
||||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||||
permanently any contributor for other behaviours that they deem inappropriate,
|
permanently any contributor for other behaviors that they deem inappropriate,
|
||||||
threatening, offensive, or harmful.
|
threatening, offensive, or harmful.
|
||||||
|
|
||||||
## Scope
|
## Scope
|
||||||
@ -54,7 +54,7 @@ further defined and clarified by project maintainers.
|
|||||||
|
|
||||||
## Enforcement
|
## Enforcement
|
||||||
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behaviour may be
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
reported by contacting the project team at mikefarah@gmail.com. All
|
reported by contacting the project team at mikefarah@gmail.com. All
|
||||||
complaints will be reviewed and investigated and will result in a response that
|
complaints will be reviewed and investigated and will result in a response that
|
||||||
is deemed necessary and appropriate to the circumstances. The project team is
|
is deemed necessary and appropriate to the circumstances. The project team is
|
||||||
|
|||||||
@ -197,21 +197,6 @@ Note: PRs with small changes (e.g. minor typos) may not be merged (see https://j
|
|||||||
make [local] test # Run in Docker container
|
make [local] test # Run in Docker container
|
||||||
```
|
```
|
||||||
|
|
||||||
- **Problem**: Tests fail with a VCS error:
|
|
||||||
```bash
|
|
||||||
error obtaining VCS status: exit status 128
|
|
||||||
Use -buildvcs=false to disable VCS stamping.
|
|
||||||
```
|
|
||||||
- **Solution**:
|
|
||||||
Git security mechanisms prevent Golang from detecting the Git details inside
|
|
||||||
the container; either build with the `local` option, or pass GOFLAGS to
|
|
||||||
disable Golang buildvcs behaviour.
|
|
||||||
```bash
|
|
||||||
make local test
|
|
||||||
# OR
|
|
||||||
make test GOFLAGS='-buildvcs=true'
|
|
||||||
```
|
|
||||||
|
|
||||||
### Documentation Generation Issues
|
### Documentation Generation Issues
|
||||||
- **Problem**: Generated docs don't update after test changes
|
- **Problem**: Generated docs don't update after test changes
|
||||||
- **Solution**:
|
- **Solution**:
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
FROM golang:1.26.4@sha256:792443b89f65105abba56b9bd5e97f680a80074ac62fc844a584212f8c8102c3 AS builder
|
FROM golang:1.25.5 AS builder
|
||||||
|
|
||||||
WORKDIR /go/src/mikefarah/yq
|
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
|
# Choose alpine as a base image to make this useful for CI, as many
|
||||||
# CI tools expect an interactive shell inside the container
|
# CI tools expect an interactive shell inside the container
|
||||||
FROM alpine:3@sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b AS production
|
FROM alpine:3 AS production
|
||||||
LABEL maintainer="Mike Farah <mikefarah@users.noreply.github.com>"
|
LABEL maintainer="Mike Farah <mikefarah@users.noreply.github.com>"
|
||||||
|
|
||||||
COPY --from=builder /go/src/mikefarah/yq/yq /usr/bin/yq
|
COPY --from=builder /go/src/mikefarah/yq/yq /usr/bin/yq
|
||||||
|
|||||||
@ -1,4 +1,8 @@
|
|||||||
FROM golang:1.26.4@sha256:792443b89f65105abba56b9bd5e97f680a80074ac62fc844a584212f8c8102c3
|
FROM golang:1.25.5
|
||||||
|
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install -y npm && \
|
||||||
|
npm install -g npx cspell@latest
|
||||||
|
|
||||||
COPY scripts/devtools.sh /opt/devtools.sh
|
COPY scripts/devtools.sh /opt/devtools.sh
|
||||||
|
|
||||||
|
|||||||
3
Makefile
3
Makefile
@ -35,14 +35,13 @@ clean:
|
|||||||
## prefix before other make targets to run in your local dev environment
|
## prefix before other make targets to run in your local dev environment
|
||||||
local: | quiet
|
local: | quiet
|
||||||
@$(eval ENGINERUN= )
|
@$(eval ENGINERUN= )
|
||||||
@$(eval GOFLAGS="$(GOFLAGS)" )
|
|
||||||
@mkdir -p tmp
|
@mkdir -p tmp
|
||||||
@touch tmp/dev_image_id
|
@touch tmp/dev_image_id
|
||||||
quiet: # this is silly but shuts up 'Nothing to be done for `local`'
|
quiet: # this is silly but shuts up 'Nothing to be done for `local`'
|
||||||
@:
|
@:
|
||||||
|
|
||||||
prepare: tmp/dev_image_id
|
prepare: tmp/dev_image_id
|
||||||
tmp/dev_image_id: Dockerfile.dev scripts/devtools.sh _typos.toml
|
tmp/dev_image_id: Dockerfile.dev scripts/devtools.sh
|
||||||
@mkdir -p tmp
|
@mkdir -p tmp
|
||||||
@${ENGINE} rmi -f ${DEV_IMAGE} > /dev/null 2>&1 || true
|
@${ENGINE} rmi -f ${DEV_IMAGE} > /dev/null 2>&1 || true
|
||||||
@${ENGINE} build -t ${DEV_IMAGE} -f Dockerfile.dev .
|
@${ENGINE} build -t ${DEV_IMAGE} -f Dockerfile.dev .
|
||||||
|
|||||||
@ -4,7 +4,6 @@ IMPORT_PATH := github.com/mikefarah/${PROJECT}
|
|||||||
export GIT_COMMIT = $(shell git rev-parse --short HEAD)
|
export GIT_COMMIT = $(shell git rev-parse --short HEAD)
|
||||||
export GIT_DIRTY = $(shell test -n "$$(git status --porcelain)" && echo "+CHANGES" || true)
|
export GIT_DIRTY = $(shell test -n "$$(git status --porcelain)" && echo "+CHANGES" || true)
|
||||||
export GIT_DESCRIBE = $(shell git describe --tags --always)
|
export GIT_DESCRIBE = $(shell git describe --tags --always)
|
||||||
GOFLAGS :=
|
|
||||||
LDFLAGS :=
|
LDFLAGS :=
|
||||||
LDFLAGS += -X main.GitCommit=${GIT_COMMIT}${GIT_DIRTY}
|
LDFLAGS += -X main.GitCommit=${GIT_COMMIT}${GIT_DIRTY}
|
||||||
LDFLAGS += -X main.GitDescribe=${GIT_DESCRIBE}
|
LDFLAGS += -X main.GitDescribe=${GIT_DESCRIBE}
|
||||||
@ -27,15 +26,13 @@ ifeq ($(CYG_CHECK),1)
|
|||||||
else
|
else
|
||||||
# all non-windows environments
|
# all non-windows environments
|
||||||
ROOT := $(shell pwd)
|
ROOT := $(shell pwd)
|
||||||
# Deliberately use `command -v` instead of `which` to be POSIX compliant
|
SELINUX := $(shell which getenforce 2>&1 >/dev/null && echo :z)
|
||||||
SELINUX := $(shell command -v getenforce >/dev/null 2>&1 && echo :z)
|
|
||||||
endif
|
endif
|
||||||
|
|
||||||
DEV_IMAGE := ${PROJECT}_dev
|
DEV_IMAGE := ${PROJECT}_dev
|
||||||
|
|
||||||
ENGINERUN := ${ENGINE} run --rm \
|
ENGINERUN := ${ENGINE} run --rm \
|
||||||
-e LDFLAGS="${LDFLAGS}" \
|
-e LDFLAGS="${LDFLAGS}" \
|
||||||
-e GOFLAGS="${GOFLAGS}" \
|
|
||||||
-e GITHUB_TOKEN="${GITHUB_TOKEN}" \
|
-e GITHUB_TOKEN="${GITHUB_TOKEN}" \
|
||||||
-v ${ROOT}/vendor:/go/src${SELINUX} \
|
-v ${ROOT}/vendor:/go/src${SELINUX} \
|
||||||
-v ${ROOT}:/${PROJECT}/src/${IMPORT_PATH}${SELINUX} \
|
-v ${ROOT}:/${PROJECT}/src/${IMPORT_PATH}${SELINUX} \
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
    
|
    
|
||||||
|
|
||||||
|
|
||||||
A lightweight and portable command-line YAML, JSON, INI and XML processor. `yq` uses [jq](https://github.com/stedolan/jq) (a popular JSON processor) like syntax but works with yaml files as well as json, kyaml, xml, ini, properties, csv and tsv. It doesn't yet support everything `jq` does - but it does support the most common operations and functions, and more is being added continuously.
|
A lightweight and portable command-line YAML, JSON, INI and XML processor. `yq` uses [jq](https://github.com/stedolan/jq) (a popular JSON processor) like syntax but works with yaml files as well as json, xml, ini, properties, csv and tsv. It doesn't yet support everything `jq` does - but it does support the most common operations and functions, and more is being added continuously.
|
||||||
|
|
||||||
yq is written in Go - so you can download a dependency free binary for your platform and you are good to go! If you prefer there are a variety of package managers that can be used as well as Docker and Podman, all listed below.
|
yq is written in Go - so you can download a dependency free binary for your platform and you are good to go! If you prefer there are a variety of package managers that can be used as well as Docker and Podman, all listed below.
|
||||||
|
|
||||||
@ -363,8 +363,6 @@ gah install yq
|
|||||||
- [Load content from other files](https://mikefarah.gitbook.io/yq/operators/load)
|
- [Load content from other files](https://mikefarah.gitbook.io/yq/operators/load)
|
||||||
- [Convert to/from json/ndjson](https://mikefarah.gitbook.io/yq/v/v4.x/usage/convert)
|
- [Convert to/from json/ndjson](https://mikefarah.gitbook.io/yq/v/v4.x/usage/convert)
|
||||||
- [Convert to/from xml](https://mikefarah.gitbook.io/yq/v/v4.x/usage/xml)
|
- [Convert to/from xml](https://mikefarah.gitbook.io/yq/v/v4.x/usage/xml)
|
||||||
- [Convert to/from hcl (terraform)](https://mikefarah.gitbook.io/yq/v/v4.x/usage/hcl)
|
|
||||||
- [Convert to/from toml](https://mikefarah.gitbook.io/yq/v/v4.x/usage/toml)
|
|
||||||
- [Convert to/from properties](https://mikefarah.gitbook.io/yq/v/v4.x/usage/properties)
|
- [Convert to/from properties](https://mikefarah.gitbook.io/yq/v/v4.x/usage/properties)
|
||||||
- [Convert to/from csv/tsv](https://mikefarah.gitbook.io/yq/usage/csv-tsv)
|
- [Convert to/from csv/tsv](https://mikefarah.gitbook.io/yq/usage/csv-tsv)
|
||||||
- [General shell completion scripts (bash/zsh/fish/powershell)](https://mikefarah.gitbook.io/yq/v/v4.x/commands/shell-completion)
|
- [General shell completion scripts (bash/zsh/fish/powershell)](https://mikefarah.gitbook.io/yq/v/v4.x/commands/shell-completion)
|
||||||
@ -415,7 +413,7 @@ Flags:
|
|||||||
-h, --help help for yq
|
-h, --help help for yq
|
||||||
-I, --indent int sets indent level for output (default 2)
|
-I, --indent int sets indent level for output (default 2)
|
||||||
-i, --inplace update the file in place of first file given.
|
-i, --inplace update the file in place of first file given.
|
||||||
-p, --input-format string [auto|a|yaml|y|json|j|kyaml|ky|props|p|csv|c|tsv|t|xml|x|base64|uri|toml|hcl|h|lua|l|ini|i] parse format for input. (default "auto")
|
-p, --input-format string [auto|a|yaml|y|json|j|props|p|csv|c|tsv|t|xml|x|base64|uri|toml|lua|l|ini|i] parse format for input. (default "auto")
|
||||||
--lua-globals output keys as top-level global variables
|
--lua-globals output keys as top-level global variables
|
||||||
--lua-prefix string prefix (default "return ")
|
--lua-prefix string prefix (default "return ")
|
||||||
--lua-suffix string suffix (default ";\n")
|
--lua-suffix string suffix (default ";\n")
|
||||||
@ -424,7 +422,7 @@ Flags:
|
|||||||
-N, --no-doc Don't print document separators (---)
|
-N, --no-doc Don't print document separators (---)
|
||||||
-0, --nul-output Use NUL char to separate values. If unwrap scalar is also set, fail if unwrapped scalar contains NUL char.
|
-0, --nul-output Use NUL char to separate values. If unwrap scalar is also set, fail if unwrapped scalar contains NUL char.
|
||||||
-n, --null-input Don't read input, simply evaluate the expression given. Useful for creating docs from scratch.
|
-n, --null-input Don't read input, simply evaluate the expression given. Useful for creating docs from scratch.
|
||||||
-o, --output-format string [auto|a|yaml|y|json|j|kyaml|ky|props|p|csv|c|tsv|t|xml|x|base64|uri|toml|hcl|h|shell|s|lua|l|ini|i] output format type. (default "auto")
|
-o, --output-format string [auto|a|yaml|y|json|j|props|p|csv|c|tsv|t|xml|x|base64|uri|toml|shell|s|lua|l|ini|i] output format type. (default "auto")
|
||||||
-P, --prettyPrint pretty print, shorthand for '... style = ""'
|
-P, --prettyPrint pretty print, shorthand for '... style = ""'
|
||||||
--properties-array-brackets use [x] in array paths (e.g. for SpringBoot)
|
--properties-array-brackets use [x] in array paths (e.g. for SpringBoot)
|
||||||
--properties-separator string separator to use between keys and values (default " = ")
|
--properties-separator string separator to use between keys and values (default " = ")
|
||||||
|
|||||||
26
SECURITY.md
26
SECURITY.md
@ -1,26 +0,0 @@
|
|||||||
# Security Policy
|
|
||||||
|
|
||||||
## Reporting a Vulnerability
|
|
||||||
|
|
||||||
Please **do not** report security vulnerabilities through public GitHub issues.
|
|
||||||
|
|
||||||
Instead, use GitHub's private vulnerability reporting feature:
|
|
||||||
👉 https://github.com/mikefarah/yq/security
|
|
||||||
|
|
||||||
This allows vulnerabilities to be triaged and addressed confidentially before any public disclosure.
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
### HTTP / TLS / Network vulnerabilities
|
|
||||||
|
|
||||||
yq is a command-line YAML/JSON/TOML processor that reads from files or standard input and writes to standard output. **yq does not include any HTTP or network libraries** and makes no network connections at runtime. CVEs related to HTTP, TLS, or networking are therefore **not applicable** to yq.
|
|
||||||
|
|
||||||
### Dependency version bumps
|
|
||||||
|
|
||||||
yq uses [Dependabot](https://docs.github.com/en/code-security/dependabot) to automatically raise pull requests for:
|
|
||||||
|
|
||||||
- Go module dependencies
|
|
||||||
- Go toolchain version
|
|
||||||
- Docker base images
|
|
||||||
|
|
||||||
Please **do not** raise pull requests or issues solely to bump dependency or Go versions — Dependabot handles this automatically and the maintainers merge those PRs regularly.
|
|
||||||
20
_typos.toml
20
_typos.toml
@ -1,20 +0,0 @@
|
|||||||
[files]
|
|
||||||
extend-exclude = ["vendor", "bin"]
|
|
||||||
|
|
||||||
[default]
|
|
||||||
locale = "en"
|
|
||||||
extend-ignore-identifiers-re = [
|
|
||||||
"NdJson",
|
|
||||||
]
|
|
||||||
|
|
||||||
[default.extend-identifiers]
|
|
||||||
AttributeIDSupressMenu = "AttributeIDSupressMenu"
|
|
||||||
|
|
||||||
[default.extend-words]
|
|
||||||
Teh = "Teh"
|
|
||||||
teh = "teh"
|
|
||||||
Supress = "Supress"
|
|
||||||
HashiCorp = "HashiCorp"
|
|
||||||
Hashi = "Hashi"
|
|
||||||
fot = "fot"
|
|
||||||
nd = "nd"
|
|
||||||
@ -6,7 +6,6 @@ setUp() {
|
|||||||
rm test*.csv 2>/dev/null || true
|
rm test*.csv 2>/dev/null || true
|
||||||
rm test*.tsv 2>/dev/null || true
|
rm test*.tsv 2>/dev/null || true
|
||||||
rm test*.xml 2>/dev/null || true
|
rm test*.xml 2>/dev/null || true
|
||||||
rm test*.tf 2>/dev/null || true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
testInputProperties() {
|
testInputProperties() {
|
||||||
@ -154,37 +153,6 @@ EOM
|
|||||||
assertEquals "$expected" "$X"
|
assertEquals "$expected" "$X"
|
||||||
}
|
}
|
||||||
|
|
||||||
testInputKYaml() {
|
|
||||||
cat >test.kyaml <<'EOL'
|
|
||||||
# leading
|
|
||||||
{
|
|
||||||
a: 1, # a line
|
|
||||||
# head b
|
|
||||||
b: 2,
|
|
||||||
c: [
|
|
||||||
# head d
|
|
||||||
"d", # d line
|
|
||||||
],
|
|
||||||
}
|
|
||||||
EOL
|
|
||||||
|
|
||||||
read -r -d '' expected <<'EOM'
|
|
||||||
# leading
|
|
||||||
a: 1 # a line
|
|
||||||
# head b
|
|
||||||
b: 2
|
|
||||||
c:
|
|
||||||
# head d
|
|
||||||
- d # d line
|
|
||||||
EOM
|
|
||||||
|
|
||||||
X=$(./yq e -p=kyaml -P test.kyaml)
|
|
||||||
assertEquals "$expected" "$X"
|
|
||||||
|
|
||||||
X=$(./yq ea -p=kyaml -P test.kyaml)
|
|
||||||
assertEquals "$expected" "$X"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -287,61 +255,4 @@ EOM
|
|||||||
assertEquals "$expected" "$X"
|
assertEquals "$expected" "$X"
|
||||||
}
|
}
|
||||||
|
|
||||||
testInputTerraform() {
|
source ./scripts/shunit2
|
||||||
cat >test.tf <<EOL
|
|
||||||
resource "aws_s3_bucket" "example" {
|
|
||||||
bucket = "my-bucket"
|
|
||||||
tags = {
|
|
||||||
Environment = "Dev"
|
|
||||||
Project = "Test"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
EOL
|
|
||||||
|
|
||||||
read -r -d '' expected << EOM
|
|
||||||
resource "aws_s3_bucket" "example" {
|
|
||||||
bucket = "my-bucket"
|
|
||||||
tags = {
|
|
||||||
Environment = "Dev"
|
|
||||||
Project = "Test"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
EOM
|
|
||||||
|
|
||||||
X=$(./yq test.tf)
|
|
||||||
assertEquals "$expected" "$X"
|
|
||||||
|
|
||||||
X=$(./yq ea test.tf)
|
|
||||||
assertEquals "$expected" "$X"
|
|
||||||
}
|
|
||||||
|
|
||||||
testInputTerraformGithubAction() {
|
|
||||||
cat >test.tf <<EOL
|
|
||||||
resource "aws_s3_bucket" "example" {
|
|
||||||
bucket = "my-bucket"
|
|
||||||
|
|
||||||
tags = {
|
|
||||||
Environment = "Dev"
|
|
||||||
Project = "Test"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
EOL
|
|
||||||
|
|
||||||
read -r -d '' expected << EOM
|
|
||||||
resource "aws_s3_bucket" "example" {
|
|
||||||
bucket = "my-bucket"
|
|
||||||
tags = {
|
|
||||||
Environment = "Dev"
|
|
||||||
Project = "Test"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
EOM
|
|
||||||
|
|
||||||
X=$(cat /dev/null | ./yq test.tf)
|
|
||||||
assertEquals "$expected" "$X"
|
|
||||||
|
|
||||||
X=$(cat /dev/null | ./yq ea test.tf)
|
|
||||||
assertEquals "$expected" "$X"
|
|
||||||
}
|
|
||||||
|
|
||||||
source ./scripts/shunit2
|
|
||||||
@ -280,55 +280,6 @@ EOM
|
|||||||
assertEquals "$expected" "$X"
|
assertEquals "$expected" "$X"
|
||||||
}
|
}
|
||||||
|
|
||||||
testOutputKYaml() {
|
|
||||||
cat >test.yml <<'EOL'
|
|
||||||
# leading
|
|
||||||
a: 1 # a line
|
|
||||||
# head b
|
|
||||||
b: 2
|
|
||||||
c:
|
|
||||||
# head d
|
|
||||||
- d # d line
|
|
||||||
EOL
|
|
||||||
|
|
||||||
read -r -d '' expected <<'EOM'
|
|
||||||
# leading
|
|
||||||
{
|
|
||||||
a: 1, # a line
|
|
||||||
# head b
|
|
||||||
b: 2,
|
|
||||||
c: [
|
|
||||||
# head d
|
|
||||||
"d", # d line
|
|
||||||
],
|
|
||||||
}
|
|
||||||
EOM
|
|
||||||
|
|
||||||
X=$(./yq e --output-format=kyaml test.yml)
|
|
||||||
assertEquals "$expected" "$X"
|
|
||||||
|
|
||||||
X=$(./yq ea --output-format=kyaml test.yml)
|
|
||||||
assertEquals "$expected" "$X"
|
|
||||||
}
|
|
||||||
|
|
||||||
testOutputKYamlShort() {
|
|
||||||
cat >test.yml <<EOL
|
|
||||||
a: b
|
|
||||||
EOL
|
|
||||||
|
|
||||||
read -r -d '' expected <<'EOM'
|
|
||||||
{
|
|
||||||
a: "b",
|
|
||||||
}
|
|
||||||
EOM
|
|
||||||
|
|
||||||
X=$(./yq e -o=ky test.yml)
|
|
||||||
assertEquals "$expected" "$X"
|
|
||||||
|
|
||||||
X=$(./yq ea -o=ky test.yml)
|
|
||||||
assertEquals "$expected" "$X"
|
|
||||||
}
|
|
||||||
|
|
||||||
testOutputXmComplex() {
|
testOutputXmComplex() {
|
||||||
cat >test.yml <<EOL
|
cat >test.yml <<EOL
|
||||||
a: {b: {c: ["cat", "dog"], +@f: meow}}
|
a: {b: {c: ["cat", "dog"], +@f: meow}}
|
||||||
|
|||||||
@ -12,6 +12,6 @@ outputs:
|
|||||||
description: "The complete result from the yq command being run"
|
description: "The complete result from the yq command being run"
|
||||||
runs:
|
runs:
|
||||||
using: 'docker'
|
using: 'docker'
|
||||||
image: 'docker://mikefarah/yq:4-githubaction@sha256:e1b8c865f299ea6b02910a7ddf147d5d431244d4cc116f89c2148c9f53822906'
|
image: 'docker://mikefarah/yq:4-githubaction'
|
||||||
args:
|
args:
|
||||||
- ${{ inputs.cmd }}
|
- ${{ inputs.cmd }}
|
||||||
|
|||||||
@ -1,95 +1,3 @@
|
|||||||
# yq — agent instructions
|
|
||||||
|
|
||||||
## ⚠️ MANDATORY: GitHub agent disclosure
|
|
||||||
|
|
||||||
**Always required. No exceptions.**
|
|
||||||
|
|
||||||
Whenever you perform **any** GitHub action on behalf of the user, you **must** disclose that an AI agent (Cursor) wrote the content and is acting on the user's behalf — **not the user personally**. Do this **before** submitting; never post first and add the disclosure later.
|
|
||||||
|
|
||||||
Applies to **all** GitHub interactions, including:
|
|
||||||
|
|
||||||
- Pull requests (titles, descriptions, and reviews)
|
|
||||||
- PR comments and inline review comments
|
|
||||||
- Issues (new issues, comments, and updates)
|
|
||||||
- Any other post or reply on GitHub
|
|
||||||
|
|
||||||
**How to disclose:** Put it prominently at the **top** of every PR description, review body, comment, or issue. Use wording like:
|
|
||||||
|
|
||||||
|
|
||||||
Inline review comments must include a short disclosure too (e.g. `> Generated by Cursor acting on the user's behalf, not the user personally.`).
|
|
||||||
|
|
||||||
**Never** submit a GitHub action without this disclosure.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
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`)
|
|
||||||
- **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` and `typos` install 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 `typos` (installed by `scripts/devtools.sh`).
|
|
||||||
- **`make local test` / `scripts/check.sh`** require `golangci-lint` on PATH (`devtools.sh`).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# General rules
|
|
||||||
✅ **DO:**
|
|
||||||
- You can use ./yq with the `--debug-node-info` flag to get a deeper understanding of the ast.
|
|
||||||
- run ./scripts/format.sh to format the code; then ./scripts/check.sh lint and finally ./scripts/spelling.sh to check spelling.
|
|
||||||
- Add comprehensive tests to cover the changes
|
|
||||||
- Run test suite to ensure there is no regression
|
|
||||||
- Use UK english spelling
|
|
||||||
- **Follow the mandatory GitHub agent disclosure rule above** on every GitHub action — no exceptions
|
|
||||||
|
|
||||||
❌ **DON'T:**
|
|
||||||
- Git add or commit
|
|
||||||
- Add comments to functions that are self-explanatory
|
|
||||||
- **Post to GitHub without the mandatory agent disclosure** (PRs, reviews, comments, issues, or any other GitHub interaction)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Adding a New Encoder/Decoder
|
# Adding a New Encoder/Decoder
|
||||||
|
|
||||||
This guide explains how to add support for a new format (encoder/decoder) to yq without modifying `candidate_node.go`.
|
This guide explains how to add support for a new format (encoder/decoder) to yq without modifying `candidate_node.go`.
|
||||||
@ -161,7 +69,6 @@ Create a test file `pkg/yqlib/<format>_test.go` using the `formatScenario` patte
|
|||||||
- `scenarioType` can be `"decode"` (test decoding to YAML) or `"roundtrip"` (encode/decode preservation)
|
- `scenarioType` can be `"decode"` (test decoding to YAML) or `"roundtrip"` (encode/decode preservation)
|
||||||
- Create a helper function `test<Format>Scenario()` that switches on `scenarioType`
|
- Create a helper function `test<Format>Scenario()` that switches on `scenarioType`
|
||||||
- Create main test function `Test<Format>FormatScenarios()` that iterates over scenarios
|
- Create main test function `Test<Format>FormatScenarios()` that iterates over scenarios
|
||||||
- The main test function should use `documentScenarios` to ensure testcase documentation is generated.
|
|
||||||
|
|
||||||
Test coverage must include:
|
Test coverage must include:
|
||||||
- Basic data types (scalars, arrays, objects/maps)
|
- Basic data types (scalars, arrays, objects/maps)
|
||||||
@ -276,6 +183,14 @@ Tests must be implemented in `<format>_test.go` following the `formatScenario` p
|
|||||||
|
|
||||||
## Common Patterns
|
## Common Patterns
|
||||||
|
|
||||||
|
### Scalar-Only Formats
|
||||||
|
Some formats only work with scalars (like base64, uri):
|
||||||
|
```go
|
||||||
|
if node.guessTagFromCustomType() != "!!str" {
|
||||||
|
return fmt.Errorf("cannot encode %v as <format>, can only operate on strings", node.Tag)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Format with Indentation
|
### Format with Indentation
|
||||||
Use preferences to control output formatting:
|
Use preferences to control output formatting:
|
||||||
```go
|
```go
|
||||||
@ -417,7 +332,6 @@ Create `pkg/yqlib/operator_<type>_test.go` using the `expressionScenario` patter
|
|||||||
- Include `subdescription` for longer test names
|
- Include `subdescription` for longer test names
|
||||||
- Set `expectedError` if testing error cases
|
- Set `expectedError` if testing error cases
|
||||||
- Create main test function that iterates over scenarios
|
- Create main test function that iterates over scenarios
|
||||||
- The main test function should use `documentScenarios` to ensure testcase documentation is generated.
|
|
||||||
|
|
||||||
Test coverage must include:
|
Test coverage must include:
|
||||||
- Basic data types and nested structures
|
- Basic data types and nested structures
|
||||||
@ -60,7 +60,7 @@ func evaluateAll(cmd *cobra.Command, args []string) (cmdError error) {
|
|||||||
out := cmd.OutOrStdout()
|
out := cmd.OutOrStdout()
|
||||||
|
|
||||||
if writeInplace {
|
if writeInplace {
|
||||||
// only use colours if its forced
|
// only use colors if its forced
|
||||||
colorsEnabled = forceColor
|
colorsEnabled = forceColor
|
||||||
writeInPlaceHandler := yqlib.NewWriteInPlaceHandler(args[0])
|
writeInPlaceHandler := yqlib.NewWriteInPlaceHandler(args[0])
|
||||||
out, err = writeInPlaceHandler.CreateTempFile()
|
out, err = writeInPlaceHandler.CreateTempFile()
|
||||||
@ -101,15 +101,12 @@ func evaluateAll(cmd *cobra.Command, args []string) (cmdError error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if frontMatter != "" {
|
if frontMatter != "" {
|
||||||
originalFilename := args[0]
|
|
||||||
frontMatterHandler := yqlib.NewFrontMatterHandler(args[0])
|
frontMatterHandler := yqlib.NewFrontMatterHandler(args[0])
|
||||||
err = frontMatterHandler.Split()
|
err = frontMatterHandler.Split()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
args[0] = frontMatterHandler.GetYamlFrontMatterFilename()
|
args[0] = frontMatterHandler.GetYamlFrontMatterFilename()
|
||||||
yqlib.SetFilenameAlias(args[0], originalFilename)
|
|
||||||
defer yqlib.ClearFilenameAliases()
|
|
||||||
|
|
||||||
if frontMatter == "process" {
|
if frontMatter == "process" {
|
||||||
reader := frontMatterHandler.GetContentReader()
|
reader := frontMatterHandler.GetContentReader()
|
||||||
|
|||||||
@ -13,7 +13,6 @@ func TestCreateEvaluateAllCommand(t *testing.T) {
|
|||||||
|
|
||||||
if cmd == nil {
|
if cmd == nil {
|
||||||
t.Fatal("createEvaluateAllCommand returned nil")
|
t.Fatal("createEvaluateAllCommand returned nil")
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test basic command properties
|
// Test basic command properties
|
||||||
|
|||||||
@ -74,7 +74,7 @@ func evaluateSequence(cmd *cobra.Command, args []string) (cmdError error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if writeInplace {
|
if writeInplace {
|
||||||
// only use colours if its forced
|
// only use colors if its forced
|
||||||
colorsEnabled = forceColor
|
colorsEnabled = forceColor
|
||||||
writeInPlaceHandler := yqlib.NewWriteInPlaceHandler(args[0])
|
writeInPlaceHandler := yqlib.NewWriteInPlaceHandler(args[0])
|
||||||
out, err = writeInPlaceHandler.CreateTempFile()
|
out, err = writeInPlaceHandler.CreateTempFile()
|
||||||
@ -122,15 +122,12 @@ func evaluateSequence(cmd *cobra.Command, args []string) (cmdError error) {
|
|||||||
|
|
||||||
if frontMatter != "" {
|
if frontMatter != "" {
|
||||||
yqlib.GetLogger().Debug("using front matter handler")
|
yqlib.GetLogger().Debug("using front matter handler")
|
||||||
originalFilename := args[0]
|
|
||||||
frontMatterHandler := yqlib.NewFrontMatterHandler(args[0])
|
frontMatterHandler := yqlib.NewFrontMatterHandler(args[0])
|
||||||
err = frontMatterHandler.Split()
|
err = frontMatterHandler.Split()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
args[0] = frontMatterHandler.GetYamlFrontMatterFilename()
|
args[0] = frontMatterHandler.GetYamlFrontMatterFilename()
|
||||||
yqlib.SetFilenameAlias(args[0], originalFilename)
|
|
||||||
defer yqlib.ClearFilenameAliases()
|
|
||||||
|
|
||||||
if frontMatter == "process" {
|
if frontMatter == "process" {
|
||||||
reader := frontMatterHandler.GetContentReader()
|
reader := frontMatterHandler.GetContentReader()
|
||||||
|
|||||||
@ -13,7 +13,6 @@ func TestCreateEvaluateSequenceCommand(t *testing.T) {
|
|||||||
|
|
||||||
if cmd == nil {
|
if cmd == nil {
|
||||||
t.Fatal("createEvaluateSequenceCommand returned nil")
|
t.Fatal("createEvaluateSequenceCommand returned nil")
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test basic command properties
|
// Test basic command properties
|
||||||
|
|||||||
31
cmd/root.go
31
cmd/root.go
@ -2,12 +2,12 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/mikefarah/yq/v4/pkg/yqlib"
|
"github.com/mikefarah/yq/v4/pkg/yqlib"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
logging "gopkg.in/op/go-logging.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
type runeValue rune
|
type runeValue rune
|
||||||
@ -68,21 +68,30 @@ yq -P -oy sample.json
|
|||||||
},
|
},
|
||||||
PersistentPreRunE: func(cmd *cobra.Command, _ []string) error {
|
PersistentPreRunE: func(cmd *cobra.Command, _ []string) error {
|
||||||
cmd.SetOut(cmd.OutOrStdout())
|
cmd.SetOut(cmd.OutOrStdout())
|
||||||
|
level := logging.WARNING
|
||||||
|
stringFormat := `[%{level}] %{color}%{time:15:04:05}%{color:reset} %{message}`
|
||||||
|
|
||||||
// when NO_COLOR environment variable presents and not an empty string the coloured output should be disabled;
|
// when NO_COLOR environment variable presents and not an empty string the coloured output should be disabled;
|
||||||
// refer to no-color.org
|
// refer to no-color.org
|
||||||
forceNoColor = forceNoColor || os.Getenv("NO_COLOR") != ""
|
forceNoColor = forceNoColor || os.Getenv("NO_COLOR") != ""
|
||||||
|
|
||||||
level := slog.LevelWarn
|
if verbose && forceNoColor {
|
||||||
if verbose {
|
level = logging.DEBUG
|
||||||
level = slog.LevelDebug
|
stringFormat = `[%{level:5.5s}] %{time:15:04:05} %{shortfile:-33s} %{shortfunc:-25s} %{message}`
|
||||||
|
} else if verbose {
|
||||||
|
level = logging.DEBUG
|
||||||
|
stringFormat = `[%{level:5.5s}] %{color}%{time:15:04:05}%{color:bold} %{shortfile:-33s} %{shortfunc:-25s}%{color:reset} %{message}`
|
||||||
|
} else if forceNoColor {
|
||||||
|
stringFormat = `[%{level}] %{time:15:04:05} %{message}`
|
||||||
}
|
}
|
||||||
|
|
||||||
yqlib.GetLogger().SetLevel(level)
|
var format = logging.MustStringFormatter(stringFormat)
|
||||||
opts := &slog.HandlerOptions{Level: level, AddSource: verbose}
|
var backend = logging.AddModuleLevel(
|
||||||
handler := slog.NewTextHandler(os.Stderr, opts)
|
logging.NewBackendFormatter(logging.NewLogBackend(os.Stderr, "", 0), format))
|
||||||
yqlib.GetLogger().SetSlogger(slog.New(handler))
|
|
||||||
|
|
||||||
|
backend.SetLevel(level, "")
|
||||||
|
|
||||||
|
logging.SetBackend(backend)
|
||||||
yqlib.InitExpressionParser()
|
yqlib.InitExpressionParser()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -156,8 +165,6 @@ 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.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.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().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)")
|
rootCmd.PersistentFlags().BoolVar(&yqlib.ConfiguredPropertiesPreferences.UseArrayBrackets, "properties-array-brackets", yqlib.ConfiguredPropertiesPreferences.UseArrayBrackets, "use [x] in array paths (e.g. for SpringBoot)")
|
||||||
|
|
||||||
@ -177,7 +184,7 @@ yq -P -oy sample.json
|
|||||||
}
|
}
|
||||||
rootCmd.Flags().BoolVarP(&version, "version", "V", false, "Print version information and quit")
|
rootCmd.Flags().BoolVarP(&version, "version", "V", false, "Print version information and quit")
|
||||||
rootCmd.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the file in place of first file given.")
|
rootCmd.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the file in place of first file given.")
|
||||||
rootCmd.PersistentFlags().VarP(unwrapScalarFlag, "unwrapScalar", "r", "unwrap scalar, print the value with no quotes, colours or comments. Defaults to true for yaml")
|
rootCmd.PersistentFlags().VarP(unwrapScalarFlag, "unwrapScalar", "r", "unwrap scalar, print the value with no quotes, colors or comments. Defaults to true for yaml")
|
||||||
rootCmd.PersistentFlags().Lookup("unwrapScalar").NoOptDefVal = "true"
|
rootCmd.PersistentFlags().Lookup("unwrapScalar").NoOptDefVal = "true"
|
||||||
rootCmd.PersistentFlags().BoolVarP(&nulSepOutput, "nul-output", "0", false, "Use NUL char to separate values. If unwrap scalar is also set, fail if unwrapped scalar contains NUL char.")
|
rootCmd.PersistentFlags().BoolVarP(&nulSepOutput, "nul-output", "0", false, "Use NUL char to separate values. If unwrap scalar is also set, fail if unwrapped scalar contains NUL char.")
|
||||||
|
|
||||||
@ -196,7 +203,6 @@ yq -P -oy sample.json
|
|||||||
}
|
}
|
||||||
rootCmd.PersistentFlags().BoolVarP(&yqlib.ConfiguredYamlPreferences.LeadingContentPreProcessing, "header-preprocess", "", true, "Slurp any header comments and separators before processing expression.")
|
rootCmd.PersistentFlags().BoolVarP(&yqlib.ConfiguredYamlPreferences.LeadingContentPreProcessing, "header-preprocess", "", true, "Slurp any header comments and separators before processing expression.")
|
||||||
rootCmd.PersistentFlags().BoolVarP(&yqlib.ConfiguredYamlPreferences.FixMergeAnchorToSpec, "yaml-fix-merge-anchor-to-spec", "", false, "Fix merge anchor to match YAML spec. Will default to true in late 2025")
|
rootCmd.PersistentFlags().BoolVarP(&yqlib.ConfiguredYamlPreferences.FixMergeAnchorToSpec, "yaml-fix-merge-anchor-to-spec", "", false, "Fix merge anchor to match YAML spec. Will default to true in late 2025")
|
||||||
rootCmd.PersistentFlags().BoolVarP(&yqlib.ConfiguredYamlPreferences.CompactSequenceIndent, "yaml-compact-seq-indent", "c", false, "Use compact sequence indentation where '- ' is considered part of the indentation.")
|
|
||||||
|
|
||||||
rootCmd.PersistentFlags().StringVarP(&splitFileExp, "split-exp", "s", "", "print each result (or doc) into a file named (exp). [exp] argument must return a string. You can use $index in the expression as the result counter. The necessary directories will be created.")
|
rootCmd.PersistentFlags().StringVarP(&splitFileExp, "split-exp", "s", "", "print each result (or doc) into a file named (exp). [exp] argument must return a string. You can use $index in the expression as the result counter. The necessary directories will be created.")
|
||||||
if err = rootCmd.RegisterFlagCompletionFunc("split-exp", cobra.NoFileCompletions); err != nil {
|
if err = rootCmd.RegisterFlagCompletionFunc("split-exp", cobra.NoFileCompletions); err != nil {
|
||||||
@ -214,7 +220,6 @@ yq -P -oy sample.json
|
|||||||
|
|
||||||
rootCmd.PersistentFlags().BoolVarP(&yqlib.ConfiguredSecurityPreferences.DisableEnvOps, "security-disable-env-ops", "", false, "Disable env related operations.")
|
rootCmd.PersistentFlags().BoolVarP(&yqlib.ConfiguredSecurityPreferences.DisableEnvOps, "security-disable-env-ops", "", false, "Disable env related operations.")
|
||||||
rootCmd.PersistentFlags().BoolVarP(&yqlib.ConfiguredSecurityPreferences.DisableFileOps, "security-disable-file-ops", "", false, "Disable file related operations (e.g. load)")
|
rootCmd.PersistentFlags().BoolVarP(&yqlib.ConfiguredSecurityPreferences.DisableFileOps, "security-disable-file-ops", "", false, "Disable file related operations (e.g. load)")
|
||||||
rootCmd.PersistentFlags().BoolVarP(&yqlib.ConfiguredSecurityPreferences.EnableSystemOps, "security-enable-system-operator", "", false, "Enable system operator to allow execution of external commands.")
|
|
||||||
|
|
||||||
rootCmd.AddCommand(
|
rootCmd.AddCommand(
|
||||||
createEvaluateSequenceCommand(),
|
createEvaluateSequenceCommand(),
|
||||||
|
|||||||
@ -195,7 +195,6 @@ func TestNew(t *testing.T) {
|
|||||||
|
|
||||||
if rootCmd == nil {
|
if rootCmd == nil {
|
||||||
t.Fatal("New() returned nil")
|
t.Fatal("New() returned nil")
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test basic command properties
|
// Test basic command properties
|
||||||
|
|||||||
25
cmd/utils.go
25
cmd/utils.go
@ -3,12 +3,12 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log/slog"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/mikefarah/yq/v4/pkg/yqlib"
|
"github.com/mikefarah/yq/v4/pkg/yqlib"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"gopkg.in/op/go-logging.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
func isAutomaticOutputFormat() bool {
|
func isAutomaticOutputFormat() bool {
|
||||||
@ -104,8 +104,8 @@ func configureFormats(args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
yqlib.GetLogger().Debugf("Using input format %v", inputFormat)
|
yqlib.GetLogger().Debug("Using input format %v", inputFormat)
|
||||||
yqlib.GetLogger().Debugf("Using output format %v", outputFormat)
|
yqlib.GetLogger().Debug("Using output format %v", outputFormat)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -117,7 +117,7 @@ func configureInputFormat(inputFilename string) error {
|
|||||||
_, err := yqlib.FormatFromString(inputFormat)
|
_, err := yqlib.FormatFromString(inputFormat)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// unknown file type, default to yaml
|
// unknown file type, default to yaml
|
||||||
yqlib.GetLogger().Debugf("Unknown file format extension '%v', defaulting to yaml", inputFormat)
|
yqlib.GetLogger().Debug("Unknown file format extension '%v', defaulting to yaml", inputFormat)
|
||||||
inputFormat = "yaml"
|
inputFormat = "yaml"
|
||||||
if isAutomaticOutputFormat() {
|
if isAutomaticOutputFormat() {
|
||||||
outputFormat = "yaml"
|
outputFormat = "yaml"
|
||||||
@ -132,7 +132,7 @@ func configureInputFormat(inputFilename string) error {
|
|||||||
//
|
//
|
||||||
outputFormat = yqlib.FormatStringFromFilename(inputFilename)
|
outputFormat = yqlib.FormatStringFromFilename(inputFilename)
|
||||||
if inputFilename != "-" {
|
if inputFilename != "-" {
|
||||||
yqlib.GetLogger().Warningf("yq default output is now 'auto' (based on the filename extension). Normally yq would output '%v', but for backwards compatibility 'yaml' has been set. Please use -oy to specify yaml, or drop the -p flag.", outputFormat)
|
yqlib.GetLogger().Warning("yq default output is now 'auto' (based on the filename extension). Normally yq would output '%v', but for backwards compatibility 'yaml' has been set. Please use -oy to specify yaml, or drop the -p flag.", outputFormat)
|
||||||
}
|
}
|
||||||
outputFormat = "yaml"
|
outputFormat = "yaml"
|
||||||
}
|
}
|
||||||
@ -166,9 +166,6 @@ func configureDecoder(evaluateTogether bool) (yqlib.Decoder, error) {
|
|||||||
}
|
}
|
||||||
yqlib.ConfiguredYamlPreferences.EvaluateTogether = evaluateTogether
|
yqlib.ConfiguredYamlPreferences.EvaluateTogether = evaluateTogether
|
||||||
|
|
||||||
if format.DecoderFactory == nil {
|
|
||||||
return nil, fmt.Errorf("no support for %s input format", inputFormat)
|
|
||||||
}
|
|
||||||
yqlibDecoder := format.DecoderFactory()
|
yqlibDecoder := format.DecoderFactory()
|
||||||
if yqlibDecoder == nil {
|
if yqlibDecoder == nil {
|
||||||
return nil, fmt.Errorf("no support for %s input format", inputFormat)
|
return nil, fmt.Errorf("no support for %s input format", inputFormat)
|
||||||
@ -200,23 +197,17 @@ func configureEncoder() (yqlib.Encoder, error) {
|
|||||||
}
|
}
|
||||||
yqlib.ConfiguredXMLPreferences.Indent = indent
|
yqlib.ConfiguredXMLPreferences.Indent = indent
|
||||||
yqlib.ConfiguredYamlPreferences.Indent = indent
|
yqlib.ConfiguredYamlPreferences.Indent = indent
|
||||||
yqlib.ConfiguredKYamlPreferences.Indent = indent
|
|
||||||
yqlib.ConfiguredJSONPreferences.Indent = indent
|
yqlib.ConfiguredJSONPreferences.Indent = indent
|
||||||
|
|
||||||
yqlib.ConfiguredYamlPreferences.UnwrapScalar = unwrapScalar
|
yqlib.ConfiguredYamlPreferences.UnwrapScalar = unwrapScalar
|
||||||
yqlib.ConfiguredKYamlPreferences.UnwrapScalar = unwrapScalar
|
|
||||||
yqlib.ConfiguredPropertiesPreferences.UnwrapScalar = unwrapScalar
|
yqlib.ConfiguredPropertiesPreferences.UnwrapScalar = unwrapScalar
|
||||||
yqlib.ConfiguredJSONPreferences.UnwrapScalar = unwrapScalar
|
yqlib.ConfiguredJSONPreferences.UnwrapScalar = unwrapScalar
|
||||||
yqlib.ConfiguredShellVariablesPreferences.UnwrapScalar = unwrapScalar
|
|
||||||
|
|
||||||
yqlib.ConfiguredYamlPreferences.ColorsEnabled = colorsEnabled
|
yqlib.ConfiguredYamlPreferences.ColorsEnabled = colorsEnabled
|
||||||
yqlib.ConfiguredKYamlPreferences.ColorsEnabled = colorsEnabled
|
|
||||||
yqlib.ConfiguredJSONPreferences.ColorsEnabled = colorsEnabled
|
yqlib.ConfiguredJSONPreferences.ColorsEnabled = colorsEnabled
|
||||||
yqlib.ConfiguredHclPreferences.ColorsEnabled = colorsEnabled
|
yqlib.ConfiguredHclPreferences.ColorsEnabled = colorsEnabled
|
||||||
yqlib.ConfiguredTomlPreferences.ColorsEnabled = colorsEnabled
|
|
||||||
|
|
||||||
yqlib.ConfiguredYamlPreferences.PrintDocSeparators = !noDocSeparators
|
yqlib.ConfiguredYamlPreferences.PrintDocSeparators = !noDocSeparators
|
||||||
yqlib.ConfiguredKYamlPreferences.PrintDocSeparators = !noDocSeparators
|
|
||||||
|
|
||||||
encoder := yqlibOutputFormat.EncoderFactory()
|
encoder := yqlibOutputFormat.EncoderFactory()
|
||||||
|
|
||||||
@ -235,7 +226,7 @@ func maybeFile(str string) bool {
|
|||||||
yqlib.GetLogger().Debugf("checking '%v' is a file", str)
|
yqlib.GetLogger().Debugf("checking '%v' is a file", str)
|
||||||
stat, err := os.Stat(str) // #nosec
|
stat, err := os.Stat(str) // #nosec
|
||||||
result := err == nil && !stat.IsDir()
|
result := err == nil && !stat.IsDir()
|
||||||
if yqlib.GetLogger().IsEnabledFor(slog.LevelDebug) {
|
if yqlib.GetLogger().IsEnabledFor(logging.DEBUG) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
yqlib.GetLogger().Debugf("error: %v", err)
|
yqlib.GetLogger().Debugf("error: %v", err)
|
||||||
} else {
|
} else {
|
||||||
@ -280,7 +271,7 @@ func processArgs(originalArgs []string) (string, []string, error) {
|
|||||||
|
|
||||||
if expressionFile == "" && maybeFirstArgIsAFile && strings.HasSuffix(args[0], ".yq") {
|
if expressionFile == "" && maybeFirstArgIsAFile && strings.HasSuffix(args[0], ".yq") {
|
||||||
// lets check if an expression file was given
|
// lets check if an expression file was given
|
||||||
yqlib.GetLogger().Debugf("Assuming arg %v is an expression file", args[0])
|
yqlib.GetLogger().Debug("Assuming arg %v is an expression file", args[0])
|
||||||
expressionFile = args[0]
|
expressionFile = args[0]
|
||||||
args = args[1:]
|
args = args[1:]
|
||||||
}
|
}
|
||||||
@ -296,7 +287,7 @@ func processArgs(originalArgs []string) (string, []string, error) {
|
|||||||
|
|
||||||
yqlib.GetLogger().Debugf("processed args: %v", args)
|
yqlib.GetLogger().Debugf("processed args: %v", args)
|
||||||
if expression == "" && len(args) > 0 && args[0] != "-" && !maybeFile(args[0]) {
|
if expression == "" && len(args) > 0 && args[0] != "-" && !maybeFile(args[0]) {
|
||||||
yqlib.GetLogger().Debugf("assuming expression is '%v'", args[0])
|
yqlib.GetLogger().Debug("assuming expression is '%v'", args[0])
|
||||||
expression = args[0]
|
expression = args[0]
|
||||||
args = args[1:]
|
args = args[1:]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -911,7 +911,7 @@ func stringsEqual(a, b []string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
for i := range a {
|
for i := range a {
|
||||||
if a[i] != b[i] { //nolint:gosec // G602 false positive: b length equality is checked above
|
if a[i] != b[i] {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -926,13 +926,13 @@ func TestSetupColors(t *testing.T) {
|
|||||||
expectColors bool
|
expectColors bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "force colour enabled",
|
name: "force color enabled",
|
||||||
forceColor: true,
|
forceColor: true,
|
||||||
forceNoColor: false,
|
forceNoColor: false,
|
||||||
expectColors: true,
|
expectColors: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "force no colour enabled",
|
name: "force no color enabled",
|
||||||
forceColor: false,
|
forceColor: false,
|
||||||
forceNoColor: true,
|
forceNoColor: true,
|
||||||
expectColors: false,
|
expectColors: false,
|
||||||
|
|||||||
@ -11,7 +11,7 @@ var (
|
|||||||
GitDescribe string
|
GitDescribe string
|
||||||
|
|
||||||
// Version is main version number that is being run at the moment.
|
// Version is main version number that is being run at the moment.
|
||||||
Version = "v4.53.3"
|
Version = "v4.50.1"
|
||||||
|
|
||||||
// VersionPrerelease is a pre-release marker for the version. If this is "" (empty string)
|
// 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
|
// then it means that it is a final release. Otherwise, this is a pre-release
|
||||||
|
|||||||
@ -1,9 +1,6 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import "testing"
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGetVersionDisplay(t *testing.T) {
|
func TestGetVersionDisplay(t *testing.T) {
|
||||||
var expectedVersion = ProductName + " (https://github.com/mikefarah/yq/) version " + Version
|
var expectedVersion = ProductName + " (https://github.com/mikefarah/yq/) version " + Version
|
||||||
@ -28,18 +25,6 @@ func TestGetVersionDisplay(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Test_getHumanVersion(t *testing.T) {
|
func Test_getHumanVersion(t *testing.T) {
|
||||||
// Save original values
|
|
||||||
origGitDescribe := GitDescribe
|
|
||||||
origGitCommit := GitCommit
|
|
||||||
origVersionPrerelease := VersionPrerelease
|
|
||||||
|
|
||||||
// Restore after test
|
|
||||||
defer func() {
|
|
||||||
GitDescribe = origGitDescribe
|
|
||||||
GitCommit = origGitCommit
|
|
||||||
VersionPrerelease = origVersionPrerelease
|
|
||||||
}()
|
|
||||||
|
|
||||||
GitDescribe = "e42813d"
|
GitDescribe = "e42813d"
|
||||||
GitCommit = "e42813d+CHANGES"
|
GitCommit = "e42813d+CHANGES"
|
||||||
var wanted string
|
var wanted string
|
||||||
@ -64,118 +49,3 @@ func Test_getHumanVersion(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_getHumanVersion_NoGitDescribe(t *testing.T) {
|
|
||||||
// Save original values
|
|
||||||
origGitDescribe := GitDescribe
|
|
||||||
origGitCommit := GitCommit
|
|
||||||
origVersionPrerelease := VersionPrerelease
|
|
||||||
|
|
||||||
// Restore after test
|
|
||||||
defer func() {
|
|
||||||
GitDescribe = origGitDescribe
|
|
||||||
GitCommit = origGitCommit
|
|
||||||
VersionPrerelease = origVersionPrerelease
|
|
||||||
}()
|
|
||||||
|
|
||||||
GitDescribe = ""
|
|
||||||
GitCommit = ""
|
|
||||||
VersionPrerelease = ""
|
|
||||||
|
|
||||||
got := getHumanVersion()
|
|
||||||
if got != Version {
|
|
||||||
t.Errorf("getHumanVersion() = %v, want %v", got, Version)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_getHumanVersion_WithPrerelease(t *testing.T) {
|
|
||||||
// Save original values
|
|
||||||
origGitDescribe := GitDescribe
|
|
||||||
origGitCommit := GitCommit
|
|
||||||
origVersionPrerelease := VersionPrerelease
|
|
||||||
|
|
||||||
// Restore after test
|
|
||||||
defer func() {
|
|
||||||
GitDescribe = origGitDescribe
|
|
||||||
GitCommit = origGitCommit
|
|
||||||
VersionPrerelease = origVersionPrerelease
|
|
||||||
}()
|
|
||||||
|
|
||||||
GitDescribe = ""
|
|
||||||
GitCommit = "abc123"
|
|
||||||
VersionPrerelease = "beta"
|
|
||||||
|
|
||||||
got := getHumanVersion()
|
|
||||||
expected := Version + "-beta (abc123)"
|
|
||||||
if got != expected {
|
|
||||||
t.Errorf("getHumanVersion() = %v, want %v", got, expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_getHumanVersion_PrereleaseInVersion(t *testing.T) {
|
|
||||||
// Save original values
|
|
||||||
origGitDescribe := GitDescribe
|
|
||||||
origGitCommit := GitCommit
|
|
||||||
origVersionPrerelease := VersionPrerelease
|
|
||||||
|
|
||||||
// Restore after test
|
|
||||||
defer func() {
|
|
||||||
GitDescribe = origGitDescribe
|
|
||||||
GitCommit = origGitCommit
|
|
||||||
VersionPrerelease = origVersionPrerelease
|
|
||||||
}()
|
|
||||||
|
|
||||||
GitDescribe = "v1.2.3-rc1"
|
|
||||||
GitCommit = "xyz789"
|
|
||||||
VersionPrerelease = "rc1"
|
|
||||||
|
|
||||||
got := getHumanVersion()
|
|
||||||
// Should not duplicate "rc1" since it's already in GitDescribe
|
|
||||||
expected := "v1.2.3-rc1 (xyz789)"
|
|
||||||
if got != expected {
|
|
||||||
t.Errorf("getHumanVersion() = %v, want %v", got, expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_getHumanVersion_StripSingleQuotes(t *testing.T) {
|
|
||||||
// Save original values
|
|
||||||
origGitDescribe := GitDescribe
|
|
||||||
origGitCommit := GitCommit
|
|
||||||
origVersionPrerelease := VersionPrerelease
|
|
||||||
|
|
||||||
// Restore after test
|
|
||||||
defer func() {
|
|
||||||
GitDescribe = origGitDescribe
|
|
||||||
GitCommit = origGitCommit
|
|
||||||
VersionPrerelease = origVersionPrerelease
|
|
||||||
}()
|
|
||||||
|
|
||||||
GitDescribe = "'v1.2.3'"
|
|
||||||
GitCommit = "'commit123'"
|
|
||||||
VersionPrerelease = ""
|
|
||||||
|
|
||||||
got := getHumanVersion()
|
|
||||||
// Should strip single quotes
|
|
||||||
if strings.Contains(got, "'") {
|
|
||||||
t.Errorf("getHumanVersion() = %v, should not contain single quotes", got)
|
|
||||||
}
|
|
||||||
expected := "v1.2.3"
|
|
||||||
if got != expected {
|
|
||||||
t.Errorf("getHumanVersion() = %v, want %v", got, expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProductName(t *testing.T) {
|
|
||||||
if ProductName != "yq" {
|
|
||||||
t.Errorf("ProductName = %v, want yq", ProductName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVersionIsSet(t *testing.T) {
|
|
||||||
if Version == "" {
|
|
||||||
t.Error("Version should not be empty")
|
|
||||||
}
|
|
||||||
if !strings.HasPrefix(Version, "v") {
|
|
||||||
t.Errorf("Version %v should start with 'v'", Version)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
14
cspell.config.yaml
Normal file
14
cspell.config.yaml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
$schema: https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json
|
||||||
|
version: '0.2'
|
||||||
|
language: en-GB
|
||||||
|
dictionaryDefinitions:
|
||||||
|
- name: project-words
|
||||||
|
path: './project-words.txt'
|
||||||
|
addWords: true
|
||||||
|
dictionaries:
|
||||||
|
- project-words
|
||||||
|
ignorePaths:
|
||||||
|
- 'vendor'
|
||||||
|
- 'bin'
|
||||||
|
- '/project-words.txt'
|
||||||
@ -1,10 +0,0 @@
|
|||||||
# leading
|
|
||||||
{
|
|
||||||
a: 1, # a line
|
|
||||||
# head b
|
|
||||||
b: 2,
|
|
||||||
c: [
|
|
||||||
# head d
|
|
||||||
"d", # d line
|
|
||||||
],
|
|
||||||
}
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
# leading
|
|
||||||
a: 1 # a line
|
|
||||||
# head b
|
|
||||||
b: 2
|
|
||||||
c:
|
|
||||||
# head d
|
|
||||||
- d # d line
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
# main.tf
|
|
||||||
|
|
||||||
# Define required providers and minimum Terraform version
|
|
||||||
terraform {
|
|
||||||
required_providers {
|
|
||||||
aws = {
|
|
||||||
source = "hashicorp/aws"
|
|
||||||
version = "~> 5.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
required_version = ">= 1.2"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Configure the AWS provider
|
|
||||||
provider "aws" {
|
|
||||||
region = var.aws_region
|
|
||||||
}
|
|
||||||
|
|
||||||
# Define an S3 bucket resource
|
|
||||||
resource "aws_s3_bucket" "example_bucket" {
|
|
||||||
bucket = var.bucket_name
|
|
||||||
|
|
||||||
tags = {
|
|
||||||
Environment = "Development"
|
|
||||||
Project = "TerraformExample"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,26 +1,6 @@
|
|||||||
|
[[fruits]]
|
||||||
|
|
||||||
|
[animals]
|
||||||
|
|
||||||
# This is a TOML document
|
[[fruits.varieties]] # nested array of tables
|
||||||
|
name = "red delicious"
|
||||||
title = "TOML Example"
|
|
||||||
|
|
||||||
[owner]
|
|
||||||
name = "Tom Preston-Werner"
|
|
||||||
dob = 1979-05-27T07:32:00-08:00
|
|
||||||
|
|
||||||
[database]
|
|
||||||
enabled = true
|
|
||||||
ports = [ 8000, 8001, 8002 ]
|
|
||||||
data = [ ["delta", "phi"], [3.14] ]
|
|
||||||
temp_targets = { cpu = 79.5, case = 72.0 }
|
|
||||||
|
|
||||||
[servers]
|
|
||||||
|
|
||||||
[servers.alpha]
|
|
||||||
ip = "10.0.0.1"
|
|
||||||
role = "frontend"
|
|
||||||
|
|
||||||
[servers.beta]
|
|
||||||
ip = "10.0.0.2"
|
|
||||||
role = "backend"
|
|
||||||
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
FROM mikefarah/yq:4@sha256:11a1f0b604b13dbbdc662260d8db6f644b22d8553122a25c1b5b2e8713ca6977
|
FROM mikefarah/yq:4
|
||||||
|
|
||||||
COPY entrypoint.sh /entrypoint.sh
|
COPY entrypoint.sh /entrypoint.sh
|
||||||
|
|
||||||
|
|||||||
31
go.mod
31
go.mod
@ -6,23 +6,23 @@ require (
|
|||||||
github.com/alecthomas/repr v0.5.2
|
github.com/alecthomas/repr v0.5.2
|
||||||
github.com/dimchansky/utfbom v1.1.1
|
github.com/dimchansky/utfbom v1.1.1
|
||||||
github.com/elliotchance/orderedmap v1.8.0
|
github.com/elliotchance/orderedmap v1.8.0
|
||||||
github.com/fatih/color v1.19.0
|
github.com/fatih/color v1.18.0
|
||||||
github.com/go-ini/ini v1.67.0
|
github.com/go-ini/ini v1.67.0
|
||||||
github.com/goccy/go-json v0.10.6
|
github.com/goccy/go-json v0.10.5
|
||||||
github.com/goccy/go-yaml v1.19.2
|
github.com/goccy/go-yaml v1.19.0
|
||||||
github.com/hashicorp/hcl/v2 v2.24.0
|
github.com/hashicorp/hcl/v2 v2.24.0
|
||||||
github.com/jinzhu/copier v0.4.0
|
github.com/jinzhu/copier v0.4.0
|
||||||
github.com/magiconair/properties v1.8.10
|
github.com/magiconair/properties v1.8.10
|
||||||
github.com/pelletier/go-toml/v2 v2.4.2
|
github.com/pelletier/go-toml/v2 v2.2.4
|
||||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e
|
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e
|
||||||
github.com/spf13/cobra v1.10.2
|
github.com/spf13/cobra v1.10.2
|
||||||
github.com/spf13/pflag v1.0.10
|
github.com/spf13/pflag v1.0.10
|
||||||
github.com/yuin/gopher-lua v1.1.2
|
github.com/yuin/gopher-lua v1.1.1
|
||||||
github.com/zclconf/go-cty v1.18.1
|
github.com/zclconf/go-cty v1.17.0
|
||||||
go.yaml.in/yaml/v4 v4.0.0-rc.6
|
go.yaml.in/yaml/v4 v4.0.0-rc.3
|
||||||
golang.org/x/mod v0.37.0
|
golang.org/x/net v0.48.0
|
||||||
golang.org/x/net v0.56.0
|
golang.org/x/text v0.32.0
|
||||||
golang.org/x/text v0.38.0
|
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@ -33,9 +33,12 @@ require (
|
|||||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
|
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
|
||||||
golang.org/x/sync v0.21.0 // indirect
|
golang.org/x/mod v0.30.0 // indirect
|
||||||
golang.org/x/sys v0.46.0 // indirect
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
golang.org/x/tools v0.45.0 // indirect
|
golang.org/x/sys v0.39.0 // indirect
|
||||||
|
golang.org/x/tools v0.39.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
go 1.25.0
|
go 1.24.0
|
||||||
|
|
||||||
|
toolchain go1.24.1
|
||||||
|
|||||||
54
go.sum
54
go.sum
@ -18,16 +18,16 @@ github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi
|
|||||||
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
|
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
|
||||||
github.com/elliotchance/orderedmap v1.8.0 h1:TrOREecvh3JbS+NCgwposXG5ZTFHtEsQiCGOhPElnMw=
|
github.com/elliotchance/orderedmap v1.8.0 h1:TrOREecvh3JbS+NCgwposXG5ZTFHtEsQiCGOhPElnMw=
|
||||||
github.com/elliotchance/orderedmap v1.8.0/go.mod h1:wsDwEaX5jEoyhbs7x93zk2H/qv0zwuhg4inXhDkYqys=
|
github.com/elliotchance/orderedmap v1.8.0/go.mod h1:wsDwEaX5jEoyhbs7x93zk2H/qv0zwuhg4inXhDkYqys=
|
||||||
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
|
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||||
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
|
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||||
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
|
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
|
||||||
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||||
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
|
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
|
||||||
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
||||||
github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU=
|
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||||
github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
|
github.com/goccy/go-yaml v1.19.0 h1:EmkZ9RIsX+Uq4DYFowegAuJo8+xdX3T/2dwNPXbxEYE=
|
||||||
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
github.com/goccy/go-yaml v1.19.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/hashicorp/hcl/v2 v2.24.0 h1:2QJdZ454DSsYGoaE6QheQZjtKZSUs9Nh2izTWiwQxvE=
|
github.com/hashicorp/hcl/v2 v2.24.0 h1:2QJdZ454DSsYGoaE6QheQZjtKZSUs9Nh2izTWiwQxvE=
|
||||||
@ -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/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 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
|
||||||
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
|
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
|
||||||
github.com/pelletier/go-toml/v2 v2.4.2 h1:M2fKKbmyvI+hGId/D0W64qDBMVhJnNR10O5gIbMc//Q=
|
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||||
github.com/pelletier/go-toml/v2 v2.4.2/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
github.com/pelletier/go-toml/v2 v2.2.4/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 h1:aoZm08cpOy4WuID//EZDgcC4zIxODThtZNPirFr42+A=
|
||||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
@ -61,28 +61,30 @@ github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3A
|
|||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
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.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
|
||||||
github.com/yuin/gopher-lua v1.1.2/go.mod h1:7aRmXIWl37SqRf0koeyylBEzJ+aPt8A+mmkQ4f1ntR8=
|
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
|
||||||
github.com/zclconf/go-cty v1.18.1 h1:yEGE8M4iIZlyKQURZNb2SnEyZlZHUcBCnx6KF81KuwM=
|
github.com/zclconf/go-cty v1.17.0 h1:seZvECve6XX4tmnvRzWtJNHdscMtYEx5R7bnnVyd/d0=
|
||||||
github.com/zclconf/go-cty v1.18.1/go.mod h1:qpnV6EDNgC1sns/AleL1fvatHw72j+S+nS+MJ+T2CSg=
|
github.com/zclconf/go-cty v1.17.0/go.mod h1:wqFzcImaLTI6A5HfsRwB0nj5n0MRZFwmey8YoFPPs3U=
|
||||||
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 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo=
|
||||||
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
|
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/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||||
go.yaml.in/yaml/v4 v4.0.0-rc.6 h1:1h7H1ohdUh93/FyE4YaDa1Zh64K6VVbjF4K6WUxMtH4=
|
go.yaml.in/yaml/v4 v4.0.0-rc.3 h1:3h1fjsh1CTAPjW7q/EMe+C8shx5d8ctzZTrLcs/j8Go=
|
||||||
go.yaml.in/yaml/v4 v4.0.0-rc.6/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
|
go.yaml.in/yaml/v4 v4.0.0-rc.3/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
|
||||||
golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
|
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
|
||||||
golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
|
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
|
||||||
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
|
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||||
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
|
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
||||||
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
|
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||||
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
|
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||||
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
|
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||||
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
|
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||||
golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8=
|
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
|
||||||
golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0=
|
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473 h1:6D+BvnJ/j6e222UW8s2qTSe3wGBtvo0MbVQG/c5k8RE=
|
||||||
|
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473/go.mod h1:N1eN2tsCx0Ydtgjl4cqmbRCsY4/+z4cYDeqwZTk6zog=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
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=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
@ -1,30 +0,0 @@
|
|||||||
//go:build goinstall
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"golang.org/x/mod/module"
|
|
||||||
"golang.org/x/mod/zip"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TestGoInstallCompatibility ensures the module can be zipped for go install.
|
|
||||||
// This is an integration test that uses the same zip.CreateFromDir function
|
|
||||||
// that go install uses internally. If this test fails, go install will fail.
|
|
||||||
//
|
|
||||||
// Built with the goinstall tag and run after the main test suite (see scripts/test.sh)
|
|
||||||
// so it does not race with pkg/yqlib tests that rewrite doc/*.md during execution.
|
|
||||||
//
|
|
||||||
// See: https://github.com/mikefarah/yq/issues/2587
|
|
||||||
func TestGoInstallCompatibility(t *testing.T) {
|
|
||||||
mod := module.Version{
|
|
||||||
Path: "github.com/mikefarah/yq/v4",
|
|
||||||
Version: "v4.0.0", // the actual version doesn't matter for validation
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := zip.CreateFromDir(io.Discard, mod, "."); err != nil {
|
|
||||||
t.Fatalf("Module cannot be zipped for go install: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -54,25 +54,3 @@ func TestAllAtOnceEvaluateNodes(t *testing.T) {
|
|||||||
test.AssertResultComplex(t, tt.expected, resultsToString(t, list))
|
test.AssertResultComplex(t, tt.expected, resultsToString(t, list))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTomlDecoderCanBeReinitializedAcrossDocuments(t *testing.T) {
|
|
||||||
decoder := NewTomlDecoder()
|
|
||||||
|
|
||||||
firstDocuments, err := ReadDocuments(strings.NewReader("id = \"Foobar\"\n"), decoder)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to read first TOML document: %v", err)
|
|
||||||
}
|
|
||||||
if firstDocuments.Len() != 1 {
|
|
||||||
t.Fatalf("expected first document count to be 1, got %d", firstDocuments.Len())
|
|
||||||
}
|
|
||||||
test.AssertResult(t, "Foobar", firstDocuments.Front().Value.(*CandidateNode).Content[1].Value)
|
|
||||||
|
|
||||||
secondDocuments, err := ReadDocuments(strings.NewReader("id = \"Banana\"\n"), decoder)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to read second TOML document: %v", err)
|
|
||||||
}
|
|
||||||
if secondDocuments.Len() != 1 {
|
|
||||||
t.Fatalf("expected second document count to be 1, got %d", secondDocuments.Len())
|
|
||||||
}
|
|
||||||
test.AssertResult(t, "Banana", secondDocuments.Front().Value.(*CandidateNode).Content[1].Value)
|
|
||||||
}
|
|
||||||
|
|||||||
@ -27,30 +27,10 @@ const (
|
|||||||
FlowStyle
|
FlowStyle
|
||||||
)
|
)
|
||||||
|
|
||||||
// EncodeHint controls how a mapping node is serialised by format-specific encoders
|
|
||||||
// that distinguish between inline and block/section representations (e.g. TOML, HCL).
|
|
||||||
type EncodeHint int
|
|
||||||
|
|
||||||
const (
|
|
||||||
// EncodeHintDefault lets the encoder choose the representation (e.g. TOML block
|
|
||||||
// mappings default to [section] headers).
|
|
||||||
EncodeHintDefault EncodeHint = iota
|
|
||||||
// EncodeHintSeparateBlock forces the node to be emitted as a separate block or
|
|
||||||
// table-section header (used by TOML [section] and HCL block decoders).
|
|
||||||
EncodeHintSeparateBlock
|
|
||||||
// EncodeHintInline forces the node to be emitted as an inline / flow table
|
|
||||||
// (used by TOML inline-table decoder and TOML encoder).
|
|
||||||
EncodeHintInline
|
|
||||||
)
|
|
||||||
|
|
||||||
func createStringScalarNode(stringValue string) *CandidateNode {
|
func createStringScalarNode(stringValue string) *CandidateNode {
|
||||||
var node = &CandidateNode{Kind: ScalarNode}
|
var node = &CandidateNode{Kind: ScalarNode}
|
||||||
node.Value = stringValue
|
node.Value = stringValue
|
||||||
if stringValue == "<<" {
|
node.Tag = "!!str"
|
||||||
node.Tag = "!!merge"
|
|
||||||
} else {
|
|
||||||
node.Tag = "!!str"
|
|
||||||
}
|
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,9 +97,9 @@ type CandidateNode struct {
|
|||||||
// (e.g. top level cross document merge). This property does not propagate to child nodes.
|
// (e.g. top level cross document merge). This property does not propagate to child nodes.
|
||||||
EvaluateTogether bool
|
EvaluateTogether bool
|
||||||
IsMapKey bool
|
IsMapKey bool
|
||||||
// EncodeHint controls how a mapping node is serialised by format-specific encoders
|
// For formats like HCL and TOML: indicates that child entries should be emitted as separate blocks/tables
|
||||||
// (e.g. TOML, HCL) that support both inline and block/section representations.
|
// rather than consolidated into nested mappings (default behaviour)
|
||||||
EncodeHint EncodeHint
|
EncodeSeparate bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *CandidateNode) CreateChild() *CandidateNode {
|
func (n *CandidateNode) CreateChild() *CandidateNode {
|
||||||
@ -300,7 +280,7 @@ func (n *CandidateNode) AddChild(rawChild *CandidateNode) {
|
|||||||
|
|
||||||
func (n *CandidateNode) AddChildren(children []*CandidateNode) {
|
func (n *CandidateNode) AddChildren(children []*CandidateNode) {
|
||||||
if n.Kind == MappingNode {
|
if n.Kind == MappingNode {
|
||||||
for i := 0; i < len(children)-1; i += 2 {
|
for i := 0; i < len(children); i += 2 {
|
||||||
key := children[i]
|
key := children[i]
|
||||||
value := children[i+1]
|
value := children[i+1]
|
||||||
n.AddKeyValueChild(key, value)
|
n.AddKeyValueChild(key, value)
|
||||||
@ -343,11 +323,11 @@ func (n *CandidateNode) guessTagFromCustomType() string {
|
|||||||
dataBucket, errorReading := parseSnippet(n.Value)
|
dataBucket, errorReading := parseSnippet(n.Value)
|
||||||
|
|
||||||
if errorReading != nil {
|
if errorReading != nil {
|
||||||
log.Debugf("guessTagFromCustomType: could not guess underlying tag type %v", errorReading)
|
log.Debug("guessTagFromCustomType: could not guess underlying tag type %v", errorReading)
|
||||||
return n.Tag
|
return n.Tag
|
||||||
}
|
}
|
||||||
guessedTag := dataBucket.Tag
|
guessedTag := dataBucket.Tag
|
||||||
log.Infof("im guessing the tag %v is a %v", n.Tag, guessedTag)
|
log.Info("im guessing the tag %v is a %v", n.Tag, guessedTag)
|
||||||
return guessedTag
|
return guessedTag
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -431,7 +411,7 @@ func (n *CandidateNode) doCopy(cloneContent bool) *CandidateNode {
|
|||||||
EvaluateTogether: n.EvaluateTogether,
|
EvaluateTogether: n.EvaluateTogether,
|
||||||
IsMapKey: n.IsMapKey,
|
IsMapKey: n.IsMapKey,
|
||||||
|
|
||||||
EncodeHint: n.EncodeHint,
|
EncodeSeparate: n.EncodeSeparate,
|
||||||
}
|
}
|
||||||
|
|
||||||
if cloneContent {
|
if cloneContent {
|
||||||
@ -465,7 +445,7 @@ func (n *CandidateNode) UpdateFrom(other *CandidateNode, prefs assignPreferences
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (n *CandidateNode) UpdateAttributesFrom(other *CandidateNode, prefs assignPreferences) {
|
func (n *CandidateNode) UpdateAttributesFrom(other *CandidateNode, prefs assignPreferences) {
|
||||||
log.Debugf("UpdateAttributesFrom: n: %v other: %v", NodeToString(n), NodeToString(other))
|
log.Debug("UpdateAttributesFrom: n: %v other: %v", NodeToString(n), NodeToString(other))
|
||||||
if n.Kind != other.Kind {
|
if n.Kind != other.Kind {
|
||||||
// clear out the contents when switching to a different type
|
// clear out the contents when switching to a different type
|
||||||
// e.g. map to array
|
// e.g. map to array
|
||||||
@ -485,9 +465,6 @@ func (n *CandidateNode) UpdateAttributesFrom(other *CandidateNode, prefs assignP
|
|||||||
n.Anchor = other.Anchor
|
n.Anchor = other.Anchor
|
||||||
}
|
}
|
||||||
|
|
||||||
// Preserve EncodeHint for format-specific encoding hints
|
|
||||||
n.EncodeHint = other.EncodeHint
|
|
||||||
|
|
||||||
// merge will pickup the style of the new thing
|
// merge will pickup the style of the new thing
|
||||||
// when autocreating nodes
|
// when autocreating nodes
|
||||||
|
|
||||||
|
|||||||
@ -36,13 +36,13 @@ func (o *CandidateNode) UnmarshalGoccyYAML(node ast.Node, cm yaml.CommentMap, an
|
|||||||
switch commentMapComment.Position {
|
switch commentMapComment.Position {
|
||||||
case yaml.CommentHeadPosition:
|
case yaml.CommentHeadPosition:
|
||||||
o.HeadComment = comment.String()
|
o.HeadComment = comment.String()
|
||||||
log.Debugf("its a head comment %v", comment.String())
|
log.Debug("its a head comment %v", comment.String())
|
||||||
case yaml.CommentLinePosition:
|
case yaml.CommentLinePosition:
|
||||||
o.LineComment = comment.String()
|
o.LineComment = comment.String()
|
||||||
log.Debugf("its a line comment %v", comment.String())
|
log.Debug("its a line comment %v", comment.String())
|
||||||
case yaml.CommentFootPosition:
|
case yaml.CommentFootPosition:
|
||||||
o.FootComment = comment.String()
|
o.FootComment = comment.String()
|
||||||
log.Debugf("its a foot comment %v", comment.String())
|
log.Debug("its a foot comment %v", comment.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -93,8 +93,8 @@ func (o *CandidateNode) UnmarshalGoccyYAML(node ast.Node, cm yaml.CommentMap, an
|
|||||||
log.Debugf("folded Type %v", astLiteral.Start.Type)
|
log.Debugf("folded Type %v", astLiteral.Start.Type)
|
||||||
o.Style = FoldedStyle
|
o.Style = FoldedStyle
|
||||||
}
|
}
|
||||||
log.Debugf("start value: %v ", node.(*ast.LiteralNode).Start.Value)
|
log.Debug("start value: %v ", node.(*ast.LiteralNode).Start.Value)
|
||||||
log.Debugf("start value: %v ", node.(*ast.LiteralNode).Start.Type)
|
log.Debug("start value: %v ", node.(*ast.LiteralNode).Start.Type)
|
||||||
// TODO: here I could put the original value with line breaks
|
// TODO: here I could put the original value with line breaks
|
||||||
// to solve the multiline > problem
|
// to solve the multiline > problem
|
||||||
o.Value = astLiteral.Value.Value
|
o.Value = astLiteral.Value.Value
|
||||||
@ -187,7 +187,7 @@ func (o *CandidateNode) UnmarshalGoccyYAML(node ast.Node, cm yaml.CommentMap, an
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (o *CandidateNode) goccyProcessMappingValueNode(mappingEntry *ast.MappingValueNode, cm yaml.CommentMap, anchorMap map[string]*CandidateNode) error {
|
func (o *CandidateNode) goccyProcessMappingValueNode(mappingEntry *ast.MappingValueNode, cm yaml.CommentMap, anchorMap map[string]*CandidateNode) error {
|
||||||
log.Debugf("UnmarshalYAML MAP KEY entry %v", mappingEntry.Key)
|
log.Debug("UnmarshalYAML MAP KEY entry %v", mappingEntry.Key)
|
||||||
|
|
||||||
// AddKeyValueFirst because it clones the nodes, and we want to have the real refs when Unmarshalling
|
// AddKeyValueFirst because it clones the nodes, and we want to have the real refs when Unmarshalling
|
||||||
// particularly for the anchorMap
|
// particularly for the anchorMap
|
||||||
@ -197,7 +197,7 @@ func (o *CandidateNode) goccyProcessMappingValueNode(mappingEntry *ast.MappingVa
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("UnmarshalYAML MAP VALUE entry %v", mappingEntry.Value)
|
log.Debug("UnmarshalYAML MAP VALUE entry %v", mappingEntry.Value)
|
||||||
if err := valueNode.UnmarshalGoccyYAML(mappingEntry.Value, cm, anchorMap); err != nil {
|
if err := valueNode.UnmarshalGoccyYAML(mappingEntry.Value, cm, anchorMap); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -106,31 +106,6 @@ func TestCreateScalarNodeScenarios(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type createStringScalarNodeScenario struct {
|
|
||||||
stringValue string
|
|
||||||
expectedTag string
|
|
||||||
}
|
|
||||||
|
|
||||||
var createStringScalarNodeScenarios = []createStringScalarNodeScenario{
|
|
||||||
{
|
|
||||||
stringValue: "yourKey",
|
|
||||||
expectedTag: "!!str",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
stringValue: "<<",
|
|
||||||
expectedTag: "!!merge",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCreateStringScalarNodeScenarios(t *testing.T) {
|
|
||||||
for _, tt := range createStringScalarNodeScenarios {
|
|
||||||
actual := createStringScalarNode(tt.stringValue)
|
|
||||||
test.AssertResultWithContext(t, tt.stringValue, actual.Value, fmt.Sprintf("Value for: %v", tt.stringValue))
|
|
||||||
test.AssertResultWithContext(t, tt.expectedTag, actual.Tag, fmt.Sprintf("Tag for: %v", tt.stringValue))
|
|
||||||
test.AssertResultWithContext(t, ScalarNode, actual.Kind, fmt.Sprintf("Kind for: %v", tt.stringValue))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetKeyForMapValue(t *testing.T) {
|
func TestGetKeyForMapValue(t *testing.T) {
|
||||||
key := createStringScalarNode("yourKey")
|
key := createStringScalarNode("yourKey")
|
||||||
n := CandidateNode{Key: key, Value: "meow", document: 3}
|
n := CandidateNode{Key: key, Value: "meow", document: 3}
|
||||||
|
|||||||
@ -55,13 +55,13 @@ func (o *CandidateNode) copyFromYamlNode(node *yaml.Node, anchorMap map[string]*
|
|||||||
|
|
||||||
if o.Anchor != "" {
|
if o.Anchor != "" {
|
||||||
anchorMap[o.Anchor] = o
|
anchorMap[o.Anchor] = o
|
||||||
log.Debugf("set anchor %v to %v", o.Anchor, NodeToString(o))
|
log.Debug("set anchor %v to %v", o.Anchor, NodeToString(o))
|
||||||
}
|
}
|
||||||
|
|
||||||
// its a single alias
|
// its a single alias
|
||||||
if node.Alias != nil && node.Alias.Anchor != "" {
|
if node.Alias != nil && node.Alias.Anchor != "" {
|
||||||
o.Alias = anchorMap[node.Alias.Anchor]
|
o.Alias = anchorMap[node.Alias.Anchor]
|
||||||
log.Debugf("set alias to %v", NodeToString(anchorMap[node.Alias.Anchor]))
|
log.Debug("set alias to %v", NodeToString(anchorMap[node.Alias.Anchor]))
|
||||||
}
|
}
|
||||||
o.HeadComment = node.HeadComment
|
o.HeadComment = node.HeadComment
|
||||||
o.LineComment = node.LineComment
|
o.LineComment = node.LineComment
|
||||||
@ -106,7 +106,7 @@ func (o *CandidateNode) UnmarshalYAML(node *yaml.Node, anchorMap map[string]*Can
|
|||||||
log.Debugf("UnmarshalYAML %v", node.Tag)
|
log.Debugf("UnmarshalYAML %v", node.Tag)
|
||||||
switch node.Kind {
|
switch node.Kind {
|
||||||
case yaml.AliasNode:
|
case yaml.AliasNode:
|
||||||
log.Debugf("UnmarshalYAML - alias from yaml: %v", o.Tag)
|
log.Debug("UnmarshalYAML - alias from yaml: %v", o.Tag)
|
||||||
o.Kind = AliasNode
|
o.Kind = AliasNode
|
||||||
o.copyFromYamlNode(node, anchorMap)
|
o.copyFromYamlNode(node, anchorMap)
|
||||||
return nil
|
return nil
|
||||||
@ -176,15 +176,15 @@ func (o *CandidateNode) UnmarshalYAML(node *yaml.Node, anchorMap map[string]*Can
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (o *CandidateNode) MarshalYAML() (*yaml.Node, error) {
|
func (o *CandidateNode) MarshalYAML() (*yaml.Node, error) {
|
||||||
log.Debugf("MarshalYAML to yaml: %v", o.Tag)
|
log.Debug("MarshalYAML to yaml: %v", o.Tag)
|
||||||
switch o.Kind {
|
switch o.Kind {
|
||||||
case AliasNode:
|
case AliasNode:
|
||||||
log.Debugf("MarshalYAML - alias to yaml: %v", o.Tag)
|
log.Debug("MarshalYAML - alias to yaml: %v", o.Tag)
|
||||||
target := &yaml.Node{Kind: yaml.AliasNode}
|
target := &yaml.Node{Kind: yaml.AliasNode}
|
||||||
o.copyToYamlNode(target)
|
o.copyToYamlNode(target)
|
||||||
return target, nil
|
return target, nil
|
||||||
case ScalarNode:
|
case ScalarNode:
|
||||||
log.Debugf("MarshalYAML - scalar: %v", o.Value)
|
log.Debug("MarshalYAML - scalar: %v", o.Value)
|
||||||
target := &yaml.Node{Kind: yaml.ScalarNode}
|
target := &yaml.Node{Kind: yaml.ScalarNode}
|
||||||
o.copyToYamlNode(target)
|
o.copyToYamlNode(target)
|
||||||
return target, nil
|
return target, nil
|
||||||
|
|||||||
@ -7,9 +7,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/goccy/go-json"
|
"github.com/goccy/go-json"
|
||||||
)
|
)
|
||||||
@ -123,7 +120,7 @@ func (o *CandidateNode) UnmarshalJSON(data []byte) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Debugf("UnmarshalJSON - scalar is %v", scalar)
|
log.Debug("UnmarshalJSON - scalar is %v", scalar)
|
||||||
|
|
||||||
return o.setScalarFromJson(scalar)
|
return o.setScalarFromJson(scalar)
|
||||||
|
|
||||||
@ -143,12 +140,6 @@ func (o *CandidateNode) MarshalJSON() ([]byte, error) {
|
|||||||
return buf.Bytes(), err
|
return buf.Bytes(), err
|
||||||
case ScalarNode:
|
case ScalarNode:
|
||||||
log.Debugf("MarshalJSON 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()
|
value, err := o.GetValueRep()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return buf.Bytes(), err
|
return buf.Bytes(), err
|
||||||
@ -186,85 +177,3 @@ func (o *CandidateNode) MarshalJSON() ([]byte, error) {
|
|||||||
return buf.Bytes(), err
|
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
|
|
||||||
}
|
|
||||||
@ -18,7 +18,7 @@ func changeOwner(info fs.FileInfo, file *os.File) error {
|
|||||||
// this happens with snap confinement
|
// this happens with snap confinement
|
||||||
// not really a big issue as users can chown
|
// not really a big issue as users can chown
|
||||||
// the file themselves if required.
|
// the file themselves if required.
|
||||||
log.Infof("Skipping chown: %v", err)
|
log.Info("Skipping chown: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@ -3,8 +3,9 @@ package yqlib
|
|||||||
import (
|
import (
|
||||||
"container/list"
|
"container/list"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
logging "gopkg.in/op/go-logging.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Context struct {
|
type Context struct {
|
||||||
@ -74,7 +75,7 @@ func (n *Context) ChildContext(results *list.List) Context {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (n *Context) ToString() string {
|
func (n *Context) ToString() string {
|
||||||
if !log.IsEnabledFor(slog.LevelDebug) {
|
if !log.IsEnabledFor(logging.DEBUG) {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
result := fmt.Sprintf("Context\nDontAutoCreate: %v\n", n.DontAutoCreate)
|
result := fmt.Sprintf("Context\nDontAutoCreate: %v\n", n.DontAutoCreate)
|
||||||
|
|||||||
@ -2,11 +2,11 @@ package yqlib
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"container/list"
|
"container/list"
|
||||||
"log/slog"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/mikefarah/yq/v4/test"
|
"github.com/mikefarah/yq/v4/test"
|
||||||
|
logging "gopkg.in/op/go-logging.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestChildContext(t *testing.T) {
|
func TestChildContext(t *testing.T) {
|
||||||
@ -155,8 +155,8 @@ func TestToString(t *testing.T) {
|
|||||||
test.AssertResultComplex(t, "", result)
|
test.AssertResultComplex(t, "", result)
|
||||||
|
|
||||||
// Test with debug logging enabled
|
// Test with debug logging enabled
|
||||||
GetLogger().SetLevel(slog.LevelDebug)
|
logging.SetLevel(logging.DEBUG, "")
|
||||||
defer GetLogger().SetLevel(slog.LevelWarn) // Reset to default
|
defer logging.SetLevel(logging.INFO, "") // Reset to default
|
||||||
|
|
||||||
result2 := context.ToString()
|
result2 := context.ToString()
|
||||||
test.AssertResultComplex(t, true, len(result2) > 0)
|
test.AssertResultComplex(t, true, len(result2) > 0)
|
||||||
|
|||||||
@ -2,7 +2,8 @@ package yqlib
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
|
||||||
|
logging "gopkg.in/op/go-logging.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DataTreeNavigator interface {
|
type DataTreeNavigator interface {
|
||||||
@ -54,7 +55,7 @@ func (d *dataTreeNavigator) GetMatchingNodes(context Context, expressionNode *Ex
|
|||||||
return context, nil
|
return context, nil
|
||||||
}
|
}
|
||||||
log.Debugf("Processing Op: %v", expressionNode.Operation.toString())
|
log.Debugf("Processing Op: %v", expressionNode.Operation.toString())
|
||||||
if log.IsEnabledFor(slog.LevelDebug) {
|
if log.IsEnabledFor(logging.DEBUG) {
|
||||||
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||||
log.Debug(NodeToString(el.Value.(*CandidateNode)))
|
log.Debug(NodeToString(el.Value.(*CandidateNode)))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -226,7 +226,7 @@ func addBlockToMapping(parent *CandidateNode, block *hclsyntax.Block, src []byte
|
|||||||
// Mark the type node if there are multiple blocks of this type at this level
|
// Mark the type node if there are multiple blocks of this type at this level
|
||||||
// This tells the encoder to emit them as separate blocks rather than consolidating them
|
// This tells the encoder to emit them as separate blocks rather than consolidating them
|
||||||
if isMultipleBlocksOfType {
|
if isMultipleBlocksOfType {
|
||||||
typeNode.EncodeHint = EncodeHintSeparateBlock
|
typeNode.EncodeSeparate = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
current = typeNode
|
current = typeNode
|
||||||
|
|||||||
@ -12,20 +12,17 @@ import (
|
|||||||
type iniDecoder struct {
|
type iniDecoder struct {
|
||||||
reader io.Reader
|
reader io.Reader
|
||||||
finished bool // Flag to signal completion of processing
|
finished bool // Flag to signal completion of processing
|
||||||
prefs INIPreferences
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewINIDecoder(prefs INIPreferences) Decoder {
|
func NewINIDecoder() Decoder {
|
||||||
return &iniDecoder{
|
return &iniDecoder{
|
||||||
finished: false, // Initialise the flag as false
|
finished: false, // Initialize the flag as false
|
||||||
prefs: prefs,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dec *iniDecoder) Init(reader io.Reader) error {
|
func (dec *iniDecoder) Init(reader io.Reader) error {
|
||||||
// Store the reader for use in Decode
|
// Store the reader for use in Decode
|
||||||
dec.reader = reader
|
dec.reader = reader
|
||||||
dec.finished = false
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,10 +39,7 @@ func (dec *iniDecoder) Decode() (*CandidateNode, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parse the INI content
|
// Parse the INI content
|
||||||
loadOpts := ini.LoadOptions{
|
cfg, err := ini.Load(content)
|
||||||
PreserveSurroundedQuote: dec.prefs.PreserveSurroundedQuote,
|
|
||||||
}
|
|
||||||
cfg, err := ini.LoadSources(loadOpts, content)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to parse INI content: %w", err)
|
return nil, fmt.Errorf("failed to parse INI content: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,11 +16,10 @@ type propertiesDecoder struct {
|
|||||||
reader io.Reader
|
reader io.Reader
|
||||||
finished bool
|
finished bool
|
||||||
d DataTreeNavigator
|
d DataTreeNavigator
|
||||||
prefs PropertiesPreferences
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPropertiesDecoder() Decoder {
|
func NewPropertiesDecoder() Decoder {
|
||||||
return &propertiesDecoder{d: NewDataTreeNavigator(), finished: false, prefs: ConfiguredPropertiesPreferences.Copy()}
|
return &propertiesDecoder{d: NewDataTreeNavigator(), finished: false}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dec *propertiesDecoder) Init(reader io.Reader) error {
|
func (dec *propertiesDecoder) Init(reader io.Reader) error {
|
||||||
@ -29,56 +28,20 @@ func (dec *propertiesDecoder) Init(reader io.Reader) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parsePropKey(key string, prefs PropertiesPreferences) []interface{} {
|
func parsePropKey(key string) []interface{} {
|
||||||
pathStrArray := strings.Split(key, ".")
|
pathStrArray := strings.Split(key, ".")
|
||||||
path := make([]interface{}, 0, len(pathStrArray))
|
path := make([]interface{}, len(pathStrArray))
|
||||||
for _, pathStr := range pathStrArray {
|
for i, pathStr := range pathStrArray {
|
||||||
path = appendPropKeySegment(path, pathStr, prefs.UseArrayBrackets)
|
num, err := strconv.ParseInt(pathStr, 10, 32)
|
||||||
|
if err == nil {
|
||||||
|
path[i] = num
|
||||||
|
} else {
|
||||||
|
path[i] = pathStr
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return path
|
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 {
|
func (dec *propertiesDecoder) processComment(c string) string {
|
||||||
if c == "" {
|
if c == "" {
|
||||||
return ""
|
return ""
|
||||||
@ -112,7 +75,7 @@ func (dec *propertiesDecoder) applyPropertyComments(context Context, path []inte
|
|||||||
|
|
||||||
func (dec *propertiesDecoder) applyProperty(context Context, properties *properties.Properties, key string) error {
|
func (dec *propertiesDecoder) applyProperty(context Context, properties *properties.Properties, key string) error {
|
||||||
value, _ := properties.Get(key)
|
value, _ := properties.Get(key)
|
||||||
path := parsePropKey(key, dec.prefs)
|
path := parsePropKey(key)
|
||||||
|
|
||||||
propertyComments := properties.GetComments(key)
|
propertyComments := properties.GetComments(key)
|
||||||
if len(propertyComments) > 0 {
|
if len(propertyComments) > 0 {
|
||||||
|
|||||||
@ -68,7 +68,7 @@ func mustProcessFormatScenario(s formatScenario, decoder Decoder, encoder Encode
|
|||||||
|
|
||||||
result, err := processFormatScenario(s, decoder, encoder)
|
result, err := processFormatScenario(s, decoder, encoder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Bad scenario %v: %v", s.description, err)
|
log.Error("Bad scenario %v: %w", s.description, err)
|
||||||
return fmt.Sprintf("Bad scenario %v: %v", s.description, err.Error())
|
return fmt.Sprintf("Bad scenario %v: %v", s.description, err.Error())
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
|
|||||||
@ -8,19 +8,16 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
toml "github.com/pelletier/go-toml/v2/unstable"
|
toml "github.com/pelletier/go-toml/v2/unstable"
|
||||||
)
|
)
|
||||||
|
|
||||||
type tomlDecoder struct {
|
type tomlDecoder struct {
|
||||||
parser toml.Parser
|
parser toml.Parser
|
||||||
finished bool
|
finished bool
|
||||||
d DataTreeNavigator
|
d DataTreeNavigator
|
||||||
rootMap *CandidateNode
|
rootMap *CandidateNode
|
||||||
pendingComments []string // Head comments collected from Comment nodes
|
|
||||||
firstContentSeen bool // Track if we've processed the first non-comment node
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTomlDecoder() Decoder {
|
func NewTomlDecoder() Decoder {
|
||||||
@ -31,7 +28,7 @@ func NewTomlDecoder() Decoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (dec *tomlDecoder) Init(reader io.Reader) error {
|
func (dec *tomlDecoder) Init(reader io.Reader) error {
|
||||||
dec.parser = toml.Parser{KeepComments: true}
|
dec.parser = toml.Parser{}
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
_, err := buf.ReadFrom(reader)
|
_, err := buf.ReadFrom(reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -42,24 +39,9 @@ func (dec *tomlDecoder) Init(reader io.Reader) error {
|
|||||||
Kind: MappingNode,
|
Kind: MappingNode,
|
||||||
Tag: "!!map",
|
Tag: "!!map",
|
||||||
}
|
}
|
||||||
dec.pendingComments = make([]string, 0)
|
|
||||||
dec.firstContentSeen = false
|
|
||||||
dec.finished = false
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dec *tomlDecoder) attachOrphanedCommentsToNode(tableNodeValue *CandidateNode) {
|
|
||||||
if len(dec.pendingComments) > 0 {
|
|
||||||
comments := strings.Join(dec.pendingComments, "\n")
|
|
||||||
if tableNodeValue.HeadComment == "" {
|
|
||||||
tableNodeValue.HeadComment = comments
|
|
||||||
} else {
|
|
||||||
tableNodeValue.HeadComment = tableNodeValue.HeadComment + "\n" + comments
|
|
||||||
}
|
|
||||||
dec.pendingComments = make([]string, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dec *tomlDecoder) getFullPath(tomlNode *toml.Node) []interface{} {
|
func (dec *tomlDecoder) getFullPath(tomlNode *toml.Node) []interface{} {
|
||||||
path := make([]interface{}, 0)
|
path := make([]interface{}, 0)
|
||||||
for {
|
for {
|
||||||
@ -74,24 +56,13 @@ func (dec *tomlDecoder) getFullPath(tomlNode *toml.Node) []interface{} {
|
|||||||
func (dec *tomlDecoder) processKeyValueIntoMap(rootMap *CandidateNode, tomlNode *toml.Node) error {
|
func (dec *tomlDecoder) processKeyValueIntoMap(rootMap *CandidateNode, tomlNode *toml.Node) error {
|
||||||
value := tomlNode.Value()
|
value := tomlNode.Value()
|
||||||
path := dec.getFullPath(value.Next())
|
path := dec.getFullPath(value.Next())
|
||||||
|
log.Debug("processKeyValueIntoMap: %v", path)
|
||||||
|
|
||||||
valueNode, err := dec.decodeNode(value)
|
valueNode, err := dec.decodeNode(value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attach pending head comments
|
|
||||||
if len(dec.pendingComments) > 0 {
|
|
||||||
valueNode.HeadComment = strings.Join(dec.pendingComments, "\n")
|
|
||||||
dec.pendingComments = make([]string, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for inline comment chained to the KeyValue node
|
|
||||||
nextNode := tomlNode.Next()
|
|
||||||
if nextNode != nil && nextNode.Kind == toml.Comment {
|
|
||||||
valueNode.LineComment = string(nextNode.Data)
|
|
||||||
}
|
|
||||||
|
|
||||||
context := Context{}
|
context := Context{}
|
||||||
context = context.SingleChildContext(rootMap)
|
context = context.SingleChildContext(rootMap)
|
||||||
|
|
||||||
@ -106,19 +77,15 @@ func (dec *tomlDecoder) decodeKeyValuesIntoMap(rootMap *CandidateNode, tomlNode
|
|||||||
|
|
||||||
for dec.parser.NextExpression() {
|
for dec.parser.NextExpression() {
|
||||||
nextItem := dec.parser.Expression()
|
nextItem := dec.parser.Expression()
|
||||||
log.Debugf("decodeKeyValuesIntoMap -- next exp, its a %v", nextItem.Kind)
|
log.Debug("decodeKeyValuesIntoMap -- next exp, its a %v", nextItem.Kind)
|
||||||
|
|
||||||
switch nextItem.Kind {
|
if nextItem.Kind == toml.KeyValue {
|
||||||
case toml.KeyValue:
|
|
||||||
if err := dec.processKeyValueIntoMap(rootMap, nextItem); err != nil {
|
if err := dec.processKeyValueIntoMap(rootMap, nextItem); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
case toml.Comment:
|
} else {
|
||||||
// Standalone comment - add to pending for next element
|
|
||||||
dec.pendingComments = append(dec.pendingComments, string(nextItem.Data))
|
|
||||||
default:
|
|
||||||
// run out of key values
|
// run out of key values
|
||||||
log.Debugf("done in decodeKeyValuesIntoMap, gota a %v", nextItem.Kind)
|
log.Debug("done in decodeKeyValuesIntoMap, gota a %v", nextItem.Kind)
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -150,39 +117,21 @@ func (dec *tomlDecoder) createInlineTableMap(tomlNode *toml.Node) (*CandidateNod
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &CandidateNode{
|
return &CandidateNode{
|
||||||
Kind: MappingNode,
|
Kind: MappingNode,
|
||||||
Tag: "!!map",
|
Tag: "!!map",
|
||||||
EncodeHint: EncodeHintInline,
|
Content: content,
|
||||||
Content: content,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dec *tomlDecoder) createArray(tomlNode *toml.Node) (*CandidateNode, error) {
|
func (dec *tomlDecoder) createArray(tomlNode *toml.Node) (*CandidateNode, error) {
|
||||||
content := make([]*CandidateNode, 0)
|
content := make([]*CandidateNode, 0)
|
||||||
var pendingArrayComments []string
|
|
||||||
|
|
||||||
iterator := tomlNode.Children()
|
iterator := tomlNode.Children()
|
||||||
for iterator.Next() {
|
for iterator.Next() {
|
||||||
child := iterator.Node()
|
child := iterator.Node()
|
||||||
|
|
||||||
// Handle comments within arrays
|
|
||||||
if child.Kind == toml.Comment {
|
|
||||||
// Collect comments to attach to the next array element
|
|
||||||
pendingArrayComments = append(pendingArrayComments, string(child.Data))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
yamlNode, err := dec.decodeNode(child)
|
yamlNode, err := dec.decodeNode(child)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attach any pending comments to this array element
|
|
||||||
if len(pendingArrayComments) > 0 {
|
|
||||||
yamlNode.HeadComment = strings.Join(pendingArrayComments, "\n")
|
|
||||||
pendingArrayComments = make([]string, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
content = append(content, yamlNode)
|
content = append(content, yamlNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -272,7 +221,7 @@ func (dec *tomlDecoder) Decode() (*CandidateNode, error) {
|
|||||||
|
|
||||||
currentNode := dec.parser.Expression()
|
currentNode := dec.parser.Expression()
|
||||||
|
|
||||||
log.Debugf("currentNode: %v ", currentNode.Kind)
|
log.Debug("currentNode: %v ", currentNode.Kind)
|
||||||
runAgainstCurrentExp, err = dec.processTopLevelNode(currentNode)
|
runAgainstCurrentExp, err = dec.processTopLevelNode(currentNode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return dec.rootMap, err
|
return dec.rootMap, err
|
||||||
@ -299,43 +248,24 @@ func (dec *tomlDecoder) Decode() (*CandidateNode, error) {
|
|||||||
func (dec *tomlDecoder) processTopLevelNode(currentNode *toml.Node) (bool, error) {
|
func (dec *tomlDecoder) processTopLevelNode(currentNode *toml.Node) (bool, error) {
|
||||||
var runAgainstCurrentExp bool
|
var runAgainstCurrentExp bool
|
||||||
var err error
|
var err error
|
||||||
log.Debugf("processTopLevelNode: Going to process %v state is current %v", currentNode.Kind, NodeToString(dec.rootMap))
|
log.Debug("processTopLevelNode: Going to process %v state is current %v", currentNode.Kind, NodeToString(dec.rootMap))
|
||||||
switch currentNode.Kind {
|
switch currentNode.Kind {
|
||||||
case toml.Comment:
|
|
||||||
// Collect comment to attach to next element
|
|
||||||
commentText := string(currentNode.Data)
|
|
||||||
// If we haven't seen any content yet, accumulate comments for root
|
|
||||||
if !dec.firstContentSeen {
|
|
||||||
if dec.rootMap.HeadComment == "" {
|
|
||||||
dec.rootMap.HeadComment = commentText
|
|
||||||
} else {
|
|
||||||
dec.rootMap.HeadComment = dec.rootMap.HeadComment + "\n" + commentText
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// We've seen content, so these comments are for the next element
|
|
||||||
dec.pendingComments = append(dec.pendingComments, commentText)
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
case toml.Table:
|
case toml.Table:
|
||||||
dec.firstContentSeen = true
|
|
||||||
runAgainstCurrentExp, err = dec.processTable(currentNode)
|
runAgainstCurrentExp, err = dec.processTable(currentNode)
|
||||||
case toml.ArrayTable:
|
case toml.ArrayTable:
|
||||||
dec.firstContentSeen = true
|
|
||||||
runAgainstCurrentExp, err = dec.processArrayTable(currentNode)
|
runAgainstCurrentExp, err = dec.processArrayTable(currentNode)
|
||||||
default:
|
default:
|
||||||
dec.firstContentSeen = true
|
|
||||||
runAgainstCurrentExp, err = dec.decodeKeyValuesIntoMap(dec.rootMap, currentNode)
|
runAgainstCurrentExp, err = dec.decodeKeyValuesIntoMap(dec.rootMap, currentNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("processTopLevelNode: DONE Processing state is now %v", NodeToString(dec.rootMap))
|
log.Debug("processTopLevelNode: DONE Processing state is now %v", NodeToString(dec.rootMap))
|
||||||
return runAgainstCurrentExp, err
|
return runAgainstCurrentExp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dec *tomlDecoder) processTable(currentNode *toml.Node) (bool, error) {
|
func (dec *tomlDecoder) processTable(currentNode *toml.Node) (bool, error) {
|
||||||
log.Debug("Enter processTable")
|
log.Debug("Enter processTable")
|
||||||
child := currentNode.Child()
|
fullPath := dec.getFullPath(currentNode.Child())
|
||||||
fullPath := dec.getFullPath(child)
|
log.Debug("fullpath: %v", fullPath)
|
||||||
log.Debugf("fullpath: %v", fullPath)
|
|
||||||
|
|
||||||
c := Context{}
|
c := Context{}
|
||||||
c = c.SingleChildContext(dec.rootMap)
|
c = c.SingleChildContext(dec.rootMap)
|
||||||
@ -346,53 +276,27 @@ func (dec *tomlDecoder) processTable(currentNode *toml.Node) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tableNodeValue := &CandidateNode{
|
tableNodeValue := &CandidateNode{
|
||||||
Kind: MappingNode,
|
Kind: MappingNode,
|
||||||
Tag: "!!map",
|
Tag: "!!map",
|
||||||
Content: make([]*CandidateNode, 0),
|
Content: make([]*CandidateNode, 0),
|
||||||
EncodeHint: EncodeHintSeparateBlock,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attach pending head comments to the table
|
|
||||||
if len(dec.pendingComments) > 0 {
|
|
||||||
tableNodeValue.HeadComment = strings.Join(dec.pendingComments, "\n")
|
|
||||||
dec.pendingComments = make([]string, 0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var tableValue *toml.Node
|
var tableValue *toml.Node
|
||||||
runAgainstCurrentExp := false
|
runAgainstCurrentExp := false
|
||||||
sawKeyValue := false
|
hasValue := dec.parser.NextExpression()
|
||||||
for dec.parser.NextExpression() {
|
// check to see if there is any table data
|
||||||
|
if hasValue {
|
||||||
tableValue = dec.parser.Expression()
|
tableValue = dec.parser.Expression()
|
||||||
// Allow standalone comments inside the table before the first key-value.
|
// next expression is not table data, so we are done
|
||||||
// These should be associated with the next element in the table (usually the first key-value),
|
|
||||||
// not treated as "end of table" (which would cause subsequent key-values to be parsed at root).
|
|
||||||
if tableValue.Kind == toml.Comment {
|
|
||||||
dec.pendingComments = append(dec.pendingComments, string(tableValue.Data))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// next expression is not table data, so we are done (but we need to re-process it at top-level)
|
|
||||||
if tableValue.Kind != toml.KeyValue {
|
if tableValue.Kind != toml.KeyValue {
|
||||||
log.Debug("got an empty table (or reached next section)")
|
log.Debug("got an empty table")
|
||||||
// If the table had only comments, attach them to the table itself so they don't leak to the next node.
|
|
||||||
if !sawKeyValue {
|
|
||||||
dec.attachOrphanedCommentsToNode(tableNodeValue)
|
|
||||||
}
|
|
||||||
runAgainstCurrentExp = true
|
runAgainstCurrentExp = true
|
||||||
break
|
} else {
|
||||||
|
runAgainstCurrentExp, err = dec.decodeKeyValuesIntoMap(tableNodeValue, tableValue)
|
||||||
|
if err != nil && !errors.Is(err, io.EOF) {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sawKeyValue = true
|
|
||||||
runAgainstCurrentExp, err = dec.decodeKeyValuesIntoMap(tableNodeValue, tableValue)
|
|
||||||
if err != nil && !errors.Is(err, io.EOF) {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// If we hit EOF after only seeing comments inside this table, attach them to the table itself
|
|
||||||
// so they don't leak to whatever comes next.
|
|
||||||
if !sawKeyValue {
|
|
||||||
dec.attachOrphanedCommentsToNode(tableNodeValue)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = dec.d.DeeplyAssign(c, fullPath, tableNodeValue)
|
err = dec.d.DeeplyAssign(c, fullPath, tableNodeValue)
|
||||||
@ -403,7 +307,7 @@ func (dec *tomlDecoder) processTable(currentNode *toml.Node) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (dec *tomlDecoder) arrayAppend(context Context, path []interface{}, rhsNode *CandidateNode) error {
|
func (dec *tomlDecoder) arrayAppend(context Context, path []interface{}, rhsNode *CandidateNode) error {
|
||||||
log.Debugf("arrayAppend to path: %v,%v", path, NodeToString(rhsNode))
|
log.Debug("arrayAppend to path: %v,%v", path, NodeToString(rhsNode))
|
||||||
rhsCandidateNode := &CandidateNode{
|
rhsCandidateNode := &CandidateNode{
|
||||||
Kind: SequenceNode,
|
Kind: SequenceNode,
|
||||||
Tag: "!!seq",
|
Tag: "!!seq",
|
||||||
@ -426,9 +330,8 @@ func (dec *tomlDecoder) arrayAppend(context Context, path []interface{}, rhsNode
|
|||||||
|
|
||||||
func (dec *tomlDecoder) processArrayTable(currentNode *toml.Node) (bool, error) {
|
func (dec *tomlDecoder) processArrayTable(currentNode *toml.Node) (bool, error) {
|
||||||
log.Debug("Enter processArrayTable")
|
log.Debug("Enter processArrayTable")
|
||||||
child := currentNode.Child()
|
fullPath := dec.getFullPath(currentNode.Child())
|
||||||
fullPath := dec.getFullPath(child)
|
log.Debug("Fullpath: %v", fullPath)
|
||||||
log.Debugf("Fullpath: %v", fullPath)
|
|
||||||
|
|
||||||
c := Context{}
|
c := Context{}
|
||||||
c = c.SingleChildContext(dec.rootMap)
|
c = c.SingleChildContext(dec.rootMap)
|
||||||
@ -443,64 +346,23 @@ func (dec *tomlDecoder) processArrayTable(currentNode *toml.Node) (bool, error)
|
|||||||
hasValue := dec.parser.NextExpression()
|
hasValue := dec.parser.NextExpression()
|
||||||
|
|
||||||
tableNodeValue := &CandidateNode{
|
tableNodeValue := &CandidateNode{
|
||||||
Kind: MappingNode,
|
Kind: MappingNode,
|
||||||
Tag: "!!map",
|
Tag: "!!map",
|
||||||
EncodeHint: EncodeHintSeparateBlock,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attach pending head comments to the array table
|
|
||||||
if len(dec.pendingComments) > 0 {
|
|
||||||
tableNodeValue.HeadComment = strings.Join(dec.pendingComments, "\n")
|
|
||||||
dec.pendingComments = make([]string, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
runAgainstCurrentExp := false
|
runAgainstCurrentExp := false
|
||||||
sawKeyValue := false
|
// if the next value is a ArrayTable or Table, then its not part of this declaration (not a key value pair)
|
||||||
if hasValue {
|
// so lets leave that expression for the next round of parsing
|
||||||
for {
|
if hasValue && (dec.parser.Expression().Kind == toml.ArrayTable || dec.parser.Expression().Kind == toml.Table) {
|
||||||
exp := dec.parser.Expression()
|
runAgainstCurrentExp = true
|
||||||
// Allow standalone comments inside array tables before the first key-value.
|
} else if hasValue {
|
||||||
if exp.Kind == toml.Comment {
|
// otherwise, if there is a value, it must be some key value pairs of the
|
||||||
dec.pendingComments = append(dec.pendingComments, string(exp.Data))
|
// first object in the array!
|
||||||
hasValue = dec.parser.NextExpression()
|
tableValue := dec.parser.Expression()
|
||||||
if !hasValue {
|
runAgainstCurrentExp, err = dec.decodeKeyValuesIntoMap(tableNodeValue, tableValue)
|
||||||
break
|
if err != nil && !errors.Is(err, io.EOF) {
|
||||||
}
|
return false, err
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the next value is a ArrayTable or Table, then its not part of this declaration (not a key value pair)
|
|
||||||
// so lets leave that expression for the next round of parsing
|
|
||||||
if exp.Kind == toml.ArrayTable || exp.Kind == toml.Table {
|
|
||||||
// If this array-table entry had only comments, attach them to the entry so they don't leak.
|
|
||||||
if !sawKeyValue {
|
|
||||||
dec.attachOrphanedCommentsToNode(tableNodeValue)
|
|
||||||
}
|
|
||||||
runAgainstCurrentExp = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
sawKeyValue = true
|
|
||||||
// otherwise, if there is a value, it must be some key value pairs of the
|
|
||||||
// first object in the array!
|
|
||||||
runAgainstCurrentExp, err = dec.decodeKeyValuesIntoMap(tableNodeValue, exp)
|
|
||||||
if err != nil && !errors.Is(err, io.EOF) {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If we hit EOF after only seeing comments inside this array-table entry, attach them to the entry
|
|
||||||
// so they don't leak to whatever comes next.
|
|
||||||
if !sawKeyValue && len(dec.pendingComments) > 0 {
|
|
||||||
comments := strings.Join(dec.pendingComments, "\n")
|
|
||||||
if tableNodeValue.HeadComment == "" {
|
|
||||||
tableNodeValue.HeadComment = comments
|
|
||||||
} else {
|
|
||||||
tableNodeValue.HeadComment = tableNodeValue.HeadComment + "\n" + comments
|
|
||||||
}
|
|
||||||
dec.pendingComments = make([]string, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// += function
|
// += function
|
||||||
err = dec.arrayAppend(c, fullPath, tableNodeValue)
|
err = dec.arrayAppend(c, fullPath, tableNodeValue)
|
||||||
@ -513,42 +375,23 @@ func (dec *tomlDecoder) processArrayTable(currentNode *toml.Node) (bool, error)
|
|||||||
// Because TOML. So we'll inject the last index into the path.
|
// Because TOML. So we'll inject the last index into the path.
|
||||||
|
|
||||||
func getPathToUse(fullPath []interface{}, dec *tomlDecoder, c Context) ([]interface{}, error) {
|
func getPathToUse(fullPath []interface{}, dec *tomlDecoder, c Context) ([]interface{}, error) {
|
||||||
// We need to check the entire path (except the last element), not just the immediate parent,
|
pathToCheck := fullPath
|
||||||
// because we may have nested array tables like [[array.subarray.subsubarray]]
|
if len(fullPath) >= 1 {
|
||||||
// where both 'array' and 'subarray' are arrays that already exist.
|
pathToCheck = fullPath[:len(fullPath)-1]
|
||||||
|
|
||||||
if len(fullPath) == 0 {
|
|
||||||
return fullPath, nil
|
|
||||||
}
|
}
|
||||||
|
readOp := createTraversalTree(pathToCheck, traversePreferences{DontAutoCreate: true}, false)
|
||||||
|
|
||||||
resultPath := make([]interface{}, 0, len(fullPath)*2) // preallocate with extra space for indices
|
resultContext, err := dec.d.GetMatchingNodes(c, readOp)
|
||||||
|
if err != nil {
|
||||||
// Process all segments except the last one
|
return nil, err
|
||||||
for i := 0; i < len(fullPath)-1; i++ {
|
}
|
||||||
resultPath = append(resultPath, fullPath[i])
|
if resultContext.MatchingNodes.Len() >= 1 {
|
||||||
|
match := resultContext.MatchingNodes.Front().Value.(*CandidateNode)
|
||||||
// Check if the current path segment points to an array
|
// path refers to an array, we need to add this to the last element in the array
|
||||||
readOp := createTraversalTree(resultPath, traversePreferences{DontAutoCreate: true}, false)
|
if match.Kind == SequenceNode {
|
||||||
resultContext, err := dec.d.GetMatchingNodes(c, readOp)
|
fullPath = append(pathToCheck, len(match.Content)-1, fullPath[len(fullPath)-1])
|
||||||
if err != nil {
|
log.Debugf("Adding to end of %v array, using path: %v", pathToCheck, fullPath)
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if resultContext.MatchingNodes.Len() >= 1 {
|
|
||||||
match := resultContext.MatchingNodes.Front().Value.(*CandidateNode)
|
|
||||||
// If this segment points to an array, we need to add the last index
|
|
||||||
// before continuing with the rest of the path
|
|
||||||
if match.Kind == SequenceNode && len(match.Content) > 0 {
|
|
||||||
lastIndex := len(match.Content) - 1
|
|
||||||
resultPath = append(resultPath, lastIndex)
|
|
||||||
log.Debugf("Path segment %v is an array, injecting index %d", resultPath[:len(resultPath)-1], lastIndex)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return fullPath, err
|
||||||
// Add the last segment
|
|
||||||
resultPath = append(resultPath, fullPath[len(fullPath)-1])
|
|
||||||
|
|
||||||
log.Debugf("getPathToUse: original path %v -> result path %v", fullPath, resultPath)
|
|
||||||
return resultPath, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,160 +0,0 @@
|
|||||||
//go:build !yq_nouri
|
|
||||||
|
|
||||||
package yqlib
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/mikefarah/yq/v4/test"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestUriDecoder_Init(t *testing.T) {
|
|
||||||
decoder := NewUriDecoder()
|
|
||||||
reader := strings.NewReader("test")
|
|
||||||
err := decoder.Init(reader)
|
|
||||||
test.AssertResult(t, nil, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUriDecoder_DecodeSimpleString(t *testing.T) {
|
|
||||||
decoder := NewUriDecoder()
|
|
||||||
reader := strings.NewReader("hello%20world")
|
|
||||||
err := decoder.Init(reader)
|
|
||||||
test.AssertResult(t, nil, err)
|
|
||||||
|
|
||||||
node, err := decoder.Decode()
|
|
||||||
test.AssertResult(t, nil, err)
|
|
||||||
test.AssertResult(t, "!!str", node.Tag)
|
|
||||||
test.AssertResult(t, "hello world", node.Value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUriDecoder_DecodeSpecialCharacters(t *testing.T) {
|
|
||||||
decoder := NewUriDecoder()
|
|
||||||
reader := strings.NewReader("hello%21%40%23%24%25")
|
|
||||||
err := decoder.Init(reader)
|
|
||||||
test.AssertResult(t, nil, err)
|
|
||||||
|
|
||||||
node, err := decoder.Decode()
|
|
||||||
test.AssertResult(t, nil, err)
|
|
||||||
test.AssertResult(t, "hello!@#$%", node.Value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUriDecoder_DecodeUTF8(t *testing.T) {
|
|
||||||
decoder := NewUriDecoder()
|
|
||||||
reader := strings.NewReader("%E2%9C%93%20check")
|
|
||||||
err := decoder.Init(reader)
|
|
||||||
test.AssertResult(t, nil, err)
|
|
||||||
|
|
||||||
node, err := decoder.Decode()
|
|
||||||
test.AssertResult(t, nil, err)
|
|
||||||
test.AssertResult(t, "✓ check", node.Value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUriDecoder_DecodePlusSign(t *testing.T) {
|
|
||||||
decoder := NewUriDecoder()
|
|
||||||
reader := strings.NewReader("a+b")
|
|
||||||
err := decoder.Init(reader)
|
|
||||||
test.AssertResult(t, nil, err)
|
|
||||||
|
|
||||||
node, err := decoder.Decode()
|
|
||||||
test.AssertResult(t, nil, err)
|
|
||||||
// Note: url.QueryUnescape does NOT convert + to space
|
|
||||||
// That's only for form encoding (url.ParseQuery)
|
|
||||||
test.AssertResult(t, "a b", node.Value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUriDecoder_DecodeEmptyString(t *testing.T) {
|
|
||||||
decoder := NewUriDecoder()
|
|
||||||
reader := strings.NewReader("")
|
|
||||||
err := decoder.Init(reader)
|
|
||||||
test.AssertResult(t, nil, err)
|
|
||||||
|
|
||||||
node, err := decoder.Decode()
|
|
||||||
test.AssertResult(t, nil, err)
|
|
||||||
test.AssertResult(t, "", node.Value)
|
|
||||||
|
|
||||||
// Second decode should return EOF
|
|
||||||
node, err = decoder.Decode()
|
|
||||||
test.AssertResult(t, io.EOF, err)
|
|
||||||
test.AssertResult(t, (*CandidateNode)(nil), node)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUriDecoder_DecodeMultipleCalls(t *testing.T) {
|
|
||||||
decoder := NewUriDecoder()
|
|
||||||
reader := strings.NewReader("test")
|
|
||||||
err := decoder.Init(reader)
|
|
||||||
test.AssertResult(t, nil, err)
|
|
||||||
|
|
||||||
// First decode
|
|
||||||
node, err := decoder.Decode()
|
|
||||||
test.AssertResult(t, nil, err)
|
|
||||||
test.AssertResult(t, "test", node.Value)
|
|
||||||
|
|
||||||
// Second decode should return EOF since we've consumed all input
|
|
||||||
node, err = decoder.Decode()
|
|
||||||
test.AssertResult(t, io.EOF, err)
|
|
||||||
test.AssertResult(t, (*CandidateNode)(nil), node)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUriDecoder_DecodeInvalidEscape(t *testing.T) {
|
|
||||||
decoder := NewUriDecoder()
|
|
||||||
reader := strings.NewReader("test%ZZ")
|
|
||||||
err := decoder.Init(reader)
|
|
||||||
test.AssertResult(t, nil, err)
|
|
||||||
|
|
||||||
_, err = decoder.Decode()
|
|
||||||
// Should return an error for invalid escape sequence
|
|
||||||
if err == nil {
|
|
||||||
t.Error("Expected error for invalid escape sequence, got nil")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUriDecoder_DecodeSlashAndQuery(t *testing.T) {
|
|
||||||
decoder := NewUriDecoder()
|
|
||||||
reader := strings.NewReader("path%2Fto%2Ffile%3Fquery%3Dvalue")
|
|
||||||
err := decoder.Init(reader)
|
|
||||||
test.AssertResult(t, nil, err)
|
|
||||||
|
|
||||||
node, err := decoder.Decode()
|
|
||||||
test.AssertResult(t, nil, err)
|
|
||||||
test.AssertResult(t, "path/to/file?query=value", node.Value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUriDecoder_DecodePercent(t *testing.T) {
|
|
||||||
decoder := NewUriDecoder()
|
|
||||||
reader := strings.NewReader("100%25")
|
|
||||||
err := decoder.Init(reader)
|
|
||||||
test.AssertResult(t, nil, err)
|
|
||||||
|
|
||||||
node, err := decoder.Decode()
|
|
||||||
test.AssertResult(t, nil, err)
|
|
||||||
test.AssertResult(t, "100%", node.Value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUriDecoder_DecodeNoEscaping(t *testing.T) {
|
|
||||||
decoder := NewUriDecoder()
|
|
||||||
reader := strings.NewReader("simple_text-123")
|
|
||||||
err := decoder.Init(reader)
|
|
||||||
test.AssertResult(t, nil, err)
|
|
||||||
|
|
||||||
node, err := decoder.Decode()
|
|
||||||
test.AssertResult(t, nil, err)
|
|
||||||
test.AssertResult(t, "simple_text-123", node.Value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mock reader that returns an error
|
|
||||||
type errorReader struct{}
|
|
||||||
|
|
||||||
func (e *errorReader) Read(_ []byte) (n int, err error) {
|
|
||||||
return 0, io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUriDecoder_DecodeReadError(t *testing.T) {
|
|
||||||
decoder := NewUriDecoder()
|
|
||||||
err := decoder.Init(&errorReader{})
|
|
||||||
test.AssertResult(t, nil, err)
|
|
||||||
|
|
||||||
_, err = decoder.Decode()
|
|
||||||
test.AssertResult(t, io.ErrUnexpectedEOF, err)
|
|
||||||
}
|
|
||||||
@ -64,7 +64,7 @@ func (dec *xmlDecoder) processComment(c string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (dec *xmlDecoder) createMap(n *xmlNode) (*CandidateNode, error) {
|
func (dec *xmlDecoder) createMap(n *xmlNode) (*CandidateNode, error) {
|
||||||
log.Debugf("createMap: headC: %v, lineC: %v, footC: %v", n.HeadComment, n.LineComment, n.FootComment)
|
log.Debug("createMap: headC: %v, lineC: %v, footC: %v", n.HeadComment, n.LineComment, n.FootComment)
|
||||||
yamlNode := &CandidateNode{Kind: MappingNode, Tag: "!!map"}
|
yamlNode := &CandidateNode{Kind: MappingNode, Tag: "!!map"}
|
||||||
|
|
||||||
if len(n.Data) > 0 {
|
if len(n.Data) > 0 {
|
||||||
@ -92,7 +92,7 @@ func (dec *xmlDecoder) createMap(n *xmlNode) (*CandidateNode, error) {
|
|||||||
log.Debugf("label=%v, i=%v, keyValuePair.FootComment: %v", label, i, keyValuePair.FootComment)
|
log.Debugf("label=%v, i=%v, keyValuePair.FootComment: %v", label, i, keyValuePair.FootComment)
|
||||||
labelNode.FootComment = dec.processComment(keyValuePair.FootComment)
|
labelNode.FootComment = dec.processComment(keyValuePair.FootComment)
|
||||||
|
|
||||||
log.Debugf("len of children in %v is %v", label, len(children))
|
log.Debug("len of children in %v is %v", label, len(children))
|
||||||
if len(children) > 1 {
|
if len(children) > 1 {
|
||||||
valueNode, err = dec.createSequence(children)
|
valueNode, err = dec.createSequence(children)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -105,7 +105,7 @@ func (dec *xmlDecoder) createMap(n *xmlNode) (*CandidateNode, error) {
|
|||||||
if len(children[0].Children) == 0 && children[0].HeadComment != "" {
|
if len(children[0].Children) == 0 && children[0].HeadComment != "" {
|
||||||
if len(children[0].Data) > 0 {
|
if len(children[0].Data) > 0 {
|
||||||
|
|
||||||
log.Debugf("scalar comment hack, currentlabel [%v]", labelNode.HeadComment)
|
log.Debug("scalar comment hack, currentlabel [%v]", labelNode.HeadComment)
|
||||||
labelNode.HeadComment = joinComments([]string{labelNode.HeadComment, strings.TrimSpace(children[0].HeadComment)}, "\n")
|
labelNode.HeadComment = joinComments([]string{labelNode.HeadComment, strings.TrimSpace(children[0].HeadComment)}, "\n")
|
||||||
children[0].HeadComment = ""
|
children[0].HeadComment = ""
|
||||||
} else {
|
} else {
|
||||||
@ -151,7 +151,7 @@ func (dec *xmlDecoder) convertToYamlNode(n *xmlNode) (*CandidateNode, error) {
|
|||||||
|
|
||||||
scalar := dec.createValueNodeFromData(n.Data)
|
scalar := dec.createValueNodeFromData(n.Data)
|
||||||
|
|
||||||
log.Debugf("scalar (%v), headC: %v, lineC: %v, footC: %v", scalar.Tag, n.HeadComment, n.LineComment, n.FootComment)
|
log.Debug("scalar (%v), headC: %v, lineC: %v, footC: %v", scalar.Tag, n.HeadComment, n.LineComment, n.FootComment)
|
||||||
scalar.HeadComment = dec.processComment(n.HeadComment)
|
scalar.HeadComment = dec.processComment(n.HeadComment)
|
||||||
scalar.LineComment = dec.processComment(n.LineComment)
|
scalar.LineComment = dec.processComment(n.LineComment)
|
||||||
if scalar.Tag == "!!seq" {
|
if scalar.Tag == "!!seq" {
|
||||||
@ -211,17 +211,17 @@ func (n *xmlNode) AddChild(s string, c *xmlNode) {
|
|||||||
if n.Children == nil {
|
if n.Children == nil {
|
||||||
n.Children = make([]*xmlChildrenKv, 0)
|
n.Children = make([]*xmlChildrenKv, 0)
|
||||||
}
|
}
|
||||||
log.Debugf("looking for %s", s)
|
log.Debug("looking for %s", s)
|
||||||
// see if we can find an existing entry to add to
|
// see if we can find an existing entry to add to
|
||||||
for _, childEntry := range n.Children {
|
for _, childEntry := range n.Children {
|
||||||
if childEntry.K == s {
|
if childEntry.K == s {
|
||||||
log.Debugf("found it, appending an entry%s", s)
|
log.Debug("found it, appending an entry%s", s)
|
||||||
childEntry.V = append(childEntry.V, c)
|
childEntry.V = append(childEntry.V, c)
|
||||||
log.Debugf("yay len of children in %v is %v", s, len(childEntry.V))
|
log.Debug("yay len of children in %v is %v", s, len(childEntry.V))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.Debugf("not there, making a new one %s", s)
|
log.Debug("not there, making a new one %s", s)
|
||||||
n.Children = append(n.Children, &xmlChildrenKv{K: s, V: []*xmlNode{c}})
|
n.Children = append(n.Children, &xmlChildrenKv{K: s, V: []*xmlNode{c}})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -267,7 +267,7 @@ func (dec *xmlDecoder) decodeXML(root *xmlNode) error {
|
|||||||
|
|
||||||
switch se := t.(type) {
|
switch se := t.(type) {
|
||||||
case xml.StartElement:
|
case xml.StartElement:
|
||||||
log.Debugf("start element %v", se.Name.Local)
|
log.Debug("start element %v", se.Name.Local)
|
||||||
elem.state = "started"
|
elem.state = "started"
|
||||||
// Build new a new current element and link it to its parent
|
// Build new a new current element and link it to its parent
|
||||||
var label = se.Name.Local
|
var label = se.Name.Local
|
||||||
@ -302,14 +302,14 @@ func (dec *xmlDecoder) decodeXML(root *xmlNode) error {
|
|||||||
if len(newBit) > 0 {
|
if len(newBit) > 0 {
|
||||||
elem.n.Data = append(elem.n.Data, newBit)
|
elem.n.Data = append(elem.n.Data, newBit)
|
||||||
elem.state = "chardata"
|
elem.state = "chardata"
|
||||||
log.Debugf("chardata [%v] for %v", elem.n.Data, elem.label)
|
log.Debug("chardata [%v] for %v", elem.n.Data, elem.label)
|
||||||
}
|
}
|
||||||
case xml.EndElement:
|
case xml.EndElement:
|
||||||
if elem == nil {
|
if elem == nil {
|
||||||
log.Debug("no element, probably bad xml")
|
log.Debug("no element, probably bad xml")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
log.Debugf("end element %v", elem.label)
|
log.Debug("end element %v", elem.label)
|
||||||
elem.state = "finished"
|
elem.state = "finished"
|
||||||
// And add it to its parent list
|
// And add it to its parent list
|
||||||
if elem.parent != nil {
|
if elem.parent != nil {
|
||||||
@ -326,10 +326,10 @@ func (dec *xmlDecoder) decodeXML(root *xmlNode) error {
|
|||||||
applyFootComment(elem, commentStr)
|
applyFootComment(elem, commentStr)
|
||||||
|
|
||||||
case "chardata":
|
case "chardata":
|
||||||
log.Debugf("got a line comment for (%v) %v: [%v]", elem.state, elem.label, commentStr)
|
log.Debug("got a line comment for (%v) %v: [%v]", elem.state, elem.label, commentStr)
|
||||||
elem.n.LineComment = joinComments([]string{elem.n.LineComment, commentStr}, " ")
|
elem.n.LineComment = joinComments([]string{elem.n.LineComment, commentStr}, " ")
|
||||||
default:
|
default:
|
||||||
log.Debugf("got a head comment for (%v) %v: [%v]", elem.state, elem.label, commentStr)
|
log.Debug("got a head comment for (%v) %v: [%v]", elem.state, elem.label, commentStr)
|
||||||
elem.n.HeadComment = joinComments([]string{elem.n.HeadComment, commentStr}, " ")
|
elem.n.HeadComment = joinComments([]string{elem.n.HeadComment, commentStr}, " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -354,7 +354,7 @@ func applyFootComment(elem *element, commentStr string) {
|
|||||||
if len(elem.n.Children) > 0 {
|
if len(elem.n.Children) > 0 {
|
||||||
lastChildIndex := len(elem.n.Children) - 1
|
lastChildIndex := len(elem.n.Children) - 1
|
||||||
childKv := elem.n.Children[lastChildIndex]
|
childKv := elem.n.Children[lastChildIndex]
|
||||||
log.Debugf("got a foot comment, putting on last child for %v: [%v]", childKv.K, commentStr)
|
log.Debug("got a foot comment, putting on last child for %v: [%v]", childKv.K, commentStr)
|
||||||
// if it's an array of scalars, put the foot comment on the scalar itself
|
// if it's an array of scalars, put the foot comment on the scalar itself
|
||||||
if len(childKv.V) > 0 && len(childKv.V[0].Children) == 0 {
|
if len(childKv.V) > 0 && len(childKv.V[0].Children) == 0 {
|
||||||
nodeToUpdate := childKv.V[len(childKv.V)-1]
|
nodeToUpdate := childKv.V[len(childKv.V)-1]
|
||||||
@ -363,7 +363,7 @@ func applyFootComment(elem *element, commentStr string) {
|
|||||||
childKv.FootComment = joinComments([]string{elem.n.FootComment, commentStr}, " ")
|
childKv.FootComment = joinComments([]string{elem.n.FootComment, commentStr}, " ")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Debugf("got a foot comment for %v: [%v]", elem.label, commentStr)
|
log.Debug("got a foot comment for %v: [%v]", elem.label, commentStr)
|
||||||
elem.n.FootComment = joinComments([]string{elem.n.FootComment, commentStr}, " ")
|
elem.n.FootComment = joinComments([]string{elem.n.FootComment, commentStr}, " ")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -78,7 +78,7 @@ func (dec *yamlDecoder) processReadStream(reader *bufio.Reader) (io.Reader, stri
|
|||||||
if separatorPrefixRe.MatchString(line) {
|
if separatorPrefixRe.MatchString(line) {
|
||||||
match := separatorPrefixRe.FindString(line)
|
match := separatorPrefixRe.FindString(line)
|
||||||
remainder := line[len(match):]
|
remainder := line[len(match):]
|
||||||
// normalise separator newline: if original had none, default to LF
|
// normalize separator newline: if original had none, default to LF
|
||||||
sepNewline := newline
|
sepNewline := newline
|
||||||
if sepNewline == "" {
|
if sepNewline == "" {
|
||||||
sepNewline = "\n"
|
sepNewline = "\n"
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Use the `alias` and `anchor` operators to read and write yaml aliases and anchors. The `explode` operator normalises a yaml file (dereference (or expands) aliases and remove anchor names).
|
Use the `alias` and `anchor` operators to read and write yaml aliases and anchors. The `explode` operator normalises a yaml file (dereference (or expands) aliases and remove anchor names).
|
||||||
|
|
||||||
`yq` supports merge keys (like `<<: *blah`) from YAML 1.1. These are no longer part of the YAML 1.2 standard, but remain common in practice. Plain `<<:` keys are recognised as merge keys and round-trip as `<<:` without an explicit `!!merge` tag. When the source uses an explicit `!!merge` tag, that is preserved on output. Internally, when `yq` synthesises a `<<` map key (for example during merge operations), it tags the key as `!!merge` rather than `!!str`.
|
`yq` supports merge aliases (like `<<: *blah`) however this is no longer in the standard yaml spec (1.2) and so `yq` will automatically add the `!!merge` tag to these nodes as it is effectively a custom tag.
|
||||||
|
|
||||||
|
|
||||||
## NOTE --yaml-fix-merge-anchor-to-spec flag
|
## NOTE --yaml-fix-merge-anchor-to-spec flag
|
||||||
@ -22,7 +22,7 @@ see https://yaml.org/type/merge.html
|
|||||||
|
|
||||||
Given a sample.yml file of:
|
Given a sample.yml file of:
|
||||||
```yaml
|
```yaml
|
||||||
- &CENTRE
|
- &CENTER
|
||||||
x: 1
|
x: 1
|
||||||
y: 2
|
y: 2
|
||||||
- &LEFT
|
- &LEFT
|
||||||
@ -32,7 +32,7 @@ Given a sample.yml file of:
|
|||||||
r: 10
|
r: 10
|
||||||
- &SMALL
|
- &SMALL
|
||||||
r: 1
|
r: 1
|
||||||
- <<: *CENTRE
|
- !!merge <<: *CENTER
|
||||||
r: 10
|
r: 10
|
||||||
```
|
```
|
||||||
then
|
then
|
||||||
@ -213,10 +213,10 @@ item_value: &item_value
|
|||||||
value: true
|
value: true
|
||||||
thingOne:
|
thingOne:
|
||||||
name: item_1
|
name: item_1
|
||||||
<<: *item_value
|
!!merge <<: *item_value
|
||||||
thingTwo:
|
thingTwo:
|
||||||
name: item_2
|
name: item_2
|
||||||
<<: *item_value
|
!!merge <<: *item_value
|
||||||
```
|
```
|
||||||
then
|
then
|
||||||
```bash
|
```bash
|
||||||
@ -231,7 +231,7 @@ thingOne:
|
|||||||
value: false
|
value: false
|
||||||
thingTwo:
|
thingTwo:
|
||||||
name: item_2
|
name: item_2
|
||||||
<<: *item_value
|
!!merge <<: *item_value
|
||||||
```
|
```
|
||||||
|
|
||||||
## LEGACY: Explode with merge anchors
|
## LEGACY: Explode with merge anchors
|
||||||
@ -249,13 +249,13 @@ bar: &bar
|
|||||||
c: bar_c
|
c: bar_c
|
||||||
foobarList:
|
foobarList:
|
||||||
b: foobarList_b
|
b: foobarList_b
|
||||||
<<:
|
!!merge <<:
|
||||||
- *foo
|
- *foo
|
||||||
- *bar
|
- *bar
|
||||||
c: foobarList_c
|
c: foobarList_c
|
||||||
foobar:
|
foobar:
|
||||||
c: foobar_c
|
c: foobar_c
|
||||||
<<: *foo
|
!!merge <<: *foo
|
||||||
thing: foobar_thing
|
thing: foobar_thing
|
||||||
```
|
```
|
||||||
then
|
then
|
||||||
@ -288,7 +288,7 @@ see https://yaml.org/type/merge.html. This has the correct data, but the wrong k
|
|||||||
|
|
||||||
Given a sample.yml file of:
|
Given a sample.yml file of:
|
||||||
```yaml
|
```yaml
|
||||||
- &CENTRE
|
- &CENTER
|
||||||
x: 1
|
x: 1
|
||||||
y: 2
|
y: 2
|
||||||
- &LEFT
|
- &LEFT
|
||||||
@ -298,8 +298,8 @@ Given a sample.yml file of:
|
|||||||
r: 10
|
r: 10
|
||||||
- &SMALL
|
- &SMALL
|
||||||
r: 1
|
r: 1
|
||||||
- <<:
|
- !!merge <<:
|
||||||
- *CENTRE
|
- *CENTER
|
||||||
- *BIG
|
- *BIG
|
||||||
```
|
```
|
||||||
then
|
then
|
||||||
@ -318,7 +318,7 @@ see https://yaml.org/type/merge.html. This has the correct data, but the wrong k
|
|||||||
|
|
||||||
Given a sample.yml file of:
|
Given a sample.yml file of:
|
||||||
```yaml
|
```yaml
|
||||||
- &CENTRE
|
- &CENTER
|
||||||
x: 1
|
x: 1
|
||||||
y: 2
|
y: 2
|
||||||
- &LEFT
|
- &LEFT
|
||||||
@ -328,7 +328,7 @@ Given a sample.yml file of:
|
|||||||
r: 10
|
r: 10
|
||||||
- &SMALL
|
- &SMALL
|
||||||
r: 1
|
r: 1
|
||||||
- <<:
|
- !!merge <<:
|
||||||
- *BIG
|
- *BIG
|
||||||
- *LEFT
|
- *LEFT
|
||||||
- *SMALL
|
- *SMALL
|
||||||
@ -361,13 +361,13 @@ bar: &bar
|
|||||||
c: bar_c
|
c: bar_c
|
||||||
foobarList:
|
foobarList:
|
||||||
b: foobarList_b
|
b: foobarList_b
|
||||||
<<:
|
!!merge <<:
|
||||||
- *foo
|
- *foo
|
||||||
- *bar
|
- *bar
|
||||||
c: foobarList_c
|
c: foobarList_c
|
||||||
foobar:
|
foobar:
|
||||||
c: foobar_c
|
c: foobar_c
|
||||||
<<: *foo
|
!!merge <<: *foo
|
||||||
thing: foobar_thing
|
thing: foobar_thing
|
||||||
```
|
```
|
||||||
then
|
then
|
||||||
@ -401,7 +401,7 @@ Taken from https://yaml.org/type/merge.html. Same values as legacy, but with the
|
|||||||
|
|
||||||
Given a sample.yml file of:
|
Given a sample.yml file of:
|
||||||
```yaml
|
```yaml
|
||||||
- &CENTRE
|
- &CENTER
|
||||||
x: 1
|
x: 1
|
||||||
y: 2
|
y: 2
|
||||||
- &LEFT
|
- &LEFT
|
||||||
@ -411,8 +411,8 @@ Given a sample.yml file of:
|
|||||||
r: 10
|
r: 10
|
||||||
- &SMALL
|
- &SMALL
|
||||||
r: 1
|
r: 1
|
||||||
- <<:
|
- !!merge <<:
|
||||||
- *CENTRE
|
- *CENTER
|
||||||
- *BIG
|
- *BIG
|
||||||
```
|
```
|
||||||
then
|
then
|
||||||
@ -432,7 +432,7 @@ Taken from https://yaml.org/type/merge.html. Same values as legacy, but with the
|
|||||||
|
|
||||||
Given a sample.yml file of:
|
Given a sample.yml file of:
|
||||||
```yaml
|
```yaml
|
||||||
- &CENTRE
|
- &CENTER
|
||||||
x: 1
|
x: 1
|
||||||
y: 2
|
y: 2
|
||||||
- &LEFT
|
- &LEFT
|
||||||
@ -442,7 +442,7 @@ Given a sample.yml file of:
|
|||||||
r: 10
|
r: 10
|
||||||
- &SMALL
|
- &SMALL
|
||||||
r: 1
|
r: 1
|
||||||
- <<:
|
- !!merge <<:
|
||||||
- *BIG
|
- *BIG
|
||||||
- *LEFT
|
- *LEFT
|
||||||
- *SMALL
|
- *SMALL
|
||||||
@ -467,7 +467,7 @@ Given a sample.yml file of:
|
|||||||
```yaml
|
```yaml
|
||||||
a:
|
a:
|
||||||
b: &b 42
|
b: &b 42
|
||||||
<<:
|
!!merge <<:
|
||||||
c: *b
|
c: *b
|
||||||
```
|
```
|
||||||
then
|
then
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Various operators for parsing and manipulating dates.
|
Various operators for parsing and manipulating dates.
|
||||||
|
|
||||||
## Date time formatting
|
## Date time formattings
|
||||||
This uses Golang's built in time library for parsing and formatting date times.
|
This uses Golang's built in time library for parsing and formatting date times.
|
||||||
|
|
||||||
When not specified, the RFC3339 standard is assumed `2006-01-02T15:04:05Z07:00` for parsing.
|
When not specified, the RFC3339 standard is assumed `2006-01-02T15:04:05Z07:00` for parsing.
|
||||||
|
|||||||
@ -55,7 +55,7 @@ yq '.a = .a / 0 | .b = .b / 0' sample.yml
|
|||||||
```
|
```
|
||||||
will output
|
will output
|
||||||
```yaml
|
```yaml
|
||||||
a: +Inf
|
a: !!float +Inf
|
||||||
b: -Inf
|
b: !!float -Inf
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Use the `alias` and `anchor` operators to read and write yaml aliases and anchors. The `explode` operator normalises a yaml file (dereference (or expands) aliases and remove anchor names).
|
Use the `alias` and `anchor` operators to read and write yaml aliases and anchors. The `explode` operator normalises a yaml file (dereference (or expands) aliases and remove anchor names).
|
||||||
|
|
||||||
`yq` supports merge keys (like `<<: *blah`) from YAML 1.1. These are no longer part of the YAML 1.2 standard, but remain common in practice. Plain `<<:` keys are recognised as merge keys and round-trip as `<<:` without an explicit `!!merge` tag. When the source uses an explicit `!!merge` tag, that is preserved on output. Internally, when `yq` synthesises a `<<` map key (for example during merge operations), it tags the key as `!!merge` rather than `!!str`.
|
`yq` supports merge aliases (like `<<: *blah`) however this is no longer in the standard yaml spec (1.2) and so `yq` will automatically add the `!!merge` tag to these nodes as it is effectively a custom tag.
|
||||||
|
|
||||||
|
|
||||||
## NOTE --yaml-fix-merge-anchor-to-spec flag
|
## NOTE --yaml-fix-merge-anchor-to-spec flag
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Various operators for parsing and manipulating dates.
|
Various operators for parsing and manipulating dates.
|
||||||
|
|
||||||
## Date time formatting
|
## Date time formattings
|
||||||
This uses Golang's built in time library for parsing and formatting date times.
|
This uses Golang's built in time library for parsing and formatting date times.
|
||||||
|
|
||||||
When not specified, the RFC3339 standard is assumed `2006-01-02T15:04:05Z07:00` for parsing.
|
When not specified, the RFC3339 standard is assumed `2006-01-02T15:04:05Z07:00` for parsing.
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
# Slice Array or String
|
# Slice/Splice Array
|
||||||
|
|
||||||
The slice operator works on both arrays and strings. Like the `jq` equivalent, `.[10:15]` will return a subarray (or substring) of length 5, starting from index 10 inclusive, up to index 15 exclusive. Negative numbers count backwards from the end of the array or string.
|
The slice array operator takes an array as input and returns a subarray. Like the `jq` equivalent, `.[10:15]` will return an array of length 5, starting from index 10 inclusive, up to index 15 exclusive. Negative numbers count backwards from the end of the array.
|
||||||
|
|
||||||
You may leave out the first or second number, which will refer to the start or end of the array or string respectively.
|
You may leave out the first or second number, which will refer to the start or end of the array respectively.
|
||||||
|
|||||||
@ -1,27 +0,0 @@
|
|||||||
# System Operators
|
|
||||||
|
|
||||||
The `system` operator allows you to run an external command and use its output as a value in your expression.
|
|
||||||
|
|
||||||
**Security warning**: The system operator is disabled by default. You must explicitly pass `--security-enable-system-operator` to use it.
|
|
||||||
|
|
||||||
**Note:** When enabled, the system operator can replicate the functionality of `env` and `load`
|
|
||||||
operators via external commands. Enabling it effectively overrides `--security-disable-env-ops`
|
|
||||||
and `--security-disable-file-ops`.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
```bash
|
|
||||||
yq --security-enable-system-operator --null-input '.field = system("command"; "arg1")'
|
|
||||||
```
|
|
||||||
|
|
||||||
The operator takes:
|
|
||||||
- A command string (required)
|
|
||||||
- An argument (or an array of arguments), separated from the command by `;` (optional)
|
|
||||||
|
|
||||||
The current matched node's value is serialised and piped to the command via stdin. The command's stdout (with trailing newline stripped) is returned as a string.
|
|
||||||
|
|
||||||
## Disabling the system operator
|
|
||||||
|
|
||||||
The system operator is disabled by default. When disabled, an error is returned instead of running the command, consistent with `--security-disable-env-ops` and `--security-disable-file-ops`.
|
|
||||||
|
|
||||||
Use `--security-enable-system-operator` flag to enable it.
|
|
||||||
@ -34,7 +34,7 @@ yq '.a = .a % .b' sample.yml
|
|||||||
```
|
```
|
||||||
will output
|
will output
|
||||||
```yaml
|
```yaml
|
||||||
a: 2
|
a: !!float 2
|
||||||
b: 2.5
|
b: 2.5
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -69,7 +69,7 @@ yq '.a = .a % .b' sample.yml
|
|||||||
```
|
```
|
||||||
will output
|
will output
|
||||||
```yaml
|
```yaml
|
||||||
a: NaN
|
a: !!float NaN
|
||||||
b: 0
|
b: 0
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@ -471,13 +471,13 @@ bar: &bar
|
|||||||
c: bar_c
|
c: bar_c
|
||||||
foobarList:
|
foobarList:
|
||||||
b: foobarList_b
|
b: foobarList_b
|
||||||
<<:
|
!!merge <<:
|
||||||
- *foo
|
- *foo
|
||||||
- *bar
|
- *bar
|
||||||
c: foobarList_c
|
c: foobarList_c
|
||||||
foobar:
|
foobar:
|
||||||
c: foobar_c
|
c: foobar_c
|
||||||
<<: *foo
|
!!merge <<: *foo
|
||||||
thing: foobar_thing
|
thing: foobar_thing
|
||||||
```
|
```
|
||||||
then
|
then
|
||||||
@ -487,7 +487,7 @@ yq '.foobar * .foobarList' sample.yml
|
|||||||
will output
|
will output
|
||||||
```yaml
|
```yaml
|
||||||
c: foobarList_c
|
c: foobarList_c
|
||||||
<<:
|
!!merge <<:
|
||||||
- *foo
|
- *foo
|
||||||
- *bar
|
- *bar
|
||||||
thing: foobar_thing
|
thing: foobar_thing
|
||||||
|
|||||||
@ -79,46 +79,6 @@ will output
|
|||||||
c: cat
|
c: cat
|
||||||
```
|
```
|
||||||
|
|
||||||
## Get the top (root) parent
|
|
||||||
Use negative numbers to get the top parents. You can think of this as indexing into the 'parents' array above
|
|
||||||
|
|
||||||
Given a sample.yml file of:
|
|
||||||
```yaml
|
|
||||||
a:
|
|
||||||
b:
|
|
||||||
c: cat
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq '.a.b.c | parent(-1)' sample.yml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
a:
|
|
||||||
b:
|
|
||||||
c: cat
|
|
||||||
```
|
|
||||||
|
|
||||||
## Root
|
|
||||||
Alias for parent(-1), returns the top level parent. This is usually the document node.
|
|
||||||
|
|
||||||
Given a sample.yml file of:
|
|
||||||
```yaml
|
|
||||||
a:
|
|
||||||
b:
|
|
||||||
c: cat
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq '.a.b.c | root' sample.yml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
a:
|
|
||||||
b:
|
|
||||||
c: cat
|
|
||||||
```
|
|
||||||
|
|
||||||
## N-th parent
|
## N-th parent
|
||||||
You can optionally supply the number of levels to go up for the parent, the default being 1.
|
You can optionally supply the number of levels to go up for the parent, the default being 1.
|
||||||
|
|
||||||
@ -156,25 +116,6 @@ a:
|
|||||||
c: cat
|
c: cat
|
||||||
```
|
```
|
||||||
|
|
||||||
## N-th negative
|
|
||||||
Similarly, use negative numbers to index backwards from the parents array
|
|
||||||
|
|
||||||
Given a sample.yml file of:
|
|
||||||
```yaml
|
|
||||||
a:
|
|
||||||
b:
|
|
||||||
c: cat
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq '.a.b.c | parent(-2)' sample.yml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
b:
|
|
||||||
c: cat
|
|
||||||
```
|
|
||||||
|
|
||||||
## No parent
|
## No parent
|
||||||
Given a sample.yml file of:
|
Given a sample.yml file of:
|
||||||
```yaml
|
```yaml
|
||||||
|
|||||||
@ -131,13 +131,13 @@ bar: &bar
|
|||||||
c: bar_c
|
c: bar_c
|
||||||
foobarList:
|
foobarList:
|
||||||
b: foobarList_b
|
b: foobarList_b
|
||||||
<<:
|
!!merge <<:
|
||||||
- *foo
|
- *foo
|
||||||
- *bar
|
- *bar
|
||||||
c: foobarList_c
|
c: foobarList_c
|
||||||
foobar:
|
foobar:
|
||||||
c: foobar_c
|
c: foobar_c
|
||||||
<<: *foo
|
!!merge <<: *foo
|
||||||
thing: foobar_thing
|
thing: foobar_thing
|
||||||
```
|
```
|
||||||
then
|
then
|
||||||
@ -147,7 +147,7 @@ yq '.foobar | [..]' sample.yml
|
|||||||
will output
|
will output
|
||||||
```yaml
|
```yaml
|
||||||
- c: foobar_c
|
- c: foobar_c
|
||||||
<<: *foo
|
!!merge <<: *foo
|
||||||
thing: foobar_thing
|
thing: foobar_thing
|
||||||
- foobar_c
|
- foobar_c
|
||||||
- *foo
|
- *foo
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
# Slice Array or String
|
# Slice/Splice Array
|
||||||
|
|
||||||
The slice operator works on both arrays and strings. Like the `jq` equivalent, `.[10:15]` will return a subarray (or substring) of length 5, starting from index 10 inclusive, up to index 15 exclusive. Negative numbers count backwards from the end of the array or string.
|
The slice array operator takes an array as input and returns a subarray. Like the `jq` equivalent, `.[10:15]` will return an array of length 5, starting from index 10 inclusive, up to index 15 exclusive. Negative numbers count backwards from the end of the array.
|
||||||
|
|
||||||
You may leave out the first or second number, which will refer to the start or end of the array or string respectively.
|
You may leave out the first or second number, which will refer to the start or end of the array respectively.
|
||||||
|
|
||||||
## Slicing arrays
|
## Slicing arrays
|
||||||
Given a sample.yml file of:
|
Given a sample.yml file of:
|
||||||
@ -103,81 +103,3 @@ will output
|
|||||||
- cow
|
- cow
|
||||||
```
|
```
|
||||||
|
|
||||||
## Slicing strings
|
|
||||||
Given a sample.yml file of:
|
|
||||||
```yaml
|
|
||||||
country: Australia
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq '.country[0:5]' sample.yml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
Austr
|
|
||||||
```
|
|
||||||
|
|
||||||
## Slicing strings - without the second number
|
|
||||||
Finishes at the end of the string
|
|
||||||
|
|
||||||
Given a sample.yml file of:
|
|
||||||
```yaml
|
|
||||||
country: Australia
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq '.country[5:]' sample.yml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
alia
|
|
||||||
```
|
|
||||||
|
|
||||||
## Slicing strings - without the first number
|
|
||||||
Starts from the start of the string
|
|
||||||
|
|
||||||
Given a sample.yml file of:
|
|
||||||
```yaml
|
|
||||||
country: Australia
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq '.country[:5]' sample.yml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
Austr
|
|
||||||
```
|
|
||||||
|
|
||||||
## Slicing strings - use negative numbers to count backwards from the end
|
|
||||||
Negative indices count from the end of the string
|
|
||||||
|
|
||||||
Given a sample.yml file of:
|
|
||||||
```yaml
|
|
||||||
country: Australia
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq '.country[-5:]' sample.yml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
ralia
|
|
||||||
```
|
|
||||||
|
|
||||||
## Slicing strings - Unicode
|
|
||||||
Indices are rune-based, so multi-byte characters are handled correctly
|
|
||||||
|
|
||||||
Given a sample.yml file of:
|
|
||||||
```yaml
|
|
||||||
greeting: héllo
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq '.greeting[1:3]' sample.yml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
él
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|||||||
@ -1,76 +0,0 @@
|
|||||||
# System Operators
|
|
||||||
|
|
||||||
The `system` operator allows you to run an external command and use its output as a value in your expression.
|
|
||||||
|
|
||||||
**Security warning**: The system operator is disabled by default. You must explicitly pass `--security-enable-system-operator` to use it.
|
|
||||||
|
|
||||||
**Note:** When enabled, the system operator can replicate the functionality of `env` and `load`
|
|
||||||
operators via external commands. Enabling it effectively overrides `--security-disable-env-ops`
|
|
||||||
and `--security-disable-file-ops`.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
```bash
|
|
||||||
yq --security-enable-system-operator --null-input '.field = system("command"; "arg1")'
|
|
||||||
```
|
|
||||||
|
|
||||||
The operator takes:
|
|
||||||
- A command string (required)
|
|
||||||
- An argument (or an array of arguments), separated from the command by `;` (optional)
|
|
||||||
|
|
||||||
The current matched node's value is serialised and piped to the command via stdin. The command's stdout (with trailing newline stripped) is returned as a string.
|
|
||||||
|
|
||||||
## Disabling the system operator
|
|
||||||
|
|
||||||
The system operator is disabled by default. When disabled, an error is returned instead of running the command, consistent with `--security-disable-env-ops` and `--security-disable-file-ops`.
|
|
||||||
|
|
||||||
Use `--security-enable-system-operator` flag to enable it.
|
|
||||||
|
|
||||||
## system operator returns error when disabled
|
|
||||||
Use `--security-enable-system-operator` to enable the system operator.
|
|
||||||
|
|
||||||
Given a sample.yml file of:
|
|
||||||
```yaml
|
|
||||||
country: Australia
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq '.country = system("/usr/bin/echo"; "test")' sample.yml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```bash
|
|
||||||
Error: system operations are disabled, use --security-enable-system-operator to enable
|
|
||||||
```
|
|
||||||
|
|
||||||
## Run a command with an argument
|
|
||||||
Use `--security-enable-system-operator` to enable the system operator.
|
|
||||||
|
|
||||||
Given a sample.yml file of:
|
|
||||||
```yaml
|
|
||||||
country: Australia
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq --security-enable-system-operator '.country = system("/bin/echo"; "test")' sample.yml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
country: test
|
|
||||||
```
|
|
||||||
|
|
||||||
## Run a command without arguments
|
|
||||||
Omit the semicolon and args to run the command with no extra arguments.
|
|
||||||
|
|
||||||
Given a sample.yml file of:
|
|
||||||
```yaml
|
|
||||||
a: hello
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq --security-enable-system-operator '.a = system("/bin/echo")' sample.yml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
a: ""
|
|
||||||
```
|
|
||||||
|
|
||||||
@ -294,13 +294,13 @@ bar: &bar
|
|||||||
c: bar_c
|
c: bar_c
|
||||||
foobarList:
|
foobarList:
|
||||||
b: foobarList_b
|
b: foobarList_b
|
||||||
<<:
|
!!merge <<:
|
||||||
- *foo
|
- *foo
|
||||||
- *bar
|
- *bar
|
||||||
c: foobarList_c
|
c: foobarList_c
|
||||||
foobar:
|
foobar:
|
||||||
c: foobar_c
|
c: foobar_c
|
||||||
<<: *foo
|
!!merge <<: *foo
|
||||||
thing: foobar_thing
|
thing: foobar_thing
|
||||||
```
|
```
|
||||||
then
|
then
|
||||||
@ -325,13 +325,13 @@ bar: &bar
|
|||||||
c: bar_c
|
c: bar_c
|
||||||
foobarList:
|
foobarList:
|
||||||
b: foobarList_b
|
b: foobarList_b
|
||||||
<<:
|
!!merge <<:
|
||||||
- *foo
|
- *foo
|
||||||
- *bar
|
- *bar
|
||||||
c: foobarList_c
|
c: foobarList_c
|
||||||
foobar:
|
foobar:
|
||||||
c: foobar_c
|
c: foobar_c
|
||||||
<<: *foo
|
!!merge <<: *foo
|
||||||
thing: foobar_thing
|
thing: foobar_thing
|
||||||
```
|
```
|
||||||
then
|
then
|
||||||
@ -376,13 +376,13 @@ bar: &bar
|
|||||||
c: bar_c
|
c: bar_c
|
||||||
foobarList:
|
foobarList:
|
||||||
b: foobarList_b
|
b: foobarList_b
|
||||||
<<:
|
!!merge <<:
|
||||||
- *foo
|
- *foo
|
||||||
- *bar
|
- *bar
|
||||||
c: foobarList_c
|
c: foobarList_c
|
||||||
foobar:
|
foobar:
|
||||||
c: foobar_c
|
c: foobar_c
|
||||||
<<: *foo
|
!!merge <<: *foo
|
||||||
thing: foobar_thing
|
thing: foobar_thing
|
||||||
```
|
```
|
||||||
then
|
then
|
||||||
@ -409,13 +409,13 @@ bar: &bar
|
|||||||
c: bar_c
|
c: bar_c
|
||||||
foobarList:
|
foobarList:
|
||||||
b: foobarList_b
|
b: foobarList_b
|
||||||
<<:
|
!!merge <<:
|
||||||
- *foo
|
- *foo
|
||||||
- *bar
|
- *bar
|
||||||
c: foobarList_c
|
c: foobarList_c
|
||||||
foobar:
|
foobar:
|
||||||
c: foobar_c
|
c: foobar_c
|
||||||
<<: *foo
|
!!merge <<: *foo
|
||||||
thing: foobar_thing
|
thing: foobar_thing
|
||||||
```
|
```
|
||||||
then
|
then
|
||||||
@ -442,13 +442,13 @@ bar: &bar
|
|||||||
c: bar_c
|
c: bar_c
|
||||||
foobarList:
|
foobarList:
|
||||||
b: foobarList_b
|
b: foobarList_b
|
||||||
<<:
|
!!merge <<:
|
||||||
- *foo
|
- *foo
|
||||||
- *bar
|
- *bar
|
||||||
c: foobarList_c
|
c: foobarList_c
|
||||||
foobar:
|
foobar:
|
||||||
c: foobar_c
|
c: foobar_c
|
||||||
<<: *foo
|
!!merge <<: *foo
|
||||||
thing: foobar_thing
|
thing: foobar_thing
|
||||||
```
|
```
|
||||||
then
|
then
|
||||||
@ -477,13 +477,13 @@ bar: &bar
|
|||||||
c: bar_c
|
c: bar_c
|
||||||
foobarList:
|
foobarList:
|
||||||
b: foobarList_b
|
b: foobarList_b
|
||||||
<<:
|
!!merge <<:
|
||||||
- *foo
|
- *foo
|
||||||
- *bar
|
- *bar
|
||||||
c: foobarList_c
|
c: foobarList_c
|
||||||
foobar:
|
foobar:
|
||||||
c: foobar_c
|
c: foobar_c
|
||||||
<<: *foo
|
!!merge <<: *foo
|
||||||
thing: foobar_thing
|
thing: foobar_thing
|
||||||
```
|
```
|
||||||
then
|
then
|
||||||
@ -513,13 +513,13 @@ bar: &bar
|
|||||||
c: bar_c
|
c: bar_c
|
||||||
foobarList:
|
foobarList:
|
||||||
b: foobarList_b
|
b: foobarList_b
|
||||||
<<:
|
!!merge <<:
|
||||||
- *foo
|
- *foo
|
||||||
- *bar
|
- *bar
|
||||||
c: foobarList_c
|
c: foobarList_c
|
||||||
foobar:
|
foobar:
|
||||||
c: foobar_c
|
c: foobar_c
|
||||||
<<: *foo
|
!!merge <<: *foo
|
||||||
thing: foobar_thing
|
thing: foobar_thing
|
||||||
```
|
```
|
||||||
then
|
then
|
||||||
@ -546,13 +546,13 @@ bar: &bar
|
|||||||
c: bar_c
|
c: bar_c
|
||||||
foobarList:
|
foobarList:
|
||||||
b: foobarList_b
|
b: foobarList_b
|
||||||
<<:
|
!!merge <<:
|
||||||
- *foo
|
- *foo
|
||||||
- *bar
|
- *bar
|
||||||
c: foobarList_c
|
c: foobarList_c
|
||||||
foobar:
|
foobar:
|
||||||
c: foobar_c
|
c: foobar_c
|
||||||
<<: *foo
|
!!merge <<: *foo
|
||||||
thing: foobar_thing
|
thing: foobar_thing
|
||||||
```
|
```
|
||||||
then
|
then
|
||||||
@ -579,13 +579,13 @@ bar: &bar
|
|||||||
c: bar_c
|
c: bar_c
|
||||||
foobarList:
|
foobarList:
|
||||||
b: foobarList_b
|
b: foobarList_b
|
||||||
<<:
|
!!merge <<:
|
||||||
- *foo
|
- *foo
|
||||||
- *bar
|
- *bar
|
||||||
c: foobarList_c
|
c: foobarList_c
|
||||||
foobar:
|
foobar:
|
||||||
c: foobar_c
|
c: foobar_c
|
||||||
<<: *foo
|
!!merge <<: *foo
|
||||||
thing: foobar_thing
|
thing: foobar_thing
|
||||||
```
|
```
|
||||||
then
|
then
|
||||||
@ -614,13 +614,13 @@ bar: &bar
|
|||||||
c: bar_c
|
c: bar_c
|
||||||
foobarList:
|
foobarList:
|
||||||
b: foobarList_b
|
b: foobarList_b
|
||||||
<<:
|
!!merge <<:
|
||||||
- *foo
|
- *foo
|
||||||
- *bar
|
- *bar
|
||||||
c: foobarList_c
|
c: foobarList_c
|
||||||
foobar:
|
foobar:
|
||||||
c: foobar_c
|
c: foobar_c
|
||||||
<<: *foo
|
!!merge <<: *foo
|
||||||
thing: foobar_thing
|
thing: foobar_thing
|
||||||
```
|
```
|
||||||
then
|
then
|
||||||
|
|||||||
@ -125,22 +125,6 @@ will output
|
|||||||
{"whatever":"cat"}
|
{"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
|
## Roundtrip JSON Lines / NDJSON
|
||||||
Given a sample.json file of:
|
Given a sample.json file of:
|
||||||
```json
|
```json
|
||||||
|
|||||||
@ -7,7 +7,7 @@ HCL is commonly used in HashiCorp tools like Terraform for configuration files.
|
|||||||
- String interpolation and expressions (preserved without quotes)
|
- String interpolation and expressions (preserved without quotes)
|
||||||
- Comments (leading, head, and line comments)
|
- Comments (leading, head, and line comments)
|
||||||
- Nested structures (maps and lists)
|
- Nested structures (maps and lists)
|
||||||
- Syntax colorisation when enabled
|
- Syntax colorization when enabled
|
||||||
|
|
||||||
|
|
||||||
## Parse HCL
|
## Parse HCL
|
||||||
|
|||||||
@ -7,5 +7,5 @@ HCL is commonly used in HashiCorp tools like Terraform for configuration files.
|
|||||||
- String interpolation and expressions (preserved without quotes)
|
- String interpolation and expressions (preserved without quotes)
|
||||||
- Comments (leading, head, and line comments)
|
- Comments (leading, head, and line comments)
|
||||||
- Nested structures (maps and lists)
|
- Nested structures (maps and lists)
|
||||||
- Syntax colorisation when enabled
|
- Syntax colorization when enabled
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +0,0 @@
|
|||||||
# KYaml
|
|
||||||
|
|
||||||
Encode and decode to and from KYaml (a restricted subset of YAML that uses flow-style collections).
|
|
||||||
|
|
||||||
KYaml is useful when you want YAML data rendered in a compact, JSON-like form while still supporting YAML features like comments.
|
|
||||||
|
|
||||||
Notes:
|
|
||||||
- Strings are always double-quoted in KYaml output.
|
|
||||||
- Anchors and aliases are expanded (KYaml output does not emit them).
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
# TOML
|
# TOML
|
||||||
|
|
||||||
Encode and decode to and from TOML.
|
Decode from TOML. Note that `yq` does not yet support outputting in TOML format (and therefore it cannot roundtrip)
|
||||||
|
|
||||||
|
|||||||
@ -1,253 +0,0 @@
|
|||||||
# KYaml
|
|
||||||
|
|
||||||
Encode and decode to and from KYaml (a restricted subset of YAML that uses flow-style collections).
|
|
||||||
|
|
||||||
KYaml is useful when you want YAML data rendered in a compact, JSON-like form while still supporting YAML features like comments.
|
|
||||||
|
|
||||||
Notes:
|
|
||||||
- Strings are always double-quoted in KYaml output.
|
|
||||||
- Anchors and aliases are expanded (KYaml output does not emit them).
|
|
||||||
|
|
||||||
## Encode kyaml: plain string scalar
|
|
||||||
Strings are always double-quoted in KYaml output.
|
|
||||||
|
|
||||||
Given a sample.yml file of:
|
|
||||||
```yaml
|
|
||||||
cat
|
|
||||||
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq -o=kyaml '.' sample.yml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
"cat"
|
|
||||||
```
|
|
||||||
|
|
||||||
## encode flow mapping and sequence
|
|
||||||
Given a sample.yml file of:
|
|
||||||
```yaml
|
|
||||||
a: b
|
|
||||||
c:
|
|
||||||
- d
|
|
||||||
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq -o=kyaml '.' sample.yml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
{
|
|
||||||
a: "b",
|
|
||||||
c: [
|
|
||||||
"d",
|
|
||||||
],
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## encode non-string scalars
|
|
||||||
Given a sample.yml file of:
|
|
||||||
```yaml
|
|
||||||
a: 12
|
|
||||||
b: true
|
|
||||||
c: null
|
|
||||||
d: "true"
|
|
||||||
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq -o=kyaml '.' sample.yml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
{
|
|
||||||
a: 12,
|
|
||||||
b: true,
|
|
||||||
c: null,
|
|
||||||
d: "true",
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## quote non-identifier keys
|
|
||||||
Given a sample.yml file of:
|
|
||||||
```yaml
|
|
||||||
"1a": b
|
|
||||||
"has space": c
|
|
||||||
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq -o=kyaml '.' sample.yml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
{
|
|
||||||
"1a": "b",
|
|
||||||
"has space": "c",
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## escape quoted strings
|
|
||||||
Given a sample.yml file of:
|
|
||||||
```yaml
|
|
||||||
a: "line1\nline2\t\"q\""
|
|
||||||
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq -o=kyaml '.' sample.yml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
{
|
|
||||||
a: "line1\nline2\t\"q\"",
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## preserve comments when encoding
|
|
||||||
Given a sample.yml file of:
|
|
||||||
```yaml
|
|
||||||
# leading
|
|
||||||
a: 1 # a line
|
|
||||||
# head b
|
|
||||||
b: 2
|
|
||||||
c:
|
|
||||||
# head d
|
|
||||||
- d # d line
|
|
||||||
- e
|
|
||||||
# trailing
|
|
||||||
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq -o=kyaml '.' sample.yml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
# leading
|
|
||||||
{
|
|
||||||
a: 1, # a line
|
|
||||||
# head b
|
|
||||||
b: 2,
|
|
||||||
c: [
|
|
||||||
# head d
|
|
||||||
"d", # d line
|
|
||||||
"e",
|
|
||||||
],
|
|
||||||
# trailing
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Encode kyaml: anchors and aliases
|
|
||||||
KYaml output does not support anchors/aliases; they are expanded to concrete values.
|
|
||||||
|
|
||||||
Given a sample.yml file of:
|
|
||||||
```yaml
|
|
||||||
base: &base
|
|
||||||
a: b
|
|
||||||
copy: *base
|
|
||||||
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq -o=kyaml '.' sample.yml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
{
|
|
||||||
base: {
|
|
||||||
a: "b",
|
|
||||||
},
|
|
||||||
copy: {
|
|
||||||
a: "b",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Encode kyaml: yaml to kyaml shows formatting differences
|
|
||||||
KYaml uses flow-style collections (braces/brackets) and explicit commas.
|
|
||||||
|
|
||||||
Given a sample.yml file of:
|
|
||||||
```yaml
|
|
||||||
person:
|
|
||||||
name: John
|
|
||||||
pets:
|
|
||||||
- cat
|
|
||||||
- dog
|
|
||||||
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq -o=kyaml '.' sample.yml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
{
|
|
||||||
person: {
|
|
||||||
name: "John",
|
|
||||||
pets: [
|
|
||||||
"cat",
|
|
||||||
"dog",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Encode kyaml: nested lists of objects
|
|
||||||
Lists and objects can be nested arbitrarily; KYaml always uses flow-style collections.
|
|
||||||
|
|
||||||
Given a sample.yml file of:
|
|
||||||
```yaml
|
|
||||||
- name: a
|
|
||||||
items:
|
|
||||||
- id: 1
|
|
||||||
tags:
|
|
||||||
- k: x
|
|
||||||
v: y
|
|
||||||
- k: x2
|
|
||||||
v: y2
|
|
||||||
- id: 2
|
|
||||||
tags:
|
|
||||||
- k: z
|
|
||||||
v: w
|
|
||||||
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq -o=kyaml '.' sample.yml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
[
|
|
||||||
{
|
|
||||||
name: "a",
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
tags: [
|
|
||||||
{
|
|
||||||
k: "x",
|
|
||||||
v: "y",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
k: "x2",
|
|
||||||
v: "y2",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
tags: [
|
|
||||||
{
|
|
||||||
k: "z",
|
|
||||||
v: "w",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
@ -1,6 +1,6 @@
|
|||||||
# TOML
|
# TOML
|
||||||
|
|
||||||
Encode and decode to and from TOML.
|
Decode from TOML. Note that `yq` does not yet support outputting in TOML format (and therefore it cannot roundtrip)
|
||||||
|
|
||||||
|
|
||||||
## Parse: Simple
|
## Parse: Simple
|
||||||
@ -141,263 +141,3 @@ will output
|
|||||||
dependencies: {}
|
dependencies: {}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Roundtrip: inline table attribute
|
|
||||||
Given a sample.toml file of:
|
|
||||||
```toml
|
|
||||||
name = { first = "Tom", last = "Preston-Werner" }
|
|
||||||
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq '.' sample.toml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
name = { first = "Tom", last = "Preston-Werner" }
|
|
||||||
```
|
|
||||||
|
|
||||||
## Roundtrip: table section
|
|
||||||
Given a sample.toml file of:
|
|
||||||
```toml
|
|
||||||
[owner.contact]
|
|
||||||
name = "Tom"
|
|
||||||
age = 36
|
|
||||||
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq '.' sample.toml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
[owner.contact]
|
|
||||||
name = "Tom"
|
|
||||||
age = 36
|
|
||||||
```
|
|
||||||
|
|
||||||
## Roundtrip: array of tables
|
|
||||||
Given a sample.toml file of:
|
|
||||||
```toml
|
|
||||||
[[fruits]]
|
|
||||||
name = "apple"
|
|
||||||
[[fruits.varieties]]
|
|
||||||
name = "red delicious"
|
|
||||||
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq '.' sample.toml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
[[fruits]]
|
|
||||||
name = "apple"
|
|
||||||
[[fruits.varieties]]
|
|
||||||
name = "red delicious"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Roundtrip: arrays and scalars
|
|
||||||
Given a sample.toml file of:
|
|
||||||
```toml
|
|
||||||
A = ["hello", ["world", "again"]]
|
|
||||||
B = 12
|
|
||||||
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq '.' sample.toml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
A = ["hello", ["world", "again"]]
|
|
||||||
B = 12
|
|
||||||
```
|
|
||||||
|
|
||||||
## Roundtrip: simple
|
|
||||||
Given a sample.toml file of:
|
|
||||||
```toml
|
|
||||||
A = "hello"
|
|
||||||
B = 12
|
|
||||||
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq '.' sample.toml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
A = "hello"
|
|
||||||
B = 12
|
|
||||||
```
|
|
||||||
|
|
||||||
## Roundtrip: deep paths
|
|
||||||
Given a sample.toml file of:
|
|
||||||
```toml
|
|
||||||
[person]
|
|
||||||
name = "hello"
|
|
||||||
address = "12 cat st"
|
|
||||||
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq '.' sample.toml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
[person]
|
|
||||||
name = "hello"
|
|
||||||
address = "12 cat st"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Roundtrip: empty array
|
|
||||||
Given a sample.toml file of:
|
|
||||||
```toml
|
|
||||||
A = []
|
|
||||||
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq '.' sample.toml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
A = []
|
|
||||||
```
|
|
||||||
|
|
||||||
## Roundtrip: sample table
|
|
||||||
Given a sample.toml file of:
|
|
||||||
```toml
|
|
||||||
var = "x"
|
|
||||||
|
|
||||||
[owner.contact]
|
|
||||||
name = "Tom Preston-Werner"
|
|
||||||
age = 36
|
|
||||||
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq '.' sample.toml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
var = "x"
|
|
||||||
|
|
||||||
[owner.contact]
|
|
||||||
name = "Tom Preston-Werner"
|
|
||||||
age = 36
|
|
||||||
```
|
|
||||||
|
|
||||||
## Roundtrip: empty table
|
|
||||||
Given a sample.toml file of:
|
|
||||||
```toml
|
|
||||||
[dependencies]
|
|
||||||
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq '.' sample.toml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
[dependencies]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Roundtrip: comments
|
|
||||||
Given a sample.toml file of:
|
|
||||||
```toml
|
|
||||||
# This is a comment
|
|
||||||
A = "hello" # inline comment
|
|
||||||
B = 12
|
|
||||||
|
|
||||||
# Table comment
|
|
||||||
[person]
|
|
||||||
name = "Tom" # name comment
|
|
||||||
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq '.' sample.toml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
# This is a comment
|
|
||||||
A = "hello" # inline comment
|
|
||||||
B = 12
|
|
||||||
|
|
||||||
# Table comment
|
|
||||||
[person]
|
|
||||||
name = "Tom" # name comment
|
|
||||||
```
|
|
||||||
|
|
||||||
## Roundtrip: sample from web
|
|
||||||
Given a sample.toml file of:
|
|
||||||
```toml
|
|
||||||
# This is a TOML document
|
|
||||||
title = "TOML Example"
|
|
||||||
|
|
||||||
[owner]
|
|
||||||
name = "Tom Preston-Werner"
|
|
||||||
dob = 1979-05-27T07:32:00-08:00
|
|
||||||
|
|
||||||
[database]
|
|
||||||
enabled = true
|
|
||||||
ports = [8000, 8001, 8002]
|
|
||||||
data = [["delta", "phi"], [3.14]]
|
|
||||||
temp_targets = { cpu = 79.5, case = 72.0 }
|
|
||||||
|
|
||||||
# [servers] yq can't do this one yet
|
|
||||||
[servers.alpha]
|
|
||||||
ip = "10.0.0.1"
|
|
||||||
role = "frontend"
|
|
||||||
|
|
||||||
[servers.beta]
|
|
||||||
ip = "10.0.0.2"
|
|
||||||
role = "backend"
|
|
||||||
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq '.' sample.toml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
# This is a TOML document
|
|
||||||
title = "TOML Example"
|
|
||||||
|
|
||||||
[owner]
|
|
||||||
name = "Tom Preston-Werner"
|
|
||||||
dob = 1979-05-27T07:32:00-08:00
|
|
||||||
|
|
||||||
[database]
|
|
||||||
enabled = true
|
|
||||||
ports = [8000, 8001, 8002]
|
|
||||||
data = [["delta", "phi"], [3.14]]
|
|
||||||
temp_targets = { cpu = 79.5, case = 72.0 }
|
|
||||||
|
|
||||||
# [servers] yq can't do this one yet
|
|
||||||
[servers.alpha]
|
|
||||||
ip = "10.0.0.1"
|
|
||||||
role = "frontend"
|
|
||||||
|
|
||||||
[servers.beta]
|
|
||||||
ip = "10.0.0.2"
|
|
||||||
role = "backend"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Encode: Simple mapping produces table section
|
|
||||||
Given a sample.yml file of:
|
|
||||||
```yaml
|
|
||||||
arg:
|
|
||||||
hello: foo
|
|
||||||
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq -o toml '.' sample.yml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```toml
|
|
||||||
[arg]
|
|
||||||
hello = "foo"
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|||||||
@ -1,12 +1,7 @@
|
|||||||
package yqlib
|
package yqlib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"errors"
|
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/fatih/color"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Encoder interface {
|
type Encoder interface {
|
||||||
@ -30,63 +25,3 @@ func mapKeysToStrings(node *CandidateNode) {
|
|||||||
mapKeysToStrings(child)
|
mapKeysToStrings(child)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Some funcs are shared between encoder_yaml and encoder_kyaml
|
|
||||||
func PrintYAMLDocumentSeparator(writer io.Writer, PrintDocSeparators bool) error {
|
|
||||||
if PrintDocSeparators {
|
|
||||||
log.Debug("writing doc sep")
|
|
||||||
if err := writeString(writer, "---\n"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
func PrintYAMLLeadingContent(writer io.Writer, content string, PrintDocSeparators bool, ColorsEnabled bool) error {
|
|
||||||
reader := bufio.NewReader(strings.NewReader(content))
|
|
||||||
|
|
||||||
// reuse precompiled package-level regex
|
|
||||||
// (declared in decoder_yaml.go)
|
|
||||||
|
|
||||||
for {
|
|
||||||
|
|
||||||
readline, errReading := reader.ReadString('\n')
|
|
||||||
if errReading != nil && !errors.Is(errReading, io.EOF) {
|
|
||||||
return errReading
|
|
||||||
}
|
|
||||||
if strings.Contains(readline, "$yqDocSeparator$") {
|
|
||||||
// Preserve the original line ending (CRLF or LF)
|
|
||||||
lineEnding := "\n"
|
|
||||||
if strings.HasSuffix(readline, "\r\n") {
|
|
||||||
lineEnding = "\r\n"
|
|
||||||
}
|
|
||||||
if PrintDocSeparators {
|
|
||||||
if err := writeString(writer, "---"+lineEnding); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
if len(readline) > 0 && readline != "\n" && readline[0] != '%' && !commentLineRe.MatchString(readline) {
|
|
||||||
readline = "# " + readline
|
|
||||||
}
|
|
||||||
if ColorsEnabled && strings.TrimSpace(readline) != "" {
|
|
||||||
readline = format(color.FgHiBlack) + readline + format(color.Reset)
|
|
||||||
}
|
|
||||||
if err := writeString(writer, readline); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if errors.Is(errReading, io.EOF) {
|
|
||||||
if readline != "" {
|
|
||||||
// the last comment we read didn't have a newline, put one in
|
|
||||||
if err := writeString(writer, "\n"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|||||||
@ -43,9 +43,6 @@ func (he *hclEncoder) PrintLeadingContent(_ io.Writer, _ string) error {
|
|||||||
|
|
||||||
func (he *hclEncoder) Encode(writer io.Writer, node *CandidateNode) error {
|
func (he *hclEncoder) Encode(writer io.Writer, node *CandidateNode) error {
|
||||||
log.Debugf("I need to encode %v", NodeToString(node))
|
log.Debugf("I need to encode %v", NodeToString(node))
|
||||||
if node.Kind == ScalarNode {
|
|
||||||
return writeString(writer, node.Value+"\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
f := hclwrite.NewEmptyFile()
|
f := hclwrite.NewEmptyFile()
|
||||||
body := f.Body()
|
body := f.Body()
|
||||||
@ -66,8 +63,8 @@ func (he *hclEncoder) Encode(writer io.Writer, node *CandidateNode) error {
|
|||||||
finalOutput := he.injectComments(compactOutput, commentMap)
|
finalOutput := he.injectComments(compactOutput, commentMap)
|
||||||
|
|
||||||
if he.prefs.ColorsEnabled {
|
if he.prefs.ColorsEnabled {
|
||||||
colourized := he.colorizeHcl(finalOutput)
|
colorized := he.colorizeHcl(finalOutput)
|
||||||
_, err := writer.Write(colourized)
|
_, err := writer.Write(colorized)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,18 +170,19 @@ func (he *hclEncoder) injectComments(output []byte, commentMap map[string]string
|
|||||||
return []byte(result)
|
return []byte(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// colorizeHcl applies syntax highlighting to HCL output using fatih/color
|
||||||
func (he *hclEncoder) colorizeHcl(input []byte) []byte {
|
func (he *hclEncoder) colorizeHcl(input []byte) []byte {
|
||||||
hcl := string(input)
|
hcl := string(input)
|
||||||
result := strings.Builder{}
|
result := strings.Builder{}
|
||||||
|
|
||||||
// Create colour functions for different token types
|
// Create color functions for different token types
|
||||||
commentColor := color.New(color.FgHiBlack).SprintFunc()
|
commentColor := color.New(color.FgHiBlack).SprintFunc()
|
||||||
stringColor := color.New(color.FgGreen).SprintFunc()
|
stringColor := color.New(color.FgGreen).SprintFunc()
|
||||||
numberColor := color.New(color.FgHiMagenta).SprintFunc()
|
numberColor := color.New(color.FgHiMagenta).SprintFunc()
|
||||||
keyColor := color.New(color.FgCyan).SprintFunc()
|
keyColor := color.New(color.FgCyan).SprintFunc()
|
||||||
boolColor := color.New(color.FgHiMagenta).SprintFunc()
|
boolColor := color.New(color.FgHiMagenta).SprintFunc()
|
||||||
|
|
||||||
// Simple tokenization for HCL colouring
|
// Simple tokenization for HCL coloring
|
||||||
i := 0
|
i := 0
|
||||||
for i < len(hcl) {
|
for i < len(hcl) {
|
||||||
ch := hcl[i]
|
ch := hcl[i]
|
||||||
@ -449,8 +447,8 @@ func (he *hclEncoder) encodeBlockIfMapping(body *hclwrite.Body, key string, valu
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// If EncodeHintSeparateBlock is set, emit children as separate blocks regardless of label extraction
|
// If EncodeSeparate is set, emit children as separate blocks regardless of label extraction
|
||||||
if valueNode.EncodeHint == EncodeHintSeparateBlock {
|
if valueNode.EncodeSeparate {
|
||||||
if handled, _ := he.encodeMappingChildrenAsBlocks(body, key, valueNode); handled {
|
if handled, _ := he.encodeMappingChildrenAsBlocks(body, key, valueNode); handled {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -475,12 +473,12 @@ func (he *hclEncoder) encodeBlockIfMapping(body *hclwrite.Body, key string, valu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If all child values are mappings, treat each child key as a labelled instance of this block type
|
// If all child values are mappings, treat each child key as a labeled instance of this block type
|
||||||
if handled, _ := he.encodeMappingChildrenAsBlocks(body, key, valueNode); handled {
|
if handled, _ := he.encodeMappingChildrenAsBlocks(body, key, valueNode); handled {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// No labels detected, render as unlabelled block
|
// No labels detected, render as unlabeled block
|
||||||
block := body.AppendNewBlock(key, nil)
|
block := body.AppendNewBlock(key, nil)
|
||||||
if err := he.encodeNodeAttributes(block.Body(), valueNode); err == nil {
|
if err := he.encodeNodeAttributes(block.Body(), valueNode); err == nil {
|
||||||
return true
|
return true
|
||||||
@ -492,7 +490,7 @@ func (he *hclEncoder) encodeBlockIfMapping(body *hclwrite.Body, key string, valu
|
|||||||
// encodeNode encodes a CandidateNode directly to HCL, preserving style information
|
// encodeNode encodes a CandidateNode directly to HCL, preserving style information
|
||||||
func (he *hclEncoder) encodeNode(body *hclwrite.Body, node *CandidateNode) error {
|
func (he *hclEncoder) encodeNode(body *hclwrite.Body, node *CandidateNode) error {
|
||||||
if node.Kind != MappingNode {
|
if node.Kind != MappingNode {
|
||||||
return fmt.Errorf("HCL encoder expects a mapping at the root level, got %v", kindToString(node.Kind))
|
return fmt.Errorf("HCL encoder expects a mapping at the root level")
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < len(node.Content); i += 2 {
|
for i := 0; i < len(node.Content); i += 2 {
|
||||||
@ -537,9 +535,9 @@ func (he *hclEncoder) encodeMappingChildrenAsBlocks(body *hclwrite.Body, blockTy
|
|||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only emit as separate blocks if EncodeHintSeparateBlock is set
|
// Only emit as separate blocks if EncodeSeparate is true
|
||||||
// This allows the encoder to respect the original block structure preserved by the decoder
|
// This allows the encoder to respect the original block structure preserved by the decoder
|
||||||
if valueNode.EncodeHint != EncodeHintSeparateBlock {
|
if !valueNode.EncodeSeparate {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,318 +0,0 @@
|
|||||||
//go:build !yq_nokyaml
|
|
||||||
|
|
||||||
package yqlib
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"io"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type kyamlEncoder struct {
|
|
||||||
prefs KYamlPreferences
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewKYamlEncoder(prefs KYamlPreferences) Encoder {
|
|
||||||
return &kyamlEncoder{prefs: prefs}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ke *kyamlEncoder) CanHandleAliases() bool {
|
|
||||||
// KYAML is a restricted subset; avoid emitting anchors/aliases.
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ke *kyamlEncoder) PrintDocumentSeparator(writer io.Writer) error {
|
|
||||||
return PrintYAMLDocumentSeparator(writer, ke.prefs.PrintDocSeparators)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ke *kyamlEncoder) PrintLeadingContent(writer io.Writer, content string) error {
|
|
||||||
return PrintYAMLLeadingContent(writer, content, ke.prefs.PrintDocSeparators, ke.prefs.ColorsEnabled)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ke *kyamlEncoder) Encode(writer io.Writer, node *CandidateNode) error {
|
|
||||||
log.Debugf("encoderKYaml - going to print %v", NodeToString(node))
|
|
||||||
if node.Kind == ScalarNode && ke.prefs.UnwrapScalar {
|
|
||||||
return writeString(writer, node.Value+"\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
destination := writer
|
|
||||||
tempBuffer := bytes.NewBuffer(nil)
|
|
||||||
if ke.prefs.ColorsEnabled {
|
|
||||||
destination = tempBuffer
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mirror the YAML encoder behaviour: trailing comments on the document root
|
|
||||||
// are stored in FootComment and need to be printed after the document.
|
|
||||||
trailingContent := node.FootComment
|
|
||||||
|
|
||||||
if err := ke.writeCommentBlock(destination, node.HeadComment, 0); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := ke.writeNode(destination, node, 0); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := ke.writeInlineComment(destination, node.LineComment); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := writeString(destination, "\n"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := ke.PrintLeadingContent(destination, trailingContent); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if ke.prefs.ColorsEnabled {
|
|
||||||
return colorizeAndPrint(tempBuffer.Bytes(), writer)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ke *kyamlEncoder) writeNode(writer io.Writer, node *CandidateNode, indent int) error {
|
|
||||||
switch node.Kind {
|
|
||||||
case MappingNode:
|
|
||||||
return ke.writeMapping(writer, node, indent)
|
|
||||||
case SequenceNode:
|
|
||||||
return ke.writeSequence(writer, node, indent)
|
|
||||||
case ScalarNode:
|
|
||||||
return writeString(writer, ke.formatScalar(node))
|
|
||||||
case AliasNode:
|
|
||||||
// Should have been exploded by the printer, but handle defensively.
|
|
||||||
if node.Alias == nil {
|
|
||||||
return writeString(writer, "null")
|
|
||||||
}
|
|
||||||
return ke.writeNode(writer, node.Alias, indent)
|
|
||||||
default:
|
|
||||||
return writeString(writer, "null")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ke *kyamlEncoder) writeMapping(writer io.Writer, node *CandidateNode, indent int) error {
|
|
||||||
if len(node.Content) == 0 {
|
|
||||||
return writeString(writer, "{}")
|
|
||||||
}
|
|
||||||
if err := writeString(writer, "{\n"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i+1 < len(node.Content); i += 2 {
|
|
||||||
keyNode := node.Content[i]
|
|
||||||
valueNode := node.Content[i+1]
|
|
||||||
|
|
||||||
entryIndent := indent + ke.prefs.Indent
|
|
||||||
if err := ke.writeCommentBlock(writer, keyNode.HeadComment, entryIndent); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if valueNode.HeadComment != "" && valueNode.HeadComment != keyNode.HeadComment {
|
|
||||||
if err := ke.writeCommentBlock(writer, valueNode.HeadComment, entryIndent); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := ke.writeIndent(writer, entryIndent); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := writeString(writer, ke.formatKey(keyNode)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := writeString(writer, ": "); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := ke.writeNode(writer, valueNode, entryIndent); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Always emit a trailing comma; KYAML encourages explicit separators,
|
|
||||||
// and this ensures all quoted strings have a trailing `",` as requested.
|
|
||||||
if err := writeString(writer, ","); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
inline := valueNode.LineComment
|
|
||||||
if inline == "" {
|
|
||||||
inline = keyNode.LineComment
|
|
||||||
}
|
|
||||||
if err := ke.writeInlineComment(writer, inline); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := writeString(writer, "\n"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
foot := valueNode.FootComment
|
|
||||||
if foot == "" {
|
|
||||||
foot = keyNode.FootComment
|
|
||||||
}
|
|
||||||
if err := ke.writeCommentBlock(writer, foot, entryIndent); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := ke.writeIndent(writer, indent); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return writeString(writer, "}")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ke *kyamlEncoder) writeSequence(writer io.Writer, node *CandidateNode, indent int) error {
|
|
||||||
if len(node.Content) == 0 {
|
|
||||||
return writeString(writer, "[]")
|
|
||||||
}
|
|
||||||
if err := writeString(writer, "[\n"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, child := range node.Content {
|
|
||||||
itemIndent := indent + ke.prefs.Indent
|
|
||||||
if err := ke.writeCommentBlock(writer, child.HeadComment, itemIndent); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := ke.writeIndent(writer, itemIndent); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := ke.writeNode(writer, child, itemIndent); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := writeString(writer, ","); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := ke.writeInlineComment(writer, child.LineComment); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := writeString(writer, "\n"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := ke.writeCommentBlock(writer, child.FootComment, itemIndent); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := ke.writeIndent(writer, indent); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return writeString(writer, "]")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ke *kyamlEncoder) writeIndent(writer io.Writer, indent int) error {
|
|
||||||
if indent <= 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return writeString(writer, strings.Repeat(" ", indent))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ke *kyamlEncoder) formatKey(keyNode *CandidateNode) string {
|
|
||||||
// KYAML examples use bare keys. Quote keys only when needed.
|
|
||||||
key := keyNode.Value
|
|
||||||
if isValidKYamlBareKey(key) {
|
|
||||||
return key
|
|
||||||
}
|
|
||||||
return `"` + escapeDoubleQuotedString(key) + `"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ke *kyamlEncoder) formatScalar(node *CandidateNode) string {
|
|
||||||
switch node.Tag {
|
|
||||||
case "!!null":
|
|
||||||
return "null"
|
|
||||||
case "!!bool":
|
|
||||||
return strings.ToLower(node.Value)
|
|
||||||
case "!!int", "!!float":
|
|
||||||
return node.Value
|
|
||||||
case "!!str":
|
|
||||||
return `"` + escapeDoubleQuotedString(node.Value) + `"`
|
|
||||||
default:
|
|
||||||
// Fall back to a string representation to avoid implicit typing surprises.
|
|
||||||
return `"` + escapeDoubleQuotedString(node.Value) + `"`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var kyamlBareKeyRe = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_-]*$`)
|
|
||||||
|
|
||||||
func isValidKYamlBareKey(s string) bool {
|
|
||||||
// Conservative: require an identifier-like key; otherwise quote.
|
|
||||||
if s == "" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return kyamlBareKeyRe.MatchString(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func escapeDoubleQuotedString(s string) string {
|
|
||||||
var b strings.Builder
|
|
||||||
b.Grow(len(s) + 2)
|
|
||||||
|
|
||||||
for _, r := range s {
|
|
||||||
switch r {
|
|
||||||
case '\\':
|
|
||||||
b.WriteString(`\\`)
|
|
||||||
case '"':
|
|
||||||
b.WriteString(`\"`)
|
|
||||||
case '\n':
|
|
||||||
b.WriteString(`\n`)
|
|
||||||
case '\r':
|
|
||||||
b.WriteString(`\r`)
|
|
||||||
case '\t':
|
|
||||||
b.WriteString(`\t`)
|
|
||||||
default:
|
|
||||||
if r < 0x20 {
|
|
||||||
// YAML double-quoted strings support \uXXXX escapes.
|
|
||||||
b.WriteString(`\u`)
|
|
||||||
hex := "0000" + strings.ToUpper(strconv.FormatInt(int64(r), 16))
|
|
||||||
b.WriteString(hex[len(hex)-4:])
|
|
||||||
} else {
|
|
||||||
b.WriteRune(r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return b.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ke *kyamlEncoder) writeCommentBlock(writer io.Writer, comment string, indent int) error {
|
|
||||||
if strings.TrimSpace(comment) == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
lines := strings.Split(strings.ReplaceAll(comment, "\r\n", "\n"), "\n")
|
|
||||||
for _, line := range lines {
|
|
||||||
trimmed := strings.TrimSpace(line)
|
|
||||||
if trimmed == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := ke.writeIndent(writer, indent); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
toWrite := line
|
|
||||||
if !commentLineRe.MatchString(toWrite) {
|
|
||||||
toWrite = "# " + toWrite
|
|
||||||
}
|
|
||||||
if err := writeString(writer, toWrite); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := writeString(writer, "\n"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ke *kyamlEncoder) writeInlineComment(writer io.Writer, comment string) error {
|
|
||||||
comment = strings.TrimSpace(strings.ReplaceAll(comment, "\r\n", "\n"))
|
|
||||||
if comment == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
lines := strings.Split(comment, "\n")
|
|
||||||
first := strings.TrimSpace(lines[0])
|
|
||||||
if first == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if !strings.HasPrefix(first, "#") {
|
|
||||||
first = "# " + first
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := writeString(writer, " "); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return writeString(writer, first)
|
|
||||||
}
|
|
||||||
@ -57,13 +57,7 @@ func (pe *shellVariablesEncoder) doEncode(w *io.Writer, node *CandidateNode, pat
|
|||||||
// let's just pick a fallback key to use if we are encoding a single scalar
|
// let's just pick a fallback key to use if we are encoding a single scalar
|
||||||
nonemptyPath = "value"
|
nonemptyPath = "value"
|
||||||
}
|
}
|
||||||
var valueString string
|
_, err := io.WriteString(*w, nonemptyPath+"="+quoteValue(node.Value)+"\n")
|
||||||
if pe.prefs.UnwrapScalar {
|
|
||||||
valueString = node.Value
|
|
||||||
} else {
|
|
||||||
valueString = quoteValue(node.Value)
|
|
||||||
}
|
|
||||||
_, err := io.WriteString(*w, nonemptyPath+"="+valueString+"\n")
|
|
||||||
return err
|
return err
|
||||||
case SequenceNode:
|
case SequenceNode:
|
||||||
for index, child := range node.Content {
|
for index, child := range node.Content {
|
||||||
|
|||||||
@ -135,36 +135,3 @@ func TestShellVariablesEncoderCustomSeparatorArray(t *testing.T) {
|
|||||||
func TestShellVariablesEncoderCustomSeparatorSingleChar(t *testing.T) {
|
func TestShellVariablesEncoderCustomSeparatorSingleChar(t *testing.T) {
|
||||||
assertEncodesToWithSeparator(t, "a:\n b: value", "aXb=value", "X")
|
assertEncodesToWithSeparator(t, "a:\n b: value", "aXb=value", "X")
|
||||||
}
|
}
|
||||||
|
|
||||||
func assertEncodesToUnwrapped(t *testing.T, yaml string, shellvars string) {
|
|
||||||
var output bytes.Buffer
|
|
||||||
writer := bufio.NewWriter(&output)
|
|
||||||
|
|
||||||
originalUnwrapScalar := ConfiguredShellVariablesPreferences.UnwrapScalar
|
|
||||||
defer func() {
|
|
||||||
ConfiguredShellVariablesPreferences.UnwrapScalar = originalUnwrapScalar
|
|
||||||
}()
|
|
||||||
|
|
||||||
ConfiguredShellVariablesPreferences.UnwrapScalar = true
|
|
||||||
|
|
||||||
var encoder = NewShellVariablesEncoder()
|
|
||||||
inputs, err := readDocuments(strings.NewReader(yaml), "test.yml", 0, NewYamlDecoder(ConfiguredYamlPreferences))
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
node := inputs.Front().Value.(*CandidateNode)
|
|
||||||
err = encoder.Encode(writer, node)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
writer.Flush()
|
|
||||||
|
|
||||||
test.AssertResult(t, shellvars, strings.TrimSuffix(output.String(), "\n"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestShellVariablesEncoderUnwrapScalar(t *testing.T) {
|
|
||||||
assertEncodesToUnwrapped(t, "a: Lewis Carroll", "a=Lewis Carroll")
|
|
||||||
assertEncodesToUnwrapped(t, "b: 123", "b=123")
|
|
||||||
assertEncodesToUnwrapped(t, "c: true", "c=true")
|
|
||||||
assertEncodesToUnwrapped(t, "d: value with spaces", "d=value with spaces")
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,58 +1,22 @@
|
|||||||
//go:build !yq_notoml
|
|
||||||
|
|
||||||
package yqlib
|
package yqlib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/fatih/color"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type tomlEncoder struct {
|
type tomlEncoder struct {
|
||||||
wroteRootAttr bool // Track if we wrote root-level attributes before tables
|
|
||||||
prefs TomlPreferences
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTomlEncoder() Encoder {
|
func NewTomlEncoder() Encoder {
|
||||||
return NewTomlEncoderWithPrefs(ConfiguredTomlPreferences)
|
return &tomlEncoder{}
|
||||||
}
|
|
||||||
|
|
||||||
func NewTomlEncoderWithPrefs(prefs TomlPreferences) Encoder {
|
|
||||||
return &tomlEncoder{prefs: prefs}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (te *tomlEncoder) Encode(writer io.Writer, node *CandidateNode) error {
|
func (te *tomlEncoder) Encode(writer io.Writer, node *CandidateNode) error {
|
||||||
if node.Kind != MappingNode {
|
if node.Kind == ScalarNode {
|
||||||
// For standalone selections, TOML tests expect raw value for scalars
|
return writeString(writer, node.Value+"\n")
|
||||||
if node.Kind == ScalarNode {
|
|
||||||
return writeString(writer, node.Value+"\n")
|
|
||||||
}
|
|
||||||
return fmt.Errorf("TOML encoder expects a mapping at the root level")
|
|
||||||
}
|
}
|
||||||
|
return fmt.Errorf("only scalars (e.g. strings, numbers, booleans) are supported for TOML output at the moment. Please use yaml output format (-oy) until the encoder has been fully implemented")
|
||||||
// Encode to a buffer first if colors are enabled
|
|
||||||
var buf bytes.Buffer
|
|
||||||
var targetWriter io.Writer
|
|
||||||
targetWriter = writer
|
|
||||||
if te.prefs.ColorsEnabled {
|
|
||||||
targetWriter = &buf
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encode a root mapping as a sequence of attributes, tables, and arrays of tables
|
|
||||||
if err := te.encodeRootMapping(targetWriter, node); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if te.prefs.ColorsEnabled {
|
|
||||||
colourised := te.colorizeToml(buf.Bytes())
|
|
||||||
_, err := writer.Write(colourised)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (te *tomlEncoder) PrintDocumentSeparator(_ io.Writer) error {
|
func (te *tomlEncoder) PrintDocumentSeparator(_ io.Writer) error {
|
||||||
@ -66,742 +30,3 @@ func (te *tomlEncoder) PrintLeadingContent(_ io.Writer, _ string) error {
|
|||||||
func (te *tomlEncoder) CanHandleAliases() bool {
|
func (te *tomlEncoder) CanHandleAliases() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- helpers ----
|
|
||||||
|
|
||||||
// tomlKey returns the key quoted if it contains characters that are not valid
|
|
||||||
// in a TOML bare key. TOML bare keys may only contain ASCII letters, ASCII
|
|
||||||
// digits, underscores, and dashes.
|
|
||||||
func tomlKey(key string) string {
|
|
||||||
for _, r := range key {
|
|
||||||
if (r < 'A' || r > 'Z') && (r < 'a' || r > 'z') && (r < '0' || r > '9') && r != '_' && r != '-' {
|
|
||||||
return fmt.Sprintf("%q", key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return key
|
|
||||||
}
|
|
||||||
|
|
||||||
// tomlDottedKey joins path components, quoting any that require it.
|
|
||||||
func tomlDottedKey(path []string) string {
|
|
||||||
parts := make([]string, len(path))
|
|
||||||
for i, p := range path {
|
|
||||||
parts[i] = tomlKey(p)
|
|
||||||
}
|
|
||||||
return strings.Join(parts, ".")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (te *tomlEncoder) writeComment(w io.Writer, comment string) error {
|
|
||||||
if comment == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
lines := strings.Split(comment, "\n")
|
|
||||||
for _, line := range lines {
|
|
||||||
line = strings.TrimSpace(line)
|
|
||||||
if !strings.HasPrefix(line, "#") {
|
|
||||||
line = "# " + line
|
|
||||||
}
|
|
||||||
if _, err := w.Write([]byte(line + "\n")); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (te *tomlEncoder) formatScalar(node *CandidateNode) string {
|
|
||||||
switch node.Tag {
|
|
||||||
case "!!str":
|
|
||||||
// Quote strings per TOML spec
|
|
||||||
return fmt.Sprintf("%q", node.Value)
|
|
||||||
case "!!bool", "!!int", "!!float":
|
|
||||||
return node.Value
|
|
||||||
case "!!null":
|
|
||||||
// TOML does not have null; encode as empty string
|
|
||||||
return `""`
|
|
||||||
default:
|
|
||||||
return node.Value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (te *tomlEncoder) encodeRootMapping(w io.Writer, node *CandidateNode) error {
|
|
||||||
te.wroteRootAttr = false // Reset state
|
|
||||||
|
|
||||||
// Write root head comment if present (at the very beginning, no leading blank line)
|
|
||||||
if node.HeadComment != "" {
|
|
||||||
if err := te.writeComment(w, node.HeadComment); 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// encodeTopLevelEntry encodes a key/value at the root, dispatching to attribute, table, or array-of-tables
|
|
||||||
func (te *tomlEncoder) encodeTopLevelEntry(w io.Writer, path []string, node *CandidateNode) error {
|
|
||||||
if len(path) == 0 {
|
|
||||||
return fmt.Errorf("cannot encode TOML entry with empty path")
|
|
||||||
}
|
|
||||||
|
|
||||||
switch node.Kind {
|
|
||||||
case ScalarNode:
|
|
||||||
// key = value
|
|
||||||
return te.writeAttribute(w, path[len(path)-1], node)
|
|
||||||
case SequenceNode:
|
|
||||||
// Empty arrays should be encoded as [] attributes
|
|
||||||
if len(node.Content) == 0 {
|
|
||||||
return te.writeArrayAttribute(w, path[len(path)-1], node)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If all items are mappings => array of tables; else => array attribute
|
|
||||||
allMaps := true
|
|
||||||
for _, it := range node.Content {
|
|
||||||
if it.Kind != MappingNode {
|
|
||||||
allMaps = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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 {
|
|
||||||
if _, err := w.Write([]byte("[[" + quotedKey + "]]\n")); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := te.encodeMappingBodyWithPath(w, []string{key}, it); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// Regular array attribute
|
|
||||||
return te.writeArrayAttribute(w, path[len(path)-1], node)
|
|
||||||
case MappingNode:
|
|
||||||
// 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)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unsupported node kind for TOML: %v", node.Kind)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
if err := te.writeComment(w, value.HeadComment); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write the attribute
|
|
||||||
line := tomlKey(key) + " = " + te.formatScalar(value)
|
|
||||||
|
|
||||||
// Add line comment if present
|
|
||||||
if value.LineComment != "" {
|
|
||||||
lineComment := strings.TrimSpace(value.LineComment)
|
|
||||||
if !strings.HasPrefix(lineComment, "#") {
|
|
||||||
lineComment = "# " + lineComment
|
|
||||||
}
|
|
||||||
line += " " + lineComment
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := w.Write([]byte(line + "\n"))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (te *tomlEncoder) writeArrayAttribute(w io.Writer, key string, seq *CandidateNode) error {
|
|
||||||
te.wroteRootAttr = true // Mark that we wrote a root attribute
|
|
||||||
|
|
||||||
// Write head comment before the array
|
|
||||||
if err := te.writeComment(w, seq.HeadComment); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle empty arrays
|
|
||||||
if len(seq.Content) == 0 {
|
|
||||||
line := tomlKey(key) + " = []"
|
|
||||||
if seq.LineComment != "" {
|
|
||||||
lineComment := strings.TrimSpace(seq.LineComment)
|
|
||||||
if !strings.HasPrefix(lineComment, "#") {
|
|
||||||
lineComment = "# " + lineComment
|
|
||||||
}
|
|
||||||
line += " " + lineComment
|
|
||||||
}
|
|
||||||
_, err := w.Write([]byte(line + "\n"))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if any array elements have head comments - if so, use multiline format
|
|
||||||
hasElementComments := false
|
|
||||||
for _, it := range seq.Content {
|
|
||||||
if it.HeadComment != "" {
|
|
||||||
hasElementComments = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if hasElementComments {
|
|
||||||
// Write multiline array format with comments
|
|
||||||
if _, err := w.Write([]byte(tomlKey(key) + " = [\n")); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, it := range seq.Content {
|
|
||||||
// Write head comment for this element
|
|
||||||
if it.HeadComment != "" {
|
|
||||||
commentLines := strings.Split(it.HeadComment, "\n")
|
|
||||||
for _, commentLine := range commentLines {
|
|
||||||
if strings.TrimSpace(commentLine) != "" {
|
|
||||||
if !strings.HasPrefix(strings.TrimSpace(commentLine), "#") {
|
|
||||||
commentLine = "# " + commentLine
|
|
||||||
}
|
|
||||||
if _, err := w.Write([]byte(" " + commentLine + "\n")); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write the element value
|
|
||||||
var itemStr string
|
|
||||||
switch it.Kind {
|
|
||||||
case ScalarNode:
|
|
||||||
itemStr = te.formatScalar(it)
|
|
||||||
case SequenceNode:
|
|
||||||
nested, err := te.sequenceToInlineArray(it)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
itemStr = nested
|
|
||||||
case MappingNode:
|
|
||||||
inline, err := te.mappingToInlineTable(it)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
itemStr = inline
|
|
||||||
case AliasNode:
|
|
||||||
return fmt.Errorf("aliases are not supported in TOML")
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unsupported array item kind: %v", it.Kind)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Always add trailing comma in multiline arrays
|
|
||||||
itemStr += ","
|
|
||||||
|
|
||||||
if _, err := w.Write([]byte(" " + itemStr + "\n")); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add blank line between elements (except after the last one)
|
|
||||||
if i < len(seq.Content)-1 {
|
|
||||||
if _, err := w.Write([]byte("\n")); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := w.Write([]byte("]\n")); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Join scalars or nested arrays recursively into TOML array syntax
|
|
||||||
items := make([]string, 0, len(seq.Content))
|
|
||||||
for _, it := range seq.Content {
|
|
||||||
switch it.Kind {
|
|
||||||
case ScalarNode:
|
|
||||||
items = append(items, te.formatScalar(it))
|
|
||||||
case SequenceNode:
|
|
||||||
// Nested arrays: encode inline
|
|
||||||
nested, err := te.sequenceToInlineArray(it)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
items = append(items, nested)
|
|
||||||
case MappingNode:
|
|
||||||
// Inline table inside array
|
|
||||||
inline, err := te.mappingToInlineTable(it)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
items = append(items, inline)
|
|
||||||
case AliasNode:
|
|
||||||
return fmt.Errorf("aliases are not supported in TOML")
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unsupported array item kind: %v", it.Kind)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
line := tomlKey(key) + " = [" + strings.Join(items, ", ") + "]"
|
|
||||||
|
|
||||||
// Add line comment if present
|
|
||||||
if seq.LineComment != "" {
|
|
||||||
lineComment := strings.TrimSpace(seq.LineComment)
|
|
||||||
if !strings.HasPrefix(lineComment, "#") {
|
|
||||||
lineComment = "# " + lineComment
|
|
||||||
}
|
|
||||||
line += " " + lineComment
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := w.Write([]byte(line + "\n"))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (te *tomlEncoder) sequenceToInlineArray(seq *CandidateNode) (string, error) {
|
|
||||||
items := make([]string, 0, len(seq.Content))
|
|
||||||
for _, it := range seq.Content {
|
|
||||||
switch it.Kind {
|
|
||||||
case ScalarNode:
|
|
||||||
items = append(items, te.formatScalar(it))
|
|
||||||
case SequenceNode:
|
|
||||||
nested, err := te.sequenceToInlineArray(it)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
items = append(items, nested)
|
|
||||||
case MappingNode:
|
|
||||||
inline, err := te.mappingToInlineTable(it)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
items = append(items, inline)
|
|
||||||
default:
|
|
||||||
return "", fmt.Errorf("unsupported array item kind: %v", it.Kind)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "[" + strings.Join(items, ", ") + "]", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (te *tomlEncoder) mappingToInlineTable(m *CandidateNode) (string, error) {
|
|
||||||
// key = { a = 1, b = "x" }
|
|
||||||
parts := make([]string, 0, len(m.Content)/2)
|
|
||||||
for i := 0; i < len(m.Content); i += 2 {
|
|
||||||
k := m.Content[i].Value
|
|
||||||
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
|
|
||||||
arr, err := te.sequenceToInlineArray(v)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
parts = append(parts, fmt.Sprintf("%s = %s", tomlKey(k), arr))
|
|
||||||
case MappingNode:
|
|
||||||
// nested inline table
|
|
||||||
inline, err := te.mappingToInlineTable(v)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
parts = append(parts, fmt.Sprintf("%s = %s", tomlKey(k), inline))
|
|
||||||
default:
|
|
||||||
return "", fmt.Errorf("unsupported inline table value kind: %v", v.Kind)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "{ " + strings.Join(parts, ", ") + " }", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (te *tomlEncoder) writeInlineTableAttribute(w io.Writer, key string, m *CandidateNode) error {
|
|
||||||
inline, err := te.mappingToInlineTable(m)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = w.Write([]byte(tomlKey(key) + " = " + inline + "\n"))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (te *tomlEncoder) writeTableHeader(w io.Writer, path []string, m *CandidateNode) error {
|
|
||||||
// Add blank line before table header (or before comment if present) if we wrote root attributes
|
|
||||||
needsBlankLine := te.wroteRootAttr
|
|
||||||
if needsBlankLine {
|
|
||||||
if _, err := w.Write([]byte("\n")); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
te.wroteRootAttr = false // Only add once
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write head comment before the table header
|
|
||||||
if m.HeadComment != "" {
|
|
||||||
if err := te.writeComment(w, m.HeadComment); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write table header [a.b.c]
|
|
||||||
header := "[" + tomlDottedKey(path) + "]\n"
|
|
||||||
_, err := w.Write([]byte(header))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// encodeSeparateMapping handles a mapping that should be encoded as table sections.
|
|
||||||
// It emits the table header for this mapping if it has any content, then processes children.
|
|
||||||
func (te *tomlEncoder) encodeSeparateMapping(w io.Writer, path []string, m *CandidateNode) error {
|
|
||||||
// Check if this mapping has any non-mapping, non-array-of-tables children (i.e., attributes).
|
|
||||||
// Inline mapping children also count as attributes since they render as key = { ... }.
|
|
||||||
hasAttrs := false
|
|
||||||
for i := 0; i < len(m.Content); i += 2 {
|
|
||||||
v := m.Content[i+1]
|
|
||||||
if v.Kind == ScalarNode && v.Tag != "!!null" {
|
|
||||||
hasAttrs = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if v.Kind == MappingNode && v.EncodeHint == EncodeHintInline {
|
|
||||||
hasAttrs = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if v.Kind == SequenceNode {
|
|
||||||
if !isTomlArrayOfTables(v) {
|
|
||||||
hasAttrs = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there are attributes or if the mapping is empty, emit the table header
|
|
||||||
if hasAttrs || len(m.Content) == 0 {
|
|
||||||
if err := te.writeTableHeader(w, path, m); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := te.encodeMappingBodyWithPath(w, path, m); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// No attributes, just nested table structures - process children recursively
|
|
||||||
for i := 0; i < len(m.Content); i += 2 {
|
|
||||||
k := m.Content[i].Value
|
|
||||||
v := m.Content[i+1]
|
|
||||||
switch v.Kind {
|
|
||||||
case MappingNode:
|
|
||||||
newPath := append(append([]string{}, path...), k)
|
|
||||||
if err := te.encodeSeparateMapping(w, newPath, v); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case SequenceNode:
|
|
||||||
// If sequence of maps, emit [[path.k]] per element
|
|
||||||
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
|
|
||||||
}
|
|
||||||
if err := te.encodeMappingBodyWithPath(w, append(append([]string{}, path...), k), it); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Regular array attribute under the current table path
|
|
||||||
if err := te.writeArrayAttribute(w, k, v); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case ScalarNode:
|
|
||||||
// Attributes directly under the current table path
|
|
||||||
if err := te.writeAttribute(w, k, v); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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, 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]
|
|
||||||
switch v.Kind {
|
|
||||||
case ScalarNode:
|
|
||||||
if err := te.writeAttribute(w, k, v); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case MappingNode:
|
|
||||||
if v.EncodeHint == EncodeHintInline {
|
|
||||||
if err := te.writeInlineTableAttribute(w, k, v); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case SequenceNode:
|
|
||||||
if !isTomlArrayOfTables(v) {
|
|
||||||
if err := te.writeArrayAttribute(w, k, v); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Then, nested arrays of tables with full path
|
|
||||||
for i := 0; i < len(m.Content); i += 2 {
|
|
||||||
k := m.Content[i].Value
|
|
||||||
v := m.Content[i+1]
|
|
||||||
if v.Kind == SequenceNode {
|
|
||||||
if isTomlArrayOfTables(v) {
|
|
||||||
dotted := tomlDottedKey(append(append([]string{}, path...), k))
|
|
||||||
for _, it := range v.Content {
|
|
||||||
if _, err := w.Write([]byte("[[" + dotted + "]]\n")); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := te.encodeMappingBodyWithPath(w, append(append([]string{}, path...), k), it); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 && v.EncodeHint != EncodeHintInline {
|
|
||||||
subPath := append(append([]string{}, path...), k)
|
|
||||||
if err := te.encodeSeparateMapping(w, subPath, v); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// colorizeToml applies syntax highlighting to TOML output using fatih/color
|
|
||||||
func (te *tomlEncoder) colorizeToml(input []byte) []byte {
|
|
||||||
toml := string(input)
|
|
||||||
result := strings.Builder{}
|
|
||||||
|
|
||||||
// Force color output (don't check for TTY)
|
|
||||||
color.NoColor = false
|
|
||||||
|
|
||||||
// Create color functions for different token types
|
|
||||||
// Use EnableColor() to ensure colors work even when NO_COLOR env is set
|
|
||||||
commentColorObj := color.New(color.FgHiBlack)
|
|
||||||
commentColorObj.EnableColor()
|
|
||||||
stringColorObj := color.New(color.FgGreen)
|
|
||||||
stringColorObj.EnableColor()
|
|
||||||
numberColorObj := color.New(color.FgHiMagenta)
|
|
||||||
numberColorObj.EnableColor()
|
|
||||||
keyColorObj := color.New(color.FgCyan)
|
|
||||||
keyColorObj.EnableColor()
|
|
||||||
boolColorObj := color.New(color.FgHiMagenta)
|
|
||||||
boolColorObj.EnableColor()
|
|
||||||
sectionColorObj := color.New(color.FgYellow, color.Bold)
|
|
||||||
sectionColorObj.EnableColor()
|
|
||||||
|
|
||||||
commentColor := commentColorObj.SprintFunc()
|
|
||||||
stringColor := stringColorObj.SprintFunc()
|
|
||||||
numberColor := numberColorObj.SprintFunc()
|
|
||||||
keyColor := keyColorObj.SprintFunc()
|
|
||||||
boolColor := boolColorObj.SprintFunc()
|
|
||||||
sectionColor := sectionColorObj.SprintFunc()
|
|
||||||
|
|
||||||
// Simple tokenization for TOML colouring
|
|
||||||
i := 0
|
|
||||||
for i < len(toml) {
|
|
||||||
ch := toml[i]
|
|
||||||
|
|
||||||
// Comments - from # to end of line
|
|
||||||
if ch == '#' {
|
|
||||||
end := i
|
|
||||||
for end < len(toml) && toml[end] != '\n' {
|
|
||||||
end++
|
|
||||||
}
|
|
||||||
result.WriteString(commentColor(toml[i:end]))
|
|
||||||
i = end
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Table sections - [section] or [[array]]
|
|
||||||
// Only treat '[' as a table section if it appears at the start of the line
|
|
||||||
// (possibly after whitespace). This avoids incorrectly colouring inline arrays like
|
|
||||||
// "ports = [8000, 8001]" as table sections.
|
|
||||||
if ch == '[' {
|
|
||||||
isSectionHeader := true
|
|
||||||
if i > 0 {
|
|
||||||
isSectionHeader = false
|
|
||||||
j := i - 1
|
|
||||||
for j >= 0 && toml[j] != '\n' {
|
|
||||||
if toml[j] != ' ' && toml[j] != '\t' && toml[j] != '\r' {
|
|
||||||
// Found a non-whitespace character before this '[' on the same line,
|
|
||||||
// so this is not a table header.
|
|
||||||
break
|
|
||||||
}
|
|
||||||
j--
|
|
||||||
}
|
|
||||||
if j < 0 || toml[j] == '\n' {
|
|
||||||
// Reached the start of the string or a newline without encountering
|
|
||||||
// any non-whitespace, so '[' is at the logical start of the line.
|
|
||||||
isSectionHeader = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if isSectionHeader {
|
|
||||||
end := i + 1
|
|
||||||
// Check for [[
|
|
||||||
if end < len(toml) && toml[end] == '[' {
|
|
||||||
end++
|
|
||||||
}
|
|
||||||
// Find closing ]
|
|
||||||
for end < len(toml) && toml[end] != ']' {
|
|
||||||
end++
|
|
||||||
}
|
|
||||||
// Include closing ]
|
|
||||||
if end < len(toml) {
|
|
||||||
end++
|
|
||||||
// Check for ]]
|
|
||||||
if end < len(toml) && toml[end] == ']' {
|
|
||||||
end++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result.WriteString(sectionColor(toml[i:end]))
|
|
||||||
i = end
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Strings - quoted text (double or single quotes)
|
|
||||||
if ch == '"' || ch == '\'' {
|
|
||||||
quote := ch
|
|
||||||
end := i + 1
|
|
||||||
for end < len(toml) {
|
|
||||||
if toml[end] == quote {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if toml[end] == '\\' && end+1 < len(toml) {
|
|
||||||
// Skip the backslash and the escaped character
|
|
||||||
end += 2
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
end++
|
|
||||||
}
|
|
||||||
if end < len(toml) {
|
|
||||||
end++ // include closing quote
|
|
||||||
}
|
|
||||||
result.WriteString(stringColor(toml[i:end]))
|
|
||||||
i = end
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Numbers - sequences of digits, possibly with decimal point or minus
|
|
||||||
if (ch >= '0' && ch <= '9') || (ch == '-' && i+1 < len(toml) && toml[i+1] >= '0' && toml[i+1] <= '9') {
|
|
||||||
end := i
|
|
||||||
if ch == '-' {
|
|
||||||
end++
|
|
||||||
}
|
|
||||||
for end < len(toml) {
|
|
||||||
c := toml[end]
|
|
||||||
if (c >= '0' && c <= '9') || c == '.' || c == 'e' || c == 'E' {
|
|
||||||
end++
|
|
||||||
} else if (c == '+' || c == '-') && end > 0 && (toml[end-1] == 'e' || toml[end-1] == 'E') {
|
|
||||||
// Only allow + or - immediately after 'e' or 'E' for scientific notation
|
|
||||||
end++
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result.WriteString(numberColor(toml[i:end]))
|
|
||||||
i = end
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Identifiers/keys - alphanumeric + underscore + dash
|
|
||||||
if (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_' {
|
|
||||||
end := i
|
|
||||||
for end < len(toml) && ((toml[end] >= 'a' && toml[end] <= 'z') ||
|
|
||||||
(toml[end] >= 'A' && toml[end] <= 'Z') ||
|
|
||||||
(toml[end] >= '0' && toml[end] <= '9') ||
|
|
||||||
toml[end] == '_' || toml[end] == '-') {
|
|
||||||
end++
|
|
||||||
}
|
|
||||||
ident := toml[i:end]
|
|
||||||
|
|
||||||
// Check if this is a boolean/null keyword
|
|
||||||
switch ident {
|
|
||||||
case "true", "false":
|
|
||||||
result.WriteString(boolColor(ident))
|
|
||||||
default:
|
|
||||||
// Check if followed by = or whitespace then = (it's a key)
|
|
||||||
j := end
|
|
||||||
for j < len(toml) && (toml[j] == ' ' || toml[j] == '\t') {
|
|
||||||
j++
|
|
||||||
}
|
|
||||||
if j < len(toml) && toml[j] == '=' {
|
|
||||||
result.WriteString(keyColor(ident))
|
|
||||||
} else {
|
|
||||||
result.WriteString(ident) // plain text for other identifiers
|
|
||||||
}
|
|
||||||
}
|
|
||||||
i = end
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Everything else (whitespace, operators, brackets) - no color
|
|
||||||
result.WriteByte(ch)
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
|
|
||||||
return []byte(result.String())
|
|
||||||
}
|
|
||||||
|
|||||||
@ -59,7 +59,7 @@ func (e *xmlEncoder) Encode(writer io.Writer, node *CandidateNode) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if _, err := e.writer.Write([]byte("\n")); err != nil {
|
if _, err := e.writer.Write([]byte("\n")); err != nil {
|
||||||
log.Warningf("Unable to write newline, skipping: %v", err)
|
log.Warning("Unable to write newline, skipping: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -131,7 +131,7 @@ func (e *xmlEncoder) encodeTopLevelMap(encoder *xml.Encoder, node *CandidateNode
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if _, err := e.writer.Write([]byte("\n")); err != nil {
|
if _, err := e.writer.Write([]byte("\n")); err != nil {
|
||||||
log.Warningf("Unable to write newline, skipping: %v", err)
|
log.Warning("Unable to write newline, skipping: %w", err)
|
||||||
}
|
}
|
||||||
} else if key.Value == e.prefs.DirectiveName {
|
} else if key.Value == e.prefs.DirectiveName {
|
||||||
var directive xml.Directive = []byte(value.Value)
|
var directive xml.Directive = []byte(value.Value)
|
||||||
@ -139,7 +139,7 @@ func (e *xmlEncoder) encodeTopLevelMap(encoder *xml.Encoder, node *CandidateNode
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if _, err := e.writer.Write([]byte("\n")); err != nil {
|
if _, err := e.writer.Write([]byte("\n")); err != nil {
|
||||||
log.Warningf("Unable to write newline, skipping: %v", err)
|
log.Warning("Unable to write newline, skipping: %w", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,13 @@
|
|||||||
package yqlib
|
package yqlib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/fatih/color"
|
||||||
"go.yaml.in/yaml/v4"
|
"go.yaml.in/yaml/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -21,15 +24,67 @@ func (ye *yamlEncoder) CanHandleAliases() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ye *yamlEncoder) PrintDocumentSeparator(writer io.Writer) error {
|
func (ye *yamlEncoder) PrintDocumentSeparator(writer io.Writer) error {
|
||||||
return PrintYAMLDocumentSeparator(writer, ye.prefs.PrintDocSeparators)
|
if ye.prefs.PrintDocSeparators {
|
||||||
|
log.Debug("writing doc sep")
|
||||||
|
if err := writeString(writer, "---\n"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ye *yamlEncoder) PrintLeadingContent(writer io.Writer, content string) error {
|
func (ye *yamlEncoder) PrintLeadingContent(writer io.Writer, content string) error {
|
||||||
return PrintYAMLLeadingContent(writer, content, ye.prefs.PrintDocSeparators, ye.prefs.ColorsEnabled)
|
reader := bufio.NewReader(strings.NewReader(content))
|
||||||
|
|
||||||
|
// reuse precompiled package-level regex
|
||||||
|
// (declared in decoder_yaml.go)
|
||||||
|
|
||||||
|
for {
|
||||||
|
|
||||||
|
readline, errReading := reader.ReadString('\n')
|
||||||
|
if errReading != nil && !errors.Is(errReading, io.EOF) {
|
||||||
|
return errReading
|
||||||
|
}
|
||||||
|
if strings.Contains(readline, "$yqDocSeparator$") {
|
||||||
|
// Preserve the original line ending (CRLF or LF)
|
||||||
|
lineEnding := "\n"
|
||||||
|
if strings.HasSuffix(readline, "\r\n") {
|
||||||
|
lineEnding = "\r\n"
|
||||||
|
}
|
||||||
|
if ye.prefs.PrintDocSeparators {
|
||||||
|
if err := writeString(writer, "---"+lineEnding); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if len(readline) > 0 && readline != "\n" && readline[0] != '%' && !commentLineRe.MatchString(readline) {
|
||||||
|
readline = "# " + readline
|
||||||
|
}
|
||||||
|
if ye.prefs.ColorsEnabled && strings.TrimSpace(readline) != "" {
|
||||||
|
readline = format(color.FgHiBlack) + readline + format(color.Reset)
|
||||||
|
}
|
||||||
|
if err := writeString(writer, readline); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if errors.Is(errReading, io.EOF) {
|
||||||
|
if readline != "" {
|
||||||
|
// the last comment we read didn't have a newline, put one in
|
||||||
|
if err := writeString(writer, "\n"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ye *yamlEncoder) Encode(writer io.Writer, node *CandidateNode) error {
|
func (ye *yamlEncoder) Encode(writer io.Writer, node *CandidateNode) error {
|
||||||
log.Debugf("encoderYaml - going to print %v", NodeToString(node))
|
log.Debug("encoderYaml - going to print %v", NodeToString(node))
|
||||||
// Detect line ending style from LeadingContent
|
// Detect line ending style from LeadingContent
|
||||||
lineEnding := "\n"
|
lineEnding := "\n"
|
||||||
if strings.Contains(node.LeadingContent, "\r\n") {
|
if strings.Contains(node.LeadingContent, "\r\n") {
|
||||||
@ -52,9 +107,6 @@ func (ye *yamlEncoder) Encode(writer io.Writer, node *CandidateNode) error {
|
|||||||
var encoder = yaml.NewEncoder(destination)
|
var encoder = yaml.NewEncoder(destination)
|
||||||
|
|
||||||
encoder.SetIndent(ye.prefs.Indent)
|
encoder.SetIndent(ye.prefs.Indent)
|
||||||
if ye.prefs.CompactSequenceIndent {
|
|
||||||
encoder.CompactSeqIndent()
|
|
||||||
}
|
|
||||||
|
|
||||||
target, err := node.MarshalYAML()
|
target, err := node.MarshalYAML()
|
||||||
|
|
||||||
|
|||||||
@ -26,7 +26,7 @@ func newExpressionParser() ExpressionParserInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *expressionParserImpl) ParseExpression(expression string) (*ExpressionNode, error) {
|
func (p *expressionParserImpl) ParseExpression(expression string) (*ExpressionNode, error) {
|
||||||
log.Debugf("Parsing expression: [%v]", expression)
|
log.Debug("Parsing expression: [%v]", expression)
|
||||||
tokens, err := p.pathTokeniser.Tokenise(expression)
|
tokens, err := p.pathTokeniser.Tokenise(expression)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@ -95,7 +95,6 @@ func TestParserSingleOperation(t *testing.T) {
|
|||||||
test.AssertResultComplex(t, nil, err)
|
test.AssertResultComplex(t, nil, err)
|
||||||
if result == nil {
|
if result == nil {
|
||||||
t.Fatal("Expected non-nil result for single operation")
|
t.Fatal("Expected non-nil result for single operation")
|
||||||
return
|
|
||||||
}
|
}
|
||||||
if result.Operation == nil {
|
if result.Operation == nil {
|
||||||
t.Fatal("Expected operation to be set")
|
t.Fatal("Expected operation to be set")
|
||||||
|
|||||||
@ -3,7 +3,8 @@ package yqlib
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
|
||||||
|
logging "gopkg.in/op/go-logging.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
type expressionPostFixer interface {
|
type expressionPostFixer interface {
|
||||||
@ -133,7 +134,7 @@ func (p *expressionPostFixerImpl) ConvertToPostfix(infixTokens []*token) ([]*Ope
|
|||||||
return nil, fmt.Errorf("bad expression - probably missing close bracket on %v", opStack[len(opStack)-1].toString(false))
|
return nil, fmt.Errorf("bad expression - probably missing close bracket on %v", opStack[len(opStack)-1].toString(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
if log.IsEnabledFor(slog.LevelDebug) {
|
if log.IsEnabledFor(logging.DEBUG) {
|
||||||
log.Debugf("PostFix Result:")
|
log.Debugf("PostFix Result:")
|
||||||
for _, currentToken := range result {
|
for _, currentToken := range result {
|
||||||
log.Debugf("> %v", currentToken.toString())
|
log.Debugf("> %v", currentToken.toString())
|
||||||
|
|||||||
@ -7,15 +7,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func tryRenameFile(from string, to string) error {
|
func tryRenameFile(from string, to string) error {
|
||||||
if info, err := os.Lstat(to); err == nil && info.Mode()&os.ModeSymlink != 0 {
|
if renameError := os.Rename(from, to); renameError != nil {
|
||||||
log.Debug("Target file is symlink, skipping rename and attempting to copy contents")
|
|
||||||
|
|
||||||
if copyError := copyFileContents(from, to); copyError != nil {
|
|
||||||
return fmt.Errorf("failed copying from %v to %v: %w", from, to, copyError)
|
|
||||||
}
|
|
||||||
tryRemoveTempFile(from)
|
|
||||||
return nil
|
|
||||||
} else if renameError := os.Rename(from, to); renameError != nil {
|
|
||||||
log.Debugf("Error renaming from %v to %v, attempting to copy contents", from, to)
|
log.Debugf("Error renaming from %v to %v, attempting to copy contents", from, to)
|
||||||
log.Debug(renameError.Error())
|
log.Debug(renameError.Error())
|
||||||
log.Debug("going to try copying instead")
|
log.Debug("going to try copying instead")
|
||||||
@ -30,7 +22,7 @@ func tryRenameFile(from string, to string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func tryRemoveTempFile(filename string) {
|
func tryRemoveTempFile(filename string) {
|
||||||
log.Debugf("Removing temp file: %v", filename)
|
log.Debug("Removing temp file: %v", filename)
|
||||||
removeErr := os.Remove(filename)
|
removeErr := os.Remove(filename)
|
||||||
if removeErr != nil {
|
if removeErr != nil {
|
||||||
log.Errorf("Failed to remove temp file: %v", filename)
|
log.Errorf("Failed to remove temp file: %v", filename)
|
||||||
@ -40,7 +32,7 @@ func tryRemoveTempFile(filename string) {
|
|||||||
// thanks https://stackoverflow.com/questions/21060945/simple-way-to-copy-a-file-in-golang
|
// thanks https://stackoverflow.com/questions/21060945/simple-way-to-copy-a-file-in-golang
|
||||||
func copyFileContents(src, dst string) (err error) {
|
func copyFileContents(src, dst string) (err error) {
|
||||||
// ignore CWE-22 gosec issue - that's more targeted for http based apps that run in a public directory,
|
// ignore CWE-22 gosec issue - that's more targeted for http based apps that run in a public directory,
|
||||||
// and ensuring that it's not possible to give a path to a file outside that directory.
|
// and ensuring that it's not possible to give a path to a file outside thar directory.
|
||||||
|
|
||||||
in, err := os.Open(src) // #nosec
|
in, err := os.Open(src) // #nosec
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -68,7 +60,8 @@ func SafelyCloseReader(reader io.Reader) {
|
|||||||
func safelyCloseFile(file *os.File) {
|
func safelyCloseFile(file *os.File) {
|
||||||
err := file.Close()
|
err := file.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Error closing file %v: %v", file.Name(), err)
|
log.Error("Error closing file!")
|
||||||
|
log.Error(err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -22,12 +22,6 @@ var YamlFormat = &Format{"yaml", []string{"y", "yml"},
|
|||||||
func() Decoder { return NewYamlDecoder(ConfiguredYamlPreferences) },
|
func() Decoder { return NewYamlDecoder(ConfiguredYamlPreferences) },
|
||||||
}
|
}
|
||||||
|
|
||||||
var KYamlFormat = &Format{"kyaml", []string{"ky"},
|
|
||||||
func() Encoder { return NewKYamlEncoder(ConfiguredKYamlPreferences) },
|
|
||||||
// KYaml is stricter YAML
|
|
||||||
func() Decoder { return NewYamlDecoder(ConfiguredYamlPreferences) },
|
|
||||||
}
|
|
||||||
|
|
||||||
var JSONFormat = &Format{"json", []string{"j"},
|
var JSONFormat = &Format{"json", []string{"j"},
|
||||||
func() Encoder { return NewJSONEncoder(ConfiguredJSONPreferences) },
|
func() Encoder { return NewJSONEncoder(ConfiguredJSONPreferences) },
|
||||||
func() Decoder { return NewJSONDecoder() },
|
func() Decoder { return NewJSONDecoder() },
|
||||||
@ -69,11 +63,11 @@ var ShFormat = &Format{"", nil,
|
|||||||
}
|
}
|
||||||
|
|
||||||
var TomlFormat = &Format{"toml", []string{},
|
var TomlFormat = &Format{"toml", []string{},
|
||||||
func() Encoder { return NewTomlEncoderWithPrefs(ConfiguredTomlPreferences) },
|
func() Encoder { return NewTomlEncoder() },
|
||||||
func() Decoder { return NewTomlDecoder() },
|
func() Decoder { return NewTomlDecoder() },
|
||||||
}
|
}
|
||||||
|
|
||||||
var HclFormat = &Format{"hcl", []string{"h", "tf"},
|
var HclFormat = &Format{"hcl", []string{"h"},
|
||||||
func() Encoder { return NewHclEncoder(ConfiguredHclPreferences) },
|
func() Encoder { return NewHclEncoder(ConfiguredHclPreferences) },
|
||||||
func() Decoder { return NewHclDecoder() },
|
func() Decoder { return NewHclDecoder() },
|
||||||
}
|
}
|
||||||
@ -90,12 +84,11 @@ var LuaFormat = &Format{"lua", []string{"l"},
|
|||||||
|
|
||||||
var INIFormat = &Format{"ini", []string{"i"},
|
var INIFormat = &Format{"ini", []string{"i"},
|
||||||
func() Encoder { return NewINIEncoder() },
|
func() Encoder { return NewINIEncoder() },
|
||||||
func() Decoder { return NewINIDecoder(ConfiguredINIPreferences) },
|
func() Decoder { return NewINIDecoder() },
|
||||||
}
|
}
|
||||||
|
|
||||||
var Formats = []*Format{
|
var Formats = []*Format{
|
||||||
YamlFormat,
|
YamlFormat,
|
||||||
KYamlFormat,
|
|
||||||
JSONFormat,
|
JSONFormat,
|
||||||
PropertiesFormat,
|
PropertiesFormat,
|
||||||
CSVFormat,
|
CSVFormat,
|
||||||
|
|||||||
@ -58,7 +58,7 @@ func (f *frontMatterHandlerImpl) Split() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
f.yamlFrontMatterFilename = yamlTempFile.Name()
|
f.yamlFrontMatterFilename = yamlTempFile.Name()
|
||||||
log.Debugf("yamlTempFile: %v", yamlTempFile.Name())
|
log.Debug("yamlTempFile: %v", yamlTempFile.Name())
|
||||||
|
|
||||||
lineCount := 0
|
lineCount := 0
|
||||||
|
|
||||||
|
|||||||
@ -108,55 +108,6 @@ yaml: doc
|
|||||||
fmHandler.CleanUp()
|
fmHandler.CleanUp()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFrontMatterFilenamePreserved(t *testing.T) {
|
|
||||||
// Regression test for https://github.com/mikefarah/yq/issues/2538
|
|
||||||
// When using --front-matter, the filename operator should return
|
|
||||||
// the original filename, not the path to the temporary file.
|
|
||||||
file := createTestFile(`---
|
|
||||||
name: john
|
|
||||||
---
|
|
||||||
Some content
|
|
||||||
`)
|
|
||||||
originalFilename := "/path/to/original/file.md"
|
|
||||||
|
|
||||||
fmHandler := NewFrontMatterHandler(file)
|
|
||||||
err := fmHandler.Split()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tempFilename := fmHandler.GetYamlFrontMatterFilename()
|
|
||||||
|
|
||||||
// Register the alias (as the command code does)
|
|
||||||
SetFilenameAlias(tempFilename, originalFilename)
|
|
||||||
defer ClearFilenameAliases()
|
|
||||||
|
|
||||||
// Verify resolveFilename returns the original name
|
|
||||||
resolved := resolveFilename(tempFilename)
|
|
||||||
test.AssertResult(t, originalFilename, resolved)
|
|
||||||
|
|
||||||
// Read documents using the temp file, verify they get the original filename
|
|
||||||
reader, err := readStream(tempFilename)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
decoder := NewYamlDecoder(ConfiguredYamlPreferences)
|
|
||||||
docs, err := readDocuments(reader, tempFilename, 0, decoder)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if docs.Len() == 0 {
|
|
||||||
t.Fatal("expected at least one document")
|
|
||||||
}
|
|
||||||
|
|
||||||
firstDoc := docs.Front().Value.(*CandidateNode)
|
|
||||||
test.AssertResult(t, originalFilename, firstDoc.filename)
|
|
||||||
|
|
||||||
tryRemoveTempFile(file)
|
|
||||||
fmHandler.CleanUp()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFrontMatterSplitWithArray(t *testing.T) {
|
func TestFrontMatterSplitWithArray(t *testing.T) {
|
||||||
file := createTestFile(`[1,2,3]
|
file := createTestFile(`[1,2,3]
|
||||||
---
|
---
|
||||||
|
|||||||
@ -230,7 +230,8 @@ var goccyYamlFormatScenarios = []formatScenario{
|
|||||||
description: "merge anchor",
|
description: "merge anchor",
|
||||||
skipDoc: true,
|
skipDoc: true,
|
||||||
input: "a: &remember\n c: mike\nb:\n <<: *remember",
|
input: "a: &remember\n c: mike\nb:\n <<: *remember",
|
||||||
expected: "a: &remember\n c: mike\nb:\n <<: *remember\n",
|
// fine to have !!merge as that's what the current impl does
|
||||||
|
expected: "a: &remember\n c: mike\nb:\n !!merge <<: *remember\n",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "custom tag",
|
description: "custom tag",
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user