[
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: TomWright # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]\npatreon: # Replace with a single Patreon username\nko_fi: # Replace with a single Ko-fi username\ntidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel\ncommunity_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry\nliberapay: # Replace with a single Liberapay username\nissuehunt: # Replace with a single IssueHunt username\notechie: # Replace with a single Otechie username\nbuy_me_a_coffee: TomWright\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: \"\"\nlabels: \"bug\"\nassignees: \"\"\n---\n\n**Discussion?**\nIf this is a question rather than a bug, please raise it in the discussions Q&A section.\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**To Reproduce**\nSteps to reproduce the behavior:\n\n1. Go to '...'\n2. Click on '....'\n3. Scroll down to '....'\n4. See error\n\nInclude (sanitized) input files and commands to help along the way.\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\n**Screenshots**\nIf applicable, add screenshots to help explain your problem.\n\n**Desktop (please complete the following information):**\n\n- OS: [e.g. iOS]\n- Version [e.g. 22] (`dasel version`)\n\n**Additional context**\nAdd any other context about the problem here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: \"\"\nlabels: \"enhancement\"\nassignees: \"\"\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where the package manifests are located.\n# Please see the documentation for all configuration options:\n# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates\n\nversion: 2\nupdates:\n  - package-ecosystem: \"gomod\" # See documentation for possible values\n    directory: \"/\" # Location of package manifests\n    schedule:\n      interval: \"weekly\"\n"
  },
  {
    "path": ".github/workflows/build-dev.yaml",
    "content": "on:\n  push:\n    branches:\n      - master\n      - main\nname: Build Dev\njobs:\n  publish:\n    strategy:\n      matrix:\n        os:\n          - linux\n          - darwin\n          - windows\n        arch:\n          - amd64\n          - 386\n          - arm64\n          - arm\n        include:\n          - os: linux\n            arch: amd64\n            artifact_name: dasel_linux_amd64\n            test_version: true\n          - os: linux\n            arch: 386\n            artifact_name: dasel_linux_386\n            test_version: false\n          - os: darwin\n            arch: amd64\n            artifact_name: dasel_darwin_amd64\n            test_version: false\n          - os: darwin\n            arch: arm64\n            artifact_name: dasel_darwin_arm64\n            test_version: false\n          - os: windows\n            arch: amd64\n            artifact_name: dasel_windows_amd64.exe\n            test_version: false\n          - os: windows\n            arch: 386\n            artifact_name: dasel_windows_386.exe\n            test_version: false\n          - os: linux\n            arch: arm64\n            artifact_name: dasel_linux_arm64\n            test_version: false\n          - os: linux\n            arch: arm\n            artifact_name: dasel_linux_arm32\n            test_version: false\n        exclude:\n          - os: darwin\n            arch: 386\n          - os: windows\n            arch: arm64\n          - os: windows\n            arch: arm\n          - os: darwin\n            arch: arm\n    name: Dev build ${{ matrix.os }} ${{ matrix.arch }}\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - uses: actions/setup-go@v6\n        with:\n          go-version: '^1.25.0' # The Go version to download (if necessary) and use.\n      - name: Set env\n        run: echo RELEASE_VERSION=development >> $GITHUB_ENV\n      - name: Build\n        run: GOOS=${{ matrix.os }} GOARCH=${{ matrix.arch }} CGO_ENABLED=0 go build -o target/release/${{ matrix.artifact_name }} -ldflags=\"-w -s -X 'github.com/tomwright/dasel/v3/internal.Version=${{ env.RELEASE_VERSION }}'\" ./cmd/dasel\n      - name: Test version\n        if: matrix.test_version == true\n        run: ./target/release/${{ matrix.artifact_name }} version\n"
  },
  {
    "path": ".github/workflows/build-test.yaml",
    "content": "on:\n  push:\n    branches-ignore:\n      - master\n      - main\n  pull_request:\n    branches:\n      - master\n      - main\nname: Build Test\njobs:\n  publish:\n    strategy:\n      matrix:\n        os:\n          - linux\n          - darwin\n          - windows\n        arch:\n          - amd64\n          - 386\n          - arm64\n          - arm\n        include:\n          - os: linux\n            arch: amd64\n            artifact_name: dasel_linux_amd64\n            test_version: true\n            test_execution: true\n          - os: linux\n            arch: 386\n            artifact_name: dasel_linux_386\n            test_version: false\n            test_execution: false\n          - os: darwin\n            arch: amd64\n            artifact_name: dasel_darwin_amd64\n            test_version: false\n            test_execution: false\n          - os: darwin\n            arch: arm64\n            artifact_name: dasel_darwin_arm64\n            test_version: false\n            test_execution: false\n          - os: windows\n            arch: amd64\n            artifact_name: dasel_windows_amd64.exe\n            test_version: false\n            test_execution: false\n          - os: windows\n            arch: 386\n            artifact_name: dasel_windows_386.exe\n            test_version: false\n            test_execution: false\n          - os: linux\n            arch: arm64\n            artifact_name: dasel_linux_arm64\n            test_version: false\n            test_execution: false\n          - os: linux\n            arch: arm\n            artifact_name: dasel_linux_arm32\n            test_version: false\n            test_execution: false\n        exclude:\n          - os: darwin\n            arch: 386\n          - os: windows\n            arch: arm64\n          - os: windows\n            arch: arm\n          - os: darwin\n            arch: arm\n    name: Dev build ${{ matrix.os }} ${{ matrix.arch }}\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - uses: actions/setup-go@v6\n        with:\n          go-version: '^1.25.0' # The Go version to download (if necessary) and use.\n      - name: Set env\n        run: echo RELEASE_VERSION=development >> $GITHUB_ENV\n      - name: Build\n        run: GOOS=${{ matrix.os }} GOARCH=${{ matrix.arch }} CGO_ENABLED=0 go build -o target/release/${{ matrix.artifact_name }} -ldflags=\"-w -s -X 'github.com/tomwright/dasel/v3/internal.Version=${{ env.RELEASE_VERSION }}'\" ./cmd/dasel\n      - name: Test version\n        if: matrix.test_version == true\n        run: ./target/release/${{ matrix.artifact_name }} version\n      - name: Test execution\n        if: matrix.test_execution == true\n        run: |\n          echo '{\"hello\": \"World\"}' | ./target/release/${{ matrix.artifact_name }} -i json 'hello'\n"
  },
  {
    "path": ".github/workflows/build.yaml",
    "content": "on:\n  push:\n    tags:\n      - 'v*.*.*'\n\nname: Build\njobs:\n  publish:\n    strategy:\n      matrix:\n        os:\n          - linux\n          - darwin\n          - windows\n        arch:\n          - amd64\n          - 386\n          - arm64\n          - arm\n        include:\n          - os: linux\n            arch: amd64\n            artifact_name: dasel_linux_amd64\n            asset_name: dasel_linux_amd64\n            test_version: true\n          - os: linux\n            arch: 386\n            artifact_name: dasel_linux_386\n            asset_name: dasel_linux_386\n            test_version: false\n          - os: darwin\n            arch: amd64\n            artifact_name: dasel_darwin_amd64\n            asset_name: dasel_darwin_amd64\n            test_version: false\n          - os: darwin\n            arch: arm64\n            artifact_name: dasel_darwin_arm64\n            asset_name: dasel_darwin_arm64\n            test_version: false\n          - os: windows\n            arch: amd64\n            artifact_name: dasel_windows_amd64.exe\n            asset_name: dasel_windows_amd64.exe\n            test_version: false\n          - os: windows\n            arch: 386\n            artifact_name: dasel_windows_386.exe\n            asset_name: dasel_windows_386.exe\n            test_version: false\n          - os: linux\n            arch: arm64\n            artifact_name: dasel_linux_arm64\n            asset_name: dasel_linux_arm64\n            test_version: false\n          - os: linux\n            arch: arm\n            artifact_name: dasel_linux_arm32\n            asset_name: dasel_linux_arm32\n            test_version: false\n        exclude:\n            - os: darwin\n              arch: 386\n            - os: windows\n              arch: arm64\n            - os: windows\n              arch: arm\n            - os: darwin\n              arch: arm\n    name: Build ${{ matrix.os }} ${{ matrix.arch }}\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - uses: actions/setup-go@v6\n        with:\n          go-version: '^1.25.0'\n      - name: Set env\n        run: echo RELEASE_VERSION=${GITHUB_REF:10} >> $GITHUB_ENV\n      - name: Build\n        run: GOOS=${{ matrix.os }} GOARCH=${{ matrix.arch }} CGO_ENABLED=0 go build -o target/release/${{ matrix.artifact_name }} -ldflags=\"-w -s -X 'github.com/tomwright/dasel/v3/internal.Version=${{ env.RELEASE_VERSION }}'\" ./cmd/dasel\n      - name: Test version\n        if: matrix.test_version == true\n        run: ./target/release/${{ matrix.artifact_name }} version\n      - name: Gzip binaries\n        run: gzip -c ./target/release/${{ matrix.artifact_name }} > ./target/release/${{ matrix.artifact_name }}.gz\n      - name: Upload binaries to release\n        uses: svenstaro/upload-release-action@v2\n        with:\n          repo_token: ${{ secrets.GITHUB_TOKEN }}\n          file: target/release/${{ matrix.artifact_name }}\n          asset_name: ${{ matrix.asset_name }}\n          tag: ${{ github.ref }}\n      - name: Upload gzip binaries to release\n        uses: svenstaro/upload-release-action@v2\n        with:\n          repo_token: ${{ secrets.GITHUB_TOKEN }}\n          file: target/release/${{ matrix.artifact_name }}.gz\n          asset_name: ${{ matrix.asset_name }}.gz\n          tag: ${{ github.ref }}\n"
  },
  {
    "path": ".github/workflows/bump-homebrew.yaml",
    "content": "on:\n  workflow_run:\n    workflows: [\"Build\"]\n    types:\n      - completed\nname: Bump homebrew\njobs:\n  publish:\n    name: Update homebrew-core\n    runs-on: macos-latest\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n      - name: Set env\n        run: echo \"RELEASE_VERSION=$(git describe --tags --abbrev=0)\" >> $GITHUB_ENV\n      - name: Homebrew bump formula\n        uses: dawidd6/action-homebrew-bump-formula@v7\n        with:\n          token: ${{ secrets.GH_HOMEBREW_TOKEN }}\n          formula: dasel\n          tap: homebrew/core\n          tag: ${{ env.RELEASE_VERSION }}\n          force: true"
  },
  {
    "path": ".github/workflows/codeql-analysis.yml",
    "content": "# For most projects, this workflow file will not need changing; you simply need\n# to commit it to your repository.\n#\n# You may wish to alter this file to override the set of languages analyzed,\n# or to provide custom queries or build logic.\n#\n# ******** NOTE ********\n# We have attempted to detect the languages in your repository. Please check\n# the `language` matrix defined below to confirm you have the correct set of\n# supported CodeQL languages.\n# ******** NOTE ********\n\nname: \"CodeQL\"\n\non:\n  push:\n    branches: [ master ]\n  pull_request:\n    # The branches below must be a subset of the branches above\n    branches: [ master ]\n  schedule:\n    - cron: '25 21 * * 1'\n\njobs:\n  analyze:\n    name: Analyze\n    runs-on: ubuntu-latest\n\n    strategy:\n      fail-fast: false\n      matrix:\n        language: [ 'go' ]\n        # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]\n        # Learn more...\n        # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection\n\n    steps:\n    - name: Checkout repository\n      uses: actions/checkout@v6\n\n    # Initializes the CodeQL tools for scanning.\n    - name: Initialize CodeQL\n      uses: github/codeql-action/init@v2\n      with:\n        languages: ${{ matrix.language }}\n        # If you wish to specify custom queries, you can do so here or in a config file.\n        # By default, queries listed here will override any specified in a config file.\n        # Prefix the list here with \"+\" to use these queries and those in the config file.\n        # queries: ./path/to/local/query, your-org/your-repo/queries@main\n\n    # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).\n    # If this step fails, then you should remove it and run the build manually (see below)\n    - name: Autobuild\n      uses: github/codeql-action/autobuild@v2\n\n    # ℹ️ Command-line programs to run using the OS shell.\n    # 📚 https://git.io/JvXDl\n\n    # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines\n    #    and modify them (or add more) to build your code if your project\n    #    uses a compiled language\n\n    #- run: |\n    #   make bootstrap\n    #   make release\n\n    - name: Perform CodeQL Analysis\n      uses: github/codeql-action/analyze@v2\n"
  },
  {
    "path": ".github/workflows/container.yaml",
    "content": "on: [push, pull_request]\n\nenv:\n  GOLANG_VERSION: 1\n  IMAGE_NAME: ghcr.io/tomwright/dasel\n\nname: Container build, test and publish\njobs:\n  container:\n    strategy:\n      fail-fast: true\n      matrix:\n        include:\n          - distro: alpine\n            distro-tag: latest\n          - distro: debian\n            distro-tag: bookworm-slim\n    runs-on: ubuntu-latest\n    steps:\n        - name: Checkout\n          uses: actions/checkout@v6\n        - name: Set up QEMU\n          uses: docker/setup-qemu-action@v3\n        - name: Set up Docker Buildx\n          uses: docker/setup-buildx-action@v3\n        - name: Process version tag\n          if: ${{ startsWith(github.ref, 'refs/tags/v') }}\n          uses: nowsprinting/check-version-format-action@v4\n          id: version\n          with:\n            prefix: 'v'\n        - name: Build and Export\n          uses: docker/build-push-action@v5\n          with:\n            context: .\n            load: true\n            build-args: |\n              GOLANG_VERSION=${{ env.GOLANG_VERSION }}\n              RELEASE_VERSION=${{ github.ref_name }}\n              MAJOR_VERSION=${{ steps.version.outputs.major || 'v3' }}\n              TARGET_BASE_IMAGE=${{ matrix.distro }}:${{ matrix.distro-tag }}\n            tags: dasel:test\n        - name: Test\n          run: |\n            echo '{\"hello\": \"World\"}' | docker run -i --rm dasel:test -i json 'hello'\n        - name: Set version tag variables\n          if: ${{ steps.version.outputs.is_valid == 'true' }}\n          run: |\n            IMAGE=${{ env.IMAGE_NAME }}\n            MAJOR=${{ steps.version.outputs.major_without_prefix }}\n            MINOR=${{ steps.version.outputs.minor }}\n            PATCH=${{ steps.version.outputs.patch }}\n\n            if [ \"${{ matrix.distro }}\" = \"alpine\" ]; then\n              echo \"VERSIONED_TAGS<<EOF\" >> $GITHUB_ENV\n              echo \"${IMAGE}:alpine\" >> $GITHUB_ENV\n              echo \"${IMAGE}:${{ github.ref_name }}-alpine\" >> $GITHUB_ENV\n              echo \"${IMAGE}:${MAJOR}-alpine\" >> $GITHUB_ENV\n              echo \"${IMAGE}:${MAJOR}.${MINOR}-alpine\" >> $GITHUB_ENV\n              echo \"${IMAGE}:${MAJOR}.${MINOR}.${PATCH}-alpine\" >> $GITHUB_ENV\n              echo \"EOF\" >> $GITHUB_ENV\n            else\n              echo \"VERSIONED_TAGS<<EOF\" >> $GITHUB_ENV\n              echo \"${IMAGE}:latest\" >> $GITHUB_ENV\n              echo \"${IMAGE}:${{ github.ref_name }}\" >> $GITHUB_ENV\n              echo \"${IMAGE}:${{ github.ref_name }}-${{ matrix.distro-tag }}\" >> $GITHUB_ENV\n              echo \"${IMAGE}:${MAJOR}-${{ matrix.distro-tag }}\" >> $GITHUB_ENV\n              echo \"${IMAGE}:${MAJOR}.${MINOR}-${{ matrix.distro-tag }}\" >> $GITHUB_ENV\n              echo \"${IMAGE}:${MAJOR}.${MINOR}.${PATCH}-${{ matrix.distro-tag }}\" >> $GITHUB_ENV\n              echo \"${IMAGE}:${MAJOR}\" >> $GITHUB_ENV\n              echo \"${IMAGE}:${MAJOR}.${MINOR}\" >> $GITHUB_ENV\n              echo \"${IMAGE}:${MAJOR}.${MINOR}.${PATCH}\" >> $GITHUB_ENV\n              echo \"EOF\" >> $GITHUB_ENV\n            fi\n        - name: Login to GitHub Container Registry\n          if: ${{ steps.version.outputs.is_valid == 'true' }}\n          uses: docker/login-action@v3\n          with:\n            registry: ghcr.io\n            username: TomWright\n            password: ${{ secrets.GHCR_PAT }}\n        - name: Build and Push\n          if: ${{ steps.version.outputs.is_valid == 'true' }}\n          uses: docker/build-push-action@v5\n          with:\n            context: .\n            platforms: linux/amd64,linux/arm64\n            push: true\n            build-args: | \n              GOLANG_VERSION=${{ env.GOLANG_VERSION }}\n              RELEASE_VERSION=${{ github.ref_name }}\n              MAJOR_VERSION=${{ steps.version.outputs.major }}\n              TARGET_BASE_IMAGE=${{ matrix.distro }}:${{ matrix.distro-tag }}\n            tags: ${{ env.VERSIONED_TAGS }}\n"
  },
  {
    "path": ".github/workflows/golangci-lint.yaml",
    "content": "name: golangci-lint\non:\n  push:\n    branches:\n      - main\n      - master\n  pull_request:\n\npermissions:\n  contents: read\n  pull-requests: read\n\njobs:\n  golangci:\n    name: lint\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - uses: actions/setup-go@v6\n        with:\n          go-version: '^1.25.0'\n      - name: golangci-lint\n        uses: golangci/golangci-lint-action@v8\n        with:\n          version: v2.4.0"
  },
  {
    "path": ".github/workflows/test.yaml",
    "content": "on: [push, pull_request]\nname: Test\njobs:\n  test:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Install Go\n        uses: actions/setup-go@v6\n        with:\n          go-version: '^1.25.0'\n      - name: Checkout code\n        uses: actions/checkout@v6\n      - uses: actions/cache@v4\n        with:\n          path: ~/go/pkg/mod\n          key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}\n          restore-keys: |\n            ${{ runner.os }}-go-\n      - name: Test\n        run: go test -coverprofile=coverage.txt -covermode=atomic -race ./...\n      - uses: codecov/codecov-action@v1\n        with:\n          token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos\n          file: ./coverage.txt # optional\n          flags: unittests # optional\n          fail_ci_if_error: false # optional (default = false)\n"
  },
  {
    "path": ".gitignore",
    "content": ".idea/\ndasel"
  },
  {
    "path": ".golangci.yaml",
    "content": "version: \"2\"\nlinters:\n  default: standard\nrun:\n  timeout: 2m"
  },
  {
    "path": ".pre-commit-hooks.yaml",
    "content": "- id: dasel-validate-docker\n  name: Validate JSON, YAML, XML, TOML files\n  description: Validate JSON files\n  language: docker_image\n  types_or:\n    - json\n    - yaml\n    - xml\n    - toml\n  entry: ghcr.io/tomwright/dasel\n  args:\n    - validate\n\n- id: dasel-validate-bin\n  name: Validate JSON, YAML, XML, TOML\n  description: Validate JSON, YAML, XML, TOML files\n  language: system\n  types_or:\n    - json\n    - yaml\n    - xml\n    - toml\n  entry: dasel\n  args:\n    - validate\n\n- id: dasel-validate\n  name: Validate JSON, YAML, XML, TOML\n  description: Validate JSON, YAML, XML, TOML files\n  language: golang\n  types_or:\n    - json\n    - yaml\n    - xml\n    - toml\n  entry: dasel\n  args:\n    - validate\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),\nand this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n\n## [Unreleased]\n\n- Nothing yet.\n\n## [v3.4.0] - 2026-03-19\n\n### Added\n\n- `keys` func that returns the keys or indices of a node. [See docs](https://daseldocs.tomwright.me/functions/keys).\n\n## [v3.3.2] - 2026-03-18\n\n### Fixed\n- Fixed a bug that caused the `get` function to return `false` instead of an error when doing an invalid lookup.\n- Fixed an issue with reading/writing null values in YAML.\n- Fixed a nil pointer dereference when reading/writing null YAML documents.\n- Fixed a security issue allowing unbounded YAML expansion. Thanks to @kq5y.\n\n## [v3.3.1] - 2026-02-26\n\n### Fixed\n\n- Fixed query selector parsing issue that incorrectly parsed array accessors when they followed a `filter` or `map` call.\n\n## [v3.3.0] - 2026-02-25\n\n### Added\n- `replace` function to replace occurrences of a substring in a string with another string. [See docs](https://daseldocs.tomwright.me/functions/replace).\n\n## [v3.2.3] - 2026-02-23\n\n### Added\n- XML parser now preserves comments and processing instructions during round-trip (#175).\n\n### Fixed\n- Spread operator within array construction is now honoured.\n\n## [v3.2.2] - 2026-02-13\n\n### Changed\n- Swapped to use goccy/go-json for improved performance. Thanks @imix\n- Updated model `IsScalar` internals to improve efficiency. Thanks @imix\n- General dependency updates.\n\n### Fixed\n- TOML parser sub table parsing. Thanks @pmeier\n\n## [v3.2.1] - 2026-01-05\n\n### Fixed\n- XML parser now correctly handles empty CDATA.\n\n## [v3.2.0] - 2025-12-26\n\n### Added\n- `join` function to join array elements into a single string with a specified separator. [See docs](https://daseldocs.tomwright.me/functions/join).\n\n## [v3.1.4] - 2025-12-18\n\n### Fixed\n- `Select` func in the exposed go API now correctly maps return values to the respective types.\n\n## [v3.1.3] - 2025-12-18\n\n### Fixed\n- XML documents no longer create redundant `root` or `item` elements.\n- Dasel no longer loses value metadata when reading/writing to internal models.\n\n## [v3.1.2] - 2025-12-17\n\n### Fixed\n\n- Fix XML reading/writing when XML processing instructions are present.\n\n## [v3.1.1] - 2025-12-16\n\n### Fixed\n\n- Homebrew release.\n\n## [v3.1.0] - 2025-12-16\n\n### Added\n- `sum` function to sum numeric values in an array. [See docs](https://daseldocs.tomwright.me/functions/sum).\n\n## [v3.0.0] - 2025-12-15\n\n### Added\n- Major new version release.\n- INI support.\n- HCL support.\n- Dasel syntax now supports variables and expressions.\n- Files can now be read and parsed inside a dasel query.\n- Variables can now be passed to dasel from the command line.\n- Support for comments in queries.\n- Dasel config file to define default file format.\n- Interactive mode for dasel CLI (alpha).\n\n### Changed\n- Go module path changed to `github.com/tomwright/dasel/v3`.\n- Internal changes to support new version.\n- Query/selector syntax revamp. See [docs](https://daseldocs.tomwright.me) for more information.\n- Majority of read/write operations will now maintain ordering.\n- Migrated from Cobra to Kong for CLI parsing/processing.\n- Removed `put` and `delete` commands. Instead, modify within the query and use `--root` flag.\n\n### Fixed\n- File redirect now works in the same way as piped input.\n- Various other bug fixes and improvements.\n- Whitespace in query syntax is now handled correctly.\n\n## [v2.8.1] - 2024-06-30\n\n### Fixed\n- Fixed a bug related to yaml aliases.\n\n## [v2.8.0] - 2024-06-28\n\n### Fixed\n\n- Fixed a bug that could cause a panic.\n- `type()` now returns `null` instead of `unknown` for null values.\n- Added YAML support for merge tag/aliases. Thanks to [pmeier](https://github.com/pmeier). [Issue 285](https://github.com/TomWright/dasel/issues/285).\n\n## [v2.7.0] - 2024-03-14\n\n### Added\n\n- `null()` function. [See docs](https://daseldocs.tomwright.me/functions/null)\n\n### Fixed\n\n- Dasel now correctly handles `null` values.\n\n## [v2.6.0] - 2024-02-15\n\n### Added\n\n- Support for `--indent` flag.\n- More descriptive errors when dasel fails to open a file.\n\n### Changed\n\n- Docker build improvements in workflows.\n\n## [v2.5.0] - 2023-11-28\n\n### Added\n\n- Add `man` that generates manpages for all dasel subcommands.\n\n### Fixed\n\n- Fixed an issue when [parsing empty input documents](https://github.com/TomWright/dasel/issues/374).\n\n## [v2.4.1] - 2023-10-18\n\n### Fixed\n\n- JSON output now acts as expected regarding the EscapeHTML flag.\n\n## [v2.4.0] - 2023-10-18\n\n### Added\n\n- `orDefault()` function. [See docs](https://daseldocs.tomwright.me/functions/ordefault)\n- `--csv-comma` flag to change the csv separator.\n- `--csv-write-comma` flag to change the csv separator specifically for writes.\n- `--csv-comment` flag to change the csv comment character.\n- `--csv-crlf` flag to enable or disable CRLF output when working with csv files.\n\n### Fixed\n\n- Resolved an issue with YAML parser that was causing strings to be read as booleans.\n- Fix a parsing issue with CSV types that forced you to expand and merge in order for it selects to work [Issue 364](https://github.com/TomWright/dasel/issues/364).\n\n## [v2.3.6] - 2023-08-30\n\n### Fixed\n\n- XML is now formatted correctly. (https://github.com/TomWright/dasel/issues/354)\n\n## [v2.3.5] - 2023-08-29\n\n### Changed\n\n- Small internal optimisation (https://github.com/TomWright/dasel/pull/341)\n- Update to go 1.21\n- Upgrade dependencies\n\n### Fixed\n\n- Resolved an issue with YAML parser that was causing strings to be read as numbers.\n- Timestamps can now be resolved as expected in YAML.\n\n## [v2.3.4] - 2023-06-01\n\n### Fixed\n\n- `len` function now works with new map type.\n- `keys` function now works with new map type.\n\n## [v2.3.3] - 2023-05-31\n\n### Fixed\n\n- Errors when selecting data are now correctly handled.\n\n## [v2.3.2] - 2023-05-31\n\n### Fixed\n\n- Restored previous octal, binary and hex number parsing support in YAML and `put` command.\n\n## [v2.3.1] - 2023-05-29\n\n### Fixed\n\n- `version` command now outputs correct version information (only affected v2 onwards)\n\n## [v2.3.0] - 2023-05-29\n\n### Changed\n\n- Maps are now ordered internally.\n- JSON and YAML maps maintain ordering on read/write.\n- `all()` func now works with strings.\n- `index()` func now works with strings.\n\n### Fixed\n\n- Multi-document output should now be displayed correctly.\n- Index shorthand selector now works with multiple indexes.\n- Null values are now correctly handled.\n\n## [v2.2.0] - 2023-04-17\n\n### Added\n\n- `keys()` function.\n\n## [v2.1.2] - 2023-03-27\n\n### Added\n\n- Join function.\n- String function.\n\n### Fixed\n\n- Null error caused by null values in arrays. See [PR 307](https://github.com/TomWright/dasel/pull/307).\n\n## [v2.1.1] - 2023-01-19\n\n### Fixed\n\n- Changed go module to `github.com/tomwright/dasel/v3` to ensure it works correctly with go modules.\n\n## [v2.1.0] - 2023-01-11\n\n### Added\n\n- Ability to jump to a parent x levels up with `parent(x)`. Defaults to 1 level.\n\n## [v2.0.2] - 2022-12-07\n\n### Fixed\n\n- Argument parsing issue that caused files to be written to the wrong place. See [discussion 268](https://github.com/TomWright/dasel/discussions/268).\n\n## [v2.0.1] - 2022-12-07\n\n### Added\n\n- `float` type in `put` command.\n\n### Fixed\n\n- Output values are now correctly de-referenced. This fixed issues with encoded values not appearing correctly.\n- Escape characters in selector strings now work as expected.\n\n## [v2.0.0] - 2022-12-02\n\nSee [documentation](https://daseldocs.tomwright.me) for all changes.\n\n- Selector syntax\n\n## [v1.27.3] - 2022-10-18\n\n### Fixed\n\n- The compact flag now works with the XML parser.\n\n## [v1.27.2] - 2022-10-18\n\n### Fixed\n\n- Help text for select and delete commands now contain all available parsers.\n- Errors now implement the `Is` interface so they are easier to use from go.\n- Floats are now formatted in decimal format instead of scientific notification when writing to CSV ([Issue 245](https://github.com/TomWright/dasel/issues/245), [Issue 229](https://github.com/TomWright/dasel/issues/229))\n\n## [v1.27.1] - 2022-09-28\n\n### Fixed\n\n- Improved selector comparison parsing to allow matching on values containing special characters.\n\n## [v1.27.0] - 2022-09-26\n\n### Added\n\n- New `value-file` flag allows you to `put` values read from a file ([Issue 246](https://github.com/TomWright/dasel/issues/246))\n\n## [v1.26.1] - 2022-08-24\n\n### Fixed\n\n- Make the completion command available for use ([Issue 216](https://github.com/TomWright/dasel/issues/216))\n- Make the `__complete` command available for use\n\n## [v1.26.0] - 2022-07-09\n\n### Added\n\n- Search optional selector - `(#:key=value)`\n\n## [v1.25.1] - 2022-06-29\n\n### Added\n\n- Pre-commit hooks for validate command.\n\n## [v1.25.0] - 2022-06-26\n\n### Added\n\n- Support for struct type usage in go package.\n- Validate command.\n\n## [v1.24.3] - 2022-04-23\n\n### Added\n\n- Gzip compressed binaries on releases.\n\n## [v1.24.2] - 2022-04-22\n\n### Fixed\n\n- Update a package to avoid a High Vulnerability in golang.org/x/crypto with CVE ID [CVE-2022-27191](https://github.com/advisories/GHSA-8c26-wmh5-6g9v)\n\n## [v1.24.1] - 2022-03-28\n\n### Changed\n\n- `storage` package has been moved outside the `internal` package.\n\n### Fixed\n\n- New funcs added in `v1.24.0` can now be used as expected since you can now access the `storage.ReadWriteOption`.\n\n## [v1.24.0] - 2022-03-18\n\n### Added\n\n- `Node.NewFromFile` func to load a root node from a file.\n- `Node.NewFromReader` func to load a root node from an `io.Reader`.\n- `Node.WriteToFile` func to write results to a file.\n- `Node.Write` func to write results to an `io.Writer`.\n\n## [v1.23.0] - 2022-03-10\n\n### Fixed\n\n- Update github.com/pelletier/go-toml to consume fix for https://github.com/TomWright/dasel/issues/191.\n\n### Added\n\n- Sprig functions to output formatter template.\n\n## [v1.22.1] - 2021-11-09\n\n### Fixed\n\n- Cleaned up error output\n\n## [v1.22.0] - 2021-11-09\n\n### Added\n\n- Type selector `[@]`.\n\n### Fixed\n\n- Errors are now written to stderr as expected.\n\n## [v1.21.2] - 2021-10-21\n\n### Added\n\n- Linux arm32 build target.\n\n## [v1.21.1] - 2021-09-30\n\n### Changed\n- `--escape-html` flag now defaults to false.\n\n## [v1.21.0] - 2021-09-29\n\n### Added\n- `--escape-html` flag.\n\n### Fixed\n- `put document` and `put object` are now aware of the `--merge-input-documents` flag.\n\n## [v1.20.1] - 2021-09-28\n\n### Added\n\n- `buster-slim` and `alpine` tags to built docker images.\n\n### Fixed\n\n- Different encodings in XML files are now [handled as expected](https://github.com/TomWright/dasel/issues/164).\n\n## [v1.20.0] - 2021-08-30\n\n### Added\n\n- `-v`, `--value` flag to workaround [dash issue](https://github.com/TomWright/dasel/issues/117).\n\n### Fixed\n\n- Fixed an issue in which unicode characters could cause issues when parsing selectors.\n\n## [v1.19.0] - 2021-08-14\n\n### Added\n\n- `--colour`,`--color` flag to enable colourised output in select command.\n\n## [v1.18.0] - 2021-08-11\n\n### Added\n\n- `--format` flag to `select` command.\n\n## [v1.17.0] - 2021-08-08\n\n### Added\n\n- Support for `!=` comparison operator in dynamic and search selectors.\n- Support for `-`/`keyValue` key in dynamic selectors.\n\n## [v1.16.1] - 2021-08-02\n\n### Fixed\n\n- Fixed a bug that stopped the delete command editing files in place.\n\n## [v1.16.0] - 2021-08-01\n\n### Added\n\n- Delete command.\n\n## [v1.15.0] - 2021-05-06\n\n### Added\n\n- `--merge-input-documents` flag.\n\n### Changed\n\n- Optional `noupdater` build tag to disable the self-update command.\n\n### Fixed\n\n- Empty XML documents are now parsed correctly.\n  - https://github.com/TomWright/dasel/issues/131\n\n## [v1.14.1] - 2021-04-15\n\n### Added\n\n- arm64 build support.\n\n## [v1.14.0] - 2021-04-11\n\n### Added\n\n- `.[#]` length selector.\n- `>` comparison operator.\n- `>=` comparison operator.\n- `<` comparison operator.\n- `<=` comparison operator.\n\n## [v1.13.6] - 2021-03-29\n\n### Changed\n\n- Development versions of dasel will now include more specific version information where possible.\n\n### Fixed\n\n- Fix an issue that stopped dasel being able to output CSV documents when parsed from JSON.\n\n## [v1.13.5] - 2021-03-22\n\n### Fixed\n\n- Empty map values are now initialised as `map[string]interface{}` rather than `map[interface{}]interface{}`.\n\n## [v1.13.4] - 2021-03-11\n\n### Fixed\n\n- Empty document input is now treated different in select and put commands.\n  - https://github.com/TomWright/dasel/issues/99\n  - https://github.com/TomWright/dasel/issues/102\n\n## [v1.13.3] - 2021-03-05\n\n### Fixed\n\n- Blank YAML and CSV input is now treated as an empty document.\n\n### Changed\n\n- Blank JSON input is now treated as an empty document.\n\n## [v1.13.2] - 2021-02-25\n\n### Changed\n\n- Improved information provided in `UnsupportedTypeForSelector` errors.\n- Upgrade to go 1.16.\n\n### Fixed\n\n- Make sure the `-n`,`--null` flag has an effect in multi-select queries.\n\n## [v1.13.1] - 2021-02-18\n\n### Fixed\n\n- Added `CGO_ENABLED=0` build flag to ensure linux_amd64 builds are statically linked.\n\n## [v1.13.0] - 2021-02-11\n\n### Added\n\n- `--length` flag to select command.\n\n## [v1.12.2] - 2021-01-05\n\n### Fixed\n\n- Fix a bug that stopped the write parser being properly detected when writing to the input file.\n\n## [v1.12.1] - 2021-01-05\n\n### Changed\n\n- Build workflows now updated to run on ubuntu-latest and use a matrix to build assets for `linux`, `darwin` and\n  `windows` for both `amd64` and `386`.\n\n### Fixed\n\n- Release asset for macos/darwin is now named `dasel_darwin_amd64` instead of `dasel_macos_amd64`.\n- Self-updater now identifies `dev` version as development.\n\n## [v1.12.0] - 2021-01-02\n\n### Added\n\n- Add `-c`, `--compact` flag to remove pretty-print formatting from JSON output.\n- Defined `storage.IndentOption(indent string) ReadWriteOption`.\n- Defined `storage.PrettyPrintOption(enabled bool) ReadWriteOption`.\n\n### Changed\n\n- Changed `storage.Parser` funcs to allow the passing of `...ReadWriteOption`.\n\n## [v1.11.0] - 2020-12-22\n\n### Added\n\n- Benchmark info now contains graphs.\n- `update` command to self-update dasel.\n\n### Changed\n\n- Benchmark info now directly compares dasel, jq and yq.\n\n## [v1.10.0] - 2020-12-19\n\n### Added\n\n- Add `dasel put document` command.\n- Benchmark information.\n\n### Fixed\n\n- `-r`,`--read` and `-w`,`--write` flags are now used in `dasel put object`.\n- Fix issues that occurred when writing to the root node.\n\n### Changed\n\n- Command names and descriptions.\n\n## [v1.9.1] - 2020-12-12\n\n### Fixed\n\n- Stopped parsing XML entities in strings.\n\n## [v1.9.0] - 2020-12-12\n\n### Added\n\n- Add keys/index selector in multi queries.\n- Add `-n`,`--null` flag.\n\n## [v1.8.0] - 2020-12-01\n\n### Added\n\n- Add ability to use `ANY_INDEX` (`[*]`) and `DYNAMIC` (`(x=y)`) selectors on maps/objects.\n\n## [v1.7.0] - 2020-11-30\n\n### Added\n\n- Add `-r`,`--read` and `-w`,`--write` flags to specifically choose input/output parsers. This allows you to convert data between formats.\n\n## [v1.6.2] - 2020-11-18\n\n### Added\n\n- Add support for multi-document JSON files.\n\n## [v1.6.1] - 2020-11-17\n\n### Changed\n\n- Remove some validation on `dasel put object` to allow you to put empty objects.\n\n## [v1.6.0] - 2020-11-17\n\n### Added\n\n- Add search selector to allow recursive searching from the current node.\n\n## [v1.5.1] - 2020-11-14\n\n### Fixed\n\n- Fixed an issue that stopped new values being saved.\n\n## [v1.5.0] - 2020-11-12\n\n### Added\n\n- Add ability to use `\\` as an escape character in selectors.\n\n## [v1.4.1] - 2020-11-11\n\n### Fixed\n\n- Fix an issue when parsing dynamic selectors.\n\n## [v1.4.0] - 2020-11-08\n\n### Added\n\n- Add `-m`,`--multiple` flag to deal with multi-value queries.\n- Add `ANY_INDEX` or `[*]` selector.\n- Add `NextMultiple` property to the `Node` struct - this is used when processing multi-value queries.\n- Add `Node.QueryMultiple` func.\n- Add `Node.PutMultiple` func.\n\n## [v1.3.0] - 2020-11-08\n\n### Added\n\n- Add support for CSV files.\n\n## [v1.2.0] - 2020-11-07\n\n### Added\n\n- Add support for multi-document YAML files.\n- Add CodeQL step in github actions.\n\n### Changed\n\n- Docker image is now pushed to ghcr instead of github packages.\n\n## [v1.1.0] - 2020-11-01\n\n### Added\n\n- Add sub-selector support in dynamic selectors.\n\n## [v1.0.4] - 2020-10-30\n\n### Added\n\n- Add `--plain` flag to tell dasel to output un-formatted values.\n\n## [v1.0.3] - 2020-10-29\n\n### Changed\n\n- Command output is now followed by a newline.\n\n## [v1.0.2] - 2020-10-28\n\n### Added\n\n- Docker image is now built and pushed when a new release is tagged.\n\n## [v1.0.1] - 2020-10-28\n\n### Added\n\n- Add support for XML.\n\n### Changed\n\n- Add `-` as an alias for `stdin`/`stdout` in `--file` and `--output` flags.\n- Selector can now be given as the first argument making the flag itself optional.\n- `select` is now the default command.\n\n## [v1.0.0] - 2020-10-27\n\n### Added\n\n- Add lots of tests.\n- Add docs.\n- Got accepted to go-awesome.\n\n## [v0.0.5] - 2020-09-27\n\n### Added\n\n- Add support for TOML.\n\n## [v0.0.4] - 2020-09-27\n\n### Added\n\n- Ability to check against the node value in a dynamic selector.\n- Code coverage.\n\n### Changed\n\n- Use reflection instead of fixed type checks.\n\n## [v0.0.3] - 2020-09-24\n\n### Changed\n\n- Use reflection instead of fixed type checks.\n- Extract commands into their own functions to make them testable.\n\n## [v0.0.2] - 2020-09-23\n\n### Added\n\n- Add ability to pipe data in/out of dasel.\n- Add dasel put command.\n\n## [v0.0.1] - 2020-09-22\n\n### Added\n\n- Everything!\n\n[unreleased]: https://github.com/TomWright/dasel/compare/v3.4.0...HEAD\n[v3.4.0]: https://github.com/TomWright/dasel/compare/v3.3.2...v3.4.0\n[v3.3.2]: https://github.com/TomWright/dasel/compare/v3.3.1...v3.3.2\n[v3.3.1]: https://github.com/TomWright/dasel/compare/v3.3.0...v3.3.1\n[v3.3.0]: https://github.com/TomWright/dasel/compare/v3.2.3...v3.3.0\n[v3.2.3]: https://github.com/TomWright/dasel/compare/v3.2.2...v3.2.3\n[v3.2.2]: https://github.com/TomWright/dasel/compare/v3.2.1...v3.2.2\n[v3.2.1]: https://github.com/TomWright/dasel/compare/v3.2.0...v3.2.1\n[v3.2.0]: https://github.com/TomWright/dasel/compare/v3.1.4...v3.2.0\n[v3.1.4]: https://github.com/TomWright/dasel/compare/v3.1.3...v3.1.4\n[v3.1.3]: https://github.com/TomWright/dasel/compare/v3.1.2...v3.1.3\n[v3.1.2]: https://github.com/TomWright/dasel/compare/v3.1.1...v3.1.2\n[v3.1.1]: https://github.com/TomWright/dasel/compare/v3.1.0...v3.1.1\n[v3.1.0]: https://github.com/TomWright/dasel/compare/v3.0.0...v3.1.0\n[v3.0.0]: https://github.com/TomWright/dasel/compare/v2.8.1...v3.0.0\n[v2.8.1]: https://github.com/TomWright/dasel/compare/v2.8.0...v2.8.1\n[v2.8.0]: https://github.com/TomWright/dasel/compare/v2.7.0...v2.8.0\n[v2.7.0]: https://github.com/TomWright/dasel/compare/v2.6.0...v2.7.0\n[v2.6.0]: https://github.com/TomWright/dasel/compare/v2.5.0...v2.6.0\n[v2.5.0]: https://github.com/TomWright/dasel/compare/v2.4.1...v2.5.0\n[v2.4.1]: https://github.com/TomWright/dasel/compare/v2.4.0...v2.4.1\n[v2.4.0]: https://github.com/TomWright/dasel/compare/v2.3.6...v2.4.0\n[v2.3.6]: https://github.com/TomWright/dasel/compare/v2.3.5...v2.3.6\n[v2.3.5]: https://github.com/TomWright/dasel/compare/v2.3.4...v2.3.5\n[v2.3.4]: https://github.com/TomWright/dasel/compare/v2.3.3...v2.3.4\n[v2.3.3]: https://github.com/TomWright/dasel/compare/v2.3.2...v2.3.3\n[v2.3.2]: https://github.com/TomWright/dasel/compare/v2.3.1...v2.3.2\n[v2.3.1]: https://github.com/TomWright/dasel/compare/v2.3.0...v2.3.1\n[v2.3.0]: https://github.com/TomWright/dasel/compare/v2.2.0...v2.3.0\n[v2.2.0]: https://github.com/TomWright/dasel/compare/v2.1.2...v2.2.0\n[v2.1.2]: https://github.com/TomWright/dasel/compare/v2.1.1...v2.1.2\n[v2.1.1]: https://github.com/TomWright/dasel/compare/v2.1.0...v2.1.1\n[v2.1.0]: https://github.com/TomWright/dasel/compare/v2.0.2...v2.1.0\n[v2.0.2]: https://github.com/TomWright/dasel/compare/v2.0.1...v2.0.2\n[v2.0.1]: https://github.com/TomWright/dasel/compare/v2.0.0...v2.0.1\n[v2.0.0]: https://github.com/TomWright/dasel/compare/v1.27.3...v2.0.0\n[v1.27.3]: https://github.com/TomWright/dasel/compare/v1.27.2...v1.27.3\n[v1.27.2]: https://github.com/TomWright/dasel/compare/v1.27.1...v1.27.2\n[v1.27.1]: https://github.com/TomWright/dasel/compare/v1.27.0...v1.27.1\n[v1.27.0]: https://github.com/TomWright/dasel/compare/v1.26.1...v1.27.0\n[v1.26.1]: https://github.com/TomWright/dasel/compare/v1.26.0...v1.26.1\n[v1.26.0]: https://github.com/TomWright/dasel/compare/v1.25.1...v1.26.0\n[v1.25.1]: https://github.com/TomWright/dasel/compare/v1.25.0...v1.25.1\n[v1.25.0]: https://github.com/TomWright/dasel/compare/v1.24.3...v1.25.0\n[v1.24.3]: https://github.com/TomWright/dasel/compare/v1.24.2...v1.24.3\n[v1.24.2]: https://github.com/TomWright/dasel/compare/v1.24.1...v1.24.2\n[v1.24.1]: https://github.com/TomWright/dasel/compare/v1.24.0...v1.24.1\n[v1.24.0]: https://github.com/TomWright/dasel/compare/v1.23.0...v1.24.0\n[v1.23.0]: https://github.com/TomWright/dasel/compare/v1.22.1...v1.23.0\n[v1.22.1]: https://github.com/TomWright/dasel/compare/v1.22.0...v1.22.1\n[v1.22.0]: https://github.com/TomWright/dasel/compare/v1.21.2...v1.22.0\n[v1.21.2]: https://github.com/TomWright/dasel/compare/v1.21.1...v1.21.2\n[v1.21.1]: https://github.com/TomWright/dasel/compare/v1.21.0...v1.21.1\n[v1.21.0]: https://github.com/TomWright/dasel/compare/v1.20.1...v1.21.0\n[v1.20.1]: https://github.com/TomWright/dasel/compare/v1.20.0...v1.20.1\n[v1.20.0]: https://github.com/TomWright/dasel/compare/v1.19.0...v1.20.0\n[v1.19.0]: https://github.com/TomWright/dasel/compare/v1.18.0...v1.19.0\n[v1.18.0]: https://github.com/TomWright/dasel/compare/v1.17.0...v1.18.0\n[v1.17.0]: https://github.com/TomWright/dasel/compare/v1.16.1...v1.17.0\n[v1.16.1]: https://github.com/TomWright/dasel/compare/v1.16.0...v1.16.1\n[v1.16.0]: https://github.com/TomWright/dasel/compare/v1.15.0...v1.16.0\n[v1.15.0]: https://github.com/TomWright/dasel/compare/v1.14.1...v1.15.0\n[v1.14.1]: https://github.com/TomWright/dasel/compare/v1.14.0...v1.14.1\n[v1.14.0]: https://github.com/TomWright/dasel/compare/v1.13.6...v1.14.0\n[v1.13.6]: https://github.com/TomWright/dasel/compare/v1.13.5...v1.13.6\n[v1.13.5]: https://github.com/TomWright/dasel/compare/v1.13.4...v1.13.5\n[v1.13.4]: https://github.com/TomWright/dasel/compare/v1.13.3...v1.13.4\n[v1.13.3]: https://github.com/TomWright/dasel/compare/v1.13.2...v1.13.3\n[v1.13.2]: https://github.com/TomWright/dasel/compare/v1.13.1...v1.13.2\n[v1.13.1]: https://github.com/TomWright/dasel/compare/v1.13.0...v1.13.1\n[v1.13.0]: https://github.com/TomWright/dasel/compare/v1.12.2...v1.13.0\n[v1.12.2]: https://github.com/TomWright/dasel/compare/v1.12.1...v1.12.2\n[v1.12.1]: https://github.com/TomWright/dasel/compare/v1.12.0...v1.12.1\n[v1.12.0]: https://github.com/TomWright/dasel/compare/v1.11.0...v1.12.0\n[v1.11.0]: https://github.com/TomWright/dasel/compare/v1.10.0...v1.11.0\n[v1.10.0]: https://github.com/TomWright/dasel/compare/v1.9.1...v1.10.0\n[v1.9.1]: https://github.com/TomWright/dasel/compare/v1.9.0...v1.9.1\n[v1.9.0]: https://github.com/TomWright/dasel/compare/v1.8.0...v1.9.0\n[v1.8.0]: https://github.com/TomWright/dasel/compare/v1.7.0...v1.8.0\n[v1.7.0]: https://github.com/TomWright/dasel/compare/v1.6.2...v1.7.0\n[v1.6.2]: https://github.com/TomWright/dasel/compare/v1.6.1...v1.6.2\n[v1.6.1]: https://github.com/TomWright/dasel/compare/v1.6.0...v1.6.1\n[v1.6.0]: https://github.com/TomWright/dasel/compare/v1.5.1...v1.6.0\n[v1.5.1]: https://github.com/TomWright/dasel/compare/v1.5.0...v1.5.1\n[v1.5.0]: https://github.com/TomWright/dasel/compare/v1.4.1...v1.5.0\n[v1.4.1]: https://github.com/TomWright/dasel/compare/v1.4.0...v1.4.1\n[v1.4.0]: https://github.com/TomWright/dasel/compare/v1.3.0...v1.4.0\n[v1.3.0]: https://github.com/TomWright/dasel/compare/v1.2.0...v1.3.0\n[v1.1.0]: https://github.com/TomWright/dasel/compare/v1.0.4...v1.1.0\n[v1.0.4]: https://github.com/TomWright/dasel/compare/v1.0.3...v1.0.4\n[v1.0.3]: https://github.com/TomWright/dasel/compare/v1.0.2...v1.0.3\n[v1.0.2]: https://github.com/TomWright/dasel/compare/v1.0.1...v1.0.2\n[v1.0.1]: https://github.com/TomWright/dasel/compare/v1.0.0...v1.0.1\n[v1.0.0]: https://github.com/TomWright/dasel/compare/v0.0.5...v1.0.0\n[v0.0.5]: https://github.com/TomWright/dasel/compare/v0.0.4...v0.0.5\n[v0.0.4]: https://github.com/TomWright/dasel/compare/v0.0.3...v0.0.4\n[v0.0.3]: https://github.com/TomWright/dasel/compare/v0.0.2...v0.0.3\n[v0.0.2]: https://github.com/TomWright/dasel/compare/v0.0.1...v0.0.2\n[v0.0.1]: https://github.com/TomWright/dasel/releases/tag/v0.0.1"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, we as\ncontributors and maintainers pledge to making participation in our project and\nour community a harassment-free experience for everyone, regardless of age, body\nsize, disability, ethnicity, sex characteristics, gender identity and expression,\nlevel of experience, education, socio-economic status, nationality, personal\nappearance, race, religion, or sexual identity and orientation.\n\n## Our Standards\n\nExamples of behavior that contributes to creating a positive environment\ninclude:\n\n- Using welcoming and inclusive language\n- Being respectful of differing viewpoints and experiences\n- Gracefully accepting constructive criticism\n- Focusing on what is best for the community\n- Showing empathy towards other community members\n\nExamples of unacceptable behavior by participants include:\n\n- The use of sexualized language or imagery and unwelcome sexual attention or\n  advances\n- Trolling, insulting/derogatory comments, and personal or political attacks\n- Public or private harassment\n- Publishing others' private information, such as a physical or electronic\n  address, without explicit permission\n- Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n## Our Responsibilities\n\nProject maintainers are responsible for clarifying the standards of acceptable\nbehavior and are expected to take appropriate and fair corrective action in\nresponse to any instances of unacceptable behavior.\n\nProject maintainers have the right and responsibility to remove, edit, or\nreject comments, commits, code, wiki edits, issues, and other contributions\nthat are not aligned to this Code of Conduct, or to ban temporarily or\npermanently any contributor for other behaviors that they deem inappropriate,\nthreatening, offensive, or harmful.\n\n## Scope\n\nThis Code of Conduct applies both within project spaces and in public spaces\nwhen an individual is representing the project or its community. Examples of\nrepresenting a project or community include using an official project e-mail\naddress, posting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event. Representation of a project may be\nfurther defined and clarified by project maintainers.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported by contacting the project team at contact@tomwright.me. All\ncomplaints will be reviewed and investigated and will result in a response that\nis deemed necessary and appropriate to the circumstances. The project team is\nobligated to maintain confidentiality with regard to the reporter of an incident.\nFurther details of specific enforcement policies may be posted separately.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good\nfaith may face temporary or permanent repercussions as determined by other\nmembers of the project's leadership.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,\navailable at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html\n\n[homepage]: https://www.contributor-covenant.org\n\nFor answers to common questions about this code of conduct, see\nhttps://www.contributor-covenant.org/faq\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to Dasel\n\nThank you for considering contributing to Dasel! Contributions of all kinds are welcome — whether it's fixing bugs, improving documentation, or adding new features.\n\n## How to Contribute\n\n### 1. Reporting Issues\n\n* Check the [issue tracker](https://github.com/TomWright/dasel/issues) to see if your issue has already been reported.\n* If not, open a new issue with a clear description. Please include:\n\n    * Steps to reproduce (if it's a bug)\n    * Expected vs actual behavior\n    * Versions of Dasel, Go, and your OS\n\n### 2. Suggesting Features\n\n* Open a [discussion](https://github.com/TomWright/dasel/discussions) if you'd like feedback before implementing.\n* If the idea is well-defined, create an issue describing the use case and possible syntax.\n\n### 3. Submitting Pull Requests\n\n1. Fork the repository and clone your fork.\n2. Create a new branch for your work:\n\n   ```bash\n   git checkout -b feature/my-new-feature\n   ```\n3. Make your changes and add tests if relevant.\n4. Run the test suite to ensure nothing is broken:\n\n   ```bash\n   go test ./...\n   ```\n5. Commit your changes with a clear message:\n\n   ```bash\n   git commit -m \"Add support for XYZ selector\"\n   ```\n6. Push your branch and open a Pull Request.\n\n### 4. Code Style\n\n* Follow Go best practices and conventions.\n* Keep code simple and readable.\n* Add comments for complex logic.\n\n### 5. Documentation\n\n* Ensure documentation requirements are listed on your PR so docs site can be updated.\n* Ensure examples are clear and consistent with the style of existing docs.\n\n### 6. Communication\n\n* Be respectful and constructive in discussions.\n* Aim to keep contributions focused and incremental.\n\n---\n\n## Getting Help\n\nIf you have questions, feel free to:\n\n* Start a [discussion](https://github.com/TomWright/dasel/discussions)\n* Ask in an open issue related to your question\n\nWe appreciate your contribution and for helping improve Dasel!\n"
  },
  {
    "path": "Dockerfile",
    "content": "ARG GOLANG_VERSION=1.25.0\nARG TARGET_BASE_IMAGE=debian:bookworm-slim\nFROM golang:${GOLANG_VERSION} AS builder\n\nARG MAJOR_VERSION=v3\nARG RELEASE_VERSION=master\nARG CGO_ENABLED=0\n\nCOPY . .\n\nRUN go build -o /dasel -ldflags=\"-w -s -X 'github.com/tomwright/dasel/${MAJOR_VERSION}/internal.Version=${RELEASE_VERSION}'\" ./cmd/dasel\n\nFROM ${TARGET_BASE_IMAGE}\n\nCOPY --from=builder --chmod=755 /dasel /usr/local/bin/dasel\n\nENTRYPOINT [\"/usr/local/bin/dasel\"]\nCMD [\"--help\"]\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020 Tom Wright\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "[![Gitbook](https://badges.aleen42.com/src/gitbook_1.svg)](https://daseldocs.tomwright.me)\n[![Go Report Card](https://goreportcard.com/badge/github.com/tomwright/dasel/v3)](https://goreportcard.com/report/github.com/tomwright/dasel/v3)\n[![PkgGoDev](https://pkg.go.dev/badge/github.com/tomwright/dasel)](https://pkg.go.dev/github.com/tomwright/dasel/v3)\n![Test](https://github.com/TomWright/dasel/workflows/Test/badge.svg)\n![Build](https://github.com/TomWright/dasel/workflows/Build/badge.svg)\n[![codecov](https://codecov.io/gh/TomWright/dasel/branch/master/graph/badge.svg)](https://codecov.io/gh/TomWright/dasel)\n[![Mentioned in Awesome Go](https://awesome.re/mentioned-badge.svg)](https://github.com/avelino/awesome-go)\n![GitHub Downloads](https://img.shields.io/github/downloads/TomWright/dasel/total)\n![Homebrew Formula Downloads](https://img.shields.io/homebrew/installs/dy/dasel?label=brew%20installs)\n![GitHub License](https://img.shields.io/github/license/TomWright/dasel)\n[![GitHub tag (latest by date)](https://img.shields.io/github/v/tag/TomWright/dasel?label=latest%20release)](https://github.com/TomWright/dasel/releases/latest)\n[![Homebrew tag (latest by date)](https://img.shields.io/homebrew/v/dasel)](https://formulae.brew.sh/formula/dasel)\n\n<div align=\"center\">\n    <img src=\"./daselgopher.png\" alt=\"Dasel mascot\" width=\"250\"/>\n</div>\n\n# Dasel\n\nDasel (short for **Data-Select**) is a command-line tool and library for querying, modifying, and transforming data structures such as JSON, YAML, TOML, XML, and CSV.\n\nIt provides a consistent, powerful syntax to traverse and update data — making it useful for developers, DevOps, and data wrangling tasks.\n\n---\n\n## Features\n\n* **Multi-format support**: JSON, YAML, TOML, XML, CSV, HCL, INI.\n* **Unified query syntax**: Access data in any format with the same selectors.\n* **Query & search**: Extract values, lists, or structures with intuitive syntax.\n* **Modify in place**: Update, insert, or delete values directly in structured files.\n* **Convert between formats**: Seamlessly transform data from JSON → YAML, TOML → JSON, etc.\n* **Script-friendly**: Simple CLI integration for shell scripts and pipelines.\n* **Library support**: Import and use in Go projects.\n\n---\n\n## Installation\n\n### Homebrew (macOS/Linux)\n\n```sh\nbrew install dasel\n```\n\n### Go Install\n\n```sh\ngo install github.com/tomwright/dasel/v3/cmd/dasel@master\n```\n\n### Prebuilt Binaries\n\nPrebuilt binaries are available on the [Releases](https://github.com/TomWright/dasel/releases) page for Linux, macOS, and Windows.\n\n### None of the above?\n\nSee the [installation docs](https://daseldocs.tomwright.me/getting-started/installation) for more options.\n\n---\n\n## Basic Usage\n\n### Selecting Values\n\nBy default, Dasel evaluates the final selector and prints the result.\n\n```sh\necho '{\"foo\": {\"bar\": \"baz\"}}' | dasel -i json 'foo.bar'\n# Output: \"baz\"\n```\n\n### Modifying Values\n\nUpdate values inline:\n\n```sh\necho '{\"foo\": {\"bar\": \"baz\"}}' | dasel -i json 'foo.bar = \"bong\"'\n# Output: \"bong\"\n```\n\nUse `--root` to output the full document after modification:\n\n```sh\necho '{\"foo\": {\"bar\": \"baz\"}}' | dasel -i json --root 'foo.bar = \"bong\"'\n# Output:\n{\n  \"foo\": {\n    \"bar\": \"bong\"\n  }\n}\n```\n\nUpdate values based on previous value:\n\n```sh\necho '[1,2,3,4,5]' | dasel -i json --root 'each($this = $this*2)'\n# Output:\n[\n    2,\n    4,\n    6,\n    8,\n    10\n]\n```\n\n### Format Conversion\n\n```sh\ncat data.json | dasel -i json -o yaml\n```\n\n### Recursive Descent (`..`)\n\nSearches all nested objects and arrays for a matching key or index.\n\n```sh\necho '{\"foo\": {\"bar\": \"baz\"}}' | dasel -i json '..bar'\n# Output:\n[\n    \"baz\"\n]\n\n```\n\n### Search (`search`)\n\nFinds all values matching a condition anywhere in the structure.\n\n```sh\necho '{\"foo\": {\"bar\": \"baz\"}}' | dasel -i json 'search(bar == \"baz\")'\n# Output:\n[\n    {\n        \"bar\": \"baz\"\n    }\n]\n\n```\n\n---\n\n## Documentation\n\nFull documentation is available at [daseldocs.tomwright.me](https://daseldocs.tomwright.me).\n\n---\n\n## Contributing\n\nContributions are welcome! Please see the [CONTRIBUTING.md](./CONTRIBUTING.md) for details.\n\n---\n\n## License\n\nMIT License. See [LICENSE](./LICENSE) for details.\n\n## Stargazers over time\n\n[![Stargazers over time](https://starchart.cc/TomWright/dasel.svg)](https://starchart.cc/TomWright/dasel)\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\n## Supported Versions\n\nOnly the latest major version of Dasel is currently supported with security updates.\n\n| Version | Supported          |\n| ------- | ------------------ |\n| 3.x.x   | :white_check_mark: |\n| 2.x.x   | :x:                |\n| 1.x.x   | :x:                |\n\n## Reporting a Vulnerability\n\nIf you believe you have found a security vulnerability in Dasel, please report it privately and directly via one of the following:\n\n- [GitHub vulnerability submission](https://github.com/TomWright/dasel/security/advisories/new)\n- [contact@tomwright.me](mailto:contact@tomwright.me)\n\nPlease do **not** create a public GitHub issue for security vulnerabilities.  \nThis allows proper investigation and remediation before disclosure.\n\n### What to Expect\n\n- You will receive an acknowledgement of your report within **7 days**.\n- If the vulnerability is confirmed, we will work to release a fix as quickly as possible and will notify you once resolved.\n- If the report is declined, we will provide reasoning where possible.\n\n### Important Notes\n\nSecurity vulnerabilities are not the same as bugs, feature requests, or integration issues.  \nFor non-security bugs, please use the standard GitHub issue tracker:\n\n👉 https://github.com/TomWright/dasel/issues  \n\nThank you for helping keep Dasel and its users safe.\n"
  },
  {
    "path": "api.go",
    "content": "// Package dasel contains everything you'll need to use dasel from a go application.\npackage dasel\n\nimport (\n\t\"context\"\n\t\"github.com/tomwright/dasel/v3/execution\"\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\n// Query queries the data using the selector and returns the results.\nfunc Query(ctx context.Context, data any, selector string, opts ...execution.ExecuteOptionFn) ([]*model.Value, int, error) {\n\toptions := execution.NewOptions(opts...)\n\tval := model.NewValue(data)\n\tout, err := execution.ExecuteSelector(ctx, selector, val, options)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\tif out.IsBranch() || out.IsSpread() {\n\t\tres := make([]*model.Value, 0)\n\t\tif err := out.RangeSlice(func(i int, v *model.Value) error {\n\t\t\tres = append(res, v)\n\t\t\treturn nil\n\t\t}); err != nil {\n\t\t\treturn nil, 0, err\n\t\t}\n\t\treturn res, len(res), nil\n\t}\n\n\treturn []*model.Value{out}, 1, nil\n}\n\n// Select queries the data using the selector and returns the results as native Go types.\n// Ordering within maps is not guaranteed.\nfunc Select(ctx context.Context, data any, selector string, opts ...execution.ExecuteOptionFn) (any, int, error) {\n\tres, count, err := Query(ctx, data, selector, opts...)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\tout := make([]any, 0)\n\tfor _, v := range res {\n\t\tgoValue, err := v.GoValue()\n\t\tif err != nil {\n\t\t\treturn nil, 0, err\n\t\t}\n\t\tout = append(out, goValue)\n\t}\n\treturn out, count, err\n}\n\n// Modify runs the query against the given data and updates it in-place.\n// Given data must be a pointer to a mutable data structure.\nfunc Modify(ctx context.Context, data any, selector string, newValue any, opts ...execution.ExecuteOptionFn) (int, error) {\n\tres, count, err := Query(ctx, data, selector, opts...)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tfor _, v := range res {\n\t\tif err := v.Set(model.NewValue(newValue)); err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t}\n\treturn count, nil\n}\n"
  },
  {
    "path": "api_example_test.go",
    "content": "package dasel_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"github.com/tomwright/dasel/v3\"\n\t\"github.com/tomwright/dasel/v3/execution\"\n)\n\nfunc ExampleSelect() {\n\tmyData := map[string]any{\n\t\t\"users\": []map[string]any{\n\t\t\t{\"name\": \"Alice\", \"age\": 30},\n\t\t\t{\"name\": \"Bob\", \"age\": 25},\n\t\t\t{\"name\": \"Tom\", \"age\": 40},\n\t\t},\n\t}\n\tquery := `users.filter(age > 27).map(name)...`\n\tselectResult, numResults, err := dasel.Select(context.Background(), myData, query, execution.WithUnstable())\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Printf(\"Found %d results:\\n\", numResults)\n\n\t// You should validate the type assertion in real code.\n\tselectResults := selectResult.([]any)\n\n\t// Results can be of various types, handle accordingly.\n\tfor _, result := range selectResults {\n\t\tfmt.Println(result)\n\t}\n\n\t// Output:\n\t// Found 2 results:\n\t// Alice\n\t// Tom\n}\n"
  },
  {
    "path": "api_test.go",
    "content": "package dasel_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/tomwright/dasel/v3\"\n)\n\ntype modifyTestCase struct {\n\tselector string\n\tin       any\n\tvalue    any\n\texp      any\n\tcount    int\n}\n\nfunc (tc modifyTestCase) run(t *testing.T) {\n\tcount, err := dasel.Modify(t.Context(), &tc.in, tc.selector, tc.value)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif count != tc.count {\n\t\tt.Errorf(\"unexpected count: %d\", count)\n\t}\n\tif !cmp.Equal(tc.exp, tc.in) {\n\t\tt.Errorf(\"unexpected result: %s\", cmp.Diff(tc.exp, tc.in))\n\t}\n}\n\nfunc TestQuery(t *testing.T) {\n\tt.Run(\"basic query\", func(t *testing.T) {\n\t\tinputData := map[string]any{\n\t\t\t\"users\": []map[string]any{\n\t\t\t\t{\"name\": \"Alice\", \"age\": 30},\n\t\t\t\t{\"name\": \"Bob\", \"age\": 25},\n\t\t\t},\n\t\t}\n\t\tresults, count, err := dasel.Query(t.Context(), inputData, \"users.map(name)...\")\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t\tif count != 2 {\n\t\t\tt.Errorf(\"unexpected count: %d\", count)\n\t\t}\n\t\texp := []string{\"Alice\", \"Bob\"}\n\t\tfor i, r := range results {\n\t\t\tstrVal, err := r.StringValue()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif strVal != exp[i] {\n\t\t\t\tt.Errorf(\"unexpected result at index %d: %s\", i, strVal)\n\t\t\t}\n\t\t}\n\t})\n}\n\nfunc TestSelect(t *testing.T) {\n\tt.Run(\"basic select\", func(t *testing.T) {\n\t\tinputData := map[string]any{\n\t\t\t\"users\": []map[string]any{\n\t\t\t\t{\"name\": \"Alice\", \"age\": 30},\n\t\t\t\t{\"name\": \"Bob\", \"age\": 25},\n\t\t\t},\n\t\t}\n\t\tresult, count, err := dasel.Select(t.Context(), inputData, \"users.map(name)...\")\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t\tif count != 2 {\n\t\t\tt.Errorf(\"unexpected count: %d\", count)\n\t\t}\n\t\texp := []any{\"Alice\", \"Bob\"}\n\t\tif !cmp.Equal(exp, result) {\n\t\t\tt.Errorf(\"unexpected result: %s\", cmp.Diff(exp, result))\n\t\t}\n\t})\n}\n\nfunc TestModify(t *testing.T) {\n\tt.Run(\"index\", func(t *testing.T) {\n\t\tt.Run(\"int over int\", modifyTestCase{\n\t\t\tselector: \"$this[1]\",\n\t\t\tin:       []int{1, 2, 3},\n\t\t\tvalue:    4,\n\t\t\texp:      []int{1, 4, 3},\n\t\t\tcount:    1,\n\t\t}.run)\n\t\tt.Run(\"string over int\", modifyTestCase{\n\t\t\tselector: \"$this[1]\",\n\t\t\tin:       []any{1, 2, 3},\n\t\t\tvalue:    \"4\",\n\t\t\texp:      []any{1, \"4\", 3},\n\t\t\tcount:    1,\n\t\t}.run)\n\t})\n}\n"
  },
  {
    "path": "codecov.yaml",
    "content": "comment: no # do not comment PR with the result\n\ncoverage:\n  range: 50..90 # coverage lower than 50 is red, higher than 90 green, between color code\n\n  status:\n    project: # settings affecting project coverage\n      default:\n        target: auto # auto % coverage target\n        threshold: 5%  # allow for 5% reduction of coverage without failing\n\n    # do not run coverage on patch nor changes\n    patch: false"
  },
  {
    "path": "execution/README.md",
    "content": "# Execution\n\nThe execution package accepts a `model.Value`, parses a selector and executes the resulting AST on the value.\n"
  },
  {
    "path": "execution/context.go",
    "content": "package execution\n\nimport (\n\t\"context\"\n\t\"fmt\"\n)\n\ntype ctxKey string\n\nconst (\n\texecutorIDCtxKey    ctxKey = \"executorID\"\n\texecutorPathCtxKey  ctxKey = \"executorPath\"\n\texecutorDepthCtxKey ctxKey = \"executorDepth\"\n)\n\nfunc WithExecutorID(ctx context.Context, executorID string) context.Context {\n\tcurrentPath := ExecutorPath(ctx)\n\tnewPath := fmt.Sprintf(\"%s/%s\", currentPath, executorID)\n\tcurrentDepth := ExecutorDepth(ctx)\n\tnewDepth := currentDepth + 1\n\tctx = context.WithValue(ctx, executorIDCtxKey, executorID)\n\tctx = context.WithValue(ctx, executorPathCtxKey, newPath)\n\tctx = context.WithValue(ctx, executorDepthCtxKey, newDepth)\n\treturn ctx\n}\n\nfunc ExecutorID(ctx context.Context) string {\n\tv, ok := ctx.Value(executorIDCtxKey).(string)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn v\n}\n\nfunc ExecutorPath(ctx context.Context) string {\n\tv, ok := ctx.Value(executorPathCtxKey).(string)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn v\n}\n\nfunc ExecutorDepth(ctx context.Context) int {\n\tv, ok := ctx.Value(executorDepthCtxKey).(int)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn v\n}\n"
  },
  {
    "path": "execution/execute.go",
    "content": "package execution\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"github.com/tomwright/dasel/v3/selector\"\n\t\"github.com/tomwright/dasel/v3/selector/ast\"\n\t\"os\"\n\t\"reflect\"\n\t\"slices\"\n)\n\n// ExecuteSelector parses the selector and executes the resulting AST with the given input.\nfunc ExecuteSelector(ctx context.Context, selectorStr string, value *model.Value, opts *Options) (*model.Value, error) {\n\tif selectorStr == \"\" {\n\t\treturn value, nil\n\t}\n\n\texpr, err := selector.Parse(selectorStr)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error parsing selector: %w\", err)\n\t}\n\n\tres, err := ExecuteAST(ctx, expr, value, opts)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error executing selector: %w\", err)\n\t}\n\n\treturn res, nil\n}\n\ntype expressionExecutor func(ctx context.Context, options *Options, data *model.Value) (*model.Value, error)\n\n// ExecuteAST executes the given AST with the given input.\nfunc ExecuteAST(ctx context.Context, expr ast.Expr, value *model.Value, options *Options) (*model.Value, error) {\n\tif expr == nil {\n\t\treturn value, nil\n\t}\n\n\texecutorFn, err := exprExecutor(options, expr)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error evaluating expression %T: %w\", expr, err)\n\t}\n\n\texecutor := func(ctx context.Context, options *Options, data *model.Value) (*model.Value, error) {\n\t\toptions.Vars[\"this\"] = data\n\t\tout, err := executorFn(ctx, options, data)\n\t\tif err != nil {\n\t\t\treturn out, err\n\t\t}\n\t\treturn out, nil\n\t}\n\n\tif !value.IsBranch() {\n\t\tres, err := executor(ctx, options, value)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"execution error when processing %T: %w\", expr, err)\n\t\t}\n\t\treturn res, nil\n\t}\n\n\tres := model.NewSliceValue()\n\tres.MarkAsBranch()\n\n\tif err := value.RangeSlice(func(i int, v *model.Value) error {\n\t\tr, err := executor(ctx, options, v)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif r.IsIgnore() {\n\t\t\treturn nil\n\t\t}\n\t\treturn res.Append(r)\n\t}); err != nil {\n\t\treturn nil, fmt.Errorf(\"branch execution error when processing %T: %w\", expr, err)\n\t}\n\n\treturn res, nil\n}\n\nvar unstableAstTypes = []reflect.Type{\n\treflect.TypeFor[ast.BranchExpr](),\n}\n\nfunc exprExecutor(options *Options, expr ast.Expr) (expressionExecutor, error) {\n\tif !options.Unstable && (slices.Contains(unstableAstTypes, reflect.TypeOf(expr)) ||\n\t\tslices.Contains(unstableAstTypes, reflect.ValueOf(expr).Type())) {\n\t\treturn nil, errors.New(\"unstable ast types are not enabled. to enable them use --unstable\")\n\t}\n\n\tswitch e := expr.(type) {\n\tcase ast.BinaryExpr:\n\t\treturn binaryExprExecutor(e)\n\tcase ast.UnaryExpr:\n\t\treturn unaryExprExecutor(e)\n\tcase ast.CallExpr:\n\t\treturn callExprExecutor(options, e)\n\tcase ast.ChainedExpr:\n\t\treturn chainedExprExecutor(e)\n\tcase ast.SpreadExpr:\n\t\treturn spreadExprExecutor()\n\tcase ast.RangeExpr:\n\t\treturn rangeExprExecutor(e)\n\tcase ast.IndexExpr:\n\t\treturn indexExprExecutor(e)\n\tcase ast.PropertyExpr:\n\t\treturn propertyExprExecutor(e)\n\tcase ast.VariableExpr:\n\t\treturn variableExprExecutor(e)\n\tcase ast.NumberIntExpr:\n\t\treturn numberIntExprExecutor(e)\n\tcase ast.NumberFloatExpr:\n\t\treturn numberFloatExprExecutor(e)\n\tcase ast.StringExpr:\n\t\treturn stringExprExecutor(e)\n\tcase ast.BoolExpr:\n\t\treturn boolExprExecutor(e)\n\tcase ast.ObjectExpr:\n\t\treturn objectExprExecutor(e)\n\tcase ast.MapExpr:\n\t\treturn mapExprExecutor(e)\n\tcase ast.EachExpr:\n\t\treturn eachExprExecutor(e)\n\tcase ast.FilterExpr:\n\t\treturn filterExprExecutor(e)\n\tcase ast.SearchExpr:\n\t\treturn searchExprExecutor(e)\n\tcase ast.RecursiveDescentExpr:\n\t\treturn recursiveDescentExprExecutor2(e)\n\tcase ast.ConditionalExpr:\n\t\treturn conditionalExprExecutor(e)\n\tcase ast.BranchExpr:\n\t\treturn branchExprExecutor(e)\n\tcase ast.ArrayExpr:\n\t\treturn arrayExprExecutor(e)\n\tcase ast.RegexExpr:\n\t\t// Noop\n\t\treturn func(ctx context.Context, options *Options, data *model.Value) (*model.Value, error) {\n\t\t\t//ctx = WithExecutorID(ctx, \"regexExpr\")\n\t\t\treturn data, nil\n\t\t}, nil\n\tcase ast.SortByExpr:\n\t\treturn sortByExprExecutor(e)\n\tcase ast.NullExpr:\n\t\treturn func(ctx context.Context, options *Options, data *model.Value) (*model.Value, error) {\n\t\t\t//ctx = WithExecutorID(ctx, \"nullExpr\")\n\t\t\treturn model.NewNullValue(), nil\n\t\t}, nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unhandled expression type: %T\", e)\n\t}\n}\n\nfunc chainedExprExecutor(e ast.ChainedExpr) (expressionExecutor, error) {\n\treturn func(ctx context.Context, options *Options, data *model.Value) (*model.Value, error) {\n\t\tctx = WithExecutorID(ctx, \"chainedExpr\")\n\t\tvar curData = data\n\t\tfor _, expr := range e.Exprs {\n\t\t\tres, err := ExecuteAST(ctx, expr, curData, options)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"error executing expression: %w\", err)\n\t\t\t}\n\t\t\tcurData = res\n\t\t}\n\t\treturn curData, nil\n\t}, nil\n}\n\nfunc variableExprExecutor(e ast.VariableExpr) (expressionExecutor, error) {\n\treturn func(ctx context.Context, options *Options, data *model.Value) (*model.Value, error) {\n\t\t//ctx = WithExecutorID(ctx, \"variableExpr\")\n\t\tvarName := e.Name\n\t\tres, ok := options.Vars[varName]\n\t\tif ok {\n\t\t\treturn res, nil\n\t\t}\n\n\t\tenvVarValue := os.Getenv(varName)\n\t\tif envVarValue != \"\" {\n\t\t\treturn model.NewStringValue(envVarValue), nil\n\t\t}\n\n\t\treturn nil, fmt.Errorf(\"variable %s not found\", varName)\n\t}, nil\n}\n"
  },
  {
    "path": "execution/execute_array.go",
    "content": "package execution\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"github.com/tomwright/dasel/v3/selector/ast\"\n)\n\nfunc arrayExprExecutor(e ast.ArrayExpr) (expressionExecutor, error) {\n\treturn func(ctx context.Context, options *Options, data *model.Value) (*model.Value, error) {\n\t\tctx = WithExecutorID(ctx, \"arrayExpr\")\n\t\tres := model.NewSliceValue()\n\n\t\tfor _, expr := range e.Exprs {\n\t\t\tel, err := ExecuteAST(ctx, expr, data, options)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tif el.IsSpread() {\n\t\t\t\tif err := el.RangeSlice(func(_ int, value *model.Value) error {\n\t\t\t\t\tif err := res.Append(value); err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t}); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif err := res.Append(el); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn res, nil\n\t}, nil\n}\n\nfunc rangeExprExecutor(e ast.RangeExpr) (expressionExecutor, error) {\n\treturn func(ctx context.Context, options *Options, data *model.Value) (*model.Value, error) {\n\t\tctx = WithExecutorID(ctx, \"rangeExpr\")\n\t\tvar start, end int64 = 0, -1\n\t\tif e.Start != nil {\n\t\t\tstartE, err := ExecuteAST(ctx, e.Start, data, options)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"error evaluating start expression: %w\", err)\n\t\t\t}\n\n\t\t\tstart, err = startE.IntValue()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"error getting start int value: %w\", err)\n\t\t\t}\n\t\t}\n\n\t\tif e.End != nil {\n\t\t\tendE, err := ExecuteAST(ctx, e.End, data, options)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"error evaluating end expression: %w\", err)\n\t\t\t}\n\n\t\t\tend, err = endE.IntValue()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"error getting end int value: %w\", err)\n\t\t\t}\n\t\t}\n\n\t\tvar res *model.Value\n\t\tvar err error\n\n\t\tswitch data.Type() {\n\t\tcase model.TypeString:\n\t\t\tres, err = data.StringIndexRange(int(start), int(end))\n\t\tcase model.TypeSlice:\n\t\t\tres, err = data.SliceIndexRange(int(start), int(end))\n\t\tdefault:\n\t\t\terr = fmt.Errorf(\"range expects a slice or string, got %s\", data.Type())\n\t\t}\n\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\treturn res, nil\n\t}, nil\n}\n\nfunc indexExprExecutor(e ast.IndexExpr) (expressionExecutor, error) {\n\treturn func(ctx context.Context, options *Options, data *model.Value) (*model.Value, error) {\n\t\tctx = WithExecutorID(ctx, \"indexExpr\")\n\t\tindexE, err := ExecuteAST(ctx, e.Index, data, options)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error evaluating index expression: %w\", err)\n\t\t}\n\n\t\tindex, err := indexE.IntValue()\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error getting index int value: %w\", err)\n\t\t}\n\n\t\treturn data.GetSliceIndex(int(index))\n\t}, nil\n}\n"
  },
  {
    "path": "execution/execute_array_test.go",
    "content": "package execution_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\nfunc TestArray(t *testing.T) {\n\tinSlice := func() *model.Value {\n\t\ts := model.NewSliceValue()\n\t\tif err := s.Append(model.NewIntValue(1)); err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t}\n\t\tif err := s.Append(model.NewIntValue(2)); err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t}\n\t\tif err := s.Append(model.NewIntValue(3)); err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t}\n\t\treturn s\n\t}\n\tinMap := func() *model.Value {\n\t\tm := model.NewMapValue()\n\t\tif err := m.SetMapKey(\"numbers\", inSlice()); err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t}\n\t\treturn m\n\t}\n\n\trunArrayTests := func(in func() *model.Value, prefix string) func(t *testing.T) {\n\t\treturn func(t *testing.T) {\n\t\t\tt.Run(\"1:2\", testCase{\n\t\t\t\ts:    prefix + `[1:2]`,\n\t\t\t\tinFn: in,\n\t\t\t\toutFn: func() *model.Value {\n\t\t\t\t\tres := model.NewSliceValue()\n\t\t\t\t\tif err := res.Append(model.NewIntValue(2)); err != nil {\n\t\t\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t\t\t}\n\t\t\t\t\tif err := res.Append(model.NewIntValue(3)); err != nil {\n\t\t\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t\t\t}\n\t\t\t\t\treturn res\n\t\t\t\t},\n\t\t\t}.run)\n\t\t\tt.Run(\"1:0\", testCase{\n\t\t\t\ts:    prefix + `[1:0]`,\n\t\t\t\tinFn: in,\n\t\t\t\toutFn: func() *model.Value {\n\t\t\t\t\tres := model.NewSliceValue()\n\t\t\t\t\tif err := res.Append(model.NewIntValue(2)); err != nil {\n\t\t\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t\t\t}\n\t\t\t\t\tif err := res.Append(model.NewIntValue(1)); err != nil {\n\t\t\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t\t\t}\n\t\t\t\t\treturn res\n\t\t\t\t},\n\t\t\t}.run)\n\t\t\tt.Run(\"1:\", testCase{\n\t\t\t\ts:    prefix + `[1:]`,\n\t\t\t\tinFn: in,\n\t\t\t\toutFn: func() *model.Value {\n\t\t\t\t\tres := model.NewSliceValue()\n\t\t\t\t\tif err := res.Append(model.NewIntValue(2)); err != nil {\n\t\t\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t\t\t}\n\t\t\t\t\tif err := res.Append(model.NewIntValue(3)); err != nil {\n\t\t\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t\t\t}\n\t\t\t\t\treturn res\n\t\t\t\t},\n\t\t\t}.run)\n\t\t\tt.Run(\":1\", testCase{\n\t\t\t\ts:    prefix + `[:1]`,\n\t\t\t\tinFn: in,\n\t\t\t\toutFn: func() *model.Value {\n\t\t\t\t\tres := model.NewSliceValue()\n\t\t\t\t\tif err := res.Append(model.NewIntValue(1)); err != nil {\n\t\t\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t\t\t}\n\t\t\t\t\tif err := res.Append(model.NewIntValue(2)); err != nil {\n\t\t\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t\t\t}\n\t\t\t\t\treturn res\n\t\t\t\t},\n\t\t\t}.run)\n\t\t\tt.Run(\"reverse\", testCase{\n\t\t\t\ts:    prefix + `[len($this)-1:0]`,\n\t\t\t\tinFn: in,\n\t\t\t\toutFn: func() *model.Value {\n\t\t\t\t\tres := model.NewSliceValue()\n\t\t\t\t\tif err := res.Append(model.NewIntValue(3)); err != nil {\n\t\t\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t\t\t}\n\t\t\t\t\tif err := res.Append(model.NewIntValue(2)); err != nil {\n\t\t\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t\t\t}\n\t\t\t\t\tif err := res.Append(model.NewIntValue(1)); err != nil {\n\t\t\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t\t\t}\n\t\t\t\t\treturn res\n\t\t\t\t},\n\t\t\t}.run)\n\t\t}\n\t}\n\n\tt.Run(\"direct to slice\", runArrayTests(inSlice, \"$this\"))\n\tt.Run(\"property to slice\", runArrayTests(inMap, \"numbers\"))\n}\n"
  },
  {
    "path": "execution/execute_assign.go",
    "content": "package execution\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"github.com/tomwright/dasel/v3/selector/ast\"\n)\n\nfunc executeAssign(ctx context.Context, left *model.Value, right *model.Value, _ ast.BinaryExpr) (*model.Value, error) {\n\terr := left.Set(right)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error setting value: %w\", err)\n\t}\n\treturn right, nil\n}\n"
  },
  {
    "path": "execution/execute_assign_test.go",
    "content": "package execution_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/execution\"\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\nfunc TestAssignVariable(t *testing.T) {\n\tt.Run(\"single assign\", testCase{\n\t\ts: `$x=1`,\n\t\toutFn: func() *model.Value {\n\t\t\tr := model.NewIntValue(1)\n\t\t\treturn r\n\t\t},\n\t\topts: []execution.ExecuteOptionFn{\n\t\t\texecution.WithUnstable(),\n\t\t},\n\t}.run)\n\tt.Run(\"double assign\", testCase{\n\t\ts: `$x=1;$y=$x+1`,\n\t\toutFn: func() *model.Value {\n\t\t\tr := model.NewIntValue(2)\n\t\t\treturn r\n\t\t},\n\t\topts: []execution.ExecuteOptionFn{\n\t\t\texecution.WithUnstable(),\n\t\t},\n\t}.run)\n\tt.Run(\"multiple assign with final statement\", testCase{\n\t\ts: `$first = 'Tom';\n$last = 'Wright';\n$full = $first + ' ' + $last;\n{first: $first, last: $last, full: $full}`,\n\t\toutFn: func() *model.Value {\n\t\t\tr := model.NewMapValue()\n\t\t\tif err := r.SetMapKey(\"first\", model.NewStringValue(\"Tom\")); err != nil {\n\t\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t\t}\n\t\t\tif err := r.SetMapKey(\"last\", model.NewStringValue(\"Wright\")); err != nil {\n\t\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t\t}\n\t\t\tif err := r.SetMapKey(\"full\", model.NewStringValue(\"Tom Wright\")); err != nil {\n\t\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t\t}\n\t\t\treturn r\n\t\t},\n\t\topts: []execution.ExecuteOptionFn{\n\t\t\texecution.WithUnstable(),\n\t\t},\n\t}.run)\n\tt.Run(\"multiple assign with final statement and mixed case variables\", testCase{\n\t\ts: `$firstName = 'Tom';\n$lastName = 'Wright';\n$fullName = $firstName + ' ' + $lastName;\n{firstName: $firstName, lastName: $lastName, fullName: $fullName}`,\n\t\toutFn: func() *model.Value {\n\t\t\tr := model.NewMapValue()\n\t\t\tif err := r.SetMapKey(\"firstName\", model.NewStringValue(\"Tom\")); err != nil {\n\t\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t\t}\n\t\t\tif err := r.SetMapKey(\"lastName\", model.NewStringValue(\"Wright\")); err != nil {\n\t\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t\t}\n\t\t\tif err := r.SetMapKey(\"fullName\", model.NewStringValue(\"Tom Wright\")); err != nil {\n\t\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t\t}\n\t\t\treturn r\n\t\t},\n\t\topts: []execution.ExecuteOptionFn{\n\t\t\texecution.WithUnstable(),\n\t\t},\n\t}.run)\n\tt.Run(\"self referencing variable\", testCase{\n\t\ts: `$x=1;$x=$x*2`,\n\t\toutFn: func() *model.Value {\n\t\t\tr := model.NewIntValue(2)\n\t\t\treturn r\n\t\t},\n\t\topts: []execution.ExecuteOptionFn{\n\t\t\texecution.WithUnstable(),\n\t\t},\n\t}.run)\n}\n"
  },
  {
    "path": "execution/execute_binary.go",
    "content": "package execution\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"github.com/tomwright/dasel/v3/selector/ast\"\n\t\"github.com/tomwright/dasel/v3/selector/lexer\"\n)\n\ntype binaryExpressionExecutorFn func(ctx context.Context, expr ast.BinaryExpr, value *model.Value, options *Options) (*model.Value, error)\n\nfunc basicBinaryExpressionExecutorFn(handler func(ctx context.Context, left *model.Value, right *model.Value, e ast.BinaryExpr) (*model.Value, error)) binaryExpressionExecutorFn {\n\treturn func(ctx context.Context, expr ast.BinaryExpr, value *model.Value, options *Options) (*model.Value, error) {\n\t\tleft, err := ExecuteAST(ctx, expr.Left, value, options)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error evaluating left expression: %w\", err)\n\t\t}\n\n\t\tif !left.IsBranch() {\n\t\t\tright, err := ExecuteAST(ctx, expr.Right, value, options)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"error evaluating right expression: %w\", err)\n\t\t\t}\n\t\t\tres, err := handler(ctx, left, right, expr)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\treturn res, nil\n\t\t}\n\n\t\tres := model.NewSliceValue()\n\t\tres.MarkAsBranch()\n\t\tif err := left.RangeSlice(func(i int, v *model.Value) error {\n\t\t\tright, err := ExecuteAST(ctx, expr.Right, v, options)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"error evaluating right expression: %w\", err)\n\t\t\t}\n\t\t\tr, err := handler(ctx, v, right, expr)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := res.Append(r); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\treturn nil\n\t\t}); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn res, nil\n\t}\n}\n\nvar binaryExpressionExecutors = map[lexer.TokenKind]binaryExpressionExecutorFn{}\n\nfunc binaryExprExecutor(e ast.BinaryExpr) (expressionExecutor, error) {\n\treturn func(ctx context.Context, options *Options, data *model.Value) (*model.Value, error) {\n\t\tctx = WithExecutorID(ctx, \"binaryExpr\")\n\t\tif e.Left == nil || e.Right == nil {\n\t\t\treturn nil, fmt.Errorf(\"left and right expressions must be provided\")\n\t\t}\n\n\t\texec, ok := binaryExpressionExecutors[e.Operator.Kind]\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"unhandled operator: %s\", e.Operator.Value)\n\t\t}\n\n\t\treturn exec(ctx, e, data, options)\n\t}, nil\n}\n\nfunc init() {\n\tbinaryExpressionExecutors[lexer.Plus] = basicBinaryExpressionExecutorFn(func(ctx context.Context, left *model.Value, right *model.Value, _ ast.BinaryExpr) (*model.Value, error) {\n\t\treturn left.Add(right)\n\t})\n\tbinaryExpressionExecutors[lexer.Dash] = basicBinaryExpressionExecutorFn(func(ctx context.Context, left *model.Value, right *model.Value, _ ast.BinaryExpr) (*model.Value, error) {\n\t\treturn left.Subtract(right)\n\t})\n\tbinaryExpressionExecutors[lexer.Star] = basicBinaryExpressionExecutorFn(func(ctx context.Context, left *model.Value, right *model.Value, _ ast.BinaryExpr) (*model.Value, error) {\n\t\treturn left.Multiply(right)\n\t})\n\tbinaryExpressionExecutors[lexer.Slash] = basicBinaryExpressionExecutorFn(func(ctx context.Context, left *model.Value, right *model.Value, _ ast.BinaryExpr) (*model.Value, error) {\n\t\treturn left.Divide(right)\n\t})\n\tbinaryExpressionExecutors[lexer.Percent] = basicBinaryExpressionExecutorFn(func(ctx context.Context, left *model.Value, right *model.Value, _ ast.BinaryExpr) (*model.Value, error) {\n\t\treturn left.Modulo(right)\n\t})\n\tbinaryExpressionExecutors[lexer.GreaterThan] = basicBinaryExpressionExecutorFn(func(ctx context.Context, left *model.Value, right *model.Value, _ ast.BinaryExpr) (*model.Value, error) {\n\t\treturn left.GreaterThan(right)\n\t})\n\tbinaryExpressionExecutors[lexer.GreaterThanOrEqual] = basicBinaryExpressionExecutorFn(func(ctx context.Context, left *model.Value, right *model.Value, _ ast.BinaryExpr) (*model.Value, error) {\n\t\treturn left.GreaterThanOrEqual(right)\n\t})\n\tbinaryExpressionExecutors[lexer.LessThan] = basicBinaryExpressionExecutorFn(func(ctx context.Context, left *model.Value, right *model.Value, _ ast.BinaryExpr) (*model.Value, error) {\n\t\treturn left.LessThan(right)\n\t})\n\tbinaryExpressionExecutors[lexer.LessThanOrEqual] = basicBinaryExpressionExecutorFn(func(ctx context.Context, left *model.Value, right *model.Value, _ ast.BinaryExpr) (*model.Value, error) {\n\t\treturn left.LessThanOrEqual(right)\n\t})\n\tbinaryExpressionExecutors[lexer.Equal] = basicBinaryExpressionExecutorFn(func(ctx context.Context, left *model.Value, right *model.Value, _ ast.BinaryExpr) (*model.Value, error) {\n\t\treturn left.Equal(right)\n\t})\n\tbinaryExpressionExecutors[lexer.NotEqual] = basicBinaryExpressionExecutorFn(func(ctx context.Context, left *model.Value, right *model.Value, _ ast.BinaryExpr) (*model.Value, error) {\n\t\treturn left.NotEqual(right)\n\t})\n\tbinaryExpressionExecutors[lexer.Equals] = func(ctx context.Context, expr ast.BinaryExpr, value *model.Value, options *Options) (*model.Value, error) {\n\t\tif leftVar, ok := expr.Left.(ast.VariableExpr); ok {\n\t\t\t// It is expected that the left side of an assignment may not exist yet.\n\t\t\tif _, ok := options.Vars[leftVar.Name]; !ok {\n\t\t\t\toptions.Vars[leftVar.Name] = model.NewNullValue()\n\t\t\t}\n\t\t}\n\t\treturn basicBinaryExpressionExecutorFn(executeAssign)(ctx, expr, value, options)\n\t}\n\tbinaryExpressionExecutors[lexer.And] = basicBinaryExpressionExecutorFn(func(ctx context.Context, left *model.Value, right *model.Value, _ ast.BinaryExpr) (*model.Value, error) {\n\t\tleftBool, err := left.BoolValue()\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error getting left bool value: %w\", err)\n\t\t}\n\t\trightBool, err := right.BoolValue()\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error getting right bool value: %w\", err)\n\t\t}\n\t\treturn model.NewBoolValue(leftBool && rightBool), nil\n\t})\n\tbinaryExpressionExecutors[lexer.Or] = basicBinaryExpressionExecutorFn(func(ctx context.Context, left *model.Value, right *model.Value, _ ast.BinaryExpr) (*model.Value, error) {\n\t\tleftBool, err := left.BoolValue()\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error getting left bool value: %w\", err)\n\t\t}\n\t\trightBool, err := right.BoolValue()\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error getting right bool value: %w\", err)\n\t\t}\n\t\treturn model.NewBoolValue(leftBool || rightBool), nil\n\t})\n\tbinaryExpressionExecutors[lexer.Like] = basicBinaryExpressionExecutorFn(func(ctx context.Context, left *model.Value, _ *model.Value, e ast.BinaryExpr) (*model.Value, error) {\n\t\tleftStr, err := left.StringValue()\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"like requires left side to be a string, got %s\", left.Type().String())\n\t\t}\n\t\trightPatt, ok := e.Right.(ast.RegexExpr)\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"like requires right side to be a regex pattern\")\n\t\t}\n\t\tres := rightPatt.Regex.MatchString(leftStr)\n\t\treturn model.NewBoolValue(res), nil\n\t})\n\tbinaryExpressionExecutors[lexer.NotLike] = basicBinaryExpressionExecutorFn(func(ctx context.Context, left *model.Value, _ *model.Value, e ast.BinaryExpr) (*model.Value, error) {\n\t\tleftStr, err := left.StringValue()\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"like requires left side to be a string, got %s\", left.Type().String())\n\t\t}\n\t\trightPatt, ok := e.Right.(ast.RegexExpr)\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"like requires right side to be a regex pattern\")\n\t\t}\n\t\tres := rightPatt.Regex.MatchString(leftStr)\n\t\treturn model.NewBoolValue(!res), nil\n\t})\n\tbinaryExpressionExecutors[lexer.DoubleQuestionMark] = func(ctx context.Context, expr ast.BinaryExpr, value *model.Value, options *Options) (*model.Value, error) {\n\t\tleft, err := ExecuteAST(ctx, expr.Left, value, options)\n\n\t\tif err == nil && !left.IsNull() {\n\t\t\treturn left, nil\n\t\t}\n\n\t\tif err != nil {\n\t\t\thandleErrs := []any{\n\t\t\t\tmodel.ErrIncompatibleTypes{},\n\t\t\t\tmodel.ErrUnexpectedType{},\n\t\t\t\tmodel.ErrUnexpectedTypes{},\n\t\t\t\tmodel.SliceIndexOutOfRange{},\n\t\t\t\tmodel.MapKeyNotFound{},\n\t\t\t}\n\t\t\tfor _, e := range handleErrs {\n\t\t\t\tif errors.As(err, &e) {\n\t\t\t\t\terr = nil\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"error evaluating left expression: %w\", err)\n\t\t\t}\n\t\t}\n\n\t\t// Do we need to handle branches here?\n\t\tright, err := ExecuteAST(ctx, expr.Right, value, options)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error evaluating right expression: %w\", err)\n\t\t}\n\t\treturn right, nil\n\t}\n}\n"
  },
  {
    "path": "execution/execute_binary_test.go",
    "content": "package execution_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"github.com/tomwright/dasel/v3/model/orderedmap\"\n)\n\nfunc TestBinary(t *testing.T) {\n\tt.Run(\"math\", func(t *testing.T) {\n\t\tt.Run(\"literals\", func(t *testing.T) {\n\t\t\tt.Run(\"addition\", testCase{\n\t\t\t\ts:   `1 + 2`,\n\t\t\t\tout: model.NewIntValue(3),\n\t\t\t}.run)\n\t\t\tt.Run(\"subtraction\", testCase{\n\t\t\t\ts:   `5 - 2`,\n\t\t\t\tout: model.NewIntValue(3),\n\t\t\t}.run)\n\t\t\tt.Run(\"multiplication\", testCase{\n\t\t\t\ts:   `5 * 2`,\n\t\t\t\tout: model.NewIntValue(10),\n\t\t\t}.run)\n\t\t\tt.Run(\"division\", testCase{\n\t\t\t\ts:   `10 / 2`,\n\t\t\t\tout: model.NewIntValue(5),\n\t\t\t}.run)\n\t\t\tt.Run(\"modulus\", testCase{\n\t\t\t\ts:   `10 % 3`,\n\t\t\t\tout: model.NewIntValue(1),\n\t\t\t}.run)\n\t\t\tt.Run(\"ordering\", testCase{\n\t\t\t\ts:   `45.2 + 5 * 4 - 2 / 2`, // 45.2 + (5 * 4) - (2 / 2) = 45.2 + 20 - 1 = 64.2\n\t\t\t\tout: model.NewFloatValue(64.2),\n\t\t\t}.run)\n\t\t\tt.Run(\"ordering with groups\", testCase{\n\t\t\t\ts:   `(45.2 + 5) * ((4 - 2) / 2)`, // (45.2 + 5) * ((4 - 2) / 2) = (50.2) * ((2) / 2) = (50.2) * (1) = 50.2\n\t\t\t\tout: model.NewFloatValue(50.2),\n\t\t\t}.run)\n\t\t\tt.Run(\"ordering with groups\", testCase{\n\t\t\t\ts:   `1 + 1 - 1 + 1 * 2`, // 1 + 1 - 1 + (1 * 2) = 1 + 1 - 1 + 2 = 3\n\t\t\t\tout: model.NewIntValue(3),\n\t\t\t}.run)\n\t\t})\n\t\tt.Run(\"variables\", func(t *testing.T) {\n\t\t\tin := func() *model.Value {\n\t\t\t\treturn model.NewValue(orderedmap.NewMap().\n\t\t\t\t\tSet(\"one\", 1).\n\t\t\t\t\tSet(\"two\", 2).\n\t\t\t\t\tSet(\"three\", 3).\n\t\t\t\t\tSet(\"four\", 4).\n\t\t\t\t\tSet(\"five\", 5).\n\t\t\t\t\tSet(\"six\", 6).\n\t\t\t\t\tSet(\"seven\", 7).\n\t\t\t\t\tSet(\"eight\", 8).\n\t\t\t\t\tSet(\"nine\", 9).\n\t\t\t\t\tSet(\"ten\", 10).\n\t\t\t\t\tSet(\"fortyfivepoint2\", 45.2))\n\t\t\t}\n\t\t\tt.Run(\"addition\", testCase{\n\t\t\t\tinFn: in,\n\t\t\t\ts:    `one + two`,\n\t\t\t\tout:  model.NewIntValue(3),\n\t\t\t}.run)\n\t\t\tt.Run(\"subtraction\", testCase{\n\t\t\t\tinFn: in,\n\t\t\t\ts:    `five - two`,\n\t\t\t\tout:  model.NewIntValue(3),\n\t\t\t}.run)\n\t\t\tt.Run(\"multiplication\", testCase{\n\t\t\t\tinFn: in,\n\t\t\t\ts:    `five * two`,\n\t\t\t\tout:  model.NewIntValue(10),\n\t\t\t}.run)\n\t\t\tt.Run(\"division\", testCase{\n\t\t\t\tinFn: in,\n\t\t\t\ts:    `ten / two`,\n\t\t\t\tout:  model.NewIntValue(5),\n\t\t\t}.run)\n\t\t\tt.Run(\"modulus\", testCase{\n\t\t\t\tinFn: in,\n\t\t\t\ts:    `ten % three`,\n\t\t\t\tout:  model.NewIntValue(1),\n\t\t\t}.run)\n\t\t\tt.Run(\"ordering\", testCase{\n\t\t\t\tinFn: in,\n\t\t\t\ts:    `fortyfivepoint2 + five * four - two / two`, // 45.2 + (5 * 4) - (2 / 2) = 45.2 + 20 - 1 = 64.2\n\t\t\t\tout:  model.NewFloatValue(64.2),\n\t\t\t}.run)\n\t\t\tt.Run(\"ordering with groups\", testCase{\n\t\t\t\tinFn: in,\n\t\t\t\ts:    `(fortyfivepoint2 + five) * ((four - two) / two)`, // (45.2 + 5) * ((4 - 2) / 2) = (50.2) * ((2) / 2) = (50.2) * (1) = 50.2\n\t\t\t\tout:  model.NewFloatValue(50.2),\n\t\t\t}.run)\n\t\t})\n\t})\n\tt.Run(\"comparison\", func(t *testing.T) {\n\t\tt.Run(\"literals\", func(t *testing.T) {\n\t\t\tt.Run(\"equal\", testCase{\n\t\t\t\ts:   `1 == 1`,\n\t\t\t\tout: model.NewBoolValue(true),\n\t\t\t}.run)\n\t\t\tt.Run(\"not equal\", testCase{\n\t\t\t\ts:   `1 != 1`,\n\t\t\t\tout: model.NewBoolValue(false),\n\t\t\t}.run)\n\t\t\tt.Run(\"greater than\", testCase{\n\t\t\t\ts:   `2 > 1`,\n\t\t\t\tout: model.NewBoolValue(true),\n\t\t\t}.run)\n\t\t\tt.Run(\"greater than or equal\", testCase{\n\t\t\t\ts:   `2 >= 2`,\n\t\t\t\tout: model.NewBoolValue(true),\n\t\t\t}.run)\n\t\t\tt.Run(\"less than\", testCase{\n\t\t\t\ts:   `1 < 2`,\n\t\t\t\tout: model.NewBoolValue(true),\n\t\t\t}.run)\n\t\t\tt.Run(\"less than or equal\", testCase{\n\t\t\t\ts:   `2 <= 2`,\n\t\t\t\tout: model.NewBoolValue(true),\n\t\t\t}.run)\n\t\t\tt.Run(\"like\", testCase{\n\t\t\t\ts:   `\"hello world\" =~ r/ello/`,\n\t\t\t\tout: model.NewBoolValue(true),\n\t\t\t}.run)\n\t\t\tt.Run(\"not like\", testCase{\n\t\t\t\ts:   `\"hello world\" !~ r/helloworld/`,\n\t\t\t\tout: model.NewBoolValue(true),\n\t\t\t}.run)\n\t\t})\n\n\t\tt.Run(\"variables\", func(t *testing.T) {\n\t\t\tin := func() *model.Value {\n\t\t\t\treturn model.NewValue(orderedmap.NewMap().\n\t\t\t\t\tSet(\"one\", 1).\n\t\t\t\t\tSet(\"two\", 2).\n\t\t\t\t\tSet(\"nested\", orderedmap.NewMap().\n\t\t\t\t\t\tSet(\"three\", 3).\n\t\t\t\t\t\tSet(\"four\", 4)))\n\t\t\t}\n\t\t\tt.Run(\"equal\", testCase{\n\t\t\t\tinFn: in,\n\t\t\t\ts:    `one == one`,\n\t\t\t\tout:  model.NewBoolValue(true),\n\t\t\t}.run)\n\t\t\tt.Run(\"not equal\", testCase{\n\t\t\t\tinFn: in,\n\t\t\t\ts:    `one != one`,\n\t\t\t\tout:  model.NewBoolValue(false),\n\t\t\t}.run)\n\t\t\tt.Run(\"greater than\", testCase{\n\t\t\t\tinFn: in,\n\t\t\t\ts:    `two > one`,\n\t\t\t\tout:  model.NewBoolValue(true),\n\t\t\t}.run)\n\t\t\tt.Run(\"greater than or equal\", testCase{\n\t\t\t\tinFn: in,\n\t\t\t\ts:    `two >= two`,\n\t\t\t\tout:  model.NewBoolValue(true),\n\t\t\t}.run)\n\t\t\tt.Run(\"less than\", testCase{\n\t\t\t\tinFn: in,\n\t\t\t\ts:    `one < two`,\n\t\t\t\tout:  model.NewBoolValue(true),\n\t\t\t}.run)\n\t\t\tt.Run(\"less than or equal\", testCase{\n\t\t\t\tinFn: in,\n\t\t\t\ts:    `two <= two`,\n\t\t\t\tout:  model.NewBoolValue(true),\n\t\t\t}.run)\n\t\t\tt.Run(\"nested with math more than\", testCase{\n\t\t\t\tinFn: in,\n\t\t\t\ts:    `nested.three + nested.four * 0 > one * 1`,\n\t\t\t\tout:  model.NewBoolValue(true),\n\t\t\t}.run)\n\t\t\tt.Run(\"nested with grouped math more than\", testCase{\n\t\t\t\tinFn: in,\n\t\t\t\ts:    `(nested.three + nested.four) * 0 > one * 1`,\n\t\t\t\tout:  model.NewBoolValue(false),\n\t\t\t}.run)\n\t\t})\n\n\t\tt.Run(\"coalesce\", func(t *testing.T) {\n\t\t\tt.Run(\"literals\", func(t *testing.T) {\n\t\t\t\tt.Run(\"coalesce\", testCase{\n\t\t\t\t\ts:   `null ?? 1`,\n\t\t\t\t\tout: model.NewIntValue(1),\n\t\t\t\t}.run)\n\t\t\t\tt.Run(\"coalesce with null\", testCase{\n\t\t\t\t\ts:   `null ?? null`,\n\t\t\t\t\tout: model.NewNullValue(),\n\t\t\t\t}.run)\n\t\t\t\tt.Run(\"coalesce with null and value\", testCase{\n\t\t\t\t\ts:   `null ?? 2`,\n\t\t\t\t\tout: model.NewIntValue(2),\n\t\t\t\t}.run)\n\t\t\t\tt.Run(\"coalesce with value\", testCase{\n\t\t\t\t\ts:   `1 ?? 2`,\n\t\t\t\t\tout: model.NewIntValue(1),\n\t\t\t\t}.run)\n\t\t\t})\n\t\t\tt.Run(\"variables\", func(t *testing.T) {\n\t\t\t\tin := func() *model.Value {\n\t\t\t\t\treturn model.NewValue(orderedmap.NewMap().\n\t\t\t\t\t\tSet(\"one\", 1).\n\t\t\t\t\t\tSet(\"two\", 2).\n\t\t\t\t\t\tSet(\"nested\", orderedmap.NewMap().\n\t\t\t\t\t\t\tSet(\"one\", 1).\n\t\t\t\t\t\t\tSet(\"two\", 2).\n\t\t\t\t\t\t\tSet(\"three\", 3).\n\t\t\t\t\t\t\tSet(\"four\", 4)).\n\t\t\t\t\t\tSet(\"list\", []any{1, 2, 3}))\n\t\t\t\t}\n\t\t\t\tt.Run(\"coalesce\", testCase{\n\t\t\t\t\tinFn: in,\n\t\t\t\t\ts:    `nested.five ?? one`,\n\t\t\t\t\tout:  model.NewIntValue(1),\n\t\t\t\t}.run)\n\t\t\t\tt.Run(\"coalesce with null\", testCase{\n\t\t\t\t\tinFn: in,\n\t\t\t\t\ts:    `nested.five ?? null`,\n\t\t\t\t\tout:  model.NewNullValue(),\n\t\t\t\t}.run)\n\t\t\t\tt.Run(\"coalesce with null and value\", testCase{\n\t\t\t\t\tinFn: in,\n\t\t\t\t\ts:    `nested.five ?? 2`,\n\t\t\t\t\tout:  model.NewIntValue(2),\n\t\t\t\t}.run)\n\t\t\t\tt.Run(\"coalesce with value\", testCase{\n\t\t\t\t\tinFn: in,\n\t\t\t\t\ts:    `nested.three ?? 2`,\n\t\t\t\t\tout:  model.NewIntValue(3),\n\t\t\t\t}.run)\n\t\t\t\tt.Run(\"coalesce with bad map key\", testCase{\n\t\t\t\t\tinFn: in,\n\t\t\t\t\ts:    `nope ?? 2`,\n\t\t\t\t\tout:  model.NewIntValue(2),\n\t\t\t\t}.run)\n\t\t\t\tt.Run(\"coalesce with nested bad map key\", testCase{\n\t\t\t\t\tinFn: in,\n\t\t\t\t\ts:    `nested.nope ?? 2`,\n\t\t\t\t\tout:  model.NewIntValue(2),\n\t\t\t\t}.run)\n\t\t\t\tt.Run(\"coalesce with list index\", testCase{\n\t\t\t\t\tinFn: in,\n\t\t\t\t\ts:    `list[1] ?? 5`,\n\t\t\t\t\tout:  model.NewIntValue(2),\n\t\t\t\t}.run)\n\t\t\t\tt.Run(\"coalesce with list bad index\", testCase{\n\t\t\t\t\tinFn: in,\n\t\t\t\t\ts:    `list[3] ?? 5`,\n\t\t\t\t\tout:  model.NewIntValue(5),\n\t\t\t\t}.run)\n\t\t\t\tt.Run(\"chained coalesce execute left to right\", func(t *testing.T) {\n\t\t\t\t\t// These tests ensure the coalesces run in order.\n\t\t\t\t\tt.Run(\"no match\", testCase{\n\t\t\t\t\t\tinFn: in,\n\t\t\t\t\t\ts:    `nested.five ?? nested.six ?? nested.seven ?? 10`,\n\t\t\t\t\t\tout:  model.NewIntValue(10),\n\t\t\t\t\t}.run)\n\t\t\t\t\tt.Run(\"first match when all exist\", testCase{\n\t\t\t\t\t\tinFn: in,\n\t\t\t\t\t\ts:    `nested.one ?? nested.two ?? nested.three ?? 10`,\n\t\t\t\t\t\tout:  model.NewIntValue(1),\n\t\t\t\t\t}.run)\n\t\t\t\t\tt.Run(\"second match\", testCase{\n\t\t\t\t\t\tinFn: in,\n\t\t\t\t\t\ts:    `nested.five ?? nested.two ?? nested.three ?? 10`,\n\t\t\t\t\t\tout:  model.NewIntValue(2),\n\t\t\t\t\t}.run)\n\t\t\t\t\tt.Run(\"third match\", testCase{\n\t\t\t\t\t\tinFn: in,\n\t\t\t\t\t\ts:    `nested.five ?? nested.six ?? nested.three ?? 10`,\n\t\t\t\t\t\tout:  model.NewIntValue(3),\n\t\t\t\t\t}.run)\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\t})\n}\n"
  },
  {
    "path": "execution/execute_branch.go",
    "content": "package execution\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"github.com/tomwright/dasel/v3/selector/ast\"\n)\n\nfunc branchExprExecutor(e ast.BranchExpr) (expressionExecutor, error) {\n\treturn func(ctx context.Context, options *Options, data *model.Value) (*model.Value, error) {\n\t\tctx = WithExecutorID(ctx, \"branchExpr\")\n\t\tres := model.NewSliceValue()\n\t\tres.MarkAsBranch()\n\n\t\tif len(e.Exprs) == 0 {\n\t\t\t// No expressions given. We'll branch on the input data.\n\t\t\tif err := data.RangeSlice(func(_ int, value *model.Value) error {\n\t\t\t\tif err := res.Append(value); err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"failed to append branch result: %w\", err)\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t}); err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"failed to range slice: %w\", err)\n\t\t\t}\n\t\t} else {\n\t\t\tfor _, expr := range e.Exprs {\n\t\t\t\tr, err := ExecuteAST(ctx, expr, data, options)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, fmt.Errorf(\"failed to execute branch expr: %w\", err)\n\t\t\t\t}\n\n\t\t\t\t// This deals with the spread operator in the branch expression.\n\t\t\t\tvalsToAppend, err := prepareSpreadValues(r)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, fmt.Errorf(\"error handling spread values: %w\", err)\n\t\t\t\t}\n\t\t\t\tfor _, v := range valsToAppend {\n\t\t\t\t\tif err := res.Append(v); err != nil {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"failed to append branch result: %w\", err)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn res, nil\n\t}, nil\n}\n"
  },
  {
    "path": "execution/execute_branch_test.go",
    "content": "package execution_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/execution\"\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\nfunc TestBranch(t *testing.T) {\n\tt.Run(\"single branch\", testCase{\n\t\ts: \"branch(1)\",\n\t\toutFn: func() *model.Value {\n\t\t\tr := model.NewSliceValue()\n\t\t\tr.MarkAsBranch()\n\t\t\tif err := r.Append(model.NewIntValue(1)); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\treturn r\n\t\t},\n\t\topts: []execution.ExecuteOptionFn{\n\t\t\texecution.WithUnstable(),\n\t\t},\n\t}.run)\n\tt.Run(\"many branches\", testCase{\n\t\ts: \"branch(1, 1+1, 3/1, 123)\",\n\t\toutFn: func() *model.Value {\n\t\t\tr := model.NewSliceValue()\n\t\t\tr.MarkAsBranch()\n\t\t\tif err := r.Append(model.NewIntValue(1)); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif err := r.Append(model.NewIntValue(2)); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif err := r.Append(model.NewIntValue(3)); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif err := r.Append(model.NewIntValue(123)); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\treturn r\n\t\t},\n\t\topts: []execution.ExecuteOptionFn{\n\t\t\texecution.WithUnstable(),\n\t\t},\n\t}.run)\n\tt.Run(\"spread into many branches\", testCase{\n\t\ts: \"[1,2,3].branch(...)\",\n\t\toutFn: func() *model.Value {\n\t\t\tr := model.NewSliceValue()\n\t\t\tr.MarkAsBranch()\n\t\t\tif err := r.Append(model.NewIntValue(1)); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif err := r.Append(model.NewIntValue(2)); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif err := r.Append(model.NewIntValue(3)); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\treturn r\n\t\t},\n\t\topts: []execution.ExecuteOptionFn{\n\t\t\texecution.WithUnstable(),\n\t\t},\n\t}.run)\n\t//t.Run(\"chained branch set\", testCase{\n\t//\ts: \"branch(1, 2, 3).=5\",\n\t//\toutFn: func() *model.Value {\n\t//\t\tr := model.NewSliceValue()\n\t//\t\tr.MarkAsBranch()\n\t//\t\tif err := r.Append(model.NewIntValue(5)); err != nil {\n\t//\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t//\t\t}\n\t//\t\tif err := r.Append(model.NewIntValue(5)); err != nil {\n\t//\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t//\t\t}\n\t//\t\tif err := r.Append(model.NewIntValue(5)); err != nil {\n\t//\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t//\t\t}\n\t//\t\treturn r\n\t//\t},\n\t//\topts: []execution.ExecuteOptionFn{\n\t//\t\texecution.WithUnstable(),\n\t//\t},\n\t//}.run)\n\tt.Run(\"chained branch math\", testCase{\n\t\ts: \"(branch(1, 2, 3)) * 2\",\n\t\toutFn: func() *model.Value {\n\t\t\tr := model.NewSliceValue()\n\t\t\tr.MarkAsBranch()\n\t\t\tif err := r.Append(model.NewIntValue(2)); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif err := r.Append(model.NewIntValue(4)); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif err := r.Append(model.NewIntValue(6)); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\treturn r\n\t\t},\n\t\topts: []execution.ExecuteOptionFn{\n\t\t\texecution.WithUnstable(),\n\t\t},\n\t}.run)\n\tt.Run(\"chained branch math using branched value\", testCase{\n\t\ts: `branch({\"x\":1}, {\"x\":2}, {\"x\":3}).x * $this`,\n\t\toutFn: func() *model.Value {\n\t\t\tr := model.NewSliceValue()\n\t\t\tr.MarkAsBranch()\n\t\t\tif err := r.Append(model.NewIntValue(1)); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif err := r.Append(model.NewIntValue(4)); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif err := r.Append(model.NewIntValue(9)); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\treturn r\n\t\t},\n\t\topts: []execution.ExecuteOptionFn{\n\t\t\texecution.WithUnstable(),\n\t\t},\n\t}.run)\n\tt.Run(\"map on branch\", testCase{\n\t\ts: `branch([1], [2], [3]).map($this * 2).branch()`,\n\t\toutFn: func() *model.Value {\n\t\t\tr := model.NewSliceValue()\n\t\t\tr.MarkAsBranch()\n\t\t\tif err := r.Append(model.NewIntValue(2)); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif err := r.Append(model.NewIntValue(4)); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif err := r.Append(model.NewIntValue(6)); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\treturn r\n\t\t},\n\t\topts: []execution.ExecuteOptionFn{\n\t\t\texecution.WithUnstable(),\n\t\t},\n\t}.run)\n}\n"
  },
  {
    "path": "execution/execute_conditional.go",
    "content": "package execution\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"github.com/tomwright/dasel/v3/selector/ast\"\n)\n\nfunc conditionalExprExecutor(e ast.ConditionalExpr) (expressionExecutor, error) {\n\treturn func(ctx context.Context, options *Options, data *model.Value) (*model.Value, error) {\n\t\tctx = WithExecutorID(ctx, \"conditionalExpr\")\n\t\tcond, err := ExecuteAST(ctx, e.Cond, data, options)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error evaluating condition: %w\", err)\n\t\t}\n\n\t\tcondBool, err := cond.BoolValue()\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error converting condition to boolean: %w\", err)\n\t\t}\n\n\t\tif condBool {\n\t\t\tres, err := ExecuteAST(ctx, e.Then, data, options)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"error executing then block: %w\", err)\n\t\t\t}\n\t\t\treturn res, nil\n\t\t}\n\n\t\tif e.Else != nil {\n\t\t\tres, err := ExecuteAST(ctx, e.Else, data, options)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"error executing else block: %w\", err)\n\t\t\t}\n\t\t\treturn res, nil\n\t\t}\n\n\t\treturn model.NewNullValue(), nil\n\t}, nil\n}\n"
  },
  {
    "path": "execution/execute_conditional_test.go",
    "content": "package execution_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\nfunc TestConditional(t *testing.T) {\n\tt.Run(\"true\", testCase{\n\t\ts:   `if (true) { \"yes\" } else { \"no\" }`,\n\t\tout: model.NewStringValue(\"yes\"),\n\t}.run)\n\tt.Run(\"false\", testCase{\n\t\ts:   `if (false) { \"yes\" } else { \"no\" }`,\n\t\tout: model.NewStringValue(\"no\"),\n\t}.run)\n\tt.Run(\"nested\", testCase{\n\t\ts: `\n\t\t\t\tif (true) {\n\t\t\t\t\tif (true) { \"yes\" }\n\t\t\t\t\telse { \"no\" }\n\t\t\t\t} else { \"no\" }`,\n\t\tout: model.NewStringValue(\"yes\"),\n\t}.run)\n\tt.Run(\"nested false\", testCase{\n\t\ts: `\n\t\t\t\tif (true) {\n\t\t\t\t\tif (false) { \"yes\" }\n\t\t\t\t\telse { \"no\" }\n\t\t\t\t} else { \"no\" }`,\n\t\tout: model.NewStringValue(\"no\"),\n\t}.run)\n\tt.Run(\"else if\", testCase{\n\t\ts: `\n\t\t\t\tif (false) { \"yes\" }\n\t\t\t\telseif (true) { \"no\" }\n\t\t\t\telse { \"maybe\" }`,\n\t\tout: model.NewStringValue(\"no\"),\n\t}.run)\n\tt.Run(\"else if else\", testCase{\n\t\ts: `\n\t\t\t\tif (false) { \"yes\" }\n\t\t\t\telseif (false) { \"no\" }\n\t\t\t\telse { \"maybe\" }`,\n\t\tout: model.NewStringValue(\"maybe\"),\n\t}.run)\n\tt.Run(\"if elseif elseif else\", testCase{\n\t\ts: `\n\t\t\t\tif (false) { \"yes\" }\n\t\t\t\telseif (false) { \"no\" }\n\t\t\t\telseif (false) { \"maybe\" }\n\t\t\t\telse { \"nope\" }`,\n\t\tout: model.NewStringValue(\"nope\"),\n\t}.run)\n}\n"
  },
  {
    "path": "execution/execute_each.go",
    "content": "package execution\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"github.com/tomwright/dasel/v3/selector/ast\"\n)\n\nfunc eachExprExecutor(e ast.EachExpr) (expressionExecutor, error) {\n\treturn func(ctx context.Context, options *Options, data *model.Value) (*model.Value, error) {\n\t\tctx = WithExecutorID(ctx, \"eachExpr\")\n\t\tif !data.IsSlice() {\n\t\t\treturn nil, fmt.Errorf(\"cannot each over non-array\")\n\t\t}\n\n\t\tif err := data.RangeSlice(func(i int, item *model.Value) error {\n\t\t\t_, err := ExecuteAST(ctx, e.Expr, item, options)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\treturn nil\n\t\t}); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error ranging over slice: %w\", err)\n\t\t}\n\n\t\treturn data, nil\n\t}, nil\n}\n"
  },
  {
    "path": "execution/execute_each_test.go",
    "content": "package execution_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\nfunc TestEach(t *testing.T) {\n\tt.Run(\"all true\", testCase{\n\t\ts: \"[1,2,3].each($this = $this + 1)\",\n\t\toutFn: func() *model.Value {\n\t\t\ts := model.NewSliceValue()\n\t\t\tif err := s.Append(model.NewIntValue(2)); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t}\n\t\t\tif err := s.Append(model.NewIntValue(3)); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t}\n\t\t\tif err := s.Append(model.NewIntValue(4)); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t}\n\t\t\treturn s\n\t\t},\n\t}.run)\n}\n"
  },
  {
    "path": "execution/execute_filter.go",
    "content": "package execution\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"github.com/tomwright/dasel/v3/selector/ast\"\n)\n\nfunc filterExprExecutor(e ast.FilterExpr) (expressionExecutor, error) {\n\treturn func(ctx context.Context, options *Options, data *model.Value) (*model.Value, error) {\n\t\tctx = WithExecutorID(ctx, \"filterExpr\")\n\t\tif !data.IsSlice() {\n\t\t\treturn nil, fmt.Errorf(\"cannot filter over non-array\")\n\t\t}\n\t\tres := model.NewSliceValue()\n\n\t\tif err := data.RangeSlice(func(i int, item *model.Value) error {\n\t\t\tv, err := ExecuteAST(ctx, e.Expr, item, options)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tboolV, err := v.BoolValue()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tif !boolV {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tif err := res.Append(item); err != nil {\n\t\t\t\treturn fmt.Errorf(\"error appending item to result: %w\", err)\n\t\t\t}\n\t\t\treturn nil\n\t\t}); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error ranging over slice: %w\", err)\n\t\t}\n\n\t\treturn res, nil\n\t}, nil\n}\n"
  },
  {
    "path": "execution/execute_filter_test.go",
    "content": "package execution_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\nfunc TestFilter(t *testing.T) {\n\tinSlice := func() *model.Value {\n\t\ts := model.NewSliceValue()\n\t\tif err := s.Append(model.NewIntValue(1)); err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t}\n\t\tif err := s.Append(model.NewIntValue(2)); err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t}\n\t\tif err := s.Append(model.NewIntValue(3)); err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t}\n\t\treturn s\n\t}\n\tt.Run(\"all true\", testCase{\n\t\tinFn: inSlice,\n\t\ts:    \"filter(true)\",\n\t\toutFn: func() *model.Value {\n\t\t\ts := model.NewSliceValue()\n\t\t\tif err := s.Append(model.NewIntValue(1)); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t}\n\t\t\tif err := s.Append(model.NewIntValue(2)); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t}\n\t\t\tif err := s.Append(model.NewIntValue(3)); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t}\n\t\t\treturn s\n\t\t},\n\t}.run)\n\tt.Run(\"all !false\", testCase{\n\t\tinFn: inSlice,\n\t\ts:    \"filter(!false)\",\n\t\toutFn: func() *model.Value {\n\t\t\ts := model.NewSliceValue()\n\t\t\tif err := s.Append(model.NewIntValue(1)); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t}\n\t\t\tif err := s.Append(model.NewIntValue(2)); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t}\n\t\t\tif err := s.Append(model.NewIntValue(3)); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t}\n\t\t\treturn s\n\t\t},\n\t}.run)\n\tt.Run(\"all false\", testCase{\n\t\tinFn: inSlice,\n\t\ts:    \"filter(false)\",\n\t\toutFn: func() *model.Value {\n\t\t\ts := model.NewSliceValue()\n\t\t\treturn s\n\t\t},\n\t}.run)\n\tt.Run(\"all !true\", testCase{\n\t\tinFn: inSlice,\n\t\ts:    \"filter(!true)\",\n\t\toutFn: func() *model.Value {\n\t\t\ts := model.NewSliceValue()\n\t\t\treturn s\n\t\t},\n\t}.run)\n\tt.Run(\"equal 2\", testCase{\n\t\tinFn: inSlice,\n\t\ts:    \"filter($this == 2)\",\n\t\toutFn: func() *model.Value {\n\t\t\ts := model.NewSliceValue()\n\t\t\tif err := s.Append(model.NewIntValue(2)); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t}\n\t\t\treturn s\n\t\t},\n\t}.run)\n\tt.Run(\"not equal 2\", testCase{\n\t\tinFn: inSlice,\n\t\ts:    \"filter($this != 2)\",\n\t\toutFn: func() *model.Value {\n\t\t\ts := model.NewSliceValue()\n\t\t\tif err := s.Append(model.NewIntValue(1)); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t}\n\t\t\tif err := s.Append(model.NewIntValue(3)); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t}\n\t\t\treturn s\n\t\t},\n\t}.run)\n}\n"
  },
  {
    "path": "execution/execute_func.go",
    "content": "package execution\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"slices\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"github.com/tomwright/dasel/v3/selector/ast\"\n)\n\nfunc prepareArgs(ctx context.Context, opts *Options, data *model.Value, argsE ast.Expressions) (model.Values, error) {\n\targs := make(model.Values, 0)\n\tfor i, arg := range argsE {\n\t\tres, err := ExecuteAST(ctx, arg, data, opts)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error evaluating argument %d: %w\", i, err)\n\t\t}\n\n\t\targVals, err := prepareSpreadValues(res)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error handling spread values: %w\", err)\n\t\t}\n\n\t\targs = append(args, argVals...)\n\t}\n\treturn args, nil\n}\n\nfunc callFnExecutor(f FuncFn, argsE ast.Expressions) (expressionExecutor, error) {\n\treturn func(ctx context.Context, options *Options, data *model.Value) (*model.Value, error) {\n\t\tctx = WithExecutorID(ctx, \"callFnExpr\")\n\t\targs, err := prepareArgs(ctx, options, data, argsE)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error preparing arguments: %w\", err)\n\t\t}\n\n\t\tres, err := f(ctx, data, args)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error executing function: %w\", err)\n\t\t}\n\n\t\treturn res, nil\n\t}, nil\n}\n\nvar unstableFuncs = []string{\n\t\"ignore\",\n}\n\nfunc callExprExecutor(options *Options, e ast.CallExpr) (expressionExecutor, error) {\n\tif !options.Unstable && (slices.Contains(unstableFuncs, e.Function)) {\n\t\treturn nil, errors.New(\"unstable function are not enabled. to enable them use --unstable\")\n\t}\n\tif f, ok := options.Funcs.Get(e.Function); ok {\n\t\tres, err := callFnExecutor(f, e.Args)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error executing function %q: %w\", e.Function, err)\n\t\t}\n\t\treturn res, nil\n\t}\n\n\treturn nil, fmt.Errorf(\"unknown function: %q\", e.Function)\n}\n"
  },
  {
    "path": "execution/execute_func_test.go",
    "content": "package execution_test\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/execution\"\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\nfunc TestFunc(t *testing.T) {\n\treturnInputData := execution.NewFunc(\n\t\t\"returnInputData\",\n\t\tfunc(ctx context.Context, data *model.Value, args model.Values) (*model.Value, error) {\n\t\t\treturn data, nil\n\t\t},\n\t\texecution.ValidateArgsExactly(0),\n\t)\n\n\treturnFirstArg := execution.NewFunc(\n\t\t\"returnFirstArg\",\n\t\tfunc(ctx context.Context, data *model.Value, args model.Values) (*model.Value, error) {\n\t\t\treturn args[0], nil\n\t\t},\n\t\texecution.ValidateArgsExactly(1),\n\t)\n\n\tfuncs := execution.NewFuncCollection(\n\t\treturnInputData,\n\t\treturnFirstArg,\n\t)\n\n\topts := []execution.ExecuteOptionFn{\n\t\tfunc(options *execution.Options) {\n\t\t\toptions.Funcs = funcs\n\t\t},\n\t}\n\n\tt.Run(\"returnInputData\", testCase{\n\t\ts:    `1.returnInputData()`,\n\t\tout:  model.NewIntValue(1),\n\t\topts: opts,\n\t}.run)\n\n\tt.Run(\"returnFirstArg\", testCase{\n\t\ts:    `1.returnFirstArg(2)`,\n\t\tout:  model.NewIntValue(2),\n\t\topts: opts,\n\t}.run)\n}\n"
  },
  {
    "path": "execution/execute_literal.go",
    "content": "package execution\n\nimport (\n\t\"context\"\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"github.com/tomwright/dasel/v3/selector/ast\"\n)\n\nfunc numberIntExprExecutor(e ast.NumberIntExpr) (expressionExecutor, error) {\n\treturn func(ctx context.Context, options *Options, data *model.Value) (*model.Value, error) {\n\t\t//ctx = WithExecutorID(ctx, \"numberIntExpr\")\n\t\treturn model.NewIntValue(e.Value), nil\n\t}, nil\n}\n\nfunc numberFloatExprExecutor(e ast.NumberFloatExpr) (expressionExecutor, error) {\n\treturn func(ctx context.Context, options *Options, data *model.Value) (*model.Value, error) {\n\t\t//ctx = WithExecutorID(ctx, \"numberFloatExpr\")\n\t\treturn model.NewFloatValue(e.Value), nil\n\t}, nil\n}\n\nfunc stringExprExecutor(e ast.StringExpr) (expressionExecutor, error) {\n\treturn func(ctx context.Context, options *Options, data *model.Value) (*model.Value, error) {\n\t\t//ctx = WithExecutorID(ctx, \"stringExpr\")\n\t\treturn model.NewStringValue(e.Value), nil\n\t}, nil\n}\n\nfunc boolExprExecutor(e ast.BoolExpr) (expressionExecutor, error) {\n\treturn func(ctx context.Context, options *Options, data *model.Value) (*model.Value, error) {\n\t\t//ctx = WithExecutorID(ctx, \"boolExpr\")\n\t\treturn model.NewBoolValue(e.Value), nil\n\t}, nil\n}\n"
  },
  {
    "path": "execution/execute_literal_test.go",
    "content": "package execution_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\nfunc TestLiteral(t *testing.T) {\n\tt.Run(\"string\", testCase{\n\t\ts:   `\"hello\"`,\n\t\tout: model.NewStringValue(\"hello\"),\n\t}.run)\n\tt.Run(\"int\", testCase{\n\t\ts:   `123`,\n\t\tout: model.NewIntValue(123),\n\t}.run)\n\tt.Run(\"float\", testCase{\n\t\ts:   `123.4`,\n\t\tout: model.NewFloatValue(123.4),\n\t}.run)\n\tt.Run(\"true\", testCase{\n\t\ts:   `true`,\n\t\tout: model.NewBoolValue(true),\n\t}.run)\n\tt.Run(\"false\", testCase{\n\t\ts:   `false`,\n\t\tout: model.NewBoolValue(false),\n\t}.run)\n\tt.Run(\"empty array\", testCase{\n\t\ts: `[]`,\n\t\toutFn: func() *model.Value {\n\t\t\tr := model.NewSliceValue()\n\t\t\treturn r\n\t\t},\n\t}.run)\n\tt.Run(\"array with one element\", testCase{\n\t\ts: `[1]`,\n\t\toutFn: func() *model.Value {\n\t\t\tr := model.NewSliceValue()\n\t\t\tif err := r.Append(model.NewIntValue(1)); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\treturn r\n\t\t},\n\t}.run)\n\tt.Run(\"array with many elements\", testCase{\n\t\ts: `[1, 2.2, \"foo\", true, [1, 2, 3]]`,\n\t\toutFn: func() *model.Value {\n\t\t\tnested := model.NewSliceValue()\n\t\t\tif err := nested.Append(model.NewIntValue(1)); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif err := nested.Append(model.NewIntValue(2)); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif err := nested.Append(model.NewIntValue(3)); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\n\t\t\tr := model.NewSliceValue()\n\t\t\tif err := r.Append(model.NewIntValue(1)); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif err := r.Append(model.NewFloatValue(2.2)); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif err := r.Append(model.NewStringValue(\"foo\")); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif err := r.Append(model.NewBoolValue(true)); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif err := r.Append(nested); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\treturn r\n\t\t},\n\t}.run)\n\tt.Run(\"array with expressions\", testCase{\n\t\ts: `[1 + 1, 2f - 2, \"foo\" + \"bar\", true || false, [1 + 1, 2 * 2, 3 / 3]]`,\n\t\toutFn: func() *model.Value {\n\t\t\tnested := model.NewSliceValue()\n\t\t\tif err := nested.Append(model.NewIntValue(2)); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif err := nested.Append(model.NewIntValue(4)); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif err := nested.Append(model.NewIntValue(1)); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\n\t\t\tr := model.NewSliceValue()\n\t\t\tif err := r.Append(model.NewIntValue(2)); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif err := r.Append(model.NewFloatValue(0)); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif err := r.Append(model.NewStringValue(\"foobar\")); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif err := r.Append(model.NewBoolValue(true)); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif err := r.Append(nested); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\treturn r\n\t\t},\n\t}.run)\n}\n"
  },
  {
    "path": "execution/execute_map.go",
    "content": "package execution\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"github.com/tomwright/dasel/v3/selector/ast\"\n)\n\nfunc mapExprExecutor(e ast.MapExpr) (expressionExecutor, error) {\n\treturn func(ctx context.Context, options *Options, data *model.Value) (*model.Value, error) {\n\t\tctx = WithExecutorID(ctx, \"mapExpr\")\n\t\tif !data.IsSlice() {\n\t\t\treturn nil, fmt.Errorf(\"cannot map over non-array\")\n\t\t}\n\t\tres := model.NewSliceValue()\n\n\t\tif err := data.RangeSlice(func(i int, item *model.Value) error {\n\t\t\titem, err := ExecuteAST(ctx, e.Expr, item, options)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := res.Append(item); err != nil {\n\t\t\t\treturn fmt.Errorf(\"error appending item to result: %w\", err)\n\t\t\t}\n\t\t\treturn nil\n\t\t}); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error ranging over slice: %w\", err)\n\t\t}\n\t\treturn res, nil\n\t}, nil\n}\n"
  },
  {
    "path": "execution/execute_map_test.go",
    "content": "package execution_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"github.com/tomwright/dasel/v3/model/orderedmap\"\n)\n\nfunc TestMap(t *testing.T) {\n\tt.Run(\"property from slice of maps\", testCase{\n\t\tinFn: func() *model.Value {\n\t\t\treturn model.NewValue([]any{\n\t\t\t\torderedmap.NewMap().Set(\"number\", 1),\n\t\t\t\torderedmap.NewMap().Set(\"number\", 2),\n\t\t\t\torderedmap.NewMap().Set(\"number\", 3),\n\t\t\t})\n\t\t},\n\t\ts: `map(number)`,\n\t\toutFn: func() *model.Value {\n\t\t\treturn model.NewValue([]any{1, 2, 3})\n\t\t},\n\t}.run)\n\tt.Run(\"with chain of selectors\", testCase{\n\t\tinFn: func() *model.Value {\n\t\t\treturn model.NewValue([]any{\n\t\t\t\torderedmap.NewMap().Set(\"foo\", 1).Set(\"bar\", 4),\n\t\t\t\torderedmap.NewMap().Set(\"foo\", 2).Set(\"bar\", 5),\n\t\t\t\torderedmap.NewMap().Set(\"foo\", 3).Set(\"bar\", 6),\n\t\t\t})\n\t\t},\n\t\ts: `\n\t\t\t\tmap (\n\t\t\t\t\t{\n\t\t\t\t\t\ttotal: add( foo, bar, 1 )\n\t\t\t\t\t}\n\t\t\t\t)\n\t\t\t\t.map ( total )`,\n\t\toutFn: func() *model.Value {\n\t\t\tres := model.NewSliceValue()\n\t\t\tif err := res.Append(model.NewValue(6)); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tif err := res.Append(model.NewValue(8)); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tif err := res.Append(model.NewValue(10)); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\treturn res\n\t\t},\n\t}.run)\n}\n"
  },
  {
    "path": "execution/execute_object.go",
    "content": "package execution\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"github.com/tomwright/dasel/v3/selector/ast\"\n)\n\nfunc objectExprExecutor(e ast.ObjectExpr) (expressionExecutor, error) {\n\treturn func(ctx context.Context, options *Options, data *model.Value) (*model.Value, error) {\n\t\tctx = WithExecutorID(ctx, \"objectExpr\")\n\t\tobj := model.NewMapValue()\n\t\tfor _, p := range e.Pairs {\n\n\t\t\tif ast.IsType[ast.SpreadExpr](p.Key) {\n\t\t\t\tvar val *model.Value\n\t\t\t\tvar err error\n\t\t\t\tif p.Value != nil {\n\t\t\t\t\t// We need to spread the resulting value.\n\t\t\t\t\tval, err = ExecuteAST(ctx, p.Value, data, options)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"error evaluating spread values: %w\", err)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tval = data\n\t\t\t\t}\n\n\t\t\t\tif err := val.RangeMap(func(key string, value *model.Value) error {\n\t\t\t\t\tif err := obj.SetMapKey(key, value); err != nil {\n\t\t\t\t\t\treturn fmt.Errorf(\"error setting map key: %w\", err)\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t}); err != nil {\n\t\t\t\t\treturn nil, fmt.Errorf(\"error spreading into object: %w\", err)\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tkey, err := ExecuteAST(ctx, p.Key, data, options)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"error evaluating key: %w\", err)\n\t\t\t}\n\t\t\tif !key.IsString() {\n\t\t\t\treturn nil, fmt.Errorf(\"expected key to resolve to string, got %s\", key.Type())\n\t\t\t}\n\n\t\t\tval, err := ExecuteAST(ctx, p.Value, data, options)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"error evaluating value: %w\", err)\n\t\t\t}\n\n\t\t\tkeyStr, err := key.StringValue()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"error getting string value: %w\", err)\n\t\t\t}\n\t\t\tif err := obj.SetMapKey(keyStr, val); err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"error setting map key: %w\", err)\n\t\t\t}\n\t\t}\n\t\treturn obj, nil\n\t}, nil\n}\n\nfunc propertyExprExecutor(e ast.PropertyExpr) (expressionExecutor, error) {\n\treturn func(ctx context.Context, options *Options, data *model.Value) (*model.Value, error) {\n\t\tctx = WithExecutorID(ctx, \"propertyExpr\")\n\t\tkey, err := ExecuteAST(ctx, e.Property, data, options)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error evaluating property: %w\", err)\n\t\t}\n\t\tswitch {\n\t\tcase key.IsString():\n\t\t\tkeyStr, err := key.StringValue()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"error getting string value: %w\", err)\n\t\t\t}\n\n\t\t\treturn data.GetMapKey(keyStr)\n\t\tcase key.IsInt():\n\t\t\tkeyInt, err := key.IntValue()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"error getting int value: %w\", err)\n\t\t\t}\n\t\t\treturn data.GetSliceIndex(int(keyInt))\n\t\tdefault:\n\t\t\treturn nil, fmt.Errorf(\"expected key to be a string or int, got %s\", key.Type())\n\t\t}\n\t}, nil\n}\n"
  },
  {
    "path": "execution/execute_object_test.go",
    "content": "package execution_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"github.com/tomwright/dasel/v3/model/orderedmap\"\n)\n\nfunc TestObject(t *testing.T) {\n\tinputMap := func() *model.Value {\n\t\treturn model.NewValue(orderedmap.NewMap().\n\t\t\tSet(\"title\", \"Mr\").\n\t\t\tSet(\"age\", int64(30)).\n\t\t\tSet(\"name\", orderedmap.NewMap().\n\t\t\t\tSet(\"first\", \"Tom\").\n\t\t\t\tSet(\"last\", \"Wright\")))\n\t}\n\tt.Run(\"get\", testCase{\n\t\tin: inputMap(),\n\t\ts:  `{title}`,\n\t\toutFn: func() *model.Value {\n\t\t\treturn model.NewValue(orderedmap.NewMap().Set(\"title\", \"Mr\"))\n\t\t},\n\t}.run)\n\tt.Run(\"get multiple\", testCase{\n\t\tin: inputMap(),\n\t\ts:  `{title, age}`,\n\t\toutFn: func() *model.Value {\n\t\t\treturn model.NewValue(orderedmap.NewMap().Set(\"title\", \"Mr\").Set(\"age\", int64(30)))\n\t\t},\n\t}.run)\n\tt.Run(\"get with spread\", testCase{\n\t\tin: inputMap(),\n\t\ts:  `{...}`,\n\t\toutFn: func() *model.Value {\n\t\t\tres := inputMap()\n\t\t\treturn res\n\t\t},\n\t}.run)\n\tt.Run(\"set\", testCase{\n\t\tin: inputMap(),\n\t\ts:  `{title:\"Mrs\"}`,\n\t\toutFn: func() *model.Value {\n\t\t\tres := model.NewMapValue()\n\t\t\t_ = res.SetMapKey(\"title\", model.NewStringValue(\"Mrs\"))\n\t\t\treturn res\n\t\t},\n\t}.run)\n\tt.Run(\"set with spread\", testCase{\n\t\tin: inputMap(),\n\t\ts:  `{..., title:\"Mrs\"}`,\n\t\toutFn: func() *model.Value {\n\t\t\tres := inputMap()\n\t\t\t_ = res.SetMapKey(\"title\", model.NewStringValue(\"Mrs\"))\n\t\t\treturn res\n\t\t},\n\t}.run)\n\tt.Run(\"merge with spread\", testCase{\n\t\tinFn: func() *model.Value {\n\t\t\ta := model.NewMapValue()\n\t\t\tif err := a.SetMapKey(\"foo\", model.NewStringValue(\"afoo\")); err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif err := a.SetMapKey(\"bar\", model.NewStringValue(\"abar\")); err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tb := model.NewMapValue()\n\t\t\tif err := b.SetMapKey(\"bar\", model.NewStringValue(\"bbar\")); err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif err := b.SetMapKey(\"baz\", model.NewStringValue(\"bbaz\")); err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tres := model.NewMapValue()\n\t\t\tif err := res.SetMapKey(\"a\", a); err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif err := res.SetMapKey(\"b\", b); err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\treturn res\n\t\t},\n\t\ts: `{a..., b..., x: 1}`,\n\t\toutFn: func() *model.Value {\n\t\t\tb := model.NewMapValue()\n\t\t\tif err := b.SetMapKey(\"foo\", model.NewStringValue(\"afoo\")); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif err := b.SetMapKey(\"bar\", model.NewStringValue(\"bbar\")); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif err := b.SetMapKey(\"baz\", model.NewStringValue(\"bbaz\")); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif err := b.SetMapKey(\"x\", model.NewIntValue(1)); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\treturn b\n\t\t},\n\t}.run)\n}\n"
  },
  {
    "path": "execution/execute_recursive_descent.go",
    "content": "package execution\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"github.com/tomwright/dasel/v3/selector/ast\"\n)\n\nfunc recursiveDescentExprExecutor2(e ast.RecursiveDescentExpr) (expressionExecutor, error) {\n\tvar doSearch func(ctx context.Context, options *Options, data *model.Value) ([]*model.Value, error)\n\tfindValue := func(ctx context.Context, options *Options, v *model.Value) (*model.Value, error) {\n\t\tproperty, err := ExecuteAST(ctx, e.Expr, v, options)\n\t\tif err != nil {\n\t\t\thandleErrs := []any{\n\t\t\t\tmodel.ErrIncompatibleTypes{},\n\t\t\t\tmodel.ErrUnexpectedType{},\n\t\t\t\tmodel.ErrUnexpectedTypes{},\n\t\t\t\tmodel.SliceIndexOutOfRange{},\n\t\t\t\tmodel.MapKeyNotFound{},\n\t\t\t}\n\t\t\tfor _, e := range handleErrs {\n\t\t\t\tif errors.As(err, &e) {\n\t\t\t\t\terr = nil\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn property, nil\n\t}\n\tdoSearch = func(ctx context.Context, options *Options, data *model.Value) ([]*model.Value, error) {\n\t\tres := make([]*model.Value, 0)\n\n\t\tswitch data.Type() {\n\t\tcase model.TypeMap:\n\t\t\tif err := data.RangeMap(func(key string, v *model.Value) error {\n\t\t\t\tif v.IsScalar() {\n\t\t\t\t\tif e.IsWildcard {\n\t\t\t\t\t\tres = append(res, v)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tif !e.IsWildcard {\n\t\t\t\t\t\tproperty, err := findValue(ctx, options, v)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif property != nil {\n\t\t\t\t\t\t\tres = append(res, property)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tgotNext, err := doSearch(ctx, options, v)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t\tres = append(res, gotNext...)\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t}); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\tcase model.TypeSlice:\n\t\t\tif err := data.RangeSlice(func(i int, v *model.Value) error {\n\t\t\t\tif v.IsScalar() {\n\t\t\t\t\tif e.IsWildcard {\n\t\t\t\t\t\tres = append(res, v)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tif !e.IsWildcard {\n\t\t\t\t\t\tproperty, err := findValue(ctx, options, v)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif property != nil {\n\t\t\t\t\t\t\tres = append(res, property)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tgotNext, err := doSearch(ctx, options, v)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t\tres = append(res, gotNext...)\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t}); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\n\t\treturn res, nil\n\t}\n\n\treturn func(ctx context.Context, options *Options, data *model.Value) (*model.Value, error) {\n\t\tctx = WithExecutorID(ctx, \"recursiveDescentExpr\")\n\t\tmatches := model.NewSliceValue()\n\n\t\tfound, err := doSearch(ctx, options, data)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tfor _, f := range found {\n\t\t\tif err := matches.Append(f); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\n\t\treturn matches, nil\n\t}, nil\n}\n"
  },
  {
    "path": "execution/execute_search.go",
    "content": "package execution\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"github.com/tomwright/dasel/v3/selector/ast\"\n)\n\nfunc searchExprExecutor(e ast.SearchExpr) (expressionExecutor, error) {\n\tvar doSearch func(ctx context.Context, options *Options, data *model.Value) ([]*model.Value, error)\n\tprocessValue := func(ctx context.Context, v *model.Value, options *Options) (bool, error) {\n\t\tgot, err := ExecuteAST(ctx, e.Expr, v, options)\n\t\tif err != nil {\n\t\t\thandleErrs := []any{\n\t\t\t\tmodel.ErrIncompatibleTypes{},\n\t\t\t\tmodel.ErrUnexpectedType{},\n\t\t\t\tmodel.ErrUnexpectedTypes{},\n\t\t\t\tmodel.SliceIndexOutOfRange{},\n\t\t\t\tmodel.MapKeyNotFound{},\n\t\t\t}\n\t\t\tfor _, e := range handleErrs {\n\t\t\t\tif errors.As(err, &e) {\n\t\t\t\t\terr = nil\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\n\t\tif got == nil {\n\t\t\treturn false, nil\n\t\t}\n\n\t\tgotV, err := got.BoolValue()\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\treturn gotV, nil\n\t}\n\tdoSearch = func(ctx context.Context, options *Options, data *model.Value) ([]*model.Value, error) {\n\t\tres := make([]*model.Value, 0)\n\n\t\tswitch data.Type() {\n\t\tcase model.TypeMap:\n\t\t\tif err := data.RangeMap(func(key string, v *model.Value) error {\n\t\t\t\tmatch, err := processValue(ctx, v, options)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\t\tif match {\n\t\t\t\t\tres = append(res, v)\n\t\t\t\t}\n\n\t\t\t\tgotNext, err := doSearch(ctx, options, v)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tres = append(res, gotNext...)\n\n\t\t\t\treturn nil\n\t\t\t}); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\tcase model.TypeSlice:\n\t\t\tif err := data.RangeSlice(func(i int, v *model.Value) error {\n\t\t\t\tmatch, err := processValue(ctx, v, options)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\t\tif match {\n\t\t\t\t\tres = append(res, v)\n\t\t\t\t}\n\n\t\t\t\tgotNext, err := doSearch(ctx, options, v)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tres = append(res, gotNext...)\n\n\t\t\t\treturn nil\n\t\t\t}); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\n\t\treturn res, nil\n\t}\n\n\treturn func(ctx context.Context, options *Options, data *model.Value) (*model.Value, error) {\n\t\tctx = WithExecutorID(ctx, \"searchExpr\")\n\t\tmatches := model.NewSliceValue()\n\n\t\tfound, err := doSearch(ctx, options, data)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tfor _, f := range found {\n\t\t\tif err := matches.Append(f); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\n\t\treturn matches, nil\n\t}, nil\n}\n"
  },
  {
    "path": "execution/execute_sort_by.go",
    "content": "package execution\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"slices\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"github.com/tomwright/dasel/v3/selector/ast\"\n)\n\nfunc sortByExprExecutor(e ast.SortByExpr) (expressionExecutor, error) {\n\treturn func(ctx context.Context, options *Options, data *model.Value) (*model.Value, error) {\n\t\tctx = WithExecutorID(ctx, \"sortByExpr\")\n\t\tif !data.IsSlice() {\n\t\t\treturn nil, fmt.Errorf(\"cannot sort by on non-slice data\")\n\t\t}\n\n\t\ttype sortableValue struct {\n\t\t\tindex int\n\t\t\tvalue *model.Value\n\t\t}\n\t\tvalues := make([]sortableValue, 0)\n\n\t\tif err := data.RangeSlice(func(i int, item *model.Value) error {\n\t\t\titem, err := ExecuteAST(ctx, e.Expr, item, options)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tvalues = append(values, sortableValue{\n\t\t\t\tindex: i,\n\t\t\t\tvalue: item,\n\t\t\t})\n\t\t\treturn nil\n\t\t}); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error ranging over slice: %w\", err)\n\t\t}\n\n\t\tslices.SortFunc(values, func(i, j sortableValue) int {\n\t\t\tres, err := i.value.Compare(j.value)\n\t\t\tif err != nil {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\tif e.Descending {\n\t\t\t\treturn -res\n\t\t\t}\n\t\t\treturn res\n\t\t})\n\n\t\tres := model.NewSliceValue()\n\n\t\tfor _, i := range values {\n\t\t\titem, err := data.GetSliceIndex(i.index)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"error getting slice index: %w\", err)\n\t\t\t}\n\t\t\tif err := res.Append(item); err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"error appending item to result: %w\", err)\n\t\t\t}\n\t\t}\n\n\t\treturn res, nil\n\t}, nil\n}\n"
  },
  {
    "path": "execution/execute_sort_by_test.go",
    "content": "package execution_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\nfunc TestFuncSortBy(t *testing.T) {\n\trunSortTests := func(in func() *model.Value, outAsc func() *model.Value, outDesc func() *model.Value) func(*testing.T) {\n\t\treturn func(t *testing.T) {\n\t\t\tt.Run(\"asc default\", testCase{\n\t\t\t\tinFn:  in,\n\t\t\t\ts:     `sortBy($this)`,\n\t\t\t\toutFn: outAsc,\n\t\t\t}.run)\n\t\t\tt.Run(\"asc\", testCase{\n\t\t\t\tinFn:  in,\n\t\t\t\ts:     `sortBy($this, asc)`,\n\t\t\t\toutFn: outAsc,\n\t\t\t}.run)\n\t\t\tt.Run(\"desc\", testCase{\n\t\t\t\tinFn:  in,\n\t\t\t\ts:     `sortBy($this, desc)`,\n\t\t\t\toutFn: outDesc,\n\t\t\t}.run)\n\t\t}\n\t}\n\n\tt.Run(\"int\", runSortTests(\n\t\tfunc() *model.Value {\n\t\t\tres := model.NewSliceValue()\n\t\t\tif err := res.Append(model.NewIntValue(2)); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tif err := res.Append(model.NewIntValue(1)); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tif err := res.Append(model.NewIntValue(4)); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tif err := res.Append(model.NewIntValue(3)); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\treturn res\n\t\t},\n\t\tfunc() *model.Value {\n\t\t\tres := model.NewSliceValue()\n\t\t\tif err := res.Append(model.NewIntValue(1)); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tif err := res.Append(model.NewIntValue(2)); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tif err := res.Append(model.NewIntValue(3)); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tif err := res.Append(model.NewIntValue(4)); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\treturn res\n\t\t},\n\t\tfunc() *model.Value {\n\t\t\tres := model.NewSliceValue()\n\t\t\tif err := res.Append(model.NewIntValue(4)); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tif err := res.Append(model.NewIntValue(3)); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tif err := res.Append(model.NewIntValue(2)); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tif err := res.Append(model.NewIntValue(1)); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\treturn res\n\t\t},\n\t))\n\n\tt.Run(\"float\", runSortTests(\n\t\tfunc() *model.Value {\n\t\t\tres := model.NewSliceValue()\n\t\t\tif err := res.Append(model.NewFloatValue(2.23)); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tif err := res.Append(model.NewFloatValue(2)); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tif err := res.Append(model.NewFloatValue(5.123)); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tif err := res.Append(model.NewFloatValue(4.2)); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\treturn res\n\t\t},\n\t\tfunc() *model.Value {\n\t\t\tres := model.NewSliceValue()\n\t\t\tif err := res.Append(model.NewFloatValue(2)); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tif err := res.Append(model.NewFloatValue(2.23)); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tif err := res.Append(model.NewFloatValue(4.2)); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tif err := res.Append(model.NewFloatValue(5.123)); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\treturn res\n\t\t},\n\t\tfunc() *model.Value {\n\t\t\tres := model.NewSliceValue()\n\t\t\tif err := res.Append(model.NewFloatValue(5.123)); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tif err := res.Append(model.NewFloatValue(4.2)); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tif err := res.Append(model.NewFloatValue(2.23)); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tif err := res.Append(model.NewFloatValue(2)); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\treturn res\n\t\t},\n\t))\n\tt.Run(\"string\", runSortTests(\n\t\tfunc() *model.Value {\n\t\t\tres := model.NewSliceValue()\n\t\t\tif err := res.Append(model.NewStringValue(\"def\")); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tif err := res.Append(model.NewStringValue(\"abc\")); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tif err := res.Append(model.NewStringValue(\"cde\")); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tif err := res.Append(model.NewStringValue(\"bcd\")); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\treturn res\n\t\t},\n\t\tfunc() *model.Value {\n\t\t\tres := model.NewSliceValue()\n\t\t\tif err := res.Append(model.NewStringValue(\"abc\")); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tif err := res.Append(model.NewStringValue(\"bcd\")); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tif err := res.Append(model.NewStringValue(\"cde\")); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tif err := res.Append(model.NewStringValue(\"def\")); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\treturn res\n\t\t},\n\t\tfunc() *model.Value {\n\t\t\tres := model.NewSliceValue()\n\t\t\tif err := res.Append(model.NewStringValue(\"def\")); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tif err := res.Append(model.NewStringValue(\"cde\")); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tif err := res.Append(model.NewStringValue(\"bcd\")); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tif err := res.Append(model.NewStringValue(\"abc\")); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\treturn res\n\t\t},\n\t))\n}\n"
  },
  {
    "path": "execution/execute_spread.go",
    "content": "package execution\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\nfunc spreadExprExecutor() (expressionExecutor, error) {\n\treturn func(ctx context.Context, options *Options, data *model.Value) (*model.Value, error) {\n\t\t//ctx = WithExecutorID(ctx, \"spreadExpr\")\n\t\ts := model.NewSliceValue()\n\n\t\ts.MarkAsSpread()\n\n\t\tswitch {\n\t\tcase data.IsSlice():\n\t\t\tif err := data.RangeSlice(func(key int, value *model.Value) error {\n\t\t\t\tif err := s.Append(value); err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"error appending value to slice: %w\", err)\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t}); err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"error ranging slice: %w\", err)\n\t\t\t}\n\t\tcase data.IsMap():\n\t\t\tif err := data.RangeMap(func(key string, value *model.Value) error {\n\t\t\t\tif err := s.Append(value); err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"error appending value to slice: %w\", err)\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t}); err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"error ranging map: %w\", err)\n\t\t\t}\n\t\tdefault:\n\t\t\treturn nil, fmt.Errorf(\"cannot spread on type %s\", data.Type())\n\t\t}\n\n\t\treturn s, nil\n\t}, nil\n}\n\n// prepareSpreadValues looks at the incoming value, and if we detect a spread value, we return the individual values.\nfunc prepareSpreadValues(val *model.Value) (model.Values, error) {\n\tif val.IsSlice() && val.IsSpread() {\n\t\tsliceLen, err := val.SliceLen()\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error getting slice length: %w\", err)\n\t\t}\n\t\tvalues := make(model.Values, sliceLen)\n\t\tfor i := 0; i < sliceLen; i++ {\n\t\t\tv, err := val.GetSliceIndex(i)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"error getting slice index %d: %w\", i, err)\n\t\t\t}\n\t\t\tvalues[i] = v\n\t\t}\n\t\treturn values, nil\n\t}\n\treturn model.Values{val}, nil\n}\n"
  },
  {
    "path": "execution/execute_spread_test.go",
    "content": "package execution_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\nfunc TestSpread(t *testing.T) {\n\tt.Run(\"build new array\", testCase{\n\t\ts: \"[[1,2,3]..., 4]\",\n\t\toutFn: func() *model.Value {\n\t\t\ts := model.NewSliceValue()\n\t\t\tif err := s.Append(model.NewIntValue(1)); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t}\n\t\t\tif err := s.Append(model.NewIntValue(2)); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t}\n\t\t\tif err := s.Append(model.NewIntValue(3)); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t}\n\t\t\tif err := s.Append(model.NewIntValue(4)); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t}\n\t\t\treturn s\n\t\t},\n\t}.run)\n}\n"
  },
  {
    "path": "execution/execute_test.go",
    "content": "package execution_test\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/tomwright/dasel/v3/execution\"\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"github.com/tomwright/dasel/v3/model/orderedmap\"\n)\n\ntype testCase struct {\n\tin          *model.Value\n\tinFn        func() *model.Value\n\ts           string\n\tout         *model.Value\n\toutFn       func() *model.Value\n\tcompareRoot bool\n\topts        []execution.ExecuteOptionFn\n}\n\nfunc (tc testCase) run(t *testing.T) {\n\tin := tc.in\n\tif tc.inFn != nil {\n\t\tin = tc.inFn()\n\t}\n\tif in == nil {\n\t\tin = model.NewValue(nil)\n\t}\n\texp := tc.out\n\tif tc.outFn != nil {\n\t\texp = tc.outFn()\n\t}\n\tres, err := execution.ExecuteSelector(context.Background(), tc.s, in, execution.NewOptions(tc.opts...))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif tc.compareRoot {\n\t\tres = in\n\t}\n\n\tequal, err := res.EqualTypeValue(exp)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif !equal {\n\t\tt.Errorf(\"unexpected output:\\nexp: %s\\ngot: %s\", exp.String(), res.String())\n\t}\n\n\texpMeta := exp.Metadata\n\tgotMeta := res.Metadata\n\tif !cmp.Equal(expMeta, gotMeta) {\n\t\tt.Errorf(\"unexpected output metadata: %v\", cmp.Diff(expMeta, gotMeta))\n\t}\n}\n\nfunc TestExecuteSelector_HappyPath(t *testing.T) {\n\tt.Run(\"get\", func(t *testing.T) {\n\t\tinputMap := func() *model.Value {\n\t\t\treturn model.NewValue(\n\t\t\t\torderedmap.NewMap().\n\t\t\t\t\tSet(\"title\", \"Mr\").\n\t\t\t\t\tSet(\"age\", int64(31)).\n\t\t\t\t\tSet(\"name\", orderedmap.NewMap().\n\t\t\t\t\t\tSet(\"first\", \"Tom\").\n\t\t\t\t\t\tSet(\"last\", \"Wright\")),\n\t\t\t)\n\t\t}\n\t\tt.Run(\"property\", testCase{\n\t\t\tin:  inputMap(),\n\t\t\ts:   `title`,\n\t\t\tout: model.NewStringValue(\"Mr\"),\n\t\t}.run)\n\t\tt.Run(\"nested property\", testCase{\n\t\t\tin:  inputMap(),\n\t\t\ts:   `name.first`,\n\t\t\tout: model.NewStringValue(\"Tom\"),\n\t\t}.run)\n\t\tt.Run(\"concat with grouping\", testCase{\n\t\t\tin:  inputMap(),\n\t\t\ts:   `title + \" \" + (name.first) + \" \" + (name.last)`,\n\t\t\tout: model.NewStringValue(\"Mr Tom Wright\"),\n\t\t}.run)\n\t\tt.Run(\"concat\", testCase{\n\t\t\tin:  inputMap(),\n\t\t\ts:   `title + \" \" + name.first + \" \" + name.last`,\n\t\t\tout: model.NewStringValue(\"Mr Tom Wright\"),\n\t\t}.run)\n\t\tt.Run(\"add evaluated fields\", testCase{\n\t\t\tin: inputMap(),\n\t\t\ts:  `{..., \"over30\": age > 30}`,\n\t\t\toutFn: func() *model.Value {\n\t\t\t\treturn model.NewValue(\n\t\t\t\t\torderedmap.NewMap().\n\t\t\t\t\t\tSet(\"title\", \"Mr\").\n\t\t\t\t\t\tSet(\"age\", int64(31)).\n\t\t\t\t\t\tSet(\"name\", orderedmap.NewMap().\n\t\t\t\t\t\t\tSet(\"first\", \"Tom\").\n\t\t\t\t\t\t\tSet(\"last\", \"Wright\")).\n\t\t\t\t\t\tSet(\"over30\", true),\n\t\t\t\t)\n\t\t\t},\n\t\t}.run)\n\t})\n\n\tt.Run(\"set\", func(t *testing.T) {\n\t\tinputMap := func() *model.Value {\n\t\t\treturn model.NewValue(\n\t\t\t\torderedmap.NewMap().\n\t\t\t\t\tSet(\"title\", \"Mr\").\n\t\t\t\t\tSet(\"age\", int64(31)).\n\t\t\t\t\tSet(\"name\", orderedmap.NewMap().\n\t\t\t\t\t\tSet(\"first\", \"Tom\").\n\t\t\t\t\t\tSet(\"last\", \"Wright\")),\n\t\t\t)\n\t\t}\n\t\tinputSlice := func() *model.Value {\n\t\t\treturn model.NewValue([]any{1, 2, 3})\n\t\t}\n\n\t\tt.Run(\"set property\", testCase{\n\t\t\tin: inputMap(),\n\t\t\ts:  `title = \"Mrs\"`,\n\t\t\toutFn: func() *model.Value {\n\t\t\t\tres := inputMap()\n\t\t\t\tif err := res.SetMapKey(\"title\", model.NewStringValue(\"Mrs\")); err != nil {\n\t\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t\t}\n\t\t\t\treturn res\n\t\t\t},\n\t\t\tcompareRoot: true,\n\t\t}.run)\n\n\t\tt.Run(\"set index\", testCase{\n\t\t\tin: inputSlice(),\n\t\t\ts:  `$this[1] = 4`,\n\t\t\toutFn: func() *model.Value {\n\t\t\t\tres := inputSlice()\n\t\t\t\tif err := res.SetSliceIndex(1, model.NewIntValue(4)); err != nil {\n\t\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t\t}\n\t\t\t\treturn res\n\t\t\t},\n\t\t\tcompareRoot: true,\n\t\t}.run)\n\t})\n}\n"
  },
  {
    "path": "execution/execute_unary.go",
    "content": "package execution\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"github.com/tomwright/dasel/v3/selector/ast\"\n\t\"github.com/tomwright/dasel/v3/selector/lexer\"\n)\n\nfunc unaryExprExecutor(e ast.UnaryExpr) (expressionExecutor, error) {\n\treturn func(ctx context.Context, options *Options, data *model.Value) (*model.Value, error) {\n\t\tctx = WithExecutorID(ctx, \"unaryExpr\")\n\t\tright, err := ExecuteAST(ctx, e.Right, data, options)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error evaluating right expression: %w\", err)\n\t\t}\n\n\t\tswitch e.Operator.Kind {\n\t\tcase lexer.Exclamation:\n\t\t\tboolV, err := right.BoolValue()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"error converting value to boolean: %w\", err)\n\t\t\t}\n\t\t\treturn model.NewBoolValue(!boolV), nil\n\t\tdefault:\n\t\t\treturn nil, fmt.Errorf(\"unhandled unary operator: %s\", e.Operator.Value)\n\t\t}\n\t}, nil\n}\n"
  },
  {
    "path": "execution/execute_unary_test.go",
    "content": "package execution_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"github.com/tomwright/dasel/v3/model/orderedmap\"\n)\n\nfunc TestUnary(t *testing.T) {\n\tt.Run(\"not\", func(t *testing.T) {\n\t\tt.Run(\"literals\", func(t *testing.T) {\n\t\t\tt.Run(\"not true\", testCase{\n\t\t\t\ts:   `!true`,\n\t\t\t\tout: model.NewBoolValue(false),\n\t\t\t}.run)\n\t\t\tt.Run(\"not not true\", testCase{\n\t\t\t\ts:   `!!true`,\n\t\t\t\tout: model.NewBoolValue(true),\n\t\t\t}.run)\n\t\t\tt.Run(\"not not not true\", testCase{\n\t\t\t\ts:   `!!!true`,\n\t\t\t\tout: model.NewBoolValue(false),\n\t\t\t}.run)\n\t\t\tt.Run(\"not false\", testCase{\n\t\t\t\ts:   `!false`,\n\t\t\t\tout: model.NewBoolValue(true),\n\t\t\t}.run)\n\t\t\tt.Run(\"not not false\", testCase{\n\t\t\t\ts:   `!!false`,\n\t\t\t\tout: model.NewBoolValue(false),\n\t\t\t}.run)\n\t\t\tt.Run(\"not not not false\", testCase{\n\t\t\t\ts:   `!!!false`,\n\t\t\t\tout: model.NewBoolValue(true),\n\t\t\t}.run)\n\t\t})\n\t\tt.Run(\"variables\", func(t *testing.T) {\n\t\t\tin := func() *model.Value {\n\t\t\t\treturn model.NewValue(orderedmap.NewMap().\n\t\t\t\t\tSet(\"t\", true).\n\t\t\t\t\tSet(\"f\", false))\n\t\t\t}\n\t\t\tt.Run(\"not true\", testCase{\n\t\t\t\ts:    `!t`,\n\t\t\t\tinFn: in,\n\t\t\t\tout:  model.NewBoolValue(false),\n\t\t\t}.run)\n\t\t\tt.Run(\"not not true\", testCase{\n\t\t\t\ts:    `!!t`,\n\t\t\t\tinFn: in,\n\t\t\t\tout:  model.NewBoolValue(true),\n\t\t\t}.run)\n\t\t\tt.Run(\"not not not true\", testCase{\n\t\t\t\ts:    `!!!t`,\n\t\t\t\tinFn: in,\n\t\t\t\tout:  model.NewBoolValue(false),\n\t\t\t}.run)\n\t\t\tt.Run(\"not false\", testCase{\n\t\t\t\ts:    `!f`,\n\t\t\t\tinFn: in,\n\t\t\t\tout:  model.NewBoolValue(true),\n\t\t\t}.run)\n\t\t\tt.Run(\"not not false\", testCase{\n\t\t\t\ts:    `!!f`,\n\t\t\t\tinFn: in,\n\t\t\t\tout:  model.NewBoolValue(false),\n\t\t\t}.run)\n\t\t\tt.Run(\"not not not false\", testCase{\n\t\t\t\ts:    `!!!f`,\n\t\t\t\tinFn: in,\n\t\t\t\tout:  model.NewBoolValue(true),\n\t\t\t}.run)\n\t\t})\n\t})\n}\n"
  },
  {
    "path": "execution/func.go",
    "content": "package execution\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\nvar (\n\t// DefaultFuncCollection is the default collection of functions that can be executed.\n\tDefaultFuncCollection = NewFuncCollection(\n\t\tFuncLen,\n\t\tFuncAdd,\n\t\tFuncToString,\n\t\tFuncToInt,\n\t\tFuncToFloat,\n\t\tFuncMerge,\n\t\tFuncReverse,\n\t\tFuncTypeOf,\n\t\tFuncMax,\n\t\tFuncMin,\n\t\tFuncIgnore,\n\t\tFuncBase64Encode,\n\t\tFuncBase64Decode,\n\t\tFuncParse,\n\t\tFuncReadFile,\n\t\tFuncHas,\n\t\tFuncGet,\n\t\tFuncContains,\n\t\tFuncSum,\n\t\tFuncJoin,\n\t\tFuncReplace,\n\t\tFuncKeys,\n\t)\n)\n\n// ArgsValidator is a function that validates the arguments passed to a function.\ntype ArgsValidator func(ctx context.Context, name string, args model.Values) error\n\n// ValidateArgsExactly returns an ArgsValidator that validates that the number of arguments passed to a function is exactly the expected number.\nfunc ValidateArgsExactly(expected int) ArgsValidator {\n\treturn func(ctx context.Context, name string, args model.Values) error {\n\t\tif len(args) == expected {\n\t\t\treturn nil\n\t\t}\n\t\treturn fmt.Errorf(\"func %q expects exactly %d arguments, got %d\", name, expected, len(args))\n\t}\n}\n\n// ValidateArgsMin returns an ArgsValidator that validates that the number of arguments passed to a function is at least the expected number.\nfunc ValidateArgsMin(expected int) ArgsValidator {\n\treturn func(ctx context.Context, name string, args model.Values) error {\n\t\tif len(args) >= expected {\n\t\t\treturn nil\n\t\t}\n\t\treturn fmt.Errorf(\"func %q expects at least %d arguments, got %d\", name, expected, len(args))\n\t}\n}\n\n// ValidateArgsMax returns an ArgsValidator that validates that the number of arguments passed to a function is at most the expected number.\nfunc ValidateArgsMax(expected int) ArgsValidator {\n\treturn func(ctx context.Context, name string, args model.Values) error {\n\t\tif len(args) <= expected {\n\t\t\treturn nil\n\t\t}\n\t\treturn fmt.Errorf(\"func %q expects no more than %d arguments, got %d\", name, expected, len(args))\n\t}\n}\n\n// ValidateArgsMinMax returns an ArgsValidator that validates that the number of arguments passed to a function is between the min and max expected numbers.\nfunc ValidateArgsMinMax(min int, max int) ArgsValidator {\n\treturn func(ctx context.Context, name string, args model.Values) error {\n\t\tif len(args) >= min && len(args) <= max {\n\t\t\treturn nil\n\t\t}\n\t\treturn fmt.Errorf(\"func %q expects between %d and %d arguments, got %d\", name, min, max, len(args))\n\t}\n}\n\n// Func represents a function that can be executed.\ntype Func struct {\n\tname          string\n\thandler       FuncFn\n\targsValidator ArgsValidator\n}\n\n// Handler returns a FuncFn that can be used to execute the function.\nfunc (f *Func) Handler() FuncFn {\n\treturn func(ctx context.Context, data *model.Value, args model.Values) (*model.Value, error) {\n\t\tif f.argsValidator != nil {\n\t\t\tif err := f.argsValidator(ctx, f.name, args); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\tres, err := f.handler(ctx, data, args)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error execution func %q: %w\", f.name, err)\n\t\t}\n\t\treturn res, nil\n\t}\n}\n\n// NewFunc creates a new Func.\nfunc NewFunc(name string, handler FuncFn, argsValidator ArgsValidator) *Func {\n\treturn &Func{\n\t\tname:          name,\n\t\thandler:       handler,\n\t\targsValidator: argsValidator,\n\t}\n}\n\n// FuncFn is a function that can be executed.\ntype FuncFn func(ctx context.Context, data *model.Value, args model.Values) (*model.Value, error)\n\n// FuncCollection is a collection of functions that can be executed.\ntype FuncCollection map[string]FuncFn\n\n// NewFuncCollection creates a new FuncCollection with the given functions.\nfunc NewFuncCollection(funcs ...*Func) FuncCollection {\n\treturn FuncCollection{}.Register(funcs...)\n}\n\n// Register registers the given functions with the FuncCollection.\nfunc (fc FuncCollection) Register(funcs ...*Func) FuncCollection {\n\tfor _, f := range funcs {\n\t\tfc[f.name] = f.Handler()\n\t}\n\treturn fc\n}\n\n// Get returns the function with the given name.\nfunc (fc FuncCollection) Get(name string) (FuncFn, bool) {\n\tfn, ok := fc[name]\n\treturn fn, ok\n}\n\n// Delete deletes the functions with the given names.\nfunc (fc FuncCollection) Delete(names ...string) FuncCollection {\n\tfor _, name := range names {\n\t\tdelete(fc, name)\n\t}\n\treturn fc\n}\n\n// Copy returns a copy of the FuncCollection.\nfunc (fc FuncCollection) Copy() FuncCollection {\n\tc := NewFuncCollection()\n\tfor k, v := range fc {\n\t\tc[k] = v\n\t}\n\treturn c\n}\n"
  },
  {
    "path": "execution/func_add.go",
    "content": "package execution\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\n// FuncAdd is a function that adds the given values together.\nvar FuncAdd = NewFunc(\n\t\"add\",\n\tfunc(ctx context.Context, data *model.Value, args model.Values) (*model.Value, error) {\n\t\tvar foundInts, foundFloats int\n\t\tvar intRes int64\n\t\tvar floatRes float64\n\t\tfor _, arg := range args {\n\t\t\tif arg.IsFloat() {\n\t\t\t\tfoundFloats++\n\t\t\t\tv, err := arg.FloatValue()\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, fmt.Errorf(\"error getting float value: %w\", err)\n\t\t\t\t}\n\t\t\t\tfloatRes += v\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif arg.IsInt() {\n\t\t\t\tfoundInts++\n\t\t\t\tv, err := arg.IntValue()\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, fmt.Errorf(\"error getting int value: %w\", err)\n\t\t\t\t}\n\t\t\t\tintRes += v\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn nil, fmt.Errorf(\"expected int or float, got %s\", arg.Type())\n\t\t}\n\t\tif foundFloats > 0 {\n\t\t\treturn model.NewFloatValue(floatRes + float64(intRes)), nil\n\t\t}\n\t\treturn model.NewIntValue(intRes), nil\n\t},\n\tValidateArgsMin(1),\n)\n"
  },
  {
    "path": "execution/func_add_test.go",
    "content": "package execution_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"github.com/tomwright/dasel/v3/model/orderedmap\"\n)\n\nfunc TestFuncAdd(t *testing.T) {\n\tt.Run(\"int\", testCase{\n\t\ts:   `add(1, 2, 3)`,\n\t\tout: model.NewIntValue(6),\n\t}.run)\n\tt.Run(\"float\", testCase{\n\t\ts:   `add(1f, 2.5, 3.5)`,\n\t\tout: model.NewFloatValue(7),\n\t}.run)\n\tt.Run(\"mixed\", testCase{\n\t\ts:   `add(1, 2f)`,\n\t\tout: model.NewFloatValue(3),\n\t}.run)\n\tt.Run(\"properties\", func(t *testing.T) {\n\t\tin := func() *model.Value {\n\t\t\treturn model.NewValue(orderedmap.NewMap().\n\t\t\t\tSet(\"numbers\", orderedmap.NewMap().\n\t\t\t\t\tSet(\"one\", 1).\n\t\t\t\t\tSet(\"two\", 2).\n\t\t\t\t\tSet(\"three\", 3)).\n\t\t\t\tSet(\"nums\", []any{1, 2, 3}))\n\t\t}\n\t\tt.Run(\"nested props\", testCase{\n\t\t\tinFn: in,\n\t\t\ts:    `numbers.one + add(numbers.two, numbers.three)`,\n\t\t\tout:  model.NewIntValue(6),\n\t\t}.run)\n\t\tt.Run(\"add on end of chain\", testCase{\n\t\t\tinFn: in,\n\t\t\ts:    `numbers.one + numbers.add(two, three)`,\n\t\t\tout:  model.NewIntValue(6),\n\t\t}.run)\n\t\tt.Run(\"add with map and spread on slice with $this addition and grouping\", testCase{\n\t\t\tinFn: in,\n\t\t\ts:    `add(nums.map(($this + 1))...)`,\n\t\t\tout:  model.NewIntValue(9),\n\t\t}.run)\n\t\tt.Run(\"add with map and spread on slice with $this addition\", testCase{\n\t\t\tinFn: in,\n\t\t\ts:    `add(nums.map($this + 1 - 2)...)`,\n\t\t\tout:  model.NewIntValue(3),\n\t\t}.run)\n\t})\n}\n"
  },
  {
    "path": "execution/func_base64.go",
    "content": "package execution\n\nimport (\n\t\"context\"\n\t\"encoding/base64\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\n// FuncBase64Encode base64 encodes the given value.\nvar FuncBase64Encode = NewFunc(\n\t\"base64e\",\n\tfunc(ctx context.Context, data *model.Value, args model.Values) (*model.Value, error) {\n\t\targ := args[0]\n\t\tstrVal, err := arg.StringValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tout := base64.StdEncoding.EncodeToString([]byte(strVal))\n\t\treturn model.NewStringValue(out), nil\n\t},\n\tValidateArgsExactly(1),\n)\n\n// FuncBase64Decode base64 decodes the given value.\nvar FuncBase64Decode = NewFunc(\n\t\"base64d\",\n\tfunc(ctx context.Context, data *model.Value, args model.Values) (*model.Value, error) {\n\t\targ := args[0]\n\t\tstrVal, err := arg.StringValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tout, err := base64.StdEncoding.DecodeString(strVal)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn model.NewStringValue(string(out)), nil\n\t},\n\tValidateArgsExactly(1),\n)\n"
  },
  {
    "path": "execution/func_contains.go",
    "content": "package execution\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\n// FuncContains is a function that returns the highest number.\nvar FuncContains = NewFunc(\n\t\"contains\",\n\tfunc(ctx context.Context, data *model.Value, args model.Values) (*model.Value, error) {\n\t\tvar contains bool\n\n\t\ttarget := args[0]\n\n\t\tlength, err := data.SliceLen()\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error getting slice length: %w\", err)\n\t\t}\n\n\t\tfor i := 0; i < length; i++ {\n\t\t\tv, err := data.GetSliceIndex(i)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"error getting slice index %d: %w\", i, err)\n\t\t\t}\n\t\t\tmatches, err := v.Equal(target)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tmatchesBool, err := matches.BoolValue()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tif matchesBool {\n\t\t\t\tcontains = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\treturn model.NewBoolValue(contains), nil\n\t},\n\tValidateArgsExactly(1),\n)\n"
  },
  {
    "path": "execution/func_contains_test.go",
    "content": "package execution_test\n\nimport (\n\t\"github.com/tomwright/dasel/v3/model\"\n\t_ \"github.com/tomwright/dasel/v3/parsing/json\"\n\t\"testing\"\n)\n\nfunc TestFuncContains(t *testing.T) {\n\tt.Run(\"array true\", testCase{\n\t\ts:   `[1,2,3,4,5].contains(3)`,\n\t\tout: model.NewBoolValue(true),\n\t}.run)\n\tt.Run(\"array false\", testCase{\n\t\ts:   `[1,2,3,4,5].contains(6)`,\n\t\tout: model.NewBoolValue(false),\n\t}.run)\n}\n"
  },
  {
    "path": "execution/func_get.go",
    "content": "package execution\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\n// FuncGet is a function returns the value at the given key/index.\nvar FuncGet = NewFunc(\n\t\"get\",\n\tfunc(ctx context.Context, data *model.Value, args model.Values) (*model.Value, error) {\n\n\t\targ := args[0]\n\n\t\tswitch arg.Type() {\n\t\tcase model.TypeInt:\n\t\t\tindex, err := arg.IntValue()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\treturn data.GetSliceIndex(int(index))\n\t\tcase model.TypeString:\n\t\t\tkey, err := arg.StringValue()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\treturn data.GetMapKey(key)\n\t\tdefault:\n\t\t\treturn nil, fmt.Errorf(\"get expects string or int argument\")\n\t\t}\n\t},\n\tValidateArgsExactly(1),\n)\n"
  },
  {
    "path": "execution/func_get_test.go",
    "content": "package execution_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n\t_ \"github.com/tomwright/dasel/v3/parsing/json\"\n)\n\nfunc TestFuncGet(t *testing.T) {\n\tt.Run(\"returns array element\", testCase{\n\t\ts:   `[1,2,3,4,5].get(3)`,\n\t\tout: model.NewIntValue(4),\n\t}.run)\n\tt.Run(\"returns map key\", testCase{\n\t\ts:   `{\"a\": 3, \"b\": 4, \"c\": 5}.get(\"b\")`,\n\t\tout: model.NewIntValue(4),\n\t}.run)\n\tt.Run(\"coalesce with invalid map accessor\", testCase{\n\t\ts:   `{}.get(\"a\") ?? \"missing\"`,\n\t\tout: model.NewStringValue(\"missing\"),\n\t}.run)\n\tt.Run(\"returns null when string accessor used on slice\", testCase{\n\t\ts:   `[].get(0) ?? \"missing\"`,\n\t\tout: model.NewStringValue(\"missing\"),\n\t}.run)\n}\n"
  },
  {
    "path": "execution/func_has.go",
    "content": "package execution\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\n// FuncHas is a function that true or false if the input has the given key/index.\nvar FuncHas = NewFunc(\n\t\"has\",\n\tfunc(ctx context.Context, data *model.Value, args model.Values) (*model.Value, error) {\n\n\t\targ := args[0]\n\n\t\tswitch arg.Type() {\n\t\tcase model.TypeInt:\n\t\t\t// Given key is int, expect a slice.\n\t\t\tif data.Type() != model.TypeSlice {\n\t\t\t\treturn model.NewBoolValue(false), nil\n\t\t\t}\n\t\t\tindex, err := arg.IntValue()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tsliceLen, err := data.SliceLen()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\treturn model.NewBoolValue(index >= 0 && index < int64(sliceLen)), nil\n\t\tcase model.TypeString:\n\t\t\t// Given key is string, expect a map.\n\t\t\tif data.Type() != model.TypeMap {\n\t\t\t\treturn model.NewBoolValue(false), nil\n\t\t\t}\n\t\t\tkey, err := arg.StringValue()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\texists, err := data.MapKeyExists(key)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\treturn model.NewBoolValue(exists), nil\n\t\tdefault:\n\t\t\treturn nil, fmt.Errorf(\"has expects string or int argument\")\n\t\t}\n\t},\n\tValidateArgsMin(1),\n)\n"
  },
  {
    "path": "execution/func_has_test.go",
    "content": "package execution_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\nfunc TestFuncHas(t *testing.T) {\n\tt.Run(\"index in range\", testCase{\n\t\ts:   `[1,2,3].has(0)`,\n\t\tout: model.NewBoolValue(true),\n\t}.run)\n\tt.Run(\"negative index\", testCase{\n\t\ts:   `[1,2,3].has(-1)`,\n\t\tout: model.NewBoolValue(false),\n\t}.run)\n\tt.Run(\"index overflow\", testCase{\n\t\ts:   `[1,2,3].has(3)`,\n\t\tout: model.NewBoolValue(false),\n\t}.run)\n\tt.Run(\"index string\", testCase{\n\t\ts:   `[1,2,3].has(\"foo\")`,\n\t\tout: model.NewBoolValue(false),\n\t}.run)\n\tt.Run(\"has map key\", testCase{\n\t\ts:   `{\"x\":1}.has(\"x\")`,\n\t\tout: model.NewBoolValue(true),\n\t}.run)\n\tt.Run(\"does not have map key\", testCase{\n\t\ts:   `{\"x\":1}.has(\"y\")`,\n\t\tout: model.NewBoolValue(false),\n\t}.run)\n\tt.Run(\"does not have map index\", testCase{\n\t\ts:   `{\"x\":1}.has(1)`,\n\t\tout: model.NewBoolValue(false),\n\t}.run)\n}\n"
  },
  {
    "path": "execution/func_ignore.go",
    "content": "package execution\n\nimport (\n\t\"context\"\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\n// FuncIgnore is a function that ignores the value, causing it to be rejected from a branch.\nvar FuncIgnore = NewFunc(\n\t\"ignore\",\n\tfunc(ctx context.Context, data *model.Value, args model.Values) (*model.Value, error) {\n\t\tdata.MarkAsIgnore()\n\t\treturn data, nil\n\t},\n\tValidateArgsExactly(0),\n)\n"
  },
  {
    "path": "execution/func_join.go",
    "content": "package execution\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\n// FuncJoin is a function that joins the given data or args to a string.\nvar FuncJoin = NewFunc(\n\t\"join\",\n\tfunc(ctx context.Context, data *model.Value, args model.Values) (*model.Value, error) {\n\t\tseparator, err := args[0].StringValue()\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"join expects a string separator as the first argument: %w\", err)\n\t\t}\n\n\t\tvar valuesToJoin []string\n\n\t\tif len(args) == 2 && args[1].IsSlice() {\n\t\t\tif err := args[1].RangeSlice(func(i int, value *model.Value) error {\n\t\t\t\tstrVal, err := value.StringValue()\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"could not read string value of index %d: %w\", i, err)\n\t\t\t\t}\n\t\t\t\tvaluesToJoin = append(valuesToJoin, strVal)\n\t\t\t\treturn nil\n\t\t\t}); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t} else if len(args) > 1 {\n\t\t\t// Join the args\n\t\t\tfor i := 1; i < len(args); i++ {\n\t\t\t\tstrVal, err := args[i].StringValue()\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, fmt.Errorf(\"could not read string value of argument index %d: %w\", i, err)\n\t\t\t\t}\n\t\t\t\tvaluesToJoin = append(valuesToJoin, strVal)\n\t\t\t}\n\t\t} else {\n\t\t\tif err := data.RangeSlice(func(i int, value *model.Value) error {\n\t\t\t\tstrVal, err := value.StringValue()\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"could not read string value of index %d: %w\", i, err)\n\t\t\t\t}\n\t\t\t\tvaluesToJoin = append(valuesToJoin, strVal)\n\t\t\t\treturn nil\n\t\t\t}); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\n\t\tjoined := strings.Join(valuesToJoin, separator)\n\n\t\treturn model.NewStringValue(joined), nil\n\t},\n\tValidateArgsMin(1),\n)\n"
  },
  {
    "path": "execution/func_join_test.go",
    "content": "package execution_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n\t_ \"github.com/tomwright/dasel/v3/parsing/json\"\n)\n\nfunc TestFuncJoin(t *testing.T) {\n\tt.Run(\"chained input\", testCase{\n\t\ts:   `[\"a\",\"b\",\"c\"].join(\",\")`,\n\t\tout: model.NewStringValue(\"a,b,c\"),\n\t}.run)\n\tt.Run(\"vararg input\", testCase{\n\t\ts:   `join(\",\", \"a\", \"b\", \"c\")`,\n\t\tout: model.NewStringValue(\"a,b,c\"),\n\t}.run)\n\tt.Run(\"array input\", testCase{\n\t\ts:   `join(\",\", [\"a\", \"b\", \"c\"])`,\n\t\tout: model.NewStringValue(\"a,b,c\"),\n\t}.run)\n}\n"
  },
  {
    "path": "execution/func_keys.go",
    "content": "package execution\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\n// FuncKeys returns the keys of a map or the indices of a slice.\nvar FuncKeys = NewFunc(\n\t\"keys\",\n\tfunc(ctx context.Context, data *model.Value, args model.Values) (*model.Value, error) {\n\t\tswitch data.Type() {\n\t\tcase model.TypeMap:\n\t\t\tkeys, err := data.MapKeys()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tres := model.NewSliceValue()\n\t\t\tfor _, key := range keys {\n\t\t\t\tif err := res.Append(model.NewStringValue(key)); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn res, nil\n\t\tcase model.TypeSlice:\n\t\t\tlen, err := data.SliceLen()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tres := model.NewSliceValue()\n\t\t\tfor i := 0; i < len; i++ {\n\t\t\t\tif err := res.Append(model.NewIntValue(int64(i))); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn res, nil\n\t\tdefault:\n\t\t\treturn nil, fmt.Errorf(\"keys can only be used on maps and slices\")\n\t\t}\n\t},\n\tValidateArgsExactly(0),\n)\n"
  },
  {
    "path": "execution/func_keys_test.go",
    "content": "package execution_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n\t_ \"github.com/tomwright/dasel/v3/parsing/json\"\n)\n\nfunc TestFuncKeys(t *testing.T) {\n\tt.Run(\"returns slice indices\", testCase{\n\t\ts: `[1,2,3,4,5].keys()`,\n\t\toutFn: func() *model.Value {\n\t\t\tr := model.NewSliceValue()\n\t\t\tfor i := 0; i < 5; i++ {\n\t\t\t\tif err := r.Append(model.NewIntValue(int64(i))); err != nil {\n\t\t\t\t\tpanic(err)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn r\n\t\t},\n\t}.run)\n\tt.Run(\"returns map keys\", testCase{\n\t\ts: `{\"a\": 3, \"b\": 4, \"c\": 5}.keys()`,\n\t\toutFn: func() *model.Value {\n\t\t\tr := model.NewSliceValue()\n\t\t\tfor _, key := range []string{\"a\", \"b\", \"c\"} {\n\t\t\t\tif err := r.Append(model.NewStringValue(key)); err != nil {\n\t\t\t\t\tpanic(err)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn r\n\t\t},\n\t}.run)\n}\n"
  },
  {
    "path": "execution/func_len.go",
    "content": "package execution\n\nimport (\n\t\"context\"\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\n// FuncLen is a function that returns the length of the given value.\nvar FuncLen = NewFunc(\n\t\"len\",\n\tfunc(ctx context.Context, data *model.Value, args model.Values) (*model.Value, error) {\n\t\targ := args[0]\n\n\t\tl, err := arg.Len()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\treturn model.NewIntValue(int64(l)), nil\n\t},\n\tValidateArgsExactly(1),\n)\n"
  },
  {
    "path": "execution/func_len_test.go",
    "content": "package execution_test\n\nimport (\n\t\"github.com/tomwright/dasel/v3/model\"\n\t_ \"github.com/tomwright/dasel/v3/parsing/json\"\n\t\"testing\"\n)\n\nfunc TestFuncLen(t *testing.T) {\n\tt.Run(\"array\", testCase{\n\t\ts:   `len([1,2,3])`,\n\t\tout: model.NewIntValue(3),\n\t}.run)\n\tt.Run(\"object\", testCase{\n\t\ts:   `len({\"foo\":1,\"bar\":2,\"baz\":3})`,\n\t\tout: model.NewIntValue(3),\n\t}.run)\n\tt.Run(\"string\", testCase{\n\t\ts:   `len(\"hello\")`,\n\t\tout: model.NewIntValue(5),\n\t}.run)\n}\n"
  },
  {
    "path": "execution/func_max.go",
    "content": "package execution\n\nimport (\n\t\"context\"\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\n// FuncMax is a function that returns the highest number.\nvar FuncMax = NewFunc(\n\t\"max\",\n\tfunc(ctx context.Context, data *model.Value, args model.Values) (*model.Value, error) {\n\t\tres := model.NewNullValue()\n\t\tfor _, arg := range args {\n\t\t\tif res.IsNull() {\n\t\t\t\tres = arg\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tgt, err := arg.GreaterThan(res)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tgtBool, err := gt.BoolValue()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tif gtBool {\n\t\t\t\tres = arg\n\t\t\t}\n\t\t}\n\t\treturn res, nil\n\t},\n\tValidateArgsMin(1),\n)\n"
  },
  {
    "path": "execution/func_max_test.go",
    "content": "package execution_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\nfunc TestFuncMax(t *testing.T) {\n\tt.Run(\"int\", testCase{\n\t\ts:   `max(1, 2, 3)`,\n\t\tout: model.NewIntValue(3),\n\t}.run)\n\tt.Run(\"float\", testCase{\n\t\ts:   `max(1f, 2.5, 3.5)`,\n\t\tout: model.NewFloatValue(3.5),\n\t}.run)\n\tt.Run(\"mixed\", testCase{\n\t\ts:   `max(1, 2f)`,\n\t\tout: model.NewFloatValue(2),\n\t}.run)\n}\n"
  },
  {
    "path": "execution/func_merge.go",
    "content": "package execution\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\n// FuncMerge is a function that merges two or more items together.\nvar FuncMerge = NewFunc(\n\t\"merge\",\n\tfunc(ctx context.Context, data *model.Value, args model.Values) (*model.Value, error) {\n\t\tif len(args) == 1 {\n\t\t\treturn args[0], nil\n\t\t}\n\n\t\texpectedType := args[0].Type()\n\n\t\tswitch expectedType {\n\t\tcase model.TypeMap:\n\t\t\tbreak\n\t\tdefault:\n\t\t\treturn nil, fmt.Errorf(\"merge exects a map, found %s\", expectedType)\n\t\t}\n\n\t\t// Validate types match\n\t\tfor _, a := range args {\n\t\t\tif a.Type() != expectedType {\n\t\t\t\treturn nil, fmt.Errorf(\"merge expects all arguments to be of the same type. expected %s, got %s\", expectedType.String(), a.Type().String())\n\t\t\t}\n\t\t}\n\n\t\tbase := model.NewMapValue()\n\n\t\tfor i := 0; i < len(args); i++ {\n\t\t\tnext := args[i]\n\n\t\t\tnextKVs, err := next.MapKeyValues()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"merge failed to extract key values for arg %d: %w\", i, err)\n\t\t\t}\n\n\t\t\tfor _, kv := range nextKVs {\n\t\t\t\tif err := base.SetMapKey(kv.Key, kv.Value); err != nil {\n\t\t\t\t\treturn nil, fmt.Errorf(\"merge failed to set map key %s: %w\", kv.Key, err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn base, nil\n\t},\n\tValidateArgsMin(1),\n)\n"
  },
  {
    "path": "execution/func_merge_test.go",
    "content": "package execution_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\nfunc TestFuncMerge(t *testing.T) {\n\tt.Run(\"shallow\", testCase{\n\t\tinFn: func() *model.Value {\n\t\t\ta := model.NewMapValue()\n\t\t\tif err := a.SetMapKey(\"foo\", model.NewStringValue(\"afoo\")); err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif err := a.SetMapKey(\"bar\", model.NewStringValue(\"abar\")); err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tb := model.NewMapValue()\n\t\t\tif err := b.SetMapKey(\"bar\", model.NewStringValue(\"bbar\")); err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif err := b.SetMapKey(\"baz\", model.NewStringValue(\"bbaz\")); err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tres := model.NewMapValue()\n\t\t\tif err := res.SetMapKey(\"a\", a); err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif err := res.SetMapKey(\"b\", b); err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\treturn res\n\t\t},\n\t\ts: `merge(a, b)`,\n\t\toutFn: func() *model.Value {\n\t\t\tb := model.NewMapValue()\n\t\t\tif err := b.SetMapKey(\"foo\", model.NewStringValue(\"afoo\")); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif err := b.SetMapKey(\"bar\", model.NewStringValue(\"bbar\")); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif err := b.SetMapKey(\"baz\", model.NewStringValue(\"bbaz\")); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\treturn b\n\t\t},\n\t}.run)\n}\n"
  },
  {
    "path": "execution/func_min.go",
    "content": "package execution\n\nimport (\n\t\"context\"\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\n// FuncMin is a function that returns the smalled number.\nvar FuncMin = NewFunc(\n\t\"min\",\n\tfunc(ctx context.Context, data *model.Value, args model.Values) (*model.Value, error) {\n\t\tres := model.NewNullValue()\n\t\tfor _, arg := range args {\n\t\t\tif res.IsNull() {\n\t\t\t\tres = arg\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tlt, err := arg.LessThan(res)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tltBool, err := lt.BoolValue()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tif ltBool {\n\t\t\t\tres = arg\n\t\t\t}\n\t\t}\n\t\treturn res, nil\n\t},\n\tValidateArgsMin(1),\n)\n"
  },
  {
    "path": "execution/func_min_test.go",
    "content": "package execution_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\nfunc TestFuncMin(t *testing.T) {\n\tt.Run(\"int\", testCase{\n\t\ts:   `min(1, 2, 3)`,\n\t\tout: model.NewIntValue(1),\n\t}.run)\n\tt.Run(\"float\", testCase{\n\t\ts:   `min(1f, 2.5, 3.5)`,\n\t\tout: model.NewFloatValue(1),\n\t}.run)\n\tt.Run(\"mixed\", testCase{\n\t\ts:   `min(1, 2f)`,\n\t\tout: model.NewIntValue(1),\n\t}.run)\n}\n"
  },
  {
    "path": "execution/func_parse.go",
    "content": "package execution\n\nimport (\n\t\"context\"\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"github.com/tomwright/dasel/v3/parsing\"\n)\n\n// FuncParse parses the given data at runtime.\nvar FuncParse = NewFunc(\n\t\"parse\",\n\tfunc(ctx context.Context, data *model.Value, args model.Values) (*model.Value, error) {\n\t\tvar format parsing.Format\n\t\tvar content []byte\n\t\t{\n\t\t\tstrVal, err := args[0].StringValue()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tformat = parsing.Format(strVal)\n\t\t}\n\t\t{\n\t\t\tstrVal, err := args[1].StringValue()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tcontent = []byte(strVal)\n\t\t}\n\n\t\treader, err := format.NewReader(parsing.DefaultReaderOptions())\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tdoc, err := reader.Read(content)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\treturn doc, nil\n\t},\n\tValidateArgsExactly(2),\n)\n"
  },
  {
    "path": "execution/func_parse_test.go",
    "content": "package execution_test\n\nimport (\n\t\"github.com/tomwright/dasel/v3/model\"\n\t_ \"github.com/tomwright/dasel/v3/parsing/json\"\n\t\"testing\"\n)\n\nfunc TestFuncParse(t *testing.T) {\n\tt.Run(\"json\", testCase{\n\t\ts:   `parse('json', '{\"foo\":\"bar\"}').foo`,\n\t\tout: model.NewStringValue(\"bar\"),\n\t}.run)\n}\n"
  },
  {
    "path": "execution/func_readfile.go",
    "content": "package execution\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"io\"\n\t\"os\"\n)\n\n// FuncReadFile reads the given filepath at runtime.\nvar FuncReadFile = NewFunc(\n\t\"readFile\",\n\tfunc(ctx context.Context, data *model.Value, args model.Values) (*model.Value, error) {\n\t\tfilepath, err := args[0].StringValue()\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"readFile: %w\", err)\n\t\t}\n\n\t\tf, err := os.Open(filepath)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"readFile: %w\", err)\n\t\t}\n\t\tdefer func() {\n\t\t\t_ = f.Close()\n\t\t}()\n\n\t\tfileBytes, err := io.ReadAll(f)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"readFile: %w\", err)\n\t\t}\n\n\t\treturn model.NewStringValue(string(fileBytes)), nil\n\t},\n\tValidateArgsExactly(1),\n)\n"
  },
  {
    "path": "execution/func_replace.go",
    "content": "package execution\n\nimport (\n\t\"context\"\n\t\"strings\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\n// FuncReplace is a function that replaces all occurrences of a substring with another string.\nvar FuncReplace = NewFunc(\n\t\"replace\",\n\tfunc(ctx context.Context, data *model.Value, args model.Values) (*model.Value, error) {\n\t\tinputData := data\n\t\tif len(args)%2 != 0 {\n\t\t\tinputData = args[0]\n\t\t\targs = args[1:]\n\t\t}\n\n\t\targStrings := make([]string, len(args))\n\t\tfor i, arg := range args {\n\t\t\ts, err := arg.StringValue()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\targStrings[i] = s\n\t\t}\n\t\treplacer := strings.NewReplacer(argStrings...)\n\n\t\tinputString, err := inputData.StringValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\toutputString := replacer.Replace(inputString)\n\n\t\treturn model.NewStringValue(outputString), nil\n\t},\n\tValidateArgsMin(2),\n)\n"
  },
  {
    "path": "execution/func_replace_test.go",
    "content": "package execution_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\nfunc TestFuncReplace(t *testing.T) {\n\tt.Run(\"input arg\", testCase{\n\t\ts:   `replace(\"hello world\", \"world\", \"there\")`,\n\t\tout: model.NewStringValue(\"hello there\"),\n\t}.run)\n\n\tt.Run(\"multiple data arg\", testCase{\n\t\ts:   `replace(\"hello world\", \"o\", \"0\", \"l\", \"1\")`,\n\t\tout: model.NewStringValue(\"he110 w0r1d\"),\n\t}.run)\n\n\tt.Run(\"data arg\", testCase{\n\t\ts:   `\"hello world\".replace(\"o\", \"0\")`,\n\t\tout: model.NewStringValue(\"hell0 w0rld\"),\n\t}.run)\n\n\tt.Run(\"multiple data arg\", testCase{\n\t\ts:   `\"hello world\".replace(\"o\", \"0\", \"h\", \"H\")`,\n\t\tout: model.NewStringValue(\"Hell0 w0rld\"),\n\t}.run)\n\n\tt.Run(\"data arg with input arg ignores data arg\", testCase{\n\t\ts:   `\"bob\".replace(\"hello world\", \"o\", \"0\", \"world\", \"there\")`,\n\t\tout: model.NewStringValue(\"hell0 there\"),\n\t}.run)\n}\n"
  },
  {
    "path": "execution/func_reverse.go",
    "content": "package execution\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\n// FuncReverse is a function that reverses the input.\nvar FuncReverse = NewFunc(\n\t\"reverse\",\n\tfunc(ctx context.Context, data *model.Value, args model.Values) (*model.Value, error) {\n\t\targ := args[0]\n\n\t\tswitch arg.Type() {\n\t\tcase model.TypeString:\n\t\t\treturn arg.StringIndexRange(-1, 0)\n\t\tcase model.TypeSlice:\n\t\t\treturn arg.SliceIndexRange(-1, 0)\n\t\tdefault:\n\t\t\treturn nil, fmt.Errorf(\"reverse expects a slice or string, got %s\", arg.Type())\n\t\t}\n\t},\n\tValidateArgsExactly(1),\n)\n"
  },
  {
    "path": "execution/func_reverse_test.go",
    "content": "package execution_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\nfunc TestFuncReverse(t *testing.T) {\n\tt.Run(\"array\", testCase{\n\t\ts: `reverse([1, 2, 3])`,\n\t\toutFn: func() *model.Value {\n\t\t\tres := model.NewSliceValue()\n\t\t\tif err := res.Append(model.NewIntValue(3)); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif err := res.Append(model.NewIntValue(2)); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif err := res.Append(model.NewIntValue(1)); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\treturn res\n\t\t},\n\t}.run)\n\n\tt.Run(\"string\", testCase{\n\t\ts:   `reverse(\"hello\")`,\n\t\tout: model.NewStringValue(\"olleh\"),\n\t}.run)\n}\n"
  },
  {
    "path": "execution/func_sum.go",
    "content": "package execution\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\n// FuncSum is a function that returns the sum of the given numbers.\nvar FuncSum = NewFunc(\n\t\"sum\",\n\tfunc(ctx context.Context, data *model.Value, args model.Values) (*model.Value, error) {\n\t\treturnType := model.TypeInt\n\n\t\tfor _, arg := range args {\n\t\t\tif arg.IsInt() {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif arg.IsFloat() {\n\t\t\t\treturnType = model.TypeFloat\n\t\t\t\tbreak\n\t\t\t}\n\t\t\treturn nil, fmt.Errorf(\"cannot sum non-numeric value of type %s\", arg.Type().String())\n\t\t}\n\n\t\tswitch returnType {\n\t\tcase model.TypeInt:\n\t\t\tvar sum int64\n\t\t\tfor _, arg := range args {\n\t\t\t\tif arg.IsInt() {\n\t\t\t\t\tintVal, err := arg.IntValue()\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t}\n\t\t\t\t\tsum += intVal\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tfloatVal, err := arg.FloatValue()\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tsum += int64(floatVal)\n\t\t\t}\n\t\t\treturn model.NewIntValue(sum), nil\n\t\tcase model.TypeFloat:\n\t\t\tvar sum float64\n\t\t\tfor _, arg := range args {\n\t\t\t\tif arg.IsInt() {\n\t\t\t\t\tintVal, err := arg.IntValue()\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t}\n\t\t\t\t\tsum += float64(intVal)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tfloatVal, err := arg.FloatValue()\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tsum += floatVal\n\t\t\t}\n\t\t\treturn model.NewFloatValue(sum), nil\n\t\tdefault:\n\t\t\treturn nil, fmt.Errorf(\"unsupported return type %s\", returnType.String())\n\t\t}\n\t},\n\tValidateArgsMin(1),\n)\n"
  },
  {
    "path": "execution/func_sum_test.go",
    "content": "package execution_test\n\nimport (\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"testing\"\n)\n\nfunc TestFuncSum(t *testing.T) {\n\tt.Run(\"int\", testCase{\n\t\ts:   `sum(1, 2, 3)`,\n\t\tout: model.NewIntValue(6),\n\t}.run)\n\tt.Run(\"float\", testCase{\n\t\ts:   `sum(1.1, 2.2, 3.3)`,\n\t\tout: model.NewFloatValue(6.6),\n\t}.run)\n\tt.Run(\"negative int\", testCase{\n\t\ts:   `sum(-1, -2, -3)`,\n\t\tout: model.NewIntValue(-6),\n\t}.run)\n\tt.Run(\"negative float\", testCase{\n\t\ts:   `sum(-1.1, -2.2, -3.3)`,\n\t\tout: model.NewFloatValue(-6.6),\n\t}.run)\n\tt.Run(\"using int and float together returns float\", testCase{\n\t\ts:   `sum(1, 1.1)`,\n\t\tout: model.NewFloatValue(2.1),\n\t}.run)\n}\n"
  },
  {
    "path": "execution/func_to_float.go",
    "content": "package execution\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strconv\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\n// FuncToFloat is a function that converts the given value to a string.\nvar FuncToFloat = NewFunc(\n\t\"toFloat\",\n\tfunc(ctx context.Context, data *model.Value, args model.Values) (*model.Value, error) {\n\t\tswitch args[0].Type() {\n\t\tcase model.TypeString:\n\t\t\tstringValue, err := args[0].StringValue()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\ti, err := strconv.ParseFloat(stringValue, 64)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\treturn model.NewFloatValue(i), nil\n\t\tcase model.TypeInt:\n\t\t\ti, err := args[0].IntValue()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\treturn model.NewFloatValue(float64(i)), nil\n\t\tcase model.TypeFloat:\n\t\t\treturn args[0], nil\n\t\tcase model.TypeBool:\n\t\t\ti, err := args[0].BoolValue()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tif i {\n\t\t\t\treturn model.NewFloatValue(1), nil\n\t\t\t}\n\t\t\treturn model.NewFloatValue(0), nil\n\t\tdefault:\n\t\t\treturn nil, fmt.Errorf(\"cannot convert %s to float\", args[0].Type())\n\t\t}\n\t},\n\tValidateArgsExactly(1),\n)\n"
  },
  {
    "path": "execution/func_to_float_test.go",
    "content": "package execution_test\n\nimport (\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"testing\"\n)\n\nfunc TestFuncToFloat(t *testing.T) {\n\tt.Run(\"string\", testCase{\n\t\ts:   `toFloat(\"1.1\")`,\n\t\tout: model.NewFloatValue(1.1),\n\t}.run)\n\tt.Run(\"int\", testCase{\n\t\ts:   `toFloat(1)`,\n\t\tout: model.NewFloatValue(1),\n\t}.run)\n\tt.Run(\"float\", testCase{\n\t\ts:   `toFloat(1.1)`,\n\t\tout: model.NewFloatValue(1.1),\n\t}.run)\n\tt.Run(\"bool\", testCase{\n\t\ts:   `toFloat(true)`,\n\t\tout: model.NewFloatValue(1),\n\t}.run)\n}\n"
  },
  {
    "path": "execution/func_to_int.go",
    "content": "package execution\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strconv\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\n// FuncToInt is a function that converts the given value to a string.\nvar FuncToInt = NewFunc(\n\t\"toInt\",\n\tfunc(ctx context.Context, data *model.Value, args model.Values) (*model.Value, error) {\n\t\tswitch args[0].Type() {\n\t\tcase model.TypeString:\n\t\t\tstringValue, err := args[0].StringValue()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\ti, err := strconv.ParseInt(stringValue, 10, 64)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\treturn model.NewIntValue(i), nil\n\t\tcase model.TypeInt:\n\t\t\treturn args[0], nil\n\t\tcase model.TypeFloat:\n\t\t\ti, err := args[0].FloatValue()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\treturn model.NewIntValue(int64(i)), nil\n\t\tcase model.TypeBool:\n\t\t\ti, err := args[0].BoolValue()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tif i {\n\t\t\t\treturn model.NewIntValue(1), nil\n\t\t\t}\n\t\t\treturn model.NewIntValue(0), nil\n\t\tdefault:\n\t\t\treturn nil, fmt.Errorf(\"cannot convert %s to int\", args[0].Type())\n\t\t}\n\t},\n\tValidateArgsExactly(1),\n)\n"
  },
  {
    "path": "execution/func_to_int_test.go",
    "content": "package execution_test\n\nimport (\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"testing\"\n)\n\nfunc TestFuncToInt(t *testing.T) {\n\tt.Run(\"string\", testCase{\n\t\ts:   `toInt(\"2\")`,\n\t\tout: model.NewIntValue(2),\n\t}.run)\n\tt.Run(\"int\", testCase{\n\t\ts:   `toInt(1)`,\n\t\tout: model.NewIntValue(1),\n\t}.run)\n\tt.Run(\"float\", testCase{\n\t\ts:   `toInt(1.1)`,\n\t\tout: model.NewIntValue(1),\n\t}.run)\n\tt.Run(\"bool\", testCase{\n\t\ts:   `toInt(true)`,\n\t\tout: model.NewIntValue(1),\n\t}.run)\n}\n"
  },
  {
    "path": "execution/func_to_string.go",
    "content": "package execution\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\n// FuncToString is a function that converts the given value to a string.\nvar FuncToString = NewFunc(\n\t\"toString\",\n\tfunc(ctx context.Context, data *model.Value, args model.Values) (*model.Value, error) {\n\t\tswitch args[0].Type() {\n\t\tcase model.TypeString:\n\t\t\tstringValue, err := args[0].StringValue()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tmodel.NewStringValue(stringValue)\n\t\t\treturn args[0], nil\n\t\tcase model.TypeInt:\n\t\t\ti, err := args[0].IntValue()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\treturn model.NewStringValue(fmt.Sprintf(\"%d\", i)), nil\n\t\tcase model.TypeFloat:\n\t\t\ti, err := args[0].FloatValue()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\treturn model.NewStringValue(fmt.Sprintf(\"%g\", i)), nil\n\t\tcase model.TypeBool:\n\t\t\ti, err := args[0].BoolValue()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\treturn model.NewStringValue(fmt.Sprintf(\"%v\", i)), nil\n\t\tdefault:\n\t\t\treturn nil, fmt.Errorf(\"cannot convert %s to string\", args[0].Type())\n\t\t}\n\t},\n\tValidateArgsExactly(1),\n)\n"
  },
  {
    "path": "execution/func_to_string_test.go",
    "content": "package execution_test\n\nimport (\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"testing\"\n)\n\nfunc TestFuncToString(t *testing.T) {\n\tt.Run(\"string\", testCase{\n\t\ts:   `toString(\"Hello\")`,\n\t\tout: model.NewStringValue(\"Hello\"),\n\t}.run)\n\tt.Run(\"int\", testCase{\n\t\ts:   `toString(1)`,\n\t\tout: model.NewStringValue(\"1\"),\n\t}.run)\n\tt.Run(\"float\", testCase{\n\t\ts:   `toString(1.1)`,\n\t\tout: model.NewStringValue(\"1.1\"),\n\t}.run)\n\tt.Run(\"bool\", testCase{\n\t\ts:   `toString(true)`,\n\t\tout: model.NewStringValue(\"true\"),\n\t}.run)\n}\n"
  },
  {
    "path": "execution/func_type_of.go",
    "content": "package execution\n\nimport (\n\t\"context\"\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\n// FuncTypeOf is a function that returns the type of the first argument as a string.\nvar FuncTypeOf = NewFunc(\n\t\"typeOf\",\n\tfunc(ctx context.Context, data *model.Value, args model.Values) (*model.Value, error) {\n\t\treturn model.NewStringValue(args[0].Type().String()), nil\n\t},\n\tValidateArgsExactly(1),\n)\n"
  },
  {
    "path": "execution/func_type_of_test.go",
    "content": "package execution_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\nfunc TestFuncTypeOf(t *testing.T) {\n\tt.Run(\"string\", testCase{\n\t\ts:   `typeOf(\"hello\")`,\n\t\tout: model.NewStringValue(\"string\"),\n\t}.run)\n\tt.Run(\"int\", testCase{\n\t\ts:   `typeOf(123)`,\n\t\tout: model.NewStringValue(\"int\"),\n\t}.run)\n\tt.Run(\"float\", testCase{\n\t\ts:   `typeOf(12.3)`,\n\t\tout: model.NewStringValue(\"float\"),\n\t}.run)\n\tt.Run(\"bool\", testCase{\n\t\ts:   `typeOf(true)`,\n\t\tout: model.NewStringValue(\"bool\"),\n\t}.run)\n\tt.Run(\"array\", testCase{\n\t\ts:   `typeOf([])`,\n\t\tout: model.NewStringValue(\"array\"),\n\t}.run)\n\tt.Run(\"map\", testCase{\n\t\ts:   `typeOf({})`,\n\t\tout: model.NewStringValue(\"map\"),\n\t}.run)\n\tt.Run(\"null\", testCase{\n\t\ts:   `typeOf(null)`,\n\t\tout: model.NewStringValue(\"null\"),\n\t}.run)\n}\n"
  },
  {
    "path": "execution/options.go",
    "content": "package execution\n\nimport \"github.com/tomwright/dasel/v3/model\"\n\n// ExecuteOptionFn is a function that can be used to set options on the execution of the selector.\ntype ExecuteOptionFn func(*Options)\n\n// Options contains the options for the execution of the selector.\ntype Options struct {\n\tFuncs    FuncCollection\n\tVars     map[string]*model.Value\n\tUnstable bool\n}\n\n// NewOptions creates a new Options struct with the given options.\nfunc NewOptions(opts ...ExecuteOptionFn) *Options {\n\to := &Options{\n\t\tFuncs: DefaultFuncCollection,\n\t\tVars:  map[string]*model.Value{},\n\t}\n\tfor _, opt := range opts {\n\t\tif opt == nil {\n\t\t\tcontinue\n\t\t}\n\t\topt(o)\n\t}\n\treturn o\n}\n\n// WithFuncs sets the functions that can be used in the selector.\nfunc WithFuncs(fc FuncCollection) ExecuteOptionFn {\n\treturn func(o *Options) {\n\t\to.Funcs = fc\n\t}\n}\n\n// WithVariable sets a variable for use in the selector.\nfunc WithVariable(key string, val *model.Value) ExecuteOptionFn {\n\treturn func(o *Options) {\n\t\to.Vars[key] = val\n\t}\n}\n\n// WithUnstable allows access to potentially unstable features.\nfunc WithUnstable() ExecuteOptionFn {\n\treturn func(o *Options) {\n\t\to.Unstable = true\n\t}\n}\n\n// WithoutUnstable disallows access to potentially unstable features.\nfunc WithoutUnstable() ExecuteOptionFn {\n\treturn func(o *Options) {\n\t\to.Unstable = false\n\t}\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/tomwright/dasel/v3\n\ngo 1.25\n\nrequire (\n\tgithub.com/alecthomas/kong v1.14.0\n\tgithub.com/charmbracelet/bubbles v1.0.0\n\tgithub.com/charmbracelet/bubbletea v1.3.10\n\tgithub.com/charmbracelet/lipgloss v1.1.0\n\tgithub.com/goccy/go-json v0.10.5\n\tgithub.com/google/go-cmp v0.7.0\n\tgithub.com/hashicorp/hcl/v2 v2.24.0\n\tgithub.com/pelletier/go-toml/v2 v2.2.5-0.20250826075308-a0e846496753\n\tgithub.com/zclconf/go-cty v1.17.0\n\tgo.yaml.in/yaml/v4 v4.0.0-rc.3\n\tgopkg.in/ini.v1 v1.67.0\n)\n\nrequire (\n\tgithub.com/agext/levenshtein v1.2.1 // indirect\n\tgithub.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect\n\tgithub.com/atotto/clipboard v0.1.4 // indirect\n\tgithub.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect\n\tgithub.com/charmbracelet/colorprofile v0.4.1 // indirect\n\tgithub.com/charmbracelet/x/ansi v0.11.6 // indirect\n\tgithub.com/charmbracelet/x/cellbuf v0.0.15 // indirect\n\tgithub.com/charmbracelet/x/term v0.2.2 // indirect\n\tgithub.com/clipperhouse/displaywidth v0.9.0 // indirect\n\tgithub.com/clipperhouse/stringish v0.1.1 // indirect\n\tgithub.com/clipperhouse/uax29/v2 v2.5.0 // indirect\n\tgithub.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect\n\tgithub.com/lucasb-eyer/go-colorful v1.3.0 // indirect\n\tgithub.com/mattn/go-isatty v0.0.20 // indirect\n\tgithub.com/mattn/go-localereader v0.0.1 // indirect\n\tgithub.com/mattn/go-runewidth v0.0.19 // indirect\n\tgithub.com/mitchellh/go-wordwrap v1.0.1 // indirect\n\tgithub.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect\n\tgithub.com/muesli/cancelreader v0.2.2 // indirect\n\tgithub.com/muesli/termenv v0.16.0 // indirect\n\tgithub.com/rivo/uniseg v0.4.7 // indirect\n\tgithub.com/stretchr/testify v1.11.1 // indirect\n\tgithub.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect\n\tgolang.org/x/mod v0.26.0 // indirect\n\tgolang.org/x/sync v0.16.0 // indirect\n\tgolang.org/x/sys v0.38.0 // indirect\n\tgolang.org/x/text v0.28.0 // indirect\n\tgolang.org/x/tools v0.35.0 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=\ngithub.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=\ngithub.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8=\ngithub.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=\ngithub.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=\ngithub.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=\ngithub.com/alecthomas/kong v1.14.0 h1:gFgEUZWu2ZmZ+UhyZ1bDhuutbKN1nTtJTwh19Wsn21s=\ngithub.com/alecthomas/kong v1.14.0/go.mod h1:wrlbXem1CWqUV5Vbmss5ISYhsVPkBb1Yo7YKJghju2I=\ngithub.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs=\ngithub.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=\ngithub.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=\ngithub.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=\ngithub.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=\ngithub.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=\ngithub.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=\ngithub.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=\ngithub.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY=\ngithub.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E=\ngithub.com/charmbracelet/bubbles v1.0.0 h1:12J8/ak/uCZEMQ6KU7pcfwceyjLlWsDLAxB5fXonfvc=\ngithub.com/charmbracelet/bubbles v1.0.0/go.mod h1:9d/Zd5GdnauMI5ivUIVisuEm3ave1XwXtD1ckyV6r3E=\ngithub.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=\ngithub.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=\ngithub.com/charmbracelet/colorprofile v0.4.1 h1:a1lO03qTrSIRaK8c3JRxJDZOvhvIeSco3ej+ngLk1kk=\ngithub.com/charmbracelet/colorprofile v0.4.1/go.mod h1:U1d9Dljmdf9DLegaJ0nGZNJvoXAhayhmidOdcBwAvKk=\ngithub.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=\ngithub.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=\ngithub.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8=\ngithub.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ=\ngithub.com/charmbracelet/x/cellbuf v0.0.15 h1:ur3pZy0o6z/R7EylET877CBxaiE1Sp1GMxoFPAIztPI=\ngithub.com/charmbracelet/x/cellbuf v0.0.15/go.mod h1:J1YVbR7MUuEGIFPCaaZ96KDl5NoS0DAWkskup+mOY+Q=\ngithub.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=\ngithub.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=\ngithub.com/clipperhouse/displaywidth v0.9.0 h1:Qb4KOhYwRiN3viMv1v/3cTBlz3AcAZX3+y9OLhMtAtA=\ngithub.com/clipperhouse/displaywidth v0.9.0/go.mod h1:aCAAqTlh4GIVkhQnJpbL0T/WfcrJXHcj8C0yjYcjOZA=\ngithub.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=\ngithub.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=\ngithub.com/clipperhouse/uax29/v2 v2.5.0 h1:x7T0T4eTHDONxFJsL94uKNKPHrclyFI0lm7+w94cO8U=\ngithub.com/clipperhouse/uax29/v2 v2.5.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=\ngithub.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=\ngithub.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=\ngithub.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=\ngithub.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=\ngithub.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=\ngithub.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=\ngithub.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=\ngithub.com/hashicorp/hcl/v2 v2.24.0 h1:2QJdZ454DSsYGoaE6QheQZjtKZSUs9Nh2izTWiwQxvE=\ngithub.com/hashicorp/hcl/v2 v2.24.0/go.mod h1:oGoO1FIQYfn/AgyOhlg9qLC6/nOJPX3qGbkZpYAcqfM=\ngithub.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=\ngithub.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=\ngithub.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=\ngithub.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=\ngithub.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=\ngithub.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=\ngithub.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=\ngithub.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=\ngithub.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=\ngithub.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=\ngithub.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=\ngithub.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=\ngithub.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=\ngithub.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=\ngithub.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=\ngithub.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=\ngithub.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=\ngithub.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=\ngithub.com/pelletier/go-toml/v2 v2.2.5-0.20250826075308-a0e846496753 h1:aTpyfgn3dz2npHl011BHQehdSavqjzhZdE6fJuJlO3A=\ngithub.com/pelletier/go-toml/v2 v2.2.5-0.20250826075308-a0e846496753/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=\ngithub.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=\ngithub.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=\ngithub.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=\ngithub.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=\ngithub.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=\ngithub.com/zclconf/go-cty v1.17.0 h1:seZvECve6XX4tmnvRzWtJNHdscMtYEx5R7bnnVyd/d0=\ngithub.com/zclconf/go-cty v1.17.0/go.mod h1:wqFzcImaLTI6A5HfsRwB0nj5n0MRZFwmey8YoFPPs3U=\ngithub.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo=\ngithub.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=\ngo.yaml.in/yaml/v4 v4.0.0-rc.3 h1:3h1fjsh1CTAPjW7q/EMe+C8shx5d8ctzZTrLcs/j8Go=\ngo.yaml.in/yaml/v4 v4.0.0-rc.3/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=\ngolang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=\ngolang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=\ngolang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=\ngolang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=\ngolang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=\ngolang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=\ngolang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=\ngolang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=\ngolang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=\ngolang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=\ngolang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=\ngolang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=\ngopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=\ngopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\n"
  },
  {
    "path": "internal/cli/command.go",
    "content": "package cli\n\nimport (\n\t\"errors\"\n\t\"io\"\n\t\"reflect\"\n\n\t\"github.com/alecthomas/kong\"\n\t\"github.com/tomwright/dasel/v3/internal\"\n)\n\nvar ErrNoArgsGiven = errors.New(\"no arguments given\")\n\ntype Globals struct {\n\tStdin       io.Reader        `kong:\"-\"`\n\tStdout      io.Writer        `kong:\"-\"`\n\tStderr      io.Writer        `kong:\"-\"`\n\thelpPrinter kong.HelpPrinter `kong:\"-\"`\n}\n\ntype CLI struct {\n\tGlobals\n\n\tQuery       QueryCmd       `cmd:\"\" default:\"withargs\" help:\"[default] Execute a query\"`\n\tVersion     VersionCmd     `cmd:\"\" help:\"Print the version\"`\n\tInteractive InteractiveCmd `cmd:\"\" help:\"Start an interactive session (alpha)\"`\n}\n\nfunc MustRun(stdin io.Reader, stdout, stderr io.Writer) {\n\tctx, err := Run(stdin, stdout, stderr)\n\tif err == nil {\n\t\treturn\n\t}\n\n\tif ctx == nil {\n\t\tpanic(err)\n\t}\n\n\tctx.Errorf(\"%s\", err.Error())\n\tif errors.Is(err, ErrNoArgsGiven) {\n\t\tif err := ctx.PrintUsage(false); err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t}\n\n\tctx.Exit(1)\n}\n\nfunc Run(stdin io.Reader, stdout, stderr io.Writer) (*kong.Context, error) {\n\tcli := &CLI{\n\t\tGlobals: Globals{\n\t\t\tStdin:       stdin,\n\t\t\tStdout:      stdout,\n\t\t\tStderr:      stderr,\n\t\t\thelpPrinter: kong.DefaultHelpPrinter,\n\t\t},\n\t}\n\n\tctx := kong.Parse(\n\t\tcli,\n\t\tkong.Name(\"dasel\"),\n\t\tkong.Description(\"Query and modify data structures from the command line.\"),\n\t\tkong.UsageOnError(),\n\t\tkong.ConfigureHelp(kong.HelpOptions{Compact: true}),\n\t\tkong.Vars{\n\t\t\t\"version\": internal.Version,\n\t\t},\n\t\tkong.Bind(&cli.Globals),\n\t\tkong.TypeMapper(reflect.TypeFor[variables](), &variableMapper{}),\n\t\tkong.TypeMapper(reflect.TypeFor[extReadWriteFlags](), &extReadWriteFlagMapper{}),\n\t\tkong.OptionFunc(func(k *kong.Kong) error {\n\t\t\tk.Stdout = cli.Stdout\n\t\t\tk.Stderr = cli.Stderr\n\t\t\treturn nil\n\t\t}),\n\t\tkong.Help(cli.helpPrinter),\n\t)\n\terr := ctx.Run()\n\treturn ctx, err\n}\n"
  },
  {
    "path": "internal/cli/command_test.go",
    "content": "package cli_test\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"os\"\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/internal/cli\"\n)\n\nfunc runDasel(args []string, in []byte) ([]byte, []byte, error) {\n\tstdOut := bytes.NewBuffer([]byte{})\n\tstdErr := bytes.NewBuffer([]byte{})\n\tstdIn := bytes.NewReader(in)\n\n\toriginalArgs := os.Args\n\tdefer func() {\n\t\tos.Args = originalArgs\n\t}()\n\n\tos.Args = append([]string{\"dasel\", \"query\"}, args...)\n\n\t_, err := cli.Run(stdIn, stdOut, stdErr)\n\n\treturn stdOut.Bytes(), stdErr.Bytes(), err\n}\n\ntype testCase struct {\n\targs   []string\n\tin     []byte\n\tstdout []byte\n\tstderr []byte\n\terr    error\n}\n\nfunc runTest(tc testCase) func(t *testing.T) {\n\treturn func(t *testing.T) {\n\t\tif tc.stdout == nil {\n\t\t\ttc.stdout = []byte{}\n\t\t}\n\t\tif tc.stderr == nil {\n\t\t\ttc.stderr = []byte{}\n\t\t}\n\n\t\tgotStdOut, gotStdErr, gotErr := runDasel(tc.args, tc.in)\n\t\tif !errors.Is(gotErr, tc.err) && !errors.Is(tc.err, gotErr) {\n\t\t\tt.Errorf(\"expected error %v, got %v\", tc.err, gotErr)\n\t\t\treturn\n\t\t}\n\n\t\tif !reflect.DeepEqual(tc.stderr, gotStdErr) {\n\t\t\tt.Errorf(\"expected stderr %s, got %s\", string(tc.stderr), string(gotStdErr))\n\t\t}\n\n\t\tif !reflect.DeepEqual(tc.stdout, gotStdOut) {\n\t\t\tt.Errorf(\"expected stdout %s, got %s\", string(tc.stdout), string(gotStdOut))\n\t\t}\n\t}\n}\n\nfunc TestRun(t *testing.T) {\n\tt.Run(\"complex set\", func(t *testing.T) {\n\t\tt.Run(\"set nested with spread\", runTest(testCase{\n\t\t\targs: []string{\"-i\", \"json\", \"-o\", \"json\", \"--root\", `user = {user..., name: {\"first\": $this.user.name, \"last\": \"Doe\"}}`},\n\t\t\tin:   []byte(`{\"user\": {\"name\": \"John\"}}`),\n\t\t\tstdout: []byte(`{\n    \"user\": {\n        \"name\": {\n            \"first\": \"John\",\n            \"last\": \"Doe\"\n        }\n    }\n}\n`),\n\t\t\tstderr: nil,\n\t\t\terr:    nil,\n\t\t}))\n\t\tt.Run(\"set nested\", runTest(testCase{\n\t\t\targs: []string{\"-i\", \"json\", \"-o\", \"json\", \"--root\", `user.name = {\"first\": user.name, \"last\": \"Doe\"}`},\n\t\t\tin:   []byte(`{\"user\": {\"name\": \"John\"}}`),\n\t\t\tstdout: []byte(`{\n    \"user\": {\n        \"name\": {\n            \"first\": \"John\",\n            \"last\": \"Doe\"\n        }\n    }\n}\n`),\n\t\t\tstderr: nil,\n\t\t\terr:    nil,\n\t\t}))\n\t\tt.Run(\"set nested with localised group\", runTest(testCase{\n\t\t\targs: []string{\"-i\", \"json\", \"-o\", \"json\", \"--root\", `user.(name = {\"first\": name, \"last\": \"Doe\"})`},\n\t\t\tin:   []byte(`{\"user\": {\"name\": \"John\"}}`),\n\t\t\tstdout: []byte(`{\n    \"user\": {\n        \"name\": {\n            \"first\": \"John\",\n            \"last\": \"Doe\"\n        }\n    }\n}\n`),\n\t\t\tstderr: nil,\n\t\t\terr:    nil,\n\t\t}))\n\t\tt.Run(\"set recursive descent\", func(t *testing.T) {\n\n\t\t\tt.Run(\"property\", runTest(testCase{\n\t\t\t\targs: []string{\"-i\", \"json\", \"-o\", \"json\", \"--root\", `$root..x.each($this = $this+1)`},\n\t\t\t\tin:   []byte(`[{\"x\":1},{\"x\":2},{\"x\":3}]`),\n\t\t\t\tstdout: []byte(`[\n    {\n        \"x\": 2\n    },\n    {\n        \"x\": 3\n    },\n    {\n        \"x\": 4\n    }\n]\n`),\n\t\t\t\tstderr: nil,\n\t\t\t\terr:    nil,\n\t\t\t}))\n\n\t\t\tt.Run(\"index\", runTest(testCase{\n\t\t\t\targs: []string{\"-i\", \"json\", \"-o\", \"json\", \"--root\", `$root..[1].each($this = $this+1)`},\n\t\t\t\tin:   []byte(`[ {\"x\":[1,2,3]} , {\"y\":[4,5,6]} , {\"z\":[7,8,9]} ]`),\n\t\t\t\tstdout: []byte(`[\n    {\n        \"x\": [\n            1,\n            3,\n            3\n        ]\n    },\n    {\n        \"y\": [\n            4,\n            6,\n            6\n        ]\n    },\n    {\n        \"z\": [\n            7,\n            9,\n            9\n        ]\n    }\n]\n`),\n\t\t\t\tstderr: nil,\n\t\t\t\terr:    nil,\n\t\t\t}))\n\n\t\t\tt.Run(\"wildcard\", runTest(testCase{\n\t\t\t\targs: []string{\"-i\", \"json\", \"-o\", \"json\", \"--root\", `$root..*.each($this = 4)`},\n\t\t\t\tin:   []byte(`[{\"x\":1},{\"x\":2},{\"x\":3}]`),\n\t\t\t\tstdout: []byte(`[\n    {\n        \"x\": 4\n    },\n    {\n        \"x\": 4\n    },\n    {\n        \"x\": 4\n    }\n]\n`),\n\t\t\t\tstderr: nil,\n\t\t\t\terr:    nil,\n\t\t\t}))\n\n\t\t})\n\t\tt.Run(\"create object with empty stdin\", runTest(testCase{\n\t\t\targs: []string{`{\"name\":\"Tom\"}`},\n\t\t\tin:   []byte{},\n\t\t\tstdout: []byte(`{\n    \"name\": \"Tom\"\n}\n`),\n\t\t\tstderr: nil,\n\t\t\terr:    nil,\n\t\t}))\n\t})\n\tt.Run(\"set search\", runTest(testCase{\n\t\targs: []string{\"-i\", \"json\", \"-o\", \"json\", \"--root\", `search(has(\"x\")).each(x = x+1)`},\n\t\tin:   []byte(`[{\"x\":1},{\"x\":2},{\"x\":3}]`),\n\t\tstdout: []byte(`[\n    {\n        \"x\": 2\n    },\n    {\n        \"x\": 3\n    },\n    {\n        \"x\": 4\n    }\n]\n`),\n\t\tstderr: nil,\n\t\terr:    nil,\n\t}))\n\tt.Run(\"recursive descent\", func(t *testing.T) {\n\t\tt.Run(\"wildcard\", runTest(testCase{\n\t\t\targs: []string{\"-i\", \"json\", `..*`},\n\t\t\tin: []byte(`{\n  \"user\": {\n    \"name\": \"Alice\",\n    \"roles\": [\"admin\", \"editor\"],\n    \"meta\": {\n      \"active\": true,\n      \"score\": 42\n    }\n  },\n  \"tags\": [\"x\", \"y\"],\n  \"count\": 10\n}`),\n\t\t\tstdout: []byte(`[\n    \"Alice\",\n    \"admin\",\n    \"editor\",\n    true,\n    42,\n    \"x\",\n    \"y\",\n    10\n]\n`),\n\t\t\tstderr: nil,\n\t\t\terr:    nil,\n\t\t}))\n\n\t\tt.Run(\"property\", runTest(testCase{\n\t\t\targs: []string{\"-i\", \"json\", `..name`},\n\t\t\tin: []byte(`{\n  \"user\": {\n    \"name\": \"Alice\",\n    \"roles\": [\"admin\", \"editor\"],\n    \"meta\": {\n      \"active\": true,\n      \"score\": 42\n    }\n  },\n  \"tags\": [\"x\", \"y\"],\n  \"count\": 10\n}`),\n\t\t\tstdout: []byte(`[\n    \"Alice\"\n]\n`),\n\t\t\tstderr: nil,\n\t\t\terr:    nil,\n\t\t}))\n\n\t\tt.Run(\"property2\", runTest(testCase{\n\t\t\targs: []string{\"-i\", \"json\", `..name`},\n\t\t\tin:   []byte(`[{\"name\":\"Tom\"}, {\"name\":\"Jim\"}, {\"foo\": \"Bar\"}]`),\n\t\t\tstdout: []byte(`[\n    \"Tom\",\n    \"Jim\"\n]\n`),\n\t\t\tstderr: nil,\n\t\t\terr:    nil,\n\t\t}))\n\n\t\tt.Run(\"index\", runTest(testCase{\n\t\t\targs: []string{\"-i\", \"json\", `..[0]`},\n\t\t\tin: []byte(`{\n  \"user\": {\n    \"name\": \"Alice\",\n    \"roles\": [\"admin\", \"editor\"],\n    \"meta\": {\n      \"active\": true,\n      \"score\": 42\n    }\n  },\n  \"tags\": [\"x\", \"y\"],\n  \"count\": 10\n}`),\n\t\t\tstdout: []byte(`[\n    \"admin\",\n    \"x\"\n]\n`),\n\t\t\tstderr: nil,\n\t\t\terr:    nil,\n\t\t}))\n\t})\n}\n"
  },
  {
    "path": "internal/cli/config.go",
    "content": "package cli\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/user\"\n\t\"strings\"\n\n\t\"go.yaml.in/yaml/v4\"\n)\n\n// Config holds the contents of a config file.\ntype Config struct {\n\tDefaultFormat string `yaml:\"default_format\"`\n}\n\nvar cfg = Config{\n\tDefaultFormat: \"json\",\n}\nvar cfgLoaded = false\n\n// LoadConfig loads the config from the given path.\n// If already loaded, returned previously loaded config.\nfunc LoadConfig(path string) (Config, error) {\n\tif cfgLoaded {\n\t\treturn cfg, nil\n\t}\n\n\tif strings.HasPrefix(path, \"~/\") {\n\t\tusr, err := user.Current()\n\t\tif err != nil {\n\t\t\treturn cfg, fmt.Errorf(\"error getting current user: %v\", err)\n\t\t}\n\t\tpath = usr.HomeDir + path[1:]\n\t}\n\n\tf, err := os.Open(path)\n\tif errors.Is(err, os.ErrNotExist) {\n\t\tcfgLoaded = true\n\t\treturn cfg, nil\n\t}\n\tif err != nil {\n\t\treturn cfg, fmt.Errorf(\"error opening config file at path %q: %w\", path, err)\n\t}\n\tdefer func() {\n\t\t_ = f.Close()\n\t}()\n\tdecoder := yaml.NewDecoder(f)\n\terr = decoder.Decode(&cfg)\n\tif err != nil && !errors.Is(err, io.EOF) {\n\t\treturn cfg, fmt.Errorf(\"error parsing config file: %w\", err)\n\t}\n\tcfgLoaded = true\n\treturn cfg, nil\n}\n"
  },
  {
    "path": "internal/cli/generic_test.go",
    "content": "package cli_test\n\nimport (\n\t\"fmt\"\n\t\"slices\"\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/parsing\"\n\t\"github.com/tomwright/dasel/v3/parsing/json\"\n\t\"github.com/tomwright/dasel/v3/parsing/toml\"\n\t\"github.com/tomwright/dasel/v3/parsing/yaml\"\n)\n\nfunc newStringWithFormat(format parsing.Format, data string) bytesWithFormat {\n\treturn bytesWithFormat{\n\t\tformat: format,\n\t\tdata:   append([]byte(data), []byte(\"\\n\")...),\n\t}\n}\n\ntype bytesWithFormat struct {\n\tformat parsing.Format\n\tdata   []byte\n}\n\ntype testCases struct {\n\tselector string\n\tin       []bytesWithFormat\n\tout      []bytesWithFormat\n\targs     []string\n\tskip     []string\n}\n\nfunc (tcs testCases) run(t *testing.T) {\n\tfor _, i := range tcs.in {\n\t\tfor _, o := range tcs.out {\n\t\t\ttcName := fmt.Sprintf(\"%s to %s\", i.format.String(), o.format.String())\n\n\t\t\tif slices.Contains(tcs.skip, tcName) {\n\t\t\t\t// Run a test and skip for visibility.\n\t\t\t\tt.Run(tcName, func(t *testing.T) {\n\t\t\t\t\tt.Skip()\n\t\t\t\t})\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\targs := slices.Clone(tcs.args)\n\t\t\targs = append(args, \"-i\", i.format.String(), \"-o\", o.format.String())\n\t\t\tif tcs.selector != \"\" {\n\t\t\t\targs = append(args, tcs.selector)\n\t\t\t}\n\t\t\ttc := testCase{\n\t\t\t\targs:   args,\n\t\t\t\tin:     i.data,\n\t\t\t\tstdout: o.data,\n\t\t\t}\n\t\t\tt.Run(tcName, runTest(tc))\n\t\t}\n\t}\n}\n\nfunc TestCrossFormatHappyPath(t *testing.T) {\n\tjsonInputData := newStringWithFormat(json.JSON, `{\n\t\"oneTwoThree\": 123,\n\t\"oneTwoDotThree\": 12.3,\n\t\"hello\": \"world\",\n\t\"boolFalse\": false,\n\t\"boolTrue\": true,\n\t\"stringFalse\": \"false\",\n\t\"stringTrue\": \"true\",\n\t\"sliceOfNumbers\": [1, 2, 3, 4, 5],\n\t\"mapData\": {\n\t\t\"oneTwoThree\": 123,\n\t\t\"oneTwoDotThree\": 12.3,\n\t\t\"hello\": \"world\",\n\t\t\"boolFalse\": false,\n\t\t\"boolTrue\": true,\n\t\t\"stringFalse\": \"false\",\n\t\t\"stringTrue\": \"true\",\n\t\t\"sliceOfNumbers\": [1, 2, 3, 4, 5],\n\t\t\"mapData\": {\n\t\t\t\"oneTwoThree\": 123,\n\t\t\t\"oneTwoDotThree\": 12.3,\n\t\t\t\"hello\": \"world\",\n\t\t\t\"boolFalse\": false,\n\t\t\t\"boolTrue\": true,\n\t\t\t\"stringFalse\": \"false\",\n\t\t\t\"stringTrue\": \"true\",\n\t\t\t\"sliceOfNumbers\": [1, 2, 3, 4, 5]\n\t\t}\n\t}\n}`)\n\tyamlInputData := newStringWithFormat(yaml.YAML, `oneTwoThree: 123\noneTwoDotThree: 12.3\nhello: world\nboolFalse: false\nboolTrue: true\nstringFalse: \"false\"\nstringTrue: \"true\"\nsliceOfNumbers:\n- 1\n- 2\n- 3\n- 4\n- 5\nmapData:\n    oneTwoThree: 123\n    oneTwoDotThree: 12.3\n    hello: world\n    boolFalse: false\n    boolTrue: true\n    stringFalse: \"false\"\n    stringTrue: \"true\"\n    sliceOfNumbers:\n    - 1\n    - 2\n    - 3\n    - 4\n    - 5\n    mapData:\n        oneTwoThree: 123\n        oneTwoDotThree: 12.3\n        hello: world\n        boolFalse: false\n        boolTrue: true\n        stringFalse: \"false\"\n        stringTrue: \"true\"\n        sliceOfNumbers:\n        - 1\n        - 2\n        - 3\n        - 4\n        - 5\n`)\n\n\ttomlInputData := newStringWithFormat(toml.TOML, `\noneTwoThree = 123\noneTwoDotThree = 12.3\nhello = 'world'\nboolFalse = false\nboolTrue = true\nstringFalse = 'false'\nstringTrue = 'true'\nsliceOfNumbers = [1, 2, 3, 4, 5]\n\n[mapData]\noneTwoThree = 123\noneTwoDotThree = 12.3\nhello = 'world'\nboolFalse = false\nboolTrue = true\nstringFalse = 'false'\nstringTrue = 'true'\nsliceOfNumbers = [1, 2, 3, 4, 5]\n\n[mapData.mapData]\noneTwoThree = 123\noneTwoDotThree = 12.3\nhello = 'world'\nboolFalse = false\nboolTrue = true\nstringFalse = 'false'\nstringTrue = 'true'\nsliceOfNumbers = [1, 2, 3, 4, 5]\n`)\n\n\tt.Run(\"select\", func(t *testing.T) {\n\t\tnewTestsWithPrefix := func(prefix string) func(*testing.T) {\n\t\t\treturn func(t *testing.T) {\n\t\t\t\tt.Run(\"string\", testCases{\n\t\t\t\t\tselector: prefix + \"hello\",\n\t\t\t\t\tin: []bytesWithFormat{\n\t\t\t\t\t\tjsonInputData,\n\t\t\t\t\t\tyamlInputData,\n\t\t\t\t\t\ttomlInputData,\n\t\t\t\t\t},\n\t\t\t\t\tout: []bytesWithFormat{\n\t\t\t\t\t\tnewStringWithFormat(json.JSON, `\"world\"`),\n\t\t\t\t\t\tnewStringWithFormat(yaml.YAML, `world`),\n\t\t\t\t\t\tnewStringWithFormat(toml.TOML, `'world'`),\n\t\t\t\t\t},\n\t\t\t\t}.run)\n\t\t\t\tt.Run(\"int\", testCases{\n\t\t\t\t\tselector: prefix + \"oneTwoThree\",\n\t\t\t\t\tin: []bytesWithFormat{\n\t\t\t\t\t\tjsonInputData,\n\t\t\t\t\t\tyamlInputData,\n\t\t\t\t\t\ttomlInputData,\n\t\t\t\t\t},\n\t\t\t\t\tout: []bytesWithFormat{\n\t\t\t\t\t\tnewStringWithFormat(json.JSON, `123`),\n\t\t\t\t\t\tnewStringWithFormat(yaml.YAML, `123`),\n\t\t\t\t\t\tnewStringWithFormat(toml.TOML, `123`),\n\t\t\t\t\t},\n\t\t\t\t}.run)\n\t\t\t\tt.Run(\"float\", testCases{\n\t\t\t\t\tselector: prefix + \"oneTwoDotThree\",\n\t\t\t\t\tin: []bytesWithFormat{\n\t\t\t\t\t\tjsonInputData,\n\t\t\t\t\t\tyamlInputData,\n\t\t\t\t\t\ttomlInputData,\n\t\t\t\t\t},\n\t\t\t\t\tout: []bytesWithFormat{\n\t\t\t\t\t\tnewStringWithFormat(json.JSON, `12.3`),\n\t\t\t\t\t\tnewStringWithFormat(yaml.YAML, `12.3`),\n\t\t\t\t\t\tnewStringWithFormat(toml.TOML, `12.3`),\n\t\t\t\t\t},\n\t\t\t\t}.run)\n\t\t\t\tt.Run(\"bool\", func(t *testing.T) {\n\t\t\t\t\tt.Run(\"true\", testCases{\n\t\t\t\t\t\tselector: prefix + \"boolTrue\",\n\t\t\t\t\t\tin: []bytesWithFormat{\n\t\t\t\t\t\t\tjsonInputData,\n\t\t\t\t\t\t\tyamlInputData,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tout: []bytesWithFormat{\n\t\t\t\t\t\t\tnewStringWithFormat(json.JSON, `true`),\n\t\t\t\t\t\t\tnewStringWithFormat(yaml.YAML, `true`),\n\t\t\t\t\t\t\tnewStringWithFormat(toml.TOML, `true`),\n\t\t\t\t\t\t},\n\t\t\t\t\t}.run)\n\t\t\t\t\tt.Run(\"false\", testCases{\n\t\t\t\t\t\tselector: prefix + \"boolFalse\",\n\t\t\t\t\t\tin: []bytesWithFormat{\n\t\t\t\t\t\t\tjsonInputData,\n\t\t\t\t\t\t\tyamlInputData,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tout: []bytesWithFormat{\n\t\t\t\t\t\t\tnewStringWithFormat(json.JSON, `false`),\n\t\t\t\t\t\t\tnewStringWithFormat(yaml.YAML, `false`),\n\t\t\t\t\t\t\tnewStringWithFormat(toml.TOML, `false`),\n\t\t\t\t\t\t},\n\t\t\t\t\t}.run)\n\t\t\t\t\tt.Run(\"true string\", testCases{\n\t\t\t\t\t\tselector: prefix + \"stringTrue\",\n\t\t\t\t\t\tin: []bytesWithFormat{\n\t\t\t\t\t\t\tjsonInputData,\n\t\t\t\t\t\t\tyamlInputData,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tout: []bytesWithFormat{\n\t\t\t\t\t\t\tnewStringWithFormat(json.JSON, `\"true\"`),\n\t\t\t\t\t\t\tnewStringWithFormat(yaml.YAML, `\"true\"`),\n\t\t\t\t\t\t\tnewStringWithFormat(toml.TOML, `'true'`),\n\t\t\t\t\t\t},\n\t\t\t\t\t}.run)\n\t\t\t\t\tt.Run(\"false string\", testCases{\n\t\t\t\t\t\tselector: prefix + \"stringFalse\",\n\t\t\t\t\t\tin: []bytesWithFormat{\n\t\t\t\t\t\t\tjsonInputData,\n\t\t\t\t\t\t\tyamlInputData,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tout: []bytesWithFormat{\n\t\t\t\t\t\t\tnewStringWithFormat(json.JSON, `\"false\"`),\n\t\t\t\t\t\t\tnewStringWithFormat(yaml.YAML, `\"false\"`),\n\t\t\t\t\t\t\tnewStringWithFormat(toml.TOML, `'false'`),\n\t\t\t\t\t\t},\n\t\t\t\t\t}.run)\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\n\t\tt.Run(\"root\", newTestsWithPrefix(\"\"))\n\t\tt.Run(\"nested once\", newTestsWithPrefix(\"mapData.\"))\n\t\tt.Run(\"nested twice\", newTestsWithPrefix(\"mapData.mapData.\"))\n\t})\n}\n"
  },
  {
    "path": "internal/cli/interactive.go",
    "content": "package cli\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/tomwright/dasel/v3/parsing\"\n)\n\nfunc NewInteractiveCmd(queryCmd *QueryCmd) *InteractiveCmd {\n\treturn &InteractiveCmd{\n\t\tVars:              queryCmd.Vars,\n\t\tExtReadWriteFlags: queryCmd.ExtReadWriteFlags,\n\t\tExtReadFlags:      queryCmd.ExtReadFlags,\n\t\tExtWriteFlags:     queryCmd.ExtWriteFlags,\n\t\tInFormat:          queryCmd.InFormat,\n\t\tOutFormat:         queryCmd.OutFormat,\n\n\t\tQuery: queryCmd.Query,\n\t}\n}\n\ntype InteractiveCmd struct {\n\tVars              variables         `flag:\"\" name:\"var\" help:\"Variables to pass to the query. E.g. --var foo=\\\"bar\\\" --var baz=json:file:./some/file.json\"`\n\tExtReadWriteFlags extReadWriteFlags `flag:\"\" name:\"rw-flag\" help:\"Read/Write flag to customise parsing/output. Applies to read + write E.g. --rw-flag csv-delimiter=;\"`\n\tExtReadFlags      extReadWriteFlags `flag:\"\" name:\"read-flag\" help:\"Reader flag to customise parsing. E.g. --read-flag xml-mode=structured\"`\n\tExtWriteFlags     extReadWriteFlags `flag:\"\" name:\"write-flag\" help:\"Writer flag to customise output. E.g. --write-flag csv-delimiter=;\"`\n\tInFormat          string            `flag:\"\" name:\"in\" short:\"i\" help:\"The format of the input data.\"`\n\tOutFormat         string            `flag:\"\" name:\"out\" short:\"o\" help:\"The format of the output data.\"`\n\n\tConfigPath string `name:\"config\" short:\"c\" help:\"Path to config file\" default:\"~/dasel.yaml\"`\n\n\tQuery string `arg:\"\" help:\"The query to execute.\" optional:\"\" default:\"\"`\n}\n\nfunc (c *InteractiveCmd) Run(ctx *Globals) error {\n\tvar stdInBytes []byte = nil\n\n\tif ctx.Stdin != nil {\n\t\tvar err error\n\t\tstdInBytes, err = io.ReadAll(ctx.Stdin)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tcfg, err := LoadConfig(c.ConfigPath)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif c.InFormat == \"\" && c.OutFormat == \"\" {\n\t\tc.InFormat = cfg.DefaultFormat\n\t\tc.OutFormat = cfg.DefaultFormat\n\t} else if c.InFormat == \"\" {\n\t\tc.InFormat = c.OutFormat\n\t} else if c.OutFormat == \"\" {\n\t\tc.OutFormat = c.InFormat\n\t}\n\n\tvar runDasel interactiveDaselExecutor = func(selector string, root bool, formatIn parsing.Format, formatOut parsing.Format, in string) (res string, err error) {\n\t\tdefer func() {\n\t\t\tif r := recover(); r != nil {\n\t\t\t\terr = fmt.Errorf(\"panic: %v\", r)\n\t\t\t}\n\t\t}()\n\t\tvar stdIn *bytes.Reader\n\t\tif in != \"\" {\n\t\t\tstdIn = bytes.NewReader([]byte(in))\n\t\t} else {\n\t\t\tstdIn = bytes.NewReader([]byte{})\n\t\t}\n\n\t\to := runOpts{\n\t\t\tVars:              c.Vars,\n\t\t\tExtReadWriteFlags: c.ExtReadWriteFlags,\n\t\t\tExtReadFlags:      c.ExtReadFlags,\n\t\t\tExtWriteFlags:     c.ExtWriteFlags,\n\t\t\tInFormat:          formatIn.String(),\n\t\t\tOutFormat:         formatOut.String(),\n\t\t\tReturnRoot:        root,\n\t\t\tUnstable:          true,\n\t\t\tQuery:             selector,\n\n\t\t\tConfigPath: c.ConfigPath,\n\n\t\t\tStdin: stdIn,\n\t\t}\n\n\t\toutBytes, err := run(o)\n\t\treturn string(outBytes), err\n\t}\n\n\tp, selectorFn := newInteractiveTeaProgram(string(stdInBytes), c.Query, parsing.Format(c.InFormat), parsing.Format(c.OutFormat), runDasel)\n\n\t_, err = p.Run()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif selectorFn != nil {\n\t\ts := selectorFn()\n\t\tif s != \"\" {\n\t\t\tif _, err := fmt.Fprintf(ctx.Stdout, \"%s\\n\", s); err != nil {\n\t\t\t\treturn fmt.Errorf(\"error writing output: %w\", err)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "internal/cli/interactive_tea.go",
    "content": "package cli\n\nimport (\n\t\"fmt\"\n\t\"slices\"\n\t\"strings\"\n\n\ttea \"github.com/charmbracelet/bubbletea\"\n\t\"github.com/charmbracelet/lipgloss\"\n\t\"github.com/tomwright/dasel/v3/internal\"\n\t\"github.com/tomwright/dasel/v3/parsing\"\n)\n\nvar (\n\tinteractiveKeyQuit       = tea.KeyCtrlC\n\tinteractiveKeyCycleRead  = tea.KeyCtrlE\n\tinteractiveKeyCycleWrite = tea.KeyCtrlD\n\n\theadingStyle = func() lipgloss.Style {\n\t\treturn lipgloss.NewStyle().Padding(0, 1, 1, 1)\n\t}()\n\tshortcutStyle = func() lipgloss.Style {\n\t\treturn lipgloss.NewStyle().Padding(0, 1).Align(lipgloss.Left)\n\t}()\n\theaderStyle = func() lipgloss.Style {\n\t\treturn lipgloss.NewStyle().Padding(1).Align(lipgloss.Center)\n\t}()\n\tinputStyle = func() lipgloss.Style {\n\t\treturn lipgloss.NewStyle().Margin(0, 0, 1, 0)\n\t}()\n\toutputContentStyle = func() lipgloss.Style {\n\t\treturn lipgloss.NewStyle().Padding(0, 1).Border(lipgloss.RoundedBorder())\n\t}()\n\toutputHeaderStyle = func() lipgloss.Style {\n\t\treturn lipgloss.NewStyle().Padding(0, 2).Margin(0, 0, 1, 0).Underline(true)\n\t}()\n)\n\ntype interactiveDaselExecutor func(selector string, root bool, formatIn parsing.Format, formatOut parsing.Format, in string) (res string, err error)\n\nfunc newInteractiveTeaProgram(initialInput string, initialSelector string, formatIn parsing.Format, formatOut parsing.Format, run interactiveDaselExecutor) (*tea.Program, func() string) {\n\tm := newInteractiveRootModel(initialInput, initialSelector, formatIn, formatOut, run)\n\treturn tea.NewProgram(m, tea.WithAltScreen()), func() string {\n\t\treturn m.sharedData.selector\n\t}\n}\n\ntype interactiveSharedData struct {\n\tformatIn  parsing.Format\n\tformatOut parsing.Format\n\tselector  string\n\tinput     string\n}\n\ntype interactiveRootModel struct {\n\tsharedData   *interactiveSharedData\n\tinputModel   *interactiveInputModel\n\toutputModels []*interactiveOutputModel\n}\n\nfunc newInteractiveRootModel(initialInput string, initialSelector string, formatIn parsing.Format, formatOut parsing.Format, run interactiveDaselExecutor) *interactiveRootModel {\n\tres := &interactiveRootModel{\n\t\tsharedData: &interactiveSharedData{\n\t\t\tformatIn:  formatIn,\n\t\t\tformatOut: formatOut,\n\t\t\tselector:  initialSelector,\n\t\t\tinput:     initialInput,\n\t\t},\n\t\toutputModels: make([]*interactiveOutputModel, 0),\n\t}\n\n\tres.inputModel = newInteractiveInputModel(res.sharedData)\n\n\toutputRootModel := newInteractiveOutputModel(res.sharedData, true, run)\n\toutputResultModel := newInteractiveOutputModel(res.sharedData, false, run)\n\n\tres.outputModels = append(res.outputModels, outputRootModel, outputResultModel)\n\n\treturn res\n}\n\nfunc (m *interactiveRootModel) Init() tea.Cmd {\n\treturn nil\n}\n\nfunc cycleFormats(all []parsing.Format, current parsing.Format) parsing.Format {\n\tslices.SortFunc(all, func(i, j parsing.Format) int {\n\t\treturn strings.Compare(string(i), string(j))\n\t})\n\tcur := -1\n\tfor i, format := range all {\n\t\tif format == current {\n\t\t\tcur = i\n\t\t\tbreak\n\t\t}\n\t}\n\tnext := cur + 1\n\tif next > len(all)-1 {\n\t\tnext = 0\n\t}\n\treturn all[next]\n}\n\nfunc (m *interactiveRootModel) cycleReader() {\n\tm.sharedData.formatIn = cycleFormats(parsing.RegisteredReaders(), m.sharedData.formatIn)\n}\n\nfunc (m *interactiveRootModel) cycleWriter() {\n\tm.sharedData.formatOut = cycleFormats(parsing.RegisteredWriters(), m.sharedData.formatOut)\n}\n\nfunc (m *interactiveRootModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tvar cmds []tea.Cmd\n\tvar cmd tea.Cmd\n\n\tswitch msg := msg.(type) {\n\tcase tea.KeyMsg:\n\t\tswitch msg.Type {\n\t\tcase interactiveKeyQuit:\n\t\t\treturn m, tea.Quit\n\t\tcase interactiveKeyCycleRead:\n\t\t\tm.cycleReader()\n\t\tcase interactiveKeyCycleWrite:\n\t\t\tm.cycleWriter()\n\t\tdefault:\n\t\t}\n\n\tcase tea.WindowSizeMsg:\n\t\theaderStyle = headerStyle.Width(msg.Width).MaxWidth(msg.Width)\n\n\t\tvar headerHeight int\n\t\t{\n\t\t\theaderHeight += lipgloss.Height(m.headerView())\n\t\t\theaderHeight += lipgloss.Height(m.inputView())\n\t\t}\n\t\tverticalMarginHeight := headerHeight\n\n\t\tnumCols := len(m.outputModels)\n\n\t\tviewportHeight := msg.Height - verticalMarginHeight - (2 * numCols)\n\t\tviewportWidth := (msg.Width / numCols) - (2 * numCols)\n\n\t\tfor _, outputModel := range m.outputModels {\n\t\t\toutputModel.setSize(viewportWidth, viewportHeight)\n\t\t\toutputModel.setVerticalPosition(verticalMarginHeight)\n\t\t}\n\t}\n\n\t{\n\t\tvar model tea.Model\n\t\tmodel, cmd = m.inputModel.Update(msg)\n\t\tm.inputModel = model.(*interactiveInputModel)\n\t\tcmds = append(cmds, cmd)\n\t}\n\n\tfor i, outputModel := range m.outputModels {\n\t\tvar model tea.Model\n\t\tmodel, cmd = outputModel.Update(msg)\n\t\tm.outputModels[i] = model.(*interactiveOutputModel)\n\t\tcmds = append(cmds, cmd)\n\t}\n\n\treturn m, tea.Batch(cmds...)\n}\n\nfunc (m *interactiveRootModel) headerView() string {\n\theader := headingStyle.Render(\"Dasel interactive mode - \" + internal.Version)\n\n\tshortcuts := \"\\n\"\n\tshortcuts += fmt.Sprintf(\"%s: %s\\n\", interactiveKeyQuit, \"Quit\")\n\tshortcuts += fmt.Sprintf(\"%s: %s\\n\", interactiveKeyCycleRead, \"Cycle reader\")\n\tshortcuts += fmt.Sprintf(\"%s: %s\\n\", interactiveKeyCycleWrite, \"Cycle writer\")\n\n\tout := append([]string{header}, shortcutStyle.Render(shortcuts))\n\n\tout = append(out, fmt.Sprintf(\"\\nReader: %s | Writer: %s\", m.sharedData.formatIn, m.sharedData.formatOut))\n\n\treturn headerStyle.Render(out...)\n}\n\nfunc (m *interactiveRootModel) inputView() string {\n\treturn inputStyle.Render(m.inputModel.View())\n}\n\nfunc (m *interactiveRootModel) View() string {\n\trows := make([]string, 0)\n\n\trows = append(rows, m.headerView())\n\n\trows = append(rows, m.inputView())\n\n\t{\n\t\tcols := make([]string, 0)\n\t\tfor _, outputModel := range m.outputModels {\n\t\t\tcols = append(cols, outputModel.View())\n\t\t}\n\t\tif len(cols) > 0 {\n\t\t\trows = append(rows, lipgloss.JoinHorizontal(lipgloss.Top, cols...))\n\t\t}\n\t}\n\n\treturn lipgloss.JoinVertical(lipgloss.Left, rows...)\n}\n"
  },
  {
    "path": "internal/cli/interactive_tea_input.go",
    "content": "package cli\n\nimport (\n\t\"github.com/charmbracelet/bubbles/textarea\"\n\ttea \"github.com/charmbracelet/bubbletea\"\n)\n\ntype interactiveInputModel struct {\n\tsharedData *interactiveSharedData\n\tinputModel textarea.Model\n}\n\nfunc newInteractiveInputModel(sharedData *interactiveSharedData) *interactiveInputModel {\n\tti := textarea.New()\n\tti.Placeholder = \"Enter a query...\"\n\tti.SetValue(sharedData.selector)\n\tti.Focus()\n\tti.SetHeight(5)\n\tti.ShowLineNumbers = false\n\n\treturn &interactiveInputModel{\n\t\tsharedData: sharedData,\n\t\tinputModel: ti,\n\t}\n}\n\nfunc (m *interactiveInputModel) Init() tea.Cmd {\n\treturn textarea.Blink\n}\n\nfunc (m *interactiveInputModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tvar cmds []tea.Cmd\n\tvar cmd tea.Cmd\n\n\tm.sharedData.selector = m.inputModel.Value()\n\n\tswitch msg := msg.(type) {\n\tcase tea.WindowSizeMsg:\n\t\tm.inputModel.SetWidth(msg.Width)\n\t}\n\n\tm.inputModel, cmd = m.inputModel.Update(msg)\n\tcmds = append(cmds, cmd)\n\n\treturn m, tea.Batch(cmds...)\n}\n\nfunc (m *interactiveInputModel) View() string {\n\treturn m.inputModel.View()\n}\n"
  },
  {
    "path": "internal/cli/interactive_tea_output.go",
    "content": "package cli\n\nimport (\n\t\"github.com/charmbracelet/bubbles/viewport\"\n\ttea \"github.com/charmbracelet/bubbletea\"\n\t\"github.com/charmbracelet/lipgloss\"\n\t\"github.com/tomwright/dasel/v3/parsing\"\n)\n\ntype interactiveOutputModel struct {\n\tsharedData          *interactiveSharedData\n\thasUpdatedBefore    bool\n\tlastSeenSelector    string\n\tlastSeenFormatIn    parsing.Format\n\tlastSeenFormatOut   parsing.Format\n\tlastSeenInput       string\n\troot                bool\n\trun                 interactiveDaselExecutor\n\toutput              string\n\toutputViewport      viewport.Model\n\toutputViewportReady bool\n}\n\nfunc newInteractiveOutputModel(sharedData *interactiveSharedData, root bool, run interactiveDaselExecutor) *interactiveOutputModel {\n\tm := &interactiveOutputModel{\n\t\tsharedData: sharedData,\n\t\troot:       root,\n\t\trun:        run,\n\t}\n\tm.outputViewport = viewport.New(10, 10)\n\treturn m\n}\n\nfunc (m *interactiveOutputModel) Init() tea.Cmd {\n\treturn nil\n}\n\nfunc (m *interactiveOutputModel) setOutput(output string) {\n\tm.output = output\n\tif m.outputViewportReady {\n\t\tm.outputViewport.SetContent(m.output)\n\t}\n}\n\nfunc (m *interactiveOutputModel) setSize(width int, height int) {\n\tif !m.outputViewportReady {\n\t\tm.outputViewportReady = true\n\t}\n\n\tm.outputViewport.Width = width\n\tm.outputViewport.Height = height\n\tm.outputViewport.SetContent(m.output)\n}\n\nfunc (m *interactiveOutputModel) setVerticalPosition(pos int) {\n\tm.outputViewport.YPosition = pos\n}\n\nfunc (m *interactiveOutputModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tvar cmds []tea.Cmd\n\tvar cmd tea.Cmd\n\n\tdefer func() {\n\t\tm.lastSeenSelector = m.sharedData.selector\n\t\tm.lastSeenFormatIn = m.sharedData.formatIn\n\t\tm.lastSeenFormatOut = m.sharedData.formatOut\n\t\tm.lastSeenInput = m.sharedData.input\n\t}()\n\tfirstUpdate := !m.hasUpdatedBefore\n\tm.hasUpdatedBefore = true\n\n\tqueryChanged := m.lastSeenSelector != m.sharedData.selector ||\n\t\tm.lastSeenFormatIn != m.sharedData.formatIn ||\n\t\tm.lastSeenFormatOut != m.sharedData.formatOut ||\n\t\tm.lastSeenInput != m.sharedData.input\n\n\t// Take care of dasel execution + output.\n\tif firstUpdate || queryChanged {\n\t\tm.setOutput(\"Executing...\")\n\t\tout, err := m.run(m.sharedData.selector, m.root, m.sharedData.formatIn, m.sharedData.formatOut, m.sharedData.input)\n\t\tif err != nil {\n\t\t\tm.setOutput(err.Error())\n\t\t} else {\n\t\t\tm.setOutput(out)\n\t\t}\n\t}\n\n\tm.outputViewport, cmd = m.outputViewport.Update(msg)\n\tcmds = append(cmds, cmd)\n\n\treturn m, tea.Batch(cmds...)\n}\n\nfunc (m *interactiveOutputModel) View() string {\n\ttitle := \"Result\"\n\tif m.root {\n\t\ttitle = \"Root\"\n\t}\n\n\tcontent := \"Initializing...\"\n\tif m.outputViewportReady {\n\t\tcontent = m.outputViewport.View()\n\t}\n\n\treturn lipgloss.JoinVertical(lipgloss.Left, outputHeaderStyle.Render(title), outputContentStyle.Render(content))\n}\n"
  },
  {
    "path": "internal/cli/query.go",
    "content": "package cli\n\nimport \"fmt\"\n\ntype QueryCmd struct {\n\tVars              variables         `flag:\"\" name:\"var\" help:\"Variables to pass to the query. E.g. --var foo=\\\"bar\\\" --var baz=json:file:./some/file.json\"`\n\tExtReadWriteFlags extReadWriteFlags `flag:\"\" name:\"rw-flag\" help:\"Read/Write flag to customise parsing/output. Applies to read + write E.g. --rw-flag csv-delimiter=;\"`\n\tExtReadFlags      extReadWriteFlags `flag:\"\" name:\"read-flag\" help:\"Reader flag to customise parsing. E.g. --read-flag xml-mode=structured\"`\n\tExtWriteFlags     extReadWriteFlags `flag:\"\" name:\"write-flag\" help:\"Writer flag to customise output. E.g. --write-flag csv-delimiter=;\"`\n\tInFormat          string            `flag:\"\" name:\"in\" short:\"i\" help:\"The format of the input data.\"`\n\tOutFormat         string            `flag:\"\" name:\"out\" short:\"o\" help:\"The format of the output data.\"`\n\tReturnRoot        bool              `flag:\"\" name:\"root\" help:\"Return the root value.\"`\n\tUnstable          bool              `flag:\"\" name:\"unstable\" help:\"Allow access to potentially unstable features.\"`\n\tInteractive       bool              `flag:\"\" name:\"it\" help:\"Run in interactive mode (alpha).\"`\n\n\tConfigPath string `name:\"config\" short:\"c\" help:\"Path to config file\" default:\"~/dasel.yaml\"`\n\n\tQuery string `arg:\"\" help:\"The query to execute.\" optional:\"\" default:\"\"`\n}\n\nfunc (c *QueryCmd) Run(ctx *Globals) error {\n\tcfg, err := LoadConfig(c.ConfigPath)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif c.InFormat == \"\" && c.OutFormat == \"\" {\n\t\tc.InFormat = cfg.DefaultFormat\n\t\tc.OutFormat = cfg.DefaultFormat\n\t}\n\n\tif c.Query == \"\" && c.InFormat == \"\" && ctx.Stdin == nil {\n\t\treturn ErrNoArgsGiven\n\t}\n\n\tif c.Interactive {\n\t\treturn NewInteractiveCmd(c).Run(ctx)\n\t}\n\n\to := runOpts{\n\t\tVars:              c.Vars,\n\t\tExtReadWriteFlags: c.ExtReadWriteFlags,\n\t\tExtReadFlags:      c.ExtReadFlags,\n\t\tExtWriteFlags:     c.ExtWriteFlags,\n\t\tInFormat:          c.InFormat,\n\t\tOutFormat:         c.OutFormat,\n\t\tReturnRoot:        c.ReturnRoot,\n\t\tUnstable:          c.Unstable,\n\t\tQuery:             c.Query,\n\n\t\tConfigPath: c.ConfigPath,\n\n\t\tStdin: ctx.Stdin,\n\t}\n\toutBytes, err := run(o)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, err = ctx.Stdout.Write(outBytes)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error writing output: %w\", err)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "internal/cli/read_write_flag.go",
    "content": "package cli\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\t\"strings\"\n\n\t\"github.com/alecthomas/kong\"\n\t\"github.com/tomwright/dasel/v3/parsing\"\n)\n\ntype extReadWriteFlag struct {\n\tName  string\n\tValue string\n}\n\ntype extReadWriteFlags *[]extReadWriteFlag\n\nfunc applyReaderFlags(readerOptions *parsing.ReaderOptions, readerFlags extReadWriteFlags, readWriterFlags extReadWriteFlags) {\n\tif readWriterFlags != nil {\n\t\tfor _, flag := range *readWriterFlags {\n\t\t\treaderOptions.Ext[flag.Name] = flag.Value\n\t\t}\n\t}\n\tif readerFlags != nil {\n\t\tfor _, flag := range *readerFlags {\n\t\t\treaderOptions.Ext[flag.Name] = flag.Value\n\t\t}\n\t}\n}\n\nfunc applyWriterFlags(writerOptions *parsing.WriterOptions, writerFlags extReadWriteFlags, readWriterFlags extReadWriteFlags) {\n\tif readWriterFlags != nil {\n\t\tfor _, flag := range *readWriterFlags {\n\t\t\twriterOptions.Ext[flag.Name] = flag.Value\n\t\t}\n\t}\n\tif writerFlags != nil {\n\t\tfor _, flag := range *writerFlags {\n\t\t\twriterOptions.Ext[flag.Name] = flag.Value\n\t\t}\n\t}\n}\n\ntype extReadWriteFlagMapper struct {\n}\n\nfunc (vm *extReadWriteFlagMapper) Decode(ctx *kong.DecodeContext, target reflect.Value) error {\n\tt := ctx.Scan.Pop()\n\n\tstrVal, ok := t.Value.(string)\n\tif !ok {\n\t\treturn fmt.Errorf(\"expected string value for variable\")\n\t}\n\n\tnameValueSplit := strings.SplitN(strVal, \"=\", 2)\n\tif len(nameValueSplit) != 2 {\n\t\treturn fmt.Errorf(\"invalid read/write flag format, expect foo=bar\")\n\t}\n\n\tres := extReadWriteFlag{\n\t\tName:  nameValueSplit[0],\n\t\tValue: nameValueSplit[1],\n\t}\n\n\ttarget.Elem().Set(reflect.Append(target.Elem(), reflect.ValueOf(res)))\n\n\treturn nil\n}\n"
  },
  {
    "path": "internal/cli/run.go",
    "content": "package cli\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/tomwright/dasel/v3/execution\"\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"github.com/tomwright/dasel/v3/parsing\"\n)\n\ntype runOpts struct {\n\tVars              variables\n\tExtReadWriteFlags extReadWriteFlags\n\tExtReadFlags      extReadWriteFlags\n\tExtWriteFlags     extReadWriteFlags\n\tInFormat          string\n\tOutFormat         string\n\tReturnRoot        bool\n\tUnstable          bool\n\tQuery             string\n\n\tConfigPath string\n\n\tStdin io.Reader\n}\n\nfunc run(o runOpts) ([]byte, error) {\n\tcfg, err := LoadConfig(o.ConfigPath)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error loading config: %w\", err)\n\t}\n\n\tvar opts []execution.ExecuteOptionFn\n\n\tif o.OutFormat == \"\" && o.InFormat != \"\" {\n\t\to.OutFormat = o.InFormat\n\t} else if o.OutFormat != \"\" && o.InFormat == \"\" {\n\t\to.InFormat = o.OutFormat\n\t} else if o.OutFormat == \"\" {\n\t\to.OutFormat = cfg.DefaultFormat\n\t}\n\n\treaderOptions := parsing.DefaultReaderOptions()\n\tapplyReaderFlags(&readerOptions, o.ExtReadFlags, o.ExtReadWriteFlags)\n\n\tvar reader parsing.Reader\n\tif len(o.InFormat) > 0 {\n\t\treader, err = parsing.Format(o.InFormat).NewReader(readerOptions)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to get input reader: %w\", err)\n\t\t}\n\t}\n\n\twriterOptions := parsing.DefaultWriterOptions()\n\tapplyWriterFlags(&writerOptions, o.ExtWriteFlags, o.ExtReadWriteFlags)\n\n\twriter, err := parsing.Format(o.OutFormat).NewWriter(writerOptions)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to get output writer: %w\", err)\n\t}\n\n\topts = append(opts, variableOptions(o.Vars)...)\n\n\t// Default to null. If stdin is being read then this will be overwritten.\n\tinputData := model.NewNullValue()\n\n\tvar inputBytes []byte\n\tif o.Stdin != nil {\n\t\tinputBytes, err = io.ReadAll(o.Stdin)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error reading stdin: %w\", err)\n\t\t}\n\t}\n\n\tif len(inputBytes) > 0 {\n\t\tif reader == nil {\n\t\t\treturn nil, fmt.Errorf(\"input format is required when reading stdin\")\n\t\t}\n\t\tinputData, err = reader.Read(inputBytes)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error reading input: %w\", err)\n\t\t}\n\t}\n\n\topts = append(opts, execution.WithVariable(\"root\", inputData))\n\n\tif o.Unstable {\n\t\topts = append(opts, execution.WithUnstable())\n\t}\n\n\toptions := execution.NewOptions(opts...)\n\tout, err := execution.ExecuteSelector(context.Background(), o.Query, inputData, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif o.ReturnRoot {\n\t\tout = inputData\n\t}\n\n\toutputBytes, err := writer.Write(out)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error writing output: %w\", err)\n\t}\n\n\treturn outputBytes, nil\n}\n"
  },
  {
    "path": "internal/cli/variable.go",
    "content": "package cli\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"reflect\"\n\t\"strings\"\n\n\t\"github.com/alecthomas/kong\"\n\t\"github.com/tomwright/dasel/v3/execution\"\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"github.com/tomwright/dasel/v3/parsing\"\n)\n\ntype variable struct {\n\tName  string\n\tValue *model.Value\n}\n\ntype variables *[]variable\n\nfunc variableOptions(vars variables) []execution.ExecuteOptionFn {\n\tvar opts []execution.ExecuteOptionFn\n\tif vars != nil {\n\t\tfor _, v := range *vars {\n\t\t\topts = append(opts, execution.WithVariable(v.Name, v.Value))\n\t\t}\n\t}\n\treturn opts\n}\n\ntype variableMapper struct {\n}\n\n// Decode decodes a variable from a flag.\n// E.g. --var foo=bar\n// E.g. --var foo=json:{\"bar\":\"baz\"}\n// E.g. --var foo=json:file:/path/to/file.json\nfunc (vm *variableMapper) Decode(ctx *kong.DecodeContext, target reflect.Value) error {\n\tt := ctx.Scan.Pop()\n\n\tstrVal, ok := t.Value.(string)\n\tif !ok {\n\t\treturn fmt.Errorf(\"expected string value for variable\")\n\t}\n\n\tnameValueSplit := strings.SplitN(strVal, \"=\", 2)\n\tif len(nameValueSplit) != 2 {\n\t\treturn fmt.Errorf(\"invalid variable format, expect foo=bar, or foo=format:file:path\")\n\t}\n\n\tres := variable{\n\t\tName: nameValueSplit[0],\n\t}\n\n\tformat := \"dasel\"\n\tvalueRaw := nameValueSplit[1]\n\n\tfirstSplit := strings.SplitN(valueRaw, \":\", 2)\n\tif len(firstSplit) == 2 {\n\t\tformat = firstSplit[0]\n\t\tvalueRaw = firstSplit[1]\n\t}\n\tif strings.HasPrefix(valueRaw, \"file:\") {\n\t\tfilePath := valueRaw[len(\"file:\"):]\n\n\t\tf, err := os.Open(filePath)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to open file: %w\", err)\n\t\t}\n\t\tdefer func() {\n\t\t\t_ = f.Close()\n\t\t}()\n\t\tcontents, err := io.ReadAll(f)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to read file contents: %w\", err)\n\t\t}\n\t\tvalueRaw = string(contents)\n\t}\n\n\treader, err := parsing.Format(format).NewReader(parsing.DefaultReaderOptions())\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create reader: %w\", err)\n\t}\n\tres.Value, err = reader.Read([]byte(valueRaw))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to read value: %w\", err)\n\t}\n\n\ttarget.Elem().Set(reflect.Append(target.Elem(), reflect.ValueOf(res)))\n\n\treturn nil\n}\n"
  },
  {
    "path": "internal/cli/version.go",
    "content": "package cli\n\nimport \"github.com/tomwright/dasel/v3/internal\"\n\ntype VersionCmd struct {\n}\n\nfunc (c *VersionCmd) Run(ctx *Globals) error {\n\t_, err := ctx.Stdout.Write([]byte(internal.Version + \"\\n\"))\n\treturn err\n}\n"
  },
  {
    "path": "internal/ptr/to.go",
    "content": "package ptr\n\n// To returns a pointer to the value passed in.\nfunc To[T any](v T) *T {\n\treturn &v\n}\n"
  },
  {
    "path": "internal/ptr/to_test.go",
    "content": "package ptr_test\n\nimport (\n\t\"github.com/tomwright/dasel/v3/internal/ptr\"\n\t\"testing\"\n)\n\nfunc TestTo(t *testing.T) {\n\ta := 1\n\tif exp, got := a, *(ptr.To(a)); exp != a {\n\t\tt.Errorf(\"expected %d, got %d\", exp, got)\n\t}\n}\n"
  },
  {
    "path": "internal/version.go",
    "content": "package internal\n\nimport (\n\t\"runtime/debug\"\n)\n\n// Version represents the current version of dasel.\n// The real version number is injected at build time using ldflags.\nvar Version = \"development\"\n\nfunc init() {\n\t// Version is set by ldflags on build.\n\tif Version != \"development\" {\n\t\treturn\n\t}\n\n\tinfo, ok := debug.ReadBuildInfo()\n\tif !ok {\n\t\treturn\n\t}\n\n\t// https://github.com/golang/go/issues/29228\n\tif info.Main.Version == \"(devel)\" || info.Main.Version == \"\" {\n\t\treturn\n\t}\n\n\tVersion += \"-\" + info.Main.Version\n}\n"
  },
  {
    "path": "model/README.md",
    "content": "# Model\n\nThe model package contains the Value struct and functionality for the application.\n\n`model.Value` is just a wrapper around `reflect.Value` but provides some additional logic for easier use.\n"
  },
  {
    "path": "model/error.go",
    "content": "package model\n\nimport \"fmt\"\n\n// MapKeyNotFound is returned when a key is not found in a map.\ntype MapKeyNotFound struct {\n\tKey string\n}\n\n// Error returns the error message.\nfunc (e MapKeyNotFound) Error() string {\n\treturn fmt.Sprintf(\"map key not found: %q\", e.Key)\n}\n\n// SliceIndexOutOfRange is returned when an index is invalid.\ntype SliceIndexOutOfRange struct {\n\tIndex int\n}\n\n// Error returns the error message.\nfunc (e SliceIndexOutOfRange) Error() string {\n\treturn fmt.Sprintf(\"slice index out of range: %d\", e.Index)\n}\n\n// ErrIncompatibleTypes is returned when two values are incompatible.\ntype ErrIncompatibleTypes struct {\n\tA *Value\n\tB *Value\n}\n\n// Error returns the error message.\nfunc (e ErrIncompatibleTypes) Error() string {\n\treturn fmt.Sprintf(\"incompatible types: %s and %s\", e.A.Type(), e.B.Type())\n}\n\ntype ErrUnexpectedType struct {\n\tExpected Type\n\tActual   Type\n}\n\nfunc (e ErrUnexpectedType) Error() string {\n\treturn fmt.Sprintf(\"unexpected type: expected %s, got %s\", e.Expected, e.Actual)\n}\n\ntype ErrUnexpectedTypes struct {\n\tExpected []Type\n\tActual   Type\n}\n\nfunc (e ErrUnexpectedTypes) Error() string {\n\treturn fmt.Sprintf(\"unexpected type: expected %v, got %s\", e.Expected, e.Actual)\n}\n"
  },
  {
    "path": "model/go_value.go",
    "content": "package model\n\nimport \"fmt\"\n\n// GoValue returns the value as a native Go value.\nfunc (v *Value) GoValue() (any, error) {\n\tvar res any\n\tvar err error\n\n\tswitch v.Type() {\n\tcase TypeString:\n\t\tres, err = v.StringValue()\n\tcase TypeInt:\n\t\tres, err = v.IntValue()\n\tcase TypeFloat:\n\t\tres, err = v.FloatValue()\n\tcase TypeBool:\n\t\tres, err = v.BoolValue()\n\tcase TypeMap:\n\t\tm := make(map[string]any)\n\t\terr = v.RangeMap(func(k string, v *Value) error {\n\t\t\tval, err := v.GoValue()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tm[k] = val\n\t\t\treturn nil\n\t\t})\n\t\tres = m\n\tcase TypeSlice:\n\t\ts := make([]any, 0)\n\t\terr = v.RangeSlice(func(i int, v *Value) error {\n\t\t\tval, err := v.GoValue()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\ts = append(s, val)\n\t\t\treturn nil\n\t\t})\n\t\tres = s\n\tcase TypeUnknown:\n\t\tres = nil\n\t\terr = fmt.Errorf(\"cannot convert unknown type to Go value\")\n\tcase TypeNull:\n\t\tres = nil\n\tdefault:\n\t\terr = fmt.Errorf(\"unhandled type %v\", v.Type())\n\t}\n\n\treturn res, err\n}\n"
  },
  {
    "path": "model/go_value_test.go",
    "content": "package model_test\n\nimport (\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"testing\"\n)\n\ntype goValueTestCase struct {\n\tin   *model.Value\n\tinFn func() *model.Value\n\texp  any\n}\n\nfunc (tc goValueTestCase) run(t *testing.T) {\n\tif tc.inFn != nil {\n\t\ttc.in = tc.inFn()\n\t}\n\tout, err := tc.in.GoValue()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif !cmp.Equal(tc.exp, out) {\n\t\tt.Errorf(\"unexpected result: %s\", cmp.Diff(tc.exp, out))\n\t}\n}\n\nfunc TestValue_GoValue(t *testing.T) {\n\tt.Run(\"null\", goValueTestCase{\n\t\tin:  model.NewNullValue(),\n\t\texp: nil,\n\t}.run)\n\tt.Run(\"int\", goValueTestCase{\n\t\tin:  model.NewIntValue(42),\n\t\texp: int64(42),\n\t}.run)\n\tt.Run(\"float\", goValueTestCase{\n\t\tin:  model.NewFloatValue(3.14),\n\t\texp: 3.14,\n\t}.run)\n\tt.Run(\"string\", goValueTestCase{\n\t\tin:  model.NewStringValue(\"hello\"),\n\t\texp: \"hello\",\n\t}.run)\n\tt.Run(\"bool\", goValueTestCase{\n\t\tin:  model.NewBoolValue(true),\n\t\texp: true,\n\t}.run)\n\tt.Run(\"slice\", goValueTestCase{\n\t\tinFn: func() *model.Value {\n\t\t\ts := model.NewSliceValue()\n\t\t\t_ = s.Append(model.NewIntValue(1))\n\t\t\t_ = s.Append(model.NewIntValue(2))\n\t\t\t_ = s.Append(model.NewIntValue(3))\n\t\t\treturn s\n\t\t},\n\t\texp: []any{int64(1), int64(2), int64(3)},\n\t}.run)\n\tt.Run(\"map\", goValueTestCase{\n\t\tinFn: func() *model.Value {\n\t\t\tm := model.NewMapValue()\n\t\t\t_ = m.SetMapKey(\"a\", model.NewStringValue(\"apple\"))\n\t\t\treturn m\n\t\t},\n\t\texp: map[string]any{\n\t\t\t\"a\": \"apple\",\n\t\t},\n\t}.run)\n}\n"
  },
  {
    "path": "model/orderedmap/map.go",
    "content": "package orderedmap\n\nimport (\n\t\"reflect\"\n)\n\n// KeyValue is a single key value pair from a *Map.\ntype KeyValue struct {\n\tKey   string\n\tValue any\n}\n\n// NewMap returns a new *Map that has its values initialised.\nfunc NewMap() *Map {\n\tkeys := make([]string, 0)\n\tdata := make(map[string]any)\n\treturn &Map{\n\t\tkeys: keys,\n\t\tdata: data,\n\t}\n}\n\n// FromMap creates a *Map from the input map.\n// Note that while the contents will be ordered, the ordering is not\n// guaranteed since the input map is unordered.\nfunc FromMap(source map[string]any) *Map {\n\tm := NewMap()\n\tfor k, v := range source {\n\t\tm.Set(k, v)\n\t}\n\treturn m\n}\n\n// Map is a map implementation that maintains ordering of keys.\ntype Map struct {\n\t// keys contains the keys within the map in the order they were added.\n\tkeys []string\n\t// data contains the actual map data.\n\tdata map[string]any\n}\n\nfunc (m *Map) Len() int {\n\treturn len(m.keys)\n}\n\nfunc (m *Map) Equal(other *Map) bool {\n\tif m.Len() != other.Len() {\n\t\treturn false\n\t}\n\n\tfor i, k := range m.keys {\n\t\tif k != other.keys[i] {\n\t\t\treturn false\n\t\t}\n\n\t\tif !reflect.DeepEqual(m.data[k], other.data[k]) {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\n// Get returns the value found under the given key.\nfunc (m *Map) Get(key string) (any, bool) {\n\tv, ok := m.data[key]\n\treturn v, ok\n}\n\n// Set sets a value under the given key.\nfunc (m *Map) Set(key string, value any) *Map {\n\tif _, ok := m.data[key]; ok {\n\t\tm.data[key] = value\n\t} else {\n\t\tm.keys = append(m.keys, key)\n\t\tm.data[key] = value\n\t}\n\treturn m\n}\n\n// Delete deletes the value found under the given key.\nfunc (m *Map) Delete(key string) *Map {\n\t// Delete the data entry.\n\tdelete(m.data, key)\n\n\t// Remove the item from the keys.\n\tfoundIndex := -1\n\tfor i, k := range m.keys {\n\t\tif k == key {\n\t\t\tfoundIndex = i\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif foundIndex >= 0 {\n\t\tm.keys = append((m.keys)[:foundIndex], (m.keys)[foundIndex+1:]...)\n\t}\n\n\treturn m\n}\n\n// KeyValues returns the KeyValue pairs within the map, in the order that they were added.\nfunc (m *Map) KeyValues() []KeyValue {\n\tres := make([]KeyValue, 0)\n\tfor _, key := range m.keys {\n\t\tres = append(res, KeyValue{\n\t\t\tKey:   key,\n\t\t\tValue: m.data[key],\n\t\t})\n\t}\n\treturn res\n}\n\n// Keys returns all the keys from the map.\nfunc (m *Map) Keys() []string {\n\treturn m.keys\n}\n\n// UnorderedData returns all the data from the map.\nfunc (m *Map) UnorderedData() map[string]any {\n\treturn m.data\n}\n"
  },
  {
    "path": "model/value.go",
    "content": "package model\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\t\"slices\"\n\t\"strings\"\n)\n\ntype Type string\n\nfunc (t Type) String() string {\n\treturn string(t)\n}\n\nconst (\n\tTypeString  Type = \"string\"\n\tTypeInt     Type = \"int\"\n\tTypeFloat   Type = \"float\"\n\tTypeBool    Type = \"bool\"\n\tTypeMap     Type = \"map\"\n\tTypeSlice   Type = \"array\"\n\tTypeUnknown Type = \"unknown\"\n\tTypeNull    Type = \"null\"\n)\n\n// KeyValue represents a key value pair.\ntype KeyValue struct {\n\tKey   string\n\tValue *Value\n}\n\n// Values represents a list of values.\ntype Values []*Value\n\n// ToSliceValue converts a list of values to a slice value.\nfunc (v Values) ToSliceValue() (*Value, error) {\n\tslice := NewSliceValue()\n\tfor _, val := range v {\n\t\tif err := slice.Append(val); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn slice, nil\n}\n\n// Value represents a value.\ntype Value struct {\n\tvalue    reflect.Value\n\tMetadata map[string]any\n\n\tsetFn func(*Value) error\n}\n\n// String returns the value as a formatted string, along with type info.\nfunc (v *Value) String() string {\n\treturn v.string(0)\n}\n\nfunc indentStr(indent int) string {\n\treturn strings.Repeat(\"    \", indent)\n}\n\nfunc (v *Value) string(indent int) string {\n\tswitch v.Type() {\n\tcase TypeString:\n\t\tval, err := v.StringValue()\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\treturn fmt.Sprintf(\"string{%s}\", val)\n\tcase TypeInt:\n\t\tval, err := v.IntValue()\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\treturn fmt.Sprintf(\"int{%d}\", val)\n\tcase TypeFloat:\n\t\tval, err := v.FloatValue()\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\treturn fmt.Sprintf(\"float(%g)\", val)\n\tcase TypeBool:\n\t\tval, err := v.BoolValue()\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\treturn fmt.Sprintf(\"bool{%t}\", val)\n\tcase TypeMap:\n\t\tres := \"{\\n\"\n\t\tif err := v.RangeMap(func(k string, v *Value) error {\n\t\t\tres += fmt.Sprintf(\"%s%s: %s,\\n\", indentStr(indent+1), k, v.string(indent+1))\n\t\t\treturn nil\n\t\t}); err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\treturn res + indentStr(indent) + \"}\"\n\tcase TypeSlice:\n\t\tmd := \"\"\n\t\tif v.IsSpread() {\n\t\t\tmd = \"spread, \"\n\t\t}\n\t\tif v.IsBranch() {\n\t\t\tmd += \"branch, \"\n\t\t}\n\t\tres := fmt.Sprintf(\"array[%s]{\\n\", strings.TrimSuffix(md, \", \"))\n\t\tif err := v.RangeSlice(func(k int, v *Value) error {\n\t\t\tres += fmt.Sprintf(\"%s%d: %s,\\n\", indentStr(indent+1), k, v.string(indent+1))\n\t\t\treturn nil\n\t\t}); err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\treturn res + indentStr(indent) + \"}\"\n\tcase TypeNull:\n\t\treturn indentStr(indent) + \"null\"\n\tdefault:\n\t\treturn fmt.Sprintf(\"unknown[%s]\", v.Interface())\n\t}\n}\n\n// NewValue creates a new value.\nfunc NewValue(v any) *Value {\n\tswitch val := v.(type) {\n\tcase *Value:\n\t\treturn NewNestedValue(val)\n\tcase reflect.Value:\n\t\treturn &Value{\n\t\t\tvalue:    val,\n\t\t\tMetadata: nil,\n\t\t}\n\tcase nil:\n\t\treturn NewNullValue()\n\tdefault:\n\t\tres := newPtr()\n\t\tif v != nil {\n\t\t\tres.Elem().Set(reflect.ValueOf(v))\n\t\t}\n\t\treturn &Value{\n\t\t\tvalue:    res,\n\t\t\tMetadata: nil,\n\t\t}\n\t}\n}\n\n// NewNestedValue creates a new nested value.\nfunc NewNestedValue(v *Value) *Value {\n\treturn &Value{\n\t\tvalue:    reflect.ValueOf(v),\n\t\tMetadata: nil,\n\t}\n}\n\nfunc (v *Value) isDaselValue() bool {\n\tcur := v.value\n\tfor cur.Kind() == reflect.Interface && !cur.IsNil() {\n\t\tcur = cur.Elem()\n\t}\n\treturn cur.Type() == reflect.TypeFor[*Value]()\n}\n\nfunc (v *Value) daselValue() (*Value, error) {\n\tif v.isDaselValue() {\n\t\tm, err := v.UnpackUntilType(reflect.TypeFor[*Value]())\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error getting dasel value: %w\", err)\n\t\t}\n\t\treturn m.value.Interface().(*Value), nil\n\t}\n\treturn nil, fmt.Errorf(\"value is not a dasel value\")\n}\n\n// Interface returns the value as an interface.\nfunc (v *Value) Interface() any {\n\tif v.IsNull() {\n\t\treturn nil\n\t}\n\treturn v.value.Interface()\n}\n\n// Kind returns the reflect kind of the value.\nfunc (v *Value) Kind() reflect.Kind {\n\treturn v.value.Kind()\n}\n\n// UnpackKinds unpacks the reflect value until it no longer matches the given kinds.\nfunc (v *Value) UnpackKinds(kinds ...reflect.Kind) *Value {\n\tval := v\n\tfor val.isDaselValue() {\n\t\tvar err error\n\t\tval, err = val.daselValue()\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t}\n\tres := val.value\n\tfor {\n\t\tif !slices.Contains(kinds, res.Kind()) || res.IsNil() {\n\t\t\treturn NewValue(res)\n\t\t}\n\t\tres = res.Elem()\n\t}\n}\n\ntype ErrCouldNotUnpackToType struct {\n\tType reflect.Type\n}\n\nfunc (e ErrCouldNotUnpackToType) Error() string {\n\treturn fmt.Sprintf(\"could not unpack to type: %s\", e.Type)\n}\n\n// UnpackUntilType unpacks the reflect value until it matches the given type.\nfunc (v *Value) UnpackUntilType(t reflect.Type) (*Value, error) {\n\tres := v.value\n\tvar daselValueType = reflect.TypeFor[*Value]()\n\tfor {\n\t\tif res.Type() == t {\n\t\t\treturn NewValue(res), nil\n\t\t}\n\t\tif t != daselValueType && res.Type() == daselValueType {\n\t\t\tm, err := v.daselValue()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"error getting dasel value: %w\", err)\n\t\t\t}\n\t\t\tres = m.value\n\t\t\tcontinue\n\n\t\t}\n\t\tif res.Kind() == reflect.Interface || res.Kind() == reflect.Pointer && !res.IsNil() {\n\t\t\tres = res.Elem()\n\t\t\tcontinue\n\t\t}\n\t\treturn nil, &ErrCouldNotUnpackToType{Type: t}\n\t}\n}\n\n// UnpackUntilAddressable unpacks the reflect value until it is addressable.\nfunc (v *Value) UnpackUntilAddressable() (*Value, error) {\n\tres := v.value\n\tfor {\n\t\tif res.CanAddr() {\n\t\t\treturn NewValue(res), nil\n\t\t}\n\t\tif res.Kind() == reflect.Interface || res.Kind() == reflect.Pointer && !res.IsNil() {\n\t\t\tres = res.Elem()\n\t\t\tcontinue\n\t\t}\n\t\treturn nil, fmt.Errorf(\"could not unpack addressable value\")\n\t}\n}\n\n// UnpackUntilKind unpacks the reflect value until it matches the given kind.\nfunc (v *Value) UnpackUntilKind(k reflect.Kind) (*Value, error) {\n\tres := v.value\n\tfor {\n\t\tif res.Kind() == k {\n\t\t\treturn NewValue(res), nil\n\t\t}\n\t\tif res.Kind() == reflect.Interface || res.Kind() == reflect.Pointer && !res.IsNil() {\n\t\t\tres = res.Elem()\n\t\t\tcontinue\n\t\t}\n\t\treturn nil, fmt.Errorf(\"could not unpack to kind: %s\", k)\n\t}\n}\n\n// UnpackUntilKinds unpacks the reflect value until it matches the given kind.\nfunc (v *Value) UnpackUntilKinds(kinds ...reflect.Kind) (*Value, error) {\n\tres := v.value\n\tfor {\n\t\tif slices.Contains(kinds, res.Kind()) {\n\t\t\treturn NewValue(res), nil\n\t\t}\n\t\tif res.Kind() == reflect.Interface || res.Kind() == reflect.Pointer && !res.IsNil() {\n\t\t\tres = res.Elem()\n\t\t\tcontinue\n\t\t}\n\t\treturn nil, fmt.Errorf(\"could not unpack to kinds: %v\", kinds)\n\t}\n}\n\n// Type returns the type of the value.\nfunc (v *Value) Type() Type {\n\tswitch {\n\tcase v.IsString():\n\t\treturn TypeString\n\tcase v.IsInt():\n\t\treturn TypeInt\n\tcase v.IsFloat():\n\t\treturn TypeFloat\n\tcase v.IsBool():\n\t\treturn TypeBool\n\tcase v.IsMap():\n\t\treturn TypeMap\n\tcase v.IsSlice():\n\t\treturn TypeSlice\n\tcase v.IsNull():\n\t\treturn TypeNull\n\tdefault:\n\t\treturn TypeUnknown\n\t}\n}\n\nfunc (v *Value) IsScalar() bool {\n\tunpacked := v.UnpackKinds(reflect.Interface, reflect.Pointer)\n\tkind := unpacked.Kind()\n\n\tswitch kind {\n\tcase reflect.String, reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16,\n\t\treflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16,\n\t\treflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64:\n\t\treturn true\n\tdefault:\n\t\treturn unpacked.isNull()\n\t}\n}\n\n// Len returns the length of the value.\nfunc (v *Value) Len() (int, error) {\n\tvar l int\n\tvar err error\n\n\tswitch {\n\tcase v.IsSlice():\n\t\tl, err = v.SliceLen()\n\tcase v.IsMap():\n\t\tl, err = v.MapLen()\n\tcase v.IsString():\n\t\tl, err = v.StringLen()\n\tdefault:\n\t\terr = ErrUnexpectedTypes{\n\t\t\tExpected: []Type{TypeSlice, TypeMap, TypeString},\n\t\t\tActual:   v.Type(),\n\t\t}\n\t}\n\n\tif err != nil {\n\t\treturn l, err\n\t}\n\n\treturn l, nil\n}\n\nfunc (v *Value) Copy() (*Value, error) {\n\tswitch v.Type() {\n\tcase TypeMap:\n\t\treturn v.MapCopy()\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"copy not supported for type: %s\", v.Type())\n\t}\n}\n"
  },
  {
    "path": "model/value_comparison.go",
    "content": "package model\n\n// Compare compares two values.\nfunc (v *Value) Compare(other *Value) (int, error) {\n\teq, err := v.Equal(other)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\teqVal, err := eq.BoolValue()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif eqVal {\n\t\treturn 0, nil\n\t}\n\n\tlt, err := v.LessThan(other)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tltVal, err := lt.BoolValue()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif ltVal {\n\t\treturn -1, nil\n\t}\n\n\treturn 1, nil\n}\n\n// Equal compares two values.\nfunc (v *Value) Equal(other *Value) (*Value, error) {\n\tif v.IsInt() && other.IsFloat() {\n\t\ta, err := v.IntValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tb, err := other.FloatValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn NewValue(float64(a) == b), nil\n\t}\n\tif v.IsFloat() && other.IsInt() {\n\t\ta, err := v.FloatValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tb, err := other.IntValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn NewValue(a == float64(b)), nil\n\t}\n\n\tif v.Type() != other.Type() {\n\t\treturn nil, ErrIncompatibleTypes{A: v, B: other}\n\t}\n\n\tisEqual, err := v.EqualTypeValue(other)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn NewValue(isEqual), nil\n}\n\n// NotEqual compares two values.\nfunc (v *Value) NotEqual(other *Value) (*Value, error) {\n\tequals, err := v.Equal(other)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tboolValue, err := equals.BoolValue()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn NewValue(!boolValue), nil\n}\n\n// LessThan compares two values.\nfunc (v *Value) LessThan(other *Value) (*Value, error) {\n\tif v.IsInt() && other.IsInt() {\n\t\ta, err := v.IntValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tb, err := other.IntValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn NewValue(a < b), nil\n\t}\n\tif v.IsFloat() && other.IsFloat() {\n\t\ta, err := v.FloatValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tb, err := other.FloatValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn NewValue(a < b), nil\n\t}\n\tif v.IsInt() && other.IsFloat() {\n\t\ta, err := v.IntValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tb, err := other.FloatValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn NewValue(float64(a) < b), nil\n\t}\n\tif v.IsFloat() && other.IsInt() {\n\t\ta, err := v.FloatValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tb, err := other.IntValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn NewValue(a < float64(b)), nil\n\t}\n\n\tif v.IsString() && other.IsString() {\n\t\ta, err := v.StringValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tb, err := other.StringValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn NewValue(a < b), nil\n\t}\n\n\treturn nil, ErrIncompatibleTypes{A: v, B: other}\n}\n\n// LessThanOrEqual compares two values.\nfunc (v *Value) LessThanOrEqual(other *Value) (*Value, error) {\n\tlessThan, err := v.LessThan(other)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tboolValue, err := lessThan.BoolValue()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tequals, err := v.Equal(other)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tboolEquals, err := equals.BoolValue()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn NewValue(boolValue || boolEquals), nil\n}\n\n// GreaterThan compares two values.\nfunc (v *Value) GreaterThan(other *Value) (*Value, error) {\n\tlessThanOrEqual, err := v.LessThanOrEqual(other)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tboolValue, err := lessThanOrEqual.BoolValue()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn NewValue(!boolValue), nil\n}\n\n// GreaterThanOrEqual compares two values.\nfunc (v *Value) GreaterThanOrEqual(other *Value) (*Value, error) {\n\tlessThan, err := v.LessThan(other)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tboolValue, err := lessThan.BoolValue()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn NewValue(!boolValue), nil\n}\n\n// EqualTypeValue compares two values of the same type.\nfunc (v *Value) EqualTypeValue(other *Value) (bool, error) {\n\tif v.Type() != other.Type() {\n\t\treturn false, nil\n\t}\n\n\tswitch v.Type() {\n\tcase TypeString:\n\t\ta, err := v.StringValue()\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\tb, err := other.StringValue()\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\treturn a == b, nil\n\tcase TypeInt:\n\t\ta, err := v.IntValue()\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\tb, err := other.IntValue()\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\treturn a == b, nil\n\tcase TypeFloat:\n\t\ta, err := v.FloatValue()\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\tb, err := other.FloatValue()\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\treturn a == b, nil\n\tcase TypeBool:\n\t\ta, err := v.BoolValue()\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\tb, err := other.BoolValue()\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\treturn a == b, nil\n\tcase TypeMap:\n\t\ta, err := v.MapKeys()\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\tb, err := other.MapKeys()\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\tif len(a) != len(b) {\n\t\t\treturn false, nil\n\t\t}\n\t\tfor _, key := range a {\n\t\t\tvalA, err := v.GetMapKey(key)\n\t\t\tif err != nil {\n\t\t\t\treturn false, err\n\t\t\t}\n\t\t\tvalB, err := other.GetMapKey(key)\n\t\t\tif err != nil {\n\t\t\t\treturn false, err\n\t\t\t}\n\t\t\tequal, err := valA.EqualTypeValue(valB)\n\t\t\tif err != nil {\n\t\t\t\treturn false, err\n\t\t\t}\n\t\t\tif !equal {\n\t\t\t\treturn false, nil\n\t\t\t}\n\t\t}\n\t\treturn true, nil\n\tcase TypeSlice:\n\t\ta, err := v.SliceLen()\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\tb, err := other.SliceLen()\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\tif a != b {\n\t\t\treturn false, nil\n\t\t}\n\t\tfor i := 0; i < a; i++ {\n\t\t\tvalA, err := v.GetSliceIndex(i)\n\t\t\tif err != nil {\n\t\t\t\treturn false, err\n\t\t\t}\n\t\t\tvalB, err := other.GetSliceIndex(i)\n\t\t\tif err != nil {\n\t\t\t\treturn false, err\n\t\t\t}\n\t\t\tequal, err := valA.EqualTypeValue(valB)\n\t\t\tif err != nil {\n\t\t\t\treturn false, err\n\t\t\t}\n\t\t\tif !equal {\n\t\t\t\treturn false, nil\n\t\t\t}\n\t\t}\n\t\treturn true, nil\n\tcase TypeNull:\n\t\treturn other.Type() == TypeNull, nil\n\tdefault:\n\t\treturn false, nil\n\t}\n}\n"
  },
  {
    "path": "model/value_comparison_test.go",
    "content": "package model_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\ntype compareTestCase struct {\n\ta   *model.Value\n\tb   *model.Value\n\texp bool\n}\n\nfunc TestValue_Equal(t *testing.T) {\n\trun := func(tc compareTestCase) func(t *testing.T) {\n\t\treturn func(t *testing.T) {\n\t\t\tgot, err := tc.a.Equal(tc.b)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tgotBool, err := got.BoolValue()\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif gotBool != tc.exp {\n\t\t\t\tt.Errorf(\"expected %v, got %v\", tc.exp, got)\n\t\t\t}\n\t\t}\n\t}\n\tt.Run(\"string\", func(t *testing.T) {\n\t\tt.Run(\"equal\", run(compareTestCase{\n\t\t\ta:   model.NewStringValue(\"hello\"),\n\t\t\tb:   model.NewStringValue(\"hello\"),\n\t\t\texp: true,\n\t\t}))\n\t\tt.Run(\"not equal\", run(compareTestCase{\n\t\t\ta:   model.NewStringValue(\"hello\"),\n\t\t\tb:   model.NewStringValue(\"world\"),\n\t\t\texp: false,\n\t\t}))\n\t})\n\tt.Run(\"int\", func(t *testing.T) {\n\t\tt.Run(\"equal\", run(compareTestCase{\n\t\t\ta:   model.NewIntValue(1),\n\t\t\tb:   model.NewIntValue(1),\n\t\t\texp: true,\n\t\t}))\n\t\tt.Run(\"not equal\", run(compareTestCase{\n\t\t\ta:   model.NewIntValue(1),\n\t\t\tb:   model.NewIntValue(2),\n\t\t\texp: false,\n\t\t}))\n\t\tt.Run(\"equal float\", run(compareTestCase{\n\t\t\ta:   model.NewIntValue(1),\n\t\t\tb:   model.NewFloatValue(1),\n\t\t\texp: true,\n\t\t}))\n\t\tt.Run(\"not equal float\", run(compareTestCase{\n\t\t\ta:   model.NewIntValue(1),\n\t\t\tb:   model.NewFloatValue(2),\n\t\t\texp: false,\n\t\t}))\n\t})\n\tt.Run(\"float\", func(t *testing.T) {\n\t\tt.Run(\"equal\", run(compareTestCase{\n\t\t\ta:   model.NewFloatValue(1.1),\n\t\t\tb:   model.NewFloatValue(1.1),\n\t\t\texp: true,\n\t\t}))\n\t\tt.Run(\"not equal\", run(compareTestCase{\n\t\t\ta:   model.NewFloatValue(1.1),\n\t\t\tb:   model.NewFloatValue(1.2),\n\t\t\texp: false,\n\t\t}))\n\t\tt.Run(\"equal int\", run(compareTestCase{\n\t\t\ta:   model.NewFloatValue(1),\n\t\t\tb:   model.NewIntValue(1),\n\t\t\texp: true,\n\t\t}))\n\t\tt.Run(\"not equal int\", run(compareTestCase{\n\t\t\ta:   model.NewFloatValue(1.1),\n\t\t\tb:   model.NewIntValue(1),\n\t\t\texp: false,\n\t\t}))\n\t})\n\tt.Run(\"bool\", func(t *testing.T) {\n\t\tt.Run(\"equal\", run(compareTestCase{\n\t\t\ta:   model.NewBoolValue(true),\n\t\t\tb:   model.NewBoolValue(true),\n\t\t\texp: true,\n\t\t}))\n\t\tt.Run(\"not equal\", run(compareTestCase{\n\t\t\ta:   model.NewBoolValue(true),\n\t\t\tb:   model.NewBoolValue(false),\n\t\t\texp: false,\n\t\t}))\n\t})\n\tt.Run(\"map\", func(t *testing.T) {\n\t\tt.Run(\"equal\", run(compareTestCase{\n\t\t\ta: model.NewValue(map[string]interface{}{\n\t\t\t\t\"hello\": \"world\",\n\t\t\t}),\n\t\t\tb: model.NewValue(map[string]interface{}{\n\t\t\t\t\"hello\": \"world\",\n\t\t\t}),\n\t\t\texp: true,\n\t\t}))\n\t\tt.Run(\"not equal\", run(compareTestCase{\n\t\t\ta: model.NewValue(map[string]interface{}{\n\t\t\t\t\"hello\": \"world\",\n\t\t\t}),\n\t\t\tb: model.NewValue(map[string]interface{}{\n\t\t\t\t\"hello\": \"world2\",\n\t\t\t}),\n\t\t\texp: false,\n\t\t}))\n\t})\n\tt.Run(\"array\", func(t *testing.T) {\n\t\tt.Run(\"equal\", run(compareTestCase{\n\t\t\ta: model.NewValue([]interface{}{\n\t\t\t\t\"hello\", \"world\",\n\t\t\t}),\n\t\t\tb: model.NewValue([]interface{}{\n\t\t\t\t\"hello\", \"world\",\n\t\t\t}),\n\t\t\texp: true,\n\t\t}))\n\t\tt.Run(\"not equal\", run(compareTestCase{\n\t\t\ta: model.NewValue([]interface{}{\n\t\t\t\t\"hello\", \"world\",\n\t\t\t}),\n\t\t\tb: model.NewValue([]interface{}{\n\t\t\t\t\"hello\", \"world2\",\n\t\t\t}),\n\t\t\texp: false,\n\t\t}))\n\t})\n\tt.Run(\"null\", func(t *testing.T) {\n\t\tt.Run(\"equal\", run(compareTestCase{\n\t\t\ta:   model.NewValue(nil),\n\t\t\tb:   model.NewValue(nil),\n\t\t\texp: true,\n\t\t}))\n\t})\n}\n\nfunc TestValue_NotEqual(t *testing.T) {\n\trun := func(tc compareTestCase) func(t *testing.T) {\n\t\treturn func(t *testing.T) {\n\t\t\tgot, err := tc.a.NotEqual(tc.b)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tgotBool, err := got.BoolValue()\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif gotBool != tc.exp {\n\t\t\t\tt.Errorf(\"expected %v, got %v\", tc.exp, got)\n\t\t\t}\n\t\t}\n\t}\n\tt.Run(\"string\", func(t *testing.T) {\n\t\tt.Run(\"equal\", run(compareTestCase{\n\t\t\ta:   model.NewStringValue(\"hello\"),\n\t\t\tb:   model.NewStringValue(\"hello\"),\n\t\t\texp: false,\n\t\t}))\n\t\tt.Run(\"not equal\", run(compareTestCase{\n\t\t\ta:   model.NewStringValue(\"hello\"),\n\t\t\tb:   model.NewStringValue(\"world\"),\n\t\t\texp: true,\n\t\t}))\n\t})\n\tt.Run(\"int\", func(t *testing.T) {\n\t\tt.Run(\"equal\", run(compareTestCase{\n\t\t\ta:   model.NewIntValue(1),\n\t\t\tb:   model.NewIntValue(1),\n\t\t\texp: false,\n\t\t}))\n\t\tt.Run(\"not equal\", run(compareTestCase{\n\t\t\ta:   model.NewIntValue(1),\n\t\t\tb:   model.NewIntValue(2),\n\t\t\texp: true,\n\t\t}))\n\t\tt.Run(\"equal float\", run(compareTestCase{\n\t\t\ta:   model.NewIntValue(1),\n\t\t\tb:   model.NewFloatValue(1),\n\t\t\texp: false,\n\t\t}))\n\t\tt.Run(\"not equal float\", run(compareTestCase{\n\t\t\ta:   model.NewIntValue(1),\n\t\t\tb:   model.NewFloatValue(2),\n\t\t\texp: true,\n\t\t}))\n\t})\n\tt.Run(\"float\", func(t *testing.T) {\n\t\tt.Run(\"equal\", run(compareTestCase{\n\t\t\ta:   model.NewFloatValue(1.1),\n\t\t\tb:   model.NewFloatValue(1.1),\n\t\t\texp: false,\n\t\t}))\n\t\tt.Run(\"not equal\", run(compareTestCase{\n\t\t\ta:   model.NewFloatValue(1.1),\n\t\t\tb:   model.NewFloatValue(1.2),\n\t\t\texp: true,\n\t\t}))\n\t\tt.Run(\"equal int\", run(compareTestCase{\n\t\t\ta:   model.NewFloatValue(1),\n\t\t\tb:   model.NewIntValue(1),\n\t\t\texp: false,\n\t\t}))\n\t\tt.Run(\"not equal int\", run(compareTestCase{\n\t\t\ta:   model.NewFloatValue(1.1),\n\t\t\tb:   model.NewIntValue(1),\n\t\t\texp: true,\n\t\t}))\n\t})\n\tt.Run(\"bool\", func(t *testing.T) {\n\t\tt.Run(\"equal\", run(compareTestCase{\n\t\t\ta:   model.NewBoolValue(true),\n\t\t\tb:   model.NewBoolValue(true),\n\t\t\texp: false,\n\t\t}))\n\t\tt.Run(\"not equal\", run(compareTestCase{\n\t\t\ta:   model.NewBoolValue(true),\n\t\t\tb:   model.NewBoolValue(false),\n\t\t\texp: true,\n\t\t}))\n\t})\n\tt.Run(\"map\", func(t *testing.T) {\n\t\tt.Run(\"equal\", run(compareTestCase{\n\t\t\ta: model.NewValue(map[string]interface{}{\n\t\t\t\t\"hello\": \"world\",\n\t\t\t}),\n\t\t\tb: model.NewValue(map[string]interface{}{\n\t\t\t\t\"hello\": \"world\",\n\t\t\t}),\n\t\t\texp: false,\n\t\t}))\n\t\tt.Run(\"not equal\", run(compareTestCase{\n\t\t\ta: model.NewValue(map[string]interface{}{\n\t\t\t\t\"hello\": \"world\",\n\t\t\t}),\n\t\t\tb: model.NewValue(map[string]interface{}{\n\t\t\t\t\"hello\": \"world2\",\n\t\t\t}),\n\t\t\texp: true,\n\t\t}))\n\t})\n\tt.Run(\"array\", func(t *testing.T) {\n\t\tt.Run(\"equal\", run(compareTestCase{\n\t\t\ta: model.NewValue([]interface{}{\n\t\t\t\t\"hello\", \"world\",\n\t\t\t}),\n\t\t\tb: model.NewValue([]interface{}{\n\t\t\t\t\"hello\", \"world\",\n\t\t\t}),\n\t\t\texp: false,\n\t\t}))\n\t\tt.Run(\"not equal\", run(compareTestCase{\n\t\t\ta: model.NewValue([]interface{}{\n\t\t\t\t\"hello\", \"world\",\n\t\t\t}),\n\t\t\tb: model.NewValue([]interface{}{\n\t\t\t\t\"hello\", \"world2\",\n\t\t\t}),\n\t\t\texp: true,\n\t\t}))\n\t})\n\tt.Run(\"null\", func(t *testing.T) {\n\t\tt.Run(\"equal\", run(compareTestCase{\n\t\t\ta:   model.NewValue(nil),\n\t\t\tb:   model.NewValue(nil),\n\t\t\texp: false,\n\t\t}))\n\t})\n}\n\nfunc TestValue_LessThan(t *testing.T) {\n\trun := func(tc compareTestCase) func(t *testing.T) {\n\t\treturn func(t *testing.T) {\n\t\t\tgot, err := tc.a.LessThan(tc.b)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tgotBool, err := got.BoolValue()\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif gotBool != tc.exp {\n\t\t\t\tt.Errorf(\"expected %v, got %v\", tc.exp, got)\n\t\t\t}\n\t\t}\n\t}\n\tt.Run(\"int\", func(t *testing.T) {\n\t\tt.Run(\"less\", run(compareTestCase{\n\t\t\ta:   model.NewIntValue(1),\n\t\t\tb:   model.NewIntValue(2),\n\t\t\texp: true,\n\t\t}))\n\t\tt.Run(\"greater\", run(compareTestCase{\n\t\t\ta:   model.NewIntValue(2),\n\t\t\tb:   model.NewIntValue(1),\n\t\t\texp: false,\n\t\t}))\n\t\tt.Run(\"equal\", run(compareTestCase{\n\t\t\ta:   model.NewIntValue(1),\n\t\t\tb:   model.NewIntValue(1),\n\t\t\texp: false,\n\t\t}))\n\t})\n\tt.Run(\"float\", func(t *testing.T) {\n\t\tt.Run(\"less\", run(compareTestCase{\n\t\t\ta:   model.NewFloatValue(1.1),\n\t\t\tb:   model.NewFloatValue(1.2),\n\t\t\texp: true,\n\t\t}))\n\t\tt.Run(\"greater\", run(compareTestCase{\n\t\t\ta:   model.NewFloatValue(1.2),\n\t\t\tb:   model.NewFloatValue(1.1),\n\t\t\texp: false,\n\t\t}))\n\t\tt.Run(\"equal\", run(compareTestCase{\n\t\t\ta:   model.NewFloatValue(1.1),\n\t\t\tb:   model.NewFloatValue(1.1),\n\t\t\texp: false,\n\t\t}))\n\t})\n\tt.Run(\"int float\", func(t *testing.T) {\n\t\tt.Run(\"less\", run(compareTestCase{\n\t\t\ta:   model.NewIntValue(1),\n\t\t\tb:   model.NewFloatValue(2),\n\t\t\texp: true,\n\t\t}))\n\t\tt.Run(\"greater\", run(compareTestCase{\n\t\t\ta:   model.NewIntValue(2),\n\t\t\tb:   model.NewFloatValue(1),\n\t\t\texp: false,\n\t\t}))\n\t\tt.Run(\"equal\", run(compareTestCase{\n\t\t\ta:   model.NewIntValue(1),\n\t\t\tb:   model.NewFloatValue(1),\n\t\t\texp: false,\n\t\t}))\n\t})\n\tt.Run(\"float int\", func(t *testing.T) {\n\t\tt.Run(\"less\", run(compareTestCase{\n\t\t\ta:   model.NewFloatValue(1.1),\n\t\t\tb:   model.NewIntValue(2),\n\t\t\texp: true,\n\t\t}))\n\t\tt.Run(\"greater\", run(compareTestCase{\n\t\t\ta:   model.NewFloatValue(2),\n\t\t\tb:   model.NewIntValue(1),\n\t\t\texp: false,\n\t\t}))\n\t\tt.Run(\"equal\", run(compareTestCase{\n\t\t\ta:   model.NewFloatValue(1),\n\t\t\tb:   model.NewIntValue(1),\n\t\t\texp: false,\n\t\t}))\n\t})\n\tt.Run(\"string\", func(t *testing.T) {\n\t\tt.Run(\"less\", run(compareTestCase{\n\t\t\ta:   model.NewStringValue(\"a\"),\n\t\t\tb:   model.NewStringValue(\"b\"),\n\t\t\texp: true,\n\t\t}))\n\t\tt.Run(\"greater\", run(compareTestCase{\n\t\t\ta:   model.NewStringValue(\"b\"),\n\t\t\tb:   model.NewStringValue(\"a\"),\n\t\t\texp: false,\n\t\t}))\n\t\tt.Run(\"equal\", run(compareTestCase{\n\t\t\ta:   model.NewStringValue(\"a\"),\n\t\t\tb:   model.NewStringValue(\"a\"),\n\t\t\texp: false,\n\t\t}))\n\t})\n}\n\nfunc TestValue_LessThanOrEqual(t *testing.T) {\n\trun := func(tc compareTestCase) func(t *testing.T) {\n\t\treturn func(t *testing.T) {\n\t\t\tgot, err := tc.a.LessThanOrEqual(tc.b)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tgotBool, err := got.BoolValue()\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif gotBool != tc.exp {\n\t\t\t\tt.Errorf(\"expected %v, got %v\", tc.exp, got)\n\t\t\t}\n\t\t}\n\t}\n\tt.Run(\"int\", func(t *testing.T) {\n\t\tt.Run(\"less\", run(compareTestCase{\n\t\t\ta:   model.NewIntValue(1),\n\t\t\tb:   model.NewIntValue(2),\n\t\t\texp: true,\n\t\t}))\n\t\tt.Run(\"greater\", run(compareTestCase{\n\t\t\ta:   model.NewIntValue(2),\n\t\t\tb:   model.NewIntValue(1),\n\t\t\texp: false,\n\t\t}))\n\t\tt.Run(\"equal\", run(compareTestCase{\n\t\t\ta:   model.NewIntValue(1),\n\t\t\tb:   model.NewIntValue(1),\n\t\t\texp: true,\n\t\t}))\n\t})\n\tt.Run(\"float\", func(t *testing.T) {\n\t\tt.Run(\"less\", run(compareTestCase{\n\t\t\ta:   model.NewFloatValue(1.1),\n\t\t\tb:   model.NewFloatValue(1.2),\n\t\t\texp: true,\n\t\t}))\n\t\tt.Run(\"greater\", run(compareTestCase{\n\t\t\ta:   model.NewFloatValue(1.2),\n\t\t\tb:   model.NewFloatValue(1.1),\n\t\t\texp: false,\n\t\t}))\n\t\tt.Run(\"equal\", run(compareTestCase{\n\t\t\ta:   model.NewFloatValue(1.1),\n\t\t\tb:   model.NewFloatValue(1.1),\n\t\t\texp: true,\n\t\t}))\n\t})\n\tt.Run(\"int float\", func(t *testing.T) {\n\t\tt.Run(\"less\", run(compareTestCase{\n\t\t\ta:   model.NewIntValue(1),\n\t\t\tb:   model.NewFloatValue(2),\n\t\t\texp: true,\n\t\t}))\n\t\tt.Run(\"greater\", run(compareTestCase{\n\t\t\ta:   model.NewIntValue(2),\n\t\t\tb:   model.NewFloatValue(1),\n\t\t\texp: false,\n\t\t}))\n\t\tt.Run(\"equal\", run(compareTestCase{\n\t\t\ta:   model.NewIntValue(1),\n\t\t\tb:   model.NewFloatValue(1),\n\t\t\texp: true,\n\t\t}))\n\t})\n\tt.Run(\"float int\", func(t *testing.T) {\n\t\tt.Run(\"less\", run(compareTestCase{\n\t\t\ta:   model.NewFloatValue(1.1),\n\t\t\tb:   model.NewIntValue(2),\n\t\t\texp: true,\n\t\t}))\n\t\tt.Run(\"greater\", run(compareTestCase{\n\t\t\ta:   model.NewFloatValue(2),\n\t\t\tb:   model.NewIntValue(1),\n\t\t\texp: false,\n\t\t}))\n\t\tt.Run(\"equal\", run(compareTestCase{\n\t\t\ta:   model.NewFloatValue(1),\n\t\t\tb:   model.NewIntValue(1),\n\t\t\texp: true,\n\t\t}))\n\t})\n\tt.Run(\"string\", func(t *testing.T) {\n\t\tt.Run(\"less\", run(compareTestCase{\n\t\t\ta:   model.NewStringValue(\"a\"),\n\t\t\tb:   model.NewStringValue(\"b\"),\n\t\t\texp: true,\n\t\t}))\n\t\tt.Run(\"greater\", run(compareTestCase{\n\t\t\ta:   model.NewStringValue(\"b\"),\n\t\t\tb:   model.NewStringValue(\"a\"),\n\t\t\texp: false,\n\t\t}))\n\t\tt.Run(\"equal\", run(compareTestCase{\n\t\t\ta:   model.NewStringValue(\"a\"),\n\t\t\tb:   model.NewStringValue(\"a\"),\n\t\t\texp: true,\n\t\t}))\n\t})\n}\n\nfunc TestValue_GreaterThan(t *testing.T) {\n\trun := func(tc compareTestCase) func(t *testing.T) {\n\t\treturn func(t *testing.T) {\n\t\t\tgot, err := tc.a.GreaterThan(tc.b)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tgotBool, err := got.BoolValue()\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif gotBool != tc.exp {\n\t\t\t\tt.Errorf(\"expected %v, got %v\", tc.exp, got)\n\t\t\t}\n\t\t}\n\t}\n\tt.Run(\"int\", func(t *testing.T) {\n\t\tt.Run(\"less\", run(compareTestCase{\n\t\t\ta:   model.NewIntValue(1),\n\t\t\tb:   model.NewIntValue(2),\n\t\t\texp: false,\n\t\t}))\n\t\tt.Run(\"greater\", run(compareTestCase{\n\t\t\ta:   model.NewIntValue(2),\n\t\t\tb:   model.NewIntValue(1),\n\t\t\texp: true,\n\t\t}))\n\t\tt.Run(\"equal\", run(compareTestCase{\n\t\t\ta:   model.NewIntValue(1),\n\t\t\tb:   model.NewIntValue(1),\n\t\t\texp: false,\n\t\t}))\n\t})\n\tt.Run(\"float\", func(t *testing.T) {\n\t\tt.Run(\"less\", run(compareTestCase{\n\t\t\ta:   model.NewFloatValue(1.1),\n\t\t\tb:   model.NewFloatValue(1.2),\n\t\t\texp: false,\n\t\t}))\n\t\tt.Run(\"greater\", run(compareTestCase{\n\t\t\ta:   model.NewFloatValue(1.2),\n\t\t\tb:   model.NewFloatValue(1.1),\n\t\t\texp: true,\n\t\t}))\n\t\tt.Run(\"equal\", run(compareTestCase{\n\t\t\ta:   model.NewFloatValue(1.1),\n\t\t\tb:   model.NewFloatValue(1.1),\n\t\t\texp: false,\n\t\t}))\n\t})\n\tt.Run(\"int float\", func(t *testing.T) {\n\t\tt.Run(\"less\", run(compareTestCase{\n\t\t\ta:   model.NewIntValue(1),\n\t\t\tb:   model.NewFloatValue(2),\n\t\t\texp: false,\n\t\t}))\n\t\tt.Run(\"greater\", run(compareTestCase{\n\t\t\ta:   model.NewIntValue(2),\n\t\t\tb:   model.NewFloatValue(1),\n\t\t\texp: true,\n\t\t}))\n\t\tt.Run(\"equal\", run(compareTestCase{\n\t\t\ta:   model.NewIntValue(1),\n\t\t\tb:   model.NewFloatValue(1),\n\t\t\texp: false,\n\t\t}))\n\t})\n\tt.Run(\"float int\", func(t *testing.T) {\n\t\tt.Run(\"less\", run(compareTestCase{\n\t\t\ta:   model.NewFloatValue(1.1),\n\t\t\tb:   model.NewIntValue(2),\n\t\t\texp: false,\n\t\t}))\n\t\tt.Run(\"greater\", run(compareTestCase{\n\t\t\ta:   model.NewFloatValue(2),\n\t\t\tb:   model.NewIntValue(1),\n\t\t\texp: true,\n\t\t}))\n\t\tt.Run(\"equal\", run(compareTestCase{\n\t\t\ta:   model.NewFloatValue(1),\n\t\t\tb:   model.NewIntValue(1),\n\t\t\texp: false,\n\t\t}))\n\t})\n\tt.Run(\"string\", func(t *testing.T) {\n\t\tt.Run(\"less\", run(compareTestCase{\n\t\t\ta:   model.NewStringValue(\"a\"),\n\t\t\tb:   model.NewStringValue(\"b\"),\n\t\t\texp: false,\n\t\t}))\n\t\tt.Run(\"greater\", run(compareTestCase{\n\t\t\ta:   model.NewStringValue(\"b\"),\n\t\t\tb:   model.NewStringValue(\"a\"),\n\t\t\texp: true,\n\t\t}))\n\t\tt.Run(\"equal\", run(compareTestCase{\n\t\t\ta:   model.NewStringValue(\"a\"),\n\t\t\tb:   model.NewStringValue(\"a\"),\n\t\t\texp: false,\n\t\t}))\n\t})\n}\n\nfunc TestValue_GreaterThanOrEqual(t *testing.T) {\n\trun := func(tc compareTestCase) func(t *testing.T) {\n\t\treturn func(t *testing.T) {\n\t\t\tgot, err := tc.a.GreaterThanOrEqual(tc.b)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tgotBool, err := got.BoolValue()\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif gotBool != tc.exp {\n\t\t\t\tt.Errorf(\"expected %v, got %v\", tc.exp, got)\n\t\t\t}\n\t\t}\n\t}\n\tt.Run(\"int\", func(t *testing.T) {\n\t\tt.Run(\"less\", run(compareTestCase{\n\t\t\ta:   model.NewIntValue(1),\n\t\t\tb:   model.NewIntValue(2),\n\t\t\texp: false,\n\t\t}))\n\t\tt.Run(\"greater\", run(compareTestCase{\n\t\t\ta:   model.NewIntValue(2),\n\t\t\tb:   model.NewIntValue(1),\n\t\t\texp: true,\n\t\t}))\n\t\tt.Run(\"equal\", run(compareTestCase{\n\t\t\ta:   model.NewIntValue(1),\n\t\t\tb:   model.NewIntValue(1),\n\t\t\texp: true,\n\t\t}))\n\t})\n\tt.Run(\"float\", func(t *testing.T) {\n\t\tt.Run(\"less\", run(compareTestCase{\n\t\t\ta:   model.NewFloatValue(1.1),\n\t\t\tb:   model.NewFloatValue(1.2),\n\t\t\texp: false,\n\t\t}))\n\t\tt.Run(\"greater\", run(compareTestCase{\n\t\t\ta:   model.NewFloatValue(1.2),\n\t\t\tb:   model.NewFloatValue(1.1),\n\t\t\texp: true,\n\t\t}))\n\t\tt.Run(\"equal\", run(compareTestCase{\n\t\t\ta:   model.NewFloatValue(1.1),\n\t\t\tb:   model.NewFloatValue(1.1),\n\t\t\texp: true,\n\t\t}))\n\t})\n\tt.Run(\"int float\", func(t *testing.T) {\n\t\tt.Run(\"less\", run(compareTestCase{\n\t\t\ta:   model.NewIntValue(1),\n\t\t\tb:   model.NewFloatValue(2),\n\t\t\texp: false,\n\t\t}))\n\t\tt.Run(\"greater\", run(compareTestCase{\n\t\t\ta:   model.NewIntValue(2),\n\t\t\tb:   model.NewFloatValue(1),\n\t\t\texp: true,\n\t\t}))\n\t\tt.Run(\"equal\", run(compareTestCase{\n\t\t\ta:   model.NewIntValue(1),\n\t\t\tb:   model.NewFloatValue(1),\n\t\t\texp: true,\n\t\t}))\n\t})\n\tt.Run(\"float int\", func(t *testing.T) {\n\t\tt.Run(\"less\", run(compareTestCase{\n\t\t\ta:   model.NewFloatValue(1.1),\n\t\t\tb:   model.NewIntValue(2),\n\t\t\texp: false,\n\t\t}))\n\t\tt.Run(\"greater\", run(compareTestCase{\n\t\t\ta:   model.NewFloatValue(2),\n\t\t\tb:   model.NewIntValue(1),\n\t\t\texp: true,\n\t\t}))\n\t\tt.Run(\"equal\", run(compareTestCase{\n\t\t\ta:   model.NewFloatValue(1),\n\t\t\tb:   model.NewIntValue(1),\n\t\t\texp: true,\n\t\t}))\n\t})\n\tt.Run(\"string\", func(t *testing.T) {\n\t\tt.Run(\"less\", run(compareTestCase{\n\t\t\ta:   model.NewStringValue(\"a\"),\n\t\t\tb:   model.NewStringValue(\"b\"),\n\t\t\texp: false,\n\t\t}))\n\t\tt.Run(\"greater\", run(compareTestCase{\n\t\t\ta:   model.NewStringValue(\"b\"),\n\t\t\tb:   model.NewStringValue(\"a\"),\n\t\t\texp: true,\n\t\t}))\n\t\tt.Run(\"equal\", run(compareTestCase{\n\t\t\ta:   model.NewStringValue(\"a\"),\n\t\t\tb:   model.NewStringValue(\"a\"),\n\t\t\texp: true,\n\t\t}))\n\t})\n}\n\nfunc TestValue_Compare(t *testing.T) {\n\trun := func(a *model.Value, b *model.Value, exp int) func(t *testing.T) {\n\t\treturn func(t *testing.T) {\n\t\t\tgot, err := a.Compare(b)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif got != exp {\n\t\t\t\tt.Errorf(\"expected %d, got %d\", exp, got)\n\t\t\t}\n\t\t}\n\t}\n\tt.Run(\"int\", func(t *testing.T) {\n\t\tt.Run(\"less\", run(\n\t\t\tmodel.NewIntValue(1),\n\t\t\tmodel.NewIntValue(2),\n\t\t\t-1,\n\t\t))\n\t\tt.Run(\"greater\", run(\n\t\t\tmodel.NewIntValue(2),\n\t\t\tmodel.NewIntValue(1),\n\t\t\t1,\n\t\t))\n\t\tt.Run(\"equal\", run(\n\t\t\tmodel.NewIntValue(1),\n\t\t\tmodel.NewIntValue(1),\n\t\t\t0,\n\t\t))\n\t})\n\tt.Run(\"float\", func(t *testing.T) {\n\t\tt.Run(\"less\", run(\n\t\t\tmodel.NewFloatValue(1.1),\n\t\t\tmodel.NewFloatValue(1.2),\n\t\t\t-1,\n\t\t))\n\t\tt.Run(\"greater\", run(\n\t\t\tmodel.NewFloatValue(1.2),\n\t\t\tmodel.NewFloatValue(1.1),\n\t\t\t1,\n\t\t))\n\t\tt.Run(\"equal\", run(\n\t\t\tmodel.NewFloatValue(1.1),\n\t\t\tmodel.NewFloatValue(1.1),\n\t\t\t0,\n\t\t))\n\t})\n\tt.Run(\"int float\", func(t *testing.T) {\n\t\tt.Run(\"less\", run(\n\t\t\tmodel.NewIntValue(1),\n\t\t\tmodel.NewFloatValue(2),\n\t\t\t-1,\n\t\t))\n\t\tt.Run(\"greater\", run(\n\t\t\tmodel.NewIntValue(2),\n\t\t\tmodel.NewFloatValue(1),\n\t\t\t1,\n\t\t))\n\t\tt.Run(\"equal\", run(\n\t\t\tmodel.NewIntValue(1),\n\t\t\tmodel.NewFloatValue(1),\n\t\t\t0,\n\t\t))\n\t})\n\tt.Run(\"float int\", func(t *testing.T) {\n\t\tt.Run(\"less\", run(\n\t\t\tmodel.NewFloatValue(1.1),\n\t\t\tmodel.NewIntValue(2),\n\t\t\t-1,\n\t\t))\n\t\tt.Run(\"greater\", run(\n\t\t\tmodel.NewFloatValue(1.1),\n\t\t\tmodel.NewIntValue(1),\n\t\t\t1,\n\t\t))\n\t\tt.Run(\"equal\", run(\n\t\t\tmodel.NewFloatValue(1),\n\t\t\tmodel.NewIntValue(1),\n\t\t\t0,\n\t\t))\n\t})\n\tt.Run(\"string\", func(t *testing.T) {\n\t\tt.Run(\"less\", run(\n\t\t\tmodel.NewStringValue(\"a\"),\n\t\t\tmodel.NewStringValue(\"b\"),\n\t\t\t-1,\n\t\t))\n\t\tt.Run(\"greater\", run(\n\t\t\tmodel.NewStringValue(\"b\"),\n\t\t\tmodel.NewStringValue(\"a\"),\n\t\t\t1,\n\t\t))\n\t\tt.Run(\"equal\", run(\n\t\t\tmodel.NewStringValue(\"a\"),\n\t\t\tmodel.NewStringValue(\"a\"),\n\t\t\t0,\n\t\t))\n\t})\n}\n"
  },
  {
    "path": "model/value_literal.go",
    "content": "package model\n\nimport (\n\t\"reflect\"\n\t\"slices\"\n)\n\nfunc newPtr() reflect.Value {\n\treturn reflect.New(reflect.TypeFor[any]())\n}\n\n// NewNullValue creates a new Value with a nil value.\nfunc NewNullValue() *Value {\n\treturn NewValue(newPtr())\n}\n\n// IsNull returns true if the value is null.\nfunc (v *Value) IsNull() bool {\n\treturn v.UnpackKinds(reflect.Pointer, reflect.Interface).isNull()\n}\n\nfunc (v *Value) isNull() bool {\n\t// This logic can be cleaned up.\n\tunpacked, err := v.UnpackUntilKinds(reflect.Chan, reflect.Func, reflect.Map, reflect.Pointer, reflect.UnsafePointer, reflect.Interface, reflect.Slice)\n\tif err != nil {\n\t\treturn false\n\t}\n\treturn unpacked.value.IsNil()\n}\n\n// NewStringValue creates a new Value with a string value.\nfunc NewStringValue(x string) *Value {\n\tres := newPtr()\n\tres.Elem().Set(reflect.ValueOf(x))\n\treturn NewValue(res)\n}\n\n// IsString returns true if the value is a string.\nfunc (v *Value) IsString() bool {\n\treturn v.UnpackKinds(reflect.Pointer, reflect.Interface).isString()\n}\n\nfunc (v *Value) isString() bool {\n\treturn v.value.Kind() == reflect.String\n}\n\n// StringValue returns the string value of the Value.\nfunc (v *Value) StringValue() (string, error) {\n\tunpacked := v.UnpackKinds(reflect.Pointer, reflect.Interface)\n\tif !unpacked.isString() {\n\t\treturn \"\", ErrUnexpectedType{\n\t\t\tExpected: TypeString,\n\t\t\tActual:   v.Type(),\n\t\t}\n\t}\n\treturn unpacked.value.String(), nil\n}\n\n// StringLen returns the length of the string.\nfunc (v *Value) StringLen() (int, error) {\n\tval, err := v.StringValue()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\treturn len(val), nil\n}\n\n// StringIndexRange returns a new string containing the values between the start and end indexes.\n// Comparable to go's string[start:end].\nfunc (v *Value) StringIndexRange(start, end int) (*Value, error) {\n\tstrVal, err := v.StringValue()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tinBytes := []rune(strVal)\n\tl := len(inBytes)\n\n\tif start < 0 {\n\t\tstart = l + start\n\t}\n\tif end < 0 {\n\t\tend = l + end\n\t}\n\n\tresBytes := make([]rune, 0)\n\n\tif start > end {\n\t\tfor i := start; i >= end; i-- {\n\t\t\tresBytes = append(resBytes, inBytes[i])\n\t\t}\n\t} else {\n\t\tfor i := start; i <= end; i++ {\n\t\t\tresBytes = append(resBytes, inBytes[i])\n\t\t}\n\t}\n\n\tres := string(resBytes)\n\n\treturn NewStringValue(res), nil\n}\n\n// NewIntValue creates a new Value with an int value.\nfunc NewIntValue(x int64) *Value {\n\tres := newPtr()\n\tres.Elem().Set(reflect.ValueOf(x))\n\treturn NewValue(res)\n}\n\n// IsInt returns true if the value is an int.\nfunc (v *Value) IsInt() bool {\n\treturn v.UnpackKinds(reflect.Pointer, reflect.Interface).isInt()\n}\n\nfunc (v *Value) isInt() bool {\n\treturn slices.Contains([]reflect.Kind{reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64}, v.value.Kind())\n}\n\n// IntValue returns the int value of the Value.\nfunc (v *Value) IntValue() (int64, error) {\n\tunpacked := v.UnpackKinds(reflect.Pointer, reflect.Interface)\n\tif !unpacked.isInt() {\n\t\treturn 0, ErrUnexpectedType{\n\t\t\tExpected: TypeInt,\n\t\t\tActual:   v.Type(),\n\t\t}\n\t}\n\treturn unpacked.value.Int(), nil\n}\n\n// NewFloatValue creates a new Value with a float value.\nfunc NewFloatValue(x float64) *Value {\n\tres := newPtr()\n\tres.Elem().Set(reflect.ValueOf(x))\n\treturn NewValue(res)\n}\n\n// IsFloat returns true if the value is a float.\nfunc (v *Value) IsFloat() bool {\n\treturn v.UnpackKinds(reflect.Pointer, reflect.Interface).isFloat()\n}\n\nfunc (v *Value) isFloat() bool {\n\treturn slices.Contains([]reflect.Kind{reflect.Float32, reflect.Float64}, v.value.Kind())\n}\n\n// FloatValue returns the float value of the Value.\nfunc (v *Value) FloatValue() (float64, error) {\n\tunpacked := v.UnpackKinds(reflect.Pointer, reflect.Interface)\n\tif !unpacked.IsFloat() {\n\t\treturn 0, ErrUnexpectedType{\n\t\t\tExpected: TypeFloat,\n\t\t\tActual:   v.Type(),\n\t\t}\n\t}\n\treturn unpacked.value.Float(), nil\n}\n\n// NewBoolValue creates a new Value with a bool value.\nfunc NewBoolValue(x bool) *Value {\n\tres := newPtr()\n\tres.Elem().Set(reflect.ValueOf(x))\n\treturn NewValue(res)\n}\n\n// IsBool returns true if the value is a bool.\nfunc (v *Value) IsBool() bool {\n\treturn v.UnpackKinds(reflect.Pointer, reflect.Interface).isBool()\n}\n\nfunc (v *Value) isBool() bool {\n\treturn v.value.Kind() == reflect.Bool\n}\n\n// BoolValue returns the bool value of the Value.\nfunc (v *Value) BoolValue() (bool, error) {\n\tunpacked := v.UnpackKinds(reflect.Pointer, reflect.Interface)\n\tif !unpacked.IsBool() {\n\t\treturn false, ErrUnexpectedType{\n\t\t\tExpected: TypeBool,\n\t\t\tActual:   v.Type(),\n\t\t}\n\t}\n\treturn unpacked.value.Bool(), nil\n}\n"
  },
  {
    "path": "model/value_literal_test.go",
    "content": "package model_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\nfunc TestValue_IsNull(t *testing.T) {\n\tv := model.NewNullValue()\n\tif !v.IsNull() {\n\t\tt.Fatalf(\"expected value to be null\")\n\t}\n}\n"
  },
  {
    "path": "model/value_map.go",
    "content": "package model\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"reflect\"\n\n\t\"github.com/tomwright/dasel/v3/model/orderedmap\"\n)\n\n// NewMapValue creates a new map value.\nfunc NewMapValue() *Value {\n\treturn NewValue(orderedmap.NewMap())\n}\n\n// IsMap returns true if the value is a map.\nfunc (v *Value) IsMap() bool {\n\treturn v.isStandardMap() || v.isDencodingMap()\n}\n\nfunc (v *Value) isStandardMap() bool {\n\treturn v.UnpackKinds(reflect.Interface, reflect.Pointer).Kind() == reflect.Map\n}\n\nfunc (v *Value) isDencodingMap() bool {\n\treturn v.UnpackKinds(reflect.Interface, reflect.Pointer).value.Type() == reflect.TypeFor[orderedmap.Map]()\n}\n\nfunc (v *Value) dencodingMapValue() (*orderedmap.Map, error) {\n\tif v.isDencodingMap() {\n\t\tm, err := v.UnpackUntilType(reflect.TypeFor[*orderedmap.Map]())\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error getting map: %w\", err)\n\t\t}\n\t\treturn m.value.Interface().(*orderedmap.Map), nil\n\t}\n\treturn nil, fmt.Errorf(\"value is not a dencoding map\")\n}\n\n// SetMapKey sets the value at the specified key in the map.\nfunc (v *Value) SetMapKey(key string, value *Value) error {\n\tswitch {\n\tcase v.isDencodingMap():\n\t\tm, err := v.dencodingMapValue()\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error getting map: %w\", err)\n\t\t}\n\t\tm.Set(key, value)\n\t\treturn nil\n\tcase v.isStandardMap():\n\t\tunpacked, err := v.UnpackUntilKind(reflect.Map)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error unpacking value: %w\", err)\n\t\t}\n\t\tunpacked.value.SetMapIndex(reflect.ValueOf(key), value.value)\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"value is not a map\")\n\t}\n}\n\nfunc (v *Value) MapCopy() (*Value, error) {\n\tres := NewMapValue()\n\tkvs, err := v.MapKeyValues()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error getting map key values: %w\", err)\n\t}\n\tfor _, kv := range kvs {\n\t\tif err := res.SetMapKey(kv.Key, kv.Value); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error setting map key: %w\", err)\n\t\t}\n\t}\n\treturn res, nil\n}\n\nfunc (v *Value) MapKeyExists(key string) (bool, error) {\n\t_, err := v.GetMapKey(key)\n\tif err != nil && !errors.As(err, &MapKeyNotFound{}) {\n\t\treturn false, err\n\t}\n\treturn err == nil, nil\n}\n\n// GetMapKey returns the value at the specified key in the map.\nfunc (v *Value) GetMapKey(key string) (*Value, error) {\n\tswitch {\n\tcase v.isDencodingMap():\n\t\tm, err := v.dencodingMapValue()\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error getting map: %w\", err)\n\t\t}\n\t\tval, ok := m.Get(key)\n\t\tif !ok {\n\t\t\treturn nil, MapKeyNotFound{Key: key}\n\t\t}\n\t\tif modelValue, isValue := val.(*Value); isValue {\n\t\t\tmodelValue.setFn = func(newValue *Value) error {\n\t\t\t\tm.Set(key, newValue)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\treturn modelValue, nil\n\t\t}\n\t\tres := NewValue(val)\n\t\tres.setFn = func(newValue *Value) error {\n\t\t\tm.Set(key, newValue)\n\t\t\treturn nil\n\t\t}\n\t\treturn res, nil\n\tcase v.isStandardMap():\n\t\tunpacked, err := v.UnpackUntilKind(reflect.Map)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error unpacking value: %w\", err)\n\t\t}\n\t\ti := unpacked.value.MapIndex(reflect.ValueOf(key))\n\t\tif !i.IsValid() {\n\t\t\treturn nil, MapKeyNotFound{Key: key}\n\t\t}\n\t\tres := NewValue(i)\n\t\tres.setFn = func(newValue *Value) error {\n\t\t\tmapRv, err := v.UnpackUntilKind(reflect.Map)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"error unpacking value: %w\", err)\n\t\t\t}\n\t\t\tmapRv.value.SetMapIndex(reflect.ValueOf(key), newValue.value)\n\t\t\treturn nil\n\t\t}\n\t\treturn res, nil\n\tdefault:\n\t\treturn nil, ErrUnexpectedType{\n\t\t\tExpected: TypeMap,\n\t\t\tActual:   v.Type(),\n\t\t}\n\t}\n}\n\n// DeleteMapKey deletes the key from the map.\nfunc (v *Value) DeleteMapKey(key string) error {\n\tswitch {\n\tcase v.isDencodingMap():\n\t\tm, err := v.dencodingMapValue()\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error getting map: %w\", err)\n\t\t}\n\t\tm.Delete(key)\n\t\treturn nil\n\tcase v.isStandardMap():\n\t\tunpacked, err := v.UnpackUntilKind(reflect.Map)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error unpacking value: %w\", err)\n\t\t}\n\t\tunpacked.value.SetMapIndex(reflect.ValueOf(key), reflect.Value{})\n\t\treturn nil\n\tdefault:\n\t\treturn ErrUnexpectedType{\n\t\t\tExpected: TypeMap,\n\t\t\tActual:   v.Type(),\n\t\t}\n\t}\n}\n\n// MapKeys returns a list of keys in the map.\nfunc (v *Value) MapKeys() ([]string, error) {\n\tswitch {\n\tcase v.isDencodingMap():\n\t\tm, err := v.dencodingMapValue()\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error getting map: %w\", err)\n\t\t}\n\t\treturn m.Keys(), nil\n\tcase v.isStandardMap():\n\t\tunpacked, err := v.UnpackUntilKind(reflect.Map)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error unpacking value: %w\", err)\n\t\t}\n\t\tkeys := unpacked.value.MapKeys()\n\t\tstrKeys := make([]string, len(keys))\n\t\tfor i, k := range keys {\n\t\t\tstrKeys[i] = k.String()\n\t\t}\n\t\treturn strKeys, nil\n\tdefault:\n\t\treturn nil, ErrUnexpectedType{\n\t\t\tExpected: TypeMap,\n\t\t\tActual:   v.Type(),\n\t\t}\n\t}\n}\n\n// RangeMap iterates over each key in the map and calls the provided function with the key and value.\nfunc (v *Value) RangeMap(f func(string, *Value) error) error {\n\tkeys, err := v.MapKeys()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error getting map keys: %w\", err)\n\t}\n\n\tfor _, k := range keys {\n\t\tva, err := v.GetMapKey(k)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error getting map key: %w\", err)\n\t\t}\n\t\tif err := f(k, va); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// MapKeyValues returns a list of key value pairs in the map.\nfunc (v *Value) MapKeyValues() ([]KeyValue, error) {\n\tkeys, err := v.MapKeys()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error getting map keys: %w\", err)\n\t}\n\n\tkvs := make([]KeyValue, len(keys))\n\n\tfor i, k := range keys {\n\t\tva, err := v.GetMapKey(k)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error getting map key: %w\", err)\n\t\t}\n\t\tkvs[i] = KeyValue{\n\t\t\tKey:   k,\n\t\t\tValue: va,\n\t\t}\n\t}\n\n\treturn kvs, nil\n}\n\n// MapLen returns the length of the slice.\nfunc (v *Value) MapLen() (int, error) {\n\tkeys, err := v.MapKeys()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\treturn len(keys), nil\n}\n"
  },
  {
    "path": "model/value_map_test.go",
    "content": "package model_test\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"github.com/tomwright/dasel/v3/model/orderedmap\"\n)\n\nfunc TestMap(t *testing.T) {\n\tstandardMap := func() *model.Value {\n\t\treturn model.NewValue(map[string]interface{}{\n\t\t\t\"foo\": \"foo1\",\n\t\t\t\"bar\": \"bar1\",\n\t\t})\n\t}\n\n\tdencodingMap := func() *model.Value {\n\t\treturn model.NewValue(orderedmap.NewMap().\n\t\t\tSet(\"foo\", \"foo1\").\n\t\t\tSet(\"bar\", \"bar1\"))\n\t}\n\n\tmodelMap := func() *model.Value {\n\t\tres := model.NewMapValue()\n\t\tif err := res.SetMapKey(\"foo\", model.NewValue(\"foo1\")); err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t}\n\t\tif err := res.SetMapKey(\"bar\", model.NewValue(\"bar1\")); err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t}\n\t\treturn res\n\t}\n\n\trunTests := func(v func() *model.Value) func(t *testing.T) {\n\t\treturn func(t *testing.T) {\n\t\t\tt.Run(\"IsMap\", func(t *testing.T) {\n\t\t\t\tv := v()\n\t\t\t\tif !v.IsMap() {\n\t\t\t\t\tt.Errorf(\"expected value to be a map\")\n\t\t\t\t}\n\t\t\t})\n\t\t\tt.Run(\"GetMapKey\", func(t *testing.T) {\n\t\t\t\tv := v()\n\t\t\t\tfoo, err := v.GetMapKey(\"foo\")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tgot, err := foo.StringValue()\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif got != \"foo1\" {\n\t\t\t\t\tt.Errorf(\"expected foo1, got %s\", got)\n\t\t\t\t}\n\t\t\t})\n\t\t\tt.Run(\"SetMapKey\", func(t *testing.T) {\n\t\t\t\tv := v()\n\t\t\t\tif err := v.SetMapKey(\"baz\", model.NewValue(\"baz1\")); err != nil {\n\t\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tbaz, err := v.GetMapKey(\"baz\")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tgot, err := baz.StringValue()\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif got != \"baz1\" {\n\t\t\t\t\tt.Errorf(\"expected baz1, got %s\", got)\n\t\t\t\t}\n\t\t\t})\n\t\t\tt.Run(\"MapKeys\", func(t *testing.T) {\n\t\t\t\tv := v()\n\t\t\t\tkeys, err := v.MapKeys()\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif len(keys) != 2 {\n\t\t\t\t\tt.Errorf(\"expected 2 keys, got %d\", len(keys))\n\t\t\t\t}\n\t\t\t\texp := []string{\"foo\", \"bar\"}\n\t\t\t\tfor _, k := range exp {\n\t\t\t\t\tvar found bool\n\t\t\t\t\tfor _, e := range keys {\n\t\t\t\t\t\tif e == k {\n\t\t\t\t\t\t\tfound = true\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif !found {\n\t\t\t\t\t\tt.Errorf(\"expected key %s not found\", k)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t})\n\t\t\tt.Run(\"RangeMap\", func(t *testing.T) {\n\t\t\t\tv := v()\n\t\t\t\tvar keys []string\n\t\t\t\terr := v.RangeMap(func(k string, v *model.Value) error {\n\t\t\t\t\tkeys = append(keys, k)\n\t\t\t\t\treturn nil\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif len(keys) != 2 {\n\t\t\t\t\tt.Errorf(\"expected 2 keys, got %d\", len(keys))\n\t\t\t\t}\n\t\t\t\texp := []string{\"foo\", \"bar\"}\n\t\t\t\tfor _, k := range exp {\n\t\t\t\t\tvar found bool\n\t\t\t\t\tfor _, e := range keys {\n\t\t\t\t\t\tif e == k {\n\t\t\t\t\t\t\tfound = true\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif !found {\n\t\t\t\t\t\tt.Errorf(\"expected key %s not found\", k)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t})\n\t\t\tt.Run(\"DeleteMapKey\", func(t *testing.T) {\n\t\t\t\tv := v()\n\t\t\t\tif _, err := v.GetMapKey(\"foo\"); err != nil {\n\t\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif err := v.DeleteMapKey(\"foo\"); err != nil {\n\t\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t_, err := v.GetMapKey(\"foo\")\n\t\t\t\tif !errors.As(err, &model.MapKeyNotFound{}) {\n\t\t\t\t\tt.Errorf(\"expected key not found error, got %s\", err)\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t}\n\n\tt.Run(\"standard map\", runTests(standardMap))\n\tt.Run(\"dencoding map\", runTests(dencodingMap))\n\tt.Run(\"model map\", runTests(modelMap))\n}\n"
  },
  {
    "path": "model/value_math.go",
    "content": "package model\n\nimport (\n\tfmt \"fmt\"\n\t\"math\"\n)\n\n// Add adds two values together.\nfunc (v *Value) Add(other *Value) (*Value, error) {\n\tif v.IsInt() && other.IsInt() {\n\t\ta, err := v.IntValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tb, err := other.IntValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn NewValue(a + b), nil\n\t}\n\tif v.IsFloat() && other.IsFloat() {\n\t\ta, err := v.FloatValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tb, err := other.FloatValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn NewValue(a + b), nil\n\t}\n\tif v.IsInt() && other.IsFloat() {\n\t\ta, err := v.IntValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tb, err := other.FloatValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn NewValue(float64(a) + b), nil\n\t}\n\tif v.IsFloat() && other.IsInt() {\n\t\ta, err := v.FloatValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tb, err := other.IntValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn NewValue(a + float64(b)), nil\n\t}\n\tif v.IsString() && other.IsString() {\n\t\ta, err := v.StringValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tb, err := other.StringValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn NewValue(a + b), nil\n\t}\n\treturn nil, fmt.Errorf(\"could not add: %w\", ErrIncompatibleTypes{A: v, B: other})\n}\n\n// Subtract returns the difference between two values.\nfunc (v *Value) Subtract(other *Value) (*Value, error) {\n\tif v.IsInt() && other.IsInt() {\n\t\ta, err := v.IntValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tb, err := other.IntValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn NewValue(a - b), nil\n\t}\n\tif v.IsFloat() && other.IsFloat() {\n\t\ta, err := v.FloatValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tb, err := other.FloatValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn NewValue(a - b), nil\n\t}\n\tif v.IsInt() && other.IsFloat() {\n\t\ta, err := v.IntValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tb, err := other.FloatValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn NewValue(float64(a) - b), nil\n\t}\n\tif v.IsFloat() && other.IsInt() {\n\t\ta, err := v.FloatValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tb, err := other.IntValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn NewValue(a - float64(b)), nil\n\t}\n\treturn nil, fmt.Errorf(\"could not subtract: %w\", ErrIncompatibleTypes{A: v, B: other})\n}\n\n// Multiply returns the product of the two values.\nfunc (v *Value) Multiply(other *Value) (*Value, error) {\n\tif v.IsInt() && other.IsInt() {\n\t\ta, err := v.IntValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tb, err := other.IntValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn NewValue(a * b), nil\n\t}\n\tif v.IsFloat() && other.IsFloat() {\n\t\ta, err := v.FloatValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tb, err := other.FloatValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn NewValue(a * b), nil\n\t}\n\tif v.IsInt() && other.IsFloat() {\n\t\ta, err := v.IntValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tb, err := other.FloatValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn NewValue(float64(a) * b), nil\n\t}\n\tif v.IsFloat() && other.IsInt() {\n\t\ta, err := v.FloatValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tb, err := other.IntValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn NewValue(a * float64(b)), nil\n\t}\n\treturn nil, fmt.Errorf(\"could not multiply: %w\", ErrIncompatibleTypes{A: v, B: other})\n}\n\n// Divide returns the result of dividing the value by another value.\nfunc (v *Value) Divide(other *Value) (*Value, error) {\n\tif v.IsInt() && other.IsInt() {\n\t\ta, err := v.IntValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tb, err := other.IntValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn NewValue(a / b), nil\n\t}\n\tif v.IsFloat() && other.IsFloat() {\n\t\ta, err := v.FloatValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tb, err := other.FloatValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn NewValue(a / b), nil\n\t}\n\tif v.IsInt() && other.IsFloat() {\n\t\ta, err := v.IntValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tb, err := other.FloatValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn NewValue(float64(a) / b), nil\n\t}\n\tif v.IsFloat() && other.IsInt() {\n\t\ta, err := v.FloatValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tb, err := other.IntValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn NewValue(a / float64(b)), nil\n\t}\n\treturn nil, fmt.Errorf(\"could not divide: %w\", ErrIncompatibleTypes{A: v, B: other})\n}\n\n// Modulo returns the remainder of the division of two values.\nfunc (v *Value) Modulo(other *Value) (*Value, error) {\n\tif v.IsInt() && other.IsInt() {\n\t\ta, err := v.IntValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tb, err := other.IntValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn NewValue(a % b), nil\n\t}\n\tif v.IsFloat() && other.IsFloat() {\n\t\ta, err := v.FloatValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tb, err := other.FloatValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn NewValue(math.Mod(a, b)), nil\n\t}\n\tif v.IsInt() && other.IsFloat() {\n\t\ta, err := v.IntValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tb, err := other.FloatValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn NewValue(math.Mod(float64(a), b)), nil\n\t}\n\tif v.IsFloat() && other.IsInt() {\n\t\ta, err := v.FloatValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tb, err := other.IntValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn NewValue(math.Mod(a, float64(b))), nil\n\t}\n\treturn nil, fmt.Errorf(\"could not modulo: %w\", ErrIncompatibleTypes{A: v, B: other})\n}\n"
  },
  {
    "path": "model/value_math_test.go",
    "content": "package model_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\nfunc TestValue_Add(t *testing.T) {\n\trun := func(a, b *model.Value, exp *model.Value) func(*testing.T) {\n\t\treturn func(t *testing.T) {\n\t\t\tgot, err := a.Add(b)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\teq, err := got.EqualTypeValue(exp)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif !eq {\n\t\t\t\tt.Errorf(\"expected %v, got %v\", exp, got)\n\t\t\t}\n\t\t}\n\t}\n\tt.Run(\"int\", func(t *testing.T) {\n\t\tt.Run(\"int\", run(model.NewIntValue(1), model.NewIntValue(2), model.NewIntValue(3)))\n\t\tt.Run(\"float\", run(model.NewIntValue(1), model.NewFloatValue(2), model.NewFloatValue(3)))\n\t})\n\tt.Run(\"float\", func(t *testing.T) {\n\t\tt.Run(\"int\", run(model.NewFloatValue(1), model.NewIntValue(2), model.NewFloatValue(3)))\n\t\tt.Run(\"float\", run(model.NewFloatValue(1), model.NewFloatValue(2), model.NewFloatValue(3)))\n\t})\n\tt.Run(\"string\", func(t *testing.T) {\n\t\tt.Run(\"string\", run(model.NewStringValue(\"hello\"), model.NewStringValue(\" world\"), model.NewStringValue(\"hello world\")))\n\t})\n}\n\nfunc TestValue_Subtract(t *testing.T) {\n\trun := func(a, b *model.Value, exp *model.Value) func(*testing.T) {\n\t\treturn func(t *testing.T) {\n\t\t\tgot, err := a.Subtract(b)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\teq, err := got.EqualTypeValue(exp)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif !eq {\n\t\t\t\tt.Errorf(\"expected %v, got %v\", exp, got)\n\t\t\t}\n\t\t}\n\t}\n\tt.Run(\"int\", func(t *testing.T) {\n\t\tt.Run(\"int\", run(model.NewIntValue(3), model.NewIntValue(2), model.NewIntValue(1)))\n\t\tt.Run(\"float\", run(model.NewIntValue(3), model.NewFloatValue(2), model.NewFloatValue(1)))\n\t})\n\tt.Run(\"float\", func(t *testing.T) {\n\t\tt.Run(\"int\", run(model.NewFloatValue(3), model.NewIntValue(2), model.NewFloatValue(1)))\n\t\tt.Run(\"float\", run(model.NewFloatValue(3), model.NewFloatValue(2), model.NewFloatValue(1)))\n\t})\n}\n\nfunc TestValue_Multiply(t *testing.T) {\n\trun := func(a, b *model.Value, exp *model.Value) func(*testing.T) {\n\t\treturn func(t *testing.T) {\n\t\t\tgot, err := a.Multiply(b)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\teq, err := got.EqualTypeValue(exp)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif !eq {\n\t\t\t\tt.Errorf(\"expected %v, got %v\", exp, got)\n\t\t\t}\n\t\t}\n\t}\n\tt.Run(\"int\", func(t *testing.T) {\n\t\tt.Run(\"int\", run(model.NewIntValue(3), model.NewIntValue(2), model.NewIntValue(6)))\n\t\tt.Run(\"float\", run(model.NewIntValue(3), model.NewFloatValue(2), model.NewFloatValue(6)))\n\t})\n\tt.Run(\"float\", func(t *testing.T) {\n\t\tt.Run(\"int\", run(model.NewFloatValue(3), model.NewIntValue(2), model.NewFloatValue(6)))\n\t\tt.Run(\"float\", run(model.NewFloatValue(3), model.NewFloatValue(2), model.NewFloatValue(6)))\n\t})\n}\n\nfunc TestValue_Divide(t *testing.T) {\n\trun := func(a, b *model.Value, exp *model.Value) func(*testing.T) {\n\t\treturn func(t *testing.T) {\n\t\t\tgot, err := a.Divide(b)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\teq, err := got.EqualTypeValue(exp)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif !eq {\n\t\t\t\tt.Errorf(\"expected %v, got %v\", exp, got)\n\t\t\t}\n\t\t}\n\t}\n\tt.Run(\"int\", func(t *testing.T) {\n\t\tt.Run(\"int\", run(model.NewIntValue(6), model.NewIntValue(2), model.NewIntValue(3)))\n\t\tt.Run(\"float\", run(model.NewIntValue(6), model.NewFloatValue(2), model.NewFloatValue(3)))\n\t})\n\tt.Run(\"float\", func(t *testing.T) {\n\t\tt.Run(\"int\", run(model.NewFloatValue(6), model.NewIntValue(2), model.NewFloatValue(3)))\n\t\tt.Run(\"float\", run(model.NewFloatValue(6), model.NewFloatValue(2), model.NewFloatValue(3)))\n\t})\n}\n\nfunc TestValue_Modulo(t *testing.T) {\n\trun := func(a, b *model.Value, exp *model.Value) func(*testing.T) {\n\t\treturn func(t *testing.T) {\n\t\t\tgot, err := a.Modulo(b)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\teq, err := got.EqualTypeValue(exp)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif !eq {\n\t\t\t\tt.Errorf(\"expected %v, got %v\", exp, got)\n\t\t\t}\n\t\t}\n\t}\n\tt.Run(\"int\", func(t *testing.T) {\n\t\tt.Run(\"int\", run(model.NewIntValue(10), model.NewIntValue(3), model.NewIntValue(1)))\n\t\tt.Run(\"float\", run(model.NewIntValue(10), model.NewFloatValue(3), model.NewFloatValue(1)))\n\t})\n\tt.Run(\"float\", func(t *testing.T) {\n\t\tt.Run(\"int\", run(model.NewFloatValue(10), model.NewIntValue(3), model.NewFloatValue(1)))\n\t\tt.Run(\"float\", run(model.NewFloatValue(10), model.NewFloatValue(3), model.NewFloatValue(1)))\n\t})\n}\n"
  },
  {
    "path": "model/value_metadata.go",
    "content": "package model\n\n// MetadataValue returns a metadata value.\nfunc (v *Value) MetadataValue(key string) (any, bool) {\n\tif v.Metadata == nil {\n\t\treturn nil, false\n\t}\n\tval, ok := v.Metadata[key]\n\treturn val, ok\n}\n\n// SetMetadataValue sets a metadata value.\nfunc (v *Value) SetMetadataValue(key string, val any) {\n\tif v.Metadata == nil {\n\t\tv.Metadata = map[string]any{}\n\t}\n\tv.Metadata[key] = val\n}\n\n// IsSpread returns true if the value is a spread value.\n// Spread values are used to represent the spread operator.\nfunc (v *Value) IsSpread() bool {\n\tif v == nil {\n\t\treturn false\n\t}\n\tval, ok := v.MetadataValue(\"spread\")\n\tif !ok {\n\t\treturn false\n\t}\n\tspread, ok := val.(bool)\n\treturn ok && spread\n}\n\n// MarkAsSpread marks the value as a spread value.\n// Spread values are used to represent the spread operator.\nfunc (v *Value) MarkAsSpread() {\n\tv.SetMetadataValue(\"spread\", true)\n}\n\n// IsBranch returns true if the value is a branched value.\nfunc (v *Value) IsBranch() bool {\n\tif v == nil {\n\t\treturn false\n\t}\n\tval, ok := v.MetadataValue(\"branch\")\n\tif !ok {\n\t\treturn false\n\t}\n\tbranch, ok := val.(bool)\n\treturn ok && branch\n}\n\n// MarkAsBranch marks the value as a branch value.\nfunc (v *Value) MarkAsBranch() {\n\tv.SetMetadataValue(\"branch\", true)\n}\n\n// IsIgnore returns true if value should be ignored.\nfunc (v *Value) IsIgnore() bool {\n\tif v == nil {\n\t\treturn false\n\t}\n\tval, ok := v.MetadataValue(\"ignore\")\n\tif !ok {\n\t\treturn false\n\t}\n\tignore, ok := val.(bool)\n\treturn ok && ignore\n}\n\n// MarkAsIgnore marks the value to be ignored.\nfunc (v *Value) MarkAsIgnore() {\n\tv.SetMetadataValue(\"ignore\", true)\n}\n"
  },
  {
    "path": "model/value_metadata_test.go",
    "content": "package model_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\nfunc TestValue_IsBranch(t *testing.T) {\n\tval := model.NewNullValue()\n\tif exp, got := false, val.IsBranch(); exp != got {\n\t\tt.Errorf(\"expected %v, got %v\", exp, got)\n\t}\n\tval.MarkAsBranch()\n\tif exp, got := true, val.IsBranch(); exp != got {\n\t\tt.Errorf(\"expected %v, got %v\", exp, got)\n\t}\n}\n\nfunc TestValue_IsSpread(t *testing.T) {\n\tval := model.NewNullValue()\n\tif exp, got := false, val.IsSpread(); exp != got {\n\t\tt.Errorf(\"expected %v, got %v\", exp, got)\n\t}\n\tval.MarkAsSpread()\n\tif exp, got := true, val.IsSpread(); exp != got {\n\t\tt.Errorf(\"expected %v, got %v\", exp, got)\n\t}\n}\n"
  },
  {
    "path": "model/value_set.go",
    "content": "package model\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n)\n\n// Set sets the value of the value.\nfunc (v *Value) Set(newValue *Value) error {\n\tif v.isDaselValue() {\n\t\tdv, err := v.daselValue()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn dv.Set(newValue)\n\t}\n\n\tif v.setFn != nil {\n\t\treturn v.setFn(newValue)\n\t}\n\n\ta, err := v.UnpackUntilAddressable()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tb := newValue.UnpackKinds(reflect.Pointer)\n\tif a.Kind() == b.Kind() {\n\t\ta.value.Set(b.value)\n\t\treturn nil\n\t}\n\n\tb = newValue.UnpackKinds(reflect.Pointer, reflect.Interface)\n\tif a.Kind() == b.Kind() {\n\t\ta.value.Set(b.value)\n\t\treturn nil\n\t}\n\n\t// These are commented out because I don't think they are needed.\n\n\t//if a.Kind() == newValue.Kind() {\n\t//\ta.Value.Set(newValue.Value)\n\t//\treturn nil\n\t//}\n\n\t//b = newValue.UnpackKinds(reflect.Interface)\n\t//if a.Kind() == b.Kind() {\n\t//\ta.Value.Set(b.Value)\n\t//\treturn nil\n\t//}\n\n\t//b = newValue.UnpackKinds(reflect.Pointer, reflect.Interface)\n\t//if a.Kind() == b.Kind() {\n\t//\ta.Value.Set(b.Value)\n\t//\treturn nil\n\t//}\n\n\t//b, err = newValue.UnpackUntilAddressable()\n\t//if err != nil {\n\t//\treturn err\n\t//}\n\t//if a.Kind() == b.Kind() {\n\t//\ta.Value.Set(b.Value)\n\t//\treturn nil\n\t//}\n\n\t// This is a hard limitation at the moment.\n\t// If the types are not the same, we cannot set the value.\n\treturn fmt.Errorf(\"could not set %s value on %s value\", newValue.Type(), v.Type())\n}\n"
  },
  {
    "path": "model/value_set_test.go",
    "content": "package model_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\ntype setTestCase struct {\n\tvalueFn    func() *model.Value\n\tvalue      *model.Value\n\tnewValueFn func() *model.Value\n\tnewValue   *model.Value\n}\n\nfunc (tc setTestCase) run(t *testing.T) {\n\tval := tc.value\n\tif tc.valueFn != nil {\n\t\tval = tc.valueFn()\n\t}\n\tnewVal := tc.newValue\n\tif tc.newValueFn != nil {\n\t\tnewVal = tc.newValueFn()\n\t}\n\tif err := val.Set(newVal); err != nil {\n\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\treturn\n\t}\n\n\teq, err := val.EqualTypeValue(newVal)\n\tif err != nil {\n\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\treturn\n\t}\n\tif !eq {\n\t\tt.Errorf(\"expected values to be equal\")\n\t}\n}\n\nfunc TestValue_Set(t *testing.T) {\n\ttestCases := []struct {\n\t\tname        string\n\t\tstringValue func() *model.Value\n\t\tintValue    func() *model.Value\n\t\tfloatValue  func() *model.Value\n\t\tboolValue   func() *model.Value\n\t\tmapValue    func() *model.Value\n\t\tsliceValue  func() *model.Value\n\t\tnullValue   func() *model.Value\n\t}{\n\t\t{\n\t\t\tname: \"model constructor\",\n\t\t\tstringValue: func() *model.Value {\n\t\t\t\treturn model.NewStringValue(\"hello\")\n\t\t\t},\n\t\t\tintValue: func() *model.Value {\n\t\t\t\treturn model.NewIntValue(1)\n\t\t\t},\n\t\t\tfloatValue: func() *model.Value {\n\t\t\t\treturn model.NewFloatValue(1)\n\t\t\t},\n\t\t\tboolValue: func() *model.Value {\n\t\t\t\treturn model.NewBoolValue(true)\n\t\t\t},\n\t\t\tmapValue: func() *model.Value {\n\t\t\t\tres := model.NewMapValue()\n\t\t\t\tif err := res.SetMapKey(\"greeting\", model.NewStringValue(\"hello\")); err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\t\t\t\treturn res\n\t\t\t},\n\t\t\tsliceValue: func() *model.Value {\n\t\t\t\tres := model.NewSliceValue()\n\t\t\t\tif err := res.Append(model.NewStringValue(\"hello\")); err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\t\t\t\treturn res\n\t\t\t},\n\t\t\tnullValue: func() *model.Value {\n\t\t\t\treturn model.NewNullValue()\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"go types non ptr\",\n\t\t\tstringValue: func() *model.Value {\n\t\t\t\tv := \"hello\"\n\t\t\t\treturn model.NewValue(v)\n\t\t\t},\n\t\t\tintValue: func() *model.Value {\n\t\t\t\tv := int64(1)\n\t\t\t\treturn model.NewValue(v)\n\t\t\t},\n\t\t\tfloatValue: func() *model.Value {\n\t\t\t\tv := 1.0\n\t\t\t\treturn model.NewValue(v)\n\t\t\t},\n\t\t\tboolValue: func() *model.Value {\n\t\t\t\tv := true\n\t\t\t\treturn model.NewValue(v)\n\t\t\t},\n\t\t\tmapValue: func() *model.Value {\n\t\t\t\tv := map[string]interface{}{\n\t\t\t\t\t\"greeting\": \"hello\",\n\t\t\t\t}\n\t\t\t\treturn model.NewValue(v)\n\t\t\t},\n\t\t\tsliceValue: func() *model.Value {\n\t\t\t\tv := []interface{}{\n\t\t\t\t\t\"hello\",\n\t\t\t\t}\n\t\t\t\treturn model.NewValue(v)\n\t\t\t},\n\t\t\tnullValue: func() *model.Value {\n\t\t\t\treturn model.NewValue(nil)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"go types ptr\",\n\t\t\tstringValue: func() *model.Value {\n\t\t\t\tv := \"hello\"\n\t\t\t\treturn model.NewValue(&v)\n\t\t\t},\n\t\t\tintValue: func() *model.Value {\n\t\t\t\tv := int64(1)\n\t\t\t\treturn model.NewValue(&v)\n\t\t\t},\n\t\t\tfloatValue: func() *model.Value {\n\t\t\t\tv := 1.0\n\t\t\t\treturn model.NewValue(&v)\n\t\t\t},\n\t\t\tboolValue: func() *model.Value {\n\t\t\t\tv := true\n\t\t\t\treturn model.NewValue(&v)\n\t\t\t},\n\t\t\tmapValue: func() *model.Value {\n\t\t\t\tv := map[string]interface{}{\n\t\t\t\t\t\"greeting\": \"hello\",\n\t\t\t\t}\n\t\t\t\treturn model.NewValue(&v)\n\t\t\t},\n\t\t\tsliceValue: func() *model.Value {\n\t\t\t\tv := []interface{}{\n\t\t\t\t\t\"hello\",\n\t\t\t\t}\n\t\t\t\treturn model.NewValue(&v)\n\t\t\t},\n\t\t\tnullValue: func() *model.Value {\n\t\t\t\tvar x any\n\t\t\t\treturn model.NewValue(&x)\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, testCase := range testCases {\n\t\ttc := testCase\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tt.Run(\"string\", setTestCase{\n\t\t\t\tvalueFn:  tc.stringValue,\n\t\t\t\tnewValue: model.NewStringValue(\"world\"),\n\t\t\t}.run)\n\t\t\tt.Run(\"int\", setTestCase{\n\t\t\t\tvalueFn:  tc.intValue,\n\t\t\t\tnewValue: model.NewIntValue(2),\n\t\t\t}.run)\n\t\t\tt.Run(\"float\", setTestCase{\n\t\t\t\tvalueFn:  tc.floatValue,\n\t\t\t\tnewValue: model.NewFloatValue(2),\n\t\t\t}.run)\n\t\t\tt.Run(\"bool\", setTestCase{\n\t\t\t\tvalueFn:  tc.boolValue,\n\t\t\t\tnewValue: model.NewBoolValue(false),\n\t\t\t}.run)\n\t\t\tt.Run(\"map\", setTestCase{\n\t\t\t\tvalueFn: tc.mapValue,\n\t\t\t\tnewValueFn: func() *model.Value {\n\t\t\t\t\tres := model.NewMapValue()\n\t\t\t\t\tif err := res.SetMapKey(\"greeting\", model.NewStringValue(\"world\")); err != nil {\n\t\t\t\t\t\tt.Fatal(err)\n\t\t\t\t\t}\n\t\t\t\t\treturn res\n\t\t\t\t},\n\t\t\t}.run)\n\t\t\tt.Run(\"slice\", setTestCase{\n\t\t\t\tvalueFn: tc.sliceValue,\n\t\t\t\tnewValueFn: func() *model.Value {\n\t\t\t\t\tres := model.NewSliceValue()\n\t\t\t\t\tif err := res.Append(model.NewStringValue(\"world\")); err != nil {\n\t\t\t\t\t\tt.Fatal(err)\n\t\t\t\t\t}\n\t\t\t\t\treturn res\n\t\t\t\t},\n\t\t\t}.run)\n\t\t\tt.Run(\"string over int\", setTestCase{\n\t\t\t\tvalueFn:  tc.intValue,\n\t\t\t\tnewValue: model.NewStringValue(\"world\"),\n\t\t\t}.run)\n\t\t\tt.Run(\"int over float\", setTestCase{\n\t\t\t\tvalueFn:  tc.floatValue,\n\t\t\t\tnewValue: model.NewIntValue(2),\n\t\t\t}.run)\n\t\t\tt.Run(\"float over bool\", setTestCase{\n\t\t\t\tvalueFn:  tc.boolValue,\n\t\t\t\tnewValue: model.NewFloatValue(2),\n\t\t\t}.run)\n\t\t\tt.Run(\"bool over map\", setTestCase{\n\t\t\t\tvalueFn:  tc.mapValue,\n\t\t\t\tnewValue: model.NewBoolValue(true),\n\t\t\t}.run)\n\t\t\tt.Run(\"map over slice\", setTestCase{\n\t\t\t\tvalueFn: tc.sliceValue,\n\t\t\t\tnewValueFn: func() *model.Value {\n\t\t\t\t\tres := model.NewMapValue()\n\t\t\t\t\tif err := res.SetMapKey(\"greeting\", model.NewStringValue(\"world\")); err != nil {\n\t\t\t\t\t\tt.Fatal(err)\n\t\t\t\t\t}\n\t\t\t\t\treturn res\n\t\t\t\t},\n\t\t\t}.run)\n\t\t\tt.Run(\"string over slice\", setTestCase{\n\t\t\t\tvalueFn:  tc.sliceValue,\n\t\t\t\tnewValue: model.NewStringValue(\"world\"),\n\t\t\t}.run)\n\t\t\tt.Run(\"slice over map\", setTestCase{\n\t\t\t\tvalueFn: tc.mapValue,\n\t\t\t\tnewValueFn: func() *model.Value {\n\t\t\t\t\tres := model.NewSliceValue()\n\t\t\t\t\tif err := res.Append(model.NewStringValue(\"world\")); err != nil {\n\t\t\t\t\t\tt.Fatal(err)\n\t\t\t\t\t}\n\t\t\t\t\treturn res\n\t\t\t\t},\n\t\t\t}.run)\n\t\t\tt.Run(\"string over null\", setTestCase{\n\t\t\t\tvalueFn:  tc.nullValue,\n\t\t\t\tnewValue: model.NewStringValue(\"world\"),\n\t\t\t}.run)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "model/value_slice.go",
    "content": "package model\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n)\n\n// NewSliceValue returns a new slice value.\nfunc NewSliceValue() *Value {\n\tres := newPtr()\n\ts := reflect.MakeSlice(reflect.SliceOf(reflect.TypeFor[any]()), 0, 0)\n\tptr := reflect.New(reflect.SliceOf(reflect.TypeFor[any]()))\n\tptr.Elem().Set(s)\n\tres.Elem().Set(ptr)\n\treturn NewValue(res)\n}\n\n// IsSlice returns true if the value is a slice.\nfunc (v *Value) IsSlice() bool {\n\treturn v.UnpackKinds(reflect.Interface, reflect.Pointer).isSlice()\n}\n\nfunc (v *Value) isSlice() bool {\n\treturn v.value.Kind() == reflect.Slice\n}\n\n// Append appends a value to the slice.\nfunc (v *Value) Append(val *Value) error {\n\t// Branches behave differently when appending to a slice.\n\t// We expect each item in a branch to be its own value.\n\tif val.IsBranch() {\n\t\treturn val.RangeSlice(func(_ int, item *Value) error {\n\t\t\treturn v.Append(item)\n\t\t})\n\t}\n\n\tunpacked := v.UnpackKinds(reflect.Interface, reflect.Pointer)\n\tif !unpacked.isSlice() {\n\t\treturn ErrUnexpectedType{\n\t\t\tExpected: TypeSlice,\n\t\t\tActual:   v.Type(),\n\t\t}\n\t}\n\n\t// Wrap the value to preserve metadata and set functions.\n\tvalToAppend := reflect.ValueOf(val)\n\tnewVal := reflect.Append(unpacked.value, valToAppend)\n\tunpacked.value.Set(newVal)\n\treturn nil\n}\n\n// SliceLen returns the length of the slice.\nfunc (v *Value) SliceLen() (int, error) {\n\tunpacked := v.UnpackKinds(reflect.Interface, reflect.Pointer)\n\tif !unpacked.isSlice() {\n\t\treturn 0, ErrUnexpectedType{\n\t\t\tExpected: TypeSlice,\n\t\t\tActual:   v.Type(),\n\t\t}\n\t}\n\treturn unpacked.value.Len(), nil\n}\n\n// GetSliceIndex returns the value at the specified index in the slice.\nfunc (v *Value) GetSliceIndex(i int) (*Value, error) {\n\tunpacked := v.UnpackKinds(reflect.Interface, reflect.Pointer)\n\tif !unpacked.isSlice() {\n\t\treturn nil, ErrUnexpectedType{\n\t\t\tExpected: TypeSlice,\n\t\t\tActual:   v.Type(),\n\t\t}\n\t}\n\tif i < 0 || i >= unpacked.value.Len() {\n\t\treturn nil, SliceIndexOutOfRange{Index: i}\n\t}\n\n\titem := unpacked.value.Index(i)\n\tif item.Kind() == reflect.Pointer && item.Type() == reflect.TypeFor[*Value]() {\n\t\treturn item.Interface().(*Value), nil\n\t}\n\tif item.Kind() == reflect.Interface && !item.IsNil() {\n\t\tinterfaceVal := item.Interface()\n\t\tif val, ok := interfaceVal.(*Value); ok {\n\t\t\treturn val, nil\n\t\t}\n\t}\n\n\tres := NewValue(item)\n\treturn res, nil\n}\n\n// SetSliceIndex sets the value at the specified index in the slice.\nfunc (v *Value) SetSliceIndex(i int, val *Value) error {\n\tunpacked := v.UnpackKinds(reflect.Interface, reflect.Pointer)\n\tif !unpacked.isSlice() {\n\t\treturn ErrUnexpectedType{\n\t\t\tExpected: TypeSlice,\n\t\t\tActual:   v.Type(),\n\t\t}\n\t}\n\tif i < 0 || i >= unpacked.value.Len() {\n\t\treturn SliceIndexOutOfRange{Index: i}\n\t}\n\tunpacked.value.Index(i).Set(reflect.ValueOf(val))\n\treturn nil\n}\n\n// RangeSlice iterates over each item in the slice and calls the provided function.\nfunc (v *Value) RangeSlice(f func(int, *Value) error) error {\n\tlength, err := v.SliceLen()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error getting slice length: %w\", err)\n\t}\n\n\tfor i := 0; i < length; i++ {\n\t\tva, err := v.GetSliceIndex(i)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error getting slice index %d: %w\", i, err)\n\t\t}\n\t\tif err := f(i, va); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// SliceIndexRange returns a new slice containing the values between the start and end indexes.\n// Comparable to go's slice[start:end].\nfunc (v *Value) SliceIndexRange(start, end int) (*Value, error) {\n\tl, err := v.SliceLen()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error getting slice length: %w\", err)\n\t}\n\n\tif start < 0 {\n\t\tstart = l + start\n\t}\n\tif end < 0 {\n\t\tend = l + end\n\t}\n\n\tres := NewSliceValue()\n\n\tif start > end {\n\t\tfor i := start; i >= end; i-- {\n\t\t\titem, err := v.GetSliceIndex(i)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"error getting slice index: %w\", err)\n\t\t\t}\n\t\t\tif err := res.Append(item); err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"error appending value to slice: %w\", err)\n\t\t\t}\n\t\t}\n\t} else {\n\t\tfor i := start; i <= end; i++ {\n\t\t\titem, err := v.GetSliceIndex(i)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"error getting slice index: %w\", err)\n\t\t\t}\n\t\t\tif err := res.Append(item); err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"error appending value to slice: %w\", err)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn res, nil\n}\n"
  },
  {
    "path": "model/value_slice_test.go",
    "content": "package model_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\nfunc TestSlice(t *testing.T) {\n\tstandardSlice := func() *model.Value {\n\t\treturn model.NewValue([]any{\"foo\", \"bar\"})\n\t}\n\n\tmodelSlice := func() *model.Value {\n\t\tres := model.NewSliceValue()\n\t\tif err := res.Append(model.NewValue(\"foo\")); err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t}\n\t\tif err := res.Append(model.NewValue(\"bar\")); err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t}\n\t\treturn res\n\t}\n\n\trunTests := func(v func() *model.Value) func(t *testing.T) {\n\t\treturn func(t *testing.T) {\n\t\t\tt.Run(\"IsSlice\", func(t *testing.T) {\n\t\t\t\tv := v()\n\t\t\t\tif !v.IsSlice() {\n\t\t\t\t\tt.Errorf(\"expected value to be a slice\")\n\t\t\t\t}\n\t\t\t})\n\t\t\tt.Run(\"GetSliceIndex\", func(t *testing.T) {\n\t\t\t\tv := v()\n\t\t\t\tfoo, err := v.GetSliceIndex(0)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tgot, err := foo.StringValue()\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif got != \"foo\" {\n\t\t\t\t\tt.Errorf(\"expected foo, got %s\", got)\n\t\t\t\t}\n\t\t\t})\n\t\t\tt.Run(\"SetSliceIndex\", func(t *testing.T) {\n\t\t\t\tv := v()\n\t\t\t\tif err := v.SetSliceIndex(0, model.NewValue(\"baz\")); err != nil {\n\t\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tbaz, err := v.GetSliceIndex(0)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tgot, err := baz.StringValue()\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif got != \"baz\" {\n\t\t\t\t\tt.Errorf(\"expected baz, got %s\", got)\n\t\t\t\t}\n\t\t\t})\n\t\t\tt.Run(\"Len\", func(t *testing.T) {\n\t\t\t\tv := v()\n\t\t\t\tgot, err := v.SliceLen()\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif got != 2 {\n\t\t\t\t\tt.Errorf(\"expected len of 2, got %d\", got)\n\t\t\t\t}\n\t\t\t})\n\t\t\tt.Run(\"RangeSlice\", func(t *testing.T) {\n\t\t\t\tv := v()\n\t\t\t\tvar keys []int\n\t\t\t\tvar vals []string\n\t\t\t\terr := v.RangeSlice(func(k int, v *model.Value) error {\n\t\t\t\t\tkeys = append(keys, k)\n\t\t\t\t\ts, err := v.StringValue()\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t\tvals = append(vals, s)\n\t\t\t\t\treturn nil\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif len(keys) != 2 {\n\t\t\t\t\tt.Errorf(\"expected 2 keys, got %d\", len(keys))\n\t\t\t\t}\n\t\t\t\tif len(vals) != 2 {\n\t\t\t\t\tt.Errorf(\"expected 2 vals, got %d\", len(keys))\n\t\t\t\t}\n\t\t\t\texp := []string{\"foo\", \"bar\"}\n\n\t\t\t\tfor k, e := range exp {\n\t\t\t\t\tif keys[k] != k {\n\t\t\t\t\t\tt.Errorf(\"expected key %d, got %d\", k, keys[k])\n\t\t\t\t\t}\n\t\t\t\t\tif vals[k] != e {\n\t\t\t\t\t\tt.Errorf(\"expected val %s, got %s\", e, vals[k])\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t})\n\t\t\t//t.Run(\"DeleteMapKey\", func(t *testing.T) {\n\t\t\t//\tv := v()\n\t\t\t//\tif _, err := v.GetSliceIndex(1); err != nil {\n\t\t\t//\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t//\t\treturn\n\t\t\t//\t}\n\t\t\t//\tif err := v.DeleteSliceIndex(1); err != nil {\n\t\t\t//\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t//\t\treturn\n\t\t\t//\t}\n\t\t\t//\t_, err := v.GetSliceIndex(1)\n\t\t\t//\tnotFoundErr := &model.SliceIndexOutOfRange{}\n\t\t\t//\tif !errors.As(err, &notFoundErr) {\n\t\t\t//\t\tt.Errorf(\"expected index not found error, got %s\", err)\n\t\t\t//\t}\n\t\t\t//})\n\t\t\tt.Run(\"SliceIndexRange\", func(t *testing.T) {\n\t\t\t\tt.Run(\"last element\", func(t *testing.T) {\n\t\t\t\t\tv := v()\n\t\t\t\t\ts, err := v.SliceIndexRange(-1, -1)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tlength, err := s.SliceLen()\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tif length != 1 {\n\t\t\t\t\t\tt.Errorf(\"expected length of 1, got %d\", length)\n\t\t\t\t\t}\n\n\t\t\t\t\tval, err := s.GetSliceIndex(0)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tgot, err := val.StringValue()\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tif got != \"bar\" {\n\t\t\t\t\t\tt.Errorf(\"expected bar, got %s\", got)\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\tt.Run(\"first element\", func(t *testing.T) {\n\t\t\t\t\tv := v()\n\t\t\t\t\ts, err := v.SliceIndexRange(0, 0)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tlength, err := s.SliceLen()\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tif length != 1 {\n\t\t\t\t\t\tt.Errorf(\"expected length of 1, got %d\", length)\n\t\t\t\t\t}\n\n\t\t\t\t\tval, err := s.GetSliceIndex(0)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tgot, err := val.StringValue()\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tif got != \"foo\" {\n\t\t\t\t\t\tt.Errorf(\"expected foo, got %s\", got)\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t})\n\t\t}\n\t}\n\n\tt.Run(\"standard slice\", runTests(standardSlice))\n\tt.Run(\"model slice\", runTests(modelSlice))\n}\n"
  },
  {
    "path": "model/value_test.go",
    "content": "package model_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\nfunc TestType_String(t *testing.T) {\n\trun := func(ty model.Type, exp string) func(*testing.T) {\n\t\treturn func(t *testing.T) {\n\t\t\tgot := ty.String()\n\t\t\tif got != exp {\n\t\t\t\tt.Errorf(\"expected %s, got %s\", exp, got)\n\t\t\t}\n\t\t}\n\t}\n\tt.Run(\"string\", run(model.TypeString, \"string\"))\n\tt.Run(\"int\", run(model.TypeInt, \"int\"))\n\tt.Run(\"float\", run(model.TypeFloat, \"float\"))\n\tt.Run(\"bool\", run(model.TypeBool, \"bool\"))\n\tt.Run(\"map\", run(model.TypeMap, \"map\"))\n\tt.Run(\"slice\", run(model.TypeSlice, \"array\"))\n\tt.Run(\"unknown\", run(model.TypeUnknown, \"unknown\"))\n\tt.Run(\"null\", run(model.TypeNull, \"null\"))\n}\n\nfunc TestValue_Len(t *testing.T) {\n\trun := func(v *model.Value, exp int) func(*testing.T) {\n\t\treturn func(t *testing.T) {\n\t\t\tgot, err := v.Len()\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif got != exp {\n\t\t\t\tt.Errorf(\"expected %d, got %d\", exp, got)\n\t\t\t}\n\t\t}\n\t}\n\tt.Run(\"string\", func(t *testing.T) {\n\t\tt.Run(\"empty\", run(model.NewStringValue(\"\"), 0))\n\t\tt.Run(\"non-empty\", run(model.NewStringValue(\"hello\"), 5))\n\t})\n\tt.Run(\"slice\", func(t *testing.T) {\n\t\tt.Run(\"empty\", run(model.NewSliceValue(), 0))\n\t\tt.Run(\"non-empty\", run(model.NewValue([]any{1, 2, 3}), 3))\n\t})\n\tt.Run(\"map\", func(t *testing.T) {\n\t\tt.Run(\"empty\", run(model.NewMapValue(), 0))\n\t\tt.Run(\"non-empty\", run(model.NewValue(map[string]any{\"one\": 1, \"two\": 2, \"three\": 3}), 3))\n\t})\n}\n\nfunc TestValue_IsScalar(t *testing.T) {\n\trun := func(v *model.Value, exp bool) func(*testing.T) {\n\t\treturn func(t *testing.T) {\n\t\t\tgot := v.IsScalar()\n\t\t\tif got != exp {\n\t\t\t\tt.Errorf(\"expected %v, got %v\", exp, got)\n\t\t\t}\n\t\t}\n\t}\n\tt.Run(\"string\", run(model.NewStringValue(\"foo\"), true))\n\tt.Run(\"bool\", run(model.NewBoolValue(true), true))\n\tt.Run(\"int\", run(model.NewIntValue(1), true))\n\tt.Run(\"float\", run(model.NewFloatValue(1.0), true))\n\tt.Run(\"null\", run(model.NewNullValue(), true))\n\tt.Run(\"map\", run(model.NewMapValue(), false))\n\tt.Run(\"slice\", run(model.NewSliceValue(), false))\n\n\tt.Run(\"nested\", func(t *testing.T) {\n\t\tt.Run(\"nested string\", run(model.NewNestedValue(model.NewStringValue(\"foo\")), true))\n\t\tt.Run(\"nested bool\", run(model.NewNestedValue(model.NewBoolValue(true)), true))\n\t\tt.Run(\"nested int\", run(model.NewNestedValue(model.NewIntValue(1)), true))\n\t\tt.Run(\"nested float\", run(model.NewNestedValue(model.NewFloatValue(1.0)), true))\n\t\tt.Run(\"nested null\", run(model.NewNestedValue(model.NewNullValue()), true))\n\t\tt.Run(\"nested map\", run(model.NewNestedValue(model.NewMapValue()), false))\n\t\tt.Run(\"nested slice\", run(model.NewNestedValue(model.NewSliceValue()), false))\n\n\t\tt.Run(\"double nested string\", run(model.NewNestedValue(model.NewNestedValue(model.NewStringValue(\"foo\"))), true))\n\t})\n}\n"
  },
  {
    "path": "parsing/csv/csv.go",
    "content": "package csv\n\nimport (\n\t\"fmt\"\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"github.com/tomwright/dasel/v3/parsing\"\n)\n\n// CSV represents the CSV file format.\nconst CSV parsing.Format = \"csv\"\n\nvar _ parsing.Reader = (*csvReader)(nil)\nvar _ parsing.Writer = (*csvWriter)(nil)\n\nfunc init() {\n\tparsing.RegisterReader(CSV, newCSVReader)\n\tparsing.RegisterWriter(CSV, newCSVWriter)\n}\n\nfunc newCSVWriter(options parsing.WriterOptions) (parsing.Writer, error) {\n\tw := &csvWriter{\n\t\tseparator: ',',\n\t}\n\tif v, ok := options.Ext[\"csv-delimiter\"]; ok && v != \"\" {\n\t\tw.separator = rune(v[0])\n\t}\n\treturn w, nil\n}\n\nfunc valueFromString(s string) (*model.Value, error) {\n\treturn model.NewStringValue(s), nil\n}\n\nfunc valueToString(v *model.Value) (string, error) {\n\tif v.IsNull() {\n\t\treturn \"\", nil\n\t}\n\n\tswitch v.Type() {\n\tcase model.TypeString:\n\t\tstringValue, err := v.StringValue()\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\treturn stringValue, nil\n\tcase model.TypeInt:\n\t\ti, err := v.IntValue()\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\treturn fmt.Sprintf(\"%d\", i), nil\n\tcase model.TypeFloat:\n\t\ti, err := v.FloatValue()\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\treturn fmt.Sprintf(\"%g\", i), nil\n\tcase model.TypeBool:\n\t\ti, err := v.BoolValue()\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\treturn fmt.Sprintf(\"%t\", i), nil\n\tdefault:\n\t\treturn \"\", fmt.Errorf(\"csv writer cannot format type %s to string\", v.Type())\n\t}\n}\n"
  },
  {
    "path": "parsing/csv/csv_test.go",
    "content": "package csv\n\nimport (\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"testing\"\n)\n\nfunc TestValueToString(t *testing.T) {\n\ttests := []struct {\n\t\tdesc string\n\t\tin   func() (*model.Value, error)\n\t\texp  string\n\t}{\n\t\t{\n\t\t\tdesc: \"basic string\",\n\t\t\tin: func() (*model.Value, error) {\n\t\t\t\treturn model.NewStringValue(\"hello\"), nil\n\t\t\t},\n\t\t\texp: \"hello\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"string\",\n\t\t\tin: func() (*model.Value, error) {\n\t\t\t\treturn model.NewStringValue(\"hello, there!!\"), nil\n\t\t\t},\n\t\t\texp: \"hello, there!!\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"int\",\n\t\t\tin: func() (*model.Value, error) {\n\t\t\t\treturn model.NewIntValue(123), nil\n\t\t\t},\n\t\t\texp: \"123\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"float\",\n\t\t\tin: func() (*model.Value, error) {\n\t\t\t\treturn model.NewFloatValue(1.234), nil\n\t\t\t},\n\t\t\texp: \"1.234\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"null\",\n\t\t\tin: func() (*model.Value, error) {\n\t\t\t\treturn model.NewNullValue(), nil\n\t\t\t},\n\t\t\texp: \"\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"bool true\",\n\t\t\tin: func() (*model.Value, error) {\n\t\t\t\treturn model.NewBoolValue(true), nil\n\t\t\t},\n\t\t\texp: \"true\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"bool false\",\n\t\t\tin: func() (*model.Value, error) {\n\t\t\t\treturn model.NewBoolValue(false), nil\n\t\t\t},\n\t\t\texp: \"false\",\n\t\t},\n\t}\n\n\tfor _, testCase := range tests {\n\t\ttc := testCase\n\t\tt.Run(tc.desc, func(t *testing.T) {\n\t\t\tin, err := tc.in()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tgot, err := valueToString(in)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif got != tc.exp {\n\t\t\t\tt.Errorf(\"expected '%s' but got '%s'\", tc.exp, got)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "parsing/csv/reader.go",
    "content": "package csv\n\nimport (\n\t\"bytes\"\n\t\"encoding/csv\"\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"github.com/tomwright/dasel/v3/parsing\"\n\t\"io\"\n)\n\nfunc newCSVReader(options parsing.ReaderOptions) (parsing.Reader, error) {\n\tr := &csvReader{\n\t\tseparator: ',',\n\t}\n\tif v, ok := options.Ext[\"csv-delimiter\"]; ok && v != \"\" {\n\t\tr.separator = rune(v[0])\n\t}\n\treturn r, nil\n}\n\ntype csvReader struct {\n\tseparator rune\n}\n\n// Read reads a value from a byte slice.\nfunc (j *csvReader) Read(data []byte) (*model.Value, error) {\n\tr := csv.NewReader(bytes.NewReader(data))\n\tr.Comma = j.separator\n\n\tres := model.NewSliceValue()\n\n\tvar headers []string\n\n\tfor rowI := 0; ; rowI++ {\n\t\trecord, err := r.Read()\n\t\tif err != nil {\n\t\t\tif errors.Is(err, io.EOF) {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif headers == nil {\n\t\t\theaders = append(headers, record...)\n\t\t\tcontinue\n\t\t}\n\n\t\trow := model.NewMapValue()\n\t\tfor colI, field := range record {\n\t\t\tif colI >= len(headers) {\n\t\t\t\treturn nil, fmt.Errorf(\"row %d has more columns than headers\", rowI)\n\t\t\t}\n\t\t\theaderKey := headers[colI]\n\n\t\t\tcolV, err := valueFromString(field)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"failed reading column %q for row %d: %w\", field, colI, err)\n\t\t\t}\n\n\t\t\tif err := row.SetMapKey(headerKey, colV); err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"failed to set map key %q: %w\", headerKey, err)\n\t\t\t}\n\t\t}\n\n\t\tif err := res.Append(row); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to append row %d: %w\", rowI, err)\n\t\t}\n\t}\n\n\treturn res, nil\n}\n"
  },
  {
    "path": "parsing/csv/reader_test.go",
    "content": "package csv_test\n\nimport (\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"github.com/tomwright/dasel/v3/parsing\"\n\t\"github.com/tomwright/dasel/v3/parsing/csv\"\n\t\"testing\"\n)\n\nfunc TestCsvReader_Read(t *testing.T) {\n\tinputBytes := []byte(`name,age,city\nAlice,30,New York\nBob,25,Los Angeles\nCharlie,35,Chicago`)\n\n\tr, err := csv.CSV.NewReader(parsing.DefaultReaderOptions())\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tgot, err := r.Read(inputBytes)\n\tif err != nil {\n\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\treturn\n\t}\n\n\texp := model.NewSliceValue()\n\trow1 := model.NewMapValue()\n\tif err := row1.SetMapKey(\"name\", model.NewStringValue(\"Alice\")); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := row1.SetMapKey(\"age\", model.NewStringValue(\"30\")); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := row1.SetMapKey(\"city\", model.NewStringValue(\"New York\")); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := exp.Append(row1); err != nil {\n\t\tt.Fatal(err)\n\t}\n\trow2 := model.NewMapValue()\n\tif err := row2.SetMapKey(\"name\", model.NewStringValue(\"Bob\")); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := row2.SetMapKey(\"age\", model.NewStringValue(\"25\")); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := row2.SetMapKey(\"city\", model.NewStringValue(\"Los Angeles\")); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := exp.Append(row2); err != nil {\n\t\tt.Fatal(err)\n\t}\n\trow3 := model.NewMapValue()\n\tif err := row3.SetMapKey(\"name\", model.NewStringValue(\"Charlie\")); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := row3.SetMapKey(\"age\", model.NewStringValue(\"35\")); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := row3.SetMapKey(\"city\", model.NewStringValue(\"Chicago\")); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := exp.Append(row3); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tmatchRes, err := got.Equal(exp)\n\tif err != nil {\n\t\tt.Fatalf(\"error comparing values: %v\", err)\n\t}\n\tmatch, err := matchRes.BoolValue()\n\tif err != nil {\n\t\tt.Fatalf(\"error getting bool value: %v\", err)\n\t}\n\tif !match {\n\t\tt.Errorf(\"expected %v, got %v\", exp, got)\n\t}\n}\n"
  },
  {
    "path": "parsing/csv/writer.go",
    "content": "package csv\n\nimport (\n\t\"bytes\"\n\t\"encoding/csv\"\n\t\"fmt\"\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\ntype csvWriter struct {\n\tseparator rune\n}\n\n// Write writes a value to a byte slice.\nfunc (j *csvWriter) Write(value *model.Value) ([]byte, error) {\n\tif !value.IsSlice() {\n\t\treturn nil, fmt.Errorf(\"csv writer expects root output to be a slice/array, got %s\", value.Type())\n\t}\n\n\tbuf := new(bytes.Buffer)\n\tw := csv.NewWriter(buf)\n\tw.Comma = j.separator\n\n\tvar headers []string\n\n\tif err := value.RangeSlice(func(i int, row *model.Value) error {\n\t\tif i == 0 {\n\t\t\tvar err error\n\t\t\theaders, err = row.MapKeys()\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"error getting map keys: %w\", err)\n\t\t\t}\n\t\t\tif err := w.Write(headers); err != nil {\n\t\t\t\treturn fmt.Errorf(\"error writing headers: %w\", err)\n\t\t\t}\n\t\t}\n\n\t\tvar values []string\n\n\t\tfor _, headerKey := range headers {\n\t\t\tcolV, err := row.GetMapKey(headerKey)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"error getting map key %q: %w\", headerKey, err)\n\t\t\t}\n\n\t\t\tcsvVal, err := valueToString(colV)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"error converting value to string: %w\", err)\n\t\t\t}\n\n\t\t\tvalues = append(values, csvVal)\n\t\t}\n\n\t\tif err := w.Write(values); err != nil {\n\t\t\treturn fmt.Errorf(\"error writing row: %w\", err)\n\t\t}\n\n\t\treturn nil\n\t}); err != nil {\n\t\treturn nil, fmt.Errorf(\"error ranging slice: %w\", err)\n\t}\n\n\tw.Flush()\n\n\treturn buf.Bytes(), nil\n}\n"
  },
  {
    "path": "parsing/csv/writer_test.go",
    "content": "package csv_test\n\nimport (\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"github.com/tomwright/dasel/v3/parsing\"\n\t\"github.com/tomwright/dasel/v3/parsing/csv\"\n\t\"testing\"\n)\n\nfunc TestCsvWriter_Write(t *testing.T) {\n\texpBytes := []byte(`name,age,city\nAlice,30,New York\nBob,25,Los Angeles\nCharlie,35,Chicago\n`)\n\n\tr, err := csv.CSV.NewWriter(parsing.DefaultWriterOptions())\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\trows := model.NewSliceValue()\n\trow1 := model.NewMapValue()\n\tif err := row1.SetMapKey(\"name\", model.NewStringValue(\"Alice\")); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := row1.SetMapKey(\"age\", model.NewStringValue(\"30\")); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := row1.SetMapKey(\"city\", model.NewStringValue(\"New York\")); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := rows.Append(row1); err != nil {\n\t\tt.Fatal(err)\n\t}\n\trow2 := model.NewMapValue()\n\tif err := row2.SetMapKey(\"name\", model.NewStringValue(\"Bob\")); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := row2.SetMapKey(\"age\", model.NewStringValue(\"25\")); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := row2.SetMapKey(\"city\", model.NewStringValue(\"Los Angeles\")); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := rows.Append(row2); err != nil {\n\t\tt.Fatal(err)\n\t}\n\trow3 := model.NewMapValue()\n\tif err := row3.SetMapKey(\"name\", model.NewStringValue(\"Charlie\")); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := row3.SetMapKey(\"age\", model.NewStringValue(\"35\")); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := row3.SetMapKey(\"city\", model.NewStringValue(\"Chicago\")); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := rows.Append(row3); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tgot, err := r.Write(rows)\n\tif err != nil {\n\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\treturn\n\t}\n\n\tif string(expBytes) != string(got) {\n\t\tt.Errorf(\"expected:\\n%s\\ngot:\\n%s\", string(expBytes), string(got))\n\t}\n}\n"
  },
  {
    "path": "parsing/d/reader.go",
    "content": "package json\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/tomwright/dasel/v3/execution\"\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"github.com/tomwright/dasel/v3/parsing\"\n)\n\nconst (\n\t// Dasel represents the dasel format.\n\tDasel parsing.Format = \"dasel\"\n)\n\nvar _ parsing.Reader = (*daselReader)(nil)\n\nfunc init() {\n\tparsing.RegisterReader(Dasel, newDaselReader)\n}\n\nfunc newDaselReader(options parsing.ReaderOptions) (parsing.Reader, error) {\n\treturn &daselReader{}, nil\n}\n\ntype daselReader struct {\n}\n\nfunc (dr *daselReader) Read(in []byte) (*model.Value, error) {\n\tif len(in) == 0 {\n\t\treturn model.NewNullValue(), nil\n\t}\n\tout, err := execution.ExecuteSelector(context.Background(), string(in), model.NewNullValue(), execution.NewOptions())\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to read value: %w\", err)\n\t}\n\treturn out, nil\n}\n"
  },
  {
    "path": "parsing/format.go",
    "content": "package parsing\n\nimport (\n\t\"fmt\"\n)\n\n// Format represents a file format.\ntype Format string\n\n// NewReader creates a new reader for the format.\nfunc (f Format) NewReader(options ReaderOptions) (Reader, error) {\n\tfn, ok := readers[f]\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"unsupported reader file format: %s\", f)\n\t}\n\treturn fn(options)\n}\n\n// NewWriter creates a new writer for the format.\nfunc (f Format) NewWriter(options WriterOptions) (Writer, error) {\n\tfn, ok := writers[f]\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"unsupported writer file format: %s\", f)\n\t}\n\tw, err := fn(options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn MultiDocumentWriter(w), nil\n}\n\n// String returns the string representation of the format.\nfunc (f Format) String() string {\n\treturn string(f)\n}\n\n// RegisteredReaders returns a list of registered readers.\nfunc RegisteredReaders() []Format {\n\tvar formats []Format\n\tfor format := range readers {\n\t\tformats = append(formats, format)\n\t}\n\treturn formats\n}\n\n// RegisteredWriters returns a list of registered writers.\nfunc RegisteredWriters() []Format {\n\tvar formats []Format\n\tfor format := range writers {\n\t\tformats = append(formats, format)\n\t}\n\treturn formats\n}\n"
  },
  {
    "path": "parsing/hcl/hcl.go",
    "content": "package hcl\n\nimport (\n\t\"github.com/tomwright/dasel/v3/parsing\"\n)\n\nconst (\n\t// HCL represents the hcl2 file format.\n\tHCL parsing.Format = \"hcl\"\n)\n\nvar _ parsing.Reader = (*hclReader)(nil)\nvar _ parsing.Writer = (*hclWriter)(nil)\n\nfunc init() {\n\tparsing.RegisterReader(HCL, newHCLReader)\n\tparsing.RegisterWriter(HCL, newHCLWriter)\n}\n"
  },
  {
    "path": "parsing/hcl/reader.go",
    "content": "package hcl\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/hashicorp/hcl/v2\"\n\t\"github.com/hashicorp/hcl/v2/gohcl\"\n\t\"github.com/hashicorp/hcl/v2/hclsyntax\"\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"github.com/tomwright/dasel/v3/parsing\"\n\t\"github.com/zclconf/go-cty/cty\"\n)\n\nfunc newHCLReader(options parsing.ReaderOptions) (parsing.Reader, error) {\n\treturn &hclReader{\n\t\talwaysReadLabelsToSlice: options.Ext[\"hcl-block-format\"] == \"array\",\n\t}, nil\n}\n\ntype hclReader struct {\n\talwaysReadLabelsToSlice bool\n}\n\n// Read reads a value from a byte slice.\n// Reads the HCL data into a model that follows the HCL JSON spec.\n// See https://github.com/hashicorp/hcl/blob/main/json%2Fspec.md\nfunc (r *hclReader) Read(data []byte) (*model.Value, error) {\n\tf, _ := hclsyntax.ParseConfig(data, \"input\", hcl.InitialPos)\n\n\tbody, ok := f.Body.(*hclsyntax.Body)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"failed to assert file body type\")\n\t}\n\n\treturn r.decodeHCLBody(body)\n}\n\nfunc (r *hclReader) decodeHCLBody(body *hclsyntax.Body) (*model.Value, error) {\n\tres := model.NewMapValue()\n\tvar err error\n\n\tfor _, attr := range body.Attributes {\n\t\tval, err := r.decodeHCLExpr(attr.Expr)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to decode attr %q: %w\", attr.Name, err)\n\t\t}\n\n\t\tif err := res.SetMapKey(attr.Name, val); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tres, err = r.decodeHCLBodyBlocks(body, res)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn res, nil\n}\n\nfunc (r *hclReader) decodeHCLBodyBlocks(body *hclsyntax.Body, res *model.Value) (*model.Value, error) {\n\tfor _, block := range body.Blocks {\n\t\tif err := r.decodeHCLBlock(block, res); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn res, nil\n}\n\nfunc (r *hclReader) decodeHCLBlock(block *hclsyntax.Block, res *model.Value) error {\n\tkey := block.Type\n\tv := res\n\tfor _, label := range block.Labels {\n\t\texists, err := v.MapKeyExists(key)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif exists {\n\t\t\tkeyV, err := v.GetMapKey(key)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tv = keyV\n\t\t} else {\n\t\t\tkeyV := model.NewMapValue()\n\t\t\tif err := v.SetMapKey(key, keyV); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tv = keyV\n\t\t}\n\n\t\tkey = label\n\t}\n\n\tbody, err := r.decodeHCLBody(block.Body)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\texists, err := v.MapKeyExists(key)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif exists {\n\t\tkeyV, err := v.GetMapKey(key)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tswitch keyV.Type() {\n\t\tcase model.TypeSlice:\n\t\t\tif err := keyV.Append(body); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase model.TypeMap:\n\t\t\t// Previous value was a map.\n\t\t\t// Create a new slice containing the previous map and the new map.\n\t\t\tnewKeyV := model.NewSliceValue()\n\t\t\tpreviousKeyV, err := keyV.Copy()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := newKeyV.Append(previousKeyV); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := newKeyV.Append(body); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := keyV.Set(newKeyV); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unexpected type: %s\", keyV.Type())\n\t\t}\n\t} else {\n\t\tif r.alwaysReadLabelsToSlice {\n\t\t\tslice := model.NewSliceValue()\n\t\t\tif err := slice.Append(body); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := v.SetMapKey(key, slice); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t} else {\n\t\t\tif err := v.SetMapKey(key, body); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (r *hclReader) decodeHCLExpr(expr hcl.Expression) (*model.Value, error) {\n\tsource := cty.Value{}\n\t_ = gohcl.DecodeExpression(expr, nil, &source)\n\n\treturn r.decodeCtyValue(source)\n}\n\nfunc (r *hclReader) decodeCtyValue(source cty.Value) (res *model.Value, err error) {\n\tdefer func() {\n\t\tr := recover()\n\t\tif r != nil {\n\t\t\terr = fmt.Errorf(\"failed to decode: %v\", r)\n\t\t\treturn\n\t\t}\n\t}()\n\tif source.IsNull() {\n\t\treturn model.NewNullValue(), nil\n\t}\n\n\tsourceT := source.Type()\n\tif sourceT.HasDynamicTypes() {\n\t\t// TODO : Handle DynamicPseudoType.\n\t\t// I haben't found a clear way to do this.\n\t\treturn model.NewNullValue(), nil\n\t}\n\tswitch {\n\tcase sourceT.IsListType(), sourceT.IsTupleType():\n\t\tres = model.NewSliceValue()\n\t\tit := source.ElementIterator()\n\t\tfor it.Next() {\n\t\t\tk, v := it.Element()\n\t\t\t// We don't need the index as they should be in order.\n\t\t\t// Just validates the key is correct.\n\t\t\t_, _ = k.AsBigFloat().Float64()\n\n\t\t\tval, err := r.decodeCtyValue(v)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"failed to decode tuple value: %w\", err)\n\t\t\t}\n\n\t\t\tif err := res.Append(val); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\treturn res, nil\n\tcase sourceT.IsMapType(), sourceT.IsObjectType(), sourceT.IsSetType():\n\t\tv := model.NewMapValue()\n\t\tit := source.ElementIterator()\n\t\tfor it.Next() {\n\t\t\tk, el := it.Element()\n\t\t\tif k.Type() != cty.String {\n\t\t\t\treturn nil, fmt.Errorf(\"object key must be a string\")\n\t\t\t}\n\t\t\tkStr := k.AsString()\n\n\t\t\telVal, err := r.decodeCtyValue(el)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"failed to decode object value: %w\", err)\n\t\t\t}\n\n\t\t\tif err := v.SetMapKey(kStr, elVal); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\treturn v, nil\n\tcase sourceT.IsPrimitiveType():\n\t\tswitch sourceT {\n\t\tcase cty.String:\n\t\t\tv := source.AsString()\n\t\t\treturn model.NewStringValue(v), nil\n\t\tcase cty.Bool:\n\t\t\tv := source.True()\n\t\t\treturn model.NewBoolValue(v), nil\n\t\tcase cty.Number:\n\t\t\tv := source.AsBigFloat()\n\t\t\tf64, _ := v.Float64()\n\t\t\tif v.IsInt() {\n\t\t\t\treturn model.NewIntValue(int64(f64)), nil\n\t\t\t}\n\t\t\treturn model.NewFloatValue(f64), nil\n\t\tdefault:\n\t\t\treturn nil, fmt.Errorf(\"unhandled primitive type %q\", source.Type())\n\t\t}\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported type: %s\", sourceT.FriendlyName())\n\t}\n}\n"
  },
  {
    "path": "parsing/hcl/reader_test.go",
    "content": "package hcl_test\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/parsing\"\n\t\"github.com/tomwright/dasel/v3/parsing/hcl\"\n)\n\ntype readTestCase struct {\n\tin string\n}\n\nfunc (tc readTestCase) run(t *testing.T) {\n\tr, err := hcl.HCL.NewReader(parsing.DefaultReaderOptions())\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tin := []byte(tc.in)\n\n\tgot, err := r.Read(in)\n\tif err != nil {\n\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\treturn\n\t}\n\n\tfmt.Println(got)\n}\n\nfunc TestHclReader_Read(t *testing.T) {\n\tt.Run(\"document a\", readTestCase{\n\t\tin: `io_mode = \"async\"\n\nservice \"http\" \"web_proxy\" {\n  listen_addr = \"127.0.0.1:8080\"\n\n  process \"main\" {\n    command = [\"/usr/local/bin/awesome-app\", \"server\"]\n  }\n\n  process \"mgmt\" {\n    command = [\"/usr/local/bin/awesome-app\", \"mgmt\"]\n  }\n}`,\n\t}.run)\n\tt.Run(\"document b\", readTestCase{\n\t\tin: `resource \"aws_instance\" \"example\" {\n  # (resource configuration omitted for brevity)\n\n  provisioner \"local-exec\" {\n    command = \"echo 'Hello World' >example.txt\"\n  }\n  provisioner \"file\" {\n    source      = \"example.txt\"\n    destination = \"/tmp/example.txt\"\n  }\n  provisioner \"remote-exec\" {\n    inline = [\n      \"sudo install-something -f /tmp/example.txt\",\n    ]\n  }\n}`,\n\t}.run)\n\tt.Run(\"document c\", readTestCase{\n\t\tin: `image_id = \"ami-123\"\ncluster_min_nodes = 2\ncluster_decimal_nodes = 2.2\ncluster_max_nodes = true\navailability_zone_names = [\n\"us-east-1a\",\n\"us-west-1c\",\n]\ndocker_ports = [{\ninternal = 8300\nexternal = 8300\nprotocol = \"tcp\"\n},\n{\ninternal = 8301\nexternal = 8301\nprotocol = \"tcp\"\n}\n]`,\n\t}.run)\n}\n"
  },
  {
    "path": "parsing/hcl/writer.go",
    "content": "package hcl\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"github.com/hashicorp/hcl/v2/hclwrite\"\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"github.com/tomwright/dasel/v3/parsing\"\n\t\"github.com/zclconf/go-cty/cty\"\n)\n\nfunc newHCLWriter(options parsing.WriterOptions) (parsing.Writer, error) {\n\treturn &hclWriter{\n\t\toptions: options,\n\t}, nil\n}\n\ntype hclWriter struct {\n\toptions parsing.WriterOptions\n}\n\n// Write writes a value to a byte slice.\nfunc (j *hclWriter) Write(value *model.Value) ([]byte, error) {\n\tf, err := j.valueToFile(value)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tbuf := new(bytes.Buffer)\n\tif _, err := f.WriteTo(buf); err != nil {\n\t\treturn nil, err\n\t}\n\treturn buf.Bytes(), nil\n}\n\nfunc (j *hclWriter) valueToFile(v *model.Value) (*hclwrite.File, error) {\n\tf := hclwrite.NewEmptyFile()\n\n\tbody := f.Body()\n\n\tif err := j.addValueToBody(nil, v, body); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn f, nil\n}\n\nfunc (j *hclWriter) addValueToBody(previousLabels []string, v *model.Value, body *hclwrite.Body) error {\n\tif !v.IsMap() {\n\t\treturn fmt.Errorf(\"hcl body is expected to be a map, got %s\", v.Type())\n\t}\n\n\tkvs, err := v.MapKeyValues()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tblocks := make([]*hclwrite.Block, 0)\n\tfor _, kv := range kvs {\n\t\tswitch kv.Value.Type() {\n\t\tcase model.TypeMap:\n\t\t\tblock, err := j.valueToBlock(kv.Key, previousLabels, kv.Value)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to encode %q to hcl block: %w\", kv.Key, err)\n\t\t\t}\n\t\t\tblocks = append(blocks, block)\n\t\tcase model.TypeSlice:\n\t\t\tvals := make([]cty.Value, 0)\n\n\t\t\tallMaps := true\n\n\t\t\tif err := kv.Value.RangeSlice(func(_ int, value *model.Value) error {\n\t\t\t\tctyVal, err := j.valueToCty(value)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tvals = append(vals, ctyVal)\n\n\t\t\t\tif !value.IsMap() {\n\t\t\t\t\tallMaps = false\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t}); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tif allMaps {\n\t\t\t\tif err := kv.Value.RangeSlice(func(_ int, value *model.Value) error {\n\t\t\t\t\tblock, err := j.valueToBlock(kv.Key, previousLabels, value)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn fmt.Errorf(\"failed to encode %q to hcl block: %w\", kv.Key, err)\n\t\t\t\t\t}\n\t\t\t\t\tblocks = append(blocks, block)\n\t\t\t\t\treturn nil\n\t\t\t\t}); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tbody.SetAttributeValue(kv.Key, cty.TupleVal(vals))\n\t\t\t}\n\n\t\tdefault:\n\t\t\tctyVal, err := j.valueToCty(kv.Value)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to encode attribute %q: %w\", kv.Key, err)\n\t\t\t}\n\t\t\tbody.SetAttributeValue(kv.Key, ctyVal)\n\t\t}\n\t}\n\n\tfor _, block := range blocks {\n\t\tbody.AppendBlock(block)\n\t}\n\n\treturn nil\n}\n\nfunc (j *hclWriter) valueToCty(v *model.Value) (cty.Value, error) {\n\tswitch v.Type() {\n\tcase model.TypeString:\n\t\tval, err := v.StringValue()\n\t\tif err != nil {\n\t\t\treturn cty.Value{}, err\n\t\t}\n\t\treturn cty.StringVal(val), nil\n\tcase model.TypeBool:\n\t\tval, err := v.BoolValue()\n\t\tif err != nil {\n\t\t\treturn cty.Value{}, err\n\t\t}\n\t\treturn cty.BoolVal(val), nil\n\tcase model.TypeInt:\n\t\tval, err := v.IntValue()\n\t\tif err != nil {\n\t\t\treturn cty.Value{}, err\n\t\t}\n\t\treturn cty.NumberIntVal(val), nil\n\tcase model.TypeFloat:\n\t\tval, err := v.FloatValue()\n\t\tif err != nil {\n\t\t\treturn cty.Value{}, err\n\t\t}\n\t\treturn cty.NumberFloatVal(val), nil\n\tcase model.TypeNull:\n\t\treturn cty.NullVal(cty.NilType), nil\n\tcase model.TypeSlice:\n\t\tvar vals []cty.Value\n\t\tif err := v.RangeSlice(func(_ int, value *model.Value) error {\n\t\t\tctyVal, err := j.valueToCty(value)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tvals = append(vals, ctyVal)\n\t\t\treturn nil\n\t\t}); err != nil {\n\t\t\treturn cty.Value{}, err\n\t\t}\n\t\treturn cty.TupleVal(vals), nil\n\tcase model.TypeMap:\n\t\tmapV := map[string]cty.Value{}\n\t\tif err := v.RangeMap(func(s string, value *model.Value) error {\n\t\t\tctyVal, err := j.valueToCty(value)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tmapV[s] = ctyVal\n\t\t\treturn nil\n\t\t}); err != nil {\n\t\t\treturn cty.Value{}, err\n\t\t}\n\t\treturn cty.ObjectVal(mapV), nil\n\tdefault:\n\t\treturn cty.Value{}, fmt.Errorf(\"unhandled type when converting to cty value %q\", v.Type())\n\t}\n}\n\nfunc (j *hclWriter) valueToBlock(key string, labels []string, v *model.Value) (*hclwrite.Block, error) {\n\tif !v.IsMap() {\n\t\treturn nil, fmt.Errorf(\"must be map\")\n\t}\n\n\tb := hclwrite.NewBlock(key, labels)\n\n\tif err := j.addValueToBody(labels, v, b.Body()); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn b, nil\n}\n"
  },
  {
    "path": "parsing/hcl/writer_test.go",
    "content": "package hcl_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/tomwright/dasel/v3/parsing\"\n\t\"github.com/tomwright/dasel/v3/parsing/hcl\"\n)\n\ntype readWriteTestCase struct {\n\tin string\n}\n\nfunc (tc readWriteTestCase) run(t *testing.T) {\n\tr, err := hcl.HCL.NewReader(parsing.DefaultReaderOptions())\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tw, err := hcl.HCL.NewWriter(parsing.DefaultWriterOptions())\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tin := []byte(tc.in)\n\n\tdata, err := r.Read(in)\n\tif err != nil {\n\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\treturn\n\t}\n\n\tgot, err := w.Write(data)\n\tif err != nil {\n\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\treturn\n\t}\n\tgotStr := string(got)\n\n\tif !cmp.Equal(tc.in, gotStr) {\n\t\tt.Errorf(\"unexpected output: %s\", cmp.Diff(tc.in, gotStr))\n\t}\n}\n\nfunc TestHclReader_ReadWrite(t *testing.T) {\n\tt.Run(\"document a\", readWriteTestCase{\n\t\tin: `io_mode = \"async\"\nservice {\n  http {\n    listen_addr = \"127.0.0.1:8080\"\n    process {\n      main {\n        command = [\"/usr/local/bin/awesome-app\", \"server\"]\n      }\n      mgmt {\n        command = [\"/usr/local/bin/awesome-app\", \"mgmt\"]\n      }\n      mgmt {\n        command = [\"/usr/local/bin/awesome-app\", \"mgmt2\"]\n      }\n    }\n  }\n}\n`,\n\t}.run)\n}\n"
  },
  {
    "path": "parsing/ini/ini.go",
    "content": "package ini\n\nimport (\n\t\"github.com/tomwright/dasel/v3/parsing\"\n)\n\nconst (\n\t// INI represents the ini file format.\n\tINI parsing.Format = \"ini\"\n)\n\nfunc init() {\n\tparsing.RegisterReader(INI, newINIReader)\n\tparsing.RegisterWriter(INI, newINIWriter)\n}\n"
  },
  {
    "path": "parsing/ini/ini_reader.go",
    "content": "package ini\n\nimport (\n\t\"fmt\"\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"github.com/tomwright/dasel/v3/parsing\"\n\t\"gopkg.in/ini.v1\"\n)\n\nvar _ parsing.Reader = (*iniReader)(nil)\n\nfunc init() {\n\tparsing.RegisterReader(INI, newINIReader)\n\tparsing.RegisterWriter(INI, newINIWriter)\n}\n\nfunc newINIReader(options parsing.ReaderOptions) (parsing.Reader, error) {\n\treturn &iniReader{}, nil\n}\n\ntype iniReader struct{}\n\n// Read reads a value from a byte slice.\nfunc (j *iniReader) Read(data []byte) (*model.Value, error) {\n\tf, err := ini.LoadSources(ini.LoadOptions{}, data)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to parse ini: %w\", err)\n\t}\n\n\tres, err := j.readSection(f.Section(ini.DefaultSection))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, s := range f.Sections() {\n\t\tif s.Name() == ini.DefaultSection {\n\t\t\tcontinue\n\t\t}\n\t\tsectionValue, err := j.readSection(s)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif err := res.SetMapKey(s.Name(), sectionValue); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn res, nil\n}\n\nfunc (j *iniReader) readSection(s *ini.Section) (*model.Value, error) {\n\tres := model.NewMapValue()\n\tfor _, k := range s.Keys() {\n\t\tkeyName := k.Name()\n\t\tkeyValue := k.Value()\n\n\t\tif err := res.SetMapKey(keyName, model.NewStringValue(keyValue)); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tfor _, s := range s.ChildSections() {\n\t\tchildSection, err := j.readSection(s)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif err := res.SetMapKey(s.Name(), childSection); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn res, nil\n}\n"
  },
  {
    "path": "parsing/ini/ini_test.go",
    "content": "package ini_test\n\nimport (\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/tomwright/dasel/v3/parsing/ini\"\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/parsing\"\n)\n\nfunc TestIni(t *testing.T) {\n\tdoc := []byte(`app_mode = development\n\n[paths]\ndata = /home/git/grafana\n\n[server]\nprotocol       = http\nhttp_port      = 9999\nenforce_domain = true\n\n[profile testing]\nsomething = foo\n`)\n\treader, err := ini.INI.NewReader(parsing.DefaultReaderOptions())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\twriter, err := ini.INI.NewWriter(parsing.DefaultWriterOptions())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tvalue, err := reader.Read(doc)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tnewDoc, err := writer.Write(value)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif string(doc) != string(newDoc) {\n\t\tt.Fatalf(\"expected %s, got %s...\\n%s\", string(doc), string(newDoc), cmp.Diff(string(doc), string(newDoc)))\n\t}\n}\n"
  },
  {
    "path": "parsing/ini/ini_writer.go",
    "content": "package ini\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"github.com/tomwright/dasel/v3/parsing\"\n\t\"gopkg.in/ini.v1\"\n\t\"strings\"\n)\n\nvar _ parsing.Writer = (*iniWriter)(nil)\n\nfunc newINIWriter(options parsing.WriterOptions) (parsing.Writer, error) {\n\treturn &iniWriter{\n\t\toptions: options,\n\t}, nil\n}\n\ntype iniWriter struct {\n\toptions parsing.WriterOptions\n}\n\n// Write writes a value to a byte slice.\nfunc (j *iniWriter) Write(value *model.Value) ([]byte, error) {\n\tif !value.IsMap() {\n\t\treturn nil, fmt.Errorf(\"ini can only represent map values\")\n\t}\n\n\tf := ini.Empty(ini.LoadOptions{\n\t\tAllowNestedValues: true,\n\t})\n\n\tif err := j.write(f, ini.DefaultSection, value); err != nil {\n\t\treturn nil, err\n\t}\n\n\tbuf := new(bytes.Buffer)\n\n\tif _, err := f.WriteTo(buf); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to write ini: %w\", err)\n\t}\n\n\treturn buf.Bytes(), nil\n}\n\nfunc (j *iniWriter) write(f *ini.File, path string, value *model.Value) error {\n\tsection, err := f.NewSection(path)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create section %s: %w\", path, err)\n\t}\n\n\tnextSectionName := func(x string) string {\n\t\tpath := strings.TrimSpace(strings.TrimPrefix(path, ini.DefaultSection))\n\t\treturn strings.TrimSpace(path + \" \" + x)\n\t}\n\n\tswitch value.Type() {\n\tcase model.TypeMap:\n\t\tif err := value.RangeMap(func(s string, value *model.Value) error {\n\t\t\tswitch {\n\t\t\tcase value.IsScalar():\n\t\t\t\tstrVal, err := valueToString(value)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"failed to convert value to string: %w\", err)\n\t\t\t\t}\n\t\t\t\t_, err = section.NewKey(s, strVal)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"failed to create key %s: %w\", s, err)\n\t\t\t\t}\n\t\t\t\treturn nil\n\n\t\t\tcase value.IsSlice():\n\t\t\t\treturn fmt.Errorf(\"ini writer cannot represent slice values directly; consider using nested sections\")\n\n\t\t\tcase value.IsMap():\n\t\t\t\tif err := j.write(f, nextSectionName(s), value); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\treturn nil\n\n\t\t\tdefault:\n\t\t\t\treturn fmt.Errorf(\"ini writer cannot represent value of type %s\", value.Type())\n\t\t\t}\n\t\t}); err != nil {\n\t\t\treturn err\n\t\t}\n\tdefault:\n\t\treturn fmt.Errorf(\"ini sections can only represent map values\")\n\t}\n\treturn nil\n}\n\nfunc valueToString(v *model.Value) (string, error) {\n\tif v.IsNull() {\n\t\treturn \"\", nil\n\t}\n\n\tswitch v.Type() {\n\tcase model.TypeString:\n\t\tstringValue, err := v.StringValue()\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\treturn stringValue, nil\n\tcase model.TypeInt:\n\t\ti, err := v.IntValue()\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\treturn fmt.Sprintf(\"%d\", i), nil\n\tcase model.TypeFloat:\n\t\ti, err := v.FloatValue()\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\treturn fmt.Sprintf(\"%g\", i), nil\n\tcase model.TypeBool:\n\t\ti, err := v.BoolValue()\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\treturn fmt.Sprintf(\"%t\", i), nil\n\tdefault:\n\t\treturn \"\", fmt.Errorf(\"csv writer cannot format type %s to string\", v.Type())\n\t}\n}\n"
  },
  {
    "path": "parsing/json/json.go",
    "content": "package json\n\nimport (\n\tjson \"github.com/goccy/go-json\"\n\t\"github.com/tomwright/dasel/v3/parsing\"\n)\n\nconst (\n\t// JSON represents the JSON file format.\n\tJSON parsing.Format = \"json\"\n\n\tjsonOpenObject  = json.Delim('{')\n\tjsonCloseObject = json.Delim('}')\n\tjsonOpenArray   = json.Delim('[')\n\tjsonCloseArray  = json.Delim(']')\n)\n\nfunc init() {\n\tparsing.RegisterReader(JSON, newJSONReader)\n\tparsing.RegisterWriter(JSON, newJSONWriter)\n}\n"
  },
  {
    "path": "parsing/json/json_reader.go",
    "content": "package json\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\tjson \"github.com/goccy/go-json\"\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"github.com/tomwright/dasel/v3/parsing\"\n\t\"strings\"\n)\n\nvar _ parsing.Reader = (*jsonReader)(nil)\n\nfunc newJSONReader(options parsing.ReaderOptions) (parsing.Reader, error) {\n\treturn &jsonReader{}, nil\n}\n\ntype jsonReader struct{}\n\n// Read reads a value from a byte slice.\nfunc (j *jsonReader) Read(data []byte) (*model.Value, error) {\n\tdecoder := json.NewDecoder(bytes.NewReader(data))\n\tdecoder.UseNumber()\n\n\tt, err := decoder.Token()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar res *model.Value\n\n\tswitch t {\n\tcase jsonOpenObject:\n\t\tres, err = j.decodeObject(decoder)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"could not decode object: %w\", err)\n\t\t}\n\tcase jsonOpenArray:\n\t\tres, err = j.decodeArray(decoder)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"could not decode array: %w\", err)\n\t\t}\n\tdefault:\n\t\tres, err = j.decodeToken(decoder, t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn res, nil\n}\n\nfunc (j *jsonReader) decodeObject(decoder *json.Decoder) (*model.Value, error) {\n\tres := model.NewMapValue()\n\n\tvar key any = nil\n\n\tfor {\n\t\tt, err := decoder.Token()\n\t\tif err != nil {\n\t\t\t// We don't expect an EOF here since we're in the middle of processing an object.\n\t\t\treturn res, err\n\t\t}\n\n\t\tswitch t {\n\t\tcase jsonOpenArray:\n\t\t\tif key == nil {\n\t\t\t\treturn res, fmt.Errorf(\"unexpected token: %v\", t)\n\t\t\t}\n\t\t\tvalue, err := j.decodeArray(decoder)\n\t\t\tif err != nil {\n\t\t\t\treturn res, err\n\t\t\t}\n\t\t\tif err := res.SetMapKey(key.(string), value); err != nil {\n\t\t\t\treturn res, err\n\t\t\t}\n\t\t\tkey = nil\n\t\tcase jsonCloseArray:\n\t\t\treturn res, fmt.Errorf(\"unexpected token: %v\", t)\n\t\tcase jsonCloseObject:\n\t\t\treturn res, nil\n\t\tcase jsonOpenObject:\n\t\t\tif key == nil {\n\t\t\t\treturn res, fmt.Errorf(\"unexpected token: %v\", t)\n\t\t\t}\n\t\t\tvalue, err := j.decodeObject(decoder)\n\t\t\tif err != nil {\n\t\t\t\treturn res, err\n\t\t\t}\n\t\t\tif err := res.SetMapKey(key.(string), value); err != nil {\n\t\t\t\treturn res, err\n\t\t\t}\n\t\t\tkey = nil\n\t\tdefault:\n\t\t\tif key == nil {\n\t\t\t\tif tStr, ok := t.(string); ok {\n\t\t\t\t\tkey = tStr\n\t\t\t\t} else {\n\t\t\t\t\treturn nil, fmt.Errorf(\"unexpected token: %v\", t)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tvalue, err := j.decodeToken(decoder, t)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tif err := res.SetMapKey(key.(string), value); err != nil {\n\t\t\t\t\treturn res, err\n\t\t\t\t}\n\t\t\t\tkey = nil\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (j *jsonReader) decodeArray(decoder *json.Decoder) (*model.Value, error) {\n\tres := model.NewSliceValue()\n\tfor {\n\t\tt, err := decoder.Token()\n\t\tif err != nil {\n\t\t\t// We don't expect an EOF here since we're in the middle of processing an object.\n\t\t\treturn res, err\n\t\t}\n\n\t\tswitch t {\n\t\tcase jsonOpenArray:\n\t\t\tvalue, err := j.decodeArray(decoder)\n\t\t\tif err != nil {\n\t\t\t\treturn res, err\n\t\t\t}\n\t\t\tif err := res.Append(value); err != nil {\n\t\t\t\treturn res, err\n\t\t\t}\n\t\tcase jsonCloseArray:\n\t\t\treturn res, nil\n\t\tcase jsonCloseObject:\n\t\t\treturn res, fmt.Errorf(\"unexpected token: %t\", t)\n\t\tcase jsonOpenObject:\n\t\t\tvalue, err := j.decodeObject(decoder)\n\t\t\tif err != nil {\n\t\t\t\treturn res, err\n\t\t\t}\n\t\t\tif err := res.Append(value); err != nil {\n\t\t\t\treturn res, err\n\t\t\t}\n\t\tdefault:\n\t\t\tvalue, err := j.decodeToken(decoder, t)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tif err := res.Append(value); err != nil {\n\t\t\t\treturn res, err\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (j *jsonReader) decodeToken(decoder *json.Decoder, t json.Token) (*model.Value, error) {\n\tswitch tv := t.(type) {\n\tcase json.Number:\n\t\tstrNum := tv.String()\n\t\tif strings.Contains(strNum, \".\") {\n\t\t\tfloatNum, err := tv.Float64()\n\t\t\tif err == nil {\n\t\t\t\treturn model.NewFloatValue(floatNum), nil\n\t\t\t}\n\t\t\treturn nil, err\n\t\t}\n\t\tintNum, err := tv.Int64()\n\t\tif err == nil {\n\t\t\treturn model.NewIntValue(intNum), nil\n\t\t}\n\n\t\treturn nil, err\n\tdefault:\n\t\treturn model.NewValue(tv), nil\n\t}\n}\n"
  },
  {
    "path": "parsing/json/json_test.go",
    "content": "package json_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/tomwright/dasel/v3/parsing\"\n\t\"github.com/tomwright/dasel/v3/parsing/json\"\n)\n\nfunc TestJson(t *testing.T) {\n\tdoc := []byte(`{\n    \"string\": \"foo\",\n    \"int\": 1,\n    \"float\": 1.1,\n    \"bool\": true,\n    \"null\": null,\n    \"array\": [\n        1,\n        2,\n        3\n    ],\n    \"object\": {\n        \"key\": \"value\"\n    }\n}\n`)\n\treader, err := json.JSON.NewReader(parsing.DefaultReaderOptions())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\twriter, err := json.JSON.NewWriter(parsing.DefaultWriterOptions())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tvalue, err := reader.Read(doc)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tnewDoc, err := writer.Write(value)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif string(doc) != string(newDoc) {\n\t\tt.Fatalf(\"expected %s, got %s...\\n%s\", string(doc), string(newDoc), cmp.Diff(string(doc), string(newDoc)))\n\t}\n}\n"
  },
  {
    "path": "parsing/json/json_writer.go",
    "content": "package json\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\tjson \"github.com/goccy/go-json\"\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"github.com/tomwright/dasel/v3/parsing\"\n\t\"io\"\n\t\"strings\"\n)\n\nvar _ parsing.Writer = (*jsonWriter)(nil)\n\n// NewJSONWriter creates a new JSON writer.\nfunc newJSONWriter(options parsing.WriterOptions) (parsing.Writer, error) {\n\treturn &jsonWriter{\n\t\toptions: options,\n\t}, nil\n}\n\ntype jsonWriter struct {\n\toptions parsing.WriterOptions\n}\n\n// Write writes a value to a byte slice.\nfunc (j *jsonWriter) Write(value *model.Value) ([]byte, error) {\n\tbuf := new(bytes.Buffer)\n\n\tes := encoderState{indentStr: \"    \"}\n\n\tencoderFn := func(v any) error {\n\t\tres, err := json.Marshal(v)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t_, err = buf.Write(res)\n\t\treturn err\n\t}\n\n\tif err := j.write(buf, encoderFn, es, value); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif _, err := buf.Write([]byte(\"\\n\")); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn buf.Bytes(), nil\n}\n\ntype encoderState struct {\n\tindent    int\n\tindentStr string\n}\n\nfunc (es encoderState) inc() encoderState {\n\tes.indent++\n\treturn es\n}\n\nfunc (es encoderState) writeIndent(w io.Writer) error {\n\tif es.indent == 0 || es.indentStr == \"\" {\n\t\treturn nil\n\t}\n\ti := strings.Repeat(es.indentStr, es.indent)\n\tif _, err := w.Write([]byte(i)); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\ntype encoderFn func(v any) error\n\nfunc (j *jsonWriter) write(w io.Writer, encoder encoderFn, es encoderState, value *model.Value) error {\n\tswitch value.Type() {\n\tcase model.TypeMap:\n\t\treturn j.writeMap(w, encoder, es, value)\n\tcase model.TypeSlice:\n\t\treturn j.writeSlice(w, encoder, es, value)\n\tcase model.TypeString:\n\t\tval, err := value.StringValue()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn encoder(val)\n\tcase model.TypeInt:\n\t\tval, err := value.IntValue()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn encoder(val)\n\tcase model.TypeFloat:\n\t\tval, err := value.FloatValue()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn encoder(val)\n\tcase model.TypeBool:\n\t\tval, err := value.BoolValue()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn encoder(val)\n\tcase model.TypeNull:\n\t\treturn encoder(nil)\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported type: %s\", value.Type())\n\t}\n}\n\nfunc (j *jsonWriter) writeMap(w io.Writer, encoder encoderFn, es encoderState, value *model.Value) error {\n\tkvs, err := value.MapKeyValues()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif _, err := w.Write([]byte(`{`)); err != nil {\n\t\treturn err\n\t}\n\n\tif len(kvs) > 0 {\n\t\tif _, err := w.Write([]byte(\"\\n\")); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tincEs := es.inc()\n\t\tfor i, kv := range kvs {\n\t\t\tif err := incEs.writeIndent(w); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tif _, err := fmt.Fprintf(w, `\"%s\": `, kv.Key); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tif err := j.write(w, encoder, incEs, kv.Value); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tif i < len(kvs)-1 {\n\t\t\t\tif _, err := w.Write([]byte(`,`)); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif _, err := w.Write([]byte(\"\\n\")); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tif err := es.writeIndent(w); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif _, err := w.Write([]byte(`}`)); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (j *jsonWriter) writeSlice(w io.Writer, encoder encoderFn, es encoderState, value *model.Value) error {\n\tif _, err := w.Write([]byte(`[`)); err != nil {\n\t\treturn err\n\t}\n\n\tlength, err := value.SliceLen()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error getting slice length: %w\", err)\n\t}\n\n\tif length > 0 {\n\t\tif _, err := w.Write([]byte(\"\\n\")); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tincEs := es.inc()\n\t\tfor i := 0; i < length; i++ {\n\t\t\tif err := incEs.writeIndent(w); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tva, err := value.GetSliceIndex(i)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"error getting slice index %d: %w\", i, err)\n\t\t\t}\n\t\t\tif err := j.write(w, encoder, incEs, va); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif i < length-1 {\n\t\t\t\tif _, err := w.Write([]byte(`,`)); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t\tif _, err := w.Write([]byte(\"\\n\")); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tif err := es.writeIndent(w); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif _, err := w.Write([]byte(`]`)); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "parsing/reader.go",
    "content": "package parsing\n\nimport \"github.com/tomwright/dasel/v3/model\"\n\nvar readers = map[Format]NewReaderFn{}\n\ntype ReaderOptions struct {\n\tExt map[string]string\n}\n\n// DefaultReaderOptions returns the default reader options.\nfunc DefaultReaderOptions() ReaderOptions {\n\treturn ReaderOptions{\n\t\tExt: make(map[string]string),\n\t}\n}\n\n// Reader reads a value from a byte slice.\ntype Reader interface {\n\t// Read reads a value from a byte slice.\n\tRead([]byte) (*model.Value, error)\n}\n\n// NewReaderFn is a function that creates a new reader.\ntype NewReaderFn func(options ReaderOptions) (Reader, error)\n\n// RegisterReader registers a new reader for the format.\nfunc RegisterReader(format Format, fn NewReaderFn) {\n\treaders[format] = fn\n}\n"
  },
  {
    "path": "parsing/toml/testdata/complex_example.toml",
    "content": "# Comprehensive TOML example exercising many features\n\n\"quoted key\" = \"quoted value\"\n\n\"a.b\" = 42\n\ntitle = \"TOML Example\"\n\n[owner]\nname = \"Tom Preston-Werner\"\norganization = \"GitHub\"\nbio = \"Developer\\nMaintainer\"\n\n# Quoted keys and dotted keys\n[server]\nip = \"192.168.0.1\"\nport = 8080\n\n[server.settings]\n# inline table-like structure but using table header\nmax_connections = 100\nenabled = true\n\n# Inline table example\nprops = { key1 = \"value1\", key2 = \"value2\" }\n\n# Arrays\nnums = [1, 2, 3]\nmix = [1, \"two\", true]\nempty = []\ntrailing = [\"a\", \"b\",]\n\n# Nested inline table and array\nprops2 = { sub = { a = 1 }, arr = [1,2] }\n\n\n# Mixed dotted and quoted\n[a.\"b.c\"]\nd = \"x\"\n\n# Negative and scientific numbers\nn = -5\nf = 1e3\n\n# Arrays of inline tables\nitems = [{a = 1}, {a = 2}]\n\n# Array of tables\n[[products]]\nname = \"Hammer\"\nsku = 738594937\n\n[products.appearance]\ncolor = \"red\"\n\n[[products]]\nname = \"Screwdriver\"\nsku = 12341234\n\n# Nested table headers\n[database]\nserver = \"192.168.1.1\"\nports = [ 8001, 8001, 8002 ]\nconnection_max = 5000\nenabled = true\n\n[database.replica]\nname = \"replica1\"\n\n# Multi-line basic string\nmultiline = \"\"\"\nThis is a\nmultiline string.\n\"\"\"\n\n# Literal multi-line string\nliteral = '''\nThis is a 'literal' multiline\nstring with no escapes.\n'''\n\n# Comments and spacing\n# End of file\n"
  },
  {
    "path": "parsing/toml/toml.go",
    "content": "package toml\n\nimport (\n\t\"github.com/tomwright/dasel/v3/parsing\"\n)\n\n// TODO : Implement using https://github.com/pelletier/go-toml/blob/v2/unstable/ast.go\n\n// TOML represents the TOML file format.\nconst TOML parsing.Format = \"toml\"\n\nfunc init() {\n\tparsing.RegisterReader(TOML, newTOMLReader)\n\tparsing.RegisterWriter(TOML, newTOMLWriter)\n}\n"
  },
  {
    "path": "parsing/toml/toml_reader.go",
    "content": "package toml\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"strconv\"\n\n\t\"github.com/pelletier/go-toml/v2/unstable\"\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"github.com/tomwright/dasel/v3/parsing\"\n)\n\nvar _ parsing.Reader = (*tomlReader)(nil)\n\nfunc newTOMLReader(options parsing.ReaderOptions) (parsing.Reader, error) {\n\treturn &tomlReader{}, nil\n}\n\nconst (\n\ttomlStringStyleKey = \"toml_string_style\"\n\n\ttomlStringStyleMultilineLiteral = \"multiline_literal\"\n\ttomlStringStyleMultilineBasic   = \"multiline_basic\"\n\ttomlStringStyleLiteral          = \"literal\"\n\ttomlStringStyleBasic            = \"basic\"\n\n\ttomlTableStyleKey      = \"toml_table_style\"\n\ttomlTableStyleStandard = \"standard\"\n\ttomlTableStyleArray    = \"array\"\n\ttomlTableStyleInline   = \"inline\"\n)\n\ntype tomlReader struct{}\n\n// Read reads a value from a byte slice.\nfunc (j *tomlReader) Read(data []byte) (*model.Value, error) {\n\tp := &unstable.Parser{}\n\tp.Reset(data)\n\n\troot := model.NewMapValue()\n\n\tvar active *model.Value\n\n\tfor p.NextExpression() {\n\t\texpr := p.Expression()\n\t\tswitch expr.Kind {\n\t\tcase unstable.Invalid, unstable.Comment:\n\t\t\t// ignore\n\t\t\tcontinue\n\t\tcase unstable.KeyValue:\n\t\t\tkeyParts, val, err := j.parseKeyValueNode(p, expr)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tif active != nil {\n\t\t\t\tif err := setDottedKey(root, active, keyParts, val); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif err := setDottedKey(root, nil, keyParts, val); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t}\n\n\t\tcase unstable.Table:\n\t\t\tparts, quoted, err := extractKeyFromTableNode(p, expr)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tm, err := ensureMapAt(root, parts)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\t// Record table header parts and quoting info on the map so the writer\n\t\t\t// can reproduce the header exactly if needed.\n\t\t\tm.SetMetadataValue(tomlTableStyleKey, tomlTableStyleStandard)\n\t\t\tm.SetMetadataValue(\"toml_table_header_parts\", parts)\n\t\t\tm.SetMetadataValue(\"toml_table_header_quoted\", quoted)\n\t\t\tactive = m\n\n\t\tcase unstable.ArrayTable:\n\t\t\tparts, quoted, err := extractKeyFromTableNode(p, expr)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tslice, err := ensureSliceAt(root, parts)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tobj := model.NewMapValue()\n\t\t\t// Mark this object as created via an array table so the writer can\n\t\t\t// emit [[...]] headers for it.\n\t\t\tobj.SetMetadataValue(tomlTableStyleKey, tomlTableStyleArray)\n\t\t\tobj.SetMetadataValue(\"toml_table_header_parts\", parts)\n\t\t\tobj.SetMetadataValue(\"toml_table_header_quoted\", quoted)\n\t\t\tif err := slice.Append(obj); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tactive = obj\n\n\t\tdefault:\n\t\t\t// top-level value nodes are unexpected; ignore\n\t\t}\n\t}\n\n\treturn root, nil\n}\n\n// readNode parses a value node (not table/keyvalue headers).\nfunc (j *tomlReader) readNode(p *unstable.Parser, n *unstable.Node) (string, *model.Value, error) {\n\tswitch n.Kind {\n\t// Meta\n\tcase unstable.Invalid:\n\t\treturn \"\", nil, nil\n\tcase unstable.Comment:\n\t\treturn \"\", nil, nil\n\tcase unstable.Key:\n\t\treturn \"\", model.NewStringValue(string(n.Data)), nil\n\n\t// Container values\n\tcase unstable.Array:\n\t\tv, err := j.readArrayValue(p, n)\n\t\treturn \"\", v, err\n\tcase unstable.InlineTable:\n\t\treturn j.readInlineTable(p, n)\n\n\t// Values\n\tcase unstable.String:\n\t\tv := model.NewStringValue(string(n.Data))\n\n\t\traw := p.Raw(n.Raw)\n\t\tswitch {\n\t\tcase len(raw) >= 3 && bytes.HasPrefix(raw, []byte(\"''\")) && bytes.HasPrefix(raw, []byte(\"'''\")):\n\t\t\tv.SetMetadataValue(tomlStringStyleKey, tomlStringStyleMultilineLiteral)\n\t\tcase len(raw) >= 3 && bytes.HasPrefix(raw, []byte(\"\\\"\\\"\\\"\")):\n\t\t\tv.SetMetadataValue(tomlStringStyleKey, tomlStringStyleMultilineBasic)\n\t\tcase len(raw) >= 1 && raw[0] == '\\'':\n\t\t\tv.SetMetadataValue(tomlStringStyleKey, tomlStringStyleLiteral)\n\t\tdefault:\n\t\t\tv.SetMetadataValue(tomlStringStyleKey, tomlStringStyleBasic)\n\t\t}\n\n\t\treturn \"\", v, nil\n\tcase unstable.Bool:\n\t\treturn \"\", model.NewBoolValue(string(n.Data) == \"true\"), nil\n\tcase unstable.Float:\n\t\tf, err := strconv.ParseFloat(string(n.Data), 64)\n\t\tif err != nil {\n\t\t\treturn \"\", nil, err\n\t\t}\n\t\treturn \"\", model.NewFloatValue(f), nil\n\tcase unstable.Integer:\n\t\ti64, err := strconv.ParseInt(string(n.Data), 10, 64)\n\t\tif err != nil {\n\t\t\treturn \"\", nil, err\n\t\t}\n\t\treturn \"\", model.NewIntValue(int64(i64)), nil\n\tcase unstable.LocalDate:\n\t\treturn \"\", model.NewStringValue(string(n.Data)), nil\n\tcase unstable.LocalTime:\n\t\treturn \"\", model.NewStringValue(string(n.Data)), nil\n\tcase unstable.LocalDateTime:\n\t\treturn \"\", model.NewStringValue(string(n.Data)), nil\n\tcase unstable.DateTime:\n\t\treturn \"\", model.NewStringValue(string(n.Data)), nil\n\tdefault:\n\t\treturn \"\", nil, fmt.Errorf(\"unhandled node kind: %s\", n.Kind.String())\n\t}\n}\n\n// parseKeyValueNode extracts the key segments and value from a KeyValue node without consuming parser expressions.\nfunc (j *tomlReader) parseKeyValueNode(p *unstable.Parser, n *unstable.Node) ([]string, *model.Value, error) {\n\ti := n.Children()\n\tvar keyParts []string\n\tvar val *model.Value\n\n\tfor i.Next() {\n\t\tchild := i.Node()\n\t\tif child.Kind == unstable.Key {\n\t\t\tkeyParts = append(keyParts, string(child.Data))\n\t\t\tcontinue\n\t\t}\n\t\t_, v, err := j.readNode(p, child)\n\t\tif err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\t\tval = v\n\t}\n\n\tif len(keyParts) == 0 {\n\t\treturn nil, nil, fmt.Errorf(\"missing key in key/value node\")\n\t}\n\tif val == nil {\n\t\treturn nil, nil, fmt.Errorf(\"missing value in key/value node\")\n\t}\n\n\treturn keyParts, val, nil\n}\n\n// extractKeyFromTableNode returns the key segments from a Table/ArrayTable node.\nfunc extractKeyFromTableNode(p *unstable.Parser, n *unstable.Node) ([]string, []bool, error) {\n\ti := n.Children()\n\tvar parts []string\n\tvar quoted []bool\n\tfor i.Next() {\n\t\tchild := i.Node()\n\t\tif child.Kind == unstable.Key {\n\t\t\tparts = append(parts, string(child.Data))\n\t\t\traw := p.Raw(child.Raw)\n\t\t\tisQuoted := false\n\t\t\tif len(raw) > 0 && (raw[0] == '\"' || raw[0] == '\\'') {\n\t\t\t\tisQuoted = true\n\t\t\t}\n\t\t\tquoted = append(quoted, isQuoted)\n\t\t\tcontinue\n\t\t}\n\t\treturn nil, nil, fmt.Errorf(\"expected table child node, got %s\", child.Kind.String())\n\t}\n\tif len(parts) == 0 {\n\t\treturn nil, nil, fmt.Errorf(\"missing table child key node\")\n\t}\n\treturn parts, quoted, nil\n}\n\n// ensureMapAt ensures a map exists at the dotted path under root and returns it.\nfunc ensureMapAt(root *model.Value, path []string) (*model.Value, error) {\n\tif len(path) == 0 {\n\t\treturn root, nil\n\t}\n\tcur := root\n\tfor _, seg := range path {\n\t\texists, err := cur.MapKeyExists(seg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif !exists {\n\t\t\tm := model.NewMapValue()\n\t\t\tif err := cur.SetMapKey(seg, m); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tcur = m\n\t\t\tcontinue\n\t\t}\n\t\tnext, err := cur.GetMapKey(seg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif next.IsSlice() {\n\t\t\tsliceLen, err := next.Len()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tnext, err = next.GetSliceIndex(sliceLen - 1)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\tif !next.IsMap() {\n\t\t\treturn nil, fmt.Errorf(\"conflicting types at path '%s': expected map\", seg)\n\t\t}\n\t\tcur = next\n\t}\n\treturn cur, nil\n}\n\n// ensureSliceAt ensures a slice exists at the dotted path under root and returns it.\nfunc ensureSliceAt(root *model.Value, path []string) (*model.Value, error) {\n\tif len(path) == 0 {\n\t\treturn nil, fmt.Errorf(\"empty path for array table\")\n\t}\n\tparentPath := path[:len(path)-1]\n\tfinalSeg := path[len(path)-1]\n\tparent, err := ensureMapAt(root, parentPath)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\texists, err := parent.MapKeyExists(finalSeg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif !exists {\n\t\ts := model.NewSliceValue()\n\t\tif err := parent.SetMapKey(finalSeg, s); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn s, nil\n\t}\n\tv, err := parent.GetMapKey(finalSeg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif !v.IsSlice() {\n\t\treturn nil, fmt.Errorf(\"conflicting types at path '%s': expected slice\", finalSeg)\n\t}\n\treturn v, nil\n}\n\n// setDottedKey sets a value at a (possibly dotted) key within the given container (creating intermediate maps).\n// If active is non-nil, it is the current table object to set keys relative to; root is only used when active is nil to\n// create implicit parent maps on the root.\nfunc setDottedKey(root, active *model.Value, parts []string, val *model.Value) error {\n\tif len(parts) == 0 {\n\t\treturn fmt.Errorf(\"empty key\")\n\t}\n\t// If active table provided, we should set relative to it. But parts may be dotted (i.e., multiple segments).\n\tif active != nil {\n\t\tif len(parts) == 1 {\n\t\t\treturn active.SetMapKey(parts[0], val)\n\t\t}\n\t\tparent, err := ensureMapAt(active, parts[:len(parts)-1])\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn parent.SetMapKey(parts[len(parts)-1], val)\n\t}\n\t// No active table: set on root\n\tif len(parts) == 1 {\n\t\treturn root.SetMapKey(parts[0], val)\n\t}\n\tparent, err := ensureMapAt(root, parts[:len(parts)-1])\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn parent.SetMapKey(parts[len(parts)-1], val)\n}\n\nfunc (j *tomlReader) readInlineTable(p *unstable.Parser, n *unstable.Node) (string, *model.Value, error) {\n\tres := model.NewMapValue()\n\tres.SetMetadataValue(tomlTableStyleKey, tomlTableStyleInline)\n\n\ti := n.Children()\n\tfor i.Next() {\n\t\tchildNode := i.Node()\n\t\t// Inline table children are key/value pairs. Handle KeyValue specially.\n\t\tswitch childNode.Kind {\n\t\tcase unstable.KeyValue:\n\t\t\tkparts, v, err := j.parseKeyValueNode(p, childNode)\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", nil, err\n\t\t\t}\n\t\t\tif err := setDottedKey(res, nil, kparts, v); err != nil {\n\t\t\t\treturn \"\", nil, err\n\t\t\t}\n\t\tdefault:\n\t\t\t// fallback to readNode for other kinds (e.g., Key)\n\t\t\tkey, val, err := j.readNode(p, childNode)\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", nil, err\n\t\t\t}\n\t\t\tif key == \"\" {\n\t\t\t\treturn \"\", nil, fmt.Errorf(\"missing key in inline table child\")\n\t\t\t}\n\t\t\tif err := res.SetMapKey(key, val); err != nil {\n\t\t\t\treturn \"\", nil, err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn \"\", res, nil\n}\n\nfunc (j *tomlReader) readArrayValue(p *unstable.Parser, n *unstable.Node) (*model.Value, error) {\n\tres := model.NewSliceValue()\n\n\ti := n.Children()\n\n\tfor i.Next() {\n\t\tchildNode := i.Node()\n\n\t\t_, val, err := j.readNode(p, childNode)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tval.SetMetadataValue(tomlTableStyleKey, tomlTableStyleArray)\n\n\t\tif err := res.Append(val); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn res, nil\n}\n"
  },
  {
    "path": "parsing/toml/toml_reader_test.go",
    "content": "package toml_test\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"github.com/tomwright/dasel/v3/parsing\"\n\t\"github.com/tomwright/dasel/v3/parsing/toml\"\n)\n\nfunc tomlReaderTest(data []byte, exp func() *model.Value) func(*testing.T) {\n\treturn func(t *testing.T) {\n\t\texp := exp()\n\t\tr, err := toml.TOML.NewReader(parsing.DefaultReaderOptions())\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\n\t\tgot, err := r.Read(data)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t\treturn\n\t\t}\n\n\t\tmatchResult, err := got.Equal(exp)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t\tmatchResultBool, err := matchResult.BoolValue()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t\tif !matchResultBool {\n\t\t\tt.Errorf(\"expected\\n%s\\ngot\\n%s\", exp.String(), got.String())\n\t\t}\n\t}\n}\n\nfunc TestTomlReader_Read(t *testing.T) {\n\tt.Run(\"key value\", func(t *testing.T) {\n\t\tt.Run(\n\t\t\t\"string\",\n\t\t\ttomlReaderTest([]byte(`foo = \"Bar\"`), func() *model.Value {\n\t\t\t\tres := model.NewMapValue()\n\t\t\t\t_ = res.SetMapKey(\"foo\", model.NewStringValue(\"Bar\"))\n\t\t\t\treturn res\n\t\t\t}),\n\t\t)\n\n\t\tt.Run(\n\t\t\t\"int\",\n\t\t\ttomlReaderTest([]byte(`foo = 123`), func() *model.Value {\n\t\t\t\tres := model.NewMapValue()\n\t\t\t\t_ = res.SetMapKey(\"foo\", model.NewIntValue(123))\n\t\t\t\treturn res\n\t\t\t}),\n\t\t)\n\n\t\tt.Run(\n\t\t\t\"float\",\n\t\t\ttomlReaderTest([]byte(`foo = 12.3`), func() *model.Value {\n\t\t\t\tres := model.NewMapValue()\n\t\t\t\t_ = res.SetMapKey(\"foo\", model.NewFloatValue(12.3))\n\t\t\t\treturn res\n\t\t\t}),\n\t\t)\n\n\t\tt.Run(\n\t\t\t\"true\",\n\t\t\ttomlReaderTest([]byte(`foo = true`), func() *model.Value {\n\t\t\t\tres := model.NewMapValue()\n\t\t\t\t_ = res.SetMapKey(\"foo\", model.NewBoolValue(true))\n\t\t\t\treturn res\n\t\t\t}),\n\t\t)\n\n\t\tt.Run(\n\t\t\t\"false\",\n\t\t\ttomlReaderTest([]byte(`foo = false`), func() *model.Value {\n\t\t\t\tres := model.NewMapValue()\n\t\t\t\t_ = res.SetMapKey(\"foo\", model.NewBoolValue(false))\n\t\t\t\treturn res\n\t\t\t}),\n\t\t)\n\t})\n\n\tt.Run(\"inline table\", tomlReaderTest([]byte(`props = { key1 = \"value1\", key2 = \"value2\" }`), func() *model.Value {\n\t\tres := model.NewMapValue()\n\t\tinlineTable := model.NewMapValue()\n\t\t_ = inlineTable.SetMapKey(\"key1\", model.NewStringValue(\"value1\"))\n\t\t_ = inlineTable.SetMapKey(\"key2\", model.NewStringValue(\"value2\"))\n\t\t_ = res.SetMapKey(\"props\", inlineTable)\n\t\treturn res\n\t}))\n\n\tt.Run(\"table\", tomlReaderTest([]byte(`[props]\nkey1 = \"value1\"\nkey2 = \"value2\"`), func() *model.Value {\n\t\tres := model.NewMapValue()\n\t\tinlineTable := model.NewMapValue()\n\t\t_ = inlineTable.SetMapKey(\"key1\", model.NewStringValue(\"value1\"))\n\t\t_ = inlineTable.SetMapKey(\"key2\", model.NewStringValue(\"value2\"))\n\t\t_ = res.SetMapKey(\"props\", inlineTable)\n\t\treturn res\n\t}))\n\n\tt.Run(\"array table\", tomlReaderTest([]byte(`[[products]]\nname = \"foo\"\nid = 1\n\n[[products]]\nname = \"bar\"\nid = 2`), func() *model.Value {\n\t\tproductsArray := model.NewSliceValue()\n\t\tproduct1 := model.NewMapValue()\n\t\t_ = product1.SetMapKey(\"name\", model.NewStringValue(\"foo\"))\n\t\t_ = product1.SetMapKey(\"id\", model.NewIntValue(1))\n\t\t_ = productsArray.Append(product1)\n\n\t\tproduct2 := model.NewMapValue()\n\t\t_ = product2.SetMapKey(\"name\", model.NewStringValue(\"bar\"))\n\t\t_ = product2.SetMapKey(\"id\", model.NewIntValue(2))\n\t\t_ = productsArray.Append(product2)\n\n\t\tres := model.NewMapValue()\n\t\t_ = res.SetMapKey(\"products\", productsArray)\n\t\treturn res\n\t}))\n\n\t//\tdataBytes := []byte(`title = \"TOML Example\"\n\t//[owner]\n\t//name = \"Tom Preston-Werner\"\n\t//props = { key1 = \"value1\", key2 = \"value2\" }\n\t//\n\t//[[products]]\n\t//name = \"Hammer\"\n\t//sku = 738594937\n\t//\n\t//[[products]]\n\t//name = \"Screwdriver\"\n\t//sku = 12341234\n\t//`)\n\t//\t//dob = 1979-05-27T07:32:00-08:00\n\t//\tdataModel := model.NewMapValue()\n\t//\t//parsedTime, err := time.Parse(time.RFC3339, \"1979-05-27T07:32:00-08:00\")\n\t//\t//if err != nil {\n\t//\t//\tt.Fatalf(\"unexpected error: %v\", err)\n\t//\t//}\n\t//\townerMap := model.NewMapValue()\n\t//\t_ = ownerMap.SetMapKey(\"name\", model.NewStringValue(\"Tom Preston-Werner\"))\n\t//\t//_ = ownerMap.SetMapKey(\"dob\", model.NewValue(parsedTime))\n\t//\t_ = dataModel.SetMapKey(\"title\", model.NewStringValue(\"TOML Example\"))\n\t//\t_ = dataModel.SetMapKey(\"owner\", ownerMap)\n}\n\nfunc TestTomlReader_MoreCases(t *testing.T) {\n\t// simple array of ints\n\tt.Run(\"simple array\", tomlReaderTest([]byte(`nums = [1, 2, 3]`), func() *model.Value {\n\t\tres := model.NewMapValue()\n\t\ts := model.NewSliceValue()\n\t\t_ = s.Append(model.NewIntValue(1))\n\t\t_ = s.Append(model.NewIntValue(2))\n\t\t_ = s.Append(model.NewIntValue(3))\n\t\t_ = res.SetMapKey(\"nums\", s)\n\t\treturn res\n\t}))\n\n\t// mixed type array\n\tt.Run(\"mixed array\", tomlReaderTest([]byte(`mix = [1, \"two\", true]`), func() *model.Value {\n\t\tres := model.NewMapValue()\n\t\ts := model.NewSliceValue()\n\t\t_ = s.Append(model.NewIntValue(1))\n\t\t_ = s.Append(model.NewStringValue(\"two\"))\n\t\t_ = s.Append(model.NewBoolValue(true))\n\t\t_ = res.SetMapKey(\"mix\", s)\n\t\treturn res\n\t}))\n\n\t// inline nested table and array\n\tt.Run(\"inline nested table\", tomlReaderTest([]byte(`props = { sub = { a = 1 }, arr = [1,2] }`), func() *model.Value {\n\t\tres := model.NewMapValue()\n\t\tprops := model.NewMapValue()\n\t\tsub := model.NewMapValue()\n\t\t_ = sub.SetMapKey(\"a\", model.NewIntValue(1))\n\t\t_ = props.SetMapKey(\"sub\", sub)\n\t\tarr := model.NewSliceValue()\n\t\t_ = arr.Append(model.NewIntValue(1))\n\t\t_ = arr.Append(model.NewIntValue(2))\n\t\t_ = props.SetMapKey(\"arr\", arr)\n\t\t_ = res.SetMapKey(\"props\", props)\n\t\treturn res\n\t}))\n\n\t// quoted key with space\n\tt.Run(\"quoted key with space\", tomlReaderTest([]byte(`\"a b\" = \"val\"`), func() *model.Value {\n\t\tres := model.NewMapValue()\n\t\t_ = res.SetMapKey(\"a b\", model.NewStringValue(\"val\"))\n\t\treturn res\n\t}))\n\n\t// dotted+quoted mixture\n\tt.Run(\"dotted and quoted mixture\", tomlReaderTest([]byte(`a.\"b.c\".d = \"x\"`), func() *model.Value {\n\t\tres := model.NewMapValue()\n\t\ta := model.NewMapValue()\n\t\tb := model.NewMapValue()\n\t\t_ = b.SetMapKey(\"d\", model.NewStringValue(\"x\"))\n\t\t_ = a.SetMapKey(\"b.c\", b)\n\t\t_ = res.SetMapKey(\"a\", a)\n\t\treturn res\n\t}))\n\n\t// negative integer\n\tt.Run(\"negative integer\", tomlReaderTest([]byte(`n = -5`), func() *model.Value {\n\t\tres := model.NewMapValue()\n\t\t_ = res.SetMapKey(\"n\", model.NewIntValue(-5))\n\t\treturn res\n\t}))\n\n\t// scientific float\n\tt.Run(\"scientific float\", tomlReaderTest([]byte(`f = 1e3`), func() *model.Value {\n\t\tres := model.NewMapValue()\n\t\t_ = res.SetMapKey(\"f\", model.NewFloatValue(1000.0))\n\t\treturn res\n\t}))\n\n\t// array of inline tables\n\tt.Run(\"array of inline tables\", tomlReaderTest([]byte(`items = [{a = 1}, {a = 2}]`), func() *model.Value {\n\t\tres := model.NewMapValue()\n\t\titems := model.NewSliceValue()\n\t\tp1 := model.NewMapValue()\n\t\t_ = p1.SetMapKey(\"a\", model.NewIntValue(1))\n\t\t_ = items.Append(p1)\n\t\tp2 := model.NewMapValue()\n\t\t_ = p2.SetMapKey(\"a\", model.NewIntValue(2))\n\t\t_ = items.Append(p2)\n\t\t_ = res.SetMapKey(\"items\", items)\n\t\treturn res\n\t}))\n\n\t// nested tables using headers\n\tt.Run(\"nested table headers\", tomlReaderTest([]byte(`[server]\nip = \"127.0.0.1\"\n[server.db]\nname = \"maindb\"`), func() *model.Value {\n\t\tres := model.NewMapValue()\n\t\tserver := model.NewMapValue()\n\t\t_ = server.SetMapKey(\"ip\", model.NewStringValue(\"127.0.0.1\"))\n\t\tdb := model.NewMapValue()\n\t\t_ = db.SetMapKey(\"name\", model.NewStringValue(\"maindb\"))\n\t\t_ = server.SetMapKey(\"db\", db)\n\t\t_ = res.SetMapKey(\"server\", server)\n\t\treturn res\n\t}))\n\n\tt.Run(\"sub-table in array of tables\", tomlReaderTest([]byte(`[[products]]\nname = \"Hammer\"\nsku = 738594937\n\n[products.appearance]\ncolor = \"red\"\n\n[[products]]\nname = \"Screwdriver\"\nsku = 12341234`), func() *model.Value {\n\t\tres := model.NewMapValue()\n\t\tproducts := model.NewSliceValue()\n\t\thammer := model.NewMapValue()\n\t\t_ = hammer.SetMapKey(\"name\", model.NewStringValue(\"Hammer\"))\n\t\t_ = hammer.SetMapKey(\"sku\", model.NewIntValue(738594937))\n\t\tappearance := model.NewMapValue()\n\t\t_ = appearance.SetMapKey(\"color\", model.NewStringValue(\"red\"))\n\t\t_ = hammer.SetMapKey(\"appearance\", appearance)\n\t\t_ = products.Append(hammer)\n\t\tscrewdriver := model.NewMapValue()\n\t\t_ = screwdriver.SetMapKey(\"name\", model.NewStringValue(\"Screwdriver\"))\n\t\t_ = screwdriver.SetMapKey(\"sku\", model.NewIntValue(12341234))\n\t\t_ = products.Append(screwdriver)\n\t\t_ = res.SetMapKey(\"products\", products)\n\t\treturn res\n\t}))\n}\n\nfunc TestTomlReader_QuotedKeys(t *testing.T) {\n\t// quoted single key with dot should be a single key\n\tt.Run(\"quoted single segment containing dot\", tomlReaderTest([]byte(`\"a.b\" = 1`), func() *model.Value {\n\t\tres := model.NewMapValue()\n\t\t_ = res.SetMapKey(\"a.b\", model.NewIntValue(1))\n\t\treturn res\n\t}))\n\n\t// unquoted dotted key should create nested maps\n\tt.Run(\"unquoted dotted key creates nested maps\", tomlReaderTest([]byte(`a.b = 2`), func() *model.Value {\n\t\tres := model.NewMapValue()\n\t\ta := model.NewMapValue()\n\t\t_ = a.SetMapKey(\"b\", model.NewIntValue(2))\n\t\t_ = res.SetMapKey(\"a\", a)\n\t\treturn res\n\t}))\n\n\t// mixture: first segment unquoted, second quoted containing dot\n\tt.Run(\"mixed quoted segment\", tomlReaderTest([]byte(`a.\"b.c\" = 3`), func() *model.Value {\n\t\tres := model.NewMapValue()\n\t\ta := model.NewMapValue()\n\t\t_ = a.SetMapKey(\"b.c\", model.NewIntValue(3))\n\t\t_ = res.SetMapKey(\"a\", a)\n\t\treturn res\n\t}))\n}\n\nfunc TestTomlReader_ComplexFile(t *testing.T) {\n\tdataPath := filepath.Join(\"testdata\", \"complex_example.toml\")\n\tb, err := os.ReadFile(dataPath)\n\tif err != nil {\n\t\tt.Fatalf(\"failed reading test data: %v\", err)\n\t}\n\n\tr, err := toml.TOML.NewReader(parsing.DefaultReaderOptions())\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error creating reader: %v\", err)\n\t}\n\n\tval, err := r.Read(b)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error reading toml: %v\", err)\n\t}\n\n\t// spot check some keys\n\towner, err := val.GetMapKey(\"owner\")\n\tif err != nil {\n\t\tt.Fatalf(\"missing owner: %v\", err)\n\t}\n\tname, err := owner.GetMapKey(\"name\")\n\tif err != nil {\n\t\tt.Fatalf(\"missing owner.name: %v\", err)\n\t}\n\tif got, _ := name.StringValue(); got != \"Tom Preston-Werner\" {\n\t\tt.Fatalf(\"unexpected owner.name: %s\", got)\n\t}\n\n\t// quoted key\n\tqk, err := val.GetMapKey(\"quoted key\")\n\tif err != nil {\n\t\tt.Fatalf(\"missing quoted key: %v\", err)\n\t}\n\tif s, _ := qk.StringValue(); s != \"quoted value\" {\n\t\tt.Fatalf(\"unexpected quoted key value: %s\", s)\n\t}\n\n\t// products array length\n\tproducts, err := val.GetMapKey(\"products\")\n\tif err != nil {\n\t\tt.Fatalf(\"missing products: %v\", err)\n\t}\n\tif l, _ := products.SliceLen(); l != 2 {\n\t\tt.Fatalf(\"expected 2 products, got %d\", l)\n\t}\n\n\t// a.b quoted\n\tab, err := val.GetMapKey(\"a.b\")\n\tif err != nil {\n\t\tt.Fatalf(\"missing a.b: %v\", err)\n\t}\n\tif i, _ := ab.IntValue(); i != 42 {\n\t\tt.Fatalf(\"unexpected a.b: %d\", i)\n\t}\n\n\t// nested table header value\n\tdb, err := val.GetMapKey(\"database\")\n\tif err != nil {\n\t\tt.Fatalf(\"missing database: %v\", err)\n\t}\n\trep, err := db.GetMapKey(\"replica\")\n\tif err != nil {\n\t\tt.Fatalf(\"missing database.replica: %v\", err)\n\t}\n\tif n, _ := rep.GetMapKey(\"name\"); n == nil {\n\t\tt.Fatalf(\"missing database.replica.name\")\n\t}\n}\n\nfunc TestTomlReader_EdgeCases(t *testing.T) {\n\t// conflict: scalar then dotted key\n\tt.Run(\"scalar then dotted key conflict\", func(t *testing.T) {\n\t\tsrc := []byte(\"a = 1\\na.b = 2\")\n\t\tr, err := toml.TOML.NewReader(parsing.DefaultReaderOptions())\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error creating reader: %v\", err)\n\t\t}\n\t\t_, err = r.Read(src)\n\t\tif err == nil {\n\t\t\tt.Fatalf(\"expected error for scalar then dotted key conflict, got nil\")\n\t\t}\n\t})\n\n\t// array-table conflict: scalar then array-table\n\tt.Run(\"scalar then array-table conflict\", func(t *testing.T) {\n\t\tsrc := []byte(\"a = 1\\n[[a]]\\nname = \\\"x\\\"\")\n\t\tr, err := toml.TOML.NewReader(parsing.DefaultReaderOptions())\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error creating reader: %v\", err)\n\t\t}\n\t\t_, err = r.Read(src)\n\t\tif err == nil {\n\t\t\tt.Fatalf(\"expected error for scalar then array-table conflict, got nil\")\n\t\t}\n\t})\n\n\t// repeated table headers should merge keys\n\tt.Run(\"repeated explicit table headers merge\", func(t *testing.T) {\n\t\tsrc := []byte(\"[t]\\na = 1\\n[t]\\nb = 2\")\n\t\tr, err := toml.TOML.NewReader(parsing.DefaultReaderOptions())\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error creating reader: %v\", err)\n\t\t}\n\t\tval, err := r.Read(src)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t\tm, err := val.GetMapKey(\"t\")\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"missing table t: %v\", err)\n\t\t}\n\t\tif a, err := m.GetMapKey(\"a\"); err != nil || a == nil {\n\t\t\tt.Fatalf(\"expected a in t: %v\", err)\n\t\t}\n\t\tif b, err := m.GetMapKey(\"b\"); err != nil || b == nil {\n\t\t\tt.Fatalf(\"expected b in t: %v\", err)\n\t\t}\n\t})\n\n\t// inline table then explicit table should merge\n\tt.Run(\"inline table then explicit header merges\", func(t *testing.T) {\n\t\tsrc := []byte(\"t = {a = 1}\\n[t]\\nb = 2\")\n\t\tr, err := toml.TOML.NewReader(parsing.DefaultReaderOptions())\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error creating reader: %v\", err)\n\t\t}\n\t\tval, err := r.Read(src)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t\tm, err := val.GetMapKey(\"t\")\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"missing table t: %v\", err)\n\t\t}\n\t\tif a, err := m.GetMapKey(\"a\"); err != nil || a == nil {\n\t\t\tt.Fatalf(\"expected a in t: %v\", err)\n\t\t}\n\t\tif b, err := m.GetMapKey(\"b\"); err != nil || b == nil {\n\t\t\tt.Fatalf(\"expected b in t: %v\", err)\n\t\t}\n\t})\n\n\t// integer overflow\n\tt.Run(\"integer overflow\", func(t *testing.T) {\n\t\tsrc := []byte(\"big = 9223372036854775808\") // int64 max + 1\n\t\tr, err := toml.TOML.NewReader(parsing.DefaultReaderOptions())\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error creating reader: %v\", err)\n\t\t}\n\t\t_, err = r.Read(src)\n\t\tif err == nil {\n\t\t\tt.Fatalf(\"expected error for integer overflow, got nil\")\n\t\t}\n\t})\n\n\t// ensure arrays with trailing commas parse (already covered elsewhere but add explicit check)\n\tt.Run(\"array trailing comma parse\", func(t *testing.T) {\n\t\tsrc := []byte(\"arr = [1,2,]\")\n\t\tr, err := toml.TOML.NewReader(parsing.DefaultReaderOptions())\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error creating reader: %v\", err)\n\t\t}\n\t\tval, err := r.Read(src)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t\tarr, err := val.GetMapKey(\"arr\")\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"missing arr: %v\", err)\n\t\t}\n\t\tif l, _ := arr.SliceLen(); l != 2 {\n\t\t\tt.Fatalf(\"expected 2 items in arr, got %d\", l)\n\t\t}\n\t})\n\n\t// ensure arrays of inline tables types preserved\n\tt.Run(\"array of inline tables preserves types\", func(t *testing.T) {\n\t\tsrc := []byte(\"items = [{a = 1}, {a = 2}]\")\n\t\tr, err := toml.TOML.NewReader(parsing.DefaultReaderOptions())\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error creating reader: %v\", err)\n\t\t}\n\t\tval, err := r.Read(src)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t\titems, err := val.GetMapKey(\"items\")\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"missing items: %v\", err)\n\t\t}\n\t\tif l, _ := items.SliceLen(); l != 2 {\n\t\t\tt.Fatalf(\"expected 2 items, got %d\", l)\n\t\t}\n\t\t// first element has a=1\n\t\tit0, _ := items.GetSliceIndex(0)\n\t\tif a, err := it0.GetMapKey(\"a\"); err != nil {\n\t\t\tt.Fatalf(\"missing a in first item: %v\", err)\n\t\t} else if ai, _ := a.IntValue(); ai != 1 {\n\t\t\tt.Fatalf(\"unexpected a in first item: %d\", ai)\n\t\t}\n\t})\n}\n\nfunc TestTomlReader_TimeStrings(t *testing.T) {\n\t// Local date\n\tt.Run(\"local date string\", func(t *testing.T) {\n\t\tsrc := []byte(\"d = 1979-05-27\")\n\t\tr, err := toml.TOML.NewReader(parsing.DefaultReaderOptions())\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error creating reader: %v\", err)\n\t\t}\n\t\tval, err := r.Read(src)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error parsing: %v\", err)\n\t\t}\n\t\tv, err := val.GetMapKey(\"d\")\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"missing key d: %v\", err)\n\t\t}\n\t\ts, err := v.StringValue()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"value not string: %v\", err)\n\t\t}\n\t\tif s != \"1979-05-27\" {\n\t\t\tt.Fatalf(\"expected %q got %q\", \"1979-05-27\", s)\n\t\t}\n\t})\n\n\t// Local time\n\tt.Run(\"local time string\", func(t *testing.T) {\n\t\tsrc := []byte(\"t = 07:32:00\")\n\t\tr, err := toml.TOML.NewReader(parsing.DefaultReaderOptions())\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error creating reader: %v\", err)\n\t\t}\n\t\tval, err := r.Read(src)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error parsing: %v\", err)\n\t\t}\n\t\tv, err := val.GetMapKey(\"t\")\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"missing key t: %v\", err)\n\t\t}\n\t\ts, err := v.StringValue()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"value not string: %v\", err)\n\t\t}\n\t\tif s != \"07:32:00\" {\n\t\t\tt.Fatalf(\"expected %q got %q\", \"07:32:00\", s)\n\t\t}\n\t})\n\n\t// Local date-time\n\tt.Run(\"local datetime string\", func(t *testing.T) {\n\t\tsrc := []byte(\"dt = 1979-05-27T07:32:00\")\n\t\tr, err := toml.TOML.NewReader(parsing.DefaultReaderOptions())\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error creating reader: %v\", err)\n\t\t}\n\t\tval, err := r.Read(src)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error parsing: %v\", err)\n\t\t}\n\t\tv, err := val.GetMapKey(\"dt\")\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"missing key dt: %v\", err)\n\t\t}\n\t\ts, err := v.StringValue()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"value not string: %v\", err)\n\t\t}\n\t\tif s != \"1979-05-27T07:32:00\" {\n\t\t\tt.Fatalf(\"expected %q got %q\", \"1979-05-27T07:32:00\", s)\n\t\t}\n\t})\n\n\t// DateTime with timezone (RFC3339)\n\tt.Run(\"datetime with tz string\", func(t *testing.T) {\n\t\tsrc := []byte(\"dt = 1979-05-27T07:32:00-08:00\")\n\t\tr, err := toml.TOML.NewReader(parsing.DefaultReaderOptions())\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error creating reader: %v\", err)\n\t\t}\n\t\tval, err := r.Read(src)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error parsing: %v\", err)\n\t\t}\n\t\tv, err := val.GetMapKey(\"dt\")\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"missing key dt: %v\", err)\n\t\t}\n\t\ts, err := v.StringValue()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"value not string: %v\", err)\n\t\t}\n\t\tif s != \"1979-05-27T07:32:00-08:00\" {\n\t\t\tt.Fatalf(\"expected %q got %q\", \"1979-05-27T07:32:00-08:00\", s)\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "parsing/toml/toml_writer.go",
    "content": "package toml\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"strconv\"\n\t\"strings\"\n\n\tpkg \"github.com/pelletier/go-toml/v2\"\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"github.com/tomwright/dasel/v3/parsing\"\n)\n\nvar _ parsing.Writer = (*tomlWriter)(nil)\n\nfunc newTOMLWriter(options parsing.WriterOptions) (parsing.Writer, error) {\n\tw := &tomlWriter{}\n\treturn w, nil\n}\n\ntype tomlWriter struct {\n}\n\n// Write converts the dasel model.Value into Go values backed by dynamically\n// generated struct types (reflect.StructOf) that preserve key ordering, then\n// delegates to go-toml Marshal to produce canonical TOML output.\n// This implementation doesn't preserve all formatting metadata (multiline strings, etc)\n// and needs some work to do so.\nfunc (j *tomlWriter) Write(value *model.Value) ([]byte, error) {\n\tif value == nil {\n\t\treturn nil, fmt.Errorf(\"nil value\")\n\t}\n\n\tvar goValue interface{}\n\tvar err error\n\n\tif value.IsMap() {\n\t\tgoValue, err = buildGoValueForMap(value)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to construct go value: %w\", err)\n\t\t}\n\t} else {\n\t\t// handle scalars, slices, etc. Use goTypeAndValue to get a concrete reflect.Value\n\t\ttyp, rv, err := goTypeAndValue(value)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to convert non-map top-level value: %w\", err)\n\t\t}\n\t\t// For nil/zero interface, ensure we pass nil interface rather than a typed zero.\n\t\tif typ.Kind() == reflect.Interface && rv.IsZero() {\n\t\t\tgoValue = nil\n\t\t} else {\n\t\t\tgoValue = rv.Interface()\n\t\t}\n\t}\n\n\t// We currently encode the top level value directly. We have little control over nested values.\n\t// Perhaps it would be better to implement a custom Encoder that respects metadata on nested values?\n\tvar buf bytes.Buffer\n\tencoder := pkg.NewEncoder(&buf)\n\n\tif err := encoder.Encode(goValue); err != nil {\n\t\treturn nil, fmt.Errorf(\"toml encode failed: %w\", err)\n\t}\n\toutBytes := buf.Bytes()\n\n\t// Ensure trailing newline for consistency with other format writers/tests.\n\tif len(outBytes) == 0 || outBytes[len(outBytes)-1] != '\\n' {\n\t\toutBytes = append(outBytes, '\\n')\n\t}\n\n\treturn outBytes, nil\n}\n\n// buildGoValueForMap constructs a reflect.Value that is a struct with fields in\n// the same order as keys in the provided map Value. The struct fields are\n// tagged with `toml:\"<original key>\"` so go-toml will use the original key\n// names when encoding.\nfunc buildGoValueForMap(v *model.Value) (interface{}, error) {\n\tkvs, err := v.MapKeyValues()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Build struct fields in order\n\tfields := make([]reflect.StructField, 0, len(kvs))\n\tfieldValues := make([]reflect.Value, 0, len(kvs))\n\n\tfor i, kv := range kvs {\n\t\tft, fv, err := goTypeAndValue(kv.Value)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error converting key %q: %w\", kv.Key, err)\n\t\t}\n\n\t\ttomlTagContents := []string{kv.Key}\n\n\t\t//  These currently do not take effect. They are placeholders for future functionality.\n\t\tif v, ok := kv.Value.MetadataValue(tomlStringStyleKey); ok {\n\t\t\tif style, ok := v.(string); ok && style != \"\" {\n\t\t\t\tswitch style {\n\t\t\t\tcase tomlStringStyleMultilineBasic, tomlStringStyleMultilineLiteral:\n\t\t\t\t\ttomlTagContents = append(tomlTagContents, \"multiline\")\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif v, ok := kv.Value.MetadataValue(tomlTableStyleKey); ok {\n\t\t\tif style, ok := v.(string); ok && style != \"\" {\n\t\t\t\tswitch style {\n\t\t\t\tcase tomlTableStyleInline:\n\t\t\t\t\ttomlTagContents = append(tomlTagContents, \"inline\")\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// create exported field name (F0, F1...) and set tag to preserve toml key\n\t\tfield := reflect.StructField{\n\t\t\tName: \"F\" + strconv.Itoa(i),\n\t\t\tType: ft,\n\t\t\tTag:  reflect.StructTag(fmt.Sprintf(`toml:\"%s\"`, strings.Join(tomlTagContents, \",\"))),\n\t\t}\n\t\tfields = append(fields, field)\n\t\tfieldValues = append(fieldValues, fv)\n\t}\n\n\tstructType := reflect.StructOf(fields)\n\tval := reflect.New(structType).Elem()\n\n\tfor i := range fieldValues {\n\t\tval.Field(i).Set(fieldValues[i])\n\t}\n\n\treturn val.Interface(), nil\n}\n\n// goTypeAndValue returns a reflect.Type and a reflect.Value suitable for use as a\n// struct field/type for the given model.Value. It converts nested maps into\n// reflect.StructOf types recursively (preserving key order), converts slices to\n// slices of appropriate element types when possible, or []interface{} otherwise.\nfunc goTypeAndValue(v *model.Value) (reflect.Type, reflect.Value, error) {\n\tswitch v.Type() {\n\tcase model.TypeString:\n\t\ts, err := v.StringValue()\n\t\tif err != nil {\n\t\t\treturn nil, reflect.Value{}, err\n\t\t}\n\t\treturn reflect.TypeOf(\"\"), reflect.ValueOf(s), nil\n\tcase model.TypeInt:\n\t\ti, err := v.IntValue()\n\t\tif err != nil {\n\t\t\treturn nil, reflect.Value{}, err\n\t\t}\n\t\t// use int64 to be safe\n\t\treturn reflect.TypeOf(int64(0)), reflect.ValueOf(i), nil\n\tcase model.TypeFloat:\n\t\tf, err := v.FloatValue()\n\t\tif err != nil {\n\t\t\treturn nil, reflect.Value{}, err\n\t\t}\n\t\treturn reflect.TypeOf(float64(0)), reflect.ValueOf(f), nil\n\tcase model.TypeBool:\n\t\tb, err := v.BoolValue()\n\t\tif err != nil {\n\t\t\treturn nil, reflect.Value{}, err\n\t\t}\n\t\treturn reflect.TypeOf(true), reflect.ValueOf(b), nil\n\tcase model.TypeNull:\n\t\t// represent null as interface{}(nil)\n\t\treturn reflect.TypeOf((*interface{})(nil)).Elem(), reflect.Zero(reflect.TypeOf((*interface{})(nil)).Elem()), nil\n\tcase model.TypeMap:\n\t\t// For nested maps, prefer map[string]interface{} values rather than\n\t\t// generating nested struct types. Using struct types for nested maps\n\t\t// causes go-toml to emit explicit table headers for those nested\n\t\t// structures which can change the document shape on round-trip. The\n\t\t// top-level map is still handled by buildGoValueForMap which generates\n\t\t// a struct to preserve ordering.\n\t\tkvs, err := v.MapKeyValues()\n\t\tif err != nil {\n\t\t\treturn nil, reflect.Value{}, err\n\t\t}\n\t\tm := make(map[string]interface{})\n\t\tfor _, kv := range kvs {\n\t\t\t_, rv, err := goTypeAndValue(kv.Value)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, reflect.Value{}, fmt.Errorf(\"error in nested key %q: %w\", kv.Key, err)\n\t\t\t}\n\t\t\tm[kv.Key] = rv.Interface()\n\t\t}\n\t\treturn reflect.TypeOf(map[string]interface{}{}), reflect.ValueOf(m), nil\n\tcase model.TypeSlice:\n\t\t// Decide element types. If all elements are maps and have compatible keys,\n\t\t// build a struct type for elements and return a slice of that struct type.\n\t\tlength, _ := v.SliceLen()\n\t\tif length == 0 {\n\t\t\t// empty slice -> use []interface{}\n\t\t\telemType := reflect.TypeOf((*interface{})(nil)).Elem()\n\t\t\treturn reflect.SliceOf(elemType), reflect.MakeSlice(reflect.SliceOf(elemType), 0, 0), nil\n\t\t}\n\n\t\t// inspect first element\n\t\tfirst, _ := v.GetSliceIndex(0)\n\t\tif first.IsMap() {\n\t\t\t// Collect union of keys across elements in order of appearance\n\t\t\tseen := map[string]bool{}\n\t\t\tkeys := make([]string, 0)\n\t\t\t_ = v.RangeSlice(func(_ int, item *model.Value) error {\n\t\t\t\tif !item.IsMap() {\n\t\t\t\t\t// mixed types -> fallback to []interface{}\n\t\t\t\t\tkeys = nil\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t\tkvs, _ := item.MapKeyValues()\n\t\t\t\tfor _, kv := range kvs {\n\t\t\t\t\tif !seen[kv.Key] {\n\t\t\t\t\t\tseen[kv.Key] = true\n\t\t\t\t\t\tkeys = append(keys, kv.Key)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t})\n\n\t\t\tif keys != nil {\n\t\t\t\t// Build []map[string]interface{} to preserve keys and values without\n\t\t\t\t// forcing a generated struct type which can cause conversion issues.\n\t\t\t\tsliceMaps := make([]map[string]interface{}, 0, length)\n\t\t\t\t_ = v.RangeSlice(func(_ int, item *model.Value) error {\n\t\t\t\t\tm := map[string]interface{}{}\n\t\t\t\t\tif item.IsMap() {\n\t\t\t\t\t\tkvs, _ := item.MapKeyValues()\n\t\t\t\t\t\tfor _, kv := range kvs {\n\t\t\t\t\t\t\t_, rv, err := goTypeAndValue(kv.Value)\n\t\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tm[kv.Key] = rv.Interface()\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tsliceMaps = append(sliceMaps, m)\n\t\t\t\t\treturn nil\n\t\t\t\t})\n\t\t\t\treturn reflect.TypeOf([]map[string]interface{}{}), reflect.ValueOf(sliceMaps), nil\n\t\t\t}\n\t\t}\n\n\t\t// fallback: build []interface{}\n\t\telems := make([]interface{}, 0, length)\n\t\t_ = v.RangeSlice(func(_ int, item *model.Value) error {\n\t\t\tgt, rv, err := goTypeAndValue(item)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\t// convert reflect.Value to interface{}\n\t\t\telems = append(elems, rv.Interface())\n\t\t\t_ = gt\n\t\t\treturn nil\n\t\t})\n\t\treturn reflect.TypeOf([]interface{}{}), reflect.ValueOf(elems), nil\n\tdefault:\n\t\t// fallback to stringified interface\n\t\ts := fmt.Sprintf(\"%v\", v.Interface())\n\t\treturn reflect.TypeOf(\"\"), reflect.ValueOf(s), nil\n\t}\n}\n"
  },
  {
    "path": "parsing/toml/toml_writer_test.go",
    "content": "package toml_test\n\nimport (\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/parsing\"\n\t\"github.com/tomwright/dasel/v3/parsing/toml\"\n)\n\nfunc TestTomlWriter_RoundTripSimple(t *testing.T) {\n\tdoc := []byte(`title = \"TOML Example\"\n[owner]\nname = \"Tom Preston-Werner\"\n\n[database]\nports = [8001, 8001, 8002]\nenabled = true\n`)\n\n\treader, err := toml.TOML.NewReader(parsing.DefaultReaderOptions())\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error creating reader: %v\", err)\n\t}\n\twriter, err := toml.TOML.NewWriter(parsing.DefaultWriterOptions())\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error creating writer: %v\", err)\n\t}\n\n\tv, err := reader.Read(doc)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to read doc: %v\", err)\n\t}\n\n\tout, err := writer.Write(v)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to write doc: %v\", err)\n\t}\n\n\tv2, err := reader.Read(out)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to read generated doc: %v\", err)\n\t}\n\n\tres, err := v.Equal(v2)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to compare values: %v\", err)\n\t}\n\tb, err := res.BoolValue()\n\tif err != nil {\n\t\tt.Fatalf(\"failed to get bool from equal result: %v\", err)\n\t}\n\tif !b {\n\t\tt.Fatalf(\"round-trip value mismatch\\norig:\\n%s\\nnew:\\n%s\", string(doc), string(out))\n\t}\n}\n\nfunc TestTomlWriter_OrderPreserved(t *testing.T) {\n\tdoc := []byte(\"a = 1\\nb = 2\\nc = 3\\n\")\n\treader, err := toml.TOML.NewReader(parsing.DefaultReaderOptions())\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error creating reader: %v\", err)\n\t}\n\twriter, err := toml.TOML.NewWriter(parsing.DefaultWriterOptions())\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error creating writer: %v\", err)\n\t}\n\n\tv, err := reader.Read(doc)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to read doc: %v\", err)\n\t}\n\n\tout, err := writer.Write(v)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to write doc: %v\", err)\n\t}\n\n\t// Ensure key order a, b, c appears in the output\n\toutStr := string(out)\n\tia := strings.Index(outStr, \"a =\")\n\tib := strings.Index(outStr, \"b =\")\n\tic := strings.Index(outStr, \"c =\")\n\tif ia == -1 || ib == -1 || ic == -1 {\n\t\tt.Fatalf(\"expected keys missing in output: %s\", outStr)\n\t}\n\tif ia >= ib || ib >= ic {\n\t\tt.Fatalf(\"expected order a,b,c in output; got:\\n%s\", outStr)\n\t}\n}\n\nfunc TestTomlWriter_ArrayOfTables_RoundTrip(t *testing.T) {\n\tdoc := []byte(`[[products]]\nname = \"Hammer\"\nsku = 738594937\n\n[[products]]\nname = \"Screwdriver\"\nsku = 12341234\n`)\n\n\treader, err := toml.TOML.NewReader(parsing.DefaultReaderOptions())\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error creating reader: %v\", err)\n\t}\n\twriter, err := toml.TOML.NewWriter(parsing.DefaultWriterOptions())\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error creating writer: %v\", err)\n\t}\n\n\tv, err := reader.Read(doc)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to read doc: %v\", err)\n\t}\n\n\tout, err := writer.Write(v)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to write doc: %v\", err)\n\t}\n\n\tv2, err := reader.Read(out)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to read generated doc: %v\", err)\n\t}\n\n\tres, err := v.Equal(v2)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to compare values: %v\", err)\n\t}\n\tb, err := res.BoolValue()\n\tif err != nil {\n\t\tt.Fatalf(\"failed to get bool from equal result: %v\", err)\n\t}\n\tif !b {\n\t\tt.Fatalf(\"array-table round-trip mismatch\\norig:\\n%s\\nnew:\\n%s\", string(doc), string(out))\n\t}\n}\n\nfunc TestTomlWriter_MoreCases(t *testing.T) {\n\tcases := map[string][]byte{\n\t\t\"simple array\":           []byte(\"nums = [1, 2, 3]\"),\n\t\t\"mixed array\":            []byte(\"mix = [1, \\\"two\\\", true]\"),\n\t\t\"inline nested table\":    []byte(\"props = { sub = { a = 1 }, arr = [1,2] }\"),\n\t\t\"quoted key with space\":  []byte(\"\\\"a b\\\" = \\\"val\\\"\"),\n\t\t\"dotted and quoted mix\":  []byte(\"a.\\\"b.c\\\".d = \\\"x\\\"\"),\n\t\t\"negative integer\":       []byte(\"n = -5\"),\n\t\t\"scientific float\":       []byte(\"f = 1e3\"),\n\t\t\"array of inline tables\": []byte(\"items = [{a = 1}, {a = 2} ]\"),\n\t\t\"nested table headers\":   []byte(\"[server]\\nip = \\\"127.0.0.1\\\"\\n[server.db]\\nname = \\\"maindb\\\"\"),\n\t\t\"quoted single dot\":      []byte(\"\\\"a.b\\\" = 1\"),\n\t\t\"unquoted dotted\":        []byte(\"a.b = 2\"),\n\t\t\"mixed quoted segment\":   []byte(\"a.\\\"b.c\\\" = 3\"),\n\t\t\"inline then explicit\":   []byte(\"t = {a = 1}\\n[t]\\nb = 2\"),\n\t\t\"array trailing comma\":   []byte(\"arr = [1,2,]\"),\n\t\t\"local date\":             []byte(\"d = 1979-05-27\"),\n\t\t\"local time\":             []byte(\"t = 07:32:00\"),\n\t\t\"local datetime\":         []byte(\"dt = 1979-05-27T07:32:00\"),\n\t\t\"datetime with tz\":       []byte(\"dt = 1979-05-27T07:32:00-08:00\"),\n\t\t\"multiline basic string\": []byte(\"m = '''not used'''\\n\"),\n\t}\n\n\treader, err := toml.TOML.NewReader(parsing.DefaultReaderOptions())\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error creating reader: %v\", err)\n\t}\n\twriter, err := toml.TOML.NewWriter(parsing.DefaultWriterOptions())\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error creating writer: %v\", err)\n\t}\n\n\tfor name, src := range cases {\n\t\tsrc := src\n\t\tname := name\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tv, err := reader.Read(src)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"reader error for %s: %v\", name, err)\n\t\t\t}\n\n\t\t\tout, err := writer.Write(v)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"writer error for %s: %v\", name, err)\n\t\t\t}\n\n\t\t\tv2, err := reader.Read(out)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"reader error for generated output %s: %v\", name, err)\n\t\t\t}\n\n\t\t\tres, err := v.Equal(v2)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"compare error for %s: %v\", name, err)\n\t\t\t}\n\t\t\tb, err := res.BoolValue()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"bool extraction error for %s: %v\", name, err)\n\t\t\t}\n\t\t\tif !b {\n\t\t\t\tt.Fatalf(\"round-trip mismatch for %s\\norig:\\n%s\\nnew:\\n%s\", name, string(src), string(out))\n\t\t\t}\n\t\t})\n\t}\n\n\t// Complex example file round-trip\n\tt.Run(\"complex example file\", func(t *testing.T) {\n\t\t//t.Skip(\"Multiline string formatting not yet preserved\")\n\t\tdataPath := \"testdata/complex_example.toml\"\n\t\tb, err := os.ReadFile(dataPath)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"failed reading test data: %v\", err)\n\t\t}\n\n\t\tv, err := reader.Read(b)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"reader error for complex file: %v\", err)\n\t\t}\n\n\t\tout, err := writer.Write(v)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"writer error for complex file: %v\", err)\n\t\t}\n\n\t\tv2, err := reader.Read(out)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"failed to re-read generated complex doc: %v\", err)\n\t\t}\n\n\t\tres, err := v.Equal(v2)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"compare error for complex file: %v\", err)\n\t\t}\n\t\tb2, err := res.BoolValue()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"bool extraction error for complex file: %v\", err)\n\t\t}\n\t\tif !b2 {\n\t\t\t// Print original and written output for debugging.\n\t\t\tt.Fatalf(\"complex-example round-trip mismatch\\n--- ORIGINAL ---\\n%s\\n--- WRITTEN ---\\n%s\\n\", string(b), string(out))\n\t\t}\n\t})\n}\n\nfunc TestTomlWriter_StrictOutput(t *testing.T) {\n\treader, err := toml.TOML.NewReader(parsing.DefaultReaderOptions())\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error creating reader: %v\", err)\n\t}\n\twriter, err := toml.TOML.NewWriter(parsing.DefaultWriterOptions())\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error creating writer: %v\", err)\n\t}\n\n\ttests := map[string]struct {\n\t\tsrc []byte\n\t\texp string\n\t}{\n\t\t\"ordered scalars\": {\n\t\t\tsrc: []byte(\"a = 1\\nb = 2\\nc = 3\\n\"),\n\t\t\texp: \"a = 1\\nb = 2\\nc = 3\\n\",\n\t\t},\n\t\t\"inline array\": {\n\t\t\tsrc: []byte(\"nums = [1,2,3]\"),\n\t\t\texp: \"nums = [1, 2, 3]\\n\",\n\t\t},\n\t\t\"quoted key\": {\n\t\t\tsrc: []byte(\"\\\"a b\\\" = \\\"val\\\"\"),\n\t\t\texp: \"'a b' = 'val'\\n\",\n\t\t},\n\t\t\"array of tables\": {\n\t\t\tsrc: []byte(`[[products]]\nname = \"Hammer\"\nsku = 738594937\n\n[[products]]\nname = \"Screwdriver\"\nsku = 12341234\n`),\n\t\t\texp: `[[products]]\nname = 'Hammer'\nsku = 738594937\n\n[[products]]\nname = 'Screwdriver'\nsku = 12341234\n`,\n\t\t},\n\t}\n\n\tfor name, tc := range tests {\n\t\tname := name\n\t\ttc := tc\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tv, err := reader.Read(tc.src)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"reader error for %s: %v\", name, err)\n\t\t\t}\n\n\t\t\tout, err := writer.Write(v)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"writer error for %s: %v\", name, err)\n\t\t\t}\n\n\t\t\tgot := string(out)\n\t\t\tif got != tc.exp {\n\t\t\t\tt.Fatalf(\"strict output mismatch for %s\\nexpected:\\n%s\\n got:\\n%s\", name, tc.exp, got)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "parsing/writer.go",
    "content": "package parsing\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\nvar writers = map[Format]NewWriterFn{}\n\ntype WriterOptions struct {\n\tCompact bool\n\tIndent  string\n\tExt     map[string]string\n}\n\n// DefaultWriterOptions returns the default writer options.\nfunc DefaultWriterOptions() WriterOptions {\n\treturn WriterOptions{\n\t\tCompact: false,\n\t\tIndent:  \"  \",\n\t\tExt:     make(map[string]string),\n\t}\n}\n\n// Writer writes a value to a byte slice.\ntype Writer interface {\n\t// Write writes a value to a byte slice.\n\tWrite(*model.Value) ([]byte, error)\n}\n\n// NewWriterFn is a function that creates a new writer.\ntype NewWriterFn func(options WriterOptions) (Writer, error)\n\n// RegisterWriter registers a new writer for the format.\nfunc RegisterWriter(format Format, fn NewWriterFn) {\n\twriters[format] = fn\n}\n\n// DocumentSeparator is an interface that can be implemented by writers to allow for custom document separators.\ntype DocumentSeparator interface {\n\t// Separator returns the document separator.\n\tSeparator() []byte\n}\n\n// MultiDocumentWriter is a writer that can write multiple documents.\nfunc MultiDocumentWriter(w Writer) Writer {\n\treturn &multiDocumentWriter{w: w}\n}\n\ntype multiDocumentWriter struct {\n\tw Writer\n}\n\n// Write writes a value to a byte slice.\nfunc (w *multiDocumentWriter) Write(value *model.Value) ([]byte, error) {\n\tif value.IsBranch() || value.IsSpread() {\n\t\tbuf := new(bytes.Buffer)\n\n\t\tdocumentSeparator := []byte(\"\\n\")\n\t\tif ds, ok := w.w.(DocumentSeparator); ok {\n\t\t\tdocumentSeparator = ds.Separator()\n\t\t}\n\n\t\ttotalDocuments, err := value.SliceLen()\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to get document length: %w\", err)\n\t\t}\n\n\t\tif err := value.RangeSlice(func(i int, v *model.Value) error {\n\t\t\tdocBytes, err := w.w.Write(v)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to write document %d: %w\", i, err)\n\t\t\t}\n\t\t\tbuf.Write(docBytes)\n\n\t\t\tif i < totalDocuments-1 {\n\t\t\t\tbuf.Write(documentSeparator)\n\t\t\t}\n\n\t\t\treturn nil\n\t\t}); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\treturn buf.Bytes(), nil\n\t}\n\treturn w.w.Write(value)\n}\n"
  },
  {
    "path": "parsing/xml/reader.go",
    "content": "package xml\n\nimport (\n\t\"bytes\"\n\t\"encoding/xml\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"github.com/tomwright/dasel/v3/parsing\"\n)\n\n// Security limits for XML parsing to prevent DoS attacks.\n// These limits are intentionally conservative to balance usability and safety.\nconst (\n\tmaxCommentLength = 10_000     // Maximum bytes per comment (10KB) - prevents memory exhaustion from single large comments\n\tmaxTotalComments = 1_000      // Maximum comments per document - prevents abuse via comment flooding\n\tmaxXMLSize       = 10_000_000 // Maximum XML input size (10MB) - prevents processing of excessively large files\n)\n\nfunc newXMLReader(options parsing.ReaderOptions) (parsing.Reader, error) {\n\treturn &xmlReader{\n\t\tstructured: options.Ext[\"xml-mode\"] == \"structured\",\n\t}, nil\n}\n\ntype xmlReader struct {\n\tstructured bool\n}\n\n// Read reads a value from a byte slice.\nfunc (j *xmlReader) Read(data []byte) (*model.Value, error) {\n\tif len(data) > maxXMLSize {\n\t\treturn nil, fmt.Errorf(\"XML input exceeds maximum size of %d bytes\", maxXMLSize)\n\t}\n\n\tdecoder := xml.NewDecoder(bytes.NewReader(data))\n\tdecoder.Strict = true\n\n\ttotalComments := 0\n\tel, err := j.parseElement(decoder, xml.StartElement{\n\t\tName: xml.Name{\n\t\t\tLocal: \"root\",\n\t\t},\n\t}, &totalComments)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif j.structured {\n\t\treturn el.toStructuredModel()\n\t}\n\treturn el.toFriendlyModel()\n}\n\nfunc (e *xmlElement) toStructuredModel() (*model.Value, error) {\n\tattrs := model.NewMapValue()\n\tfor _, attr := range e.Attrs {\n\t\tif err := attrs.SetMapKey(attr.Name, model.NewStringValue(attr.Value)); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tres := model.NewMapValue()\n\tif len(e.ProcessingInstructions) > 0 {\n\t\tres.SetMetadataValue(\"xml_processing_instructions\", e.ProcessingInstructions)\n\t}\n\tif len(e.Comments) > 0 {\n\t\tres.SetMetadataValue(\"xml_comments\", e.Comments)\n\t}\n\tif err := res.SetMapKey(\"name\", model.NewStringValue(e.Name)); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := res.SetMapKey(\"attrs\", attrs); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err := res.SetMapKey(\"content\", model.NewStringValue(e.Content)); err != nil {\n\t\treturn nil, err\n\t}\n\tchildren := model.NewSliceValue()\n\tfor _, child := range e.Children {\n\t\tchildModel, err := child.toStructuredModel()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif err := children.Append(childModel); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tif err := res.SetMapKey(\"children\", children); err != nil {\n\t\treturn nil, err\n\t}\n\treturn res, nil\n}\n\nfunc (e *xmlElement) toFriendlyModel() (*model.Value, error) {\n\tif len(e.Attrs) == 0 && len(e.Children) == 0 && len(e.Comments) == 0 {\n\t\treturn model.NewStringValue(e.Content), nil\n\t}\n\n\tres := model.NewMapValue()\n\tif len(e.ProcessingInstructions) > 0 {\n\t\tres.SetMetadataValue(\"xml_processing_instructions\", e.ProcessingInstructions)\n\t}\n\tif len(e.Comments) > 0 {\n\t\tres.SetMetadataValue(\"xml_comments\", e.Comments)\n\t}\n\tfor _, attr := range e.Attrs {\n\t\tif err := res.SetMapKey(\"-\"+attr.Name, model.NewStringValue(attr.Value)); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tif len(e.Content) > 0 {\n\t\tif err := res.SetMapKey(\"#text\", model.NewStringValue(e.Content)); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tif len(e.Children) > 0 {\n\t\tchildElementKeys := make([]string, 0)\n\t\tchildElements := make(map[string][]*xmlElement)\n\n\t\tfor _, child := range e.Children {\n\t\t\tif _, ok := childElements[child.Name]; !ok {\n\t\t\t\tchildElementKeys = append(childElementKeys, child.Name)\n\t\t\t}\n\t\t\tchildElements[child.Name] = append(childElements[child.Name], child)\n\t\t}\n\n\t\tfor _, key := range childElementKeys {\n\t\t\tcs := childElements[key]\n\t\t\tswitch len(cs) {\n\t\t\tcase 0:\n\t\t\t\tcontinue\n\t\t\tcase 1:\n\t\t\t\tchildModel, err := cs[0].toFriendlyModel()\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tif err := res.SetMapKey(key, childModel); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t\tchildren := model.NewSliceValue()\n\t\t\t\tfor _, child := range cs {\n\t\t\t\t\tchildModel, err := child.toFriendlyModel()\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t}\n\t\t\t\t\tif err := children.Append(childModel); err != nil {\n\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif err := res.SetMapKey(key, children); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn res, nil\n}\n\nfunc (j *xmlReader) parseElement(decoder *xml.Decoder, element xml.StartElement, totalComments *int) (*xmlElement, error) {\n\tel := &xmlElement{\n\t\tName:                   element.Name.Local,\n\t\tAttrs:                  make([]xmlAttr, 0),\n\t\tChildren:               make([]*xmlElement, 0),\n\t\tProcessingInstructions: make([]*xmlProcessingInstruction, 0),\n\t\tComments:               make([]*xmlComment, 0),\n\t}\n\n\tfor _, attr := range element.Attr {\n\t\tel.Attrs = append(el.Attrs, xmlAttr{\n\t\t\tName:  attr.Name.Local,\n\t\t\tValue: attr.Value,\n\t\t})\n\t}\n\n\tvar processingInstructions []*xmlProcessingInstruction\n\tvar comments []*xmlComment\n\n\tfor {\n\t\tt, err := decoder.Token()\n\t\tif errors.Is(err, io.EOF) {\n\t\t\tif el.Name == \"root\" {\n\t\t\t\treturn el, nil\n\t\t\t}\n\t\t\treturn nil, fmt.Errorf(\"unexpected EOF\")\n\t\t}\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to read token: %w\", err)\n\t\t}\n\t\tif t == nil {\n\t\t\tif el.Name == \"root\" {\n\t\t\t\treturn el, nil\n\t\t\t}\n\t\t\treturn nil, fmt.Errorf(\"unexpected nil token\")\n\t\t}\n\n\t\tswitch t := t.(type) {\n\t\tcase xml.StartElement:\n\t\t\tchild, err := j.parseElement(decoder, t, totalComments)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tif len(comments) > 0 {\n\t\t\t\tchild.Comments = append(comments, child.Comments...)\n\t\t\t\tcomments = nil\n\t\t\t}\n\t\t\tif len(processingInstructions) > 0 {\n\t\t\t\tchild.ProcessingInstructions = processingInstructions\n\t\t\t\tprocessingInstructions = nil\n\t\t\t}\n\t\t\tel.Children = append(el.Children, child)\n\t\tcase xml.CharData:\n\t\t\tstringContent := string(t)\n\t\t\tif strings.TrimSpace(stringContent) == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tel.Content += stringContent\n\t\tcase xml.EndElement:\n\t\t\tif len(comments) > 0 {\n\t\t\t\tel.Comments = append(el.Comments, comments...)\n\t\t\t}\n\t\t\treturn el, nil\n\t\tcase xml.Comment:\n\t\t\tcommentText := string(t)\n\t\t\tif len(commentText) > maxCommentLength {\n\t\t\t\treturn nil, fmt.Errorf(\"comment exceeds maximum length of %d bytes\", maxCommentLength)\n\t\t\t}\n\t\t\tif *totalComments >= maxTotalComments {\n\t\t\t\treturn nil, fmt.Errorf(\"document exceeds maximum comment count of %d\", maxTotalComments)\n\t\t\t}\n\t\t\tcomment := &xmlComment{\n\t\t\t\tText: commentText,\n\t\t\t}\n\t\t\tcomments = append(comments, comment)\n\t\t\t*totalComments++\n\t\t\tcontinue\n\t\tcase xml.ProcInst:\n\t\t\tpi := &xmlProcessingInstruction{\n\t\t\t\tTarget: t.Target,\n\t\t\t\tValue:  string(t.Inst),\n\t\t\t}\n\t\t\tprocessingInstructions = append(processingInstructions, pi)\n\t\t\tcontinue\n\t\tcase xml.Directive:\n\t\t\tcontinue\n\t\tcase xml.Attr:\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn nil, fmt.Errorf(\"unexpected token: %v\", t)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "parsing/xml/reader_test.go",
    "content": "package xml_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/parsing\"\n\t\"github.com/tomwright/dasel/v3/parsing/json\"\n\t\"github.com/tomwright/dasel/v3/parsing/xml\"\n)\n\nfunc TestXmlReader_Read(t *testing.T) {\n\tt.Run(\"nested xml elements\", func(t *testing.T) {\n\t\tr, err := xml.XML.NewReader(parsing.DefaultReaderOptions())\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t}\n\n\t\tw, err := json.JSON.NewWriter(parsing.DefaultWriterOptions())\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t}\n\n\t\tdata, err := r.Read([]byte(`<Document>\n  <Sender>Ivanov</Sender>\n  <In_N_Document>\n    <N_Document>1024</N_Document>\n    <Date_Reg>2024-06-21T15:07:29.0451517+03:00</Date_Reg>\n  </In_N_Document>\n  <Out_N_Document>\n    <N_Document>2043</N_Document>\n    <Date_Reg>2024-05-01T00:00:00</Date_Reg>\n  </Out_N_Document>\n  <Content>Skzzkz</Content>\n  <DSP>true</DSP>\n</Document>\n`))\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t}\n\n\t\tjsonBytes, err := w.Write(data)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t}\n\n\t\texpected := `{\n    \"Document\": {\n        \"Sender\": \"Ivanov\",\n        \"In_N_Document\": {\n            \"N_Document\": \"1024\",\n            \"Date_Reg\": \"2024-06-21T15:07:29.0451517+03:00\"\n        },\n        \"Out_N_Document\": {\n            \"N_Document\": \"2043\",\n            \"Date_Reg\": \"2024-05-01T00:00:00\"\n        },\n        \"Content\": \"Skzzkz\",\n        \"DSP\": \"true\"\n    }\n}\n`\n\t\tif string(jsonBytes) != expected {\n\t\t\tt.Fatalf(\"Expected:\\n%s\\nGot:\\n%s\", expected, string(jsonBytes))\n\t\t}\n\t})\n\n\tt.Run(\"nested xml elements with processing instruction\", func(t *testing.T) {\n\t\tr, err := xml.XML.NewReader(parsing.DefaultReaderOptions())\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t}\n\n\t\tw, err := json.JSON.NewWriter(parsing.DefaultWriterOptions())\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t}\n\n\t\tdata, err := r.Read([]byte(`<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>\n<Document>\n  <Sender>Ivanov</Sender>\n  <In_N_Document>\n    <N_Document>1024</N_Document>\n    <Date_Reg>2024-06-21T15:07:29.0451517+03:00</Date_Reg>\n  </In_N_Document>\n  <Out_N_Document>\n    <N_Document>2043</N_Document>\n    <Date_Reg>2024-05-01T00:00:00</Date_Reg>\n  </Out_N_Document>\n  <Content>Skzzkz</Content>\n  <DSP>true</DSP>\n</Document>\n`))\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t}\n\n\t\tjsonBytes, err := w.Write(data)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t}\n\n\t\texpected := `{\n    \"Document\": {\n        \"Sender\": \"Ivanov\",\n        \"In_N_Document\": {\n            \"N_Document\": \"1024\",\n            \"Date_Reg\": \"2024-06-21T15:07:29.0451517+03:00\"\n        },\n        \"Out_N_Document\": {\n            \"N_Document\": \"2043\",\n            \"Date_Reg\": \"2024-05-01T00:00:00\"\n        },\n        \"Content\": \"Skzzkz\",\n        \"DSP\": \"true\"\n    }\n}\n`\n\t\tif string(jsonBytes) != expected {\n\t\t\tt.Fatalf(\"Expected:\\n%s\\nGot:\\n%s\", expected, string(jsonBytes))\n\t\t}\n\t})\n\n\tt.Run(\"cdata tag\", func(t *testing.T) {\n\t\tr, err := xml.XML.NewReader(parsing.DefaultReaderOptions())\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t}\n\n\t\tdata, err := r.Read([]byte(`<foo>\n\t<![CDATA[<bar>baz</bar>]]>\n</foo>\n`))\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t}\n\n\t\tx, err := data.GetMapKey(\"foo\")\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t}\n\t\tgot, err := x.StringValue()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t}\n\n\t\texp := \"<bar>baz</bar>\"\n\t\tif exp != got {\n\t\t\tt.Fatalf(\"Expected value %q but got %q\", exp, got)\n\t\t}\n\t})\n\n\tt.Run(\"empty cdata tag\", func(t *testing.T) {\n\t\tr, err := xml.XML.NewReader(parsing.DefaultReaderOptions())\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t}\n\n\t\tdata, err := r.Read([]byte(`<foo>\n\t<![CDATA[]]>\n</foo>\n`))\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t}\n\n\t\tx, err := data.GetMapKey(\"foo\")\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t}\n\t\tgot, err := x.StringValue()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t}\n\n\t\texp := \"\"\n\t\tif exp != got {\n\t\t\tt.Fatalf(\"Expected value %q but got %q\", exp, got)\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "parsing/xml/structured_comment_test.go",
    "content": "package xml_test\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/parsing\"\n\tdaselxml \"github.com/tomwright/dasel/v3/parsing/xml\"\n)\n\n// newTestReaderWriter creates a reader and writer for round-trip testing.\nfunc newTestReaderWriter(t *testing.T) (parsing.Reader, parsing.Writer) {\n\tt.Helper()\n\tr, err := daselxml.XML.NewReader(parsing.DefaultReaderOptions())\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error creating reader: %s\", err)\n\t}\n\tw, err := daselxml.XML.NewWriter(parsing.DefaultWriterOptions())\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error creating writer: %s\", err)\n\t}\n\treturn r, w\n}\n\n// assertRoundTrip performs a read-write round-trip and verifies expected strings are present.\nfunc assertRoundTrip(t *testing.T, r parsing.Reader, w parsing.Writer, input string, expected []string) {\n\tt.Helper()\n\tdata, err := r.Read([]byte(input))\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error reading XML: %s\", err)\n\t}\n\n\toutput, err := w.Write(data)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error writing XML: %s\", err)\n\t}\n\n\toutputStr := string(output)\n\tfor _, exp := range expected {\n\t\tif !strings.Contains(outputStr, exp) {\n\t\t\tt.Errorf(\"Expected output to contain %q, got:\\n%s\", exp, outputStr)\n\t\t}\n\t}\n}\n\n// TestXmlReader_StructuredModeWithComments tests comment preservation in structured mode\nfunc TestXmlReader_StructuredModeWithComments(t *testing.T) {\n\tt.Run(\"basic_comment_structured\", func(t *testing.T) {\n\t\toptions := parsing.DefaultReaderOptions()\n\t\toptions.Ext = map[string]string{\"xml-mode\": \"structured\"}\n\t\tr, err := daselxml.XML.NewReader(options)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t}\n\n\t\tinput := `<!--comment--><root><child>text</child></root>`\n\t\tdata, err := r.Read([]byte(input))\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t}\n\n\t\tchildrenNode, err := data.GetMapKey(\"children\")\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Expected 'children' key in structured mode: %s\", err)\n\t\t}\n\n\t\tchildrenLen, err := childrenNode.SliceLen()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Expected children to be a slice: %s\", err)\n\t\t}\n\n\t\tif childrenLen == 0 {\n\t\t\tt.Fatalf(\"Expected at least one child element\")\n\t\t}\n\n\t\trootElement, err := childrenNode.GetSliceIndex(0)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Expected to get first child: %s\", err)\n\t\t}\n\n\t\tcomments, ok := rootElement.MetadataValue(\"xml_comments\")\n\t\tif !ok {\n\t\t\tt.Errorf(\"Expected xml_comments metadata to exist\")\n\t\t}\n\n\t\tif comments == nil {\n\t\t\tt.Errorf(\"Expected comments to be preserved in structured mode\")\n\t\t}\n\t})\n}\n\n// TestXmlRoundTrip_CommentPreservation tests round-trip preservation of XML comments\nfunc TestXmlRoundTrip_CommentPreservation(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tinput    string\n\t\texpected []string\n\t}{\n\t\t{\n\t\t\tname: \"single comment\",\n\t\t\tinput: `<!--This is a comment-->\n<root>\n  <child>text</child>\n</root>\n`,\n\t\t\texpected: []string{\"<!--This is a comment-->\", \"<root>\", \"<child>text</child>\"},\n\t\t},\n\t\t{\n\t\t\tname: \"multiple comments before root\",\n\t\t\tinput: `<!--First comment-->\n<!--Second comment-->\n<root>\n  <child>text</child>\n</root>\n`,\n\t\t\texpected: []string{\"<!--First comment-->\", \"<!--Second comment-->\"},\n\t\t},\n\t\t{\n\t\t\tname:     \"comment before complex child element\",\n\t\t\tinput:    `<root><!--Section comment--><section><item>text</item></section></root>`,\n\t\t\texpected: []string{\"<!--Section comment-->\", \"<section>\"},\n\t\t},\n\t\t{\n\t\t\tname: \"processing instruction and comments\",\n\t\t\tinput: `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--Document comment-->\n<root>\n  <child>text</child>\n</root>\n`,\n\t\t\texpected: []string{`<?xml version=\"1.0\" encoding=\"UTF-8\"?>`, \"<!--Document comment-->\", \"<root>\"},\n\t\t},\n\t\t{\n\t\t\tname: \"nested elements and comments\",\n\t\t\tinput: `<!--Root level comment-->\n<Document>\n  <!--Section comment-->\n  <Section>\n    <Item>value1</Item>\n    <Item>value2</Item>\n  </Section>\n</Document>\n`,\n\t\t\texpected: []string{\"<!--Root level comment-->\", \"<!--Section comment-->\", \"<Document>\", \"<Section>\"},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tr, w := newTestReaderWriter(t)\n\t\t\tassertRoundTrip(t, r, w, tt.input, tt.expected)\n\t\t})\n\t}\n}\n\n// TestXmlRoundTrip_EdgeCases tests edge cases for comment handling\nfunc TestXmlRoundTrip_EdgeCases(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tinput    string\n\t\texpected []string\n\t}{\n\t\t{\n\t\t\tname:     \"empty comment\",\n\t\t\tinput:    `<!----><root><child>text</child></root>`,\n\t\t\texpected: []string{\"<!---->\", \"<root>\"},\n\t\t},\n\t\t{\n\t\t\tname:     \"whitespace-only comment\",\n\t\t\tinput:    `<!--   --><root><child>text</child></root>`,\n\t\t\texpected: []string{\"<!--   -->\"},\n\t\t},\n\t\t{\n\t\t\tname:     \"comment between sibling elements\",\n\t\t\tinput:    `<root><first>one</first><!--between siblings--><second>two</second></root>`,\n\t\t\texpected: []string{\"<!--between siblings-->\", \"<first>one</first>\", \"<second>two</second>\"},\n\t\t},\n\t\t{\n\t\t\tname:     \"multiple comments between siblings\",\n\t\t\tinput:    `<root><a>1</a><!--comment1--><!--comment2--><b>2</b></root>`,\n\t\t\texpected: []string{\"<!--comment1-->\", \"<!--comment2-->\"},\n\t\t},\n\t\t{\n\t\t\tname:     \"trailing comment after last child\",\n\t\t\tinput:    `<root><child>text</child><!--trailing comment--></root>`,\n\t\t\texpected: []string{\"<!--trailing comment-->\", \"<child>text</child>\"},\n\t\t},\n\t\t{\n\t\t\tname:     \"multiple trailing comments\",\n\t\t\tinput:    `<root><child>text</child><!--trailing1--><!--trailing2--></root>`,\n\t\t\texpected: []string{\"<!--trailing1-->\", \"<!--trailing2-->\"},\n\t\t},\n\t\t{\n\t\t\tname:     \"trailing comment in nested structure\",\n\t\t\tinput:    `<root><parent><child>text</child><!--inner trailing--></parent><!--outer trailing--></root>`,\n\t\t\texpected: []string{\"<!--inner trailing-->\", \"<!--outer trailing-->\"},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tr, w := newTestReaderWriter(t)\n\t\t\tassertRoundTrip(t, r, w, tt.input, tt.expected)\n\t\t})\n\t}\n}\n\n// TestXmlRoundTrip_SpecialCommentContent tests comments with special but valid content\nfunc TestXmlRoundTrip_SpecialCommentContent(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tinput    string\n\t\texpected []string\n\t}{\n\t\t{\n\t\t\tname:     \"special characters\",\n\t\t\tinput:    `<!--Comment with <special> & \"characters\" 'here'--><root><child>text</child></root>`,\n\t\t\texpected: []string{\"<!--Comment with <special>\"},\n\t\t},\n\t\t{\n\t\t\tname:     \"single dash\",\n\t\t\tinput:    `<!--Comment with a-single-dash--><root><child>text</child></root>`,\n\t\t\texpected: []string{\"<!--Comment with a-single-dash-->\"},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tr, w := newTestReaderWriter(t)\n\t\t\tassertRoundTrip(t, r, w, tt.input, tt.expected)\n\t\t})\n\t}\n}\n\n// TestXmlReader_SecurityLimits tests error handling for security limits\nfunc TestXmlReader_SecurityLimits(t *testing.T) {\n\tt.Run(\"reject oversized XML input\", func(t *testing.T) {\n\t\tr, err := daselxml.XML.NewReader(parsing.DefaultReaderOptions())\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error creating reader: %s\", err)\n\t\t}\n\n\t\tlargeContent := strings.Repeat(\"x\", 10_000_001)\n\t\tinput := \"<root>\" + largeContent + \"</root>\"\n\n\t\t_, err = r.Read([]byte(input))\n\t\tif err == nil {\n\t\t\tt.Errorf(\"Expected error for oversized XML input\")\n\t\t}\n\t\tif !strings.Contains(err.Error(), \"exceeds maximum size\") {\n\t\t\tt.Errorf(\"Expected error about maximum size, got: %s\", err)\n\t\t}\n\t})\n\n\tt.Run(\"reject oversized comment\", func(t *testing.T) {\n\t\tr, err := daselxml.XML.NewReader(parsing.DefaultReaderOptions())\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error creating reader: %s\", err)\n\t\t}\n\n\t\tlargeComment := strings.Repeat(\"x\", 10_001)\n\t\tinput := \"<!--\" + largeComment + \"--><root><child>text</child></root>\"\n\n\t\t_, err = r.Read([]byte(input))\n\t\tif err == nil {\n\t\t\tt.Errorf(\"Expected error for oversized comment\")\n\t\t}\n\t\tif !strings.Contains(err.Error(), \"exceeds maximum length\") {\n\t\t\tt.Errorf(\"Expected error about maximum length, got: %s\", err)\n\t\t}\n\t})\n\n\tt.Run(\"reject document with too many comments\", func(t *testing.T) {\n\t\tr, err := daselxml.XML.NewReader(parsing.DefaultReaderOptions())\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error creating reader: %s\", err)\n\t\t}\n\n\t\tvar builder strings.Builder\n\t\tbuilder.WriteString(\"<root>\")\n\t\tfor i := 0; i < 1001; i++ {\n\t\t\tbuilder.WriteString(\"<!--comment-->\")\n\t\t}\n\t\tbuilder.WriteString(\"<child>text</child></root>\")\n\t\tinput := builder.String()\n\n\t\t_, err = r.Read([]byte(input))\n\t\tif err == nil {\n\t\t\tt.Errorf(\"Expected error for too many comments\")\n\t\t}\n\t\tif !strings.Contains(err.Error(), \"exceeds maximum comment count\") {\n\t\t\tt.Errorf(\"Expected error about maximum comment count, got: %s\", err)\n\t\t}\n\t})\n}\n\n// TestXmlRoundTrip_ProcessingInstructionReset tests that processing instructions are not duplicated across siblings\nfunc TestXmlRoundTrip_ProcessingInstructionReset(t *testing.T) {\n\tt.Run(\"processing instructions not duplicated to siblings\", func(t *testing.T) {\n\t\tr, w := newTestReaderWriter(t)\n\n\t\tinput := `<?xml version=\"1.0\"?><root><first>one</first><second>two</second></root>`\n\n\t\tdata, err := r.Read([]byte(input))\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error reading XML: %s\", err)\n\t\t}\n\n\t\toutput, err := w.Write(data)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error writing XML: %s\", err)\n\t\t}\n\n\t\toutputStr := string(output)\n\n\t\tpiCount := strings.Count(outputStr, `<?xml version=\"1.0\"?>`)\n\t\tif piCount != 1 {\n\t\t\tt.Errorf(\"Expected PI to appear exactly once, but found %d occurrences in:\\n%s\", piCount, outputStr)\n\t\t}\n\n\t\tif !strings.Contains(outputStr, \"<first>one</first>\") {\n\t\t\tt.Errorf(\"Expected first element to be preserved in:\\n%s\", outputStr)\n\t\t}\n\t\tif !strings.Contains(outputStr, \"<second>two</second>\") {\n\t\t\tt.Errorf(\"Expected second element to be preserved in:\\n%s\", outputStr)\n\t\t}\n\t})\n\n\tt.Run(\"sibling elements do not inherit each others processing instructions\", func(t *testing.T) {\n\t\toptions := parsing.DefaultReaderOptions()\n\t\toptions.Ext = map[string]string{\"xml-mode\": \"structured\"}\n\t\tr, err := daselxml.XML.NewReader(options)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error creating reader: %s\", err)\n\t\t}\n\n\t\tinput := `<root><?target instruction?><first>one</first><second>two</second></root>`\n\n\t\tdata, err := r.Read([]byte(input))\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error reading XML: %s\", err)\n\t\t}\n\n\t\tchildren, err := data.GetMapKey(\"children\")\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Expected children key: %s\", err)\n\t\t}\n\n\t\tchildLen, _ := children.SliceLen()\n\t\tif childLen < 1 {\n\t\t\tt.Fatalf(\"Expected at least one child\")\n\t\t}\n\n\t\trootEl, err := children.GetSliceIndex(0)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Expected to get root element: %s\", err)\n\t\t}\n\n\t\trootChildren, err := rootEl.GetMapKey(\"children\")\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Expected root children: %s\", err)\n\t\t}\n\n\t\trootChildLen, _ := rootChildren.SliceLen()\n\t\tif rootChildLen < 2 {\n\t\t\tt.Fatalf(\"Expected at least two children in root, got %d\", rootChildLen)\n\t\t}\n\n\t\tfirstChild, _ := rootChildren.GetSliceIndex(0)\n\t\t_, hasPI := firstChild.MetadataValue(\"xml_processing_instructions\")\n\t\tif !hasPI {\n\t\t\tt.Log(\"First child does not have PI (which is expected behavior after fix)\")\n\t\t}\n\n\t\tsecondChild, _ := rootChildren.GetSliceIndex(1)\n\t\t_, secondHasPI := secondChild.MetadataValue(\"xml_processing_instructions\")\n\t\tif secondHasPI {\n\t\t\tt.Errorf(\"Second child should NOT have processing instructions (PI was incorrectly duplicated)\")\n\t\t}\n\t})\n}\n\n// TestXmlRoundTrip_ProcessingInstructions tests round-trip preservation of processing instructions\nfunc TestXmlRoundTrip_ProcessingInstructions(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tinput    string\n\t\texpected []string\n\t}{\n\t\t{\n\t\t\tname: \"xml declaration\",\n\t\t\tinput: `<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>\n<root>\n  <child>text</child>\n</root>\n`,\n\t\t\texpected: []string{`<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>`},\n\t\t},\n\t\t{\n\t\t\tname: \"multiple processing instructions\",\n\t\t\tinput: `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<?xml-stylesheet type=\"text/xsl\" href=\"style.xsl\"?>\n<root>\n  <child>text</child>\n</root>\n`,\n\t\t\texpected: []string{`<?xml version=\"1.0\" encoding=\"UTF-8\"?>`, `<?xml-stylesheet type=\"text/xsl\" href=\"style.xsl\"?>`},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tr, w := newTestReaderWriter(t)\n\t\t\tassertRoundTrip(t, r, w, tt.input, tt.expected)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "parsing/xml/writer.go",
    "content": "package xml\n\nimport (\n\t\"bytes\"\n\t\"encoding/xml\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"github.com/tomwright/dasel/v3/parsing\"\n)\n\nfunc newXMLWriter(options parsing.WriterOptions) (parsing.Writer, error) {\n\treturn &xmlWriter{\n\t\toptions: options,\n\t}, nil\n}\n\ntype xmlWriter struct {\n\toptions parsing.WriterOptions\n}\n\n// Write writes a value to a byte slice.\nfunc (j *xmlWriter) Write(value *model.Value) ([]byte, error) {\n\tbuf := new(bytes.Buffer)\n\twriter := xml.NewEncoder(buf)\n\tdefer func() {\n\t\t_ = writer.Close()\n\t}()\n\twriter.Indent(\"\", \"  \")\n\n\telement, err := j.toElement(\"root\", value)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to convert to element: %w\", err)\n\t}\n\tfor _, c := range element.Children {\n\t\tif err := writer.Encode(c); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif err := writer.EncodeToken(xml.CharData(\"\\n\")); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tif err := writer.Flush(); err != nil {\n\t\treturn nil, err\n\t}\n\toutBytes := buf.Bytes()\n\tif !bytes.HasSuffix(outBytes, []byte(\"\\n\")) {\n\t\toutBytes = append(outBytes, '\\n')\n\t}\n\treturn outBytes, nil\n}\n\nfunc (j *xmlWriter) toElement(key string, value *model.Value) (*xmlElement, error) {\n\treadProcessingInstructions := func() []*xmlProcessingInstruction {\n\t\tif piMeta, ok := value.MetadataValue(\"xml_processing_instructions\"); ok && piMeta != nil {\n\t\t\tif pis, ok := piMeta.([]*xmlProcessingInstruction); ok {\n\t\t\t\treturn pis\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\treadComments := func() []*xmlComment {\n\t\tif commentMeta, ok := value.MetadataValue(\"xml_comments\"); ok && commentMeta != nil {\n\t\t\tif comments, ok := commentMeta.([]*xmlComment); ok {\n\t\t\t\treturn comments\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\tswitch value.Type() {\n\n\tcase model.TypeString:\n\t\tstrVal, err := valueToString(value)\n\t\treturn &xmlElement{\n\t\t\tName:                   key,\n\t\t\tContent:                strVal,\n\t\t\tProcessingInstructions: readProcessingInstructions(),\n\t\t\tComments:               readComments(),\n\t\t}, err\n\n\tcase model.TypeMap:\n\t\tkvs, err := value.MapKeyValues()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tel := &xmlElement{\n\t\t\tName:                   key,\n\t\t\tProcessingInstructions: readProcessingInstructions(),\n\t\t\tComments:               readComments(),\n\t\t}\n\n\t\tfor _, kv := range kvs {\n\t\t\tif strings.HasPrefix(kv.Key, \"-\") {\n\t\t\t\tattr := xmlAttr{\n\t\t\t\t\tName: kv.Key[1:],\n\t\t\t\t}\n\t\t\t\tattr.Value, err = valueToString(kv.Value)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, fmt.Errorf(\"failed to convert attribute %q to string: %w\", attr.Name, err)\n\t\t\t\t}\n\t\t\t\tel.Attrs = append(el.Attrs, attr)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif kv.Key == \"#text\" {\n\t\t\t\tel.Content, err = valueToString(kv.Value)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, fmt.Errorf(\"failed to convert content to string: %w\", err)\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tchildEl, err := j.toElement(kv.Key, kv.Value)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"failed to convert child element %q to element: %w\", kv.Key, err)\n\t\t\t}\n\t\t\tif childEl.useChildrenOnly {\n\t\t\t\tel.Children = append(el.Children, childEl.Children...)\n\t\t\t} else {\n\t\t\t\tel.Children = append(el.Children, childEl)\n\t\t\t}\n\t\t}\n\n\t\treturn el, nil\n\tcase model.TypeSlice:\n\t\tel := &xmlElement{\n\t\t\tName:                   \"root\",\n\t\t\tProcessingInstructions: readProcessingInstructions(),\n\t\t\tComments:               readComments(),\n\t\t\tuseChildrenOnly:        true,\n\t\t}\n\t\tif err := value.RangeSlice(func(i int, value *model.Value) error {\n\t\t\tchildEl, err := j.toElement(key, value)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif childEl.useChildrenOnly {\n\t\t\t\tel.Children = append(el.Children, childEl.Children...)\n\t\t\t} else {\n\t\t\t\tel.Children = append(el.Children, childEl)\n\t\t\t}\n\n\t\t\treturn nil\n\t\t}); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn el, nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"xml writer does not support value type: %s\", value.Type())\n\t}\n}\n\nfunc valueToString(v *model.Value) (string, error) {\n\tif v.IsNull() {\n\t\treturn \"\", nil\n\t}\n\n\tswitch v.Type() {\n\tcase model.TypeString:\n\t\tstringValue, err := v.StringValue()\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\treturn stringValue, nil\n\tcase model.TypeInt:\n\t\ti, err := v.IntValue()\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\treturn fmt.Sprintf(\"%d\", i), nil\n\tcase model.TypeFloat:\n\t\ti, err := v.FloatValue()\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\treturn fmt.Sprintf(\"%g\", i), nil\n\tcase model.TypeBool:\n\t\ti, err := v.BoolValue()\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\treturn fmt.Sprintf(\"%t\", i), nil\n\tdefault:\n\t\treturn \"\", fmt.Errorf(\"xml writer cannot format type %s to string\", v.Type())\n\t}\n}\n\n// indentString returns the indentation for a given depth level.\nfunc indentString(depth int) string {\n\treturn strings.Repeat(\"  \", depth)\n}\n\nfunc (e *xmlElement) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {\n\t// Write processing instructions before the element (document-level)\n\tif len(e.ProcessingInstructions) > 0 {\n\t\tfor _, pi := range e.ProcessingInstructions {\n\t\t\tif err := enc.EncodeToken(xml.ProcInst{\n\t\t\t\tTarget: pi.Target,\n\t\t\t\tInst:   []byte(pi.Value),\n\t\t\t}); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := enc.EncodeToken(xml.CharData(\"\\n\")); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\t// Write comments before the element (document-level comments only)\n\t// Child-level comments are written by the parent inside its body\n\tif e.depth == 0 && len(e.Comments) > 0 {\n\t\tfor _, comment := range e.Comments {\n\t\t\tif strings.Contains(comment.Text, \"--\") {\n\t\t\t\treturn fmt.Errorf(\"comment text cannot contain '--' sequence (invalid XML comment)\")\n\t\t\t}\n\t\t\tif err := enc.EncodeToken(xml.Comment(comment.Text)); err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to encode comment: %w\", err)\n\t\t\t}\n\t\t\tif err := enc.EncodeToken(xml.CharData(\"\\n\")); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\tstart.Name = xml.Name{Local: e.Name}\n\n\tif len(e.Attrs) > 0 {\n\t\tfor _, attr := range e.Attrs {\n\t\t\tstart.Attr = append(start.Attr, xml.Attr{\n\t\t\t\tName:  xml.Name{Local: attr.Name},\n\t\t\t\tValue: attr.Value,\n\t\t\t})\n\t\t}\n\t}\n\n\tif err := enc.EncodeToken(start); err != nil {\n\t\treturn err\n\t}\n\n\t// TODO : Handle CDATA sections on write.\n\n\tif len(e.Content) > 0 {\n\t\tif err := enc.EncodeToken(xml.CharData(e.Content)); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// Write children with their preceding comments\n\tchildDepth := e.depth + 1\n\tfor _, child := range e.Children {\n\t\t// Write child's comments inside parent, before the child element\n\t\tif len(child.Comments) > 0 {\n\t\t\tfor _, comment := range child.Comments {\n\t\t\t\tif strings.Contains(comment.Text, \"--\") {\n\t\t\t\t\treturn fmt.Errorf(\"comment text cannot contain '--' sequence (invalid XML comment)\")\n\t\t\t\t}\n\t\t\t\t// Add newline + indentation before comment\n\t\t\t\tif err := enc.EncodeToken(xml.CharData(\"\\n\" + indentString(childDepth))); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tif err := enc.EncodeToken(xml.Comment(comment.Text)); err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"failed to encode comment: %w\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Clear comments so child doesn't write them again\n\t\t\tchild.Comments = nil\n\t\t}\n\t\t// Set child depth for recursive calls\n\t\tchild.depth = childDepth\n\t\tif err := enc.Encode(child); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn enc.EncodeToken(start.End())\n}\n"
  },
  {
    "path": "parsing/xml/writer_internal_test.go",
    "content": "package xml\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"github.com/tomwright/dasel/v3/parsing\"\n)\n\n// TestXmlWriter_CommentValidation tests validation of comment content during write\nfunc TestXmlWriter_CommentValidation(t *testing.T) {\n\tt.Run(\"reject comment containing double dash sequence\", func(t *testing.T) {\n\t\tw, err := newXMLWriter(parsing.DefaultWriterOptions())\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error creating writer: %s\", err)\n\t\t}\n\n\t\t// Create a child value with an invalid comment containing -- sequence\n\t\t// The comments are attached to child elements, not the root\n\t\tchildValue := model.NewMapValue()\n\t\tchildValue.SetMetadataValue(\"xml_comments\", []*xmlComment{{Text: \"invalid--comment\"}})\n\t\tif err := childValue.SetMapKey(\"item\", model.NewStringValue(\"text\")); err != nil {\n\t\t\tt.Fatalf(\"Unexpected error setting map key: %s\", err)\n\t\t}\n\n\t\t// Create root value containing the child\n\t\trootValue := model.NewMapValue()\n\t\tif err := rootValue.SetMapKey(\"child\", childValue); err != nil {\n\t\t\tt.Fatalf(\"Unexpected error setting map key: %s\", err)\n\t\t}\n\n\t\t_, err = w.Write(rootValue)\n\t\tif err == nil {\n\t\t\tt.Errorf(\"Expected error for comment containing double dash\")\n\t\t}\n\t\tif err != nil && !strings.Contains(err.Error(), \"cannot contain '--'\") {\n\t\t\tt.Errorf(\"Expected error about double dash sequence, got: %s\", err)\n\t\t}\n\t})\n\n\tt.Run(\"accept valid comment without double dash\", func(t *testing.T) {\n\t\tw, err := newXMLWriter(parsing.DefaultWriterOptions())\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error creating writer: %s\", err)\n\t\t}\n\n\t\t// Create a child value with a valid comment\n\t\tchildValue := model.NewMapValue()\n\t\tchildValue.SetMetadataValue(\"xml_comments\", []*xmlComment{{Text: \"valid comment with single - dash\"}})\n\t\tif err := childValue.SetMapKey(\"item\", model.NewStringValue(\"text\")); err != nil {\n\t\t\tt.Fatalf(\"Unexpected error setting map key: %s\", err)\n\t\t}\n\n\t\t// Create root value containing the child\n\t\trootValue := model.NewMapValue()\n\t\tif err := rootValue.SetMapKey(\"child\", childValue); err != nil {\n\t\t\tt.Fatalf(\"Unexpected error setting map key: %s\", err)\n\t\t}\n\n\t\toutput, err := w.Write(rootValue)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error writing XML: %s\", err)\n\t\t}\n\n\t\toutputStr := string(output)\n\t\tif !strings.Contains(outputStr, \"<!--valid comment with single - dash-->\") {\n\t\t\tt.Errorf(\"Expected output to contain valid comment, got:\\n%s\", outputStr)\n\t\t}\n\t})\n}\n\n// Test_valueToString tests the valueToString function for all supported types\nfunc Test_valueToString(t *testing.T) {\n\tt.Run(\"null value returns empty string\", func(t *testing.T) {\n\t\tv := model.NewNullValue()\n\t\tresult, err := valueToString(v)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"Unexpected error for null value: %s\", err)\n\t\t}\n\t\tif result != \"\" {\n\t\t\tt.Errorf(\"Expected empty string for null value, got: %q\", result)\n\t\t}\n\t})\n\n\tt.Run(\"string value\", func(t *testing.T) {\n\t\tv := model.NewStringValue(\"hello world\")\n\t\tresult, err := valueToString(v)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"Unexpected error for string value: %s\", err)\n\t\t}\n\t\tif result != \"hello world\" {\n\t\t\tt.Errorf(\"Expected 'hello world', got: %q\", result)\n\t\t}\n\t})\n\n\tt.Run(\"empty string value\", func(t *testing.T) {\n\t\tv := model.NewStringValue(\"\")\n\t\tresult, err := valueToString(v)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"Unexpected error for empty string value: %s\", err)\n\t\t}\n\t\tif result != \"\" {\n\t\t\tt.Errorf(\"Expected empty string, got: %q\", result)\n\t\t}\n\t})\n\n\tt.Run(\"int value positive\", func(t *testing.T) {\n\t\tv := model.NewIntValue(42)\n\t\tresult, err := valueToString(v)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"Unexpected error for int value: %s\", err)\n\t\t}\n\t\tif result != \"42\" {\n\t\t\tt.Errorf(\"Expected '42', got: %q\", result)\n\t\t}\n\t})\n\n\tt.Run(\"int value negative\", func(t *testing.T) {\n\t\tv := model.NewIntValue(-123)\n\t\tresult, err := valueToString(v)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"Unexpected error for negative int value: %s\", err)\n\t\t}\n\t\tif result != \"-123\" {\n\t\t\tt.Errorf(\"Expected '-123', got: %q\", result)\n\t\t}\n\t})\n\n\tt.Run(\"int value zero\", func(t *testing.T) {\n\t\tv := model.NewIntValue(0)\n\t\tresult, err := valueToString(v)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"Unexpected error for zero int value: %s\", err)\n\t\t}\n\t\tif result != \"0\" {\n\t\t\tt.Errorf(\"Expected '0', got: %q\", result)\n\t\t}\n\t})\n\n\tt.Run(\"float value\", func(t *testing.T) {\n\t\tv := model.NewFloatValue(3.14159)\n\t\tresult, err := valueToString(v)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"Unexpected error for float value: %s\", err)\n\t\t}\n\t\tif result != \"3.14159\" {\n\t\t\tt.Errorf(\"Expected '3.14159', got: %q\", result)\n\t\t}\n\t})\n\n\tt.Run(\"float value negative\", func(t *testing.T) {\n\t\tv := model.NewFloatValue(-2.5)\n\t\tresult, err := valueToString(v)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"Unexpected error for negative float value: %s\", err)\n\t\t}\n\t\tif result != \"-2.5\" {\n\t\t\tt.Errorf(\"Expected '-2.5', got: %q\", result)\n\t\t}\n\t})\n\n\tt.Run(\"float value zero\", func(t *testing.T) {\n\t\tv := model.NewFloatValue(0.0)\n\t\tresult, err := valueToString(v)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"Unexpected error for zero float value: %s\", err)\n\t\t}\n\t\tif result != \"0\" {\n\t\t\tt.Errorf(\"Expected '0', got: %q\", result)\n\t\t}\n\t})\n\n\tt.Run(\"bool value true\", func(t *testing.T) {\n\t\tv := model.NewBoolValue(true)\n\t\tresult, err := valueToString(v)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"Unexpected error for bool true value: %s\", err)\n\t\t}\n\t\tif result != \"true\" {\n\t\t\tt.Errorf(\"Expected 'true', got: %q\", result)\n\t\t}\n\t})\n\n\tt.Run(\"bool value false\", func(t *testing.T) {\n\t\tv := model.NewBoolValue(false)\n\t\tresult, err := valueToString(v)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"Unexpected error for bool false value: %s\", err)\n\t\t}\n\t\tif result != \"false\" {\n\t\t\tt.Errorf(\"Expected 'false', got: %q\", result)\n\t\t}\n\t})\n\n\tt.Run(\"map value returns error\", func(t *testing.T) {\n\t\tv := model.NewMapValue()\n\t\t_, err := valueToString(v)\n\t\tif err == nil {\n\t\t\tt.Errorf(\"Expected error for map value\")\n\t\t}\n\t\tif err != nil && !strings.Contains(err.Error(), \"cannot format type\") {\n\t\t\tt.Errorf(\"Expected error about formatting type, got: %s\", err)\n\t\t}\n\t})\n\n\tt.Run(\"slice value returns error\", func(t *testing.T) {\n\t\tv := model.NewSliceValue()\n\t\t_, err := valueToString(v)\n\t\tif err == nil {\n\t\t\tt.Errorf(\"Expected error for slice value\")\n\t\t}\n\t\tif err != nil && !strings.Contains(err.Error(), \"cannot format type\") {\n\t\t\tt.Errorf(\"Expected error about formatting type, got: %s\", err)\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "parsing/xml/writer_test.go",
    "content": "package xml_test\n\nimport (\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/parsing\"\n\t\"github.com/tomwright/dasel/v3/parsing/xml\"\n)\n\nfunc TestXmlReader_Write(t *testing.T) {\n\tt.Run(\"nested xml elements\", func(t *testing.T) {\n\t\tr, err := xml.XML.NewReader(parsing.DefaultReaderOptions())\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t}\n\n\t\tw, err := xml.XML.NewWriter(parsing.DefaultWriterOptions())\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t}\n\n\t\tdata, err := r.Read([]byte(`<Document>\n  <Sender>Ivanov</Sender>\n  <In_N_Document>\n    <N_Document>1024</N_Document>\n    <Date_Reg>2024-06-21T15:07:29.0451517+03:00</Date_Reg>\n  </In_N_Document>\n  <Out_N_Document>\n    <N_Document>2043</N_Document>\n    <Date_Reg>2024-05-01T00:00:00</Date_Reg>\n  </Out_N_Document>\n  <Content>Skzzkz</Content>\n  <DSP>true</DSP>\n</Document>\n`))\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t}\n\n\t\txmlBytes, err := w.Write(data)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t}\n\n\t\texpected := `<Document>\n  <Sender>Ivanov</Sender>\n  <In_N_Document>\n    <N_Document>1024</N_Document>\n    <Date_Reg>2024-06-21T15:07:29.0451517+03:00</Date_Reg>\n  </In_N_Document>\n  <Out_N_Document>\n    <N_Document>2043</N_Document>\n    <Date_Reg>2024-05-01T00:00:00</Date_Reg>\n  </Out_N_Document>\n  <Content>Skzzkz</Content>\n  <DSP>true</DSP>\n</Document>\n`\n\t\tif string(xmlBytes) != expected {\n\t\t\tt.Fatalf(\"Expected:\\n%s\\nGot:\\n%s\", expected, string(xmlBytes))\n\t\t}\n\t})\n\n\tt.Run(\"nested xml elements with processing instruction\", func(t *testing.T) {\n\t\tr, err := xml.XML.NewReader(parsing.DefaultReaderOptions())\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t}\n\n\t\tw, err := xml.XML.NewWriter(parsing.DefaultWriterOptions())\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t}\n\n\t\tdata, err := r.Read([]byte(`<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>\n<Document>\n  <Sender>Ivanov</Sender>\n  <In_N_Document>\n    <N_Document>1024</N_Document>\n    <Date_Reg>2024-06-21T15:07:29.0451517+03:00</Date_Reg>\n  </In_N_Document>\n  <Out_N_Document>\n    <N_Document>2043</N_Document>\n    <Date_Reg>2024-05-01T00:00:00</Date_Reg>\n  </Out_N_Document>\n  <Content>Skzzkz</Content>\n  <DSP>true</DSP>\n</Document>\n`))\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t}\n\n\t\tdoc, err := data.GetMapKey(\"Document\")\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t}\n\t\tdocProcessingInstructions, ok := doc.MetadataValue(\"xml_processing_instructions\")\n\t\tif !ok || docProcessingInstructions == nil {\n\t\t\tt.Fatalf(\"Expected processing instructions on Document element\")\n\t\t}\n\n\t\tjsonBytes, err := w.Write(data)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t}\n\n\t\texpected := `<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>\n<Document>\n  <Sender>Ivanov</Sender>\n  <In_N_Document>\n    <N_Document>1024</N_Document>\n    <Date_Reg>2024-06-21T15:07:29.0451517+03:00</Date_Reg>\n  </In_N_Document>\n  <Out_N_Document>\n    <N_Document>2043</N_Document>\n    <Date_Reg>2024-05-01T00:00:00</Date_Reg>\n  </Out_N_Document>\n  <Content>Skzzkz</Content>\n  <DSP>true</DSP>\n</Document>\n`\n\t\tif string(jsonBytes) != expected {\n\t\t\tt.Fatalf(\"Expected:\\n%s\\nGot:\\n%s\", expected, string(jsonBytes))\n\t\t}\n\t})\n\n\tt.Run(\"encode attributes\", func(t *testing.T) {\n\t\tw, err := xml.XML.NewWriter(parsing.DefaultWriterOptions())\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t}\n\n\t\ttoEncode := model.NewMapValue()\n\t\tfoo := model.NewMapValue()\n\t\t_ = foo.SetMapKey(\"-fiz\", model.NewStringValue(\"hello\"))\n\t\t_ = foo.SetMapKey(\"bar\", model.NewStringValue(\"\"))\n\t\t_ = toEncode.SetMapKey(\"foo\", foo)\n\n\t\tgot, err := w.Write(toEncode)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t}\n\t\texp := []byte(`<foo fiz=\"hello\">\n  <bar></bar>\n</foo>\n`)\n\t\tif string(got) != string(exp) {\n\t\t\tt.Errorf(\"Expected:\\n%s\\nGot:\\n%s\", string(exp), string(got))\n\t\t}\n\t})\n\n\tt.Run(\"encode cdata\", func(t *testing.T) {\n\t\tw, err := xml.XML.NewWriter(parsing.DefaultWriterOptions())\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t}\n\t\ttoEncode := model.NewMapValue()\n\t\t_ = toEncode.SetMapKey(\"foo\", model.NewStringValue(\"<bar>baz</bar>\"))\n\t\tgot, err := w.Write(toEncode)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t}\n\t\t// TODO : Change this to use CDATA sections rather than escaping.\n\t\texp := []byte(`<foo>&lt;bar&gt;baz&lt;/bar&gt;</foo>\n`)\n\t\tif string(got) != string(exp) {\n\t\t\tt.Errorf(\"Expected:\\n%s\\nGot:\\n%s\", string(exp), string(got))\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "parsing/xml/xml.go",
    "content": "package xml\n\nimport (\n\t\"github.com/tomwright/dasel/v3/parsing\"\n)\n\nconst (\n\t// XML represents the XML file format.\n\tXML parsing.Format = \"xml\"\n)\n\nvar _ parsing.Reader = (*xmlReader)(nil)\nvar _ parsing.Writer = (*xmlWriter)(nil)\n\nfunc init() {\n\tparsing.RegisterReader(XML, newXMLReader)\n\tparsing.RegisterWriter(XML, newXMLWriter)\n}\n\ntype xmlAttr struct {\n\tName  string\n\tValue string\n}\n\ntype xmlProcessingInstruction struct {\n\tTarget string\n\tValue  string\n}\n\ntype xmlComment struct {\n\tText string\n}\n\ntype xmlElement struct {\n\tName                   string\n\tAttrs                  []xmlAttr\n\tChildren               []*xmlElement\n\tContent                string\n\tProcessingInstructions []*xmlProcessingInstruction\n\tComments               []*xmlComment\n\tuseChildrenOnly        bool\n\tdepth                  int // Tracks nesting depth for proper indentation\n}\n"
  },
  {
    "path": "parsing/yaml/yaml.go",
    "content": "package yaml\n\nimport (\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"github.com/tomwright/dasel/v3/parsing\"\n\t\"go.yaml.in/yaml/v4\"\n)\n\n// YAML represents the YAML file format.\nconst YAML parsing.Format = \"yaml\"\n\nfunc init() {\n\tparsing.RegisterReader(YAML, newYAMLReader)\n\tparsing.RegisterWriter(YAML, newYAMLWriter)\n}\n\ntype yamlValue struct {\n\tnode              *yaml.Node\n\tvalue             *model.Value\n\texpansionDepth    int\n\tmaxExpansionDepth int\n\texpansionBudget   *int\n}\n"
  },
  {
    "path": "parsing/yaml/yaml_reader.go",
    "content": "package yaml\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"strconv\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"github.com/tomwright/dasel/v3/parsing\"\n\t\"go.yaml.in/yaml/v4\"\n)\n\nvar _ parsing.Reader = (*yamlReader)(nil)\n\nfunc newYAMLReader(options parsing.ReaderOptions) (parsing.Reader, error) {\n\treturn &yamlReader{\n\t\tmaxExpansionDepth:  maxExpansionDepth,\n\t\tmaxExpansionBudget: maxExpansionBudget,\n\t}, nil\n}\n\ntype yamlReader struct {\n\tmaxExpansionDepth  int\n\tmaxExpansionBudget int\n}\n\n// ErrYamlExpansionDepthExceeded is returned when the maximum expansion depth is exceeded.\nvar ErrYamlExpansionDepthExceeded = errors.New(\"yaml expansion depth exceeded\")\n\n// ErrYamlExpansionBudgetExceeded is returned when the maximum expansion budget is exceeded.\nvar ErrYamlExpansionBudgetExceeded = errors.New(\"yaml expansion budget exceeded\")\n\nconst maxExpansionDepth = 32\nconst maxExpansionBudget = 1000\n\n// Read reads a value from a byte slice.\nfunc (j *yamlReader) Read(data []byte) (*model.Value, error) {\n\td := yaml.NewDecoder(bytes.NewReader(data))\n\tres := make([]*yamlValue, 0)\n\tfor {\n\t\texpansionBudget := j.maxExpansionBudget\n\t\tunmarshalled := &yamlValue{\n\t\t\texpansionDepth:    0,\n\t\t\tmaxExpansionDepth: j.maxExpansionDepth,\n\t\t\texpansionBudget:   &expansionBudget,\n\t\t}\n\t\tif err := d.Decode(&unmarshalled); err != nil {\n\t\t\tif err == io.EOF {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\treturn nil, err\n\t\t}\n\t\tif unmarshalled == nil {\n\t\t\texpansionBudget := j.maxExpansionBudget\n\t\t\tunmarshalled = &yamlValue{\n\t\t\t\tnode:              nil,\n\t\t\t\tvalue:             model.NewNullValue(),\n\t\t\t\texpansionDepth:    0,\n\t\t\t\tmaxExpansionDepth: j.maxExpansionDepth,\n\t\t\t\texpansionBudget:   &expansionBudget,\n\t\t\t}\n\t\t}\n\t\tres = append(res, unmarshalled)\n\t}\n\n\tswitch len(res) {\n\tcase 0:\n\t\treturn model.NewNullValue(), nil\n\tcase 1:\n\t\treturn res[0].value, nil\n\tdefault:\n\t\tslice := model.NewSliceValue()\n\t\tslice.MarkAsBranch()\n\t\tfor _, v := range res {\n\t\t\tif v == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif err := slice.Append(v.value); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\treturn slice, nil\n\t}\n}\n\nfunc (yv *yamlValue) UnmarshalYAML(value *yaml.Node) error {\n\tyv.node = value\n\tif yv.expansionDepth > yv.maxExpansionDepth {\n\t\treturn ErrYamlExpansionDepthExceeded\n\t}\n\tswitch value.Kind {\n\tcase yaml.ScalarNode:\n\t\tswitch value.Tag {\n\t\tcase \"!!bool\":\n\t\t\tyv.value = model.NewBoolValue(value.Value == \"true\")\n\t\tcase \"!!int\":\n\t\t\ti, err := strconv.Atoi(value.Value)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tyv.value = model.NewIntValue(int64(i))\n\t\tcase \"!!float\":\n\t\t\tf, err := strconv.ParseFloat(value.Value, 64)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tyv.value = model.NewFloatValue(f)\n\t\tcase \"!!null\":\n\t\t\tyv.value = model.NewNullValue()\n\t\tcase \"!!str\":\n\t\t\tyv.value = model.NewStringValue(value.Value)\n\t\tdefault:\n\t\t\tyv.value = model.NewStringValue(value.Value)\n\t\t}\n\tcase yaml.DocumentNode:\n\t\tyv.value = model.NewNullValue()\n\tcase yaml.SequenceNode:\n\t\tres := model.NewSliceValue()\n\t\tfor _, item := range value.Content {\n\t\t\tnewItem := &yamlValue{\n\t\t\t\texpansionDepth:    yv.expansionDepth,\n\t\t\t\tmaxExpansionDepth: yv.maxExpansionDepth,\n\t\t\t\texpansionBudget:   yv.expansionBudget,\n\t\t\t}\n\t\t\tif err := newItem.UnmarshalYAML(item); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := res.Append(newItem.value); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tyv.value = res\n\tcase yaml.MappingNode:\n\t\tres := model.NewMapValue()\n\t\tfor i := 0; i < len(value.Content); i += 2 {\n\t\t\tkey := value.Content[i]\n\t\t\tval := value.Content[i+1]\n\n\t\t\tnewKey := &yamlValue{\n\t\t\t\texpansionDepth:    yv.expansionDepth,\n\t\t\t\tmaxExpansionDepth: yv.maxExpansionDepth,\n\t\t\t\texpansionBudget:   yv.expansionBudget,\n\t\t\t}\n\t\t\tif err := newKey.UnmarshalYAML(key); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tnewVal := &yamlValue{\n\t\t\t\texpansionDepth:    yv.expansionDepth,\n\t\t\t\tmaxExpansionDepth: yv.maxExpansionDepth,\n\t\t\t\texpansionBudget:   yv.expansionBudget,\n\t\t\t}\n\t\t\tif err := newVal.UnmarshalYAML(val); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tkeyStr, err := newKey.value.StringValue()\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"keys are expected to be strings: %w\", err)\n\t\t\t}\n\n\t\t\tif err := res.SetMapKey(keyStr, newVal.value); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tyv.value = res\n\tcase yaml.AliasNode:\n\t\tif yv.expansionBudget != nil {\n\t\t\t*yv.expansionBudget = *yv.expansionBudget - 1\n\t\t\tif *yv.expansionBudget < 0 {\n\t\t\t\treturn ErrYamlExpansionBudgetExceeded\n\t\t\t}\n\t\t}\n\t\tnewVal := &yamlValue{\n\t\t\texpansionDepth:    yv.expansionDepth + 1,\n\t\t\tmaxExpansionDepth: yv.maxExpansionDepth,\n\t\t\texpansionBudget:   yv.expansionBudget,\n\t\t}\n\t\tif err := newVal.UnmarshalYAML(value.Alias); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tyv.value = newVal.value\n\t\tyv.value.SetMetadataValue(\"yaml-alias\", value.Value)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "parsing/yaml/yaml_test.go",
    "content": "package yaml_test\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"github.com/tomwright/dasel/v3/parsing\"\n\t\"github.com/tomwright/dasel/v3/parsing/yaml\"\n)\n\ntype testCase struct {\n\tin     string\n\tassert func(t *testing.T, res *model.Value)\n}\n\nfunc (tc testCase) run(t *testing.T) {\n\tr, err := yaml.YAML.NewReader(parsing.DefaultReaderOptions())\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tres, err := r.Read([]byte(tc.in))\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\n\ttc.assert(t, res)\n}\n\ntype rwTestCase struct {\n\tin  string\n\tout string\n}\n\nfunc (tc rwTestCase) run(t *testing.T) {\n\tif tc.out == \"\" {\n\t\ttc.out = tc.in\n\t}\n\tr, err := yaml.YAML.NewReader(parsing.DefaultReaderOptions())\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tw, err := yaml.YAML.NewWriter(parsing.WriterOptions{})\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tres, err := r.Read([]byte(tc.in))\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tout, err := w.Write(res)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\n\tif !bytes.Equal([]byte(tc.out), out) {\n\t\tt.Errorf(\"unexpected output: %s\", cmp.Diff(tc.out, string(out)))\n\t}\n}\n\nfunc TestYamlValue_UnmarshalYAML(t *testing.T) {\n\tt.Run(\"simple key value\", testCase{\n\t\tin: `name: Tom`,\n\t\tassert: func(t *testing.T, res *model.Value) {\n\t\t\tgot, err := res.GetMapKey(\"name\")\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t}\n\n\t\t\tgotStr, err := got.StringValue()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t}\n\n\t\t\tif gotStr != \"Tom\" {\n\t\t\t\tt.Errorf(\"unexpected value: %s\", gotStr)\n\t\t\t}\n\t\t},\n\t}.run)\n\n\tt.Run(\"multi document\", testCase{\n\t\tin: `name: Tom\n---\nname: Jerry`,\n\t\tassert: func(t *testing.T, res *model.Value) {\n\t\t\ta, err := res.GetSliceIndex(0)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t}\n\t\t\tgot, err := a.GetMapKey(\"name\")\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t}\n\t\t\tgotStr, err := got.StringValue()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t}\n\t\t\tif gotStr != \"Tom\" {\n\t\t\t\tt.Errorf(\"unexpected value: %s\", gotStr)\n\t\t\t}\n\n\t\t\tb, err := res.GetSliceIndex(1)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t}\n\t\t\tgot, err = b.GetMapKey(\"name\")\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t}\n\t\t\tgotStr, err = got.StringValue()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t}\n\t\t\tif gotStr != \"Jerry\" {\n\t\t\t\tt.Errorf(\"unexpected value: %s\", gotStr)\n\t\t\t}\n\t\t},\n\t}.run)\n\n\tt.Run(\"multi document\", testCase{\n\t\tin: `name: Tom\n---\nname: Jerry`,\n\t\tassert: func(t *testing.T, res *model.Value) {\n\t\t\ta, err := res.GetSliceIndex(0)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t}\n\t\t\tgot, err := a.GetMapKey(\"name\")\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t}\n\t\t\tgotStr, err := got.StringValue()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t}\n\t\t\tif gotStr != \"Tom\" {\n\t\t\t\tt.Errorf(\"unexpected value: %s\", gotStr)\n\t\t\t}\n\n\t\t\tb, err := res.GetSliceIndex(1)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t}\n\t\t\tgot, err = b.GetMapKey(\"name\")\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t}\n\t\t\tgotStr, err = got.StringValue()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t}\n\t\t\tif gotStr != \"Jerry\" {\n\t\t\t\tt.Errorf(\"unexpected value: %s\", gotStr)\n\t\t\t}\n\t\t},\n\t}.run)\n\n\tt.Run(\"multi document\", rwTestCase{\n\t\tin: `name: Tom\n---\nname: Jerry\n`,\n\t}.run)\n\n\tt.Run(\"generic\", rwTestCase{\n\t\tin: `str: foo\nint: 1\nfloat: 1.1\nbool: true\nmap:\n    key: value\nlist:\n    - item1\n    - item2\n`,\n\t}.run)\n\n\t// This test is technically wrong because we're only supporting the alias on read and not write.\n\tt.Run(\"alias\", rwTestCase{\n\t\tin: `name: &name Tom\nname2: *name\n`,\n\t\tout: `name: Tom\nname2: Tom\n`,\n\t}.run)\n\n\tt.Run(\"null read write\", rwTestCase{\n\t\tin: `name: null\n`,\n\t\tout: `name: null\n`,\n\t}.run)\n\n\tt.Run(\"null document read write\", rwTestCase{\n\t\tin: `null\n`,\n\t\tout: `null\n`,\n\t}.run)\n\n\tt.Run(\"bounded yaml expansion\", func(t *testing.T) {\n\t\tin := `a: &a [\"lol\",\"lol\",\"lol\",\"lol\",\"lol\",\"lol\",\"lol\",\"lol\",\"lol\"]\nb: &b [*a,*a,*a,*a,*a,*a,*a,*a,*a]\nc: &c [*b,*b,*b,*b,*b,*b,*b,*b,*b]\nd: &d [*c,*c,*c,*c,*c,*c,*c,*c,*c]\ne: &e [*d,*d,*d,*d,*d,*d,*d,*d,*d]\nf: &f [*e,*e,*e,*e,*e,*e,*e,*e,*e]\ng: &g [*f,*f,*f,*f,*f,*f,*f,*f,*f]\nh: &h [*g,*g,*g,*g,*g,*g,*g,*g,*g]\ni: &i [*h,*h,*h,*h,*h,*h,*h,*h,*h]\n`\n\n\t\treader, err := parsing.Format(\"yaml\").NewReader(parsing.DefaultReaderOptions())\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t}\n\n\t\tvar gotErr error\n\n\t\tmaxWaitTime := 10 * time.Second\n\t\tgotErrCh := make(chan error)\n\t\tgo func() {\n\t\t\t_, gotErr = reader.Read([]byte(in))\n\t\t\tgotErrCh <- gotErr\n\t\t}()\n\n\t\tselect {\n\t\tcase gotErr = <-gotErrCh:\n\t\t\tif gotErr == nil {\n\t\t\t\tt.Fatal(\"expected error, got nil\")\n\t\t\t}\n\t\t\tif !errors.Is(gotErr, yaml.ErrYamlExpansionDepthExceeded) && !errors.Is(gotErr, yaml.ErrYamlExpansionBudgetExceeded) {\n\t\t\t\tt.Fatalf(\"unexpected error: %s\", gotErr)\n\t\t\t}\n\t\tcase <-time.After(maxWaitTime):\n\t\t\tt.Fatalf(\"expected error within %s, but did not get one\", maxWaitTime)\n\t\t}\n\t})\n\n\tt.Run(\"alias metadata is preserved\", func(t *testing.T) {\n\t\tr, err := yaml.YAML.NewReader(parsing.DefaultReaderOptions())\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t}\n\n\t\tres, err := r.Read([]byte(\"name: &name Tom\\nname2: *name\\n\"))\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t}\n\n\t\taliasValue, err := res.GetMapKey(\"name2\")\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t}\n\n\t\tgot, ok := aliasValue.MetadataValue(\"yaml-alias\")\n\t\tif !ok {\n\t\t\tt.Fatal(\"expected yaml-alias metadata to be set\")\n\t\t}\n\t\tif got != \"name\" {\n\t\t\tt.Fatalf(\"unexpected yaml-alias metadata: %v\", got)\n\t\t}\n\t})\n\n\tt.Run(\"yaml expansion depth boundary\", func(t *testing.T) {\n\t\treader, err := parsing.Format(\"yaml\").NewReader(parsing.DefaultReaderOptions())\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t}\n\n\t\tmakeDoc := func(depth int) string {\n\t\t\tif depth == 0 {\n\t\t\t\treturn \"root0: &root0 [value]\\nresult: *root0\\n\"\n\t\t\t}\n\n\t\t\tres := \"root0: &root0 [value]\\n\"\n\t\t\tfor i := 1; i <= depth; i++ {\n\t\t\t\tres += fmt.Sprintf(\"root%d: &root%d [*root%d]\\n\", i, i, i-1)\n\t\t\t}\n\t\t\tres += fmt.Sprintf(\"result: *root%d\\n\", depth)\n\t\t\treturn res\n\t\t}\n\n\t\tdepthLimit := 32\n\t\tfor _, tc := range []struct {\n\t\t\tname    string\n\t\t\tdepth   int\n\t\t\twantErr bool\n\t\t}{\n\t\t\t{name: \"within limit\", depth: depthLimit - 2, wantErr: false},\n\t\t\t{name: \"at limit\", depth: depthLimit - 1, wantErr: false},\n\t\t\t{name: \"over limit\", depth: depthLimit, wantErr: true},\n\t\t} {\n\t\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\t\tres, err := reader.Read([]byte(makeDoc(tc.depth)))\n\t\t\t\tif tc.wantErr {\n\t\t\t\t\tif err == nil {\n\t\t\t\t\t\tt.Fatal(\"expected error, got nil\")\n\t\t\t\t\t}\n\t\t\t\t\tif !errors.Is(err, yaml.ErrYamlExpansionDepthExceeded) {\n\t\t\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t\t\t}\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t\t}\n\n\t\t\t\tresult, err := res.GetMapKey(\"result\")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t\t}\n\t\t\t\tfor i := 0; i <= tc.depth; i++ {\n\t\t\t\t\tresult, err = result.GetSliceIndex(0)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tt.Fatalf(\"unexpected error at nested index %d: %s\", i, err)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tgot, err := result.StringValue()\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t\t}\n\t\t\t\tif got != \"value\" {\n\t\t\t\t\tt.Fatalf(\"unexpected value: %s\", got)\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t})\n\n\tt.Run(\"yaml expansion budget boundary\", func(t *testing.T) {\n\t\treader, err := parsing.Format(\"yaml\").NewReader(parsing.DefaultReaderOptions())\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t}\n\n\t\tmakeDoc := func(aliasCount int) string {\n\t\t\tres := \"root: &root value\\nitems:\\n\"\n\t\t\tfor i := 0; i < aliasCount; i++ {\n\t\t\t\tres += \"  - *root\\n\"\n\t\t\t}\n\t\t\treturn res\n\t\t}\n\n\t\tres, err := reader.Read([]byte(makeDoc(1000)))\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error at budget boundary: %s\", err)\n\t\t}\n\t\titems, err := res.GetMapKey(\"items\")\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t}\n\t\tlast, err := items.GetSliceIndex(999)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t}\n\t\tgot, err := last.StringValue()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t}\n\t\tif got != \"value\" {\n\t\t\tt.Fatalf(\"unexpected value: %s\", got)\n\t\t}\n\n\t\t_, err = reader.Read([]byte(makeDoc(1001)))\n\t\tif err == nil {\n\t\t\tt.Fatal(\"expected error, got nil\")\n\t\t}\n\t\tif !errors.Is(err, yaml.ErrYamlExpansionBudgetExceeded) {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t})\n\n\tt.Run(\"yaml expansion budget resets per document\", func(t *testing.T) {\n\t\treader, err := parsing.Format(\"yaml\").NewReader(parsing.DefaultReaderOptions())\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t}\n\n\t\tmakeDoc := func(aliasCount int) string {\n\t\t\tres := \"root: &root value\\nitems:\\n\"\n\t\t\tfor i := 0; i < aliasCount; i++ {\n\t\t\t\tres += \"  - *root\\n\"\n\t\t\t}\n\t\t\treturn res\n\t\t}\n\n\t\tmultiDoc := makeDoc(1000) + \"---\\n\" + makeDoc(1000)\n\t\tres, err := reader.Read([]byte(multiDoc))\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t}\n\n\t\tfirst, err := res.GetSliceIndex(0)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t}\n\t\tsecond, err := res.GetSliceIndex(1)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t}\n\t\tfor _, doc := range []*model.Value{first, second} {\n\t\t\titems, err := doc.GetMapKey(\"items\")\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t}\n\t\t\tlast, err := items.GetSliceIndex(999)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t}\n\t\t\tgot, err := last.StringValue()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t}\n\t\t\tif got != \"value\" {\n\t\t\t\tt.Fatalf(\"unexpected value: %s\", got)\n\t\t\t}\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "parsing/yaml/yaml_writer.go",
    "content": "package yaml\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"github.com/tomwright/dasel/v3/parsing\"\n\t\"go.yaml.in/yaml/v4\"\n)\n\nvar _ parsing.Writer = (*yamlWriter)(nil)\n\nfunc newYAMLWriter(options parsing.WriterOptions) (parsing.Writer, error) {\n\treturn &yamlWriter{}, nil\n}\n\ntype yamlWriter struct{}\n\nfunc (j *yamlWriter) Separator() []byte {\n\treturn []byte(\"---\\n\")\n}\n\n// Write writes a value to a byte slice.\nfunc (j *yamlWriter) Write(value *model.Value) ([]byte, error) {\n\tyv := &yamlValue{value: value}\n\tres, err := yv.ToNode()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn yaml.Marshal(res)\n}\n\nfunc (yv *yamlValue) ToNode() (*yaml.Node, error) {\n\tres := &yaml.Node{}\n\n\t// TODO : Handle yaml aliases.\n\t//yamlAlias, ok := yv.value.Metadata[\"yaml-alias\"].(string)\n\t//if ok {\n\t//res.Kind = yaml.AliasNode\n\t//res.Value = yamlAlias\n\t//return res, nil\n\t//}\n\n\tswitch yv.value.Type() {\n\tcase model.TypeString:\n\t\tv, err := yv.value.StringValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tres.Kind = yaml.ScalarNode\n\t\tres.Value = v\n\t\tres.Tag = \"!!str\"\n\tcase model.TypeBool:\n\t\tv, err := yv.value.BoolValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tres.Kind = yaml.ScalarNode\n\t\tres.Value = fmt.Sprintf(\"%t\", v)\n\t\tres.Tag = \"!!bool\"\n\tcase model.TypeInt:\n\t\tv, err := yv.value.IntValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tres.Kind = yaml.ScalarNode\n\t\tres.Value = fmt.Sprintf(\"%d\", v)\n\t\tres.Tag = \"!!int\"\n\tcase model.TypeFloat:\n\t\tv, err := yv.value.FloatValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tres.Kind = yaml.ScalarNode\n\t\tres.Value = fmt.Sprintf(\"%g\", v)\n\t\tres.Tag = \"!!float\"\n\tcase model.TypeMap:\n\t\tres.Kind = yaml.MappingNode\n\t\tif err := yv.value.RangeMap(func(key string, val *model.Value) error {\n\t\t\tkeyNode := &yamlValue{value: model.NewStringValue(key)}\n\t\t\tvalNode := &yamlValue{value: val}\n\n\t\t\tmarshalledKey, err := keyNode.ToNode()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tmarshalledVal, err := valNode.ToNode()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tres.Content = append(res.Content, marshalledKey)\n\t\t\tres.Content = append(res.Content, marshalledVal)\n\n\t\t\treturn nil\n\t\t}); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\tcase model.TypeSlice:\n\t\tres.Kind = yaml.SequenceNode\n\t\tif err := yv.value.RangeSlice(func(i int, val *model.Value) error {\n\t\t\tvalNode := &yamlValue{value: val}\n\t\t\tmarshalledVal, err := valNode.ToNode()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tres.Content = append(res.Content, marshalledVal)\n\t\t\treturn nil\n\t\t}); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\tcase model.TypeNull:\n\t\tres.Kind = yaml.ScalarNode\n\t\tres.Value = \"null\"\n\t\tres.Tag = \"!!null\"\n\tcase model.TypeUnknown:\n\t\treturn nil, fmt.Errorf(\"unknown type: %s\", yv.value.Type())\n\t}\n\n\treturn res, nil\n}\n\nfunc (yv *yamlValue) MarshalYAML() (any, error) {\n\tres, err := yv.ToNode()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn res, nil\n}\n"
  },
  {
    "path": "selector/README.md",
    "content": "# Selector\n\nThe selector package contains everything needed to parse a selector string into an AST, which we can then execute.\n"
  },
  {
    "path": "selector/ast/ast.go",
    "content": "package ast\n\ntype Program struct {\n\tStatements []Statement\n}\n\ntype Statement struct {\n\tExpressions Expr\n}\n\ntype Expressions []Expr\n\ntype Expr interface {\n\texpr()\n}\n\nfunc IsType[T Expr](e Expr) bool {\n\t_, ok := AsType[T](e)\n\treturn ok\n}\n\nfunc AsType[T Expr](e Expr) (T, bool) {\n\tv, ok := e.(T)\n\treturn v, ok\n}\n\nfunc LastAsType[T Expr](e Expr) (T, bool) {\n\treturn AsType[T](Last(e))\n}\n\nfunc Last(e Expr) Expr {\n\tif v, ok := e.(ChainedExpr); ok {\n\t\treturn v.Exprs[len(v.Exprs)-1]\n\t}\n\treturn e\n}\n\nfunc RemoveLast(e Expr) Expr {\n\tvar res Expressions\n\tif v, ok := e.(ChainedExpr); ok {\n\t\tres = v.Exprs[0 : len(v.Exprs)-1]\n\t}\n\treturn ChainExprs(res...)\n}\n"
  },
  {
    "path": "selector/ast/ast_test.go",
    "content": "package ast\n\nimport \"testing\"\n\n// TestExpr_expr tests the expr method of all the types in the ast package.\n// Note that this doesn't actually do anything and is just forcing test coverage.\n// The expr func only exists for type safety with the Expr interface.\nfunc TestExpr_expr(t *testing.T) {\n\tNumberFloatExpr{}.expr()\n\tNumberIntExpr{}.expr()\n\tStringExpr{}.expr()\n\tBoolExpr{}.expr()\n\tBinaryExpr{}.expr()\n\tUnaryExpr{}.expr()\n\tCallExpr{}.expr()\n\tChainedExpr{}.expr()\n\tSpreadExpr{}.expr()\n\tRangeExpr{}.expr()\n\tIndexExpr{}.expr()\n\tArrayExpr{}.expr()\n\tPropertyExpr{}.expr()\n\tObjectExpr{}.expr()\n\tMapExpr{}.expr()\n\tEachExpr{}.expr()\n\tVariableExpr{}.expr()\n\tGroupExpr{}.expr()\n\tConditionalExpr{}.expr()\n\tBranchExpr{}.expr()\n}\n"
  },
  {
    "path": "selector/ast/expression_complex.go",
    "content": "package ast\n\nimport \"github.com/tomwright/dasel/v3/selector/lexer\"\n\ntype BinaryExpr struct {\n\tLeft     Expr\n\tOperator lexer.Token\n\tRight    Expr\n}\n\nfunc (BinaryExpr) expr() {}\n\ntype UnaryExpr struct {\n\tOperator lexer.Token\n\tRight    Expr\n}\n\nfunc (UnaryExpr) expr() {}\n\ntype CallExpr struct {\n\tFunction string\n\tArgs     Expressions\n}\n\nfunc (CallExpr) expr() {}\n\ntype ChainedExpr struct {\n\tExprs Expressions\n}\n\nfunc ChainExprs(exprs ...Expr) Expr {\n\tif len(exprs) == 0 {\n\t\treturn nil\n\t}\n\tif len(exprs) == 1 {\n\t\treturn exprs[0]\n\t}\n\treturn ChainedExpr{\n\t\tExprs: exprs,\n\t}\n}\n\nfunc (ChainedExpr) expr() {}\n\ntype SpreadExpr struct{}\n\nfunc (SpreadExpr) expr() {}\n\ntype RangeExpr struct {\n\tStart Expr\n\tEnd   Expr\n}\n\nfunc (RangeExpr) expr() {}\n\ntype IndexExpr struct {\n\tIndex Expr\n}\n\nfunc (IndexExpr) expr() {}\n\ntype ArrayExpr struct {\n\tExprs Expressions\n}\n\nfunc (ArrayExpr) expr() {}\n\ntype PropertyExpr struct {\n\t// Property can resolve to a string or number.\n\t// If it resolves to a number, we expect to be reading from an array.\n\t// If it resolves to a string, we expect to be reading from a map.\n\tProperty Expr\n}\n\nfunc (PropertyExpr) expr() {}\n\ntype KeyValue struct {\n\tKey   Expr\n\tValue Expr\n}\n\ntype ObjectExpr struct {\n\tPairs []KeyValue\n}\n\nfunc (ObjectExpr) expr() {}\n\ntype MapExpr struct {\n\tExpr Expr\n}\n\nfunc (MapExpr) expr() {}\n\ntype EachExpr struct {\n\tExpr Expr\n}\n\nfunc (EachExpr) expr() {}\n\ntype FilterExpr struct {\n\tExpr Expr\n}\n\nfunc (FilterExpr) expr() {}\n\ntype SearchExpr struct {\n\tExpr Expr\n}\n\nfunc (SearchExpr) expr() {}\n\ntype RecursiveDescentExpr struct {\n\tIsWildcard bool\n\tExpr       Expr\n}\n\nfunc (RecursiveDescentExpr) expr() {}\n\ntype SortByExpr struct {\n\tExpr       Expr\n\tDescending bool\n}\n\nfunc (SortByExpr) expr() {}\n\ntype VariableExpr struct {\n\tName string\n}\n\nfunc (VariableExpr) expr() {}\n\ntype GroupExpr struct {\n\tExpr Expr\n}\n\nfunc (GroupExpr) expr() {}\n\ntype ConditionalExpr struct {\n\tCond Expr\n\tThen Expr\n\tElse Expr\n}\n\nfunc (ConditionalExpr) expr() {}\n\ntype BranchExpr struct {\n\tExprs []Expr\n}\n\nfunc (BranchExpr) expr() {}\n\nfunc BranchExprs(exprs ...Expr) Expr {\n\treturn BranchExpr{\n\t\tExprs: exprs,\n\t}\n}\n\ntype AssignExpr struct {\n\tVariable VariableExpr\n\tValue    Expr\n}\n\nfunc (AssignExpr) expr() {}\n"
  },
  {
    "path": "selector/ast/expression_literal.go",
    "content": "package ast\n\nimport \"regexp\"\n\ntype NumberFloatExpr struct {\n\tValue float64\n}\n\nfunc (NumberFloatExpr) expr() {}\n\ntype NumberIntExpr struct {\n\tValue int64\n}\n\nfunc (NumberIntExpr) expr() {}\n\ntype StringExpr struct {\n\tValue string\n}\n\nfunc (StringExpr) expr() {}\n\ntype BoolExpr struct {\n\tValue bool\n}\n\nfunc (BoolExpr) expr() {}\n\ntype RegexExpr struct {\n\tRegex *regexp.Regexp\n}\n\nfunc (RegexExpr) expr() {}\n\ntype NullExpr struct{}\n\nfunc (NullExpr) expr() {}\n"
  },
  {
    "path": "selector/lexer/token.go",
    "content": "package lexer\n\nimport (\n\t\"fmt\"\n\t\"slices\"\n)\n\ntype TokenKind int\n\nfunc TokenKinds(tk ...TokenKind) []TokenKind {\n\treturn tk\n}\n\nconst (\n\tEOF TokenKind = iota\n\tSymbol\n\tComma\n\tColon\n\tOpenBracket  // [\n\tCloseBracket // ]\n\tOpenCurly\n\tCloseCurly\n\tOpenParen\n\tCloseParen\n\tEqual    // ==\n\tEquals   // =\n\tNotEqual // !=\n\tAnd\n\tOr\n\tLike    // =~\n\tNotLike // !~\n\tString\n\tNumber\n\tBool\n\tPlus\n\tIncrement\n\tIncrementBy\n\tDash\n\tDecrement\n\tDecrementBy\n\tStar\n\tSlash\n\tPercent\n\tDot\n\tSpread           // ...\n\tRecursiveDescent // ..\n\tDollar\n\tVariable\n\tGreaterThan\n\tGreaterThanOrEqual\n\tLessThan\n\tLessThanOrEqual\n\tExclamation\n\tNull\n\tIf\n\tElse\n\tElseIf\n\tBranch\n\tMap\n\tEach\n\tFilter\n\tSearch\n\tRegexPattern\n\tSortBy\n\tAsc\n\tDesc\n\tQuestionMark\n\tDoubleQuestionMark\n\tSemicolon\n)\n\ntype Tokens []Token\n\nfunc (tt Tokens) Split(kind TokenKind) []Tokens {\n\tvar res []Tokens\n\tvar cur Tokens\n\tfor _, t := range tt {\n\t\tif t.Kind == kind {\n\t\t\tif len(cur) > 0 {\n\t\t\t\tres = append(res, cur)\n\t\t\t}\n\t\t\tcur = nil\n\t\t\tcontinue\n\t\t}\n\t\tcur = append(cur, t)\n\t}\n\tif len(cur) > 0 {\n\t\tres = append(res, cur)\n\t}\n\treturn res\n}\n\ntype Token struct {\n\tKind  TokenKind\n\tValue string\n\tPos   int\n\tLen   int\n}\n\nfunc NewToken(kind TokenKind, value string, pos int, len int) Token {\n\treturn Token{\n\t\tKind:  kind,\n\t\tValue: value,\n\t\tPos:   pos,\n\t\tLen:   len,\n\t}\n}\n\nfunc (t Token) IsKind(kind ...TokenKind) bool {\n\treturn slices.Contains(kind, t.Kind)\n}\n\ntype UnexpectedTokenError struct {\n\tPos   int\n\tToken rune\n}\n\nfunc (e *UnexpectedTokenError) Error() string {\n\treturn fmt.Sprintf(\"failed to tokenize: unexpected token: %s at position %d.\", string(e.Token), e.Pos)\n}\n\ntype UnexpectedEOFError struct {\n\tPos int\n}\n\nfunc (e *UnexpectedEOFError) Error() string {\n\treturn fmt.Sprintf(\"failed to tokenize: unexpected EOF at position %d.\", e.Pos)\n}\n"
  },
  {
    "path": "selector/lexer/tokenize.go",
    "content": "package lexer\n\nimport (\n\t\"strings\"\n\t\"unicode\"\n\n\t\"github.com/tomwright/dasel/v3/internal/ptr\"\n)\n\ntype Tokenizer struct {\n\ti      int\n\tsrc    string\n\tsrcLen int\n}\n\nfunc NewTokenizer(src string) *Tokenizer {\n\treturn &Tokenizer{\n\t\ti:      0,\n\t\tsrc:    src,\n\t\tsrcLen: len([]rune(src)),\n\t}\n}\n\nfunc (p *Tokenizer) Tokenize() (Tokens, error) {\n\tvar tokens Tokens\n\tfor {\n\t\ttok, err := p.Next()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif tok.Kind == EOF {\n\t\t\tbreak\n\t\t}\n\t\ttokens = append(tokens, tok)\n\t}\n\treturn tokens, nil\n}\n\nfunc (p *Tokenizer) peekRuneEqual(i int, to rune) bool {\n\tif i >= p.srcLen {\n\t\treturn false\n\t}\n\treturn rune(p.src[i]) == to\n}\n\nfunc (p *Tokenizer) peekRuneMatches(i int, fn func(rune) bool) bool {\n\tif i >= p.srcLen {\n\t\treturn false\n\t}\n\treturn fn(rune(p.src[i]))\n}\n\nfunc (p *Tokenizer) parseCurRune() (Token, error) {\n\t// Skip over whitespace\n\tfor p.i < p.srcLen && unicode.IsSpace(rune(p.src[p.i])) {\n\t\tp.i++\n\t}\n\n\t// Skip over comments\n\tif p.src[p.i] == '/' && p.i+1 < p.srcLen && p.src[p.i+1] == '/' {\n\t\tp.i += 2\n\t\tfor p.i < p.srcLen && p.src[p.i] != '\\n' {\n\t\t\tp.i++\n\t\t}\n\t\t// After skipping the comment, skip any whitespace again.\n\t\tfor p.i < p.srcLen && unicode.IsSpace(rune(p.src[p.i])) {\n\t\t\tp.i++\n\t\t}\n\t\tif p.i >= p.srcLen {\n\t\t\treturn NewToken(EOF, \"\", p.i, 0), nil\n\t\t}\n\t}\n\n\tswitch p.src[p.i] {\n\tcase '.':\n\t\tif p.peekRuneEqual(p.i+1, '.') && p.peekRuneEqual(p.i+2, '.') {\n\t\t\treturn NewToken(Spread, \"...\", p.i, 3), nil\n\t\t}\n\t\tif p.peekRuneEqual(p.i+1, '.') {\n\t\t\treturn NewToken(RecursiveDescent, \"..\", p.i, 2), nil\n\t\t}\n\t\treturn NewToken(Dot, \".\", p.i, 1), nil\n\tcase ',':\n\t\treturn NewToken(Comma, \",\", p.i, 1), nil\n\tcase ':':\n\t\treturn NewToken(Colon, \":\", p.i, 1), nil\n\tcase ';':\n\t\treturn NewToken(Semicolon, \";\", p.i, 1), nil\n\tcase '[':\n\t\treturn NewToken(OpenBracket, \"[\", p.i, 1), nil\n\tcase ']':\n\t\treturn NewToken(CloseBracket, \"]\", p.i, 1), nil\n\tcase '(':\n\t\treturn NewToken(OpenParen, \"(\", p.i, 1), nil\n\tcase ')':\n\t\treturn NewToken(CloseParen, \")\", p.i, 1), nil\n\tcase '{':\n\t\treturn NewToken(OpenCurly, \"{\", p.i, 1), nil\n\tcase '}':\n\t\treturn NewToken(CloseCurly, \"}\", p.i, 1), nil\n\tcase '*':\n\t\treturn NewToken(Star, \"*\", p.i, 1), nil\n\tcase '/':\n\t\treturn NewToken(Slash, \"/\", p.i, 1), nil\n\tcase '%':\n\t\treturn NewToken(Percent, \"%\", p.i, 1), nil\n\tcase '$':\n\t\tif p.peekRuneMatches(p.i+1, unicode.IsLetter) || p.peekRuneEqual(p.i+1, '_') {\n\t\t\tpos := p.i + 1\n\t\t\tfor pos < p.srcLen && (unicode.IsLetter(rune(p.src[pos])) ||\n\t\t\t\tunicode.IsDigit(rune(p.src[pos])) ||\n\t\t\t\tp.src[pos] == '_') {\n\t\t\t\tpos++\n\t\t\t}\n\t\t\treturn NewToken(Variable, p.src[p.i+1:pos], p.i, pos-p.i), nil\n\t\t}\n\t\treturn NewToken(Dollar, \"$\", p.i, 1), nil\n\tcase '=':\n\t\tif p.peekRuneEqual(p.i+1, '=') {\n\t\t\treturn NewToken(Equal, \"==\", p.i, 2), nil\n\t\t}\n\t\tif p.peekRuneEqual(p.i+1, '~') {\n\t\t\treturn NewToken(Like, \"=~\", p.i, 2), nil\n\t\t}\n\t\treturn NewToken(Equals, \"=\", p.i, 1), nil\n\tcase '+':\n\t\tif p.peekRuneEqual(p.i+1, '=') {\n\t\t\treturn NewToken(IncrementBy, \"+=\", p.i, 2), nil\n\t\t}\n\t\tif p.peekRuneEqual(p.i+1, '+') {\n\t\t\treturn NewToken(Increment, \"++\", p.i, 2), nil\n\t\t}\n\t\treturn NewToken(Plus, \"+\", p.i, 1), nil\n\tcase '-':\n\t\tif p.peekRuneEqual(p.i+1, '=') {\n\t\t\treturn NewToken(DecrementBy, \"-=\", p.i, 2), nil\n\t\t}\n\t\tif p.peekRuneEqual(p.i+1, '-') {\n\t\t\treturn NewToken(Decrement, \"--\", p.i, 2), nil\n\t\t}\n\t\treturn NewToken(Dash, \"-\", p.i, 1), nil\n\tcase '>':\n\t\tif p.peekRuneEqual(p.i+1, '=') {\n\t\t\treturn NewToken(GreaterThanOrEqual, \">=\", p.i, 2), nil\n\t\t}\n\t\treturn NewToken(GreaterThan, \">\", p.i, 1), nil\n\tcase '<':\n\t\tif p.peekRuneEqual(p.i+1, '=') {\n\t\t\treturn NewToken(LessThanOrEqual, \"<>>=\", p.i, 2), nil\n\t\t}\n\t\treturn NewToken(LessThan, \"<\", p.i, 1), nil\n\tcase '!':\n\t\tif p.peekRuneEqual(p.i+1, '=') {\n\t\t\treturn NewToken(NotEqual, \"!=\", p.i, 2), nil\n\t\t}\n\t\tif p.peekRuneEqual(p.i+1, '~') {\n\t\t\treturn NewToken(NotLike, \"!~\", p.i, 2), nil\n\t\t}\n\t\treturn NewToken(Exclamation, \"!\", p.i, 1), nil\n\tcase '&':\n\t\tif p.peekRuneEqual(p.i+1, '&') {\n\t\t\treturn NewToken(And, \"&&\", p.i, 2), nil\n\t\t}\n\t\treturn Token{}, &UnexpectedTokenError{\n\t\t\tPos:   p.i,\n\t\t\tToken: rune(p.src[p.i]),\n\t\t}\n\tcase '|':\n\t\tif p.peekRuneEqual(p.i+1, '|') {\n\t\t\treturn NewToken(Or, \"||\", p.i, 2), nil\n\t\t}\n\t\treturn Token{}, &UnexpectedTokenError{\n\t\t\tPos:   p.i,\n\t\t\tToken: rune(p.src[p.i]),\n\t\t}\n\tcase '?':\n\t\tif p.peekRuneEqual(p.i+1, '?') {\n\t\t\treturn NewToken(DoubleQuestionMark, \"??\", p.i, 2), nil\n\t\t}\n\t\treturn NewToken(QuestionMark, \"?\", p.i, 1), nil\n\tcase '\"', '\\'':\n\t\tpos := p.i\n\t\tbuf := make([]rune, 0)\n\t\tpos++\n\t\tfoundCloseRune := false\n\t\tfor pos < p.srcLen {\n\t\t\tif p.src[pos] == p.src[p.i] {\n\t\t\t\tfoundCloseRune = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif p.src[pos] == '\\\\' {\n\t\t\t\tpos++\n\t\t\t\tbuf = append(buf, rune(p.src[pos]))\n\t\t\t\tpos++\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tbuf = append(buf, rune(p.src[pos]))\n\t\t\tpos++\n\t\t}\n\t\tif !foundCloseRune {\n\t\t\t// We didn't find a closing quote.\n\t\t\tif pos < p.srcLen {\n\t\t\t\t// This shouldn't be possible.\n\t\t\t\treturn Token{}, &UnexpectedTokenError{\n\t\t\t\t\tPos:   p.i,\n\t\t\t\t\tToken: rune(p.src[pos]),\n\t\t\t\t}\n\t\t\t}\n\t\t\t// This can happen if the selector ends before the closing quote.\n\t\t\treturn Token{}, &UnexpectedEOFError{\n\t\t\t\tPos: pos,\n\t\t\t}\n\t\t}\n\t\tres := NewToken(String, string(buf), p.i, pos+1-p.i)\n\t\treturn res, nil\n\tdefault:\n\t\tpos := p.i\n\n\t\tmatchStr := func(pos int, m string, caseInsensitive bool, kind TokenKind) *Token {\n\t\t\tl := len(m)\n\t\t\tif pos+(l-1) >= p.srcLen {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tother := p.src[pos : pos+l]\n\t\t\tif m != other && (!caseInsensitive || !strings.EqualFold(m, other)) {\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\tif pos+(l) < p.srcLen && (unicode.IsLetter(rune(p.src[pos+l])) || unicode.IsDigit(rune(p.src[pos+l]))) {\n\t\t\t\t// There is a follow letter or digit.\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\treturn ptr.To(NewToken(kind, other, pos, l))\n\t\t}\n\n\t\tmatchRegexPattern := func(pos int) *Token {\n\t\t\tif p.src[pos] != 'r' || !p.peekRuneEqual(pos+1, '/') {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tstart := pos\n\t\t\tpos += 2\n\t\t\tfor !p.peekRuneEqual(pos, '/') {\n\t\t\t\tpos++\n\t\t\t}\n\t\t\tpos++\n\t\t\treturn ptr.To(NewToken(RegexPattern, p.src[start+2:pos-1], start, pos-start))\n\t\t}\n\n\t\tif t := matchStr(pos, \"null\", true, Null); t != nil {\n\t\t\treturn *t, nil\n\t\t}\n\t\tif t := matchStr(pos, \"true\", true, Bool); t != nil {\n\t\t\treturn *t, nil\n\t\t}\n\t\tif t := matchStr(pos, \"false\", true, Bool); t != nil {\n\t\t\treturn *t, nil\n\t\t}\n\t\tif t := matchStr(pos, \"elseif\", false, ElseIf); t != nil {\n\t\t\treturn *t, nil\n\t\t}\n\t\tif t := matchStr(pos, \"if\", false, If); t != nil {\n\t\t\treturn *t, nil\n\t\t}\n\t\tif t := matchStr(pos, \"else\", false, Else); t != nil {\n\t\t\treturn *t, nil\n\t\t}\n\t\tif t := matchStr(pos, \"branch\", false, Branch); t != nil {\n\t\t\treturn *t, nil\n\t\t}\n\t\tif t := matchStr(pos, \"map\", false, Map); t != nil {\n\t\t\treturn *t, nil\n\t\t}\n\t\tif t := matchStr(pos, \"each\", false, Each); t != nil {\n\t\t\treturn *t, nil\n\t\t}\n\t\tif t := matchStr(pos, \"filter\", false, Filter); t != nil {\n\t\t\treturn *t, nil\n\t\t}\n\t\tif t := matchStr(pos, \"search\", false, Search); t != nil {\n\t\t\treturn *t, nil\n\t\t}\n\t\tif t := matchStr(pos, \"sortBy\", false, SortBy); t != nil {\n\t\t\treturn *t, nil\n\t\t}\n\t\tif t := matchStr(pos, \"asc\", false, Asc); t != nil {\n\t\t\treturn *t, nil\n\t\t}\n\t\tif t := matchStr(pos, \"desc\", false, Desc); t != nil {\n\t\t\treturn *t, nil\n\t\t}\n\n\t\tif t := matchRegexPattern(pos); t != nil {\n\t\t\treturn *t, nil\n\t\t}\n\n\t\tif unicode.IsDigit(rune(p.src[pos])) {\n\t\t\t// Handle whole numbers\n\t\t\tfor pos < p.srcLen && unicode.IsDigit(rune(p.src[pos])) {\n\t\t\t\tpos++\n\t\t\t}\n\t\t\t// Handle floats\n\t\t\tif pos < p.srcLen && p.src[pos] == '.' && pos+1 < p.srcLen && unicode.IsDigit(rune(p.src[pos+1])) {\n\t\t\t\tpos++\n\t\t\t\tfor pos < p.srcLen && unicode.IsDigit(rune(p.src[pos])) {\n\t\t\t\t\tpos++\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn NewToken(Number, p.src[p.i:pos], p.i, pos-p.i), nil\n\t\t}\n\n\t\tif unicode.IsLetter(rune(p.src[pos])) || p.src[pos] == '_' {\n\t\t\tfor pos < p.srcLen && (unicode.IsLetter(rune(p.src[pos])) ||\n\t\t\t\tunicode.IsDigit(rune(p.src[pos])) ||\n\t\t\t\tp.src[pos] == '_') {\n\t\t\t\tpos++\n\t\t\t}\n\t\t\treturn NewToken(Symbol, p.src[p.i:pos], p.i, pos-p.i), nil\n\t\t}\n\n\t\treturn Token{}, &UnexpectedTokenError{\n\t\t\tPos:   p.i,\n\t\t\tToken: rune(p.src[p.i]),\n\t\t}\n\t}\n}\n\nfunc (p *Tokenizer) Next() (Token, error) {\n\tif p.i >= len(p.src) {\n\t\treturn NewToken(EOF, \"\", p.i, 0), nil\n\t}\n\n\tt, err := p.parseCurRune()\n\tif err != nil {\n\t\treturn Token{}, err\n\t}\n\tp.i += t.Len\n\treturn t, nil\n}\n"
  },
  {
    "path": "selector/lexer/tokenize_test.go",
    "content": "package lexer_test\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/selector/lexer\"\n)\n\ntype testCase struct {\n\tin  string\n\tout []lexer.TokenKind\n}\n\nfunc (tc testCase) run(t *testing.T) {\n\ttok := lexer.NewTokenizer(tc.in)\n\ttokens, err := tok.Tokenize()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif len(tokens) != len(tc.out) {\n\t\tt.Fatalf(\"unexpected number of tokens: %d\", len(tokens))\n\t}\n\tfor i := range tokens {\n\t\tif tokens[i].Kind != tc.out[i] {\n\t\t\tt.Errorf(\"unexpected token kind at position %d: exp %v, got %v\", i, tc.out[i], tokens[i].Kind)\n\t\t\treturn\n\t\t}\n\t}\n}\n\ntype errTestCase struct {\n\tin    string\n\tmatch func(error) bool\n}\n\nfunc (tc errTestCase) run(t *testing.T) {\n\ttok := lexer.NewTokenizer(tc.in)\n\ttokens, err := tok.Tokenize()\n\tif !tc.match(err) {\n\t\tt.Errorf(\"unexpected error, got %v\", err)\n\t}\n\tif tokens != nil {\n\t\tt.Errorf(\"unexpected tokens: %v\", tokens)\n\t}\n}\n\n// nolint:unused\nfunc matchUnexpectedError(r rune, p int) func(error) bool {\n\treturn func(err error) bool {\n\t\tvar e *lexer.UnexpectedTokenError\n\t\tif !errors.As(err, &e) {\n\t\t\treturn false\n\t\t}\n\n\t\treturn e.Token == r && e.Pos == p\n\t}\n}\n\nfunc matchUnexpectedEOFError(p int) func(error) bool {\n\treturn func(err error) bool {\n\t\tvar e *lexer.UnexpectedEOFError\n\t\tif !errors.As(err, &e) {\n\t\t\treturn false\n\t\t}\n\n\t\treturn e.Pos == p\n\t}\n}\n\nfunc TestTokenizer_Parse(t *testing.T) {\n\tt.Run(\"variables\", testCase{\n\t\tin: \"$foo $bar123 $baz $ $quietLoudSCREAM $_hello $hello_world\",\n\t\tout: []lexer.TokenKind{\n\t\t\tlexer.Variable,\n\t\t\tlexer.Variable,\n\t\t\tlexer.Variable,\n\t\t\tlexer.Dollar,\n\t\t\tlexer.Variable,\n\t\t\tlexer.Variable,\n\t\t\tlexer.Variable,\n\t\t},\n\t}.run)\n\n\tt.Run(\"if\", testCase{\n\t\tin: `if elseif else`,\n\t\tout: []lexer.TokenKind{\n\t\t\tlexer.If,\n\t\t\tlexer.ElseIf,\n\t\t\tlexer.Else,\n\t\t},\n\t}.run)\n\n\tt.Run(\"regex\", testCase{\n\t\tin: `r/asd/ r/hello there/`,\n\t\tout: []lexer.TokenKind{\n\t\t\tlexer.RegexPattern,\n\t\t\tlexer.RegexPattern,\n\t\t},\n\t}.run)\n\n\tt.Run(\"sort by\", testCase{\n\t\tin: `sortBy(foo, asc)`,\n\t\tout: []lexer.TokenKind{\n\t\t\tlexer.SortBy,\n\t\t\tlexer.OpenParen,\n\t\t\tlexer.Symbol,\n\t\t\tlexer.Comma,\n\t\t\tlexer.Asc,\n\t\t\tlexer.CloseParen,\n\t\t},\n\t}.run)\n\n\tt.Run(\"recursive descent\", func(t *testing.T) {\n\t\tt.Run(\"key\", testCase{\n\t\t\tin: `..foo`,\n\t\t\tout: []lexer.TokenKind{\n\t\t\t\tlexer.RecursiveDescent,\n\t\t\t\tlexer.Symbol,\n\t\t\t},\n\t\t}.run)\n\t\tt.Run(\"index\", testCase{\n\t\t\tin: `..[1]`,\n\t\t\tout: []lexer.TokenKind{\n\t\t\t\tlexer.RecursiveDescent,\n\t\t\t\tlexer.OpenBracket,\n\t\t\t\tlexer.Number,\n\t\t\t\tlexer.CloseBracket,\n\t\t\t},\n\t\t}.run)\n\t\tt.Run(\"wildcard\", testCase{\n\t\t\tin: `..*`,\n\t\t\tout: []lexer.TokenKind{\n\t\t\t\tlexer.RecursiveDescent,\n\t\t\t\tlexer.Star,\n\t\t\t},\n\t\t}.run)\n\t})\n\n\tt.Run(\"everything\", testCase{\n\t\tin: \"foo.bar.baz[1] != 42.123 || foo.b_a_r.baz['hello'] == 42 && x == 'a\\\\'b' + false true . .... asd... $name null\",\n\t\tout: []lexer.TokenKind{\n\t\t\tlexer.Symbol, lexer.Dot, lexer.Symbol, lexer.Dot, lexer.Symbol, lexer.OpenBracket, lexer.Number, lexer.CloseBracket, lexer.NotEqual, lexer.Number,\n\t\t\tlexer.Or,\n\t\t\tlexer.Symbol, lexer.Dot, lexer.Symbol, lexer.Dot, lexer.Symbol, lexer.OpenBracket, lexer.String, lexer.CloseBracket, lexer.Equal, lexer.Number,\n\t\t\tlexer.And,\n\t\t\tlexer.Symbol, lexer.Equal, lexer.String,\n\t\t\tlexer.Plus, lexer.Bool, lexer.Bool,\n\t\t\tlexer.Dot, lexer.Spread, lexer.Dot,\n\t\t\tlexer.Symbol, lexer.Spread,\n\t\t\tlexer.Variable, lexer.Null,\n\t\t},\n\t}.run)\n\n\tt.Run(\"unhappy\", func(t *testing.T) {\n\t\tt.Run(\"unfinished double quote\", errTestCase{\n\t\t\tin:    `\"hello`,\n\t\t\tmatch: matchUnexpectedEOFError(6),\n\t\t}.run)\n\t\tt.Run(\"unfinished single quote\", errTestCase{\n\t\t\tin:    `'hello`,\n\t\t\tmatch: matchUnexpectedEOFError(6),\n\t\t}.run)\n\t})\n}\n"
  },
  {
    "path": "selector/parser/denotations.go",
    "content": "package parser\n\nimport \"github.com/tomwright/dasel/v3/selector/lexer\"\n\n// left denotation tokens are tokens that expect a token to the left of them.\nvar leftDenotationTokens = []lexer.TokenKind{\n\tlexer.Plus,\n\tlexer.Dash,\n\tlexer.Slash,\n\tlexer.Star,\n\tlexer.Percent,\n\tlexer.Equal,\n\tlexer.NotEqual,\n\tlexer.GreaterThan,\n\tlexer.GreaterThanOrEqual,\n\tlexer.LessThan,\n\tlexer.LessThanOrEqual,\n\tlexer.And,\n\tlexer.Or,\n\tlexer.Like,\n\tlexer.NotLike,\n\tlexer.Equals,\n\tlexer.DoubleQuestionMark,\n}\n\n// right denotation tokens are tokens that expect a token to the right of them.\nvar rightDenotationTokens = []lexer.TokenKind{\n\tlexer.Exclamation, // Not operator\n}\n\ntype bindingPower int\n\nconst (\n\tbpDefault bindingPower = iota\n\tbpAssignment\n\tbpLogical\n\tbpEarlyLogical\n\tbpRelational\n\tbpAdditive\n\tbpMultiplicative\n\tbpUnary\n\tbpCall\n\tbpProperty\n\tbpLiteral\n)\n\nvar tokenBindingPowers = map[lexer.TokenKind]bindingPower{\n\tlexer.String: bpLiteral,\n\tlexer.Number: bpLiteral,\n\tlexer.Bool:   bpLiteral,\n\tlexer.Null:   bpLiteral,\n\n\tlexer.Variable:    bpProperty,\n\tlexer.Dot:         bpProperty,\n\tlexer.OpenBracket: bpProperty,\n\n\tlexer.OpenParen: bpCall,\n\n\tlexer.Exclamation: bpUnary,\n\n\tlexer.Star:    bpMultiplicative,\n\tlexer.Slash:   bpMultiplicative,\n\tlexer.Percent: bpMultiplicative,\n\n\tlexer.Plus: bpAdditive,\n\tlexer.Dash: bpAdditive,\n\n\tlexer.Equal:              bpRelational,\n\tlexer.NotEqual:           bpRelational,\n\tlexer.GreaterThan:        bpRelational,\n\tlexer.GreaterThanOrEqual: bpRelational,\n\tlexer.LessThan:           bpRelational,\n\tlexer.LessThanOrEqual:    bpRelational,\n\n\tlexer.And:     bpLogical,\n\tlexer.Or:      bpLogical,\n\tlexer.Like:    bpLogical,\n\tlexer.NotLike: bpLogical,\n\n\tlexer.DoubleQuestionMark: bpEarlyLogical,\n\n\tlexer.Equals: bpAssignment,\n}\n\nfunc getTokenBindingPower(t lexer.TokenKind) bindingPower {\n\tif bp, ok := tokenBindingPowers[t]; ok {\n\t\treturn bp\n\t}\n\treturn bpDefault\n}\n"
  },
  {
    "path": "selector/parser/error.go",
    "content": "package parser\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/tomwright/dasel/v3/selector/lexer\"\n)\n\ntype PositionalError struct {\n\tPosition int\n\tErr      error\n}\n\nfunc (e *PositionalError) Error() string {\n\treturn fmt.Sprintf(\"%v. Position %d.\", e.Err, e.Position)\n}\n\ntype UnexpectedTokenError struct {\n\tToken lexer.Token\n}\n\nfunc (e *UnexpectedTokenError) Error() string {\n\treturn fmt.Sprintf(\"failed to parse: unexpected token %v %q at position %d.\", e.Token.Kind, e.Token.Value, e.Token.Pos)\n}\n"
  },
  {
    "path": "selector/parser/parse_array.go",
    "content": "package parser\n\nimport (\n\t\"github.com/tomwright/dasel/v3/selector/ast\"\n\t\"github.com/tomwright/dasel/v3/selector/lexer\"\n)\n\nfunc parseArray(p *Parser) (ast.Expr, error) {\n\tif err := p.expect(lexer.OpenBracket); err != nil {\n\t\treturn nil, err\n\t}\n\tp.advance()\n\n\telements, err := p.parseExpressionsAsSlice(\n\t\tlexer.TokenKinds(lexer.CloseBracket),\n\t\tlexer.TokenKinds(lexer.Comma),\n\t\tfalse,\n\t\tbpDefault,\n\t\ttrue,\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tarr := ast.ArrayExpr{\n\t\tExprs: elements,\n\t}\n\n\tres, err := parseFollowingSymbol(p, arr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn res, nil\n}\n\n// parseIndexSquareBrackets parses square bracket array access.\n// E.g. [0], [0:1], [0:], [:2]\nfunc parseIndexSquareBrackets(p *Parser, expectIndex bool) (ast.Expr, error) {\n\t// Handle index (from bracket)\n\tif err := p.expect(lexer.OpenBracket); err != nil {\n\t\treturn nil, err\n\t}\n\n\tp.advance()\n\n\t// Spread [...]\n\tif p.current().IsKind(lexer.Spread) {\n\t\tp.advance()\n\t\tif err := p.expect(lexer.CloseBracket); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tp.advance()\n\t\treturn ast.SpreadExpr{}, nil\n\t}\n\n\tvar (\n\t\tstart ast.Expr\n\t\tend   ast.Expr\n\t\terr   error\n\t)\n\n\tif p.current().IsKind(lexer.Colon) {\n\t\tp.advance()\n\t\t// We have no start index\n\t\tend, err = p.parseExpression(bpDefault)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tp.advance()\n\t\treturn ast.RangeExpr{\n\t\t\tEnd: end,\n\t\t}, nil\n\t}\n\n\tstart, err = p.parseExpression(bpDefault)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif p.current().IsKind(lexer.CloseBracket) {\n\t\t// This is an index\n\t\tp.advance()\n\t\treturn ast.IndexExpr{\n\t\t\tIndex: start,\n\t\t}, nil\n\t}\n\tif expectIndex {\n\t\tif err := p.expect(lexer.CloseBracket); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tif err := p.expect(lexer.Colon); err != nil {\n\t\treturn nil, err\n\t}\n\n\tp.advance()\n\n\tif p.current().IsKind(lexer.CloseBracket) {\n\t\t// There is no end\n\t\tp.advance()\n\t\treturn ast.RangeExpr{\n\t\t\tStart: start,\n\t\t}, nil\n\t}\n\n\tend, err = p.parseExpression(bpDefault)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tp.advance()\n\treturn ast.RangeExpr{\n\t\tStart: start,\n\t\tEnd:   end,\n\t}, nil\n}\n"
  },
  {
    "path": "selector/parser/parse_branch.go",
    "content": "package parser\n\nimport (\n\t\"github.com/tomwright/dasel/v3/selector/ast\"\n\t\"github.com/tomwright/dasel/v3/selector/lexer\"\n)\n\nfunc parseBranch(p *Parser) (ast.Expr, error) {\n\tif err := p.expect(lexer.Branch); err != nil {\n\t\treturn nil, err\n\t}\n\n\tp.advance()\n\tif err := p.expect(lexer.OpenParen); err != nil {\n\t\treturn nil, err\n\t}\n\tp.advance()\n\n\texpressions, err := p.parseExpressionsAsSlice(\n\t\t[]lexer.TokenKind{lexer.CloseParen},\n\t\t[]lexer.TokenKind{lexer.Comma},\n\t\ttrue,\n\t\tbpDefault,\n\t\ttrue,\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn ast.BranchExpr{\n\t\tExprs: expressions,\n\t}, nil\n}\n"
  },
  {
    "path": "selector/parser/parse_each.go",
    "content": "package parser\n\nimport (\n\t\"github.com/tomwright/dasel/v3/selector/ast\"\n\t\"github.com/tomwright/dasel/v3/selector/lexer\"\n)\n\nfunc parseEach(p *Parser) (ast.Expr, error) {\n\tif err := p.expect(lexer.Each); err != nil {\n\t\treturn nil, err\n\t}\n\tp.advance()\n\n\texpr, err := p.parseExpressionsFromTo(\n\t\tlexer.OpenParen,\n\t\tlexer.CloseParen,\n\t\t[]lexer.TokenKind{},\n\t\ttrue,\n\t\tbpDefault,\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn ast.EachExpr{\n\t\tExpr: expr,\n\t}, nil\n}\n"
  },
  {
    "path": "selector/parser/parse_filter.go",
    "content": "package parser\n\nimport (\n\t\"github.com/tomwright/dasel/v3/selector/ast\"\n\t\"github.com/tomwright/dasel/v3/selector/lexer\"\n)\n\nfunc parseFilter(p *Parser) (ast.Expr, error) {\n\tif err := p.expect(lexer.Filter); err != nil {\n\t\treturn nil, err\n\t}\n\tp.advance()\n\n\texpr, err := p.parseExpressionsFromTo(\n\t\tlexer.OpenParen,\n\t\tlexer.CloseParen,\n\t\t[]lexer.TokenKind{},\n\t\ttrue,\n\t\tbpDefault,\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfilterExpr := ast.FilterExpr{\n\t\tExpr: expr,\n\t}\n\n\tres, err := parseFollowingSymbol(p, filterExpr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn res, nil\n}\n"
  },
  {
    "path": "selector/parser/parse_func.go",
    "content": "package parser\n\nimport (\n\t\"github.com/tomwright/dasel/v3/selector/ast\"\n\t\"github.com/tomwright/dasel/v3/selector/lexer\"\n)\n\nfunc parseFunc(p *Parser) (ast.Expr, error) {\n\tif err := p.expect(lexer.Symbol); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := p.expectN(1, lexer.OpenParen); err != nil {\n\t\treturn nil, err\n\t}\n\n\ttoken := p.current()\n\n\tp.advanceN(2)\n\targs, err := parseArgs(p)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn ast.CallExpr{\n\t\tFunction: token.Value,\n\t\tArgs:     args,\n\t}, nil\n}\n\nfunc parseArgs(p *Parser) (ast.Expressions, error) {\n\treturn p.parseExpressionsAsSlice(\n\t\t[]lexer.TokenKind{lexer.CloseParen},\n\t\t[]lexer.TokenKind{lexer.Comma},\n\t\tfalse,\n\t\tbpCall,\n\t\ttrue,\n\t)\n}\n"
  },
  {
    "path": "selector/parser/parse_group.go",
    "content": "package parser\n\nimport (\n\t\"github.com/tomwright/dasel/v3/selector/ast\"\n\t\"github.com/tomwright/dasel/v3/selector/lexer\"\n)\n\nfunc parseGroup(p *Parser) (ast.Expr, error) {\n\tif err := p.expect(lexer.OpenParen); err != nil {\n\t\treturn nil, err\n\t}\n\tp.advance() // skip the open paren\n\n\treturn p.parseExpressions(\n\t\t[]lexer.TokenKind{lexer.CloseParen},\n\t\t[]lexer.TokenKind{},\n\t\ttrue,\n\t\tbpDefault,\n\t\ttrue,\n\t)\n}\n"
  },
  {
    "path": "selector/parser/parse_if.go",
    "content": "package parser\n\nimport (\n\t\"github.com/tomwright/dasel/v3/selector/ast\"\n\t\"github.com/tomwright/dasel/v3/selector/lexer\"\n)\n\nfunc parseIfBody(p *Parser) (ast.Expr, error) {\n\treturn p.parseExpressionsFromTo(lexer.OpenCurly, lexer.CloseCurly, []lexer.TokenKind{}, true, bpDefault)\n}\n\nfunc parseIfCondition(p *Parser) (ast.Expr, error) {\n\treturn p.parseExpressionsFromTo(lexer.OpenParen, lexer.CloseParen, []lexer.TokenKind{}, true, bpDefault)\n}\n\nfunc parseIf(p *Parser) (ast.Expr, error) {\n\tif err := p.expect(lexer.If); err != nil {\n\t\treturn nil, err\n\t}\n\tp.advance()\n\n\tvar exprs []*ast.ConditionalExpr\n\n\tprocess := func(parseCond bool) error {\n\t\tvar err error\n\t\tvar cond ast.Expr\n\t\tif parseCond {\n\t\t\tcond, err = parseIfCondition(p)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\tbody, err := parseIfBody(p)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\texprs = append(exprs, &ast.ConditionalExpr{\n\t\t\tCond: cond,\n\t\t\tThen: body,\n\t\t})\n\n\t\treturn nil\n\t}\n\n\tif err := process(true); err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor p.current().IsKind(lexer.ElseIf) {\n\t\tp.advance()\n\n\t\tif err := process(true); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tif p.current().IsKind(lexer.Else) {\n\t\tp.advance()\n\n\t\tbody, err := parseIfBody(p)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\texprs[len(exprs)-1].Else = body\n\t}\n\n\tfor i := len(exprs) - 1; i >= 0; i-- {\n\t\tif i > 0 {\n\t\t\texprs[i-1].Else = *exprs[i]\n\t\t}\n\t}\n\n\treturn *exprs[0], nil\n}\n\nfunc (p *Parser) parseExpressionsFromTo(\n\tfrom lexer.TokenKind,\n\tto lexer.TokenKind,\n\tsplitOn []lexer.TokenKind,\n\trequireExpressions bool,\n\tbp bindingPower,\n) (ast.Expr, error) {\n\tif err := p.expect(from); err != nil {\n\t\treturn nil, err\n\t}\n\tp.advance()\n\n\tt, err := p.parseExpressions(\n\t\t[]lexer.TokenKind{to},\n\t\tsplitOn,\n\t\trequireExpressions,\n\t\tbp,\n\t\ttrue,\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn t, nil\n}\n"
  },
  {
    "path": "selector/parser/parse_literal.go",
    "content": "package parser\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/tomwright/dasel/v3/selector/ast\"\n\t\"github.com/tomwright/dasel/v3/selector/lexer\"\n)\n\nfunc parseStringLiteral(p *Parser) (ast.Expr, error) {\n\ttoken := p.current()\n\tp.advance()\n\treturn ast.StringExpr{\n\t\tValue: token.Value,\n\t}, nil\n}\n\nfunc parseBoolLiteral(p *Parser) (ast.Expr, error) {\n\ttoken := p.current()\n\tp.advance()\n\treturn ast.BoolExpr{\n\t\tValue: strings.EqualFold(token.Value, \"true\"),\n\t}, nil\n}\n\nfunc parseSpread(p *Parser) (ast.Expr, error) {\n\tp.advance()\n\treturn ast.SpreadExpr{}, nil\n}\n\nfunc parseNumberLiteral(p *Parser) (ast.Expr, error) {\n\ttoken := p.current()\n\n\tvar negative bool\n\tif token.IsKind(lexer.Dash) {\n\t\tnegative = true\n\t\ttoken = p.advance()\n\t}\n\n\tnext := p.advance()\n\tswitch {\n\tcase next.IsKind(lexer.Symbol) && strings.EqualFold(next.Value, \"f\"):\n\t\tvalue, err := strconv.ParseFloat(token.Value, 64)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tp.advance()\n\t\tif negative {\n\t\t\tvalue = -value\n\t\t}\n\t\treturn ast.NumberFloatExpr{\n\t\t\tValue: value,\n\t\t}, nil\n\n\tcase strings.Contains(token.Value, \".\"):\n\t\tvalue, err := strconv.ParseFloat(token.Value, 64)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif negative {\n\t\t\tvalue = -value\n\t\t}\n\t\treturn ast.NumberFloatExpr{\n\t\t\tValue: value,\n\t\t}, nil\n\n\tdefault:\n\t\tvalue, err := strconv.ParseInt(token.Value, 10, 64)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif negative {\n\t\t\tvalue = -value\n\t\t}\n\t\treturn ast.NumberIntExpr{\n\t\t\tValue: value,\n\t\t}, nil\n\t}\n}\n\nfunc parseRegexPattern(p *Parser) (ast.Expr, error) {\n\tif err := p.expect(lexer.RegexPattern); err != nil {\n\t\treturn nil, err\n\t}\n\n\tpattern := p.current()\n\n\tp.advance()\n\n\tcomp, err := regexp.Compile(pattern.Value)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to compile regexp pattern: %w\", err)\n\t}\n\n\treturn ast.RegexExpr{\n\t\tRegex: comp,\n\t}, nil\n}\n"
  },
  {
    "path": "selector/parser/parse_map.go",
    "content": "package parser\n\nimport (\n\t\"github.com/tomwright/dasel/v3/selector/ast\"\n\t\"github.com/tomwright/dasel/v3/selector/lexer\"\n)\n\nfunc parseMap(p *Parser) (ast.Expr, error) {\n\tif err := p.expect(lexer.Map); err != nil {\n\t\treturn nil, err\n\t}\n\tp.advance()\n\n\texpr, err := p.parseExpressionsFromTo(\n\t\tlexer.OpenParen,\n\t\tlexer.CloseParen,\n\t\t[]lexer.TokenKind{},\n\t\ttrue,\n\t\tbpDefault,\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tmapExpr := ast.MapExpr{\n\t\tExpr: expr,\n\t}\n\n\tres, err := parseFollowingSymbol(p, mapExpr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn res, nil\n}\n"
  },
  {
    "path": "selector/parser/parse_object.go",
    "content": "package parser\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/tomwright/dasel/v3/selector/ast\"\n\t\"github.com/tomwright/dasel/v3/selector/lexer\"\n)\n\nfunc parseObject(p *Parser) (ast.Expr, error) {\n\n\t//p.parseExpressionsFromTo(\n\t//\tlexer.OpenCurly,\n\t//\tlexer.CloseCurly,\n\t//\tlexer.TokenKinds(lexer.Comma),\n\t//\tfalse,\n\t//\tbpDefault,\n\t//)\n\n\tif err := p.expect(lexer.OpenCurly); err != nil {\n\t\treturn nil, err\n\t}\n\tp.advance()\n\n\tpairs := make([]ast.KeyValue, 0)\n\n\tparseKeyValue := func() (ast.KeyValue, error) {\n\t\tvar res ast.KeyValue\n\t\tk, err := p.parseExpression(bpDefault)\n\t\tif err != nil {\n\t\t\treturn res, err\n\t\t}\n\n\t\t// Handle spread\n\t\tkSpread, isSpread := ast.LastAsType[ast.SpreadExpr](k)\n\t\tif isSpread {\n\t\t\tres.Key = kSpread\n\t\t\tres.Value = ast.RemoveLast(k)\n\t\t\tif err := p.expect(lexer.Comma, lexer.CloseCurly); err != nil {\n\t\t\t\treturn res, err\n\t\t\t}\n\t\t\treturn res, nil\n\t\t}\n\n\t\tkProp, kIsProp := ast.AsType[ast.PropertyExpr](k)\n\t\tif p.current().IsKind(lexer.Comma, lexer.CloseCurly) {\n\t\t\tif !kIsProp {\n\t\t\t\treturn res, fmt.Errorf(\"invalid shorthand property\")\n\t\t\t}\n\t\t\tres.Key = kProp.Property\n\t\t\tres.Value = kProp\n\t\t\treturn res, nil\n\t\t}\n\n\t\t// Handle unquoted keys\n\t\tif kIsProp {\n\t\t\tif kStr, ok := ast.AsType[ast.StringExpr](kProp.Property); ok {\n\t\t\t\tk = kStr\n\t\t\t}\n\t\t}\n\n\t\tif err := p.expect(lexer.Colon); err != nil {\n\t\t\treturn res, err\n\t\t}\n\t\tp.advance()\n\n\t\tv, err := p.parseExpression(bpDefault)\n\t\tif err != nil {\n\t\t\treturn res, err\n\t\t}\n\n\t\tres.Key = k\n\t\tres.Value = v\n\t\treturn res, nil\n\t}\n\n\tfor !p.current().IsKind(lexer.CloseCurly) {\n\t\tkv, err := parseKeyValue()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tpairs = append(pairs, kv)\n\n\t\tif err := p.expect(lexer.Comma, lexer.CloseCurly); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"expected end of object element: %w\", err)\n\t\t}\n\t\tif p.current().IsKind(lexer.Comma) {\n\t\t\tp.advance()\n\t\t}\n\t}\n\n\tif err := p.expect(lexer.CloseCurly); err != nil {\n\t\treturn nil, fmt.Errorf(\"expected end of object: %w\", err)\n\t}\n\tp.advance()\n\n\tobj := ast.ObjectExpr{\n\t\tPairs: pairs,\n\t}\n\n\tres, err := parseFollowingSymbol(p, obj)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn res, nil\n}\n"
  },
  {
    "path": "selector/parser/parse_recursive_descent.go",
    "content": "package parser\n\nimport (\n\t\"github.com/tomwright/dasel/v3/selector/ast\"\n\t\"github.com/tomwright/dasel/v3/selector/lexer\"\n)\n\nfunc parseRecursiveDescent(p *Parser) (ast.Expr, error) {\n\tif err := p.expect(lexer.RecursiveDescent); err != nil {\n\t\treturn nil, err\n\t}\n\tp.advance()\n\n\tcur := p.current()\n\n\tres := ast.RecursiveDescentExpr{}\n\n\tvar err error\n\tswitch cur.Kind {\n\tcase lexer.Star:\n\t\tres.IsWildcard = true\n\t\tp.advance()\n\tcase lexer.OpenBracket:\n\t\tres.Expr, err = parseIndexSquareBrackets(p, true)\n\tdefault:\n\t\tres.Expr, err = parseSymbol(p, false, false)\n\t}\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn res, nil\n}\n"
  },
  {
    "path": "selector/parser/parse_search.go",
    "content": "package parser\n\nimport (\n\t\"github.com/tomwright/dasel/v3/selector/ast\"\n\t\"github.com/tomwright/dasel/v3/selector/lexer\"\n)\n\nfunc parseSearch(p *Parser) (ast.Expr, error) {\n\tif err := p.expect(lexer.Search); err != nil {\n\t\treturn nil, err\n\t}\n\tp.advance()\n\n\texpr, err := p.parseExpressionsFromTo(\n\t\tlexer.OpenParen,\n\t\tlexer.CloseParen,\n\t\t[]lexer.TokenKind{},\n\t\ttrue,\n\t\tbpDefault,\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn ast.SearchExpr{\n\t\tExpr: expr,\n\t}, nil\n}\n"
  },
  {
    "path": "selector/parser/parse_sort_by.go",
    "content": "package parser\n\nimport (\n\t\"github.com/tomwright/dasel/v3/selector/ast\"\n\t\"github.com/tomwright/dasel/v3/selector/lexer\"\n)\n\nfunc parseSortBy(p *Parser) (ast.Expr, error) {\n\tif err := p.expect(lexer.SortBy); err != nil {\n\t\treturn nil, err\n\t}\n\tp.advance()\n\n\tif err := p.expect(lexer.OpenParen); err != nil {\n\t\treturn nil, err\n\t}\n\tp.advance()\n\n\tsortExpr, err := p.parseExpressions(\n\t\tlexer.TokenKinds(lexer.CloseParen, lexer.Comma),\n\t\tnil,\n\t\ttrue,\n\t\tbpDefault,\n\t\tfalse,\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tres := ast.SortByExpr{\n\t\tExpr:       sortExpr,\n\t\tDescending: false,\n\t}\n\n\tif p.current().IsKind(lexer.CloseParen) {\n\t\tp.advance()\n\t\treturn res, nil\n\t}\n\n\tif err := p.expect(lexer.Comma); err != nil {\n\t\treturn nil, err\n\t}\n\tp.advance()\n\n\tif err := p.expect(lexer.Asc, lexer.Desc); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif p.current().IsKind(lexer.Desc) {\n\t\tres.Descending = true\n\t}\n\n\tp.advance()\n\tif err := p.expect(lexer.CloseParen); err != nil {\n\t\treturn nil, err\n\t}\n\tp.advance()\n\n\treturn res, nil\n}\n"
  },
  {
    "path": "selector/parser/parse_symbol.go",
    "content": "package parser\n\nimport (\n\t\"github.com/tomwright/dasel/v3/selector/ast\"\n\t\"github.com/tomwright/dasel/v3/selector/lexer\"\n)\n\n// parseFollowingSymbols deals with the expressions following symbols/variables, e.g.\n// $this[0][1]['name']\n// foo['bar']['baz'][1]\nfunc parseFollowingSymbol(p *Parser, prev ast.Expr) (ast.Expr, error) {\n\tres := ast.Expressions{prev}\n\n\tfor p.hasToken() {\n\t\tif p.current().IsKind(lexer.Spread) {\n\t\t\tp.advanceN(1)\n\t\t\tres = append(res, ast.SpreadExpr{})\n\t\t\tcontinue\n\t\t}\n\n\t\t// String based indexes\n\t\tif p.current().IsKind(lexer.OpenBracket) {\n\n\t\t\tif p.peekN(1).IsKind(lexer.Spread) && p.peekN(2).IsKind(lexer.CloseBracket) {\n\t\t\t\tp.advanceN(3)\n\t\t\t\tres = append(res, ast.SpreadExpr{})\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif p.peekN(1).IsKind(lexer.Star) && p.peekN(2).IsKind(lexer.CloseBracket) {\n\t\t\t\tp.advanceN(3)\n\t\t\t\tres = append(res, ast.SpreadExpr{})\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\te, err := parseIndexSquareBrackets(p, false)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tswitch ex := e.(type) {\n\t\t\tcase ast.RangeExpr:\n\t\t\t\tres = append(res, ex)\n\t\t\tcase ast.IndexExpr:\n\t\t\t\t// Convert this to a property expr. This property executor deals\n\t\t\t\t// with maps + arrays.\n\t\t\t\tres = append(res, ast.PropertyExpr{\n\t\t\t\t\tProperty: ex.Index,\n\t\t\t\t})\n\t\t\t}\n\n\t\t\t//e, err := p.parseExpressionsFromTo(lexer.OpenBracket, lexer.CloseBracket, nil, true, bpDefault)\n\t\t\t//if err != nil {\n\t\t\t//\treturn nil, err\n\t\t\t//}\n\t\t\t//res = append(res, ast.PropertyExpr{\n\t\t\t//\tProperty: e,\n\t\t\t//})\n\t\t\tcontinue\n\t\t}\n\n\t\tbreak\n\t}\n\n\treturn ast.ChainExprs(res...), nil\n}\n\nfunc parseSymbol(p *Parser, withFollowing bool, allowFunc bool) (ast.Expr, error) {\n\ttoken := p.current()\n\n\tnext := p.peek()\n\n\t// Handle functions\n\tif next.IsKind(lexer.OpenParen) && allowFunc {\n\t\treturn parseFunc(p)\n\t}\n\n\tprop := ast.PropertyExpr{\n\t\tProperty: ast.StringExpr{Value: token.Value},\n\t}\n\n\tp.advance()\n\n\tif withFollowing {\n\t\tres, err := parseFollowingSymbol(p, prop)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn res, nil\n\t}\n\n\treturn prop, nil\n}\n"
  },
  {
    "path": "selector/parser/parse_variable.go",
    "content": "package parser\n\nimport (\n\t\"github.com/tomwright/dasel/v3/selector/ast\"\n)\n\nfunc parseVariable(p *Parser) (ast.Expr, error) {\n\ttoken := p.current()\n\n\tprop := ast.VariableExpr{\n\t\tName: token.Value,\n\t}\n\n\tp.advance()\n\n\tres, err := parseFollowingSymbol(p, prop)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn res, nil\n}\n"
  },
  {
    "path": "selector/parser/parser.go",
    "content": "package parser\n\nimport (\n\t\"slices\"\n\n\t\"github.com/tomwright/dasel/v3/selector/ast\"\n\t\"github.com/tomwright/dasel/v3/selector/lexer\"\n)\n\ntype Parser struct {\n\ttokens lexer.Tokens\n\ti      int\n}\n\nfunc NewParser(tokens lexer.Tokens) *Parser {\n\treturn &Parser{\n\t\ttokens: tokens,\n\t}\n}\n\nfunc (p *Parser) parseExpressionsAsSlice(\n\tbreakOn []lexer.TokenKind,\n\tsplitOn []lexer.TokenKind,\n\trequireExpressions bool,\n\tbp bindingPower,\n\tadvanceOnBreak bool,\n) (ast.Expressions, error) {\n\tvar finalExpr ast.Expressions\n\tvar current ast.Expressions\n\tfor p.hasToken() {\n\t\tif p.current().IsKind(breakOn...) {\n\t\t\tif advanceOnBreak {\n\t\t\t\tp.advance()\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t\tif p.current().IsKind(splitOn...) {\n\t\t\tif requireExpressions && len(current) == 0 {\n\t\t\t\treturn nil, &UnexpectedTokenError{Token: p.current()}\n\t\t\t}\n\t\t\tfinalExpr = append(finalExpr, ast.ChainExprs(current...))\n\t\t\tcurrent = nil\n\t\t\tp.advance()\n\t\t\tcontinue\n\t\t}\n\t\texpr, err := p.parseExpression(bp)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tcurrent = append(current, expr)\n\t}\n\n\tif len(current) > 0 {\n\t\tfinalExpr = append(finalExpr, ast.ChainExprs(current...))\n\t}\n\n\tif len(finalExpr) == 0 {\n\t\treturn nil, nil\n\t}\n\n\treturn finalExpr, nil\n}\n\nfunc (p *Parser) parseExpressions(\n\tbreakOn []lexer.TokenKind,\n\tsplitOn []lexer.TokenKind,\n\trequireExpressions bool,\n\tbp bindingPower,\n\tadvanceOnBreak bool,\n) (ast.Expr, error) {\n\texpressions, err := p.parseExpressionsAsSlice(breakOn, splitOn, requireExpressions, bp, advanceOnBreak)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tswitch len(expressions) {\n\tcase 0:\n\t\treturn nil, nil\n\tdefault:\n\t\treturn ast.ChainExprs(expressions...), nil\n\t}\n}\n\nfunc (p *Parser) Parse() (ast.Expr, error) {\n\treturn p.parseExpressions([]lexer.TokenKind{lexer.EOF}, []lexer.TokenKind{lexer.Semicolon}, true, bpDefault, true)\n}\n\nfunc (p *Parser) parseExpression(bp bindingPower) (left ast.Expr, err error) {\n\tif p.hasToken() && slices.Contains(rightDenotationTokens, p.current().Kind) {\n\t\tunary := ast.UnaryExpr{\n\t\t\tOperator: p.current(),\n\t\t\tRight:    nil,\n\t\t}\n\t\tp.advance()\n\t\texpr, err := p.parseExpression(getTokenBindingPower(unary.Operator.Kind))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tp.advance()\n\t\tunary.Right = expr\n\t\tleft = unary\n\t}\n\n\tif !p.hasToken() {\n\t\treturn\n\t}\n\n\tswitch p.current().Kind {\n\tcase lexer.String:\n\t\tleft, err = parseStringLiteral(p)\n\tcase lexer.Number:\n\t\tleft, err = parseNumberLiteral(p)\n\tcase lexer.Dash:\n\t\tif p.peek().Kind == lexer.Number {\n\t\t\tleft, err = parseNumberLiteral(p)\n\t\t} else {\n\t\t\treturn nil, &UnexpectedTokenError{\n\t\t\t\tToken: p.current(),\n\t\t\t}\n\t\t}\n\tcase lexer.Symbol:\n\t\tleft, err = parseSymbol(p, true, true)\n\tcase lexer.OpenBracket:\n\t\tleft, err = parseArray(p)\n\tcase lexer.OpenCurly:\n\t\tleft, err = parseObject(p)\n\tcase lexer.Bool:\n\t\tleft, err = parseBoolLiteral(p)\n\tcase lexer.Spread:\n\t\tleft, err = parseSpread(p)\n\tcase lexer.Variable:\n\t\tleft, err = parseVariable(p)\n\tcase lexer.OpenParen:\n\t\tleft, err = parseGroup(p)\n\tcase lexer.If:\n\t\tleft, err = parseIf(p)\n\tcase lexer.Branch:\n\t\tleft, err = parseBranch(p)\n\tcase lexer.Map:\n\t\tleft, err = parseMap(p)\n\tcase lexer.Each:\n\t\tleft, err = parseEach(p)\n\tcase lexer.Filter:\n\t\tleft, err = parseFilter(p)\n\tcase lexer.Search:\n\t\tleft, err = parseSearch(p)\n\tcase lexer.RecursiveDescent:\n\t\tleft, err = parseRecursiveDescent(p)\n\tcase lexer.RegexPattern:\n\t\tleft, err = parseRegexPattern(p)\n\tcase lexer.SortBy:\n\t\tleft, err = parseSortBy(p)\n\tcase lexer.Null:\n\t\tleft = ast.NullExpr{}\n\t\terr = nil\n\t\tp.advance()\n\tdefault:\n\t\treturn nil, &UnexpectedTokenError{\n\t\t\tToken: p.current(),\n\t\t}\n\t}\n\n\tif err != nil {\n\t\treturn\n\t}\n\n\ttoChain := ast.Expressions{left}\n\t// Ensure dot separated chains are parsed as a sequence of expressions\n\tif p.hasToken() && p.current().IsKind(lexer.Dot) {\n\t\tfor p.hasToken() && p.current().IsKind(lexer.Dot) {\n\t\t\tp.advance()\n\t\t\texpr, err := p.parseExpression(bpUnary)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\ttoChain = append(toChain, expr)\n\t\t}\n\t}\n\n\t// Handle spread\n\tif p.hasToken() && p.current().IsKind(lexer.Spread) {\n\t\texpr, err := p.parseExpression(bpLiteral)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ttoChain = append(toChain, expr)\n\t}\n\n\tif len(toChain) > 1 {\n\t\tleft = ast.ChainExprs(toChain...)\n\t}\n\n\t// Handle binding powers\n\tfor p.hasToken() && slices.Contains(leftDenotationTokens, p.current().Kind) && getTokenBindingPower(p.current().Kind) > bp {\n\t\tleft, err = parseBinary(p, left)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (p *Parser) hasToken() bool {\n\treturn p.hasTokenN(0)\n}\n\nfunc (p *Parser) hasTokenN(n int) bool {\n\treturn p.i+n < len(p.tokens) && !p.tokens[p.i+n].IsKind(lexer.EOF)\n}\n\nfunc (p *Parser) current() lexer.Token {\n\tif p.hasToken() {\n\t\treturn p.tokens[p.i]\n\t}\n\treturn lexer.Token{Kind: lexer.EOF}\n}\n\nfunc (p *Parser) advance() lexer.Token {\n\treturn p.advanceN(1)\n}\n\nfunc (p *Parser) advanceN(n int) lexer.Token {\n\tp.i += n\n\treturn p.current()\n}\n\nfunc (p *Parser) peek() lexer.Token {\n\treturn p.peekN(1)\n}\n\nfunc (p *Parser) peekN(n int) lexer.Token {\n\tif p.i+n >= len(p.tokens) {\n\t\treturn lexer.Token{Kind: lexer.EOF}\n\t}\n\treturn p.tokens[p.i+n]\n}\n\nfunc (p *Parser) expect(kind ...lexer.TokenKind) error {\n\tt := p.current()\n\tif p.current().IsKind(kind...) {\n\t\treturn nil\n\t}\n\treturn &UnexpectedTokenError{\n\t\tToken: t,\n\t}\n}\n\nfunc (p *Parser) expectN(n int, kind ...lexer.TokenKind) error {\n\tt := p.peekN(n)\n\tif t.IsKind(kind...) {\n\t\treturn nil\n\t}\n\treturn &UnexpectedTokenError{\n\t\tToken: t,\n\t}\n}\n"
  },
  {
    "path": "selector/parser/parser_binary.go",
    "content": "package parser\n\nimport (\n\t\"github.com/tomwright/dasel/v3/selector/ast\"\n)\n\nfunc parseBinary(p *Parser, left ast.Expr) (ast.Expr, error) {\n\tif err := p.expect(leftDenotationTokens...); err != nil {\n\t\treturn nil, err\n\t}\n\toperator := p.current()\n\tp.advance()\n\tright, err := p.parseExpression(getTokenBindingPower(operator.Kind))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t//if l, ok := left.(ast.BinaryExpr); ok && l.Operator.Kind == lexer.DoubleQuestionMark {\n\t//\tif r, ok := right.(ast.BinaryExpr); ok && r.Operator.Kind == lexer.DoubleQuestionMark {\n\t//\t\treturn ast.BinaryExpr{\n\t//\t\t\tLeft:     l.Left,\n\t//\t\t\tOperator: l.Operator,\n\t//\t\t\tRight: ast.BinaryExpr{\n\t//\t\t\t\tLeft:     l.Right,\n\t//\t\t\t\tOperator: r.Operator,\n\t//\t\t\t\tRight:    r.Right,\n\t//\t\t\t},\n\t//\t\t}, nil\n\t//\t}\n\t//}\n\t//\n\t//if r, ok := right.(ast.BinaryExpr); ok && r.Operator.Kind == lexer.DoubleQuestionMark {\n\t//\treturn ast.BinaryExpr{\n\t//\t\tLeft: ast.BinaryExpr{\n\t//\t\t\tLeft:     left,\n\t//\t\t\tOperator: operator,\n\t//\t\t\tRight:    r.Left,\n\t//\t\t},\n\t//\t\tOperator: r.Operator,\n\t//\t\tRight:    r.Right,\n\t//\t}, nil\n\t//}\n\t//\n\t//if l, ok := left.(ast.BinaryExpr); ok && l.Operator.Kind == lexer.DoubleQuestionMark {\n\t//\treturn ast.BinaryExpr{\n\t//\t\tLeft:     l.Left,\n\t//\t\tOperator: l.Operator,\n\t//\t\tRight: ast.BinaryExpr{\n\t//\t\t\tLeft:     l.Right,\n\t//\t\t\tOperator: operator,\n\t//\t\t\tRight:    right,\n\t//\t\t},\n\t//\t}, nil\n\t//}\n\n\treturn ast.BinaryExpr{\n\t\tLeft:     left,\n\t\tOperator: operator,\n\t\tRight:    right,\n\t}, nil\n}\n"
  },
  {
    "path": "selector/parser/parser_test.go",
    "content": "package parser_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/tomwright/dasel/v3/selector/ast\"\n\t\"github.com/tomwright/dasel/v3/selector/lexer\"\n\t\"github.com/tomwright/dasel/v3/selector/parser\"\n)\n\ntype happyTestCase struct {\n\tinput    string\n\texpected ast.Expr\n}\n\nfunc (tc happyTestCase) run(t *testing.T) {\n\ttokens, err := lexer.NewTokenizer(tc.input).Tokenize()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tgot, err := parser.NewParser(tokens).Parse()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif !cmp.Equal(tc.expected, got) {\n\t\tt.Errorf(\"unexpected result: %s\", cmp.Diff(tc.expected, got))\n\t}\n}\n\nfunc TestParser_Parse_HappyPath(t *testing.T) {\n\tt.Run(\"branching\", func(t *testing.T) {\n\t\tt.Run(\"two branches\", happyTestCase{\n\t\t\tinput: `branch(\"hello\", len(\"world\"))`,\n\t\t\texpected: ast.BranchExprs(\n\t\t\t\tast.StringExpr{Value: \"hello\"},\n\t\t\t\tast.ChainExprs(\n\t\t\t\t\tast.CallExpr{\n\t\t\t\t\t\tFunction: \"len\",\n\t\t\t\t\t\tArgs:     ast.Expressions{ast.StringExpr{Value: \"world\"}},\n\t\t\t\t\t},\n\t\t\t\t),\n\t\t\t),\n\t\t}.run)\n\t\tt.Run(\"three branches\", happyTestCase{\n\t\t\tinput: `branch(\"foo\", \"bar\", \"baz\")`,\n\t\t\texpected: ast.BranchExprs(\n\t\t\t\tast.StringExpr{Value: \"foo\"},\n\t\t\t\tast.StringExpr{Value: \"bar\"},\n\t\t\t\tast.StringExpr{Value: \"baz\"},\n\t\t\t),\n\t\t}.run)\n\t})\n\n\tt.Run(\"literal access\", func(t *testing.T) {\n\t\tt.Run(\"string\", happyTestCase{\n\t\t\tinput:    `\"hello world\"`,\n\t\t\texpected: ast.StringExpr{Value: \"hello world\"},\n\t\t}.run)\n\t\tt.Run(\"int\", happyTestCase{\n\t\t\tinput:    \"42\",\n\t\t\texpected: ast.NumberIntExpr{Value: 42},\n\t\t}.run)\n\t\tt.Run(\"float\", happyTestCase{\n\t\t\tinput:    \"42.1\",\n\t\t\texpected: ast.NumberFloatExpr{Value: 42.1},\n\t\t}.run)\n\t\tt.Run(\"whole number float\", happyTestCase{\n\t\t\tinput:    \"42f\",\n\t\t\texpected: ast.NumberFloatExpr{Value: 42},\n\t\t}.run)\n\t\tt.Run(\"bool true lowercase\", happyTestCase{\n\t\t\tinput:    \"true\",\n\t\t\texpected: ast.BoolExpr{Value: true},\n\t\t}.run)\n\t\tt.Run(\"bool true uppercase\", happyTestCase{\n\t\t\tinput:    \"TRUE\",\n\t\t\texpected: ast.BoolExpr{Value: true},\n\t\t}.run)\n\t\tt.Run(\"bool true mixed case\", happyTestCase{\n\t\t\tinput:    \"TrUe\",\n\t\t\texpected: ast.BoolExpr{Value: true},\n\t\t}.run)\n\t\tt.Run(\"bool false lowercase\", happyTestCase{\n\t\t\tinput:    \"false\",\n\t\t\texpected: ast.BoolExpr{Value: false},\n\t\t}.run)\n\t\tt.Run(\"bool false uppercase\", happyTestCase{\n\t\t\tinput:    \"FALSE\",\n\t\t\texpected: ast.BoolExpr{Value: false},\n\t\t}.run)\n\t\tt.Run(\"bool false mixed case\", happyTestCase{\n\t\t\tinput:    \"FaLsE\",\n\t\t\texpected: ast.BoolExpr{Value: false},\n\t\t}.run)\n\t})\n\n\tt.Run(\"property access\", func(t *testing.T) {\n\t\tt.Run(\"single property access\", happyTestCase{\n\t\t\tinput:    \"foo\",\n\t\t\texpected: ast.PropertyExpr{Property: ast.StringExpr{Value: \"foo\"}},\n\t\t}.run)\n\t\tt.Run(\"chained property access\", happyTestCase{\n\t\t\tinput: \"foo.bar\",\n\t\t\texpected: ast.ChainExprs(\n\t\t\t\tast.PropertyExpr{Property: ast.StringExpr{Value: \"foo\"}},\n\t\t\t\tast.PropertyExpr{Property: ast.StringExpr{Value: \"bar\"}},\n\t\t\t),\n\t\t}.run)\n\t\tt.Run(\"property access spread\", happyTestCase{\n\t\t\tinput: \"foo...\",\n\t\t\texpected: ast.ChainExprs(\n\t\t\t\tast.PropertyExpr{Property: ast.StringExpr{Value: \"foo\"}},\n\t\t\t\tast.SpreadExpr{},\n\t\t\t),\n\t\t}.run)\n\t\tt.Run(\"property access spread into property access\", happyTestCase{\n\t\t\tinput: \"foo....bar\",\n\t\t\texpected: ast.ChainExprs(\n\t\t\t\tast.ChainExprs(\n\t\t\t\t\tast.PropertyExpr{Property: ast.StringExpr{Value: \"foo\"}},\n\t\t\t\t\tast.SpreadExpr{},\n\t\t\t\t),\n\t\t\t\tast.PropertyExpr{Property: ast.StringExpr{Value: \"bar\"}},\n\t\t\t),\n\t\t}.run)\n\t})\n\n\tt.Run(\"array access\", func(t *testing.T) {\n\t\tt.Run(\"chained with filter\", happyTestCase{\n\t\t\tinput: \"filter(name == \\\"foo\\\")[0]\",\n\t\t\texpected: ast.ChainExprs(\n\t\t\t\tast.FilterExpr{\n\t\t\t\t\tExpr: ast.BinaryExpr{\n\t\t\t\t\t\tLeft: ast.PropertyExpr{Property: ast.StringExpr{Value: \"name\"}},\n\t\t\t\t\t\tOperator: lexer.Token{\n\t\t\t\t\t\t\tKind:  lexer.Equal,\n\t\t\t\t\t\t\tValue: \"==\",\n\t\t\t\t\t\t\tPos:   12,\n\t\t\t\t\t\t\tLen:   2,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tRight: ast.StringExpr{Value: \"foo\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tast.PropertyExpr{Property: ast.NumberIntExpr{Value: 0}},\n\t\t\t),\n\t\t}.run)\n\t\tt.Run(\"chained with map\", happyTestCase{\n\t\t\tinput: \"map(foo)[0]\",\n\t\t\texpected: ast.ChainExprs(\n\t\t\t\tast.MapExpr{\n\t\t\t\t\tExpr: ast.PropertyExpr{Property: ast.StringExpr{Value: \"foo\"}},\n\t\t\t\t},\n\t\t\t\tast.PropertyExpr{Property: ast.NumberIntExpr{Value: 0}},\n\t\t\t),\n\t\t}.run)\n\t\tt.Run(\"root array\", func(t *testing.T) {\n\t\t\tt.Run(\"index\", happyTestCase{\n\t\t\t\tinput: \"$this[1]\",\n\t\t\t\texpected: ast.ChainExprs(\n\t\t\t\t\tast.VariableExpr{Name: \"this\"},\n\t\t\t\t\tast.PropertyExpr{Property: ast.NumberIntExpr{Value: 1}},\n\t\t\t\t),\n\t\t\t}.run)\n\t\t\tt.Run(\"range\", func(t *testing.T) {\n\t\t\t\tt.Run(\"start and end funcs\", happyTestCase{\n\t\t\t\t\tinput: \"$this[calcStart(1):calcEnd()]\",\n\t\t\t\t\texpected: ast.ChainExprs(\n\t\t\t\t\t\tast.VariableExpr{Name: \"this\"},\n\t\t\t\t\t\tast.RangeExpr{\n\t\t\t\t\t\t\tStart: ast.CallExpr{\n\t\t\t\t\t\t\t\tFunction: \"calcStart\",\n\t\t\t\t\t\t\t\tArgs: ast.Expressions{\n\t\t\t\t\t\t\t\t\tast.NumberIntExpr{Value: 1},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tEnd: ast.CallExpr{\n\t\t\t\t\t\t\t\tFunction: \"calcEnd\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t),\n\t\t\t\t}.run)\n\t\t\t\tt.Run(\"start and end\", happyTestCase{\n\t\t\t\t\tinput: \"$this[5:10]\",\n\t\t\t\t\texpected: ast.ChainExprs(\n\t\t\t\t\t\tast.VariableExpr{Name: \"this\"},\n\t\t\t\t\t\tast.RangeExpr{Start: ast.NumberIntExpr{Value: 5}, End: ast.NumberIntExpr{Value: 10}},\n\t\t\t\t\t),\n\t\t\t\t}.run)\n\t\t\t\tt.Run(\"start\", happyTestCase{\n\t\t\t\t\tinput: \"$this[5:]\",\n\t\t\t\t\texpected: ast.ChainExprs(\n\t\t\t\t\t\tast.VariableExpr{Name: \"this\"},\n\t\t\t\t\t\tast.RangeExpr{Start: ast.NumberIntExpr{Value: 5}},\n\t\t\t\t\t),\n\t\t\t\t}.run)\n\t\t\t\tt.Run(\"end\", happyTestCase{\n\t\t\t\t\tinput: \"$this[:10]\",\n\t\t\t\t\texpected: ast.ChainExprs(\n\t\t\t\t\t\tast.VariableExpr{Name: \"this\"},\n\t\t\t\t\t\tast.RangeExpr{End: ast.NumberIntExpr{Value: 10}},\n\t\t\t\t\t),\n\t\t\t\t}.run)\n\t\t\t})\n\t\t\tt.Run(\"spread\", func(t *testing.T) {\n\t\t\t\tt.Run(\"standard\", happyTestCase{\n\t\t\t\t\tinput: \"$this...\",\n\t\t\t\t\texpected: ast.ChainExprs(\n\t\t\t\t\t\tast.VariableExpr{Name: \"this\"},\n\t\t\t\t\t\tast.SpreadExpr{},\n\t\t\t\t\t),\n\t\t\t\t}.run)\n\t\t\t\tt.Run(\"brackets\", happyTestCase{\n\t\t\t\t\tinput: \"$this[...]\",\n\t\t\t\t\texpected: ast.ChainExprs(\n\t\t\t\t\t\tast.VariableExpr{Name: \"this\"},\n\t\t\t\t\t\tast.SpreadExpr{},\n\t\t\t\t\t),\n\t\t\t\t}.run)\n\t\t\t})\n\t\t})\n\t\tt.Run(\"property array\", func(t *testing.T) {\n\t\t\tt.Run(\"index\", happyTestCase{\n\t\t\t\tinput: \"foo[1]\",\n\t\t\t\texpected: ast.ChainExprs(\n\t\t\t\t\tast.PropertyExpr{Property: ast.StringExpr{Value: \"foo\"}},\n\t\t\t\t\tast.PropertyExpr{Property: ast.NumberIntExpr{Value: 1}},\n\t\t\t\t),\n\t\t\t}.run)\n\t\t\tt.Run(\"range\", func(t *testing.T) {\n\t\t\t\tt.Run(\"start and end funcs\", happyTestCase{\n\t\t\t\t\tinput: \"foo[calcStart(1):calcEnd()]\",\n\t\t\t\t\texpected: ast.ChainExprs(\n\t\t\t\t\t\tast.PropertyExpr{Property: ast.StringExpr{Value: \"foo\"}},\n\t\t\t\t\t\tast.RangeExpr{\n\t\t\t\t\t\t\tStart: ast.CallExpr{\n\t\t\t\t\t\t\t\tFunction: \"calcStart\",\n\t\t\t\t\t\t\t\tArgs: ast.Expressions{\n\t\t\t\t\t\t\t\t\tast.NumberIntExpr{Value: 1},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tEnd: ast.CallExpr{\n\t\t\t\t\t\t\t\tFunction: \"calcEnd\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t),\n\t\t\t\t}.run)\n\t\t\t\tt.Run(\"start and end\", happyTestCase{\n\t\t\t\t\tinput: \"foo[5:10]\",\n\t\t\t\t\texpected: ast.ChainExprs(\n\t\t\t\t\t\tast.PropertyExpr{Property: ast.StringExpr{Value: \"foo\"}},\n\t\t\t\t\t\tast.RangeExpr{Start: ast.NumberIntExpr{Value: 5}, End: ast.NumberIntExpr{Value: 10}},\n\t\t\t\t\t),\n\t\t\t\t}.run)\n\t\t\t\tt.Run(\"start\", happyTestCase{\n\t\t\t\t\tinput: \"foo[5:]\",\n\t\t\t\t\texpected: ast.ChainExprs(\n\t\t\t\t\t\tast.PropertyExpr{Property: ast.StringExpr{Value: \"foo\"}},\n\t\t\t\t\t\tast.RangeExpr{Start: ast.NumberIntExpr{Value: 5}},\n\t\t\t\t\t),\n\t\t\t\t}.run)\n\t\t\t\tt.Run(\"end\", happyTestCase{\n\t\t\t\t\tinput: \"foo[:10]\",\n\t\t\t\t\texpected: ast.ChainExprs(\n\t\t\t\t\t\tast.PropertyExpr{Property: ast.StringExpr{Value: \"foo\"}},\n\t\t\t\t\t\tast.RangeExpr{End: ast.NumberIntExpr{Value: 10}},\n\t\t\t\t\t),\n\t\t\t\t}.run)\n\t\t\t})\n\t\t\tt.Run(\"spread\", func(t *testing.T) {\n\t\t\t\tt.Run(\"standard\", happyTestCase{\n\t\t\t\t\tinput: \"foo...\",\n\t\t\t\t\texpected: ast.ChainExprs(\n\t\t\t\t\t\tast.PropertyExpr{Property: ast.StringExpr{Value: \"foo\"}},\n\t\t\t\t\t\tast.SpreadExpr{},\n\t\t\t\t\t),\n\t\t\t\t}.run)\n\t\t\t\tt.Run(\"brackets\", happyTestCase{\n\t\t\t\t\tinput: \"foo[...]\",\n\t\t\t\t\texpected: ast.ChainExprs(\n\t\t\t\t\t\tast.PropertyExpr{Property: ast.StringExpr{Value: \"foo\"}},\n\t\t\t\t\t\tast.SpreadExpr{},\n\t\t\t\t\t),\n\t\t\t\t}.run)\n\t\t\t})\n\t\t})\n\t})\n\n\tt.Run(\"map\", func(t *testing.T) {\n\t\tt.Run(\"single property\", happyTestCase{\n\t\t\tinput: \"foo.map(x)\",\n\t\t\texpected: ast.ChainExprs(\n\t\t\t\tast.PropertyExpr{Property: ast.StringExpr{Value: \"foo\"}},\n\t\t\t\tast.MapExpr{\n\t\t\t\t\tExpr: ast.PropertyExpr{Property: ast.StringExpr{Value: \"x\"}},\n\t\t\t\t},\n\t\t\t),\n\t\t}.run)\n\t\tt.Run(\"nested property\", happyTestCase{\n\t\t\tinput: \"foo.map(x.y)\",\n\t\t\texpected: ast.ChainExprs(\n\t\t\t\tast.PropertyExpr{Property: ast.StringExpr{Value: \"foo\"}},\n\t\t\t\tast.MapExpr{\n\t\t\t\t\tExpr: ast.ChainExprs(\n\t\t\t\t\t\tast.PropertyExpr{Property: ast.StringExpr{Value: \"x\"}},\n\t\t\t\t\t\tast.PropertyExpr{Property: ast.StringExpr{Value: \"y\"}},\n\t\t\t\t\t),\n\t\t\t\t},\n\t\t\t),\n\t\t}.run)\n\t})\n\n\tt.Run(\"object\", func(t *testing.T) {\n\t\tt.Run(\"get single property\", happyTestCase{\n\t\t\tinput: \"{foo}\",\n\t\t\texpected: ast.ObjectExpr{Pairs: []ast.KeyValue{\n\t\t\t\t{Key: ast.StringExpr{Value: \"foo\"}, Value: ast.PropertyExpr{Property: ast.StringExpr{Value: \"foo\"}}},\n\t\t\t}},\n\t\t}.run)\n\t\tt.Run(\"get multiple properties\", happyTestCase{\n\t\t\tinput: \"{foo, bar, baz}\",\n\t\t\texpected: ast.ObjectExpr{Pairs: []ast.KeyValue{\n\t\t\t\t{Key: ast.StringExpr{Value: \"foo\"}, Value: ast.PropertyExpr{Property: ast.StringExpr{Value: \"foo\"}}},\n\t\t\t\t{Key: ast.StringExpr{Value: \"bar\"}, Value: ast.PropertyExpr{Property: ast.StringExpr{Value: \"bar\"}}},\n\t\t\t\t{Key: ast.StringExpr{Value: \"baz\"}, Value: ast.PropertyExpr{Property: ast.StringExpr{Value: \"baz\"}}},\n\t\t\t}},\n\t\t}.run)\n\t\tt.Run(\"set single property\", happyTestCase{\n\t\t\tinput: `{\"foo\":1}`,\n\t\t\texpected: ast.ObjectExpr{Pairs: []ast.KeyValue{\n\t\t\t\t{Key: ast.StringExpr{Value: \"foo\"}, Value: ast.NumberIntExpr{Value: 1}},\n\t\t\t}},\n\t\t}.run)\n\t\tt.Run(\"set multiple properties\", happyTestCase{\n\t\t\tinput: `{foo: 1, bar: 2, baz: 3}`,\n\t\t\texpected: ast.ObjectExpr{Pairs: []ast.KeyValue{\n\t\t\t\t{Key: ast.StringExpr{Value: \"foo\"}, Value: ast.NumberIntExpr{Value: 1}},\n\t\t\t\t{Key: ast.StringExpr{Value: \"bar\"}, Value: ast.NumberIntExpr{Value: 2}},\n\t\t\t\t{Key: ast.StringExpr{Value: \"baz\"}, Value: ast.NumberIntExpr{Value: 3}},\n\t\t\t}},\n\t\t}.run)\n\t\tt.Run(\"combine get set\", happyTestCase{\n\t\t\tinput: `{\n\t\t\t\t...,\n\t\t\t\tnestedSpread...,\n\t\t\t\tfoo,\n\t\t\t\tbar: 2,\n\t\t\t\t\"baz\": evalSomething(),\n\t\t\t\t\"Name\": \"Tom\",\n\t\t\t}`,\n\t\t\texpected: ast.ObjectExpr{Pairs: []ast.KeyValue{\n\t\t\t\t{Key: ast.SpreadExpr{}, Value: nil},\n\t\t\t\t{Key: ast.SpreadExpr{}, Value: ast.PropertyExpr{Property: ast.StringExpr{Value: \"nestedSpread\"}}},\n\t\t\t\t{Key: ast.StringExpr{Value: \"foo\"}, Value: ast.PropertyExpr{Property: ast.StringExpr{Value: \"foo\"}}},\n\t\t\t\t{Key: ast.StringExpr{Value: \"bar\"}, Value: ast.NumberIntExpr{Value: 2}},\n\t\t\t\t{Key: ast.StringExpr{Value: \"baz\"}, Value: ast.CallExpr{Function: \"evalSomething\"}},\n\t\t\t\t{Key: ast.StringExpr{Value: \"Name\"}, Value: ast.StringExpr{Value: \"Tom\"}},\n\t\t\t}},\n\t\t}.run)\n\t})\n\n\tt.Run(\"variables\", func(t *testing.T) {\n\t\tt.Run(\"single variable\", happyTestCase{\n\t\t\tinput:    `$foo`,\n\t\t\texpected: ast.VariableExpr{Name: \"foo\"},\n\t\t}.run)\n\t\tt.Run(\"variable passed to func\", happyTestCase{\n\t\t\tinput:    `len($foo)`,\n\t\t\texpected: ast.CallExpr{Function: \"len\", Args: ast.Expressions{ast.VariableExpr{Name: \"foo\"}}},\n\t\t}.run)\n\t})\n\n\tt.Run(\"combinations and grouping\", func(t *testing.T) {\n\t\tt.Run(\"string concat with grouping\", happyTestCase{\n\t\t\tinput: `(foo.a) + (foo.b)`,\n\t\t\texpected: ast.BinaryExpr{\n\t\t\t\tLeft:     ast.ChainExprs(ast.PropertyExpr{Property: ast.StringExpr{Value: \"foo\"}}, ast.PropertyExpr{Property: ast.StringExpr{Value: \"a\"}}),\n\t\t\t\tOperator: lexer.Token{Kind: lexer.Plus, Value: \"+\", Pos: 8, Len: 1},\n\t\t\t\tRight:    ast.ChainExprs(ast.PropertyExpr{Property: ast.StringExpr{Value: \"foo\"}}, ast.PropertyExpr{Property: ast.StringExpr{Value: \"b\"}}),\n\t\t\t},\n\t\t}.run)\n\t\tt.Run(\"string concat with nested properties\", happyTestCase{\n\t\t\tinput: `foo.a + foo.b`,\n\t\t\texpected: ast.BinaryExpr{\n\t\t\t\tLeft:     ast.ChainExprs(ast.PropertyExpr{Property: ast.StringExpr{Value: \"foo\"}}, ast.PropertyExpr{Property: ast.StringExpr{Value: \"a\"}}),\n\t\t\t\tOperator: lexer.Token{Kind: lexer.Plus, Value: \"+\", Pos: 6, Len: 1},\n\t\t\t\tRight:    ast.ChainExprs(ast.PropertyExpr{Property: ast.StringExpr{Value: \"foo\"}}, ast.PropertyExpr{Property: ast.StringExpr{Value: \"b\"}}),\n\t\t\t},\n\t\t}.run)\n\t})\n\n\tt.Run(\"conditional\", func(t *testing.T) {\n\t\tt.Run(\"if\", happyTestCase{\n\t\t\tinput: `if (foo == 1) { \"yes\" } else { \"no\" }`,\n\t\t\texpected: ast.ConditionalExpr{\n\t\t\t\tCond: ast.BinaryExpr{\n\t\t\t\t\tLeft:     ast.PropertyExpr{Property: ast.StringExpr{Value: \"foo\"}},\n\t\t\t\t\tOperator: lexer.Token{Kind: lexer.Equal, Value: \"==\", Pos: 8, Len: 2},\n\t\t\t\t\tRight:    ast.NumberIntExpr{Value: 1},\n\t\t\t\t},\n\t\t\t\tThen: ast.StringExpr{Value: \"yes\"},\n\t\t\t\tElse: ast.StringExpr{Value: \"no\"},\n\t\t\t},\n\t\t}.run)\n\t\tt.Run(\"if elseif else\", happyTestCase{\n\t\t\tinput: `if (foo == 1) { \"yes\" } elseif (foo == 2) { \"maybe\" } else { \"no\" }`,\n\t\t\texpected: ast.ConditionalExpr{\n\t\t\t\tCond: ast.BinaryExpr{\n\t\t\t\t\tLeft:     ast.PropertyExpr{Property: ast.StringExpr{Value: \"foo\"}},\n\t\t\t\t\tOperator: lexer.Token{Kind: lexer.Equal, Value: \"==\", Pos: 8, Len: 2},\n\t\t\t\t\tRight:    ast.NumberIntExpr{Value: 1},\n\t\t\t\t},\n\t\t\t\tThen: ast.StringExpr{Value: \"yes\"},\n\t\t\t\tElse: ast.ConditionalExpr{\n\t\t\t\t\tCond: ast.BinaryExpr{\n\t\t\t\t\t\tLeft:     ast.PropertyExpr{Property: ast.StringExpr{Value: \"foo\"}},\n\t\t\t\t\t\tOperator: lexer.Token{Kind: lexer.Equal, Value: \"==\", Pos: 36, Len: 2},\n\t\t\t\t\t\tRight:    ast.NumberIntExpr{Value: 2},\n\t\t\t\t\t},\n\t\t\t\t\tThen: ast.StringExpr{Value: \"maybe\"},\n\t\t\t\t\tElse: ast.StringExpr{Value: \"no\"},\n\t\t\t\t},\n\t\t\t},\n\t\t}.run)\n\t\tt.Run(\"if elseif elseif else\", happyTestCase{\n\t\t\tinput: `if (foo == 1) { \"yes\" } elseif (foo == 2) { \"maybe\" } elseif (foo == 3) { \"probably\" } else { \"no\" }`,\n\t\t\texpected: ast.ConditionalExpr{\n\t\t\t\tCond: ast.BinaryExpr{\n\t\t\t\t\tLeft:     ast.PropertyExpr{Property: ast.StringExpr{Value: \"foo\"}},\n\t\t\t\t\tOperator: lexer.Token{Kind: lexer.Equal, Value: \"==\", Pos: 8, Len: 2},\n\t\t\t\t\tRight:    ast.NumberIntExpr{Value: 1},\n\t\t\t\t},\n\t\t\t\tThen: ast.StringExpr{Value: \"yes\"},\n\t\t\t\tElse: ast.ConditionalExpr{\n\t\t\t\t\tCond: ast.BinaryExpr{\n\t\t\t\t\t\tLeft:     ast.PropertyExpr{Property: ast.StringExpr{Value: \"foo\"}},\n\t\t\t\t\t\tOperator: lexer.Token{Kind: lexer.Equal, Value: \"==\", Pos: 36, Len: 2},\n\t\t\t\t\t\tRight:    ast.NumberIntExpr{Value: 2},\n\t\t\t\t\t},\n\t\t\t\t\tThen: ast.StringExpr{Value: \"maybe\"},\n\t\t\t\t\tElse: ast.ConditionalExpr{\n\t\t\t\t\t\tCond: ast.BinaryExpr{\n\t\t\t\t\t\t\tLeft:     ast.PropertyExpr{Property: ast.StringExpr{Value: \"foo\"}},\n\t\t\t\t\t\t\tOperator: lexer.Token{Kind: lexer.Equal, Value: \"==\", Pos: 66, Len: 2},\n\t\t\t\t\t\t\tRight:    ast.NumberIntExpr{Value: 3},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tThen: ast.StringExpr{Value: \"probably\"},\n\t\t\t\t\t\tElse: ast.StringExpr{Value: \"no\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}.run)\n\t})\n\n\tt.Run(\"coalesce\", func(t *testing.T) {\n\t\tt.Run(\"chained on left side\", happyTestCase{\n\t\t\tinput: `foo ?? bar ?? baz`,\n\t\t\texpected: ast.BinaryExpr{\n\t\t\t\tLeft: ast.BinaryExpr{\n\t\t\t\t\tLeft:     ast.PropertyExpr{Property: ast.StringExpr{Value: \"foo\"}},\n\t\t\t\t\tOperator: lexer.Token{Kind: lexer.DoubleQuestionMark, Value: \"??\", Pos: 4, Len: 2},\n\t\t\t\t\tRight:    ast.PropertyExpr{Property: ast.StringExpr{Value: \"bar\"}},\n\t\t\t\t},\n\t\t\t\tOperator: lexer.Token{Kind: lexer.DoubleQuestionMark, Value: \"??\", Pos: 11, Len: 2},\n\t\t\t\tRight:    ast.PropertyExpr{Property: ast.StringExpr{Value: \"baz\"}},\n\t\t\t},\n\t\t}.run)\n\n\t\tt.Run(\"chained nested on left side\", happyTestCase{\n\t\t\tinput: `nested.one ?? nested.two ?? nested.three ?? 10`,\n\t\t\texpected: ast.BinaryExpr{\n\t\t\t\tLeft: ast.BinaryExpr{\n\t\t\t\t\tLeft: ast.BinaryExpr{\n\t\t\t\t\t\tLeft: ast.ChainExprs(\n\t\t\t\t\t\t\tast.PropertyExpr{Property: ast.StringExpr{Value: \"nested\"}},\n\t\t\t\t\t\t\tast.PropertyExpr{Property: ast.StringExpr{Value: \"one\"}},\n\t\t\t\t\t\t),\n\t\t\t\t\t\tOperator: lexer.Token{Kind: lexer.DoubleQuestionMark, Value: \"??\", Pos: 11, Len: 2},\n\t\t\t\t\t\tRight: ast.ChainExprs(\n\t\t\t\t\t\t\tast.PropertyExpr{Property: ast.StringExpr{Value: \"nested\"}},\n\t\t\t\t\t\t\tast.PropertyExpr{Property: ast.StringExpr{Value: \"two\"}},\n\t\t\t\t\t\t),\n\t\t\t\t\t},\n\t\t\t\t\tOperator: lexer.Token{Kind: lexer.DoubleQuestionMark, Value: \"??\", Pos: 25, Len: 2},\n\t\t\t\t\tRight: ast.ChainExprs(\n\t\t\t\t\t\tast.PropertyExpr{Property: ast.StringExpr{Value: \"nested\"}},\n\t\t\t\t\t\tast.PropertyExpr{Property: ast.StringExpr{Value: \"three\"}},\n\t\t\t\t\t),\n\t\t\t\t},\n\t\t\t\tOperator: lexer.Token{Kind: lexer.DoubleQuestionMark, Value: \"??\", Pos: 41, Len: 2},\n\t\t\t\tRight:    ast.NumberIntExpr{Value: 10},\n\t\t\t},\n\t\t}.run)\n\t})\n}\n"
  },
  {
    "path": "selector/parser.go",
    "content": "package selector\n\nimport (\n\t\"github.com/tomwright/dasel/v3/selector/ast\"\n\t\"github.com/tomwright/dasel/v3/selector/lexer\"\n\t\"github.com/tomwright/dasel/v3/selector/parser\"\n)\n\nfunc Parse(selector string) (ast.Expr, error) {\n\ttokens, err := lexer.NewTokenizer(selector).Tokenize()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn parser.NewParser(tokens).Parse()\n}\n"
  }
]